/* ── Tokens ─────────────────────────────────────────────────
   Colour, type and section rhythm follow karolinahess.com:
   light grey panels stacking over a dark base, oversized display
   type at regular (400) weight on near-solid leading, and an
   accent used on the second line of a heading or on key words.
   Her lime is replaced by the brand rust #EE3124 (the Contact ground).

   Neue Montreal is commercial, so Switzer (Fontshare) stands in —
   the closest free neo-grotesque. Inter is kept for the dock,
   which is what the reference uses there too.
   ──────────────────────────────────────────────────────────── */
:root {
  /* Surfaces — a single dark family, panels separated by tone */
  --paper: #0f0f0f;   /* primary panel   */
  --surface: #141414; /* alternate panel */
  --card: #1a1a1a;    /* raised panels   */
  --base: #0a0a0a;    /* ground / footer */
  --dark: #0a0a0a;

  --accent: #fd2e02;
  --accent-ink: #ffffff;

  /* Ink */
  --head: #fafafa;
  --ink: #fafafa;
  --ink-2: #c2c2c2;
  --muted: #9a9a9a;
  --faint: #7a7a7a;

  /* aliases kept so panel rules read clearly */
  --on-dark: #fafafa;
  --on-dark-muted: #c2c2c2;
  --faint-dark: #7a7a7a;

  --invert: #fafafa;      /* dock pill fill */
  --invert-ink: #0a0a0a;

  --rule: rgba(255, 255, 255, 0.14);
  --rule-strong: rgba(255, 255, 255, 0.3);

  --sans: "Switzer", "Neue Montreal", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
  --ui: "Inter", -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
  --mono: var(--sans);   /* the reference has no mono; labels are tracked Switzer */
  --display: var(--sans);
  --tight: -0.04em;
  --mono-track: 0.12em;

  --page: 1240px;
  --gut: 32px;
  --header-h: 64px;
  --bleed: 40px;

  --r-sm: 8px;
  --r-md: 10px;
  --r-lg: 16px;
  --r-pill: 999px;

  --shadow: 0 0 0 1px rgba(255, 255, 255, 0.07);
  --shadow-lift: 0 0 0 1px rgba(255, 255, 255, 0.16);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink-2);
  font-family: var(--sans);
  font-optical-sizing: auto;
  font-weight: 500;
  font-size: 1rem;           /* 16px body text */
  line-height: 1.5;
  letter-spacing: -0.014em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Uppercase labels: DM Mono, tracked outward — jenn's signature detail. */
.mono, .eyebrow, .case-meta dt, .case-nav .label, .pc-label {
  font-family: var(--mono);
  font-weight: 500;
  letter-spacing: var(--mono-track);
}
/* Functional UI shares the prose face at a heavier weight (600),
   set on each component below rather than here, so later component
   rules can't silently override it. */

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

a { color: inherit; }

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

/* ── Layout ─────────────────────────────────────────────── */
.wrap {
  max-width: var(--page);
  margin: 0 auto;
  padding: 0 var(--gut);
}

.wrap-narrow {
  max-width: 700px;
  margin: 0 auto;
  padding: 0 var(--gut);
}

section { padding: 100px 0; }
section.soft { background: var(--surface); }

/* ── Typography ─────────────────────────────────────────── */
h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 600;
  color: var(--head);
  letter-spacing: var(--tight);
  line-height: 1.1;
  margin: 0;
  text-wrap: balance;
}

/* display sizes carry less weight, matching the reference's optical scale */
h1 { font-size: clamp(2.5rem, 5.8vw, 4rem); font-weight: 500; letter-spacing: -0.034em; line-height: 1.05; }
h2 { font-size: clamp(2rem, 4.2vw, 3.4rem); font-weight: 500; letter-spacing: -0.031em; }
h3 { font-size: clamp(1.4rem, 2.2vw, 1.9rem); font-weight: 600; line-height: 1.1; }

p { margin: 0 0 1.15em; }
p:last-child { margin-bottom: 0; }

.lede {
  font-size: clamp(1.125rem, 1.5vw, 1.3rem);
  font-weight: 500;
  line-height: 1.5;
  color: var(--muted);
  letter-spacing: -0.014em;
}

/* Small caps label. Neutral hairline rule replaces the deck's red bar. */
.eyebrow {
  font-size: 0.78rem;
  font-weight: 500;
  text-transform: uppercase;
  color: var(--head);
  margin: 0 0 18px;
  padding: 8px 14px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.08);
  display: inline-block;
  line-height: 1;
}

/* Centred section headers — crazyui's signature on marketing sections. */
.section-head {
  max-width: 720px;
  margin: 0 auto 64px;
  text-align: center;
}
.section-head p { color: var(--muted); }
.section-head h2 { margin-bottom: 16px; }

/* ── Header ─────────────────────────────────────────────── */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(15, 15, 15, 0.82);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  box-shadow: 0 1px 0 transparent;
  transition: box-shadow 0.25s ease;
}
.site-header.scrolled { box-shadow: 0 1px 0 var(--rule); }

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--header-h);
}

.brand {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  border-radius: var(--r-md);
  transition: opacity 0.2s ease;
}
.brand img {
  width: 32px;
  height: 32px;
  display: block;
}
.brand:hover { opacity: 0.75; }
.brand:focus-visible {
  outline: 2px solid var(--head);
  outline-offset: 3px;
}

.nav { display: flex; gap: 4px; }
.nav a {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  padding: 9px 14px;
  border-radius: var(--r-pill);
  transition: color 0.18s ease, background 0.18s ease;
}
.nav a:hover { color: var(--head); background: rgba(255, 255, 255, 0.08); }
.nav a[aria-current="page"] { color: var(--head); }

/* ── Hero ───────────────────────────────────────────────────
   Modelled on jenncsrubin.com: a full-height stage, an oversized
   greeting whose words stagger in on load, and a circular scroll
   cue that retreats as the page moves.
   ──────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  background: var(--paper);
  min-height: calc(100svh - var(--header-h));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 120px 0 140px;
  text-align: center;
}
.hero .eyebrow { margin-bottom: 28px; }

.hero-title {
  font-size: clamp(2.9rem, 9vw, 8.5rem);
  font-weight: 600;          /* the reference's display weight */
  line-height: 1.02;
  letter-spacing: -0.036em;  /* the reference's display tracking */
  margin: 0 auto 30px;
  max-width: none;
  text-wrap: balance;
}
/* each word is its own animated unit, as on the reference */
.hero-title .w {
  display: inline-block;
  will-change: transform, opacity;
}
.hero .lede {
  max-width: 34ch;
  margin: 0 auto;
}

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin-top: 40px;
  font-size: 0.85rem;
  color: var(--muted);
}
.hero-meta span {
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  border-radius: var(--r-pill);
  background: transparent;
  border: 1px solid var(--rule);
}

/* ── entrance stagger ───────────────────────────────────── */
.js .hero-title .w,
.js .hero .eyebrow,
.js .hero .lede,
.js .hero-meta { opacity: 0; }

.js .hero-ready .eyebrow { animation: rise 0.7s cubic-bezier(0.22, 0.8, 0.28, 1) 0.05s forwards; }
.js .hero-ready .hero-title .w { animation: rise 0.9s cubic-bezier(0.22, 0.8, 0.28, 1) forwards; }
.js .hero-ready .hero-title .w:nth-child(1) { animation-delay: 0.18s; }
.js .hero-ready .hero-title .w:nth-child(2) { animation-delay: 0.30s; }
.js .hero-ready .hero-title .w:nth-child(3) { animation-delay: 0.42s; }
.js .hero-ready .lede { animation: rise 0.8s cubic-bezier(0.22, 0.8, 0.28, 1) 0.60s forwards; }
.js .hero-ready .hero-meta { animation: rise 0.8s cubic-bezier(0.22, 0.8, 0.28, 1) 0.72s forwards; }

@keyframes rise {
  from { opacity: 0; transform: translateY(14px) scale(0.96); }
  to   { opacity: 1; transform: none; }
}

/* ── circular scroll cue ────────────────────────────────── */
.scroll-cue {
  position: absolute;
  /* Centred with auto margins, not translateX(-50%): the `rise`
     entrance animation ends on `transform: none` and fill-mode
     forwards makes that stick, which would wipe out a centering
     transform and shove the ring half its width off-centre. */
  left: 0;
  right: 0;
  bottom: 46px;
  width: fit-content;
  margin-inline: auto;
  text-decoration: none;
  color: var(--head);
  opacity: 0;
  transition: opacity 0.45s ease;
}
.js .hero-ready .scroll-cue { animation: rise 0.9s cubic-bezier(0.22, 0.8, 0.28, 1) 0.95s forwards; }
html:not(.js) .scroll-cue { opacity: 1; }

/* The entrance animation owns .scroll-cue's opacity (animation-fill-mode
   wins over plain declarations), so the scroll fade lives on the ring. */
.scroll-cue-ring { opacity: calc(1 - var(--scrolled, 0)); }

.scroll-cue-ring {
  width: 78px;
  height: 78px;
  border-radius: 50%;
  border: 1px solid var(--rule-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  animation: bob 2.6s ease-in-out infinite;
  transition: background 0.3s ease, border-color 0.3s ease;
}
.scroll-cue:hover .scroll-cue-ring {
  background: var(--head);
  border-color: var(--head);
}
.scroll-cue-label {
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.02em;
  color: var(--muted);
  transition: color 0.3s ease;
}
.scroll-cue:hover .scroll-cue-label { color: var(--invert-ink); }

@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(7px); }
}

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

@media (prefers-reduced-motion: reduce) {
  .js .hero-title .w,
  .js .hero .eyebrow,
  .js .hero .lede,
  .js .hero-meta,
  .scroll-cue { opacity: 1; animation: none; }
  .scroll-cue-ring { animation: none; }
}

/* ── Work gallery ───────────────────────────────────────────
   Two-up grid after gustavoh.de: square-cornered 16:10 media,
   20px gutters, and a two-line caption — title in white over a
   grey category line, both 16px/500. No numbering, description
   or metrics; those live on the case-study pages.
   ──────────────────────────────────────────────────────────── */
#work > .wrap { max-width: none; padding: 0 var(--bleed); }

#work .section-head {
  max-width: 720px;
  margin: 0 auto 64px;
  padding: 0 var(--gut);
}

.work-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 56px 20px;
}

.card {
  display: block;
  text-decoration: none;
  color: inherit;
}

.card-media {
  width: 100%;
  overflow: hidden;
  background: var(--surface);
  margin-bottom: 20px;
}
.card-media img {
  width: 100%;
  aspect-ratio: 16 / 10;     /* the reference's 1.619 */
  object-fit: cover;
  object-position: center;
  transition: transform 0.7s cubic-bezier(0.22, 0.8, 0.28, 1),
              opacity 0.4s ease;
}
.card:hover .card-media img { transform: scale(1.03); }

/* caption: two 16px/500 lines, 4px apart */
.card h3 {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--head);
  margin: 0 0 2px;
  max-width: none;
  transition: opacity 0.25s ease;
}
.card .card-tag {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0;
}
.card:hover h3 { opacity: 0.7; }

@media (prefers-reduced-motion: reduce) {
  .card-media img, .card:hover .card-media img { transition: none; transform: none; }
}

/* ── About ──────────────────────────────────────────────── */
.about-grid {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: 64px;
}
.about-grid h2 { margin-bottom: 20px; }
.about-grid p { color: var(--muted); }

.skill-list {
  list-style: none;
  margin: 32px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px 28px;
}
.skill-list li {
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.9rem;
  color: var(--ink-2);
}

/* ── Case study hero ────────────────────────────────────── */
.case-hero { padding: 100px 0 56px; }
.case-hero h1 { max-width: 17ch; margin: 0 0 24px; }
.case-hero .lede { max-width: 64ch; }

.case-meta {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 32px;
  margin-top: 56px;
  padding-top: 32px;
  border-top: 1px solid var(--rule);
}
.case-meta dt {
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 8px;
}
.case-meta dd {
  margin: 0;
  font-size: 0.9rem;
  color: var(--ink-2);
  line-height: 1.55;
}

.case-cover img {
  width: 100%;
  height: auto;
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
}

/* ── Case study body ────────────────────────────────────── */
.case-section { padding: 88px 0; }
.case-section > .wrap > .eyebrow { margin-bottom: 16px; }
.case-section h2 { font-size: 2.5rem; max-width: none; margin-bottom: 20px; }
.case-section .intro {
  max-width: 68ch;
  color: var(--muted);
  margin-bottom: 44px;
}

figure { margin: 44px 0 0; }
figure img {
  width: 100%;
  /* height:auto lets the width/height attributes give the box an
     aspect-ratio, so the space is reserved before the file loads.
     Without it a lazy image occupied 0px until it arrived, and any
     scroll started before that landed short. */
  height: auto;
  border-radius: var(--r-md);
  box-shadow: var(--shadow);
  cursor: zoom-in;
}
/* These read as captions but they are not labels — each one explains
   the image above it, so they are set as body text: the same size,
   weight, leading and colour as the section prose, just placed under
   a figure. 13px in --faint made explanation look like a credit. */
figcaption {
  margin-top: 20px;
  font-family: var(--sans);
  /* no font-size: the section prose does not set one either, it just
     inherits the body's. Declaring a clamp here made captions 20px
     against the body's 18px — larger than the text they support. */
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
  color: var(--muted);
  max-width: 68ch;          /* the same measure as .case-section .intro */
}

.figure-pair {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 32px;
  margin-top: 44px;
}
.figure-pair figure { margin: 0; }

/* Findings / options / pros-cons */
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px; }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 32px; }

.point {
  background: var(--card);
  border-radius: var(--r-md);
  padding: 28px;
  box-shadow: var(--shadow);
}
.point h3 { font-size: 1rem; margin-bottom: 10px; }
.point p { font-size: 1rem; color: var(--muted); margin: 0; }

.metrics {
  background: var(--card);
  border-radius: var(--r-md);
  padding: 28px 32px;
  box-shadow: var(--shadow);
  max-width: 640px;
}
.metrics h3 { font-size: 1.15rem; margin-bottom: 14px; }
.metrics ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 8px;
}
.metrics li {
  font-size: 0.925rem;
  color: var(--muted);
  padding-left: 20px;
  position: relative;
}
.metrics li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--rule-strong);
}

.option {
  padding: 32px;
  background: var(--card);
  border-radius: var(--r-md);
  box-shadow: var(--shadow);
}
/* card titles sit at 16px, level with the card body text; this
   one set no size and inherited the generic h3 at 30.4px */
.option h3 { font-size: 1rem; margin-bottom: 8px; }
.option > p { color: var(--muted); font-size: 1rem; }
.option ul { margin: 0 0 4px; padding-left: 18px; }
.option li { font-size: 1rem; color: var(--ink-2); margin-bottom: 6px; }
.option .pc-label {
  /* was 0.68rem (10.88px) — the smallest text on the site, and a size
     used nowhere else. Now the side-nav size, so the label register is
     one value instead of two. */
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: normal;
  text-transform: none;
  margin: 20px 0 8px;
  color: var(--faint);
}
.pc-label.pro { color: #7fc7a0; }
.pc-label.con { color: #d79b90; }

/* Quotes */
.quote {
  border-left: 1px solid var(--rule-strong);
  padding-left: 26px;
  margin: 0;
}
.quote p {
  font-family: var(--display);
  font-size: 1.25rem;
  font-weight: 300;
  line-height: 1.45;
  letter-spacing: -0.02em;
  color: var(--head);
}
.quote cite {
  font-style: normal;
  font-size: 0.82rem;
  color: var(--faint);
}

/* Stats band */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 40px;
  padding-bottom: 48px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 48px;
}
.stat b {
  display: block;
  font-family: var(--display);
  font-size: 2.25rem;
  font-weight: 400;
  color: var(--head);
  letter-spacing: -0.04em;
  line-height: 1;
  margin-bottom: 12px;
}
.stat span { font-size: 0.925rem; color: var(--muted); }

/* Process steps */
.steps {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 44px;
  padding: 0;
}
.steps li {
  list-style: none;
  font-size: 0.8rem;
  font-weight: 400;
  padding: 8px 16px;
  border-radius: var(--r-pill);
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--muted);
}

/* Prev/next */
.case-nav {
  border-top: 1px solid var(--rule);
  padding: 56px 0 100px;
}
.case-nav a {
  text-decoration: none;
  display: inline-flex;
  flex-direction: column;
  gap: 8px;
}
.case-nav .label {
  font-size: 0.7rem;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--faint);
}
.case-nav .title {
  font-family: var(--display);
  font-size: 1.45rem;
  font-weight: 400;
  color: var(--head);
  letter-spacing: -0.03em;
  transition: opacity 0.2s ease;
}
.case-nav a:hover .title { opacity: 0.6; }

/* ── Contact ────────────────────────────────────────────── */
.contact { text-align: center; }
.contact > .wrap > div { margin: 0 auto; }
.contact h2 { margin-bottom: 20px; }
.contact-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 36px;
}
.btn {
  display: inline-block;
  padding: 13px 26px;
  border-radius: var(--r-pill);
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  border: 1px solid transparent;
  transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.btn:hover { transform: translateY(-1px); }
.btn-primary { background: var(--invert); color: var(--invert-ink); }
.btn-primary:hover { background: #d9d9d9; }
.btn-ghost {
  background: transparent;
  color: var(--head);
  border-color: var(--rule);
}
.btn-ghost:hover { background: rgba(255, 255, 255, 0.10); }


/* ── Lightbox ───────────────────────────────────────────── */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(10, 10, 10, 0.94);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.lightbox.open { opacity: 1; pointer-events: auto; }
.lightbox img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--r-sm);
}
.lightbox button {
  position: absolute;
  top: 20px;
  right: 24px;
  background: none;
  border: 0;
  color: #fff;
  font-size: 2rem;
  line-height: 1;
  cursor: pointer;
  padding: 8px;
}

/* ── Reveal on scroll ───────────────────────────────────── */
.js .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.2, 0.7, 0.3, 1);
}
.reveal.visible { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* ── Responsive ─────────────────────────────────────────── */
@media (max-width: 900px) {
  /* the 2-up grid collapses to a single column */
  .work-list { grid-template-columns: 1fr; gap: 44px; }
  .about-grid { grid-template-columns: 1fr; gap: 36px; }
  .case-meta { grid-template-columns: repeat(2, 1fr); gap: 26px; }
  .grid-3, .grid-2, .figure-pair { grid-template-columns: 1fr; gap: 24px; }
}

@media (max-width: 620px) {
  :root { --gut: 22px; }
  section, .case-section { padding: 68px 0; }
  .hero { padding: 92px 0 68px; }
  .section-head { margin-bottom: 44px; }
  .nav { gap: 0; }
  .nav a { font-size: 0.85rem; padding: 8px 10px; }
  .skill-list { grid-template-columns: 1fr; }
  .work-list { gap: 56px; }
  :root { --bleed: 22px; }
}

/* ── Karolina Hess interaction layer ────────────────────────
   Three mechanics from karolinahess.com:
   1. sections as sticky panels with rounded tops that stack
      over one another as you scroll;
   2. navigation carried in the header,
      with the active section tracked by a sliding highlight;
   3. the stack ends on the About panel — there is no footer.
   ──────────────────────────────────────────────────────────── */

/* ── 1. stacking panels ─────────────────────────────────── */
.stack {
  position: relative;
  z-index: 1;
  /* no footer to reveal any more */
  margin-bottom: 0;
}

.panel {
  position: sticky;
  top: var(--header-h);
  background: var(--paper);
  border-top: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 26px 26px 0 0;
  /* lifts each panel off the one it covers */
  box-shadow: 0 -24px 60px rgba(0, 0, 0, 0.85);
}
.panel.soft { background: var(--surface); }
/* the hero sits at the bottom of the stack, nothing to lift off */
.hero.panel {
  border-top: 0;
  border-radius: 0;
  box-shadow: none;
}

/* ── portrait ───────────────────────────────────────────── */
.portrait {
  margin: 32px 0 0;
  max-width: 260px;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--surface);
}
.portrait img {
  width: 100%;
  height: auto;
  display: block;
  filter: grayscale(1) contrast(1.05);
  transition: filter 0.5s ease, transform 0.7s cubic-bezier(0.22, 0.8, 0.28, 1);
}
.portrait:hover img { filter: none; transform: scale(1.03); }
/* JS hides this if the file is not present yet */
.portrait[hidden] { display: none; }

/* work media settles as it scrolls in, as on the reference */
.js .card .card-media img { transform: scale(1.05); }
.js .card.visible .card-media img { transform: scale(1); }

@media (prefers-reduced-motion: reduce) {
  .js .card .card-media img,
  .js .card.visible .card-media img { transform: none; }
}

@media (max-width: 620px) {
  .portrait { max-width: 180px; }
}

/* ── karolinahess layer ─────────────────────────────────────
   Display type at regular weight, near-solid leading and heavy
   negative tracking; light panels alternating with dark ones;
   the accent carrying the closing word of a heading.
   ──────────────────────────────────────────────────────────── */
body {
  background: var(--base);
  color: var(--ink-2);
  font-family: var(--sans);
  font-weight: 400;
  font-size: 1rem;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 400;              /* the reference never bolds display type */
  color: var(--head);
  letter-spacing: var(--tight);
}
h1 { font-size: clamp(3rem, 10vw, 8.75rem); line-height: 0.88; font-weight: 400; }
h2 { font-size: clamp(2.25rem, 5.4vw, 4.4rem); line-height: 1.05; font-weight: 400; }
h3 { font-size: clamp(1.35rem, 2vw, 1.75rem); line-height: 1.15; font-weight: 400; }

.accent { color: var(--accent); }

.lede {
  font-size: clamp(1rem, 1.3vw, 1.125rem);
  font-weight: 400;
  line-height: 1.3;
  color: var(--muted);
  letter-spacing: -0.01em;
}

/* the reference's mid-scale statement line */
.statement {
  font-size: clamp(1.6rem, 3.4vw, 3rem);
  line-height: 1.1;
  letter-spacing: var(--tight);
  color: var(--on-dark);
  margin: 0 0 28px;
}

/* ── panels ─────────────────────────────────────────────── */
.panel { background: var(--paper); border-top-color: rgba(255, 255, 255, 0.1); }
.panel.soft { background: var(--surface); }
.panel.is-dark {
  background: var(--base);
  color: var(--on-dark-muted);
  border-top-color: rgba(255, 255, 255, 0.12);
}
.panel.is-dark h1, .panel.is-dark h2, .panel.is-dark h3 { color: var(--on-dark); }
.panel.is-dark p, .panel.is-dark .lede { color: var(--on-dark-muted); }
.panel.is-dark .eyebrow { background: rgba(255, 255, 255, 0.1); color: var(--on-dark); }
.panel.is-dark .skill-list li { color: var(--on-dark-muted); border-bottom-color: rgba(255, 255, 255, 0.14); }
.hero.panel { background: var(--paper); }

/* ── hero ───────────────────────────────────────────────── */
.hero-title {
  font-size: clamp(3rem, 10vw, 8.75rem);
  font-weight: 400;
  line-height: 0.88;
  letter-spacing: var(--tight);
}
.hero .lede { max-width: 40ch; color: var(--muted); }
.hero-meta span {
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--muted);
}
.eyebrow { background: rgba(255, 255, 255, 0.09); color: var(--head); }

/* ── section heads sit left, as on the reference ────────── */
.section-head {
  max-width: 900px;
  margin: 0 0 56px;
  text-align: left;
}
#work .section-head { margin-left: 0; padding: 0; }
.section-head h2 { margin-bottom: 18px; }
.section-head p { color: var(--muted); max-width: 52ch; }

/* ── work cards on light ────────────────────────────────── */
.card-media { background: var(--surface); }
.card h3 { color: var(--head); }
.card .card-tag { color: var(--faint); }
.card:hover h3 { color: var(--accent); opacity: 1; }

/* ── contact ────────────────────────────────────────────── */
.contact { text-align: left; }
.contact > .wrap > div { margin: 0; max-width: 900px; }
.contact-links { justify-content: flex-start; }
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: #d92701; }
.btn-ghost {
  background: transparent;
  color: var(--on-dark);
  border-color: rgba(255, 255, 255, 0.28);
}
.btn-ghost:hover { background: rgba(255, 255, 255, 0.12); }

/* ── chrome on a light ground ───────────────────────────── */
.site-header { background: rgba(15, 15, 15, 0.82); }
.site-header.scrolled { box-shadow: 0 1px 0 rgba(255, 255, 255, 0.12); }
.nav a { color: var(--muted); }
.nav a:hover { color: var(--head); background: rgba(255, 255, 255, 0.09); }

