/* ==========================================================================
   Eric Gustafson — portfolio
   --bg / --ink are driven by js/site.js: they follow the active project's
   brand color (ink flips black/white for contrast). Outside a project they
   fall back to the "chrome" colors. Light only: no dark mode.
   ========================================================================== */

@property --bg { syntax: "<color>"; inherits: true; initial-value: #ffffff; }
@property --ink { syntax: "<color>"; inherits: true; initial-value: #000000; }
/* Date boxes: the active palette inverted (box = page text, label = page bg) */
@property --date-bg { syntax: "<color>"; inherits: true; initial-value: #000000; }
@property --date-ink { syntax: "<color>"; inherits: true; initial-value: #ffffff; }

:root {
  /* Light mode page grey: matches the hero clip's paper backdrop */
  --chrome-bg: #cdcbce;
  --chrome-ink: #000000;
  --bg: var(--chrome-bg);
  --ink: var(--chrome-ink);
  --date-bg: var(--chrome-ink);
  --date-ink: var(--chrome-bg);
  --muted: color-mix(in srgb, var(--ink) 60%, var(--bg));
  --rule: var(--ink); /* every line is solid ink */

  --font: "Outfit", system-ui, -apple-system, "Segoe UI", sans-serif;
  --pad: 64px;
  --header-h: 96px; /* phones: 60px (max-width: 809px) */
  --about-h: var(--header-h); /* bottom bar matches the header */
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  /* One timing for the page color change and the project media fade */
  --color-shift: 0.7s var(--ease);

  color-scheme: light;
  /* The page blends between project colors. The solid footer button's
     colors (--date-bg/--date-ink) switch instantly instead: blending them
     too would pass the label through its own background color. */
  transition: --bg var(--color-shift), --ink var(--color-shift);
}


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

html {
  scroll-behavior: smooth;
  scrollbar-gutter: stable;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-text-size-adjust: 100%;
}

html.sheet-open { overflow: hidden; }

body {
  font-family: var(--font);
  font-size: 18px;
  font-weight: 300;
  line-height: 1.45;
  background: var(--bg);
  color: var(--ink);
  /* No bottom padding: the sticky about bar takes its own space at the end */
}

a { color: inherit; }
img, video { display: block; max-width: 100%; height: auto; }
button { font: inherit; color: inherit; }

:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

.skip-link {
  position: fixed; top: -100px; left: var(--pad); z-index: 999;
  padding: 10px 14px; background: var(--ink); color: var(--bg);
  font-weight: 600; text-decoration: none;
}
.skip-link:focus { top: 12px; }

/* Lucide icons from the inline sprite. stroke-width is set per size so the
   rendered line stays 1px (24 / rendered px). */
.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.icon {
  display: inline-block; flex: none;
  width: 24px; height: 24px;
  fill: none; stroke: currentColor; stroke-width: 1;
  stroke-linecap: round; stroke-linejoin: round;
  overflow: visible;
}

.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

/* ---------- Header ---------- */

.site-header {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  height: var(--header-h);
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 0 var(--pad);
  background: var(--bg);
}
.site-header::after {
  content: ""; position: absolute; left: var(--pad); right: var(--pad); bottom: 0;
  height: 1px; background: var(--ink);
}
.header-name {
  flex: none;
  /* Interpolates on Outfit's variable wght axis (loaded as 100..900) */
  font-size: 26px; font-weight: 600; letter-spacing: -0.025em; text-decoration: none;
  text-transform: uppercase; /* matches ANTILIMIT in the name ticker */
}

/* Name ticker (site.js): one clipped slot per character, sized to the
   letter it shows. Its reel rolls from the name's letter to the ANTILIMIT
   letter in the scroll's direction, with the same ease as the partner
   slots count, and the slot width eases with it. Letters next in line to roll out in the current
   scroll direction sit dimmed. */
.name-roll { position: relative; display: inline-flex; line-height: 1.25; white-space: nowrap; }
/* Clipped top and bottom only, so a letter can sit a hair wider than its
   tightly spaced slot without losing its edge */
.name-slot {
  display: inline-block; height: 1.25em; clip-path: inset(0 -0.12em);
  transition: width 0.45s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.4s var(--ease);
}
.name-roll.is-down .name-slot:not(.is-rolled),
.name-roll.is-up .name-slot.is-rolled { opacity: 0.45; }
.name-reel { display: block; } /* rolled by site.js (Web Animations) */
.name-reel > span { display: block; height: 1.25em; letter-spacing: 0; } /* spacing lives in the slot widths */
.name-roll.is-instant .name-slot { transition: none; }
.name-measure { position: absolute; left: 0; top: 0; visibility: hidden; white-space: pre; letter-spacing: 0; }

/* Austin time + weather, top right */
.header-meta {
  min-width: 0;
  display: flex; flex-direction: column; align-items: flex-end;
  font-size: 13px; font-weight: 400; line-height: 1.25; letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.header-meta-line {
  display: flex; align-items: center; gap: 0.6em;
  max-width: 100%; overflow: hidden; white-space: nowrap;
}
.header-meta-line > *:not(.sep):not(.icon) { line-height: 1; padding-top: 0.12em; } /* Nudge text to optical center */
/* Thin vertical rule between data points */
.header-meta .sep {
  flex: none; align-self: center;
  width: 1px; height: 0.95em;
  background: var(--muted);
}
.header-meta .js-temp-wrap:not([hidden]) { display: contents; } /* join the line's flex gap */
.header-meta .icon { width: 15px; height: 15px; stroke-width: 1.6; }

/* ---------- Hero (sticky curtain) ---------- */

/* The hero fills the screen down to the fixed about bar, so the stats sit
   just out of view on first load and rise in as soon as the reader scrolls.
   Type, top padding and the loop also scale with viewport height (svh), so
   short laptop screens shrink the whole hero instead of clipping it. */
.hero {
  --hero-h: max(380px, calc(100svh - var(--about-h)));
  position: sticky; top: 0; z-index: 1;
  height: var(--hero-h);
  padding: calc(var(--header-h) + clamp(28px, 6svh, 56px)) var(--pad) 0;
  background: var(--bg);
  /* Visible, not hidden: the loop can run on past the hero's bottom edge
     down to the about bar. Stays visible on both axes; mixing
     overflow-x hidden with overflow-y visible would compute to auto. */
  overflow: visible;
}
/* Clip only at the end of <main>: once the sticky hero is released, the part
   of the loop below the hero would otherwise hang into the inline About and
   add scroll height. clip (not hidden) keeps main from becoming a scroll
   container, so the sticky hero and project bars still work. */
main { overflow: clip; }
/* Backdrop for the loop's blend. The loop runs past the hero box down to
   the about bar; without this, the part below the box had nothing to
   multiply into and showed the clip's white paper (a cream plate under the
   projects header on colored pages). Same --bg as the page, so it never
   shows on its own. */
.hero::before {
  content: ""; position: absolute; z-index: -1; inset: 0 0 auto 0;
  height: max(100%, calc(100svh - var(--about-h)));
  background: var(--bg);
  pointer-events: none;
}
/* The right 40% stays clear so the headline never runs into the loop.
   Padding sits here, not on the loop, so the loop keeps its 9:16 box. */
.hero-copy { position: relative; z-index: 1; padding-right: 40%; }
/* Set by site.js once the opaque stats row starts covering it. Hides
   instantly (so nothing shows through the see-through header row behind
   it) and fades back in only on the way up. */
.hero-headline, .hero-sub { transition: opacity 0.25s var(--ease); }
.hero-headline.is-covered, .hero-sub.is-covered { opacity: 0; transition: none; }
.hero-headline {
  font-size: clamp(34px, min(4.8vw, 7.6svh), 88px);
  font-weight: 300;
  line-height: 1.06;
  letter-spacing: -0.03em;
  max-width: 21ch;
}
/* Smaller second line under the headline */
.hero-sub {
  margin-top: clamp(16px, 2.6svh, 32px);
  font-size: clamp(19px, min(2vw, 3.2svh), 34px);
  font-weight: 300;
  line-height: 1.2;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
/* Portrait column on the right, from the bottom of the fixed header to the
   top of the fixed about bar, so it is taller than the hero (it runs on
   behind the stats, which cover it as the work scrolls up). The
   right edge stops at --pad, in line with the black rules; the gutter stays
   plain --bg. Width follows the height at 9:16, capped so the headline
   stays clear on tall, narrow screens. Fades on both sides into --bg. */
.hero-loop {
  position: absolute; z-index: 0;
  top: var(--header-h); right: var(--pad); bottom: auto; left: auto;
  height: calc(100svh - var(--header-h) - var(--about-h));
  width: auto; max-width: 50%;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  pointer-events: none;
  /* Side fades into the page grey */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 30%, #000 72%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 30%, #000 72%, transparent);
  /* Takes on the page color underneath, so it tints with each project */
  mix-blend-mode: luminosity;
}
/* The 9:16 clip has a wide white margin; the painting sits in source rows
   320 to 1650 of 1920. At 144.4% of the box height (1920 / 1330) those rows
   fill the box exactly: shifted up 24.07% (320 / 1330) the painting meets
   the header at the top and the about bar at the bottom with no empty band.
   Centered, so the sides crop evenly. */
.hero-loop video {
  position: absolute; top: -24.07%; left: 50%;
  height: 144.4%; width: auto; aspect-ratio: 9 / 16; max-width: none;
  transform: translateX(-50%);
}
/* Desktop: the video's own box is the painting. The 1080x1920 clip has
   blank paper above and below the painting (source rows 320 to 1650), so the
   video is sized to the painting's 1080:1330 box and object-fit crops the
   paper (object-position 54.24% = 320 / 590). The box starts flush with the
   bottom of the header and ends on the about bar, so the whole painting shows
   with nothing behind the header. Pulled in from the right edge. The copy
   centers vertically between the header and the hero's bottom edge. */
@media (min-width: 1200px) {
  .hero {
    display: flex; flex-direction: column; justify-content: center;
    padding-top: var(--header-h);
  }
  .hero > .hero-loop {
    --loop-h: calc(100svh - var(--header-h) - var(--about-h));
    right: calc(var(--pad) + 8vw - 0.125 * var(--loop-h));
    width: calc(var(--loop-h) * 1080 / 1330); max-width: 60%;
    aspect-ratio: auto;
  }
  .hero > .hero-loop video {
    top: 0; height: 100%; aspect-ratio: 1080 / 1330;
    object-fit: cover; object-position: 50% 54.24%;
  }
}

/* ---------- Work (slides over the hero) ---------- */

/* No background on .work itself: each section below brings its own opaque
   sheet, so only the projects header row (.work-intro) lets the sticky hero
   show through. The offset shadows paint --bg across the side gutters
   (--pad) without spilling up or down into the header row. */
.work {
  position: relative; z-index: 2;
  padding: 0 var(--pad);
}
.stats, .project, .closing {
  background: var(--bg);
  box-shadow: var(--pad) 0 0 var(--bg), calc(-1 * var(--pad)) 0 0 var(--bg);
}

/* Stats */
.stats { border-top: 1px solid var(--ink); }
.stats-grid {
  list-style: none;
  display: grid; grid-template-columns: repeat(4, 1fr);
}
/* Grid lines between cells only: vertical between columns, horizontal between
   rows. The bottom row's verticals run straight into the first project's rule. */
.stats-grid li {
  padding: 40px 32px 48px;
  border-left: 1px solid var(--rule);
  /* One sentence per stat, one size and weight (regular weight stays narrow;
     its bold widens) */
  font-size: clamp(20px, 1.75vw, 28px); font-weight: 300;
  line-height: 1.2; letter-spacing: -0.015em;
}
.stats-grid li:nth-child(4n + 1) { border-left: 0; padding-left: 0; }
.stats-grid li:nth-child(n + 5) { border-top: 1px solid var(--rule); }
.stats-grid .icon { display: block; width: 38px; height: 38px; stroke-width: 0.632; margin-bottom: 18px; }
.stats-grid strong { font-weight: 500; } /* key figures, a step heavier */
.stats-grid em { font: inherit; }

/* Section header row between the stats and the projects. Opens with the same
   3px rule as the stats; the stats' vertical rules run straight into it.
   Equal space above and below the text (--intro-pad). Transparent on purpose,
   and the right quarter stays empty so the sticky hero loop shows there. */
.work-intro {
  --intro-pad: clamp(112px, 14svh, 192px);
  position: relative;
  border-top: 1px solid var(--ink);
  padding: var(--intro-pad) 25% var(--intro-pad) 0;
}
/* One sentence, same type as .hero-headline at every breakpoint. No
   background plate. */
.work-heading {
  width: fit-content;
  font-size: clamp(34px, min(4.8vw, 7.6svh), 88px);
  font-weight: 300;
  line-height: 1.06;
  letter-spacing: -0.03em;
}

/* Project */
/* Each project opens with a 3px rule in the current text color */
.project { border-top: 1px solid var(--ink); }

/* Desktop (default): the project is a 2-column grid. The year sits sticky in the
   left column only; the media column runs flush to the project's top rule. */
.project {
  display: grid; grid-template-columns: 2fr 3fr; grid-template-rows: auto 1fr;
  column-gap: 64px;
  padding-bottom: 120px;
}
.project-content { display: contents; }
.project-bar {
  grid-column: 1; grid-row: 1; align-self: start;
  position: sticky; top: var(--header-h); z-index: 50;
  display: flex; align-items: center; gap: 14px;
  padding: 16px 0 12px;
  background: var(--bg);
}
.project-info { grid-column: 1; grid-row: 2; align-self: start; margin-top: 12px; }
.project-media { grid-column: 2; grid-row: 1 / span 2; }
.project-year {
  flex: none;
  display: inline-block;
  font-size: 13px; font-weight: 600; line-height: 1;
  letter-spacing: 0.06em; text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.bar-title { display: none; }
@media (min-width: 1200px) {
  .project-bar { padding-bottom: 0; }
  .project-info { margin-top: 24px; }
  .project-year {
    padding: 6px 0 14px;
    font-size: 17px;
    background: var(--bg);
    color: var(--ink);
    border-bottom: 1px solid var(--ink);
  }
}

.project-info {
  position: sticky;
  top: calc(var(--header-h) + 64px);
}
/* Desktop: the sticky copy centers in the space between the year bar
   (54px + 24px gap) and the about bar, never higher than before.
   --info-h is the copy's height, kept current by site.js. */
@media (min-width: 1200px) {
  .project-info {
    top: max(
      calc(var(--header-h) + 78px),
      calc(var(--header-h) + 78px + (100svh - var(--header-h) - 78px - var(--about-h) - var(--info-h, 100svh)) / 2)
    );
  }
}
/* Logo only (name stays in the h2 for screen readers) */
.project-title {
  --logo-h: clamp(96px, 9vw, 144px);
  --logo-s: clamp(88px, 8.5vw, 128px);
  display: flex; align-items: center;
  margin-bottom: 32px;
}
.project-title .mono { font-size: calc(var(--logo-h) * 0.32); }

/* Logos are sized by area, not height. --logo-s is the side of a square
   with the target area. Each logo's --lw (set inline) is sqrt(aspect ratio)
   times an optical trim k, so width = s * k * sqrt(ar) and
   height = s * k / sqrt(ar): wide wordmarks get shorter, square marks taller,
   and every logo covers about the same area (s * k)^2. k nudges dense marks
   down and light ones up. Height follows from aspect-ratio, and max-width
   keeps any logo inside its column. */
.project-title .logo,
.bar-title .logo {
  width: calc(var(--logo-s) * var(--lw, 1.5));
  height: auto; max-width: 100%;
}

/* Logos are drawn as masks in the current text color, so white, black and
   colored marks all stay legible on any background. */
.logo {
  display: inline-block; flex: none;
  height: 1em; aspect-ratio: var(--ar, 1);
  background: currentColor;
  -webkit-mask: var(--logo) center / contain no-repeat;
          mask: var(--logo) center / contain no-repeat;
}
/* Monogram fallback for brands without a logo */
.mono {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  font-size: 0.34em; height: 2.4em; min-width: 2.4em; padding: 0 0.45em;
  border: 1px solid currentColor;
  font-weight: 700; letter-spacing: -0.01em; line-height: 1;
}

.project-description {
  font-size: clamp(18px, 1.45vw, 24px);
  line-height: 1.3;
}

.project-meta { border-top: 1px solid var(--ink); margin-top: 40px; }
.meta-row {
  display: grid; grid-template-columns: 7.5em minmax(0, 1fr); gap: 12px;
  padding: 13px 0;
  border-bottom: 1px solid var(--rule);
  font-size: 16px; line-height: 1.3;
}
.meta-row:last-child { border-bottom: none; }
.meta-row dt { color: var(--muted); }
.meta-row a { text-underline-offset: 3px; }

.project-media { --media-gap: 24px; display: flex; flex-direction: column; gap: var(--media-gap); }
/* The Cube: images 2-8 stack edge to edge as one continuous run */
#the-cube .project-media img:nth-of-type(n+3):nth-of-type(-n+8) { margin-top: calc(-1 * var(--media-gap)); }
/* Apartment Home Living: images 2-5 stack edge to edge */
#apartment-home-living .project-media img:nth-of-type(n+3):nth-of-type(-n+5) { margin-top: calc(-1 * var(--media-gap)); }
.project-media img,
.project-media > video,
.media-tint video,
.media-crop video { display: block; width: 100%; height: auto; }
/* Screen recordings with a stray frame baked into their edges: the video is
   enlarged 4% inside a rounded box that clips it, trimming roughly 1-2% off
   every side (The Cube's orange browser frame) without editing the files. */
.media-crop { overflow: hidden; clip-path: inset(0 round 10px); border-radius: 10px; }
.media-crop video { transform: scale(1.04); }
/* Wraps a video so a color wash can sit on top of it (set --tint) */
.media-tint { position: relative; overflow: hidden; }
.media-tint::after {
  content: ""; position: absolute; inset: 0;
  background: var(--tint, transparent);
  pointer-events: none;
}
/* Only the active project's media is fully shown; the rest fade back to
   faint grayscale. Same timing as the page color change. No blend modes.
   Scoped to .js (set in <head>) so media stays visible without JS. */
.project-media img,
.project-media video {
  opacity: 1; filter: none;
  transition: opacity var(--color-shift), filter var(--color-shift);
}
.js .project:not(.is-active) .project-media img,
.js .project:not(.is-active) .project-media video {
  opacity: 0.15;
  filter: grayscale(1);
}
.project-media .is-device { width: 46%; align-self: center; } /* phone-sized shots and clips */

/* Typographic card for projects without imagery */
.type-card {
  aspect-ratio: 4 / 3;
  display: flex; flex-direction: column; justify-content: space-between;
  padding: clamp(20px, 3vw, 40px);
  border: 1px solid currentColor;
  container-type: inline-size;
}
.type-card-mark {
  font-size: 12.5cqi; /* scales with the card so single words never break */
  font-weight: 700; line-height: 0.92; letter-spacing: -0.045em;
  overflow-wrap: break-word;
}
.type-card-logo { height: clamp(44px, 6vw, 104px); max-width: 100%; }
.type-card-line { font-size: clamp(16px, 1.4vw, 22px); font-weight: 400; line-height: 1.25; }

/* Closing */
.closing {
  border-top: 1px solid var(--ink);
  padding: 120px 0;
}
.closing-heading {
  font-size: clamp(30px, 3.6vw, 60px);
  font-weight: 600; line-height: 1.05; letter-spacing: -0.03em;
}
.closing-heading a { text-decoration-thickness: 0.06em; text-underline-offset: 0.12em; }
.closing-heading a:hover { opacity: 0.6; }

/* ---------- "More about me" bar ---------- */

/* Sticky, not fixed: it sits in the flow right after <main>, so it looks
   fixed to the viewport bottom until the reader reaches the original page
   end, where it lands in place and the inline About follows it. Its height
   must never change (js/site.js measures main + bar to find the page end),
   so the hover lift below is paint-only. */
.about-bar {
  position: sticky; bottom: 0; z-index: 90;
  height: calc(var(--about-h) + env(safe-area-inset-bottom));
  padding: 0 var(--pad) env(safe-area-inset-bottom);
  /* Icons left, both buttons together on the right */
  display: flex; align-items: stretch; justify-content: space-between;
  background: var(--bg); color: var(--ink);
  box-shadow: 0 0 0 var(--bg);
  transition: box-shadow 0.35s var(--ease);
}
.about-bar::before,
.about-bar > * { transition: transform 0.35s var(--ease); }
/* Left: contact icons. 44px targets; the negative margin lines the first
   glyph up with the page gutter (the icon box has empty sides). */
.about-social {
  flex: none;
  display: flex; align-items: center;
  margin-left: -10px;
}
.about-social-link {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;
  transition: opacity 0.2s;
}
.about-social-link .icon { width: 22px; height: 22px; stroke-width: 1.091; } /* 1px line at 22px */
.about-social-link:focus-visible { outline-offset: -2px; }
@media (hover: hover) {
  .about-social-link:hover { opacity: 0.6; }
}
/* Right: About (outline), then the partner link (or "My work & projects"
   on /partner) */
.about-actions {
  flex: none; min-width: 0;
  display: flex; align-items: center; gap: 10px;
  margin-left: 12px;
}
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 40px; padding: 0 16px;
  border: 1px solid var(--ink);
  font: inherit; font-size: 15px; font-weight: 460; letter-spacing: -0.005em;
  line-height: 1; white-space: nowrap; text-decoration: none;
  cursor: pointer;
  /* No color transition: buttons recolor in step with the page when the
     active project changes. Only the hover fade on the solid button. */
  transition: opacity 0.2s;
}
.btn > span { padding-top: 0.1em; } /* optical center */
.btn-outline { background: transparent; color: var(--ink); }
.btn-solid { background: var(--date-bg); color: var(--date-ink); border-color: var(--date-bg); }
@media (hover: hover) {
  .btn-outline:hover { background: color-mix(in srgb, var(--ink) 10%, transparent); }
  .btn-solid:hover { opacity: 0.85; }
}
.btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.label-short { display: none; }
/* Partner link: "Partner with me", swapping to the open count on hover
   (count set in js/slots.js). Both labels share one cell so the button keeps
   the wider label's width and never jumps. */
.slots-labels { display: grid; }
.slots-labels > span { grid-area: 1 / 1; text-align: center; transition: opacity 0.18s var(--ease); }
.slots-avail { opacity: 0; }
@media (hover: hover) {
  .slots-link:hover .slots-label { opacity: 0; }
  .slots-link:hover .slots-avail { opacity: 1; }
}
.slots-link:focus-visible .slots-label { opacity: 0; }
.slots-link:focus-visible .slots-avail { opacity: 1; }
/* One rounded corner, the bottom right, on the bar's last button ("Partner
   with me", or "My work & projects" on /partner): it sits in the screen's
   bottom right corner, so it echoes the device's own rounded corner. Soft on
   tablets, rounder on phones (half the button's height); square on desktop. */
.about-actions > :last-child { border-radius: 0 0 14px 0; }
/* Desktop: square like the rest (laptop and desktop screens have square corners) */
@media (min-width: 1200px) { .about-actions > :last-child { border-radius: 0; } }
/* Arrow after the partner slots label */
.btn-arrow { width: 16px; height: 16px; stroke-width: 1.5; margin-right: -2px; }
/* Inset rule, same as the header's */
.about-bar::before {
  content: ""; position: absolute; left: var(--pad); right: var(--pad); top: 0;
  height: 1px; background: var(--ink);
}

/* ---------- Bottom sheet ---------- */

.sheet-backdrop {
  position: fixed; inset: 0; z-index: 200;
  background: rgb(0 0 0 / 0.5);
  opacity: 0; transition: opacity 0.4s var(--ease);
  cursor: pointer;
}
.sheet-backdrop.is-open { opacity: 1; }

.sheet {
  position: fixed; inset: auto 0 0 0; z-index: 201;
  max-height: min(80svh, 880px);
  display: flex; flex-direction: column;
  background: var(--chrome-bg); color: var(--chrome-ink);
  transform: translateY(100%);
  transition: transform 0.55s var(--ease);
  /* The sheet always uses the default page colors, not the active project's */
  --ink: var(--chrome-ink); --bg: var(--chrome-bg);
  --muted: color-mix(in srgb, var(--chrome-ink) 60%, var(--chrome-bg));
  --rule: var(--chrome-ink);
}
/* display: flex above would otherwise beat [hidden]: keep the closed sheet
   out of the tab order, and make the inline -> sheet switch instant */
.sheet[hidden] { display: none; }
.sheet.is-open { transform: translateY(0); }
.sheet.is-dragging { transition: none; }

.sheet-grab {
  flex: none; display: flex; justify-content: center;
  padding: 10px 0 6px;
  cursor: grab; touch-action: none;
}
.sheet-grab span { width: 44px; height: 4px; background: currentColor; }
.sheet-head {
  flex: none;
  display: flex; justify-content: flex-end; align-items: center;
  padding: 4px var(--pad) 16px;
  touch-action: none;
}
.sheet-close {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 6px; margin: -6px -6px -6px 0;
  border: 0; background: transparent;
  cursor: pointer;
  transition: opacity 0.2s;
}
.sheet-close .icon { width: 24px; height: 24px; }
/* Chevron on touch sizes (it reads with the drag handle); X on desktop */
.sheet-close .icon-x { display: none; }
.sheet-close:hover { opacity: 0.6; }

.sheet-body {
  overflow-y: auto; overscroll-behavior: contain;
  display: grid; grid-template-columns: 1.15fr 1fr; gap: 48px;
  padding: 28px var(--pad) calc(40px + env(safe-area-inset-bottom));
}
.sheet-media { position: sticky; top: 0; align-self: start; }
.sheet-media video {
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  background: #111;
}
.sheet-lede { font-size: clamp(20px, 1.7vw, 27px); font-weight: 400; line-height: 1.28; }
.sheet-media .sheet-lede + .sheet-lede { margin-top: 0.7em; }
.sheet-media .sheet-lede:last-of-type { margin-bottom: 24px; }
/* The right column now opens with the first list, flush with the intro */
.sheet-copy > :first-child h3,
.sheet-copy > [hidden]:first-child + * h3 { margin-top: 0; }
.sheet-copy > p:not(.sheet-lede) { margin-top: 16px; font-size: 17px; color: var(--muted); }

.link-list h3,
.sheet-activity h3 {
  margin: 36px 0 8px;
  font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted);
}
.link-list ul { list-style: none; }
.link-list li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; align-items: baseline;
  padding: 11px 0;
  border-top: 1px solid var(--rule);
}
.link-list a { font-size: 18px; font-weight: 500; text-underline-offset: 3px; }
.link-list a:hover { opacity: 0.6; }
.link-list span { font-size: 14px; color: var(--muted); text-align: right; }
/* One row, two links (Resume: PDF / Plain Text) */
.link-list .link-label { color: inherit; text-align: left; font-size: 18px; font-weight: 500; }
/* Link plus a muted, unlinked note after it (DeviantArt "(the early days)") */
.link-list .link-name { font-size: inherit; color: inherit; text-align: left; }
.link-list .link-note { margin-left: 4px; }
.link-list .link-pair { display: flex; gap: 18px; justify-content: flex-end; color: inherit; }
.link-list .link-pair a { font-size: 15px; font-weight: 500; }

.activity-line {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 0;
  border-top: 1px solid var(--rule); /* no bottom rule: it ends its list */
  font-size: 18px; font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.activity-line .icon { width: 20px; height: 20px; stroke-width: 1.2; } /* 1px line at 20px */
.activity-when { font-weight: 400; color: var(--muted); }

/* ---------- About inline (original page end) ---------- */

/* Same element as the sheet, laid out as a normal page section under the
   bar. It follows the page palette (not the sheet's fixed chrome colors) so
   it reads as part of the page. */
.sheet.is-inline {
  position: relative; inset: auto; z-index: auto;
  max-height: none;
  display: block;
  background: transparent;
  transform: none; transition: none;
  --ink: inherit; --bg: inherit; --muted: inherit; --rule: inherit;
  color: var(--ink);
}
.sheet.is-inline .sheet-grab,
.sheet.is-inline .sheet-head { display: none; } /* the bar is the header row */
.sheet.is-inline .sheet-body {
  overflow: visible;
  padding: 40px var(--pad) calc(120px + env(safe-area-inset-bottom));
}
.sheet.is-inline .sheet-media { top: calc(var(--header-h) + 28px); }
/* Rise in as it's revealed (it's added below the fold, so this plays as the
   reader keeps scrolling into it) */
.sheet.is-inline .sheet-media,
.sheet.is-inline .sheet-copy { animation: about-rise 0.8s var(--ease) both; }
.sheet.is-inline .sheet-copy { animation-delay: 0.08s; }
@keyframes about-rise {
  from { opacity: 0; transform: translateY(32px); }
}

/* ---------- Desktop About sheet ---------- */

/* Centered, capped at 1200px wide. One scroller: the whole body scrolls, so
   the wheel works over the video too, and the video sticks. The copy column
   is clamped to the row and spills into the body's scroll overflow, so the
   body's bottom padding never counts after it; the ::after spacer gives the
   copy the same end padding as the sides instead. The sheet is at least
   64svh tall and grows to fit the left column (--media-h, set by site.js)
   with the side padding above and below the video. The inline About at the
   page end stays full width. */
@media (min-width: 1200px) {
  .sheet:not(.is-inline) {
    max-width: 1200px; margin-inline: auto;
    height: max(64svh, calc(var(--media-h, 0px) + 2 * var(--pad) + env(safe-area-inset-bottom)));
    max-height: calc(100svh - var(--header-h));
  }
  .sheet:not(.is-inline) .sheet-body {
    flex: 1 1 auto; min-height: 0;
    padding-top: var(--pad);
    padding-bottom: calc(var(--pad) + env(safe-area-inset-bottom));
  }
  .sheet:not(.is-inline) .sheet-copy { min-height: 0; }
  .sheet:not(.is-inline) .sheet-copy::after {
    content: ""; display: block;
    height: calc(var(--pad) + env(safe-area-inset-bottom));
  }
  .sheet-grab { display: none; }
  /* X sits in the top right corner, the same distance from the top and
     right edges; the body starts at the sheet's top */
  .sheet-head { position: absolute; top: 20px; right: 20px; z-index: 1; padding: 0; }
  .sheet-close { margin: 0; }
  .sheet-close .icon-chevron { display: none; }
  .sheet-close .icon-x { display: block; }
}

/* ---------- Short desktop screens: un-stick project info ---------- */

@media (min-width: 1200px) and (max-height: 760px) {
  .project-info { position: static; }
}

/* ---------- Tablet + mobile: single column, sticky year + title ---------- */

@media (max-width: 1199px) {
  :root { --pad: 40px; }

  .hero-headline, .work-heading { font-size: clamp(30px, min(5.8vw, 7svh), 64px); }

  .project { display: block; padding-bottom: 0; }
  .project-content { display: grid; }
  .project-info, .project-media { grid-column: auto; grid-row: auto; }
  .project-bar { position: sticky; padding: 10px 0; }
  .project-info { margin-top: 0; }
  /* Small filled year chip only in the sticky bar on smaller screens */
  .project-year { padding: 7px 0 8px; background: var(--bg); color: var(--ink); border-bottom: 1px solid var(--ink); }
  /* Logo on the left, date box on the right */
  .project-bar { justify-content: space-between; }
  /* Fixed row: every project's bar is the same height whatever the logo's
     shape. --bar-row fits the tallest area-sized logo (Tella); logos
     are capped to it and the mask keeps their proportions (no stretch). */
  .bar-title {
    --logo-h: 66px;
    --logo-s: 90px;
    --bar-row: 78px;
    order: -1;
    display: flex; align-items: center; min-width: 0;
    height: var(--bar-row);
  }
  .bar-title .logo {
    max-height: 100%;
    -webkit-mask-position: left center;
            mask-position: left center;
  }
  .bar-title .mono { font-size: 21px; }

  /* The sticky bar carries the title here; keep the h2 for screen readers */
  .project-title {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; margin: 0;
  }

  .project-content { grid-template-columns: 1fr; gap: 40px; margin-top: 28px; padding-bottom: 96px; }
  .project-info { position: static; }
  .project-meta { margin-top: 32px; }

  .stats-grid { grid-template-columns: repeat(2, 1fr); }
  .stats-grid li:nth-child(4n + 1) { border-left: 1px solid var(--rule); padding-left: 24px; }
  .stats-grid li:nth-child(2n + 1) { border-left: 0; padding-left: 0; }
  .stats-grid li:nth-child(n + 3) { border-top: 1px solid var(--rule); }

  .sheet-body { grid-template-columns: 1fr; gap: 28px; }
  .sheet-media { position: static; }
}

/* Very narrow phones: tighten the meta so it never crowds the name */
@media (max-width: 389px) {
  .site-header .header-name { font-size: 17px; }
  .header-meta { letter-spacing: 0; }
  .header-meta-line { gap: 0.45em; }
  /* Bottom bar: slightly smaller targets so icons + opener fit at 320 */
  .about-social { margin-left: -9px; }
  .about-social-link { width: 40px; height: 40px; }
  .about-social-link .icon { width: 20px; height: 20px; stroke-width: 1.2; } /* 1px line at 20px */
}
@media (max-width: 359px) {
  .header-meta .meta-city { display: none; }
  /* 320-wide phones: tighter buttons so both fit beside the two icons */
  .about-actions { gap: 6px; margin-left: 6px; }
  .about-actions .btn { padding: 0 8px; font-size: 13px; gap: 5px; } /* beats the phone .btn rule below */
  .slots-link .btn-arrow { display: none; } /* room for both buttons beside the icons */
  /* 320-wide phones: narrower (still 44px tall) icon targets and a smaller
     opener label so both sides fit without overlapping */
  .about-social { margin-left: -8px; }
  .about-social-link { width: 36px; height: 44px; }
}

@media (max-width: 809px) {
  :root { --pad: 20px; --header-h: 60px; --about-h: 60px; }
  .hide-phone { display: none; }
  .about-actions { gap: 8px; }
  .btn { height: 36px; padding: 0 12px; font-size: 14px; gap: 7px; }
  .label-long { display: none; }
  .label-short { display: inline; }
  /* Phones: no hover count (no hover on touch) */
  .slots-avail { display: none; }
  .about-actions > :last-child { border-radius: 0 0 18px 0; }
  body { font-size: 16px; }

  .header-name { font-size: 19px; }
  .site-header { gap: 12px; }
  .header-meta { font-size: 11px; }
  .header-meta .icon { width: 13px; height: 13px; stroke-width: 1.846; }

  .hero {
    height: calc(100svh - var(--about-h)); /* to the about bar, same as desktop */
    min-height: 500px;
    padding-top: calc(var(--header-h) + 36px);
  }
  .hero-headline, .work-heading { font-size: clamp(30px, 8.8vw, 44px); line-height: 1.08; }
  .hero-sub { margin-top: 14px; font-size: clamp(18px, 5vw, 22px); }
  .hero-copy { padding-right: 0; }
  /* Phones: full-bleed column, header to about bar (same height rule as
     desktop, with the 60px bars). A top fade keeps the headline clean, and
     wide side fades reach full transparency at both screen edges.
     Intersected so each edge fades on its own. */
  .hero-loop {
    left: 0; right: 0; width: auto; max-width: none; aspect-ratio: auto;
    -webkit-mask-image:
      linear-gradient(to bottom, transparent 18%, #000 42%),
      linear-gradient(to right, transparent, #000 26%, #000 74%, transparent);
            mask-image:
      linear-gradient(to bottom, transparent 18%, #000 42%),
      linear-gradient(to right, transparent, #000 26%, #000 74%, transparent);
    -webkit-mask-composite: source-in;
            mask-composite: intersect;
  }
  /* Smaller than desktop so the whole figure fits under the headline:
     centered on the column, painted bottom (source row 1650) on the about
     bar. 111% height, top 4.6% = 100% - 111% * 1650 / 1920. */
  .hero-loop video { top: 4.6%; left: 50%; height: 111%; }

  .stats-grid li { padding: 26px 16px 30px; font-size: 20px; }
  .stats-grid li:nth-child(4n + 1) { padding-left: 16px; }
  .stats-grid li:nth-child(2n + 1) { padding-left: 0; }
  .stats-grid .icon { width: 32px; height: 32px; stroke-width: 0.75; margin-bottom: 12px; }

  /* Full width: no empty right column on phones. A 60% wash of the page
     color keeps the heading readable over the loop; it spans the side
     gutters too. */
  .work-intro {
    --intro-pad: 88px;
    padding-right: 0;
  }
  .work-intro::before {
    content: ""; position: absolute; z-index: -1;
    inset: 0 calc(-1 * var(--pad));
    background: color-mix(in srgb, var(--bg) 60%, transparent);
    pointer-events: none;
  }

  .bar-title { --logo-h: 57px; --logo-s: 78px; --bar-row: 68px; }
  .bar-title .mono { font-size: 18px; }
  .project-year { font-size: 12px; padding: 6px 0 7px; }

  .project-content { gap: 28px; margin-top: 20px; padding-bottom: 72px; }
  .project-description { font-size: 17px; }
  .meta-row { font-size: 14px; padding: 11px 0; }
  .project-media { --media-gap: 16px; }
  .project-media .is-device { width: 64%; }

  .closing { padding: 80px 0; }


  .sheet { max-height: 90svh; }
  .sheet-body { padding-top: 20px; }
  .link-list a, .activity-line, .link-list .link-label { font-size: 17px; }
}

/* ---------- Reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  :root { transition: none; }
  .sheet, .sheet-backdrop { transition-duration: 0.01s; }
  .project-media img, .project-media video { transition: none; }
  .hero-headline, .hero-sub { transition: none; }
  .about-bar, .about-bar::before, .about-bar > * { transition: none; }
  .sheet.is-inline .sheet-media,
  .sheet.is-inline .sheet-copy { animation: none; }
}

/* ---------- Video links + modal ---------- */

/* Several video links in one details row, separated by a dot */
.video-links a + a::before {
  content: "·"; display: inline-block; margin: 0 0.5em;
  text-decoration: none; color: var(--muted);
}
.video-links a { text-underline-offset: 3px; }

.video-modal {
  /* Full screen dark stage; the frame sizes to the video's shape */
  position: fixed; inset: 0;
  width: 100%; height: 100%; max-width: none; max-height: none;
  margin: 0; padding: 0; border: 0;
  background: transparent; color: #fff;
}
.video-modal[open] { display: flex; align-items: center; justify-content: center; }
.video-modal::backdrop { background: rgb(0 0 0 / 0.88); }
.video-modal-frame {
  --ratio: 16 / 9;
  aspect-ratio: var(--ratio);
  /* Largest box of that shape that fits with room for the close button */
  width: min(92vw, calc((100svh - 128px) * (var(--ratio))));
  background: #000;
}
.video-modal-frame video,
.video-modal-frame iframe { display: block; width: 100%; height: 100%; border: 0; }
.video-modal-close {
  position: absolute; top: 20px; right: 20px;
  display: inline-flex; padding: 6px;
  border: 0; background: transparent; color: inherit;
  cursor: pointer; transition: opacity 0.2s;
}
.video-modal-close .icon { width: 28px; height: 28px; }
.video-modal-close:hover { opacity: 0.6; }
html.video-open { overflow: hidden; }
/* Phones: one video link per line */
@media (max-width: 809px) {
  .video-links a { display: table; }
  .video-links a + a { margin-top: 6px; }
  .video-links a + a::before { content: none; }
}

/* Tella video: a 5% wash of Tella blue (#00417D) on top */
#tella .media-tint { --tint: rgb(0 65 125 / 0.05); }

/* Phones and tablets: the About sheet closes by its drag handle (or a tap
   outside), so no chevron in the corner */
@media (max-width: 1199px) {
  .sheet-close { display: none; }
}

/* ---------- Page transitions (main page <-> /partner) ----------
   Same-origin navigations between the two pages morph instead of cutting
   (cross-document view transitions; browsers without support just load the
   page). The header and footer bar hold still, the hero loops fade out and
   in, the hero copy
   slides out one way and in the other, and what sits under the hero fades. site.js adds .vt-hero only
   when the hero is on screen, so a page scrolled deep doesn't fly its
   offscreen hero in. Reverses on the way back. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
.site-header { view-transition-name: site-header; }
.about-bar { view-transition-name: about-bar; }
/* Each page's loop fades on its own (no slide between sides) */
.vt-hero .hero > .hero-loop { view-transition-name: loop-home; }
.vt-hero .fr-visual .hero-loop { view-transition-name: loop-fr; }
.vt-hero .hero-copy { view-transition-name: copy-home; }
.vt-hero .fr-hero-copy { view-transition-name: copy-fr; }
/* What sits under the hero fades out/in on its own layer, above the
   moving video (main page data points, partner first section) */
.vt-hero .stats { view-transition-name: below-home; }
.vt-hero .fr-hero + .fr-section { view-transition-name: below-fr; }

::view-transition-group(*) {
  animation-duration: 0.7s;
  animation-timing-function: cubic-bezier(0.65, 0, 0.25, 1);
}
::view-transition-old(root),
::view-transition-new(root) { animation-duration: 0.4s; }
/* Footer and header just hold; the footer's button swap is a quick fade */
::view-transition-old(about-bar),
::view-transition-new(about-bar) { animation-duration: 0.25s; }
::view-transition-old(below-home),
::view-transition-old(below-fr) { animation: vt-fade-out 0.3s var(--ease) both; }
::view-transition-new(below-home),
::view-transition-new(below-fr) { animation: vt-fade-in 0.45s var(--ease) 0.25s both; }
@keyframes vt-fade-out { to { opacity: 0; } }
@keyframes vt-fade-in { from { opacity: 0; } }
/* Loops and the slots box: quick eased fade out, then in */
::view-transition-old(loop-home),
::view-transition-old(loop-fr),
::view-transition-old(slots-box) { animation: vt-fade-out 0.35s var(--ease) both; }
::view-transition-new(loop-home),
::view-transition-new(loop-fr),
::view-transition-new(slots-box) { animation: vt-fade-in 0.45s var(--ease) 0.15s both; }
/* Stacking during the transition (layers otherwise follow page order):
   videos at the bottom, then what sits under the hero, the hero copy and
   slots box, and the fixed header and footer always on top */
::view-transition-group(loop-home),
::view-transition-group(loop-fr) { z-index: 1; }
::view-transition-group(below-home),
::view-transition-group(below-fr) { z-index: 2; }
::view-transition-group(copy-home),
::view-transition-group(copy-fr),
::view-transition-group(slots-box) { z-index: 3; }
::view-transition-group(site-header),
::view-transition-group(about-bar) { z-index: 10; }
/* Main page copy exits left / returns from the left; partner copy
   enters from the right / exits right */
::view-transition-old(copy-home) { animation: vt-out-left 0.4s var(--ease) both; }
::view-transition-new(copy-home) { animation: vt-in-left 0.6s var(--ease) 0.15s both; }
::view-transition-old(copy-fr) { animation: vt-out-right 0.4s var(--ease) both; }
::view-transition-new(copy-fr) { animation: vt-in-right 0.6s var(--ease) 0.15s both; }
@keyframes vt-out-left { to { opacity: 0; transform: translateX(-48px); } }
@keyframes vt-in-left { from { opacity: 0; transform: translateX(-48px); } }
@keyframes vt-out-right { to { opacity: 0; transform: translateX(48px); } }
@keyframes vt-in-right { from { opacity: 0; transform: translateX(48px); } }