.scroll-cue-ring { border-color: var(--rule-strong); }
.scroll-cue-label { color: var(--muted); }
.scroll-cue:hover .scroll-cue-ring { background: var(--accent); border-color: var(--accent); }
.scroll-cue:hover .scroll-cue-label { color: #fff; }


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

/* portrait sits on the dark about panel */
.panel.is-dark .portrait { background: rgba(255, 255, 255, 0.06); }

@media (max-width: 900px) {
  .section-head { margin-bottom: 40px; }
}

/* The header used to retreat past the hero so it would not share
   the screen with the floating dock. The dock is gone and the
   header is now the only navigation, so it stays for the whole
   page and only its underline reacts to scrolling. */
.site-header { transition: box-shadow 0.25s ease; }
@media (prefers-reduced-motion: reduce) {
  .site-header { transition: none; }
}

/* ── Dark theme + hero portrait + staggered works ───────────
   Every panel goes dark; the hero pairs the portrait with the
   reference's oversized copy; the works section drops the grid
   for her staggered collage — items alternating sides at
   varying widths, one bleeding past the left edge.
   ──────────────────────────────────────────────────────────── */

.panel { background: var(--paper); border-top-color: rgba(255, 255, 255, 0.1); }
.panel.soft { background: var(--surface); }
.panel.is-dark { background: var(--base); }
.hero.panel { background: var(--paper); }
.panel.is-dark .eyebrow,
.eyebrow { background: rgba(255, 255, 255, 0.09); color: var(--head); }

.site-header { background: rgba(15, 15, 15, 0.82); }
.site-header.scrolled { box-shadow: 0 1px 0 rgba(255, 255, 255, 0.12); }
.nav a { color: var(--muted); }
.nav a:hover { color: var(--head); background: rgba(255, 255, 255, 0.09); }

.hero-meta span { border-color: var(--rule); color: var(--muted); background: transparent; }
.card-media { background: var(--surface); }
.btn-ghost { color: var(--head); border-color: rgba(255, 255, 255, 0.3); }
.btn-ghost:hover { background: rgba(255, 255, 255, 0.12); }
.scroll-cue-ring { border-color: var(--rule-strong); }
.scroll-cue-label { color: var(--muted); }
.skill-list li { color: var(--ink-2); border-bottom-color: var(--rule); }
.statement { color: var(--head); }

/* ── hero: copy beside the portrait ─────────────────────── */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: 64px;
  align-items: center;
  width: 100%;
}
.hero { text-align: left; }
.hero-copy { min-width: 0; }
.hero-title { margin: 0 0 24px; max-width: none; }
.hero .lede { margin: 0; max-width: 34ch; }
.hero-meta { justify-content: flex-start; }

.portrait {
  margin: 0;
  max-width: none;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--surface);
  align-self: stretch;
  max-height: 62vh;
}
.portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 22%;
  filter: grayscale(1) contrast(1.06);
  transition: filter 0.6s ease, transform 0.9s cubic-bezier(0.22, 0.8, 0.28, 1);
}
.portrait:hover img { filter: none; transform: scale(1.03); }
/* if the file is not in place yet the frame removes itself */
.portrait[hidden] { display: none; }
.hero-grid:has(.portrait[hidden]) { grid-template-columns: 1fr; }

/* ── works: staggered collage ───────────────────────────── */
#work .section-head { position: relative; max-width: none; }
.work-count {
  position: absolute;
  right: 0;
  top: 6px;
  font-family: var(--ui);
  font-size: 0.875rem;
  color: var(--faint);
}

.work-list {
  display: block;          /* replaces the 2-up grid */
  margin-top: 8px;
}

.card {
  display: block;
  position: relative;
  margin-bottom: 128px;
}
.card:last-child { margin-bottom: 0; }

/* alternating widths and offsets, following the reference's rhythm */
.card:nth-child(1) { width: 58%; margin-left: -2%; }
.card:nth-child(2) { width: 40%; margin-left: 58%; }
.card:nth-child(3) { width: 48%; margin-left: 22%; }

.card-media { border-radius: 0; overflow: hidden; }
.card-media img {
  width: 100%;
  aspect-ratio: auto;
  height: auto;
  object-fit: cover;
  transition: transform 0.9s cubic-bezier(0.22, 0.8, 0.28, 1);
}
.card:hover .card-media img { transform: scale(1.04); }

/* title left, category in a second column — her label pattern */
.card-meta {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
  margin-top: 20px;
}
.card .card-meta h3 {
  font-size: 1.125rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: -0.01em;
  color: var(--head);
  margin: 0;
  transition: color 0.3s ease;
}
.card .card-meta .card-tag {
  font-family: var(--sans);
  font-size: 1.125rem;
  font-weight: 400;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--muted);
  margin: 0;
}
.card:hover .card-meta h3 { color: var(--accent); }

/* each item rises as it enters, staggered like the reference */
.js .card.reveal { transform: translateY(56px); }
.js .card.reveal.visible { transform: none; }

@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; gap: 36px; }
  .portrait { max-height: 46vh; }
  .card:nth-child(1),
  .card:nth-child(2),
  .card:nth-child(3) { width: 100%; margin-left: 0; }
  .card { margin-bottom: 72px; }
  .card-meta { grid-template-columns: 1fr; gap: 4px; }
  .work-count { position: static; display: block; margin-top: 10px; }
}

/* ── hero: centred, with the reference's circular portrait ──
   karolinahess sets her photo as a 120px circle sitting between
   the heading and the copy, not as a large portrait panel.
   ──────────────────────────────────────────────────────────── */
.hero { text-align: center; }
.hero-copy { display: block; }
.hero-title { margin: 0 auto 34px; max-width: 18ch; }
.hero .lede { margin: 0 auto; max-width: 44ch; }
.hero-meta { justify-content: center; }

.portrait {
  width: 140px;
  height: 140px;
  margin: 0 auto 30px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--rule);
  max-width: none;
  max-height: none;
}
.portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 18%;   /* keeps the face centred in the circle */
  filter: grayscale(1) contrast(1.06);
  transition: filter 0.5s ease, transform 0.8s cubic-bezier(0.22, 0.8, 0.28, 1);
}
.portrait:hover img { filter: none; transform: scale(1.06); }
.portrait[hidden] { display: none; }

@media (max-width: 900px) {
  .portrait { width: 110px; height: 110px; margin-bottom: 24px; }
}

/* ── About closes with the contact call ─────────────────────
   One section rather than two: the About column ends, a hairline
   separates, and the closing call sits beneath it — so the page
   reads "here's who I am, now let's talk" instead of stopping
   and starting again.
   ──────────────────────────────────────────────────────────── */
.section-close {
  margin-top: 96px;
  padding-top: 64px;
  border-top: 1px solid var(--rule);
  max-width: 900px;
}
.section-close h2 { margin: 0 0 20px; }
.section-close .lede { margin: 0; max-width: 46ch; }
.section-close .contact-links { justify-content: flex-start; margin-top: 36px; }

@media (max-width: 900px) {
  .section-close { margin-top: 64px; padding-top: 44px; }
}

/* ── About column carries the contact call ──────────────────
   The closing block is gone; the buttons sit under the name,
   which also fills the left column's dead space.
   ──────────────────────────────────────────────────────────── */
.about-grid .contact-links {
  justify-content: flex-start;
  margin-top: 36px;
}

@media (max-width: 900px) {
  .about-grid .contact-links { margin-top: 28px; }
}

/* ── hero: copy left, photograph right ──────────────────────
   The portrait becomes the hero image rather than an avatar,
   keeping the reference's left-aligned type, generous gutter
   and 20px corner radius — on the dark ground.
   ──────────────────────────────────────────────────────────── */
.hero { text-align: left; }
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr);
  gap: 72px;
  align-items: center;
  width: 100%;
}
.hero-copy { min-width: 0; }
.hero-title { margin: 0 0 26px; max-width: 14ch; }
.hero .lede { margin: 0; max-width: 36ch; }
.hero-meta { justify-content: flex-start; margin-top: 34px; }

.portrait {
  width: 100%;
  height: min(66vh, 620px);
  margin: 0;
  border-radius: 20px;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--rule);
  max-width: none;
  max-height: none;
}
.portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 20%;   /* holds the face in the upper third */
  /* left in colour: the reference applies no filter, and the warm
     backlight is the character of this photograph */
  transition: transform 0.9s cubic-bezier(0.22, 0.8, 0.28, 1);
}
.portrait:hover img { transform: scale(1.03); }
.portrait[hidden] { display: none; }
/* if the photo is missing the copy simply takes the full width */
.hero-grid:has(.portrait[hidden]) { grid-template-columns: 1fr; }

@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; gap: 40px; }
  .hero-title { max-width: none; }
  .portrait { height: 52vh; }
}

/* ── Full-page sections, no overlap ─────────────────────────
   The panels were sticky, so each one slid over the previous —
   that overlap is what the stacking effect was. Dropping sticky
   puts them back in normal flow: every section now claims at
   least a full viewport and nothing covers anything else.

   Snapping is `proximity`, not `mandatory`: the work section is
   taller than the screen, and mandatory snap on an over-tall
   section fights the user on every scroll.
   ──────────────────────────────────────────────────────────── */
/* No scroll-snap: the works section is a long pinned sequence, and a
   snap point on the following section yanks the reader out of it
   mid-way. Sections are full-height on their own. */

.panel {
  position: static;          /* was sticky — the source of the overlap */
  /* clear the offset the sticky rule left behind: .work-scroll turns
     positioning back on, and a stale `top` would shift that section
     64px out of its own box */
  top: auto;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  border-radius: 0;
  box-shadow: none;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  scroll-margin-top: var(--header-h);
}
/* the first screen already subtracts the header bar */
.hero.panel {
  min-height: calc(100svh - var(--header-h));
  border-top: 0;
}


/* ── Recent works: pinned horizontal gallery ────────────────
   As on karolinahess.com, the section pins for the length of a
   tall scroll track and the projects travel sideways across the
   screen, passing over a large centred heading. The section is
   300vh of scroll but only ever occupies one screen.
   ──────────────────────────────────────────────────────────── */
.work-scroll {
  display: block;          /* not the centred flex column of other panels */
  height: 300vh;
  min-height: 0;
  padding: 0;
  position: relative;
}

.work-pin {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  display: flex;
  align-items: center;
}

/* the heading the projects slide over */
.work-title {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  font-size: clamp(2.5rem, 7vw, 6.5rem);
  font-weight: 400;
  letter-spacing: var(--tight);
  color: var(--head);
  pointer-events: none;
  z-index: 0;
  will-change: opacity, transform;
}
.js .work-title { opacity: 0; }
.js .work-track { opacity: 0; }

.work-track {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  /* the three projects sit together as one screenful and fade in;
     they no longer travel sideways */
  width: 100%;
  justify-content: center;
  gap: 2.4vw;
  padding: 0 4vw;
  will-change: opacity, transform;
}

.work-scroll .card {
  flex: 0 0 auto;
  width: auto;
  margin: 0;
  transform: none;
}
/* sizes vary to give the band rhythm; the ratio stays native so
   nothing is re-cropped */
.work-scroll .card[data-i="0"] { width: 31%; margin-top: -5vh; }
.work-scroll .card[data-i="1"] { width: 28%; margin-top: 5vh; }
.work-scroll .card[data-i="2"] { width: 30%; margin-top: -2vh; }

.work-scroll .card-media { border-radius: 14px; }
.work-scroll .card-meta { grid-template-columns: 1fr; gap: 2px; margin-top: 16px; }
.work-scroll .card-meta h3 { font-size: 1rem; }
.work-scroll .card-meta .card-tag { font-size: 1rem; }

.work-scroll .work-count {
  position: absolute;
  top: calc(var(--header-h) + 28px);
  right: var(--gut);
  margin: 0;
  z-index: 2;
  font-family: var(--ui);
  font-size: 0.875rem;
  color: var(--faint);
}

/* Without JS, or with motion reduced, the band becomes a normal
   horizontal scroller rather than a broken pinned section. */
html:not(.js) .work-scroll,
.reduce-motion .work-scroll { height: auto; }
html:not(.js) .work-pin,
.reduce-motion .work-pin {
  position: static;
  height: auto;
  overflow-x: auto;
  padding: 120px 0;
}
html:not(.js) .work-title,
.reduce-motion .work-title { position: static; margin-bottom: 48px; }

@media (max-width: 900px) {
  .work-scroll { height: 280vh; }
  .work-track { flex-direction: column; gap: 34px; padding: 0 var(--gut); }
  .work-scroll .card[data-i] { width: 100%; margin-top: 0; }
  .work-title { font-size: clamp(2rem, 12vw, 3.5rem); }
}

/* ── karolinahess colour system ─────────────────────────────
   Her actual palette, sampled from the live site: light grey
   panels over a deep green base, near-black green ink, and an
   accent that carries the closing words of a heading.
   Her accent is lime #BFDB39 — replaced here by the brand red
   #EE3124 — the Contact section ground, now the single accent.
   ──────────────────────────────────────────────────────────── */
:root {
  --paper: #e9e8e8;    /* primary light panel   */
  --surface: #dbdcdb;  /* alternate light panel */
  --card: #f2f1f1;
  --base: #1a5241;     /* her deep green ground */
  --base-deep: #123c2f;
  --dark: #1a5241;

  --accent: #fd2e02;
  --accent-ink: #ffffff;

  --head: #18231d;     /* ink on light */
  --ink: #18231d;
  --ink-2: #3b3b3b;
  --muted: #5c5c5c;
  --faint: #8a8a8a;

  --on-dark: #fafafa;  /* ink on the green */
  --on-dark-muted: #c2c2c2;
  --faint-dark: #9fb5ab;

  --invert: #18231d;
  --invert-ink: #ffffff;

  --rule: rgba(0, 0, 0, 0.12);
  --rule-strong: rgba(0, 0, 0, 0.26);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 12px 34px rgba(0, 0, 0, 0.07);
  --shadow-lift: 0 2px 6px rgba(0, 0, 0, 0.07), 0 24px 56px rgba(0, 0, 0, 0.12);
}

body { background: var(--base); color: var(--ink-2); }

/* panels: two light screens, then the green one */
.panel { background: var(--paper); border-top-color: rgba(0, 0, 0, 0.08); }
.hero.panel { background: var(--paper); }
.panel.soft, .work-scroll { background: var(--surface); }
.panel.is-dark { background: var(--base); color: var(--on-dark-muted); }
.panel.is-dark h1, .panel.is-dark h2, .panel.is-dark h3 { color: var(--on-dark); }
.panel.is-dark p { color: var(--on-dark-muted); }
.panel.is-dark .eyebrow { background: rgba(255, 255, 255, 0.12); color: var(--on-dark); }
.panel.is-dark .skill-list li { color: var(--on-dark-muted); border-bottom-color: rgba(255, 255, 255, 0.18); }

.eyebrow { background: rgba(0, 0, 0, 0.07); color: var(--head); }

/* chrome sits on the light hero */
.site-header { background: rgba(233, 232, 232, 0.82); }
.site-header.scrolled { box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); }
.nav a { color: var(--muted); }
.nav a:hover { color: var(--head); background: rgba(0, 0, 0, 0.07); }

.hero-meta span { background: transparent; border-color: var(--rule); color: var(--muted); }
.portrait { border-color: var(--rule); background: var(--surface); }
.scroll-cue-ring { border-color: var(--rule-strong); }
.scroll-cue-label { color: var(--muted); }
.scroll-cue:hover .scroll-cue-ring { background: var(--accent); border-color: var(--accent); }

/* works band on the lighter panel */
.work-title { color: var(--head); }
.work-scroll .card-media { background: var(--paper); box-shadow: var(--shadow); }
.work-scroll .card-meta h3 { color: var(--head); }
.work-scroll .card-meta .card-tag { color: var(--muted); }
.work-scroll .card:hover .card-meta h3 { color: var(--accent); }
.work-count { color: var(--faint); }

/* buttons on the green about panel */
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: #d92701; }
.btn-ghost { background: transparent; color: var(--on-dark); border-color: rgba(255, 255, 255, 0.34); }
.btn-ghost:hover { background: rgba(255, 255, 255, 0.14); }

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

/* her "Recent works" sits at 70px, not display scale */
.work-title { font-size: clamp(2.25rem, 5.4vw, 4.4rem); }

/* ── Hero, after karolinahess.com ───────────────────────────
   A cut-out portrait anchored to the bottom-left, an oversized
   two-line headline right-aligned above and beside it, a quiet
   sub-line beneath, an accent Contact pill top-right and the
   scroll circle bottom-left.
   ──────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  overflow: hidden;
  display: block;
  padding: 0;
  text-align: left;
}

.hero-inner {
  position: relative;
  z-index: 2;
  height: calc(100svh - var(--header-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-end;
  pointer-events: none;
}

.hero-title {
  margin: 0;
  text-align: right;
  font-size: clamp(3rem, 9.6vw, 9rem);
  font-weight: 400;
  line-height: 0.94;
  letter-spacing: -0.035em;
  color: var(--head);
  max-width: none;
}
.hero-sub {
  margin: 2.2rem 0 0;
  text-align: right;
  font-size: clamp(0.95rem, 1.3vw, 1.125rem);
  color: var(--muted);
  letter-spacing: -0.01em;
  max-width: 42ch;
}

/* the portrait sits behind the type, grounded on the bottom edge */
.hero-photo {
  position: absolute;
  left: 6vw;
  bottom: 0;
  z-index: 1;
  margin: 0;
  height: min(76vh, 780px);
  width: auto;
  pointer-events: none;
}
.hero-photo img {
  height: 100%;
  width: auto;
  display: block;
  object-fit: contain;
  object-position: bottom center;
  filter: grayscale(1) contrast(1.06);
}

/* Contact as the accent pill from the reference */
.contact-pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 11px 20px;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--accent-ink) !important;
  font-size: 0.95rem;
  text-decoration: none;
  transition: background 0.25s ease, transform 0.25s ease;
}
.contact-pill:hover { background: #d92701; transform: translateY(-1px); }
.nav a.contact-pill:hover { background: #d92701; }

/* scroll circle moves to the bottom-left corner */
.scroll-cue {
  left: var(--gut);
  right: auto;
  bottom: 34px;
  margin: 0;
  width: fit-content;
}
.scroll-cue-ring { width: 74px; height: 74px; }
.scroll-cue-label { font-size: 0; }
.scroll-cue-label::after {
  content: "↓";
  font-size: 1.15rem;
  color: var(--muted);
}
.scroll-cue:hover .scroll-cue-label::after { color: #fff; }

/* the positioning tags now live in About */
.about-tags { justify-content: flex-start; margin-top: 26px; }
.panel.is-dark .about-tags span {
  border-color: rgba(255, 255, 255, 0.28);
  color: var(--on-dark-muted);
}

@media (max-width: 900px) {
  .hero-inner {
    height: auto;
    min-height: calc(100svh - var(--header-h));
    justify-content: flex-start;
    padding-top: 12vh;
  }
  .hero-title { font-size: clamp(2.6rem, 13vw, 4rem); }
  .hero-photo { left: 50%; transform: translateX(-50%); height: 48vh; opacity: 0.9; }
  .hero-sub { max-width: 30ch; }
  .scroll-cue { display: none; }
}

/* The case-study `figure img` rule adds a shadow; on a transparent
   cut-out that shadow traces the element's rectangle rather than the
   silhouette, drawing a visible box. Clear it, and let the portrait
   run larger so the headline overlaps it as on the reference. */
.hero-photo img {
  box-shadow: none;
  border-radius: 0;
  border: 0;
  cursor: default;
}
.hero-photo {
  left: 9vw;
  height: min(84vh, 880px);
}
.hero-inner { padding-right: 2vw; }
.hero-title { line-height: 0.92; }

@media (max-width: 900px) {
  .hero-photo { height: 46vh; left: 50%; }
}

/* ── Typography after helloakriti.com ───────────────────────
   Three families, as she runs them:
     display + UI  PP Neue Montreal Medium  -> Switzer 500
     serif accent  PP Editorial New         -> Instrument Serif
     small labels  ABC Favorit Mono         -> DM Mono, uppercase
   Her display sits at weight 500 on solid leading (line-height ==
   font-size) with -0.04em tracking; supporting copy is 18px/400
   in grey, and descriptive lines shift to the serif.
   ──────────────────────────────────────────────────────────── */
:root {
  --serif: "Instrument Serif", Georgia, "Times New Roman", serif;
  --mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --tight: -0.04em;
  --mono-track: 0.06em;
}

/* display: medium weight, solid leading, tracked in hard */
h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 500;
  letter-spacing: var(--tight);
  line-height: 1;
}
h1 { font-size: clamp(3rem, 9.6vw, 9rem); line-height: 1; }
h2 { font-size: clamp(2.25rem, 5.4vw, 4.4rem); line-height: 1; letter-spacing: -0.036em; }
h3 { font-size: clamp(1.3rem, 2vw, 1.75rem); line-height: 1.1; letter-spacing: -0.025em; }

.hero-title { font-weight: 500; line-height: 1; letter-spacing: var(--tight); }
.work-title { font-weight: 500; line-height: 1; letter-spacing: -0.036em; }

/* body: 18px, regular, grey — her supporting text */
body { font-weight: 400; font-size: 1rem; line-height: 1.35; letter-spacing: normal; }
p { letter-spacing: normal; }

/* descriptive lines take the serif, as hers do */
.hero-sub,
.statement,
.section-head p,
.case-hero .lede {
  font-family: var(--serif);
  font-weight: 400;
  letter-spacing: normal;
}
.hero-sub { font-size: clamp(1.15rem, 1.7vw, 1.5rem); line-height: 1.35; }
.statement { font-size: clamp(1.6rem, 3vw, 2.6rem); line-height: 1.2; }
.section-head p { font-size: clamp(1.05rem, 1.5vw, 1.35rem); line-height: 1.35; }/* small labels: mono, uppercase, 14px */
.eyebrow, .card-tag, .case-meta dt, .case-nav .label, .pc-label, .work-count {
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.875rem;
  letter-spacing: var(--mono-track);
  text-transform: uppercase;
}
.eyebrow { font-size: 0.8rem; }
.work-scroll .card-meta .card-tag { font-size: 0.8rem; }

/* her display is set lowercase at the largest size */
.hero-title { text-transform: lowercase; }

/* .work-scroll .card-meta .card-tag out-specifies the label rule
   above, so the mono treatment has to be restated at that weight. */
.work-scroll .card-meta .card-tag {
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: var(--mono-track);
  text-transform: uppercase;
  color: var(--muted);
}
.work-scroll .card-meta h3 {
  font-family: var(--display);
  font-weight: 500;
  letter-spacing: -0.025em;
  text-transform: none;
}
/* her labels sit at 14px */
.eyebrow { font-size: 0.875rem; }

/* ── Typography after bureaudimanche.com ────────────────────
   A single-family system: Switzer for everything. No serif, no
   mono — they load Fragment Mono but never use it. Weight 500 is
   the default (nothing sits at 400), 600 carries emphasis and
   labels, and tracking tightens as size grows: -0.03em on display,
   -0.02em mid, -0.01em small. Italic is reserved for quotes.
   ──────────────────────────────────────────────────────────── */
:root {
  --serif: var(--sans);    /* their system has neither */
  --mono: var(--sans);
  --display: var(--sans);
  --tight: -0.03em;
  --mono-track: -0.01em;
}

body {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 1rem;
  line-height: 1.5;
  letter-spacing: -0.02em;
}

h1, h2, h3, h4 {
  font-family: var(--sans);
  font-weight: 500;
  letter-spacing: var(--tight);
}
/* their largest type is 72px — the scale is restrained throughout */
h1 { font-size: clamp(2.5rem, 6vw, 4.5rem); line-height: 0.95; }
h2 { font-size: clamp(2.25rem, 5vw, 4rem);  line-height: 1; }
h3 { font-size: clamp(1.35rem, 2.4vw, 2rem); line-height: 1.15; letter-spacing: -0.03em; }

.hero-title {
  font-family: var(--sans);
  font-weight: 500;
  font-size: clamp(2.5rem, 6.4vw, 4.75rem);
  line-height: 0.95;
  letter-spacing: var(--tight);
  text-transform: none;      /* theirs is sentence case */
}
.work-title { font-weight: 500; line-height: 1; letter-spacing: var(--tight); }

/* supporting copy: 18px/500 grey, then 16px/500 */
.hero-sub, .section-head p, .lede {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 1.125rem;
  line-height: 1.4;
  letter-spacing: -0.02em;
  color: var(--muted);
}
.statement {
  font-family: var(--sans);
  font-weight: 500;
  font-size: clamp(1.375rem, 2.4vw, 1.75rem);
  line-height: 1.4;
  letter-spacing: -0.03em;
}
p { letter-spacing: -0.02em; }/* labels drop the mono/uppercase treatment: 14px, weight 600 */
.eyebrow, .card-tag, .case-meta dt, .case-nav .label, .pc-label, .work-count, .work-scroll .card-meta .card-tag {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.875rem;
  line-height: 1.12;
  letter-spacing: -0.01em;
  text-transform: none;
}
.work-scroll .card-meta h3 { font-weight: 500; letter-spacing: -0.03em; }

/* italic carries quotes and taglines, as it does on the reference */
.quote p { font-style: italic; font-weight: 500; letter-spacing: -0.03em; }

/* emphasis weight */
strong, b, .skill-list li { font-weight: 600; }
.card-stats b { font-weight: 600; }

/* Two strays in a single-family system: the counter is out-specified
   by .work-scroll .work-count, and buttons don't inherit font-family. */
.work-scroll .work-count {
  font-family: var(--sans);
  font-weight: 600;
  letter-spacing: -0.01em;
}
button, input, select, textarea { font-family: inherit; }

/* ── Dark theme + full-bleed hero photograph ────────────────
   The portrait becomes the hero's background, covering the whole
   screen, with a scrim over it so the centred type stays legible.
   Everything else returns to the dark palette.
   ──────────────────────────────────────────────────────────── */
:root {
  --paper: #0f0f0f;
  --surface: #141414;
  --card: #1a1a1a;
  --base: #0a0a0a;
  --dark: #0a0a0a;

  --head: #fafafa;
  --ink: #fafafa;
  --ink-2: #c9c9c9;
  --muted: #9a9a9a;
  --faint: #7a7a7a;

  --on-dark: #fafafa;
  --on-dark-muted: #c9c9c9;
  --faint-dark: #8a8a8a;

  --invert: #fafafa;
  --invert-ink: #0a0a0a;

  --rule: rgba(255, 255, 255, 0.14);
  --rule-strong: rgba(255, 255, 255, 0.3);
  --shadow: 0 0 0 1px rgba(255, 255, 255, 0.07);
  --shadow-lift: 0 0 0 1px rgba(255, 255, 255, 0.16);
}

body { background: var(--base); color: var(--ink-2); }

.panel { background: var(--paper); border-top-color: rgba(255, 255, 255, 0.1); }
.panel.soft, .work-scroll { background: var(--surface); }
.panel.is-dark { background: var(--base); }
.eyebrow, .panel.is-dark .eyebrow { background: rgba(255, 255, 255, 0.1); color: var(--head); }
.panel.is-dark .skill-list li { border-bottom-color: var(--rule); }

.site-header { background: rgba(15, 15, 15, 0.6); }
.site-header.scrolled { box-shadow: 0 1px 0 rgba(255, 255, 255, 0.12); }
.nav a { color: var(--muted); }
.nav a:hover { color: var(--head); background: rgba(255, 255, 255, 0.1); }

.card-media { background: var(--surface); }
.work-title { color: var(--head); }
.work-scroll .card-media { box-shadow: none; }
.btn-ghost { color: var(--head); border-color: rgba(255, 255, 255, 0.3); }
.btn-ghost:hover { background: rgba(255, 255, 255, 0.12); }

/* ── hero: photograph behind, content centred on top ────── */
.hero {
  position: relative;
  overflow: hidden;
  display: block;
  padding: 0;
  text-align: center;
  background: var(--base);
}

.hero-photo {
  position: absolute;
  inset: 0;
  z-index: 0;
  margin: 0;
  height: auto;
  width: auto;
  left: 0;
  bottom: auto;
  pointer-events: none;
}
.hero-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 22%;   /* holds the face in frame as it crops */
  filter: none;
  box-shadow: none;
  border-radius: 0;
}
/* scrim: the photograph is bright, so the type needs a ground */
.hero-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(10,10,10,0.82) 0%, rgba(10,10,10,0.62) 38%, rgba(10,10,10,0.86) 100%),
    rgba(10, 10, 10, 0.28);
}

.hero-inner {
  position: relative;
  z-index: 2;
  height: calc(100svh - var(--header-h));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding-right: 0;
  pointer-events: auto;
  text-align: center;
}
.hero-title {
  margin: 0 auto 22px;
  text-align: center;
  max-width: 20ch;
  color: #fff;
  text-shadow: 0 2px 40px rgba(0, 0, 0, 0.45);
}
.hero-sub {
  margin: 0 auto;
  text-align: center;
  max-width: 40ch;
  color: rgba(255, 255, 255, 0.82);
}
.hero .eyebrow {
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  margin-bottom: 26px;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.hero-meta { justify-content: center; margin-top: 34px; }
.hero-meta span {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.22);
  color: rgba(255, 255, 255, 0.88);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* scroll cue returns to the centre, over the photograph */
.scroll-cue { left: 0; right: 0; margin-inline: auto; bottom: 40px; width: fit-content; }
.scroll-cue-ring { border-color: rgba(255, 255, 255, 0.4); width: 74px; height: 74px; }
.scroll-cue-label { font-size: 0.78rem; color: rgba(255, 255, 255, 0.85); line-height: 1.25; }
.scroll-cue-label::after { content: none; }
.scroll-cue:hover .scroll-cue-ring { background: var(--accent); border-color: var(--accent); }
.scroll-cue:hover .scroll-cue-label { color: #fff; }

@media (max-width: 900px) {
  .hero-photo img { object-position: 50% 18%; }
  .hero-title { max-width: none; }
  .scroll-cue { display: flex; }
}

/* `bottom: auto` above cancelled the inset stretch, so the figure sized
   itself to the photo's intrinsic ratio (2279px) instead of the hero.
   Pin all four edges explicitly and let them define the box. */
.hero-photo {
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  height: auto;
  width: auto;
}
.hero-photo img { object-position: 50% 34%; }   /* centres the face in the crop */

/* ── Contact, after bohdan.design ───────────────────────────
   A deep rust panel (#781F06 there) carrying an oversized
   headline top-left and a numbered form bottom-right, with the
   note and links along the base. Built with the brand red in
   place of his #E34C22, which is all but the same hue.

   The field labels are monospace on the reference; this site runs
   a single-family system, so they use tracked, tabular Switzer
   instead of pulling in a second face for one section.
   ──────────────────────────────────────────────────────────── */
.contact-panel {
  --rust: #c41b0f;
  --rust-ink: #ffffff;
  --rust-line: rgba(255, 255, 255, 0.3);
  background: var(--rust);
  color: var(--rust-ink);
  border-top: 0;
  padding: 0;
  display: block;
}

/* ── jenncsrubin.com design system ──────────────────────────
   Type: Inter Display throughout, DM Mono for small uppercase
   labels tracked OUTWARD (+0.14em). Body 18px/500 on 1.5. Her
   headings get lighter as they grow — 600 at mid sizes, 500 at
   display — and tracking tightens with size.
   Colour: pure white page, #EFEFEF band, black panels, #000
   headings, #3C3C3C prose, #848484 captions. No warm tint.
   The brand red is kept as the accent; her system has none.
   ──────────────────────────────────────────────────────────── */
:root {
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --display: var(--sans);
  --serif: var(--sans);
  --mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --tight: -0.03em;
  --mono-track: 0.14em;

  --paper: #ffffff;
  --surface: #efefef;
  --card: #ffffff;
  --base: #000000;
  --dark: #000000;

  --head: #000000;
  --ink: #000000;
  --ink-2: #3c3c3c;
  --muted: #3c3c3c;     /* she keeps all prose at one value */
  --faint: #848484;

  --on-dark: #ffffff;
  --on-dark-muted: #ebebeb;
  --faint-dark: #bdbdbd;

  --invert: #000000;
  --invert-ink: #ffffff;

  --rule: rgba(0, 0, 0, 0.12);
  --rule-strong: rgba(0, 0, 0, 0.26);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 12px 34px rgba(0, 0, 0, 0.07);
  --shadow-lift: 0 2px 6px rgba(0, 0, 0, 0.07), 0 24px 56px rgba(0, 0, 0, 0.12);
}

body {
  background: var(--paper);
  color: var(--ink-2);
  font-family: var(--sans);
  font-optical-sizing: auto;
  font-weight: 500;
  font-size: 1rem;          /* 16px body text */
  line-height: 1.5;
  letter-spacing: -0.014em;
}

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 600;
  color: var(--head);
  letter-spacing: var(--tight);
  line-height: 1.1;
}
h1 { font-size: clamp(2.75rem, 7vw, 5.5rem); font-weight: 600; letter-spacing: -0.036em; line-height: 1.05; }
h2 { font-size: clamp(2.25rem, 5vw, 4rem);   font-weight: 500; letter-spacing: -0.031em; }
h3 { font-size: clamp(1.35rem, 2.2vw, 1.9rem); font-weight: 600; letter-spacing: -0.03em; }

.hero-title { font-weight: 600; letter-spacing: -0.036em; line-height: 1.05; text-transform: none; }
.work-title { font-weight: 500; letter-spacing: -0.031em; }

.lede, .hero-sub, .section-head p, .statement {
  font-family: var(--sans);
  font-weight: 500;
  letter-spacing: -0.014em;
  line-height: 1.5;
  color: var(--muted);
}
.statement { font-size: clamp(1.4rem, 2.6vw, 2rem); line-height: 1.3; letter-spacing: -0.025em; }
p { letter-spacing: -0.014em; }/* labels: DM Mono, tracked outward — her signature detail */
.eyebrow, .card-tag, .case-meta dt, .case-nav .label, .pc-label, .work-count, .work-scroll .card-meta .card-tag {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 0.78rem;
  letter-spacing: var(--mono-track);
  text-transform: uppercase;
}
.nav a, .btn, .card-link { font-weight: 600; }

/* ── surfaces ───────────────────────────────────────────── */
.panel { background: var(--paper); border-top-color: rgba(0, 0, 0, 0.1); }
.panel.soft, .work-scroll { background: var(--surface); }
.panel.is-dark { background: var(--base); color: var(--on-dark-muted); }
.panel.is-dark h1, .panel.is-dark h2, .panel.is-dark h3 { color: var(--on-dark); }
.panel.is-dark p, .panel.is-dark .statement { color: var(--on-dark-muted); }
.panel.is-dark .eyebrow { background: rgba(255, 255, 255, 0.14); color: var(--on-dark); }
.panel.is-dark .skill-list li { color: var(--on-dark-muted); border-bottom-color: rgba(255, 255, 255, 0.2); }
.eyebrow { background: rgba(0, 0, 0, 0.07); color: var(--head); }

.site-header { background: rgba(255, 255, 255, 0.82); }
.site-header.scrolled { box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1); }
.nav a { color: var(--muted); }
.nav a:hover { color: var(--head); background: rgba(0, 0, 0, 0.07); }

.card-media { background: var(--surface); }
.work-title { color: var(--head); }
.work-scroll .card-meta h3 { color: var(--head); font-weight: 600; }
.work-scroll .card:hover .card-meta h3 { color: var(--accent); }
.btn-ghost { color: var(--head); border-color: var(--rule-strong); background: transparent; }
.btn-ghost:hover { background: rgba(0, 0, 0, 0.06); }
::selection { background: var(--accent); color: #fff; }

/* ── One screen per section, one gesture per move ───────────
   Every panel fills the viewport and snaps; scroll-snap-stop
   prevents a single flick from skipping past a section.

   The works section drops its 300vh pinned sequence — a section
   that needs three screens of scroll to play cannot also be
   reachable in one gesture. Heading and projects now share one
   screen and reveal on entry instead.
   ──────────────────────────────────────────────────────────── */
html {
  /* proximity, not mandatory: the works list is taller than one
     screen now, and mandatory snapping would drag the reader back
     to its start part-way through rows two and three. Proximity
     still snaps the one-screen sections when you land near them. */
  scroll-snap-type: y proximity;
}

.panel {
  scroll-snap-align: start;
  scroll-snap-stop: always;
  scroll-margin-top: 0;
  min-height: 100svh;
}
.hero.panel { min-height: 100svh; }

/* works: back to a single screen */
.work-scroll {
  height: auto;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.work-pin {
  position: static;
  height: auto;
  overflow: visible;
  display: block;
  padding: 0;
}
.work-title {
  position: static;
  inset: auto;
  display: block;
  text-align: center;
  margin: 0 0 clamp(28px, 4vh, 56px);
  font-size: clamp(2rem, 4.6vw, 3.6rem);
}
.work-track {
  opacity: 1;
  transform: none;
  width: 100%;
  justify-content: center;
  gap: 2vw;
  padding: 0 var(--gut);
}
.work-scroll .card[data-i="0"],
.work-scroll .card[data-i="1"],
.work-scroll .card[data-i="2"] { width: 31%; margin-top: 0; }
.work-scroll .work-count { top: auto; bottom: 4vh; right: var(--gut); }

/* the contact panel is taller than a screen on small viewports */
.contact-panel { min-height: 100svh; }
.contact-grid { min-height: calc(100svh - 12vh); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-snap-type: none; }
}

@media (max-width: 900px) {
  /* mandatory snap fights long content on phones */
  html { scroll-snap-type: none; }
  .work-track { flex-direction: column; gap: 26px; }
  .work-scroll .card[data-i] { width: 100%; }
  .work-scroll { min-height: 0; padding: 80px 0; }
}

/* The hero sits below the sticky header, so a full 100svh pushes its
   last 64px past the fold. Subtract the bar so screen one fits exactly. */
.hero.panel { min-height: calc(100svh - var(--header-h)); }
.hero-inner { height: calc(100svh - var(--header-h)); }

/* The Jenn colour layer sits after the contact and hero rules and
   out-orders them, so both need restating:
   - .panel{background:var(--paper)} had flattened the rust panel
   - .hero-sub picked up --muted (#3C3C3C), which vanishes on the photo */
.panel.contact-panel { background: var(--rust); color: var(--rust-ink); }

.hero .hero-sub { color: rgba(255, 255, 255, 0.85); }
.hero .hero-title { color: #fff; }
.hero .eyebrow { background: rgba(255, 255, 255, 0.16); color: #fff; }
.hero-meta span {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.24);
  color: rgba(255, 255, 255, 0.9);
}

/* ── One headline scale, one description scale ──────────────
   Every section now carries the same three parts — eyebrow,
   headline, description — at the same sizes. Colour can't be
   literally identical across a white, a black and a rust panel,
   so each role keeps a fixed relationship to its ground:
   headline = full-strength ink, description = the muted step.
   ──────────────────────────────────────────────────────────── */
.hero-title,
.work-title,
#about h2,
#contact .section-head h2 {
  font-family: var(--display);
  font-size: clamp(2.25rem, 5vw, 4rem);
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: -0.031em;
  margin: 0 0 20px;
  max-width: 18ch;
}
.hero-title { max-width: 20ch; }

.hero-sub,
.section-desc,
.statement {
  font-family: var(--sans);
  font-size: clamp(1.05rem, 1.4vw, 1.25rem);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: -0.014em;
  margin: 0;
  max-width: 46ch;
}

/* headline / description colour, per ground */
.panel .work-title,
#about h2 { color: var(--head); }
#about h2 { color: var(--on-dark); }
.section-desc { color: var(--muted); }
#about .statement { color: var(--on-dark-muted); }
.hero-title { color: #fff; }
.hero-sub { color: rgba(255, 255, 255, 0.85); }

/* the works head is centred with the band beneath it */
#work .section-head {
  max-width: 46ch;
  margin: 0 auto clamp(28px, 4vh, 52px);
  text-align: center;
  padding: 0;
}
#work .section-head .work-title,
#work .section-head .section-desc { margin-inline: auto; }
#work .section-desc { max-width: 52ch; }

/* the hero stack stays centred */
.hero-title, .hero-sub { margin-inline: auto; }

/* `.section-head p` (0,1,1) out-specifies `.eyebrow` and `.section-desc`
   (0,1,0), so inside a section head both reverted to body type. Restate
   at two classes so the shared roles hold everywhere. */
.section-head .eyebrow {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 0.78rem;
  line-height: 1;
  letter-spacing: var(--mono-track);
  text-transform: uppercase;
  color: var(--head);
  background: rgba(0, 0, 0, 0.07);
}
.section-head .section-desc {
  font-family: var(--sans);
  font-weight: 500;
  font-size: clamp(1.05rem, 1.4vw, 1.25rem);
  line-height: 1.5;
  letter-spacing: -0.014em;
  color: var(--muted);
}

/* the eyebrow on the rust panel needs the rust ink, not black */
.contact-panel .eyebrow {
  color: var(--rust-ink);
  background: rgba(255, 255, 255, 0.14);
}

/* ── Dark theme, on Jenn's system ───────────────────────────
   Her dark sections run #000 ground, #FFF headings, #EBEBEB body
   and #BDBDBD muted — so this is her own dark half applied across
   the whole page rather than a new palette. Type scale untouched.
   ──────────────────────────────────────────────────────────── */
:root {
  --paper: #000000;
  --surface: #0f0f0f;
  --card: #141414;
  --base: #000000;
  --dark: #000000;

  --head: #ffffff;
  --ink: #ffffff;
  --ink-2: #ebebeb;
  --muted: #bdbdbd;     /* her muted-on-dark */
  --faint: #8a8a8a;

  --on-dark: #ffffff;
  --on-dark-muted: #ebebeb;
  --faint-dark: #bdbdbd;

  --invert: #ffffff;
  --invert-ink: #000000;

  --rule: rgba(255, 255, 255, 0.14);
  --rule-strong: rgba(255, 255, 255, 0.3);
  --shadow: 0 0 0 1px rgba(255, 255, 255, 0.07);
  --shadow-lift: 0 0 0 1px rgba(255, 255, 255, 0.16);
}

body { background: var(--paper); color: var(--ink-2); }

.panel { background: var(--paper); border-top-color: rgba(255, 255, 255, 0.1); }
.panel.soft, .work-scroll { background: var(--surface); }
.panel.is-dark { background: var(--base); color: var(--on-dark-muted); }

.eyebrow, .section-head .eyebrow {
  background: rgba(255, 255, 255, 0.12);
  color: var(--head);
}
.section-head .section-desc { color: var(--muted); }
.work-title, #about h2 { color: var(--head); }

.site-header { background: rgba(0, 0, 0, 0.7); }
.site-header.scrolled { box-shadow: 0 1px 0 rgba(255, 255, 255, 0.14); }
.nav a { color: var(--muted); }
.nav a:hover { color: var(--head); background: rgba(255, 255, 255, 0.1); }

.card-media { background: var(--surface); }
.work-scroll .card-meta h3 { color: var(--head); }
.work-scroll .card-meta .card-tag { color: var(--faint); }
.skill-list li { color: var(--ink-2); border-bottom-color: var(--rule); }
.btn-ghost { color: var(--head); border-color: var(--rule-strong); background: transparent; }
.btn-ghost:hover { background: rgba(255, 255, 255, 0.12); }
.scroll-cue-ring { border-color: rgba(255, 255, 255, 0.4); }
.scroll-cue-label { color: rgba(255, 255, 255, 0.85); }

/* the rust contact panel stays as its own accent surface */
.panel.contact-panel { background: var(--rust); color: var(--rust-ink); }

/* hero photograph in black and white, to sit with the dark theme */
.hero-photo img { filter: grayscale(1) contrast(1.05) brightness(0.92); }
.hero-photo::after {
  background:
    linear-gradient(180deg, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.5) 40%, rgba(0,0,0,0.8) 100%),
    rgba(0, 0, 0, 0.22);
}

/* Push the photograph back so the headline carries the screen:
   deeper black point, stronger separation, and a scrim weighted
   toward the middle band where the type sits. */
.hero-photo img {
  filter: grayscale(1) contrast(1.32) brightness(0.58);
}
.hero-photo::after {
  background:
    radial-gradient(120% 80% at 50% 46%, rgba(0,0,0,0.62) 0%, rgba(0,0,0,0.38) 45%, rgba(0,0,0,0.55) 100%),
    linear-gradient(180deg, rgba(0,0,0,0.78) 0%, rgba(0,0,0,0.52) 38%, rgba(0,0,0,0.85) 100%);
}

/* ── One button system, every page ──────────────────────────
   Three shapes existed: an accent pill in the index header, a
   plain text link in the case-study headers. They are one family
   — same padding, radius, weight and transition — in two variants.
   ──────────────────────────────────────────────────────────── */
.btn,
.contact-pill,
.nav a.contact-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px 22px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  font-family: var(--sans);
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-transform: none;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.22s ease, border-color 0.22s ease,
              color 0.22s ease, transform 0.22s ease;
}
.btn:hover,
.contact-pill:hover { transform: translateY(-1px); }

/* primary — accent fill */
.btn-primary,
.contact-pill,
.nav a.contact-pill {
  background: var(--accent);
  color: #fff !important;
  border-color: var(--accent);
}
.btn-primary:hover,
.contact-pill:hover,
.nav a.contact-pill:hover { background: #d92701; border-color: #d92701; }

/* secondary — outlined */
.btn-ghost {
  background: transparent;
  color: var(--head);
  border-color: var(--rule-strong);
}
.btn-ghost:hover { background: rgba(255, 255, 255, 0.12); }

/* focus is shared rather than per-component */
.btn:focus-visible,
.contact-pill:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* ── Contact: direct details, no form ───────────────────────
   The form is gone; the email carries the section instead, with
   LinkedIn and the deck beneath it. Sized like a headline so it
   reads as the section's action.
   ──────────────────────────────────────────────────────────── */
.contact-direct {
  grid-column: 2;
  grid-row: 2;
  align-self: end;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.direct-label {
  margin: 0 0 10px;
  font-family: var(--mono);
  font-weight: 500;
  font-size: 0.78rem;
  letter-spacing: var(--mono-track);
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.7);
}

.direct-email {
  font-family: var(--display);
  font-size: clamp(1.5rem, 3.2vw, 2.6rem);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.031em;
  color: var(--rust-ink);
  text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.4);
  padding-bottom: 4px;
  transition: border-color 0.22s ease, opacity 0.22s ease;
  overflow-wrap: anywhere;
}
.direct-email:hover { border-bottom-color: var(--rust-ink); opacity: 0.85; }

.direct-links {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 32px;
  margin: 30px 0 0;
  padding: 0;
}
.direct-links a {
  color: rgba(255, 255, 255, 0.88);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  transition: color 0.2s ease;
}
.direct-links a:hover { color: #fff; }

@media (max-width: 900px) {
  .contact-direct { grid-column: 1; grid-row: 3; }
}


.direct-label { margin-top: 0; }

/* ── Contact: white text on the rust ────────────────────────
   Everything in this section was set in the accent itself, which
   sits close to its own ground. White separates cleanly and lifts
   the whole panel's contrast.
   ──────────────────────────────────────────────────────────── */
.contact-panel {
  --rust-line: rgba(255, 255, 255, 0.28);
  color: #ffffff;
}
.contact-panel .eyebrow {
  color: #ffffff;
  background: rgba(255, 255, 255, 0.16);
}
#contact .section-head h2 { color: #ffffff; }
#contact .section-desc { color: rgba(255, 255, 255, 0.86); }
.contact-panel .direct-label { color: rgba(255, 255, 255, 0.86); }
.contact-panel .direct-email {
  color: #ffffff;
  border-bottom-color: rgba(255, 255, 255, 0.45);
}
.contact-panel .direct-email:hover { border-bottom-color: #ffffff; opacity: 1; }
.contact-panel .direct-links a { color: rgba(255, 255, 255, 0.88); }
.contact-panel .direct-links a:hover { color: #ffffff; }

/* ── 12-column grid ─────────────────────────────────────────
   Every section's type now sits on one 12-column grid, so the
   headline, description and secondary column share the same
   column lines down the whole page instead of each section
   inventing its own measure.

   Track = (--page - 2*--gut - 11*--grid-gap) / 12
   ──────────────────────────────────────────────────────────── */
:root { --grid-gap: 24px; }

.hero-inner,
.work-pin,
.about-grid,
.contact-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
}

/* hero — centred block on cols 3-10 */
.hero-inner {
  align-content: center;
  justify-items: center;
  row-gap: 0;
}
.hero-inner > .eyebrow,
.hero-inner > .hero-title,
.hero-inner > .hero-sub,
.hero-inner > .hero-meta { grid-column: 3 / span 8; }

/* works — head on the same 8 columns, band spans all 12 */
.work-pin { align-content: center; row-gap: 0; }
#work .section-head { grid-column: 3 / span 8; margin-inline: 0; max-width: none; }
#work .work-track { grid-column: 1 / -1; padding: 0; }
#work .work-count { grid-column: 1 / -1; justify-self: end; }

/* about — name on cols 1-4, prose on 6-12 */
.about-grid > div:first-child { grid-column: 1 / span 4; }
.about-grid > div:nth-of-type(2)  { grid-column: 6 / span 7; }

/* contact — head on 1-7 exactly like works, details on 1-6 beneath */
#contact .section-head { grid-column: 1 / span 7; }
.contact-direct { grid-column: 1 / span 6; }

@media (max-width: 900px) {
  .hero-inner > .eyebrow,
  .hero-inner > .hero-title,
  .hero-inner > .hero-sub,
  .hero-inner > .hero-meta,
  #work .section-head,
  .about-grid > div:first-child,
  .about-grid > div:nth-of-type(2),
  #contact .section-head,
  .contact-direct { grid-column: 1 / -1; }
}

/* ── One shared grid container ──────────────────────────────
   The four grids resolved to four different widths: the hero had
   padding-right:0, .work-pin sits outside .wrap and ran full
   bleed, and .contact-grid's `padding: 10vh 0` cancelled the
   horizontal gutter. All four now resolve to the same content
   box, so column 1 starts at the same x in every section.
   ──────────────────────────────────────────────────────────── */
.hero-inner,
.work-pin,
.about-grid,
.contact-grid {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding-left: var(--gut);
  padding-right: var(--gut);
  box-sizing: border-box;
}
.hero-inner { padding-top: 0; padding-bottom: 0; }
.work-pin { padding-top: 0; padding-bottom: 0; }
.contact-grid { padding-top: 10vh; padding-bottom: 10vh; }

/* .about-grid is nested inside a .wrap, so the shared container rule
   gave it a second gutter — column 1 started 32px right of every
   other section. Its parent already supplies the bounds. */
.about-grid {
  max-width: none;
  margin-inline: 0;
  padding-left: 0;
  padding-right: 0;
}

/* The hero type centred inside its cell on an auto width, so its box
   floated off the column lines. Let the boxes fill cols 3-10 and centre
   the text inside them — centred look, edges still on the grid. */
.hero-inner > .hero-title,
.hero-inner > .hero-sub {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  text-align: center;
}
.hero-inner { justify-items: stretch; }
.hero-inner > .eyebrow { justify-self: center; }
.hero-inner > .hero-meta { justify-content: center; }

/* ── Section rhythm, unified ────────────────────────────────
   The audit found: two sections centred and two left-aligned;
   eyebrow margins of 26 vs 18px; headline margins of 0, 18 and
   20px; four different max-widths in ch; and the project cards
   sitting 11.5px off the column lines at a width no column adds
   up to. All of it now derives from the grid and two spacing
   tokens.
   ──────────────────────────────────────────────────────────── */
:root {
  --space-eyebrow: 18px;   /* eyebrow -> headline */
  --space-head: 22px;      /* headline -> description */
  --space-block: clamp(48px, 6vh, 88px);   /* head -> content */
}

/* one rhythm for every section head */
.eyebrow,
.hero .eyebrow,
.section-head .eyebrow { margin: 0 0 var(--space-eyebrow); }

.hero-title,
.work-title,
#about h2,
#contact .section-head h2 { margin: 0 0 var(--space-head); }

.hero-sub,
.section-desc,
.statement { margin: 0; }

/* measure comes from the grid cell, not arbitrary ch values */
.hero-title,
.work-title,
#about h2,
#contact .section-head h2 { max-width: none; }
.hero-sub,
.section-desc { max-width: 46ch; }
#about .statement { max-width: none; }

/* works head joins About and Contact on column 1 */
#work .section-head {
  grid-column: 1 / span 7;
  text-align: left;
  margin: 0 0 var(--space-block);
}
#work .section-head .work-title,
#work .section-head .section-desc { margin-inline: 0; text-align: left; }
#work .section-head .eyebrow { justify-self: start; }

/* project cards land on the columns: three cards, four columns each */
.work-track {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  padding: 0;
  width: 100%;
}
.work-scroll .card,
.work-scroll .card[data-i="0"],
.work-scroll .card[data-i="1"],
.work-scroll .card[data-i="2"] {
  grid-column: span 4;
  width: auto;
  margin: 0;
}

/* the hero stays centred — it is the one full-bleed image section,
   a deliberate exception rather than a leftover */
.hero-inner > .hero-title,
.hero-inner > .hero-sub { text-align: center; }

@media (max-width: 900px) {
  #work .section-head { grid-column: 1 / -1; margin-bottom: 36px; }
  .work-track { grid-template-columns: 1fr; row-gap: 28px; }
  .work-scroll .card,
  .work-scroll .card[data-i="0"],
  .work-scroll .card[data-i="1"],
  .work-scroll .card[data-i="2"] { grid-column: 1 / -1; }
}

/* `.section-head h2` (0,1,1) out-specifies the shared headline rule
   (0,1,0), and the works headline is the only one nested in a
   .section-head — so it alone kept an 18px gap. */
#work .section-head .work-title { margin-bottom: var(--space-head); }
#work .section-head .eyebrow { margin-bottom: var(--space-eyebrow); }

/* ── Portrait moves to About ────────────────────────────────
   The hero is type on the dark ground now; the photograph sits in
   the About column beneath the name, where the left column was
   otherwise empty below the heading.
   ──────────────────────────────────────────────────────────── */
.about-photo {
  margin: clamp(28px, 4vh, 44px) 0 0;
  width: 100%;
  max-width: 320px;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: var(--r-lg);
  background: var(--surface);
  border: 1px solid var(--rule);
}
.about-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 22%;      /* keeps the face in the crop */
  display: block;
  filter: grayscale(1) contrast(1.06);
  transition: filter 0.5s ease, transform 0.8s cubic-bezier(0.22, 0.8, 0.28, 1);
  box-shadow: none;
  border-radius: 0;
  cursor: default;
}
.about-photo:hover img { filter: none; transform: scale(1.03); }

/* the hero no longer carries an image, so its type returns to the
   shared tokens instead of the white it needed over the photo */
.hero { background: var(--paper); }
.hero .hero-title { color: var(--head); }
.hero .hero-sub { color: var(--muted); }
.hero .eyebrow { background: rgba(255, 255, 255, 0.12); color: var(--head); }
.hero-meta span {
  background: transparent;
  border-color: var(--rule);
  color: var(--muted);
  backdrop-filter: none;
}

@media (max-width: 900px) {
  .about-photo { max-width: 240px; }
}

/* ── About: photograph as the section background ────────────
   Same construction as the hero had — the image covers the panel,
   a scrim sits over it, and the grid rides on top. Greyscaled so
   it reads as ground rather than competing with the type.
   ──────────────────────────────────────────────────────────── */
.panel.has-bg { position: relative; overflow: hidden; }

.about-bg {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  z-index: 0;
  margin: 0;
  width: auto;
  height: auto;
  pointer-events: none;
}
.about-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 22% 24%;   /* face toward the left, clear of the prose */
  display: block;
  filter: grayscale(1) contrast(1.28) brightness(0.5);
  border-radius: 0;
  box-shadow: none;
  cursor: default;
}
.about-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, rgba(0,0,0,0.62) 0%, rgba(0,0,0,0.78) 42%, rgba(0,0,0,0.9) 100%),
    rgba(0, 0, 0, 0.2);
}

/* the grid rides above the photograph */
#about .wrap { position: relative; z-index: 1; }

@media (max-width: 900px) {
  .about-bg img { object-position: 50% 20%; }
  .about-bg::after {
    background:
      linear-gradient(180deg, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.86) 100%),
      rgba(0, 0, 0, 0.25);
  }
}

/* ── Hero display, at the reference's scale ─────────────────
   jenncsrubin runs her hero at 138px/600 on 1.2 leading with
   -0.036em, against 64px section heads — the hero is meant to
   outweigh them. "Hi. I'm Eunsung." is longer than her line, so
   the block spans all 12 columns to stay on one line.
   ──────────────────────────────────────────────────────────── */
.hero-inner > .hero-title { grid-column: 1 / -1; }
.hero-title {
  font-size: clamp(2.75rem, 9.9vw, 8.75rem);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.036em;
  margin: 0 0 var(--space-head);
}
/* the sub-line and chips stay on the inner columns */
.hero-inner > .hero-sub { grid-column: 3 / span 8; }

@media (max-width: 900px) {
  .hero-title { font-size: clamp(2.4rem, 11vw, 3.6rem); line-height: 1.1; }
}

/* ── Section text fade ──────────────────────────────────────
   Text blocks start lowered and transparent; when their section
   enters the viewport they rise into place, staggered in document
   order via --fade-delay set by the script.

   This supersedes the hero's keyframe entrance and the old .reveal
   observer — both are neutralised below so nothing double-animates.
   ──────────────────────────────────────────────────────────── */
.js .fade-item {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity 0.75s cubic-bezier(0.22, 0.8, 0.28, 1) var(--fade-delay, 0ms),
    transform 0.75s cubic-bezier(0.22, 0.8, 0.28, 1) var(--fade-delay, 0ms);
  will-change: opacity, transform;
}
.js .panel.in-view .fade-item {
  opacity: 1;
  transform: none;
}

/* the superseded systems */
.js .hero-title .w,
.js .hero .eyebrow,
.js .hero .lede,
.js .hero-meta,
.js .hero-ready .eyebrow,
.js .hero-ready .hero-title .w,
.js .hero-ready .lede,
.js .hero-ready .hero-meta {
  opacity: 1;
  animation: none;
  transform: none;
}
.js .reveal { opacity: 1; transform: none; }
.js .card.reveal { transform: none; }
/* the works layers are no longer script-driven */
.js .work-title,
.js .work-track { opacity: 1; }
.scroll-cue { opacity: 1; }
.js .hero-ready .scroll-cue { animation: none; opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .js .fade-item { opacity: 1; transform: none; transition: none; }
}

/* Media fades a touch slower and without the lift, so images settle
   rather than slide; the background photograph only fades. */
.js .work-row.fade-item { transform: translateY(22px); }
.js .about-bg.fade-item {
  transform: none;
  transition: opacity 1.1s ease var(--fade-delay, 0ms);
}
.js .scroll-cue.fade-item { transform: translateY(10px); }

/* ── Contact, rebuilt on the shared components ──────────────
   It was the only section with its own layout language: column
   spans 1/8, 1/5 and 7/12 that appeared nowhere else, a bespoke
   note class in place of .section-desc, its own headline wrapper,
   and divider rules no other section used.

   It now carries the same .section-head as Works — eyebrow,
   headline, description on columns 1-7 — with the details block
   beneath it on column 1. Type, colour, spacing and placement all
   come from the shared groups above; only the two-row stacking
   is specific to this section.
   ──────────────────────────────────────────────────────────── */
.contact-grid {
  grid-template-rows: auto auto;
  align-content: center;
  gap: 0 var(--grid-gap);
}
#contact .section-head { grid-row: 1; margin-bottom: var(--space-block); }

.contact-direct {
  grid-row: 2;
  align-self: start;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  margin: 0;
}

@media (max-width: 900px) {
  #contact .section-head { margin-bottom: 36px; }
}


/* ════════════════════════════════════════════════════════════
   REFINEMENT LAYER
   A pass over the finished layout for detail rather than
   structure. Nothing here moves a grid column or changes a
   type size that the sections share — it works on the smaller
   register: labels, rules, numerals, hover states, and the
   quality of the surface itself.
   ════════════════════════════════════════════════════════════ */

:root {
  --ease: cubic-bezier(0.22, 0.8, 0.28, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ── Type detail ────────────────────────────────────────────
   Headlines were breaking with orphaned last words. `balance`
   evens the line lengths of short display text; `pretty` only
   prevents orphans, which is the right tool for body copy. */
h1, h2, h3, .hero-title, .work-title { text-wrap: balance; }
p, li, .section-desc, .statement, .lede { text-wrap: pretty; }

body {
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* numerals in labels and data should be even-width, so they
   don't shift as they change */
.eyebrow, .card-tag, .work-count, .hero-meta span,
.case-meta, .metric b, .card-stats b, .direct-label {
  font-variant-numeric: tabular-nums;
}

/* ── Editorial section labels ───────────────────────────────
   The eyebrow was a grey bubble — soft next to type this sharp.
   It becomes a tracked mono label with a counted index and a
   hairline running off it, which reads as a section marker
   rather than a tag. Numbering is a CSS counter, so pages stay
   correct if sections are ever reordered. */
main { counter-reset: sec; }

.eyebrow,
.hero .eyebrow,
.section-head .eyebrow,
.panel.is-dark .eyebrow,
.contact-panel .eyebrow,
#contact .eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 0;
  background: none;
  border-radius: 0;
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 1;
}
.eyebrow::after {
  content: "";
  width: 48px;
  height: 1px;
  background: var(--rule);
}

/* every section label except the two page openers is numbered */
.panel:not(.hero) .eyebrow,
.case-section .eyebrow { counter-increment: sec; }
.panel:not(.hero) .eyebrow::before,
.case-section .eyebrow::before {
  content: counter(sec, decimal-leading-zero);
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* the hero label is centred, where a rule running off one side
   reads as unbalanced rather than editorial */
.hero .eyebrow::after { display: none; }

/* on the rust panel the label sits brighter, like its neighbours */
#contact .eyebrow { color: rgba(255, 255, 255, 0.86); }
#contact .eyebrow::before { color: #ffffff; }
#contact .eyebrow::after { background: rgba(255, 255, 255, 0.32); }

/* ── Hero meta: pills → one ruled row ───────────────────────
   Three rounded chips read as UI controls. As a single row
   divided by hairlines they read as a masthead credit line,
   which is what they are. */
.hero-meta {
  display: inline-flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0;
  /* the 52px above this row was spaced for a hero subtitle that
     no longer exists; the row now sits directly under the title. */
  margin-top: 20px;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.hero-meta span {
  padding: 15px 30px;
  border: 0;
  border-radius: 0;
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
}
.hero-meta span + span { border-left: 1px solid var(--rule); }

/* ── Work cards ─────────────────────────────────────────────
   The cards were static images with a caption under them. They
   now carry a counted index and an arrow, and the image moves
   under its own frame on hover — the card behaves like a link
   instead of only looking like one. */
.work-track { counter-reset: card; }
.card { counter-increment: card; }

.card-media {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-md);
}
.card-media img {
  display: block;
  transition: transform 0.85s var(--ease-out);
  will-change: transform;
}
.card:hover .card-media img,
.card:focus-visible .card-media img { transform: scale(1.035); }

.card-meta {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  column-gap: 18px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
  transition: border-color 0.4s var(--ease);
}
.card:hover .card-meta { border-top-color: var(--rule-strong); }

.card-meta h3 { grid-column: 1; transition: color 0.3s var(--ease); }
.card .card-tag { grid-column: 1; }

/* the index doubles as the hover arrow */
.card-meta::after {
  content: counter(card, decimal-leading-zero);
  grid-column: 2;
  grid-row: 1 / span 2;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--faint);
  transition: color 0.3s var(--ease), transform 0.4s var(--ease);
}
.card:hover .card-meta::after {
  content: "\2197";              /* ↗ */
  color: var(--accent);
  transform: translate(2px, -2px);
}

/* ── About: hierarchy in the prose ──────────────────────────
   The lead and the two body paragraphs ran at one texture, so
   the block read as a wall. The lead keeps the shared
   description size; the paragraphs under it step back in
   colour and open up their leading. */
.about-grid > div:nth-of-type(2) > p + p { margin-top: 18px; }
.about-grid > div:nth-of-type(2) > p:not(.statement) {
  font-size: 1rem;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.72);
  max-width: 54ch;
}

/* skills become a tracked mono index, not a bulleted table */
.skill-list {
  margin-top: 36px;
  gap: 0 32px;
}
#about .skill-list li {
  padding: 11px 0;
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.62);
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}
#about .skill-list li:hover {
  color: #ffffff;
  border-bottom-color: var(--accent);
}

/* ── Contact: the links become the same ruled row ───────────
   They were two small text links floating under the address.
   Ruled and tracked, they match the hero's credit row and the
   skills index — one detail language across the page. */
.direct-links {
  gap: 0;
  margin-top: 34px;
  border-top: 1px solid rgba(255, 255, 255, 0.28);
}
.direct-links li + li { border-left: 1px solid rgba(255, 255, 255, 0.28); }
.direct-links a {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 15px 26px 0 0;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}
.direct-links li + li a { padding-left: 26px; }
.direct-links a span { transition: transform 0.35s var(--ease); }
.direct-links a:hover span { transform: translate(2px, -2px); }

/* ── Surface ────────────────────────────────────────────────
   Flat #000 across four full-screen panels reads as empty
   rather than deep. A fixed grain gives the ground a material
   quality. It is plain, not blended: `overlay` weights hardest
   on the lightest pixels, which greyed out the white project
   images. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: 0.022;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-reduced-motion: reduce) { .grain { display: none; } }

/* ── Small system details ───────────────────────────────────*/
::selection { background: var(--accent); color: #ffffff; }

html { scrollbar-color: rgba(255, 255, 255, 0.22) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  border: 3px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.3); background-clip: content-box; }

@media (max-width: 900px) {
  /* the three items each take their own line at this width, so a
     separator would read as a stray leading bullet */
  .hero-meta {
    display: flex; flex-direction: column; align-items: center;
    gap: 9px; border: 0; margin-top: 34px;
  }
  .hero-meta span { padding: 0; }
  .hero-meta span + span { border-left: 0; }
  .direct-links { display: flex; flex-wrap: wrap; }
}


/* ── Footer ─────────────────────────────────────────────────
   One row closing every page, in the same register as the hero
   credit line and the skills index: a hairline, tracked mono,
   nothing else.

   On the index it lives INSIDE the contact panel rather than
   after it. A footer of its own after <main> would become a
   fifth scroll-snap stop, and the whole page is built on one
   gesture per section.

   The identity sits on the left and the top link on the right;
   the middle stays clear, which is where the floating dock used
   to sit before the header took over navigation.
   ──────────────────────────────────────────────────────────── */
.site-footer { width: 100%; flex: 0 0 auto; }

.footer-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  align-items: center;
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding: 20px var(--gut) 24px;
  box-sizing: border-box;
  border-top: 1px solid var(--rule);
}

.footer-id,
.footer-top {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}
.footer-id { grid-column: 1 / span 6; justify-self: start; }
.footer-sep { margin: 0 10px; opacity: 0.6; }

.footer-top {
  grid-column: 8 / span 5;
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  transition: color 0.3s var(--ease);
}
.footer-top span { transition: transform 0.35s var(--ease); }
.footer-top:hover { color: var(--head); }
.footer-top:hover span { transform: translateY(-3px); }

/* the contact panel becomes a column so the grid takes the free
   height and the footer settles on the floor of the screen */
.contact-panel { display: flex; flex-direction: column; }
.contact-panel > .contact-grid {
  flex: 1 1 auto;
  min-height: 0;
  padding-bottom: 4vh;
}
#contact .site-footer .footer-grid { border-top-color: rgba(255, 255, 255, 0.28); }
#contact .footer-id,
#contact .footer-top { color: rgba(255, 255, 255, 0.86); }
#contact .footer-top:hover { color: #ffffff; }

@media (max-width: 900px) {
  .footer-grid {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: 12px;
    text-align: center;
    padding-bottom: 34px;
  }
  .footer-id, .footer-top { grid-column: 1; justify-self: center; }
  .footer-id { display: flex; flex-wrap: wrap; justify-content: center; }
}


/* ── Contact: the email, at reading size ────────────────────
   It was set at display scale (up to 41.6px) — larger than the
   section headline's role warranted, and it dominated the panel.
   It now sits at the same size as the body copy around it, led
   by an envelope mark, with a copy control beside it. The two
   are one unit: shared hairline, shared hover.
   ──────────────────────────────────────────────────────────── */
.email-row {
  display: inline-flex;
  align-items: stretch;
  gap: 0;
  margin: 0;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: var(--r-sm);
  transition: border-color 0.3s var(--ease);
  max-width: 100%;
}
.email-row:hover { border-color: rgba(255, 255, 255, 0.5); }

/* strokes, not fills, so the marks match the hairline detailing */
.ico { fill: none; stroke: currentColor; stroke-width: 1.6;
       stroke-linecap: round; stroke-linejoin: round; flex: none; }

.contact-panel .direct-email,
.direct-email {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  padding: 13px 18px;
  font-family: var(--sans);
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: #ffffff;
  text-decoration: none;
  border: 0;
  border-bottom: 0;
  /* the children round themselves, so the row needs no overflow clip */
  border-radius: calc(var(--r-sm) - 1px) 0 0 calc(var(--r-sm) - 1px);
  transition: background 0.3s var(--ease);
}
.direct-email span { overflow-wrap: anywhere; }
.direct-email .ico { opacity: 0.7; transition: opacity 0.3s var(--ease); }
.contact-panel .direct-email:hover,
.direct-email:hover {
  background: rgba(255, 255, 255, 0.09);
  border-bottom: 0;
  opacity: 1;
}
.direct-email:hover .ico { opacity: 1; }

/* the copy control shares the frame, divided by the same hairline */
.email-copy {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  padding: 0;
  border: 0;
  border-left: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 0 calc(var(--r-sm) - 1px) calc(var(--r-sm) - 1px) 0;
  background: transparent;
  color: #ffffff;
  cursor: pointer;
  transition: background 0.3s var(--ease);
}
.email-copy:hover { background: rgba(255, 255, 255, 0.09); }
.email-copy .ico { opacity: 0.7; transition: opacity 0.3s var(--ease); }
.email-copy:hover .ico { opacity: 1; }

/* the check replaces the copy mark in place, so nothing reflows */
.email-copy .ico-check { position: absolute; opacity: 0; transform: scale(0.7); }
.email-copy.is-copied .ico-copy { opacity: 0; transform: scale(0.7); }
.email-copy.is-copied .ico-check { opacity: 1; transform: none; color: #ffffff; }
.email-copy .ico-copy,
.email-copy .ico-check {
  transition: opacity 0.25s var(--ease), transform 0.25s var(--ease);
}

/* a label on hover, so the icon is never the only affordance */
.copy-tip {
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translate(-50%, 4px);
  padding: 5px 9px;
  border-radius: var(--r-sm);
  background: #ffffff;
  color: var(--rust);
  font-family: var(--mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s var(--ease), transform 0.22s var(--ease);
}
.email-copy:hover .copy-tip,
.email-copy:focus-visible .copy-tip { opacity: 1; transform: translate(-50%, 0); }

.email-copy:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: -2px;
}
.direct-email:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: -2px;
}

@media (max-width: 480px) {
  .direct-email { font-size: 0.95rem; padding: 12px 14px; gap: 10px; }
  .email-copy { width: 44px; }
}


/* ── Header nav ─────────────────────────────────────────────
   The floating bottom dock is gone. It duplicated navigation
   the header could carry, sat over the contact panel's footer,
   and forced the header to retreat past the hero so the two
   would not share the screen.

   The header now stays put for the whole page and holds the
   sections itself, with the active one marked.
   ──────────────────────────────────────────────────────────── */
.site-header { transform: none; opacity: 1; pointer-events: auto; }

.nav {
  display: flex;
  align-items: center;
  gap: 2px;
}

.nav .nav-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 9px 14px;
  border-radius: var(--r-pill);
  font-family: var(--sans);
  font-size: 0.85rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: rgba(255, 255, 255, 0.62);
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.28s var(--ease), background 0.28s var(--ease);
}
.nav .nav-link:hover { color: #ffffff; background: rgba(255, 255, 255, 0.08); }
.nav .nav-link.is-active { color: #ffffff; }

/* an underline bar sitting on the header's bottom edge. The link is
   vertically centred in the header, so (100% - header height) / 2 is
   the (negative) distance from the link's bottom to the header's —
   it stays on the edge if the link's height ever changes */
.nav .nav-link.is-active::after {
  content: "";
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: calc((100% - var(--header-h)) / 2);
  height: 4px;
  background: var(--accent);
}

.nav .contact-pill { margin-left: 10px; }

.nav-short { display: none; }

@media (max-width: 860px) {
  /* "Recent work" is the first thing to give at this width */
  .nav-full { display: none; }
  .nav-short { display: inline; }
  .nav { gap: 0; }
  .nav .nav-link { padding: 9px 10px; font-size: 0.8rem; }
  .nav .contact-pill { margin-left: 6px; padding: 10px 16px; font-size: 0.82rem; }
}

@media (max-width: 560px) {
  .nav .nav-link { padding: 8px 7px; font-size: 0.75rem; }
  .nav .contact-pill { margin-left: 4px; padding: 9px 13px; font-size: 0.78rem; }
  .nav .contact-pill span { display: none; }
}


/* ── Section titles at the case-study hero's scale ──────────
   The titles ran 64px against an 18px description — a 3.5x step,
   which is the same ratio the case-study *sections* use. The jump
   that reads as decisive is the one on the case-study *hero*:
   88px against the same 18px, near 5x. The section titles now
   carry that spec exactly — size, weight, tracking and leading —
   so the index opens with the same authority as a project page.
   ──────────────────────────────────────────────────────────── */
.work-title,
#about h2,
#contact .section-head h2 {
  font-size: clamp(2.25rem, 6.6vw, 5.5rem);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.036em;
}

/* "Eunsung." alone measures ~390px at the top of that ramp, and
   the four-column cell it sat in was 376px, so the name would
   have overhung its own column. One column wider clears it. */
.about-grid > div:first-child { grid-column: 1 / span 5; }


/* ── Works as a stacked list ────────────────────────────────
   Was a 3-up card row. Now one project per row: copy on the
   left, image on the right, hairline between — the eliostudio
   featured-work arrangement, measured at 31% text / 100px gap
   / 59% image, which lands almost exactly on this site's own
   grid as columns 1-4 and 6-12.

   The type, rules, mono tags and corner radius are this site's
   existing ones, not the reference's, so the section still
   reads as part of the same system.
   ──────────────────────────────────────────────────────────── */
.work-list {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--rule);
}

.work-row {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  align-items: center;
  padding: clamp(28px, 3.4vh, 48px) 0;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.4s var(--ease);
}
.work-row:hover { border-bottom-color: var(--rule-strong); }

.work-row-text {
  grid-column: 1 / span 4;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.work-row-media {
  grid-column: 6 / span 7;
  min-width: 0;
  overflow: hidden;
  border-radius: var(--r-md);
}
.work-row-media img {
  display: block;
  width: 100%;
  height: auto;
  transition: transform 0.85s var(--ease-out);
  will-change: transform;
}
.work-row:hover .work-row-media img,
.work-row:focus-visible .work-row-media img { transform: scale(1.035); }

.work-row h3 {
  margin: 0 0 14px;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.03em;
  color: var(--head);
  max-width: none;
  transition: color 0.3s var(--ease);
}

.work-row-desc {
  margin: 0;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: rgba(255, 255, 255, 0.66);
  max-width: 46ch;
}

/* the tags keep this site's mono label treatment rather than the
   reference's soft grey chips */
.work-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 22px 0 0;
  padding: 0;
  list-style: none;
}
.work-tags li {
  padding: 7px 12px;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.7);
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.work-row:hover .work-tags li { border-color: var(--rule-strong); color: #ffffff; }

.work-row-cue {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: 24px;
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  transition: color 0.3s var(--ease);
}
.work-row-cue span { transition: transform 0.35s var(--ease); }
.work-row:hover .work-row-cue { color: var(--accent); }
.work-row:hover .work-row-cue span { transform: translateX(4px); }

.work-row:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  border-radius: var(--r-sm);
}

/* the list is taller than one screen, so the works panel is the
   one section that scrolls rather than snapping as a single page */
#work.work-scroll { min-height: 100svh; height: auto; }
#work .work-pin { height: auto; padding-top: clamp(72px, 12vh, 128px); padding-bottom: clamp(48px, 8vh, 96px); }
#work .work-count { position: static; grid-column: 1 / -1; justify-self: end; margin-top: 18px; }

@media (max-width: 900px) {
  .work-row {
    grid-template-columns: 1fr;
    row-gap: 22px;
    padding: 30px 0;
  }
  .work-row-text,
  .work-row-media { grid-column: 1; }
  .work-row-media { order: -1; }   /* image leads on a narrow screen */
  .work-row-desc { max-width: none; }
}


/* ════════════════════════════════════════════════════════════
   TYPOGRAPHY — eliostudio.framer.website
   Measured from the live site. Its system is unusually plain:

     family    Geist, and only Geist — no mono, no serif
     display   106px / 700 / -0.05em / 1.0
     heading   40px  / 500 / -0.04em / 1.0
     subhead   40px  / 500 / -0.02em / 1.25
     button    20px  / 500 / -0.02em
     nav       18px  / 500 / normal
     body      16px  / 400 / normal  / 1.5
     caption   14px  / 400 / normal

   The two things that define it: everything is one family, and
   the small text is plain — sentence case, no tracking, no
   uppercase. That replaces this site's DM Mono label layer.
   ════════════════════════════════════════════════════════════ */
:root {
  --sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --display: var(--sans);
  --serif: var(--sans);
  --mono: var(--sans);          /* their system has no second face */
  --ui: var(--sans);
  --tight: -0.04em;
  --mono-track: normal;         /* label rules read this; nothing is tracked now */
}

body { font-family: var(--sans); font-weight: 400; }

/* ── display and headings ──────────────────────────────────*/
.hero-title {
  font-weight: 700;
  letter-spacing: -0.05em;
  line-height: 1;
}
h1,
.work-title,
#about h2,
#contact .section-head h2,
.case-hero h1 {
  font-weight: 700;
  letter-spacing: -0.05em;
  line-height: 1;
}
h2, .case-section h2 { font-weight: 700; letter-spacing: -0.05em; }
h3,
.work-row h3,
.card-meta h3 {
  font-family: var(--sans);
  font-weight: 500;
  letter-spacing: -0.04em;
  line-height: 1.05;
}

/* ── body ──────────────────────────────────────────────────*/
p, li, .lede, .section-desc, .statement, .work-row-desc,
.about-grid > div:nth-of-type(2) > p:not(.statement) {
  font-family: var(--sans);
  font-weight: 400;
  letter-spacing: normal;
  line-height: 1.5;
}
.section-desc, .hero-sub, .statement, .lede { font-weight: 400; }/* ── captions and labels: plain, which is the whole point ──
   Every one of these was DM Mono, uppercase, tracked +0.11em
   to +0.14em. Their system has no such register — small text is
   simply small text, in the same face as everything else. */
.eyebrow, .card-tag, .work-tags li, .work-row-cue, .work-count, .direct-label, .direct-links a, .footer-id, .footer-top, .hero-meta span, .skill-list li, #about .skill-list li, .case-meta dt, .case-nav .label, .copy-tip, .pc-label {
  font-family: var(--sans);
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  font-size: 0.875rem;
}
.eyebrow, .direct-label, .work-row-cue { font-weight: 500; }
.eyebrow::before { font-weight: 500; }

/* nav and buttons */
.nav .nav-link, .nav a { font-size: 1.125rem; font-weight: 500; letter-spacing: normal; }
.btn, .contact-pill, .nav a.contact-pill {
  font-size: 1.125rem; font-weight: 500; letter-spacing: -0.02em;
}
.direct-email, .contact-panel .direct-email {
  font-family: var(--sans); font-weight: 500; letter-spacing: -0.02em;
}

/* the tags become plain chips rather than tracked mono pills */
.work-tags li { padding: 6px 13px; font-size: 0.875rem; color: rgba(255,255,255,0.72); }

/* Two earlier rules out-specify the resets above and had to be
   matched selector-for-selector: `.section-head .section-desc`
   (0,2,0) still carried weight 500 with -0.014em tracking, and the
   eyebrow's own reset list (up to `.panel.is-dark .eyebrow`, 0,3,0)
   still carried the uppercase mono treatment. */
.section-head .section-desc,
#work .section-desc,
#contact .section-desc {
  font-family: var(--sans);
  font-weight: 400;
  letter-spacing: normal;
  line-height: 1.5;
}

.eyebrow,
.hero .eyebrow,
.section-head .eyebrow,
.panel.is-dark .eyebrow,
.contact-panel .eyebrow,
#contact .eyebrow,
#work .section-head .eyebrow {
  font-family: var(--sans);
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: normal;
  text-transform: none;
}


/* ── About: skills as a bento ───────────────────────────────
   Was a two-column list of eight bare labels on hairlines. Now
   the eliostudio arrangement: one tall figure card beside a 2x2
   of capability cards, one of them filled in the accent.

   The eight labels are grouped into the four cards rather than
   dropped — research and usability together, interaction and
   end-to-end together, systems with IA and service design,
   metrics with cross-functional leadership.

   Surfaces, rules, radius and type are this site's own.
   ──────────────────────────────────────────────────────────── */
.skill-bento {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 14px;
  margin: 36px 0 0;
  padding: 0;
  list-style: none;
}

.skill-stat,
.skill-card {
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  background: rgba(255, 255, 255, 0.045);
}

/* the figure card runs the full height of the 2x2 beside it */
.skill-stat {
  grid-column: 1 / span 4;
  grid-row: 1 / span 2;
  display: flex;
  flex-direction: column;
  padding: 22px;
}
.skill-stat-figure {
  margin: 0;
  font-size: clamp(2.75rem, 4.4vw, 4rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.05em;
  color: var(--head);
}
.skill-stat-figure span { font-size: 0.5em; font-weight: 500; vertical-align: super; }
.skill-stat-label {
  margin: 14px 0 0;
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.4;
  color: var(--head);
  max-width: 20ch;
}
.skill-stat-note {
  margin: auto 0 0;
  padding-top: 18px;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.6);
}

.skill-card {
  grid-column: span 4;
  display: flex;
  flex-direction: column;
  padding: 20px;
}
.skill-card .ico {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--accent);
  margin-bottom: auto;
}
.skill-card h4 {
  margin: 30px 0 6px;
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.02em;
  color: var(--head);
}
.skill-card p {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.62);
}

/* one card carries the accent, as the reference does */
.skill-card.is-accent {
  background: var(--accent);
  border-color: var(--accent);
}
.skill-card.is-accent .ico { color: #ffffff; }
.skill-card.is-accent h4 { color: #ffffff; }
.skill-card.is-accent p { color: rgba(255, 255, 255, 0.86); }

@media (max-width: 1100px) {
  .skill-stat { grid-column: 1 / -1; grid-row: auto; }
  .skill-card { grid-column: span 6; }
}
@media (max-width: 640px) {
  .skill-card { grid-column: 1 / -1; }
}


/* ── Skills bento, full page width ──────────────────────────
   It sat inside the prose column (cols 6-12). It is now a direct
   child of .about-grid on its own row, spanning all twelve — the
   reference runs these boxes the full width of the page.

   Across those twelve: the figure card takes 1-4 and the four
   capability cards fill 5-8 and 9-12 in two rows, which is the
   reference's one-tall-plus-2x2 arrangement.
   ──────────────────────────────────────────────────────────── */
.about-grid > .skill-bento {
  grid-column: 1 / -1;
  /* no explicit grid-row: it is the last child and spans all twelve,
     so auto-placement puts it on its own row below the two columns.
     Pinning it to row 2 sent it ABOVE the prose once the grid
     collapsed to a single column on narrow screens. */
  margin-top: clamp(36px, 5vh, 64px);
}
.skill-bento { gap: 16px; }
.skill-stat { grid-column: 1 / span 4; grid-row: 1 / span 2; }
.skill-card { grid-column: span 4; }

/* --rust was declared only inside .contact-panel, so var(--rust)
   resolved to nothing out here and the card lost its fill. It is a
   shared value now; the contact panel still sets the same one. */
:root { --rust: #c41b0f; }

/* the accent card takes the contact panel's own orange, so the
   two places that carry a filled colour agree */
.skill-card.is-accent {
  background: var(--rust);
  border-color: var(--rust);
}
.skill-card.is-accent .ico { color: #ffffff; }
.skill-card.is-accent h4 { color: #ffffff; }
.skill-card.is-accent p { color: rgba(255, 255, 255, 0.86); }

@media (max-width: 1100px) {
  .skill-stat { grid-column: 1 / -1; grid-row: auto; }
  .skill-card { grid-column: span 6; }
}
@media (max-width: 640px) {
  .skill-card { grid-column: 1 / -1; }
}


/* ── Section indicator: uppercase ───────────────────────────
   The Elio pass set every label to sentence case. The section
   indicator is the exception — it reads as a marker, and reads
   better capitalised.

   The selector list matches the reset that set it to `none`
   (up to `#work .section-head .eyebrow`, 1,2,0); an unqualified
   `.eyebrow` would lose to it. */
.eyebrow,
.hero .eyebrow,
.section-head .eyebrow,
.panel.is-dark .eyebrow,
.contact-panel .eyebrow,
#contact .eyebrow,
#work .section-head .eyebrow {
  text-transform: uppercase;
}


/* ── About: headline on one line, prose beneath ─────────────
   Was a two-column split — name on cols 1-4, prose on 6-12 —
   which forced "Hello, I'm Eunsung." to wrap in a narrow cell.
   The headline now takes the full twelve on its own line, with
   the paragraphs directly under it and the skills below that.

   `#about` prefix because the placement rules these replace are
   `.about-grid > div:first-child` (0,2,1) — an unqualified class
   selector would lose to the :first-child pseudo. */
#about .about-grid > .about-head {
  grid-column: 1 / -1;
  grid-row: 1;
}
#about .about-grid > .about-prose {
  grid-column: 1 / span 7;
  grid-row: 2;
  margin-top: 0;
}

/* Works gets its rhythm purely from margins: --space-eyebrow to
   the headline, --space-head to the description. About was a grid
   row apart, so a 64px row-gap stacked on top of both margins and
   the title-to-description gap came out at 115px against Works'
   22px. Zero the row gap and let the same tokens govern; the
   bento keeps its own margin-top for the break below the prose. */
#about .about-grid { row-gap: 0; }

/* one line: the clamp scales the headline with the viewport, and
   at the top of its ramp the string still measures inside the
   1176px content width */
#about .about-head h2 {
  max-width: none;
  white-space: nowrap;
}

/* Measured: the string runs ~10.6x the font size, and the font is
   6.6vw against a content width of vw-64, so it clears comfortably
   at every width until the clamp floor (2.25rem) takes over below
   ~545px. From there the text is a fixed ~383px and stops fitting
   around 450px, so that is where it is allowed to wrap. */
@media (max-width: 480px) {
  #about .about-head h2 { white-space: normal; }
}
@media (max-width: 900px) {
  #about .about-grid > .about-prose { grid-column: 1 / -1; }
}


/* ── Scroll cue: make it reachable ──────────────────────────
   The cue is an <a href="#work"> and always has been, but it was
   never clickable. `.hero-inner` is `position:relative; z-index:2`
   and its box covers the whole hero — including the cue's corner
   at the bottom — so it painted over the link and took the click.
   elementFromPoint at the ring's centre returned .hero-inner.

   The cue comes later in the DOM but had `z-index:auto`, which
   loses to an explicit 2. Lifting it above puts it back in front. */
.scroll-cue { position: absolute; z-index: 3; }


/* ── Case study overview ───────────────────────────────────
   The first block in the section column, beside the sub-nav: four
   label/value rows with the headline figures underneath. It used
   to sit above the case body with its own left-hand "Overview"
   label, which duplicated the sub-nav's first entry — so the label
   is gone and the sub-nav names it instead.

   It reuses what the page already has rather than inventing a
   parallel set: .eyebrow for the label, .case-meta's dt treatment
   for the row labels, and the same .stat used in Outcomes, so the
   figures here and there are one component.

   .wrap already supplies max-width and gutters, so the grid only
   declares columns — adding them again is what gave .about-grid a
   doubled gutter previously.
   ──────────────────────────────────────────────────────────── */
.case-flow .case-overview {
  padding: 0 0 clamp(56px, 8vh, 96px);
}

.overview-list {
  margin: 0;
}
.overview-row {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 9fr);
  column-gap: var(--grid-gap);
  padding: 20px 0;
  border-top: 1px solid var(--rule);
}
.overview-row:first-child { border-top: 0; padding-top: 0; }

.overview-list dt {
  font-size: 0.9375rem;
  font-weight: 400;
  color: var(--faint);
  letter-spacing: normal;
  text-transform: none;
}
.overview-list dd {
  margin: 0;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 62ch;
}

/* ── Stat row: one layout everywhere ─────────────────────────
   Overview and Impact rows share it: a rule above, figures divided
   rather than boxed, and a fixed four-column track so a stat cell is
   the same width on every page whether the row holds two, three or
   four figures. Only the bottom margin varies — the Overview row ends
   its section, the Impact row is followed by a quote. */
.stats {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0;
  margin: 0 0 48px;
  padding: 36px 0 0;
  border-top: 1px solid var(--rule);
  border-bottom: 0;
}
.stats.overview-stats { margin: 44px 0 0; }
.stats .stat { padding-left: 28px; border-left: 1px solid var(--rule); }
.stats .stat:first-child { padding-left: 0; border-left: 0; }
/* the stat figure is one component: size, weight and tracking come
   from `.stat b` alone, so the Overview, Problem and Impact rows
   cannot drift apart. This block used to re-declare them here. */

@media (max-width: 900px) {
  .overview-row { grid-template-columns: 1fr; row-gap: 8px; }
  .stats { grid-template-columns: 1fr; gap: 26px; }
  .stats .stat { padding: 0; border-left: 0; }
}


/* ── Case study structure (leahkim.design) ──────────────────
   Two structural changes, in this site's own type and colour:

   1. The hero's Role/Team/Timeline/Platform definition list is
      now a row of pills, led by a filled status chip.
   2. The narrative runs in two columns — a section list on the
      left, the sections on the right. Measured on the reference
      at a 200px list, a 64px gap and an 836px column against a
      1100px content width; on this site's twelve that is 1-3
      and 4-12.

   The reference re-renders its whole list inside every section
   and darkens the current one. One sticky list with a scroll
   spy gives the same reading without repeating the markup.
   ──────────────────────────────────────────────────────────── */
.case-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 40px 0 0;
  padding: 0;
  list-style: none;
}
.chip {
  display: inline-flex;
  align-items: center;
  padding: 7px 14px;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  font-size: 0.8125rem;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.72);
}
.chip-status {
  background: #ffffff;
  border-color: #ffffff;
  color: #000000;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.72rem;
}

.case-body { padding: clamp(56px, 8vh, 96px) 0 0; }
.case-body-grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  align-items: start;
}

.case-toc {
  grid-column: 1 / span 3;
  position: sticky;
  top: calc(var(--header-h) + 32px);
  /* the reference gutters the list from the content by 64px; the
     grid's own column gap is 24, so the remaining 40 sits here —
     the content column stays on column 4 rather than being nudged
     off the grid to buy the space */
  padding-right: 40px;
}
.case-toc ol {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.case-toc a {
  display: block;
  padding: 7px 0;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.3;
  color: var(--faint);
  text-decoration: none;
  transition: color 0.25s var(--ease);
}
.case-toc a:hover { color: var(--ink-2); }
.case-toc a.is-current { color: var(--head); }

.case-flow { grid-column: 4 / span 9; min-width: 0; }

/* the sections sit on one continuous ground now — the alternating
   `soft` bands were full-bleed and cannot band across a layout
   that has a column beside them */
.case-flow .case-section {
  padding: 0 0 clamp(56px, 8vh, 96px);
  background: none;
  border: 0;
}
.case-flow .case-section:last-child { padding-bottom: clamp(40px, 6vh, 72px); }
.case-flow .case-section h2 { max-width: none; }

@media (max-width: 1000px) {
  .case-toc {
    grid-column: 1 / -1;
    position: static;
    margin-bottom: 40px;
    padding-bottom: 24px;
    border-bottom: 1px solid var(--rule);
  }
  .case-toc ol { flex-direction: row; flex-wrap: wrap; gap: 6px 18px; }
  .case-toc a { padding: 4px 0; font-size: 0.875rem; }
  .case-flow { grid-column: 1 / -1; }
}


/* ── Case study sub-heading ─────────────────────────────────
   Splits a long section into named halves — the usability
   section now carries both what held up and what changed.

   Deliberately not an .eyebrow: `.case-section .eyebrow`
   increments the section counter, so a second one inside a
   section would push every following section's number up by one.
   ──────────────────────────────────────────────────────────── */
.case-subhead {
  margin: clamp(38px, 5vh, 60px) 0 22px;
  padding-top: 22px;
  border-top: 1px solid var(--rule);
  font-size: 1.5rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.2;
  color: var(--head);
  max-width: none;
}
.case-subhead:first-of-type { margin-top: clamp(30px, 4vh, 44px); }


/* ── Case study: clear the sticky header when jumping ───────
   The section list scrolls a section to `block: "start"`, which
   puts its top at the very top of the viewport — underneath the
   sticky 64px header, so the eyebrow and the first line of the
   headline were hidden behind it.

   `.panel` carries an offset for this on the index; the case
   sections never had one. The extra 28px keeps the eyebrow off
   the header's edge rather than flush against it. */
.case-flow .case-section,
.case-hero,
.case-overview {
  scroll-margin-top: calc(var(--header-h) + 28px);
}


/* ── Header nav: into the system's label register ───────────
   The Elio pass set the nav to that site's 18px. On this site
   everything else navigational or label-like settled at 14-15px
   — the case-study section nav is 15, the overview labels 15,
   eyebrows and tags 14 — so 18 read as the largest UI text on
   the page and sat outside the scale.

   The links sit at 14px, one step below the 15px section nav.
   The pill
   keeps its accent fill for emphasis rather than extra size, and
   its padding comes in to stay proportional to the smaller text. */
.nav .nav-link,
.nav a {
  font-size: 0.875rem;
  font-weight: 500;
}
.nav .contact-pill,
.nav a.contact-pill,
.btn {
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  padding: 11px 20px;
}


/* ── Navigation: one bar, two behaviours ────────────────────
   Four labels plus a filled pill plus a logo cannot share a
   375px row without crowding — every previous attempt just shrank
   the type (0.8rem, then 0.75rem) until the links were smaller
   than the body copy and the touch targets were under 30px.

   Desktop keeps the inline row unchanged. Below 860px the links
   move into a full-screen panel behind a text toggle, which gives
   the header air and the links a proper 56px+ target.
   ──────────────────────────────────────────────────────────── */
.nav-toggle { display: none; }

@media (max-width: 860px) {
  .nav-toggle {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    position: relative;
    z-index: 62;
    margin: 0;
    padding: 10px 0;
    border: 0;
    background: none;
    color: var(--head);
    font-family: var(--sans);
    font-size: 0.9375rem;      /* the nav's own size */
    font-weight: 500;
    letter-spacing: -0.01em;
    cursor: pointer;
  }
  .nav-toggle-icon {
    position: relative;
    width: 18px;
    height: 10px;
    flex: none;
  }
  .nav-toggle-icon::before,
  .nav-toggle-icon::after {
    content: "";
    position: absolute;
    left: 0;
    width: 18px;
    height: 1.5px;
    background: currentColor;
    transition: transform 0.3s var(--ease), top 0.3s var(--ease);
  }
  .nav-toggle-icon::before { top: 1px; }
  .nav-toggle-icon::after  { top: 8px; }
  .nav-toggle[aria-expanded="true"] .nav-toggle-icon::before { top: 4.5px; transform: rotate(45deg); }
  .nav-toggle[aria-expanded="true"] .nav-toggle-icon::after  { top: 4.5px; transform: rotate(-45deg); }

  /* the mark stays above the panel: the reader keeps a fixed point,
     and it remains a way back to the index from a case study */
  .brand { position: relative; z-index: 62; }

  /* the panel */
  .site-header .nav {
    position: fixed;
    inset: 0;
    z-index: 61;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: center;
    gap: 0;
    padding: var(--header-h) var(--gut) 40px;
    background: var(--paper);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
    transition: opacity 0.32s var(--ease), transform 0.32s var(--ease), visibility 0.32s;
  }
  .site-header .nav.is-open { opacity: 1; visibility: visible; transform: none; }

  /* full labels here — there is room, so "Recent work" stays whole */
  .nav-full { display: inline; }
  .nav-short { display: none; }

  .site-header .nav .nav-link {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 56px;                       /* comfortable target */
    padding: 12px 0;
    border-radius: 0;
    border-bottom: 1px solid var(--rule);
    font-size: clamp(1.75rem, 8vw, 2.25rem);
    font-weight: 500;
    letter-spacing: -0.03em;
    color: var(--head);
  }
  .site-header .nav .nav-link:hover { background: none; }
  /* the active dot is too small at this scale — mark it in the accent */
  .site-header .nav .nav-link.is-active { color: var(--accent); }
  .site-header .nav .nav-link.is-active::after { display: none; }

  .site-header .nav .contact-pill {
    align-self: flex-start;
    margin: 34px 0 0;
    padding: 15px 26px;
    font-size: 1rem;
  }

  /* `backdrop-filter` makes an element a containing block for its
     fixed descendants, so the header's blur trapped the panel inside
     the 64px bar — it rendered 42px tall instead of filling the
     screen. Drop the blur at this width and make the bar opaque
     instead; the panel then positions against the viewport. */
  .site-header {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: rgba(0, 0, 0, 0.92);
  }

  /* the page must not scroll behind an open panel */
  body.nav-open { overflow: hidden; }
}

@media (max-width: 560px) {
  .site-header .nav .nav-link { font-size: clamp(1.6rem, 8.5vw, 2rem); }
}

@media (prefers-reduced-motion: reduce) {
  .site-header .nav,
  .nav-toggle-icon::before,
  .nav-toggle-icon::after { transition: none; }
}


/* ── Menu toggle: icon only ─────────────────────────────────
   The word is gone from view — the mark is understood on its own
   — but the span stays in the DOM under .visually-hidden so the
   button still has an accessible name, and the script's swap to
   "Close" is still announced.

   Without a label beside it the icon has to carry the target on
   its own, so the button is a full 44px square, and the bars grow
   to 24px at 2.5px so they read as deliberate rather than faint.
   ──────────────────────────────────────────────────────────── */
@media (max-width: 860px) {
  .nav-toggle {
    justify-content: flex-end;
    width: 44px;
    height: 44px;
    padding: 0;
    gap: 0;
  }
  .nav-toggle-icon { width: 24px; height: 13px; }
  .nav-toggle-icon::before,
  .nav-toggle-icon::after { width: 24px; height: 2.5px; border-radius: 2px; }
  .nav-toggle-icon::before { top: 0; }
  .nav-toggle-icon::after  { top: 10.5px; }
  .nav-toggle[aria-expanded="true"] .nav-toggle-icon::before { top: 5.25px; transform: rotate(45deg); }
  .nav-toggle[aria-expanded="true"] .nav-toggle-icon::after  { top: 5.25px; transform: rotate(-45deg); }
}


/* ── Hero motion ────────────────────────────────────────────
   A dot field behind the headline. It sits under .hero-inner
   (z-index 2) and the scroll cue (3), takes no pointer events,
   and is decorative — aria-hidden, so nothing is announced.

   Black and white only, and dim: the headline reads at 17:1 on
   this ground and the motion must not erode that.
   ──────────────────────────────────────────────────────────── */
.hero-motion {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}


/* ── Case study video ───────────────────────────────────────
   Sits in a <figure> exactly as the stills do, so it inherits the
   section's rhythm. width/height come from the file itself once
   metadata loads; aspect-ratio holds the box before that so the
   page does not jump — the same reason every still carries its
   intrinsic dimensions.
   ──────────────────────────────────────────────────────────── */
.case-video {
  display: block;
  width: 100%;
  /* the attributes give the box an aspect-ratio, so the space is held
     before metadata arrives; without it the element was 0px tall and
     anything scrolling to this section landed past it */
  height: auto;
  aspect-ratio: 1548 / 1080;
  border-radius: var(--r-md);
  box-shadow: var(--shadow);
  background: var(--card);
}

/* An autoplaying loop is motion the reader did not ask for. Under
   reduced-motion the script pauses it and shows the controls, so it
   is still watchable on demand. */
.case-video[controls] { cursor: default; }


/* ── Paired figures ─────────────────────────────────────────
   Two images of equal weight side by side. Both here are 1200px
   native and the column is far narrower, so they only ever scale
   DOWN — the low-resolution one is never enlarged past itself.
   ──────────────────────────────────────────────────────────── */
.figure-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
.figure-pair img {
  width: 100%;
  height: auto;
  max-width: 1200px;          /* never past native resolution */
  object-fit: cover;
  aspect-ratio: 1200 / 1031;  /* identical, so the pair stays level */
}
.figure-pair figcaption { grid-column: 1 / -1; }

@media (max-width: 640px) {
  .figure-pair { grid-template-columns: 1fr; gap: 12px; }
}


/* ── Half-width figure ──────────────────────────────────────
   A single supporting image inside a sub-section. Held to half
   the column so it reads as evidence beside the argument rather
   than a full-bleed plate — and so the two in this section stay
   the same size as each other, which full width would not
   guarantee once their captions differ in length.

   Both are 1200px native; half the column is far smaller, so the
   low-resolution one is never enlarged.
   ──────────────────────────────────────────────────────────── */
.figure-half img {
  width: 100%;
  max-width: 520px;
  height: auto;
  aspect-ratio: 1200 / 1031;
  object-fit: cover;
}
.figure-half figcaption { max-width: 520px; }

@media (max-width: 640px) {
  .figure-half img, .figure-half figcaption { max-width: none; }
}


/* ── Figure followed directly by a stat row ─────────────────
   `figure` carries a top margin but no bottom one, and `.stats`
   has no top margin — the two only ever looked separated because
   a figcaption sat between them. Remove the caption and the
   numbers press straight against the image. Give the pair the
   same rhythm a sub-head would have provided.
   ──────────────────────────────────────────────────────────── */
figure + .stats { margin-top: clamp(38px, 5vh, 60px); }


/* ── Option comparison ──────────────────────────────────────
   Two options read side by side, in the same order on both sides:
   title, one-line summary, evidence, pros, cons. Comparison only
   works if the eye can travel straight across, so the rows are
   forced to align — `align-items: start` alone would let the taller
   image block push one side's pros below the other's.
   ──────────────────────────────────────────────────────────── */
.compare {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--grid-gap);
  align-items: stretch;
}
/* Each card being its own grid only equalises card HEIGHT, not row
   positions — the pros/cons landed 16px apart because the two verdict
   blocks are different heights. Subgrid makes both cards share one set
   of row tracks, so every row starts on the same line. Where subgrid is
   unsupported this falls back to the previous behaviour. */
.compare { grid-template-rows: auto auto auto auto; }
.compare .option {
  display: grid;
  grid-row: span 4;
  grid-template-rows: subgrid;
  gap: 0;
}

.option-lede {
  margin: 0 0 18px;
  color: var(--ink-2);
}

/* evidence block */
.option-shots { display: grid; gap: 8px; align-content: start; }
.option-shots-quad { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.option-shots img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-sm);
}
.option-shots figure { margin: 0; }          /* the global 44px top margin would break the stack */
.option-shots figcaption { margin-top: 6px; }
.option-note {
  margin: 8px 0 0;
  font-size: 0.875rem;
  color: var(--faint);
}

/* pros and cons, pinned to the bottom of each card so they line up */
/* start, not end: the two verdict blocks are different heights, and
   aligning them to the bottom of a shared row left their headings
   15px apart — the opposite of what a comparison needs. */
.option-verdict { margin: 22px 0 0; align-self: start; }
.option-verdict dt { margin: 16px 0 6px; }
.option-verdict dt:first-child { margin-top: 0; }
.option-verdict dd { margin: 0; }
.option-verdict ul { margin: 0; padding-left: 1.1em; }

/* now sits inside .intro, directly under the framing sentence, so it
   needs paragraph spacing rather than the section-sized gap it had
   when it followed the comparison */
.option-call {
  margin: 14px 0 0;
  max-width: 62ch;
}

@media (max-width: 860px) {
  .compare { grid-template-columns: 1fr; gap: 36px; }
}

/* Both cards must expose the SAME four children for their grid rows to
   correspond — Option A carries an extra note under its stills, so the
   evidence is wrapped rather than sitting loose in the card. */
.option-evidence { display: block; }

/* Option B carries one image where Option A carries a 2x2 block. Two
   half-width images stacked come to the same height as one full-width
   image of the same aspect, plus the grid gap — so B is stretched to
   fill the shared evidence row rather than left 8px short. Subgrid
   already makes that row one height for both cards. */
.option-shots-single { height: 100%; }
.option-shots-single img {
  height: 100%;
  object-fit: cover;
}

/* The rule above each sub-head separates one sub-section from the last.
   The FIRST one has nothing before it but the section's own intro, so
   the line read as a divider under the section title rather than
   between steps. Scoped to this section; the pattern elsewhere is
   unchanged. */
#final-design .case-subhead:first-of-type {
  border-top: 0;
  padding-top: 0;
}

/* ── Step points ────────────────────────────────────────────
   A step's detail as bullets rather than a run-on paragraph.
   Matches .intro's type so the switch reads as the same voice,
   not a different register. */
.step-points {
  margin: 0 0 var(--space-head);
  padding-left: 1.15em;
  max-width: 62ch;
}
.step-points li {
  margin: 0 0 8px;
  line-height: 1.5;
  color: var(--ink-2);
}
.step-points li:last-child { margin-bottom: 0; }

/* Text sitting between two figures describes the figure BELOW it,
   but `figure` carries a top margin and no bottom one — so the block
   sat flush against the image above and 44px clear of the one it
   belonged to. Bind it to the figure that follows. Scoped to the
   figure-text-figure sandwich so ordinary intros are untouched. */
.case-section figure + .step-points,
.case-section figure + .intro { margin-top: clamp(38px, 5vh, 60px); margin-bottom: 0; }
.case-section figure + .step-points + figure,
.case-section figure + .intro + figure { margin-top: 18px; }

/* hero label in the accent — #fd2e02 on the black hero is 5.6:1,
   above the 4.5:1 minimum for text at this size */
.hero .eyebrow { color: var(--accent); }

/* ── Navigation states: one pattern for the top nav and side nav ──
   Every nav item carries the same indicator bar; only its intensity
   changes by state, so hover, focus and selection read as one system:
     default   text 62% white, bar hidden
     hover     text white,     bar 45% white (a preview of selection)
     focus     as hover, plus a 2px white outline for keyboard users
     selected  text white,     bar in the accent
   The bar sits on the bottom edge where items run horizontally (desktop
   top nav, side nav on narrow screens) and on the left where they stack
   (side nav, mobile menu). */
.nav .nav-link,
.case-toc a {
  --nav-bar: transparent;
  color: rgba(255, 255, 255, 0.62);
  border-radius: 4px;
  transition: color 0.28s var(--ease);
}
.nav .nav-link:hover,
.nav .nav-link:focus-visible,
.case-toc a:hover,
.case-toc a:focus-visible {
  --nav-bar: rgba(255, 255, 255, 0.45);
  color: #ffffff;
  background: none;
}
.nav .nav-link.is-active,
.case-toc a.is-current {
  --nav-bar: var(--accent);
  color: #ffffff;
}
.nav .nav-link:focus-visible,
.case-toc a:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

/* top nav: bar on the header's bottom edge, under every item */
.nav .nav-link::after,
.nav .nav-link.is-active::after {
  content: "";
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: calc((100% - var(--header-h)) / 2);
  height: 2px;
  background: var(--nav-bar);
  transition: background-color 0.28s var(--ease);
}

/* side nav: bar on the left of each stacked item */
.case-toc a {
  position: relative;
  padding-left: 16px;
}
.case-toc a::after {
  content: "";
  position: absolute;
  left: 0;
  top: 7px;
  bottom: 7px;
  width: 2px;
  background: var(--nav-bar);
  transition: background-color 0.28s var(--ease);
}

@media (max-width: 1000px) {
  /* the side nav turns into a wrapped row: bar moves to the bottom */
  .case-toc a { padding: 4px 0 10px; }
  .case-toc a::after { top: auto; bottom: 0; right: 0; width: auto; height: 2px; }
}

@media (max-width: 860px) {
  /* mobile menu rows stack, so the bar sits on the left */
  .site-header .nav .nav-link { color: rgba(255, 255, 255, 0.62); padding-left: 20px; }
  .site-header .nav .nav-link:hover,
  .site-header .nav .nav-link:focus-visible,
  .site-header .nav .nav-link.is-active { color: #ffffff; }
  .site-header .nav .nav-link::after,
  .site-header .nav .nav-link.is-active::after {
    display: block;
    left: 0;
    right: auto;
    top: 14px;
    bottom: 14px;
    width: 2px;
    height: auto;
  }
}

/* ── Spec figures (portfolio case study) ─────────────────────
   Built in HTML rather than as images, so the type scale, nav states
   and measurements shown are the site's real styles and numbers. */
.spec {
  margin: 44px 0 0;
  padding: clamp(22px, 3.2vw, 36px);
  background: #0a0a0a;
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
}
.spec-title {
  margin: 0 0 18px;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--faint);
}

/* type scale */
.spec-row {
  display: grid;
  grid-template-columns: minmax(0, 7.5rem) minmax(0, 1fr);
  align-items: baseline;
  gap: 24px;
  padding: 14px 0;
  border-top: 1px solid var(--rule);
}
.spec-row:first-of-type { border-top: 0; padding-top: 0; }
.spec-meta { font-size: 0.875rem; color: var(--faint); line-height: 1.4; }
.spec-meta b { display: block; font-weight: 500; color: var(--ink-2); }
.spec-sample {
  margin: 0;
  color: var(--head);
  line-height: 1.15;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* colour */
.swatches { display: flex; flex-wrap: wrap; gap: 20px 28px; }
.swatch { display: flex; align-items: center; gap: 12px; font-size: 0.875rem; color: var(--muted); }
.swatch i { width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--rule); flex: none; }
.swatch b { display: block; font-weight: 500; color: var(--ink-2); }

/* nav states */
.state-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}
.state-cell {
  padding: 22px 18px 18px;
  background: #000000;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
}
.state-demo {
  position: relative;
  display: inline-block;
  padding: 6px 2px 12px;
  font-size: 0.875rem;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.62);
}
.state-demo::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--bar, transparent);
}
.state-demo.is-hover { color: #ffffff; --bar: rgba(255, 255, 255, 0.45); }
.state-demo.is-focus { color: #ffffff; --bar: rgba(255, 255, 255, 0.45); outline: 2px solid #ffffff; outline-offset: 2px; border-radius: 4px; }
.state-demo.is-selected { color: #ffffff; --bar: var(--accent); }
.state-name { margin: 16px 0 0; font-size: 0.875rem; color: var(--ink-2); }
.state-name span { display: block; margin-top: 2px; color: var(--faint); }

/* measured bars */
.bar-row {
  display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr) minmax(0, 5.5rem);
  align-items: center;
  gap: 16px;
  padding: 11px 0;
  border-top: 1px solid var(--rule);
  font-size: 0.875rem;
  color: var(--muted);
}
.bar-row:first-of-type { border-top: 0; }
.bar-track { height: 8px; background: rgba(255, 255, 255, 0.08); border-radius: 999px; overflow: hidden; }
.bar-fill { display: block; height: 100%; background: rgba(255, 255, 255, 0.55); border-radius: 999px; }
.bar-fill.is-accent { background: var(--accent); }
.bar-value { text-align: right; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.bar-note { margin: 16px 0 0; font-size: 0.875rem; color: var(--faint); }

@media (max-width: 860px) {
  .state-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .spec-row { grid-template-columns: 1fr; gap: 6px; }
  .bar-row { grid-template-columns: minmax(0, 1fr) minmax(0, 4.5rem); }
  .bar-row .bar-track { grid-column: 1 / -1; grid-row: 2; }
}

/* ── Goals (portfolio case study) ────────────────────────────
   Audience first, then each goal as the reviewer's question,
   the goal it sets, and where the site answers it. */
.audience {
  margin: 44px 0 0;
  padding: clamp(22px, 3.2vw, 36px);
  background: #0a0a0a;
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
}
.audience-label {
  margin: 0 0 18px;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--faint);
}
.audience-list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.audience-list li { padding-left: 16px; border-left: 2px solid var(--rule); }
.audience-list b { display: block; font-size: 1rem; font-weight: 500; color: var(--head); }
.audience-list span { display: block; margin-top: 4px; font-size: 0.875rem; line-height: 1.45; color: var(--muted); }

.goal-list { margin: 44px 0 0; padding: 0; list-style: none; }
.goal {
  display: grid;
  grid-template-columns: 3rem minmax(0, 5fr) minmax(0, 6fr);
  column-gap: var(--grid-gap);
  align-items: start;
  padding: 26px 0;
  border-top: 1px solid var(--rule);
}
.goal:last-child { border-bottom: 1px solid var(--rule); }
.goal-num { font-size: 0.875rem; font-weight: 500; color: var(--faint); line-height: 1.9; font-variant-numeric: tabular-nums; }
.goal-q { margin: 0 0 6px; font-size: 0.875rem; color: var(--faint); }
.goal h3 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.3;
  color: var(--head);
}
.goal-how { margin: 0; font-size: 1rem; line-height: 1.5; color: var(--muted); }
.goal-how span { display: block; margin-bottom: 6px; font-size: 0.875rem; color: var(--faint); }

@media (max-width: 860px) {
  .audience-list { grid-template-columns: 1fr; gap: 16px; }
  .goal { grid-template-columns: 2.25rem minmax(0, 1fr); row-gap: 14px; }
  .goal-how { grid-column: 2; }
}

/* ── Works: open grid with captions and a "View project" cursor ──
   Modelled on collinscole.framer.website's Featured Projects. Two
   columns with wide gutters; each project is a square-cornered image,
   close to square, with its name on the left and company · type on
   the right beneath it. Hovering the image eases it up to 110% inside
   its frame and swaps the pointer for a "View project" label — an
   arrow in a white disc beside a frosted pill (js). The caption keeps
   the normal pointer. */
#work.work-scroll,
#work.panel.soft { background: #000000; }

#work .work-head {
  grid-column: 1 / -1;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  margin: 0 0 clamp(28px, 4vh, 48px);
}
#work .work-head .work-title { margin: 0; }

#work .work-list {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(40px, 5.3vw, 72px) clamp(24px, 5.3vw, 72px);
  border-top: 0;
}
#work .work-row {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: 0;
  border: 0;
  border-radius: 0;
  overflow: visible;
  background: none;
  color: inherit;
  text-decoration: none;
}
#work .work-row::after { content: none; }

#work .work-row-media {
  position: relative;
  aspect-ratio: 8 / 6;                            /* 4:3 — near-square cropped too much of the product */
  overflow: hidden;
  border-radius: 0;
  background: #111111;
}
#work .work-row-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1);
  transition: transform 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}
#work .work-row:hover .work-row-media img,
#work .work-row:focus-visible .work-row-media img { transform: scale(1.1); }

#work .work-row-text {
  position: static;
  display: flex;
  flex-direction: row;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: clamp(16px, 2vw, 28px) 0 0;
  text-align: left;
  color: #ffffff;
  opacity: 1;
  transform: none;
  transition: none;
  pointer-events: auto;
}
#work .work-row h3,
#work .work-row .work-row-meta {
  position: static;
  width: auto; height: auto;
  max-width: none;
  margin: 0;
  overflow: visible;
  clip: auto;
  text-transform: none;
}
#work .work-row h3 {
  font-size: clamp(1.25rem, 2.1vw, 2.25rem);
  font-weight: 700;
  letter-spacing: -0.04em;
  line-height: 1.1;
  white-space: normal;
  color: #ffffff;
}
#work .work-row .work-row-meta {
  flex: none;
  font-size: 0.875rem;
  font-weight: 400;
  letter-spacing: normal;
  line-height: 1.3;
  white-space: nowrap;
  text-align: right;
  color: rgba(255, 255, 255, 0.55);
}
#work .work-row:focus-visible { outline: 2px solid #ffffff; outline-offset: 6px; border-radius: 0; }

/* the "View project" label that stands in for the pointer over an image */
.work-cursor {
  position: fixed;
  left: 0; top: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  gap: 8px;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-400px, -400px, 0);
  transition: opacity 0.25s var(--ease);
}
.work-cursor.is-on { opacity: 1; }
.work-cursor-icon,
.work-cursor-text {
  transform: scale(0.6);
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.work-cursor.is-on .work-cursor-icon,
.work-cursor.is-on .work-cursor-text { transform: none; }
.work-cursor-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: #ffffff;
  color: #000000;
}
.work-cursor-icon svg { width: 16px; height: 16px; }
.work-cursor-text {
  display: flex;
  align-items: center;
  height: 36px;
  padding: 0 16px;
  border-radius: 50px;
  background: rgba(0, 0, 0, 0.35);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
  color: #ffffff;
}
.has-work-cursor #work .work-row-media { cursor: none; }

@media (max-width: 760px) {
  #work .work-list { grid-template-columns: 1fr; gap: 40px; }
  #work .work-row-text { flex-direction: column; gap: 6px; }
  #work .work-row .work-row-meta { white-space: normal; text-align: left; }
}
@media (prefers-reduced-motion: reduce) {
  #work .work-row-media img { transition: none; }
}

/* ── About as a poster ──────────────────────────────────────
   Inspired by a bold agency poster rather than copied from it: one
   saturated field in the site accent with black type only (5.1:1),
   an oversized uppercase headline, small bold uppercase labels pinned
   to the edges, and the portrait as a black-and-white object.

   The arrangement is this site's own: the headline set left across
   the full width, then three columns — the years, the portrait, the
   statement — resting on one baseline, and the four skills as a
   numbered strip beneath a black rule. */
#about.panel { background: var(--accent); color: #000000; border-top-color: transparent; }

#about .about-layout {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  padding-block: clamp(72px, 12vh, 136px) clamp(56px, 9vh, 104px);
}

#about .about-bar {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: clamp(24px, 4vh, 48px);
}
#about .eyebrow { margin: 0; }
#about .eyebrow,
#about .eyebrow::before { color: #000000; }
#about .eyebrow::after { background: rgba(0, 0, 0, 0.35); }
#about .about-place {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  text-transform: uppercase;
  color: #000000;
}

#about .about-title {
  grid-column: 1 / -1;
  max-width: none;
  margin: 0 0 clamp(40px, 7vh, 88px);
  font-size: clamp(2.75rem, 9.2vw, 9.5rem);
  font-weight: 700;
  letter-spacing: -0.055em;
  line-height: 0.94;                              /* 0.86 let the comma touch the line below */
  text-transform: uppercase;
  white-space: normal;
  color: #000000;
}

#about .about-body {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  align-items: end;
}
#about .about-stat { grid-column: 1 / span 4; }
#about .about-stat-figure {
  margin: 0;
  font-size: clamp(4.5rem, 10vw, 10rem);
  font-weight: 700;
  letter-spacing: -0.06em;
  line-height: 0.8;
  color: #000000;
}
#about .about-stat-figure span { font-size: 0.6em; vertical-align: top; }
#about .about-stat-label,
#about .about-lead {
  font-size: clamp(1rem, 1.4vw, 1.375rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.05;
  text-transform: uppercase;
  color: #000000;
}
#about .about-stat-label { max-width: 18ch; margin: 18px 0 0; }

#about .about-portrait {
  grid-column: 6 / span 3;
  aspect-ratio: 4 / 5;
  margin: 0;
  overflow: hidden;
  background: #000000;
}
#about .about-portrait img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 30% 22%;
  filter: grayscale(1) contrast(1.12);
  border-radius: 0;                               /* a shared img rule rounded only the top corners */
}

#about .about-statement { grid-column: 9 / span 4; text-align: right; }
#about .about-lead { max-width: 20ch; margin: 0 0 0 auto; }
#about .about-approach {
  max-width: 34ch;
  margin: 18px 0 0 auto;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.45;
  color: rgba(0, 0, 0, 0.82);
}

#about .about-skills {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  margin: clamp(48px, 8vh, 96px) 0 0;
  padding: 20px 0 0;
  border-top: 2px solid #000000;
  list-style: none;
}
#about .about-skills li { margin: 0; }
#about .about-skill-num {
  display: block;
  margin-bottom: 14px;
  font-size: 0.875rem;
  font-weight: 700;
  color: #000000;
}
#about .about-skills h3 {
  margin: 0 0 8px;
  font-size: clamp(1rem, 1.3vw, 1.25rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  text-transform: uppercase;
  color: #000000;
}
#about .about-skills p {
  max-width: 30ch;
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.45;
  color: rgba(0, 0, 0, 0.8);
}

@media (max-width: 860px) {
  #about .about-body { grid-template-columns: 1fr; row-gap: 32px; align-items: start; }
  #about .about-stat,
  #about .about-portrait,
  #about .about-statement { grid-column: 1; }
  #about .about-portrait { order: -1; width: 100%; max-width: 420px; }
  #about .about-statement { text-align: left; }
  #about .about-lead,
  #about .about-approach { margin-left: 0; }
  #about .about-skills { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 28px; }
}
@media (max-width: 520px) {
  #about .about-bar { flex-direction: column; gap: 8px; }
  #about .about-skills { grid-template-columns: 1fr; }
}

/* ── Contact: a lead and an index ───────────────────────────
   Shares the About poster's feel — heavy uppercase type, one accent,
   nothing decorative — without repeating its arrangement. The field
   is black. On the left, "Let's talk" is stacked huge in the accent
   (5.1:1 on black) with the rest of the line small beneath it; on the
   right, the ways to reach me read as an index: ruled rows, a small
   label, a large link, and an arrow that nudges on hover. */
#contact.panel { background: #000000; color: #ffffff; }

#contact .contact-layout {
  width: 100%;                                    /* the panel is a flex column; don't shrink to content when stacked */
  box-sizing: border-box;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  row-gap: 56px;
  align-items: end;
  padding-block: clamp(72px, 12vh, 136px) clamp(56px, 9vh, 104px);
}

#contact .contact-lead { grid-column: 1 / span 6; }
#contact .eyebrow { margin: 0 0 clamp(20px, 3vh, 32px); color: #ffffff; }
#contact .eyebrow::before { color: var(--accent); }
#contact .eyebrow::after { background: rgba(255, 255, 255, 0.3); }

#contact .contact-title {
  max-width: none;
  margin: 0;
  font-weight: 700;
  text-transform: uppercase;
  white-space: normal;
  color: #ffffff;
}
#contact .contact-title-big {
  display: block;
  font-size: clamp(4.5rem, 13.5vw, 13.5rem);
  letter-spacing: -0.06em;
  line-height: 0.82;
  color: var(--accent);
}
#contact .contact-title-small {
  display: block;
  margin-top: clamp(16px, 2.4vh, 28px);
  font-size: clamp(1.25rem, 2.4vw, 2.5rem);
  letter-spacing: -0.04em;
  line-height: 1;
  color: #ffffff;
}
#contact .contact-desc {
  max-width: 40ch;
  margin: 22px 0 0;
  font-size: 1rem;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.72);
}

#contact .contact-side { grid-column: 7 / span 6; }   /* wide enough for the address, arrow and copy button on one line */
#contact .contact-index {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid rgba(255, 255, 255, 0.28);
}
#contact .contact-row {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr);
  align-items: center;
  gap: 16px;
  padding: 22px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.28);
}
#contact .contact-row-label {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
}
#contact .contact-row-value {
  display: flex;
  flex-wrap: wrap;                                /* the copy button drops below rather than squeezing the address */
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
}
#contact .contact-row a,
#contact .direct-email {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3em;
  min-width: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  font-size: clamp(1.125rem, 1.75vw, 1.75rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.1;
  color: #ffffff;
  text-decoration: none;
  transition: color 0.3s var(--ease);
}
#contact .contact-arrow { display: inline-block; transition: transform 0.35s var(--ease); }
#contact .contact-row a:hover,
#contact .direct-email:hover { color: var(--accent); background: none; }
#contact .contact-row a:hover .contact-arrow { transform: translate(4px, -4px); }
#contact .contact-row a:focus-visible { outline: 2px solid #ffffff; outline-offset: 4px; }

#contact .email-copy {
  position: relative;
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 44px; height: 44px;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  background: transparent;
  color: #ffffff;
}
#contact .email-copy:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.6); }

/* an address is never broken mid-word */
#contact .direct-email span { overflow-wrap: normal; white-space: nowrap; }

/* below this the index column is too narrow beside the lead: stack them */
@media (max-width: 1100px) {
  #contact .contact-lead,
  #contact .contact-side { grid-column: 1 / -1; }
  #contact .contact-side { max-width: 720px; }
}
@media (max-width: 860px) {
  #contact .contact-title-big { font-size: clamp(4.5rem, 24vw, 9rem); }
}
@media (max-width: 420px) {
  #contact .contact-row a,
  #contact .direct-email { font-size: 1.125rem; }
}
@media (max-width: 520px) {
  #contact .contact-row { grid-template-columns: 1fr; gap: 8px; padding: 18px 0; }
}

/* ── Section headings: one system ───────────────────────────
   Home, Recent works, About and Contact each set their heading a
   different way. They now share the Contact treatment: one size, bold,
   uppercase, tight tracking and leading, with the section label and
   the gaps around the heading on the same two measures. Colour stays
   per section (white on black, black on the About red, the Contact
   accent phrase), and the hero title stays centred over the particles. */
:root {
  --section-title-size: clamp(2.75rem, 9.6vw, 10rem);
  --section-title-track: -0.055em;
  --section-title-leading: 0.9;
  --eyebrow-gap: clamp(20px, 3vh, 32px);          /* label → heading */
  --title-gap: clamp(40px, 7vh, 88px);            /* heading → content */
}

#top .hero-title,
#work .work-head .work-title,
#about .about-title,
#contact .contact-title-big {
  font-size: var(--section-title-size);
  font-weight: 700;
  letter-spacing: var(--section-title-track);
  line-height: var(--section-title-leading);
  text-transform: uppercase;
}
#top .hero-title { grid-column: 1 / -1; text-align: center; }

#top .hero-inner > .eyebrow,
#work .work-head .eyebrow,
#contact .contact-lead .eyebrow { margin-bottom: var(--eyebrow-gap); }
#about .about-bar { margin-bottom: var(--eyebrow-gap); }

#work .work-head { margin-bottom: var(--title-gap); }
#about .about-title { margin-bottom: var(--title-gap); }

/* ── Type scale specimen (portfolio case study) ─────────────
   Each row pairs a style's name, values and where it is used with a
   live sample set in that exact style — fluid sizes use the same
   clamp() as the site, so the sample is true at any width. */
.type-spec {
  margin: 44px 0 0;
  padding: clamp(22px, 3.2vw, 36px);
  background: #0a0a0a;
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
}
.type-group {
  margin: 0;
  padding: 36px 0 12px;
  font-size: 0.875rem;
  font-weight: 500;
  text-transform: uppercase;
  color: var(--accent);
}
.type-spec > .type-group:first-child { padding-top: 0; }
.type-row {
  display: grid;
  grid-template-columns: minmax(0, 14.5rem) minmax(0, 1fr);
  align-items: baseline;
  gap: 28px;
  padding: 18px 0;
  border-top: 1px solid var(--rule);
}
.type-meta { margin: 0; font-size: 0.875rem; line-height: 1.45; color: var(--faint); }
.type-meta b { display: block; margin-bottom: 2px; font-size: 1rem; font-weight: 500; color: var(--head); }
.type-meta span { display: block; }
.type-meta .type-use { color: var(--muted); }
.type-sample {
  margin: 0;
  min-width: 0;
  color: var(--head);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.type-sample.is-wrap { white-space: normal; max-width: 44ch; }
@media (max-width: 860px) {
  .type-row { grid-template-columns: 1fr; gap: 12px; }
}

/* ── H4: one style for subheads ─────────────────────────────
   The type review found two neighbouring heading styles: case-study
   subheads (24px · 500) and the About statement and skill titles
   (16–22px · 700 · caps). They are merged into a single H4, in
   sentence case so the longer case-study subheads stay easy to read. */
:root {
  --h4-size: clamp(1.125rem, 1.7vw, 1.5rem);      /* 18–24px */
  --h4-weight: 600;
  --h4-leading: 1.2;
  --h4-track: -0.02em;
}
.case-subhead,
#about .about-stat-label,
#about .about-lead,
#about .about-skills h3 {
  font-size: var(--h4-size);
  font-weight: var(--h4-weight);
  line-height: var(--h4-leading);
  letter-spacing: var(--h4-track);
  text-transform: none;
}
#about .about-stat-label { max-width: 20ch; }
#about .about-lead { max-width: 24ch; }

/* ── Body 3: one style for secondary text ───────────────────
   Body 3 (15px — overview labels, stat and skill descriptions) and
   Body 4 (14px — captions, meta, footer) sat a pixel apart. They are
   merged into a single Body 3 at 14px. */
:root {
  --body3-size: 0.875rem;                          /* 14px */
  --body3-weight: 400;
  --body3-leading: 1.5;
}
.overview-list dt,
.stat span,
#about .about-skills p,
#work .work-row .work-row-meta,
.footer-id,
.footer-id span {
  font-size: var(--body3-size);
  font-weight: var(--body3-weight);
  line-height: var(--body3-leading);
  letter-spacing: normal;
}

/* ── Labels: one style ──────────────────────────────────────
   The type review found label text set five different ways (14px bold
   caps, 13px, 11.5px tracked caps, 14px regular, 15px medium). Every
   label inside a section now uses Label 2 — the section label style —
   keeping only its colour. The top navigation keeps its own style. */
:root {
  --label-size: 0.75rem;                           /* 12px */
  --label-weight: 500;
  --label-leading: 1;
}
#about .about-bar .about-place,
#contact .contact-row .contact-row-label,
#about .about-skills .about-skill-num,
.case-hero .case-chips .chip,
.case-hero .case-chips .chip.chip-status,
.comp-chips .chip,
.comp-chips .chip.chip-status,
blockquote.quote cite,
.case-nav a .label,
.option-verdict dt.pc-label,
.spec .spec-title,
.audience .audience-label,
.goal .goal-num {
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  line-height: var(--label-leading);
  letter-spacing: normal;
  text-transform: uppercase;
}

/* ── Numbers use the heading scale ──────────────────────────
   The two numeric styles are gone from the type system: the About
   figure takes Display, and case-study metrics take H2. */
#about .about-stat .about-stat-figure {
  font-size: var(--section-title-size);
  font-weight: 700;
  line-height: var(--section-title-leading);
  letter-spacing: var(--section-title-track);
}
.stats .stat b {
  font-size: 40px;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.05em;
}

/* ── Top navigation uses the Label style ────────────────────
   Label 1 (the navigation's own 14px · 500 · 1.20 style) is gone: on
   desktop the header links take the single Label style. The full-screen
   mobile menu keeps its large links; the Contact pill is a button. */
@media (min-width: 861px) {
  .site-header .nav .nav-link {
    font-size: var(--label-size);
    font-weight: var(--label-weight);
    line-height: var(--label-leading);
    letter-spacing: normal;
    text-transform: uppercase;
  }
}
/* the nav-state figure in the portfolio case study shows the same text */
.state-grid .state-demo { line-height: var(--label-leading); text-transform: uppercase; }

/* ── About: H4 in capitals ──────────────────────────────────
   The years label and the four skill titles keep H4's size, weight,
   leading and tracking, set in capitals to match the poster. The
   statement beside the portrait stays in sentence case. */
#about .about-stat .about-stat-label,
#about .about-skills li h3 { text-transform: uppercase; }

/* Contact's "About what you're building" matches the About H4 in capitals */
#contact .contact-title .contact-title-small {
  font-size: var(--h4-size);
  font-weight: var(--h4-weight);
  line-height: var(--h4-leading);
  letter-spacing: var(--h4-track);
  text-transform: uppercase;
}

/* About skill descriptions use Body 2 */
#about .about-skills li p {
  font-size: 1rem;                                 /* 16px */
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
}

/* About skill numbers match their titles: H4 in capitals */
#about .about-skills li .about-skill-num {
  font-size: var(--h4-size);
  font-weight: var(--h4-weight);
  line-height: var(--h4-leading);
  letter-spacing: var(--h4-track);
  text-transform: uppercase;
}

/* About skill titles in sentence case (H4 as defined) */
#about .about-skills li h3,
#about .about-skills ol li h3 { text-transform: none; }

/* About years label in sentence case (H4 as defined) */
#about .about-body .about-stat .about-stat-label { text-transform: none; }

/* Contact subtitle in sentence case (H4 as defined), first letter capitalised */
#contact .contact-lead .contact-title .contact-title-small { text-transform: none; }
#contact .contact-lead .contact-title .contact-title-small::first-letter { text-transform: uppercase; }

/* About skill numbers use the Label style */
#about ol.about-skills li span.about-skill-num {
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  line-height: var(--label-leading);
  letter-spacing: normal;
  text-transform: uppercase;
}

/* About subtitle under the title: Body 1 */
#about .about-layout .about-title { margin-bottom: clamp(20px, 3vh, 32px); }
#about .about-subtitle {
  grid-column: 1 / span 8;
  max-width: 44ch;
  margin: 0 0 var(--title-gap);
  font-size: 20px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: -0.03em;
  color: #000000;
}
@media (max-width: 860px) { #about .about-subtitle { grid-column: 1 / -1; } }

/* About subtitle uses H2 */
#about .about-layout .about-subtitle {
  grid-column: 1 / span 10;
  max-width: 28ch;
  font-size: 40px;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.05em;
}
@media (max-width: 860px) { #about .about-layout .about-subtitle { grid-column: 1 / -1; max-width: none; } }

/* ── H2 is fluid: 28px on phones, 40px on desktop ─────────── */
:root { --h2-size: clamp(1.75rem, 3.2vw, 2.5rem); }
.case-section h2,
.stats .stat b,
#about .about-layout .about-subtitle { font-size: var(--h2-size); }

/* ── About: title → statement → portrait → experience ──────
   Two connected groups. The statement sits straight under the title as
   its deck, so the two read as one message, with the portrait centred
   beneath. Below a black rule the years and the four skills form one
   experience row: 10+ on the left, the skills in a 2×2 grid beside it.
   Type styles are unchanged. */
#about .about-layout > .about-title { margin-bottom: clamp(24px, 4vh, 40px); }
@media (min-width: 861px) {
  #about .about-layout > .about-bar {
    grid-column: 1 / -1;
    grid-row: 1;
    align-self: start;
    margin: 0;
    padding-top: calc(var(--section-title-size) * 0.1);
    position: relative;
    z-index: 1;
  }
  #about .about-layout > .about-title { grid-column: 1 / -1; grid-row: 1; text-align: center; }
  #about .about-layout > .about-statement { justify-self: center; text-align: center; }
  #about .about-layout > .about-statement .about-lead { margin-inline: auto; }
  #about .about-layout > .about-portrait { grid-column: 5 / span 4; }
}

#about .about-layout > .about-statement {
  grid-column: 1 / -1;
  margin: 0 0 var(--title-gap);
  text-align: left;
}
/* H4, not H3. This rule used to re-impose H3 on top of the H4 group
   above, which already lists .about-lead; it now defers to the same
   --h4-* tokens so the statement tracks the scale if H4 ever moves. */
#about .about-layout > .about-statement .about-lead {
  max-width: 34ch;
  margin: 0;
  font-size: var(--h4-size);
  font-weight: var(--h4-weight);
  line-height: var(--h4-leading);
  letter-spacing: var(--h4-track);
  text-transform: none;
}

#about .about-layout > .about-portrait {
  grid-column: 1 / -1;
  order: 0;
  width: auto;
  max-width: none;
  aspect-ratio: 1 / 1;
  margin: 0;
}
#about .about-layout > .about-portrait img { object-position: 30% 18%; }

#about .about-layout > .about-experience {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  align-items: start;
  margin-top: clamp(64px, 10vh, 120px);
  padding-top: 28px;
  border-top: 2px solid #000000;
}
#about .about-experience .about-stat { grid-column: 1 / span 4; text-align: left; }
#about .about-experience .about-stat .about-stat-label { max-width: 16ch; margin-left: 0; }
#about .about-layout .about-experience .about-skills {
  grid-column: 5 / span 8;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  row-gap: 40px;
  margin: 0;
  padding: 0;
  border-top: 0;
}

@media (max-width: 860px) {
  #about .about-layout > .about-portrait { max-width: 420px; }
  #about .about-layout > .about-experience { grid-template-columns: 1fr; row-gap: 36px; }
  #about .about-experience .about-stat,
  #about .about-layout .about-experience .about-skills { grid-column: 1; }
}
@media (max-width: 520px) {
  #about .about-layout .about-experience .about-skills { grid-template-columns: 1fr; row-gap: 28px; }
}

/* About fixes: centred portrait and statement text on wider screens; years label in sentence case */
@media (min-width: 861px) {
  #about .about-layout > figure.about-portrait { grid-column: 5 / span 4; }
  #about .about-layout > div.about-statement { justify-self: center; text-align: center; }
}
#about .about-layout .about-experience .about-stat p.about-stat-label { text-transform: none; }

/* About: tighter vertical rhythm between title, statement, portrait and skills */
#about .about-layout > h2.about-title { margin-bottom: clamp(16px, 2vh, 24px); }
#about .about-layout > div.about-statement { margin-bottom: clamp(32px, 4.5vh, 48px); }
#about .about-layout > div.about-experience { margin-top: clamp(40px, 5.5vh, 56px); }

/* About: tighter overall rhythm — section padding, internal gaps, no forced full-screen height */
#about.panel { min-height: 0; }
#about .wrap.about-layout { padding-block: clamp(56px, 8vh, 96px) clamp(48px, 6vh, 72px); }
#about .about-layout > h2.about-title { margin-bottom: clamp(12px, 1.5vh, 16px); }
#about .about-layout > div.about-statement { margin-bottom: clamp(24px, 3vh, 32px); }
/* 20px left the "10+" figure and the 01–04 labels sitting tight under
   the 2px rule — 40px to their tops once each element's own box is
   counted. 32px is the Spacing spec's named measure and sits inside
   the 18–36px band it states for component padding. margin-top is
   untouched: the space above the rule already reads correctly. */
#about .about-layout > div.about-experience { margin-top: clamp(28px, 3.5vh, 40px); padding-top: 32px; }
#about .about-layout .about-experience .about-skills { row-gap: 28px; }
#about .about-layout .about-experience .about-skills li h3 { margin-bottom: 6px; }
@media (max-width: 860px) {
  #about .about-layout > div.about-experience { row-gap: 28px; }
}

/* About: one layer of section padding instead of two stacked ones */
#about.panel.about-poster { padding-block: 0; }
#about.panel .wrap.about-layout { padding-block: clamp(64px, 9vh, 104px) clamp(56px, 8vh, 88px); }

/* About: trim the top and bottom edges. Scroll snap already stops the section
   below the sticky header, so the top padding does not need to clear it. */
#about.panel.about-poster .wrap.about-layout {
  padding-top: clamp(32px, 4vh, 48px);
  padding-bottom: clamp(32px, 4vh, 48px);
}

/* About portrait a little smaller, still centred */
@media (min-width: 861px) {
  #about .about-layout > figure.about-portrait { width: 78%; justify-self: center; }
}
@media (max-width: 860px) {
  #about .about-layout > figure.about-portrait { max-width: 340px; }
}

/* ── Footer: oversized wordmark ─────────────────────────────
   The name set as one line that runs the full content width, in the
   Display style (bold, caps, tight tracking), with the copyright and
   back-to-top row small beneath it. js/main.js sizes the line so it
   fills the width exactly at any screen size. */
.site-footer .footer-wordmark {
  display: block;
  width: 100%;
  max-width: var(--page);
  margin: 0 auto;
  padding: clamp(40px, 6vh, 72px) var(--gut) 0;
  box-sizing: border-box;
  overflow: hidden;
  font-size: 12vw;                                 /* fallback before the fit runs */
  font-weight: 700;
  line-height: 0.8;
  letter-spacing: -0.055em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #ffffff;
}
.site-footer .footer-wordmark span { display: inline-block; padding-right: 0.06em; }
.site-footer .footer-grid { padding-top: clamp(16px, 2vh, 24px); }

/* the wordmark replaces the divider: no rule cutting through the letters */
.site-footer .footer-wordmark { line-height: 0.9; padding-bottom: 0.04em; }
.site-footer .footer-grid,
.site-footer { border-top: 0; }
.site-footer .footer-grid { padding-top: clamp(20px, 2.5vh, 32px); }

/* ── One vertical rhythm for every section ──────────────────
   Recent works, About and Contact share the same top and bottom
   padding; the hero keeps its full-screen panel. */
:root { --section-pad: 96px; }
#work.panel .work-pin,
#about.panel.about-poster .wrap.about-layout,
#contact.panel .wrap.contact-layout {
  padding-top: var(--section-pad);
  padding-bottom: var(--section-pad);
}
#work.panel, #contact.panel { padding-block: 0; }

/* Contact: no forced full-screen height, so its 80px padding is the whole top gap */
#contact.panel.contact-panel { min-height: 0; justify-content: flex-start; }

/* Contact description uses H3 */
#contact .contact-lead .contact-desc {
  max-width: 24ch;
  margin-top: clamp(20px, 2.5vh, 28px);
  font-size: clamp(1.25rem, 2.1vw, 2.25rem);       /* 20–36px */
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.04em;
  color: #ffffff;
}

/* ── Contact, rebuilt on the site's own patterns ────────────
   Top row: the red Display headline on the left with the H3 line
   resting on its baseline at the right. Beneath, the three ways to
   reach me sit under one rule as three columns — the same shape as the
   About skills row — each a small Label above an H3 link. */
#contact .contact-layout {
  align-items: end;
  row-gap: 0;
}
#contact .contact-layout > .contact-lead { grid-column: 1 / span 7; }
#contact .contact-layout > .contact-desc {
  grid-column: 8 / span 5;
  align-self: end;
  max-width: 26ch;
  margin: 0 0 0.35em;
}
#contact .contact-layout > .contact-side {
  grid-column: 1 / -1;
  margin-top: clamp(48px, 7vh, 88px);
}
#contact .contact-side .contact-index {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  border-top: 2px solid rgba(255, 255, 255, 0.32);
}
#contact .contact-side .contact-index .contact-row {
  display: block;
  padding: 24px 0 0;
  border-bottom: 0;
}
#contact .contact-side .contact-index .contact-row-label { margin-bottom: 10px; }
#contact .contact-side .contact-index .contact-row-value { justify-content: flex-start; gap: 16px; }

@media (max-width: 860px) {
  #contact .contact-layout > .contact-lead,
  #contact .contact-layout > .contact-desc,
  #contact .contact-layout > .contact-side { grid-column: 1 / -1; }
  #contact .contact-layout > .contact-desc { margin: 20px 0 0; max-width: none; }
  #contact .contact-layout > .contact-side { margin-top: 40px; }
  #contact .contact-side .contact-index { grid-template-columns: 1fr; }
  #contact .contact-side .contact-index .contact-row { padding: 20px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.28); }
  #contact .contact-side .contact-index .contact-row:last-child { border-bottom: 0; }
}

/* Contact index: keep the address, arrow and copy button on one line in narrower columns */
@media (min-width: 861px) and (max-width: 1200px) {
  #contact .contact-side .contact-index .contact-row a { font-size: 1.25rem; }
  #contact .contact-side .contact-index .email-copy { width: 36px; height: 36px; }
  #contact .contact-side .contact-index .contact-row-value { gap: 12px; }
}

/* the email column carries the longest value plus the copy button: give it more width */
@media (min-width: 861px) and (max-width: 1200px) {
  #contact .contact-side .contact-index { grid-template-columns: 1.5fr 1fr 1fr; }
}

/* Contact description keeps H3 in its place beside the headline */
/* H4, matching the About statement — both are the one supporting line
   under a section title, so they carry the same weight. */
#contact .contact-layout > .contact-desc {
  font-size: var(--h4-size);
  font-weight: var(--h4-weight);
  line-height: var(--h4-leading);
  letter-spacing: var(--h4-track);
  color: #ffffff;
  max-width: 30ch;
}

/* Contact: headline on one line with the description directly beneath it */
#contact .contact-layout > .contact-lead { grid-column: 1 / -1; }
#contact .contact-layout > .contact-desc {
  grid-column: 1 / -1;
  align-self: auto;
  max-width: 34ch;
  margin: clamp(20px, 2.5vh, 28px) 0 0;
}

/* ── Contact index built like the hero's meta row ───────────
   A rule above and below, the three entries divided by hairlines and
   padded evenly inside their cells. On phones the dividers give way to
   a plain stacked list, exactly as the hero row does. */
#contact .contact-side .contact-index {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
#contact .contact-side .contact-index .contact-row {
  padding: clamp(20px, 2.4vh, 28px) clamp(20px, 2vw, 32px);
  border-left: 1px solid var(--rule);
  border-bottom: 0;
}
#contact .contact-side .contact-index .contact-row:first-child {
  padding-left: 0;
  border-left: 0;
}
@media (min-width: 861px) and (max-width: 1200px) {
  #contact .contact-side .contact-index { grid-template-columns: 1.5fr 1fr 1fr; }
}
@media (max-width: 860px) {
  #contact .contact-side .contact-index { border-bottom: 1px solid var(--rule); }
  #contact .contact-side .contact-index .contact-row {
    padding: 20px 0;
    border-left: 0;
    border-bottom: 1px solid var(--rule);
  }
  #contact .contact-side .contact-index .contact-row:last-child { border-bottom: 0; }
}

/* phones: the index stacks again (the three-column rule above must not apply here) */
@media (max-width: 860px) {
  #contact .contact-side .contact-index { grid-template-columns: 1fr; }
}

/* ── Back to top: round icon button ─────────────────────────
   The text link becomes a circular icon button, built from the same
   control as the email copy button: 44px circle, hairline border,
   filling slightly on hover. The label stays for screen readers. */
.site-footer .footer-top {
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  color: #ffffff;
  text-decoration: none;
  transition: background-color 0.3s var(--ease), border-color 0.3s var(--ease), transform 0.3s var(--ease);
}
.site-footer .footer-top svg { width: 16px; height: 16px; }
.site-footer .footer-top:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.6);
  transform: translateY(-2px);
}
.site-footer .footer-top:focus-visible { outline: 2px solid #ffffff; outline-offset: 3px; }

/* the section labels follow the Label size too (their own rules set 14px) */
.eyebrow,
.hero .eyebrow,
.section-head .eyebrow,
.panel.is-dark .eyebrow,
.contact-panel .eyebrow,
#contact .eyebrow,
#work .section-head .eyebrow,
#about .about-bar .eyebrow,
#contact .contact-lead .eyebrow,
.case-section .eyebrow { font-size: var(--label-size); }

/* ── More projects: thumbnail carousel ──────────────────────
   Closes each case study with the other projects as cards — the same
   8:6 frame, H3 name and Label caption as Recent works — in a row that
   scrolls sideways. The round arrows are the footer button; they show
   only when the row actually overflows. */
.more-projects { padding-block: var(--section-pad); border-top: 1px solid var(--rule); }
.more-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: clamp(20px, 3vh, 32px);
}
.more-head .eyebrow { margin: 0; }
.more-controls { display: flex; gap: 10px; }
/* .comp-icon is the Components spec's stand-in: it shares this rule
   so the specimen cannot drift from the shipped control, but keeps a
   separate class because the carousel script binds to every .more-btn
   on the page and would wire the specimen to the live track. */
.more-btn,
.comp-icon {
  display: inline-grid;
  place-items: center;
  width: 44px; height: 44px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  background: transparent;
  color: #ffffff;
  transition: background-color 0.3s var(--ease), border-color 0.3s var(--ease), opacity 0.3s var(--ease);
}
.more-btn.is-prev svg { transform: rotate(180deg); }
.more-btn:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.6); }
.more-btn:focus-visible { outline: 2px solid #ffffff; outline-offset: 3px; }
.more-btn[disabled] { opacity: 0.35; pointer-events: none; }
.more-btn svg, .comp-icon svg { width: 16px; height: 16px; }
.more-controls[hidden] { display: none; }

.more-track {
  display: flex;
  gap: var(--grid-gap);
  margin: 0;
  padding: 0 0 4px;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.more-track::-webkit-scrollbar { display: none; }
.more-track > li { flex: 0 0 clamp(240px, 30vw, 380px); scroll-snap-align: start; }

.more-card { display: block; color: inherit; text-decoration: none; }
.more-media {
  display: block;
  aspect-ratio: 8 / 6;
  overflow: hidden;
  background: #111111;
}
.more-media img {
  display: block;
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1);
  transition: transform 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}
.more-card:hover .more-media img,
.more-card:focus-visible .more-media img { transform: scale(1.06); }
.more-text { display: block; padding-top: 16px; }
.more-title {
  display: block;
  font-size: clamp(1.125rem, 1.6vw, 1.5rem);
  font-weight: 700;
  letter-spacing: -0.04em;
  line-height: 1.15;
  color: var(--head);
}
.more-meta {
  display: block;
  margin-top: 6px;
  font-size: var(--label-size);
  font-weight: 500;
  line-height: 1;
  text-transform: uppercase;
  color: var(--muted);
}
.more-card:focus-visible { outline: 2px solid #ffffff; outline-offset: 6px; }

@media (max-width: 760px) {
  .more-track > li { flex-basis: 74vw; }
}

/* Recent works thumbnails take the system's media radius (the same as case study figures) */
#work .work-list .work-row-media { border-radius: var(--r-md); }
#work .work-list .work-row-media img { border-radius: inherit; }

/* ── Image radius scale ─────────────────────────────────────
   Three radii for pictures, picked by how large the image renders:
   the bigger the picture, the softer its corners. Interface controls
   keep their own shapes — pills for buttons and chips, circles for
   icon buttons. */
:root {
  --r-img-sm: 8px;                                 /* under ~300px */
  --r-img-md: 12px;                                /* ~300–800px */
  --r-img-lg: 16px;                                /* ~800px and wider */
}
.case-cover img,
.case-flow figure img,
.figure-pair img { border-radius: var(--r-img-lg); }

#work .work-list .work-row-media,
#work .work-list .work-row-media img,
.more-media,
.more-media img,
.option-shots img { border-radius: var(--r-img-md); }

#about .about-portrait,
#about .about-portrait img { border-radius: var(--r-img-sm); }

/* the radius specimen in the case study */
.radius-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; }
.radius-swatch {
  display: block;
  height: 104px;
  background: #1c1c1c;
  border: 1px solid var(--rule-strong);
}
.radius-name {
  margin: 14px 0 0;
  font-size: 1rem;
  font-weight: 500;
  color: var(--head);
}
.radius-name span {
  display: block;
  margin-top: 4px;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.45;
  color: var(--muted);
}
@media (max-width: 760px) { .radius-grid { grid-template-columns: 1fr; gap: 20px; } }

/* ────────────────────────────────────────────────────────────
   Colour spec — swatch cards. A filled block over a labelled
   footer, so each token is read as one object. Chips carry a
   hairline because #000 and #FFF would otherwise dissolve into
   the card and the page.
   ──────────────────────────────────────────────────────────── */
.color-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px;
}
.color-card {
  overflow: hidden;
  background: #141414;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
}
.color-chip {
  display: block;
  height: 132px;
  border-bottom: 1px solid var(--rule);
}
.color-name {
  margin: 0;
  padding: 15px 16px 17px;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.2;
  color: var(--head);
}
.color-name span {
  display: block;
  margin-top: 5px;
  font-family: var(--mono);
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--muted);
}
.color-name em {
  display: block;
  margin-top: 7px;
  font-style: normal;
  font-size: 0.8125rem;
  font-weight: 400;
  line-height: 1.45;
  color: var(--muted);
}
@media (max-width: 860px) {
  .color-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
  .color-chip { height: 112px; }
}
@media (max-width: 460px) {
  .color-grid { grid-template-columns: 1fr; }
}

/* ────────────────────────────────────────────────────────────
   Components spec. Demos reuse the real component classes where
   that is safe (.contact-pill, .chip). The carousel is the
   exception: its script binds globally to .more-track and every
   .more-btn on the page, so a second set of those classes here
   would hijack the live carousel at the page foot. The demo uses
   .comp-* classes instead, with .comp-icon folded into the real
   .more-btn rule so the two cannot drift.
   ──────────────────────────────────────────────────────────── */
.comp-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}
.comp-cell {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 24px 18px 18px;
  background: #000000;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
}
.comp-demo { display: flex; align-items: center; gap: 12px; min-height: 46px; }
.comp-name { margin: 0; font-size: 0.875rem; color: var(--ink-2); }
.comp-name span { display: block; margin-top: 4px; line-height: 1.45; color: var(--faint); }

/* chips */
.comp-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* card specimen. No stand-in needed — .point is a global rule, so the
   specimen is the shipped component rather than a copy of it. */
.comp-points { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }

/* carousel anatomy */
.comp-carousel {
  display: flex;
  gap: var(--grid-gap);
  margin: 0;
  padding: 0;
  list-style: none;
  overflow: hidden;
}
.comp-carousel > li { flex: 0 0 clamp(170px, 30%, 260px); }
.comp-carousel-media {
  display: block;
  aspect-ratio: 8 / 6;
  overflow: hidden;
  background: #111111;
  border-radius: var(--r-img-md);
}
.comp-carousel-media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.comp-carousel-title {
  display: block;
  margin-top: 14px;
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.15;
  color: var(--head);
}
.comp-carousel-meta {
  display: block;
  margin-top: 6px;
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  line-height: 1;
  text-transform: uppercase;
  color: var(--muted);
}
.comp-carousel-foot { display: flex; align-items: center; gap: 10px; margin-top: 26px; }
.comp-note { margin: 0 0 0 6px; font-size: 0.875rem; line-height: 1.45; color: var(--faint); }

/* shared fact list under a specimen */
.comp-list { margin: 22px 0 0; padding: 0; list-style: none; }
.comp-list li {
  display: grid;
  grid-template-columns: minmax(0, 7.5rem) minmax(0, 1fr);
  gap: 20px;
  padding: 12px 0;
  border-top: 1px solid var(--rule);
  font-size: 0.875rem;
  line-height: 1.45;
  color: var(--faint);
}
.comp-list li:first-child { border-top: 0; padding-top: 0; }
.comp-list b { font-weight: 500; color: var(--ink-2); }

@media (max-width: 860px) {
  .comp-grid { grid-template-columns: 1fr; }
  .comp-carousel > li { flex-basis: 44%; }
  .comp-list li { grid-template-columns: 1fr; gap: 4px; }
  .comp-points { grid-template-columns: 1fr; }
}

/* ────────────────────────────────────────────────────────────
   One icon button, three placements. The hero scroll cue was a
   74px ring holding the words "Scroll down", filling with the
   accent on hover and carrying no focus style at all. The Contact
   back-to-top and the carousel arrows were already a 44px circle
   with a 30% hairline, a 10% white wash and a visible focus ring.
   They are one control now, and the only thing that differs
   between the three is which way the arrow points.

   44px wins over 74px because that size already serves three
   controls and is what the Components spec documents; a two-line
   label cannot survive inside it, so the words become a ↓ glyph
   and the accessible name stays on the visually-hidden span.

   Appended last so it beats the five earlier .scroll-cue-ring
   layers (971, 1018, 1457, 1568, 1914) without unpicking each.
   `animation: none` drops the idle bob: an animated transform
   outranks the hover lift, so keeping it would make the shared
   hover state quietly fail to apply.
   ──────────────────────────────────────────────────────────── */
.scroll-cue .scroll-cue-ring {
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: transparent;
  color: #ffffff;
  animation: none;
  transition: background-color 0.3s var(--ease), border-color 0.3s var(--ease), transform 0.3s var(--ease);
}
.scroll-cue .scroll-cue-ring svg { width: 16px; height: 16px; }
.scroll-cue:hover .scroll-cue-ring {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.6);
  transform: translateY(-2px);
}
.scroll-cue:focus-visible { outline: none; }
.scroll-cue:focus-visible .scroll-cue-ring { outline: 2px solid #ffffff; outline-offset: 3px; }

/* ────────────────────────────────────────────────────────────
   Thumbnail frame for dark artwork. Most project thumbnails are
   photographic and draw their own edge; the portfolio one is a
   near-black field on a near-black page, so its 8:6 frame simply
   vanishes. `.has-frame` lends it the system's own grey hairline —
   no new colour, just --rule-strong.

   --rule-strong rather than --rule: composited on the black ground,
   14% white lands on rgb(36,36,36) for 1.35:1, which is at the very
   edge of perceptible and defeats the point of drawing an edge at
   all. 30% gives rgb(76,76,76) at 2.45:1 — a line you can actually
   see, still quiet enough to read as a frame rather than a border.

   Drawn as an overlay rather than a border or an inset shadow: a
   border would shrink the 8:6 content box and rescale the image,
   and an inset box-shadow paints beneath replaced content, so the
   <img> would hide it. The media box is already position:relative.
   ──────────────────────────────────────────────────────────── */
#work .work-list .work-row-media.has-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid var(--rule-strong);
  border-radius: inherit;
  pointer-events: none;
}

/* ────────────────────────────────────────────────────────────
   Design directions opens straight on its first subhead now that
   the section intro is gone, so that subhead drops its divider —
   a rule immediately under the H2 reads as a stray line rather
   than a separator between two blocks.

   Scoped to this section's first subhead only: .case-subhead's
   border-top (4110) is shared by every subhead on the page, and
   removing the declaration would strip the dividers from
   Typography, Colors, Buttons, Card, Carousel and the rest.
   ──────────────────────────────────────────────────────────── */
#direction .case-subhead:first-of-type {
  padding-top: 0;
  border-top: 0;
}

/* ────────────────────────────────────────────────────────────
   Divider specimen, drawn with the real --rule token so the spec
   cannot drift from the site (58 uses — type rows, subheads, spec
   panels, the hero meta row).

   Only the hairline is shown. --rule-strong at 30% is still in the
   stylesheet and still in use, but it outlines objects — the 44px
   icon buttons, the scroll cue, the dark thumbnail frame — which
   makes it a border, not a divider, so it belongs with those
   components rather than here.
   ──────────────────────────────────────────────────────────── */
.rule-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}
.rule-line {
  display: block;
  height: 1px;
  margin-top: 6px;
  background: var(--rule);
}
.rule-name {
  margin: 18px 0 0;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.2;
  color: var(--head);
}
.rule-name span {
  display: block;
  margin-top: 5px;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.45;
  color: var(--muted);
}

/* ────────────────────────────────────────────────────────────
   Spacing specimen. The track and fill are the Performance
   section's own .bar-track / .bar-fill, so the two measure
   readouts on this page stay one component; only the row grid
   is new, because .bar-row is built for benchmark labels at
   13rem and would crowd a three-character value like "8px".
   ──────────────────────────────────────────────────────────── */
.space-scale { display: grid; gap: 14px; }
.space-row {
  display: grid;
  grid-template-columns: minmax(0, 4rem) minmax(0, 1fr) minmax(0, 20rem);
  align-items: center;
  gap: 18px;
}
.space-val {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.space-use { font-size: 0.875rem; line-height: 1.45; color: var(--faint); }
@media (max-width: 860px) {
  .space-row { grid-template-columns: minmax(0, 3.5rem) minmax(0, 1fr); row-gap: 6px; }
  .space-use { grid-column: 1 / -1; }
}

/* Spacing steps are drawn at their true size, not as a share of a
   track. The first attempt reused .bar-track / .bar-fill from the
   Performance benchmark, which fills a full-width rail in
   proportion — so "8px" rendered 31px wide inside a 382px rail and
   read as a chart. A spec that states measurements has to show
   them literally, so the rail is a fixed 116px and each step is
   exactly as wide as the number beside it. */
.space-rail {
  display: block;
  width: 116px;
  height: 14px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 3px;
  overflow: hidden;
}
.space-step {
  display: block;
  height: 100%;
  background: rgba(255, 255, 255, 0.55);
  border-radius: 3px;
}
.space-row { grid-template-columns: minmax(0, 4rem) 116px minmax(0, 1fr); }
@media (max-width: 860px) {
  .space-row { grid-template-columns: minmax(0, 3.5rem) 116px; row-gap: 6px; }
}

/* ────────────────────────────────────────────────────────────
   One divider everywhere. The panel tops were the last separator
   still on an ad-hoc value: .panel set border-top-color to 10%
   white (908, and again at 1002), a hair under the 14% hairline
   every other separator on the site uses. Appended rather than
   edited into those two legacy layers, so the token wins by order.

   Deliberately NOT normalised, because neither is a divider:
   .about-experience is a 2px black rule anchoring the skills
   block on the orange panel (a 12% hairline would vanish there),
   and .quote's left bar is the 30% edge weight marking a pull
   quote rather than parting two things.
   ──────────────────────────────────────────────────────────── */
.panel { border-top-color: var(--rule); }

/* ────────────────────────────────────────────────────────────
   No divider between subsections. .case-subhead carried a 1px
   hairline above it (4107); the space alone now does the parting,
   across all 27 subheads on the four case studies.

   The 22px padding-top stays, so the vertical rhythm is exactly
   as it was and only the line is gone.

   This also retires two exceptions that existed purely to hide
   that border — #final-design (4514) and #direction (6255) — by
   restoring their padding. Left as they were, those two subheads
   would sit 22px tighter than the other 25.
   ──────────────────────────────────────────────────────────── */
.case-subhead { border-top: 0; }
#final-design .case-subhead:first-of-type,
#direction .case-subhead:first-of-type {
  border-top: 0;
  padding-top: 22px;
}

/* ────────────────────────────────────────────────────────────
   Full-screen menu links take H1. Home, Recent work and About
   move from 32px/500 to the H1 step — 44–88px, 700, 1.0,
   −0.05em — so the open menu reads like a page title rather than
   a list. The Contact pill is untouched; it stays a button.

   Appended as its own max-width:860px block because TWO earlier
   rules set the size: 4241 (the base mobile block) and 4282,
   which re-declares it smaller under max-width:560px. Overriding
   only the first would leave every phone under 560px unchanged.
   ──────────────────────────────────────────────────────────── */
@media (max-width: 860px) {
  .site-header .nav .nav-link {
    font-size: clamp(2.75rem, 7vw, 5.5rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.05em;
  }
}

/* ── Hero background photo ──────────────────────────────────
   A backlit silhouette fills the hero behind the dot field and the
   headline. The photo's ground runs up to near-white at the top
   right, so a black scrim sits over it: the headline keeps AA
   contrast wherever it lands, and the bottom fades fully to black
   so the hero meets the Work section without a seam.

   Stacking: .hero-bg (0) → .hero-motion (0, later in the DOM, so
   above) → .hero-inner (2) → .scroll-cue (3).
   ──────────────────────────────────────────────────────────── */
.hero-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  container-type: size;
  --crop-x: 0.5;                  /* where the cover crop sits, 0–1; the */
  --crop-y: 0.4;                  /* hero light reads these to find the glow */
}

/* ── The photograph breathes ────────────────────────────────
   The hero is one photograph, whole — no cut-out layers, so there is
   no matte edge anywhere to show as a line around the face. The life
   comes from the frame instead: the picture drifts in by 2% and lifts
   a fraction, then eases back, on a slow nine-second breath centred
   on the face. `alternate` makes the loop seamless, and because it
   only ever scales up, the edges never show.

   .hero-stage does the cover crop itself: container units size it to
   the larger of the hero's width or its height at the photo's aspect,
   and --crop-x / --crop-y place it; the hero light reads the same two
   values to sit on the photo's bright spot.
   ──────────────────────────────────────────────────────────── */
.hero-stage {
  --w: max(100cqw, calc(100cqh * 895 / 1200));
  --h: calc(var(--w) * 1200 / 895);
  position: absolute;
  width: var(--w);
  height: var(--h);
  left: calc((100cqw - var(--w)) * var(--crop-x));
  top: calc((100cqh - var(--h)) * var(--crop-y));
}
.hero-stage img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.hero-photo {
  transform-origin: 55% 40%;                 /* the face */
  animation: hero-breathe 9s cubic-bezier(0.45, 0, 0.55, 1) infinite alternate;
  will-change: transform;
}
@keyframes hero-breathe {
  from { transform: scale(1) translateY(0); }
  to   { transform: scale(1.02) translateY(-0.3%); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-photo { animation: none; }
}
.hero-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  /* a studio backdrop rather than open space: the ground keeps its
     light-to-dark gradient, a soft vignette closes the corners so the
     room has no edges you can place, and the foot runs to black */
  background:
    radial-gradient(ellipse 75% 70% at 55% 38%, rgba(0, 0, 0, 0) 45%, rgba(0, 0, 0, 0.38) 100%),
    linear-gradient(to bottom, rgba(0, 0, 0, 0) 55%, #000 100%),
    rgba(0, 0, 0, 0.55);
}
.js .hero-bg { opacity: 0; }
.js .hero.in-view .hero-bg { animation: hero-bg-in 1.4s ease-out forwards; }
@keyframes hero-bg-in { to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .js .hero-bg { opacity: 1; animation: none; }
}

/* ── Hero: three lines, stepped ─────────────────────────────
   One statement in three stepped lines, built only from documented
   styles:

     EUNSUNG SONG · PRODUCT DESIGNER ──     Label, Muted, label rule
     SIMPLIFYING                            Display, Type white
     COMPLEX                                Display, Muted
         PRODUCTS                           Display, Type white

   The step: the lines sit in an inline grid as wide as its longest
   line, and the last line is justified to that grid's end, so
   PRODUCTS finishes exactly under the end of SIMPLIFYING at any size
   without counting characters. Label → heading uses --eyebrow-gap.
   The lockup is centred on the photograph, which stays sharp.
   ──────────────────────────────────────────────────────────── */
.hero .hero-inner {
  align-content: center;
  justify-items: center;
  text-align: left;
  padding-top: var(--header-h);
}
#top .hero-inner > .hero-lockup { grid-column: 1 / -1; justify-self: center; }

.hero-lockup .hero-label {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 var(--eyebrow-gap);
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  line-height: var(--label-leading);
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--muted);
}
.hero-lockup .hero-label::after {
  content: "";
  width: 48px;
  height: 1px;
  background: var(--rule);
}

#top .hero-lockup .hero-lines {
  display: inline-grid;
  margin: 0;
  font-size: var(--section-title-size);
  font-weight: 700;
  line-height: var(--section-title-leading);
  letter-spacing: var(--section-title-track);
  text-transform: uppercase;
  color: var(--ink);
}
.hero-lines > span { white-space: nowrap; }
.hero-lines > span:nth-child(2) { color: var(--muted); }
.hero-lines > span:nth-child(3) { justify-self: end; }

.hero .scroll-cue {
  --rail-edge: max(var(--gut), calc((100% - var(--page)) / 2 + var(--gut)));
  left: var(--rail-edge);
  right: var(--rail-edge);
  bottom: clamp(24px, 4vh, 40px);
  width: auto;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  pointer-events: none;           /* the rail is open space; only its two ends click */
}
.hero .scroll-cue > * { pointer-events: auto; }
.hero .scroll-cue .scroll-cue-label {
  font-size: var(--label-size);
  font-weight: var(--label-weight);
  line-height: var(--label-leading);
  letter-spacing: normal;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
}
.hero .scroll-cue .scroll-cue-label::after { content: none; }
.hero .scroll-cue:hover .scroll-cue-label { color: #ffffff; }
.hero .scroll-cue:focus-visible .scroll-cue-label { text-decoration: underline; text-underline-offset: 4px; }


/* ── Top nav: type on the page ──────────────────────────────
   No bar and no pill: the header is the logo, the section links and
   Contact set straight on the page, aligned to the 1240px page and its
   gutters like every section below — logo at the left, links centred
   on the page, Contact at the right edge as a text link.

   Over the hero it is fully transparent, so the photograph runs to the
   top edge behind it. Once the page scrolls, header.scrolled fades in a
   dark band with a hairline, so the links stay legible over white type
   and images. The band is painted on ::before, not on the header, so
   backdrop-filter never makes the header the containing block for the
   full-screen mobile menu, which is position: fixed inside it.

   Type and states are the documented ones: Label for every item, the
   navigation state pattern (62% → white, indicator bar) for the links.
   Contact is the page's action, so it rests in full white and shares
   the hover bar; on phones the menu keeps its accent pill.

   The header box keeps --header-h, so sticky offsets are unchanged, and
   on the home page it still pulls the hero up under itself.
   ──────────────────────────────────────────────────────────── */
.site-header,
.site-header.scrolled {
  height: var(--header-h);
  padding: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(0, 0, 0, 0.72);
  box-shadow: inset 0 -1px 0 var(--rule);
  -webkit-backdrop-filter: blur(16px) saturate(1.2);
  backdrop-filter: blur(16px) saturate(1.2);
  opacity: 0;
  transition: opacity 0.35s var(--ease);
  pointer-events: none;
}
.site-header.scrolled::before { opacity: 1; }

.site-header:has(+ main .hero.panel) { margin-bottom: calc(-1 * var(--header-h)); }
.site-header:has(+ main .hero.panel) + main .hero.panel { min-height: 100svh; }
.site-header:has(+ main .hero.panel) + main .hero-inner { height: 100svh; }

.site-header .wrap {
  position: relative;
  width: auto;
  max-width: var(--page);
  height: var(--header-h);
  margin: 0 auto;
  padding: 0 var(--gut);
  border-radius: 0;
}
.site-header .brand { position: relative; z-index: 1; }
.site-header .brand img { width: 30px; height: 30px; }

@media (min-width: 861px) {
  /* links centred on the page, not on the space the logo leaves */
  .site-header .nav {
    position: absolute;
    inset: 0 var(--gut);
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 16px;
    pointer-events: none;
  }
  .site-header .nav > * { pointer-events: auto; }
  .site-header .nav .nav-link,
  .site-header .nav .contact-pill {
    position: relative;
    padding: 8px 12px;
    font-size: var(--label-size);
    font-weight: var(--label-weight);
    line-height: var(--label-leading);
    letter-spacing: normal;
    text-transform: uppercase;
  }
  /* the indicator bar sits on the header's bottom line, not under the
     word: the item is centred in the header, so half the difference
     between the header and the item drops the bar to that edge */
  .site-header .nav .nav-link::after,
  .site-header .nav .contact-pill::after {
    content: "";
    position: absolute;
    left: 12px;
    right: 12px;
    bottom: calc((100% - var(--header-h)) / 2);
    height: 2px;
    background: var(--nav-bar);
    transition: background-color 0.28s var(--ease);
  }
  .site-header .nav .contact-pill {
    --nav-bar: transparent;
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: auto;
    margin: 0;
    padding-right: 0;
    border: 0;
    border-radius: 0;
    background: none;
    color: #ffffff;
    box-shadow: none;
  }
  .site-header .nav .contact-pill::after { right: 0; }
  .site-header .nav .contact-pill:hover,
  .site-header .nav .contact-pill:focus-visible {
    --nav-bar: rgba(255, 255, 255, 0.45);
    background: none;
    color: #ffffff;
    transform: translateY(-50%);
  }
  .site-header .nav .contact-pill:focus-visible { outline: 2px solid #ffffff; outline-offset: 2px; border-radius: 4px; }
}

/* ── Project card (shared component) ─────────────────────────
   One card for a project wherever it appears: Recent works on the
   home page, the "More projects" carousel closing each case study,
   and the style guide demos. The card sets no width — the grid or
   the track around it does.

   - A square picture at 16px radius. Thumbnails are prepared as
     squares (assets/thumb-*-square.jpg), recomposed so no product is
     cut.
   - Platforms ride at the top right as small frosted pills.
   - Underneath, straight on the page: the title, then the kind of work
     with where and when.
   - Hover and keyboard focus: the picture zooms to 1.03, the title
     rolls up to "View project", and a white arrow button rises in at
     the right of the caption. .is-hover holds that state for demos;
     touch screens always show the arrow.
   The #work selectors repeat each rule inside Recent works, where
   older ID-scoped .work-row rules would otherwise win.
   Type for the title, meta and tags lives with the type system.
   ──────────────────────────────────────────────────────────── */
html body .project-card,
html body #work .work-list .project-card {
  position: relative;
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  overflow: visible;
  background: none;
  color: #ffffff;
  text-decoration: none;
}
html body #work .work-list .project-card::after { content: none; }
html body .project-card-media {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  border-radius: 16px;
  overflow: hidden;
  isolation: isolate;
  background: #111111;
}
html body .project-card-media img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  border-radius: 0;
  transform: none;
  transition: transform 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}

html body .project-card-tags {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}
html body .project-card-tags li {
  margin: 0;
  padding: 7px 12px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.45);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: rgba(255, 255, 255, 0.9);
}

html body .project-card-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 16px 64px 0 0;
}
html body .project-card .project-card-title,
html body #work .project-card .project-card-title {
  position: relative;
  display: block;
  width: auto;
  max-width: none;
  height: 1.25em;
  margin: 0;
  overflow: hidden;
  white-space: normal;
  color: #ffffff;
}
html body .project-card-title span {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}
html body .project-card-meta { color: rgba(255, 255, 255, 0.6); }

html body .project-card-go {
  position: absolute;
  right: 0;
  bottom: 2px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #ffffff;
  color: #000000;
  opacity: 0;
  transform: translateY(8px) scale(0.9);
  transition: opacity 0.35s var(--ease), transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
html body .project-card-go svg { width: 16px; height: 16px; }
html body .project-card-go.is-static { position: static; opacity: 1; transform: none; }

html body .project-card:hover .project-card-media img,
html body .project-card:focus-visible .project-card-media img,
html body .project-card.is-hover .project-card-media img { transform: scale(1.03); }
html body .project-card:hover .project-card-title span,
html body .project-card:focus-visible .project-card-title span,
html body .project-card.is-hover .project-card-title span { transform: translateY(-100%); }
html body .project-card:hover .project-card-go,
html body .project-card:focus-visible .project-card-go,
html body .project-card.is-hover .project-card-go { opacity: 1; transform: none; }
html body .project-card:focus-visible,
html body #work .work-list .project-card:focus-visible { outline: 2px solid #ffffff; outline-offset: 6px; border-radius: 16px; }

@media (max-width: 1024px) {
  html body .project-card-text { padding-top: 14px; }
  html body .project-card-tags { top: 12px; right: 12px; }
}
/* touch screens have no hover: keep the arrow in view */
@media (hover: none) {
  html body .project-card-go { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  html body .project-card-media img,
  html body .project-card-title span,
  html body .project-card-go { transition: none; }
}

/* More projects: the carousel sizes its cards to whole columns, so the
   last card ends exactly on the content edge instead of being cut.
   Three across on desktop, two on tablets, one on phones; the arrows
   page one card at a time and hide when everything already fits. */
html body .more-track > li { flex: 0 0 calc((100% - 2 * var(--grid-gap)) / 3); min-width: 0; }
@media (max-width: 1024px) {
  html body .more-track > li { flex-basis: calc((100% - var(--grid-gap)) / 2); }
}
@media (max-width: 600px) {
  html body .more-track > li { flex-basis: 100%; }
}

/* Recent works: the cards in two columns */
html body #work .work-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 16px;
  row-gap: clamp(36px, 4vw, 56px);
  margin-top: 0;
  border: 0;
}
@media (max-width: 760px) {
  html body #work .work-list { grid-template-columns: minmax(0, 1fr); row-gap: 36px; }
}

/* ── About on white ─────────────────────────────────────────
   The About panel turns from the accent field to plain white with
   black type, so the page alternates black · white · black and the
   accent is left to actions and state alone. Every colour inside the
   panel was already black, so only the ground changes. */
html body #about.panel { background: #ffffff; color: #000000; }
/* the section label matches the others: number in the accent, rule at
   the same 14% hairline weight, inverted for the white ground */
html body #about .eyebrow::before { color: var(--accent); }
html body #about .eyebrow::after { background: rgba(0, 0, 0, 0.14); }
/* the rule above the years and skills: the system's 1px hairline at
   14%, inverted for the white ground, in place of the 2px black rule */
html body #about .about-layout > div.about-experience { border-top: 1px solid rgba(0, 0, 0, 0.14); }
/* the portrait: more contrast than the 1.12 it had, so the black and
   white holds up against the white ground */
html body #about .about-portrait img { filter: grayscale(1) contrast(1.32); }

/* ── Portfolio case study: current components ────────────────
   Demos for the Portfolio website style guide that mirror the live
   site: the header's text action.
   Type for these lives with the type system (css/type-*.css). */

.comp-action { display: inline-flex; align-items: center; gap: 6px; color: #ffffff; }


/* ── Portfolio case study: project card and thumbnail demos ──
   Layout for the style guide demos. The cards themselves are the
   shared .project-card; this only places them, plus the wide / square
   thumbnail pair. */
.comp-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.comp-card-cell .state-name { margin-top: 18px; }
/* the carousel demo: equal cards, cropped by the panel edge like the real track */
html body .spec .comp-carousel { align-items: flex-start; }
html body .spec .comp-carousel > li { flex: 0 0 clamp(220px, 38%, 300px); min-width: 0; margin: 0; padding: 0; }
@media (max-width: 860px) { html body .spec .comp-carousel > li { flex-basis: 70%; } }

.thumb-pair { display: grid; grid-template-columns: 1.6fr 1fr; gap: 16px; align-items: end; }
.thumb-pair figure { margin: 0; }
.thumb-wide, .thumb-square { display: block; overflow: hidden; background: #111111; }
.thumb-wide { aspect-ratio: 16 / 10; border-radius: 4px; }
.thumb-square { aspect-ratio: 1 / 1; border-radius: 16px; }
.thumb-wide img, .thumb-square img { display: block; width: 100%; height: 100%; object-fit: cover; }
.thumb-pair figcaption { margin-top: 12px; color: rgba(255, 255, 255, 0.6); }

@media (max-width: 760px) {
  .comp-cards, .thumb-pair { grid-template-columns: 1fr; }
}

/* ── Contact title in white ─────────────────────────────────
   "Get in touch" drops the accent for plain white, like every other
   section title; the accent stays with actions and state. */
html body #contact .contact-title .contact-title-big { color: #ffffff; }
/* ── Contact: paper plane ────────────────────────────────────
   A canvas behind the Contact content where a paper plane flies
   through (js/main.js "Contact paper plane"). Everything else in the
   panel sits above it. */
html body #contact.panel { position: relative; isolation: isolate; }
html body #contact .contact-flight {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
html body #contact .contact-layout,
html body #contact .site-footer { position: relative; z-index: 1; }

/* the Email · LinkedIn · Portfolio row spans the full content width;
   an older rule capped .contact-side at 720px, leaving it stranded
   short of the right edge */
html body #contact .contact-layout > .contact-side { width: auto; max-width: none; justify-self: stretch; }

/* The type system lives in its own file — css/type-geist.css (default)
   or css/type-eva.css — linked after this sheet; see the <head>. */
