/* ─── Typeface: Neue Haas Display ───────────────────────────────────────────
   Self-hosted from `typefaces/NeueHaasDisplay/`, so the site makes no
   third-party font request. This family ships as .ttf only (there are no .otf
   files), and every face carries 395 glyphs — a fuller set than the previous
   two families, and it includes the caret (0x5E) they lacked.

   The site draws 400 (body), 500, 600 and 700. The family provides Roman (400),
   Medium (500), Bold (700) and Black (900) but no SemiBold, so the `600` a few
   heading rules ask for resolves to Bold through ordinary CSS weight matching.
   That is expected rather than a mistake. Only the three weights the site
   actually draws are declared, plus their italics; Light, Thin, XThin, XXThin
   and Black are in the folder and can each be added with one more block.

   Note: this family has no ← / → glyphs (0x2190 / 0x2192) either — the one gap
   all three of the site's families share — so the slide buttons' arrows fall
   back per-character to the next font in the stack. Prevalent Sans Pro and
   Stenz Graxon Pro remain in `typefaces/` and can be restored by repointing
   these blocks at them. */
@font-face {
  font-family: "Neue Haas Display";
  src: url("typefaces/NeueHaasDisplay/NeueHaasDisplayRoman.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Neue Haas Display";
  src: url("typefaces/NeueHaasDisplay/NeueHaasDisplayRomanItalic.ttf") format("truetype");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Neue Haas Display";
  src: url("typefaces/NeueHaasDisplay/NeueHaasDisplayMediu.ttf") format("truetype");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Neue Haas Display";
  src: url("typefaces/NeueHaasDisplay/NeueHaasDisplayMediumItalic.ttf") format("truetype");
  font-weight: 500;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Neue Haas Display";
  src: url("typefaces/NeueHaasDisplay/NeueHaasDisplayBold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Neue Haas Display";
  src: url("typefaces/NeueHaasDisplay/NeueHaasDisplayBoldItalic.ttf") format("truetype");
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}


:root {
  /* Colour */
  --bg: #050607;
  --bg-soft: #0d1014;
  --panel: rgba(255, 255, 255, 0.025);
  --panel-strong: rgba(255, 255, 255, 0.045);
  --line: rgba(255, 255, 255, 0.085);
  --line-soft: rgba(255, 255, 255, 0.06);
  --text: #f4f0ea;
  --muted: #b7afa1;
  --accent: #d8c7ad;
  --accent-strong: #f0d3a1;
  --shadow: rgba(0, 0, 0, 0.42);
  --shadow-soft: 0 14px 34px rgba(0, 0, 0, 0.28);
  --shadow-soft-lift: 0 20px 46px rgba(0, 0, 0, 0.4);

  /* Glass card surface — a translucent, frosted panel. Only the panel itself is
     see-through (its tint is translucent and the page behind it is blurred);
     the media frames inside keep their opaque #121519 fill, so the assets and
     text in the cards always render at full opacity. */
  --glass-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.03));
  --glass-edge: 1px solid rgba(255, 255, 255, 0.1);
  --glass-sheen: inset 0 1px 0 rgba(255, 255, 255, 0.07);
  --glass-blur: blur(14px) saturate(140%);

  /* Section glass (the frosted panels/plaques over the homepage artwork):
     a subtle white sheen over a slight black tint, with a light backdrop blur. */
  --panel-glass-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0) 45%),
    linear-gradient(180deg, rgba(0, 0, 0, 0.18) 0%, rgba(0, 0, 0, 0.3) 100%);
  --panel-glass-blur: blur(10px) saturate(140%);

  /* 8pt spatial scale */
  --space-2xs: 0.25rem;   /*  4px */
  --space-xs:  0.5rem;    /*  8px */
  --space-sm:  0.75rem;   /* 12px */
  --space-md:  1rem;      /* 16px */
  --space-lg:  1.5rem;    /* 24px */
  --space-xl:  2rem;      /* 32px */
  --space-2xl: 3rem;      /* 48px */
  --space-3xl: 4.5rem;    /* 72px */

  /* Grid gutters */
  --grid-gutter:    1rem;   /* 16px — card, section and structured split gap */
  --grid-gutter-sm: 0.5rem; /*  8px — masonry grid gap (asset tessellation) */

  /* Corner radii — soft curves on every framed surface (grids, cards, stages) */
  --radius-sm:   10px;  /* media nested inside a padded panel */
  --radius-md:   16px;  /* grid tiles — photo cards, asset frames, lightbox media */
  --radius-lg:   22px;  /* cards, panels, stages, flush media wrappers */
  --radius-xl:   28px;  /* the lightbox shell */
  --radius-pill: 999px; /* tags, buttons, chips */

  /* Typography scale — tracking kept conservative so glyphs never overlap */
  --track-display: -0.015em;  /* h1 display */
  --track-heading: -0.01em;   /* h2 */
  --track-subhead: -0.008em;  /* h3 */
  --track-label:    0.1em;    /* eyebrows, tags, nav */
  --track-body:     0em;      /* paragraphs */

  --leading-display: 1.12;
  --leading-heading: 1.18;
  --leading-subhead: 1.22;
  --leading-body:    1.6;
  --leading-label:   1.4;

  /* Typefaces — one consistent sans throughout, never swapped per-section.
     `--font-sans` is the single source of truth and the other two alias it, so
     the whole site's typography is set by this one line. Neue Haas Display is
     self-hosted (its @font-face block sits at the top of this sheet); the
     system sans stack that came before stays on as the fallback for the moment
     before the webfont arrives and for glyphs the family does not carry. */
  --font-sans:     "Neue Haas Display", "Inter", "Segoe UI", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
  --font-display:  var(--font-sans);
  --font-body:     var(--font-sans);
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  scroll-margin-top: 110px;
}

body {
  margin: 0;
  min-height: 100vh;
  overflow-x: hidden;
  background:
    radial-gradient(circle at top, rgba(255,255,255,0.06), transparent 30%),
    linear-gradient(180deg, #050607 0%, #090b0d 30%, #050607 100%);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: var(--leading-body);
  letter-spacing: var(--track-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  background:
    repeating-linear-gradient(
      0deg,
      rgba(255,255,255,0.035),
      rgba(255,255,255,0.035) 1px,
      transparent 1px,
      transparent 4px
    );
  opacity: 0.15;
  pointer-events: none;
}

img,
video {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
}

/* ─── Global typography ──────────────────────────────────── */

/* Headings: balanced line-wrap, no glyph collisions */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  text-wrap: balance;
  orphans: 2;
  widows: 2;
}

/* Body copy: widow/orphan prevention, comfortable reading */
p, li, figcaption, blockquote {
  text-wrap: pretty;
  orphans: 2;
  widows: 2;
  line-height: var(--leading-body);
  max-width: 72ch; /* limits line length to ~72 characters — standard legibility threshold */
}

.page-shell {
  position: relative;
  width: min(100%, 1540px);
  margin: 0 auto;
  padding: 0 28px;
}

/* Full-bleed header: the homepage and all collection/project pages run the
   topbar the full width of the viewport (mirroring the homepage), with its
   inner content re-aligned to the page gutter so it lines up with the page. */
.page-shell--home .topbar,
.page-shell--home .hero,
.page-collection .topbar {
  width: 100vw;
  margin-left: calc(50% - 50vw);
}

.page-shell--home .topbar,
.page-collection .topbar {
  padding-left: max(28px, calc((100vw - 1540px) / 2 + 28px));
  padding-right: max(28px, calc((100vw - 1540px) / 2 + 28px));
}

.page-shell--home .hero-content {
  margin-left: max(28px, calc((100vw - 1540px) / 2 + 28px));
}

/* Grey body text was illegible against the homepage artwork, so on the homepage
   every muted tone renders at full text strength (white). */
.page-shell--home {
  --muted: var(--text);
}

/* The section-number labels ("03 / 3D Design", "01 / Graphic Design", the hero
   and footer eyebrows, the showcase category) use --accent, a greyed tan that
   also reads as grey over the artwork — white on the homepage as well. The
   accent's decorative uses (nav underline, accordion marker) are untouched. */
.page-shell--home .eyebrow,
.page-shell--home .meta-label {
  color: var(--text);
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: clamp(18px, 3vw, 40px);
  padding: 18px 0 16px;
  background: rgba(5, 6, 7, 0.82);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}

.brand-wrap {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  font-size: 0.72rem;
  line-height: var(--leading-label);
}

.brand-mark {
  display: block;
  width: auto;
  height: 34px;
  object-fit: contain;
}

.brand-name {
  color: var(--text);
}

.main-nav {
  display: flex;
  align-items: center;
  gap: clamp(12px, 2.5vw, 32px);
  color: var(--muted);
  font-size: 0.74rem;
  letter-spacing: var(--track-label);
  line-height: var(--leading-label);
  text-transform: uppercase;
}

.main-nav a {
  position: relative;
  transition: color 180ms ease;
}

.main-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -8px;
  width: 100%;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 180ms ease;
}

.main-nav a:hover,
.main-nav a:focus-visible {
  color: var(--text);
}

.main-nav a:hover::after,
.main-nav a:focus-visible::after {
  transform: scaleX(1);
}

.nav-cta,
.inline-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.8rem 1.4rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 0.74rem;
  letter-spacing: var(--track-label);
  line-height: var(--leading-label);
  text-transform: uppercase;
  transition: border-color 180ms ease, transform 180ms ease, background 180ms ease;
}

.nav-cta:hover,
.inline-link:hover,
.nav-cta:focus-visible,
.inline-link:focus-visible {
  border-color: rgba(255, 255, 255, 0.2);
  transform: translateY(-1px);
  background: rgba(255, 255, 255, 0.02);
}

.hero {
  position: relative;
  display: flex;
  align-items: end;
  min-height: 82vh;
  padding: var(--space-3xl) 0 var(--space-2xl);
  border-bottom: 1px solid var(--line);
  background: #040506;
}

.hero-video,
.hero-overlay {
  position: absolute;
  inset: 0;
}

.hero-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.6);
  transform-origin: top center;
  filter: saturate(0.85) contrast(1.06) brightness(0.48);
}

.hero-overlay {
  transform: scale(1.6);
  transform-origin: top center;
  background:
    linear-gradient(90deg, rgba(5, 6, 7, 0.9) 0%, rgba(5, 6, 7, 0.45) 35%, rgba(5, 6, 7, 0.18) 100%),
    radial-gradient(circle at 75% 35%, rgba(255,255,255,0.08), transparent 30%);
}

.hero-content {
  position: relative;
  z-index: 1;
  width: min(100%, 980px);
  padding-left: clamp(0px, 2vw, 20px);
}

/* Eyebrows and meta labels: consistent label tracking */
.eyebrow,
.meta-label {
  margin: 0 0 var(--space-sm);
  color: var(--accent);
  font-size: 0.7rem;
  letter-spacing: var(--track-label);
  line-height: var(--leading-label);
  text-transform: uppercase;
}

/* Hero h1: display scale — tracking normalized to prevent glyph overlap */
.hero-content h1 {
  margin: 0;
  max-width: 100%;
  font-size: clamp(2.2rem, 4.6vw, 5rem);
  line-height: var(--leading-display);
  letter-spacing: var(--track-display);
  font-weight: 700;
  text-wrap: balance;
}

.hero-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-top: var(--space-lg);
}

.hero-tags span {
  display: inline-flex;
  align-items: center;
  padding: var(--space-xs) var(--space-md);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 999px;
  background: rgba(255,255,255,0.02);
  color: var(--text);
  font-size: 0.68rem;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.section-intro,
.work-section,
.site-footer {
  position: relative;
  z-index: 1;
  padding: var(--space-3xl) 0;
  border-bottom: 1px solid var(--line);
}

/* ─── Homepage full-bleed section backgrounds ───────────────────────────
   Each homepage work-section carries one of the large background images, bled
   out to the viewport edges. The image sits on a `::before` layer so it can be
   blurred without touching the section's content; `z-index: -1` keeps that
   layer inside the section's own stacking context, so it always paints behind
   the cards, videos, images and text (all of which keep their own full
   opacity). script.js tags each section `is-bg-blurred` until it scrolls into
   view, so the artwork eases from out of focus to sharp. Scoped to the
   homepage, so the collection pages keep the flat background. */
/* NOTE: a CSS id selector cannot begin with a digit, so `#3d-design` is an
   invalid selector and Chrome drops the *whole* rule that contains it. The
   sections are therefore targeted by id attribute, which is valid for all three. */
.page-shell--home [id="3d-design"],
.page-shell--home [id="photography"],
.page-shell--home [id="graphic-design"] {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  padding-left: max(28px, calc((100vw - 1540px) / 2 + 28px));
  padding-right: max(28px, calc((100vw - 1540px) / 2 + 28px));
  overflow: hidden; /* clips the blurred layer's soft edges at the section bounds */
}

/* The artwork layer. A slight upscale pushes blur fringing outside the clip. */
.page-shell--home [id="3d-design"]::before,
.page-shell--home [id="photography"]::before,
.page-shell--home [id="graphic-design"]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-repeat: no-repeat;
  background-position: center center;
  background-size: cover;
  transform: scale(1.06);
  transition: filter 900ms ease, transform 900ms ease;
}

.page-shell--home [id="3d-design"]::before {
  background-image: url("backgrounds/background1.webp");
}

.page-shell--home [id="photography"]::before {
  background-image: url("backgrounds/background2.webp");
}

.page-shell--home [id="graphic-design"]::before {
  background-image: url("backgrounds/background3.webp");
}

/* Default (no JS, or before script.js runs) the artwork is sharp. script.js
   adds `is-bg-blurred` while a section is off screen and removes it once the
   section has scrolled into view, so the image fades back into focus. */
.page-shell--home .work-section.is-bg-blurred::before {
  filter: blur(26px);
  transform: scale(1.14);
}

/* ─── Collection pages: one artwork + frosted asset boxes ────────────────
   Where the homepage gives every section its own background band, the 3D
   Design, Photography and Graphic Design collection pages each carry a single
   image behind the whole page: one full-bleed `::before` layer on the shell
   (bled to the viewport with the same `calc(50% - 50vw)` idiom the homepage
   bands use), so the artwork runs behind the header, the hero and the asset
   grid as one continuous background — 3D Design uses background5, Photography
   uses background6, Graphic Design uses background7. The asset boxes wear the
   darkened section glass (the same `--panel-glass-*` recipe as the homepage's
   3D tiles), so the artwork shows through them while the assets stay fully
   opaque, and each page's title plus project number sit in a matching frosted
   plaque. */
.page-collection--3d,
.page-collection--photography,
.page-collection--graphic {
  position: relative;
  z-index: 1;
}

.page-collection--3d::before,
.page-collection--photography::before,
.page-collection--graphic::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(50% - 50vw);
  width: 100vw;
  z-index: -1;
  background-repeat: no-repeat;
  background-position: center center;
  background-size: cover;
}

.page-collection--3d::before {
  background-image: url("backgrounds/background5.webp");
}

.page-collection--photography::before {
  background-image: url("backgrounds/background6.webp");
}

.page-collection--graphic::before {
  background-image: url("backgrounds/background7.webp");
}

/* The Graphic Design artwork is dark (p50 relative luminance 0.030, and no more
   than 0.196 anywhere in the showcase's band), so the page's grey body copy would
   drop to roughly 3.3:1 against its brightest patches — below AA. The homepage
   answered the same problem this way in T34, so the collection page takes the
   same one-line safeguard: every `--muted` string (the showcase description, the
   card body copy, the note blocks) renders as the full-strength text colour.
   Delete this declaration to go back to grey. */
.page-collection--graphic {
  --muted: var(--text);
}

/* The bands keep the homepage's full-bleed geometry, so each grid holds the
   same inner width it had before and the artwork reads in the gutters. The
   photography grid is measured by its own JS masonry, and this padding works
   out to exactly the shell's inner width, so its columns are untouched. */
.page-collection--3d .page-hero,
.page-collection--3d .bg-band,
.page-collection--photography .page-hero,
.page-collection--photography .photo-groups {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  padding-left: max(28px, calc((100vw - 1540px) / 2 + 28px));
  padding-right: max(28px, calc((100vw - 1540px) / 2 + 28px));
}

.page-collection--3d .bg-band {
  display: block;
  padding-top: var(--space-xl);
  padding-bottom: var(--space-3xl);
}

.page-collection--3d .bg-band .collection-list-grid {
  margin-top: 0;
}

/* Asset boxes: the darkened frosted glass used by the homepage's 3D section.
   The 3D Design and Graphic Design collection pages both wear it on their
   `.card-panel` project tiles, which already carry copy, so the glass fills the
   whole box (the Photography page frames its copy-less tiles instead). */
.page-collection--3d .card-panel,
.page-collection--graphic .card-panel {
  background: var(--panel-glass-bg);
  border: 1px solid rgba(255, 255, 255, 0.14);
  backdrop-filter: var(--panel-glass-blur);
  -webkit-backdrop-filter: var(--panel-glass-blur);
}

/* Photography — the whole grid sits inside ONE frosted parent box instead of a
   box around every tile. The panel is a `::before` on the band so the JS
   masonry keeps measuring the band's content box: column count, column widths
   and the packing are untouched. The band's vertical padding here plus the
   panel's matching horizontal inset leave the same 24px of glass on every
   side of the grid; on narrow screens the inset floors at 8px so the panel can
   never spill past the band. */
.page-collection--photography .photo-groups {
  padding-top: var(--space-lg);
  padding-bottom: var(--space-lg);
}

.page-collection--photography .photo-groups::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: max(8px, calc(max(28px, calc((100vw - 1540px) / 2 + 28px)) - var(--space-lg)));
  right: max(8px, calc(max(28px, calc((100vw - 1540px) / 2 + 28px)) - var(--space-lg)));
  border-radius: var(--radius-lg);
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: var(--panel-glass-bg);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.12);
  backdrop-filter: var(--panel-glass-blur);
  -webkit-backdrop-filter: var(--panel-glass-blur);
}

/* The tiles are bare images on that glass — no frame of their own, and no dark
   fill blocking the panel showing through the 8px gutters. They keep the
   shared grid-tile corner (radius-md, 16px) and the media inherits it. */
.page-collection--photography .photo-card {
  background: transparent;
}

/* The page title and project number sit inside a frosted plaque — the same
   `--panel-glass-*` recipe, border, radius, shadow and padding the homepage
   uses for its section plaques (`.section-header > div`). Sized to its content
   so the glass hugs the eyebrow + heading rather than spanning the band. */
.page-collection--3d .hero-plaque,
.page-collection--photography .hero-plaque,
.page-collection--graphic .hero-plaque {
  width: fit-content;
  max-width: 100%;
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-lg);
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: var(--panel-glass-bg);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.12);
  backdrop-filter: var(--panel-glass-blur);
  -webkit-backdrop-filter: var(--panel-glass-blur);
}

/* ─── Frosted white glass per section ───────────────────────────────────
   Two surfaces share one glass recipe (the --panel-glass-* tokens):
   · Title plaque — every homepage work-section (3D Design, Photography and
     Graphic Design): the glass is applied straight to the title block
     (`.section-header > div`), which hugs its text as a flex item, so the plaque
     fits only the section number + heading.
   · Showcase copy panel — the Graphic Design showcase's `.project-copy`, which
     houses the typographic information beside the project hero.
   The glass always paints behind the content (the ::before artwork is painted
   first and every card, video and line of text sits above), so nothing is ever
   covered. The intro block stays unpanelled, as does
   the footer. */

/* The 3D Design section's project boxes wear the same darkened glass as the
   Graphic Design showcase's copy panel, rather than the lighter white glass the
   cards use elsewhere on the site. */
.page-shell--home [id="3d-design"] .card-panel {
  background: var(--panel-glass-bg);
  border: 1px solid rgba(255, 255, 255, 0.14);
  backdrop-filter: var(--panel-glass-blur);
  -webkit-backdrop-filter: var(--panel-glass-blur);
}

/* Compact glass plaque around the section number + title, for every homepage
   work-section. The title block hugs its text (it is a flex item), so the plaque
   fits just the eyebrow and the heading. */
.page-shell--home .work-section .section-header > div {
  padding: var(--space-md) var(--space-lg);
  border-radius: var(--radius-lg);
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: var(--panel-glass-bg);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.12);
  backdrop-filter: var(--panel-glass-blur);
  -webkit-backdrop-filter: var(--panel-glass-blur);
}

/* The Graphic Design showcase's typographic panel: the category, project title,
   description, year/type and project link that sit beside the project hero. The
   Graphic Design collection page runs the same showcase, so it wears the same
   panel. */
.page-shell--home .graphic-showcase .project-copy,
.page-collection--graphic .graphic-showcase .project-copy {
  padding: var(--space-lg);
  border-radius: var(--radius-lg);
  border: 1px solid rgba(255, 255, 255, 0.14);
  background: var(--panel-glass-bg);
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.12);
  backdrop-filter: var(--panel-glass-blur);
  -webkit-backdrop-filter: var(--panel-glass-blur);
}

/* Line the copy panel's top edge up with the top of the hero asset. The hero
   video now owns the first row of the stage, so the panel only needs the stage's
   own inset — border (1px) + padding (--grid-gutter-sm).
   Only needed while the showcase is side by side; below 980px it stacks. */
@media (min-width: 981px) {
  .page-shell--home .graphic-showcase .project-copy,
  .page-collection--graphic .graphic-showcase .project-copy {
    margin-top: calc(1px + var(--grid-gutter-sm));
  }
}

.page-collection {
  padding-bottom: var(--space-2xl);
}

.collection-page {
  padding-top: var(--space-xl);
}

/* Full-bleed page header: runs over the shell margins to the viewport edges,
   while the inner text re-aligns to the page gutter. */
.page-hero {
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  width: 100vw;
  padding: var(--space-2xl) max(28px, calc((100vw - 1540px) / 2 + 28px)) var(--space-xl);
  border-bottom: 1px solid var(--line);
}

/* Page-level h1 (collection / project pages) */
.page-hero h1 {
  margin: 0;
  max-width: 880px;
  font-size: clamp(2.65rem, 5.3vw, 5.3rem);
  line-height: var(--leading-display);
  letter-spacing: var(--track-display);
  font-weight: 600;
  text-wrap: balance;
}

/* The intro block holds the site's lead-in line — now the short "Software used"
   label — plus the software icon grid. One column keeps both flush with the
   section's right edge instead of stranding them inside the old 1.5fr column. */
.section-intro {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--grid-gutter);
  align-items: start;
  padding-top: var(--space-2xl);
}

/* Shared heading baseline — no negative tracking overlap */
.section-intro h2,
.section-header h2,
.project-copy h3,
.card-copy h3,
.site-footer h3 {
  margin: 0;
  font-weight: 500;
}

/* Section intro h2 */
.section-intro h2 {
  font-size: clamp(1.95rem, 3.5vw, 3.5rem);
  line-height: var(--leading-heading);
  letter-spacing: var(--track-heading);
  max-width: 760px;
  text-wrap: balance;
}

.intro-text {
  margin: 0;
  padding-top: var(--space-xs); /* small top offset so the note is not flush to the section edge */
  color: var(--muted);
  font-size: 1rem;
  line-height: var(--leading-body);
  max-width: 38ch;
  justify-self: end;
}

/* Software strip (the intro block's grid): 7 across, 32px cells, shrinking to
   fit narrow viewports so the row never overflows the section. 13 of the 14
   icons are black artwork on transparency — confirmed against the original
   PNGs, which contain no pixel brighter than 0,0,0 — so on this dark page they
   are normalised to solid white at render time. The filter is
   `brightness(0) invert(1)` rather than a plain `invert(1)`: flattening to
   black first and then inverting guarantees white, and it is what keeps
   `Iclone` — the one icon that mixes black and white — whole. A plain invert
   would simply swap its tones, leaving the half that turns black invisible
   against a near-black page, which is exactly the problem being solved. The
   artwork files themselves are untouched, every icon lands at about 20:1
   against the page, and there is no plate of any kind. `contain` keeps each
   logo in proportion inside its square cell (several are non-square). */
.software-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 32px));
  gap: var(--space-sm);
  justify-self: end;
  margin: 0;
  padding: 0;
  list-style: none;
}

.software-grid li {
  aspect-ratio: 1;
  display: grid;
  place-items: center;
}

.software-grid img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: brightness(0) invert(1);
}

.section-header {
  display: flex;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-xl);
  align-items: baseline;
}

/* Section header h2 */
.section-header h2 {
  font-size: clamp(1.75rem, 2.6vw, 2.6rem);
  line-height: var(--leading-heading);
  letter-spacing: var(--track-heading);
  max-width: 650px;
  text-wrap: balance;
}

/* Project cards — frosted glass. The panel is slightly transparent: its tint is
   a translucent white gradient and the page behind it is blurred (never
   `opacity`, which would fade the contents too). Everything inside stays fully
   opaque — media frames keep their opaque #121519 fill, so assets never wash
   out and text stays crisp. Tune the whole effect from the --glass-* tokens. */
.card-panel {
  border-radius: var(--radius-lg);
  background: var(--glass-bg);
  border: var(--glass-edge);
  box-shadow: var(--shadow-soft), var(--glass-sheen);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

/* ─── Soft rounded corners ──────────────────────────────────────────────
   Every surface shown in a grid or panel is curved. The radius scales with
   the surface — media → tile → card → stage — so nested curves stay
   concentric: wrappers flush inside a card use the card's radius, and each
   media element inherits the radius of the frame that holds it. */
.card-panel,
.collection-card,
.story-card,
.feature-large,
.feature-stack,
.graphic-project-hero,
.project-stage {
  border-radius: var(--radius-lg);
}

/* Grid tiles: photography cards, 3D + graphic asset frames */
.photo-card,
.three-d-asset-frame,
.graphic-asset-frame,
.project-detail-grid img {
  border-radius: var(--radius-md);
}

/* Media wrappers sit flush inside their card, so they take the card's curve
   (the scrub hero keeps its own radius — it is a padded panel, not flush). */
.collection-card__media:not(.graphic-project-hero),
.media-shell,
.thumb-wrap {
  border-radius: var(--radius-lg);
  overflow: hidden;
}

/* Media follows the frame that holds it */
.collection-card__media img,
.collection-card__media video,
.media-shell img,
.media-shell video,
.thumb-wrap img,
.thumb-wrap video,
.photo-card img,
.photo-card video,
.three-d-asset,
.graphic-asset {
  border-radius: inherit;
}

/* Media inside a padded panel keeps a tighter curve than its panel */
.project-stage video,
.graphic-project-hero video {
  border-radius: var(--radius-sm);
}

.asset-lightbox {
  border-radius: var(--radius-xl);
}

.asset-lightbox__media {
  border-radius: var(--radius-md);
}

.asset-lightbox__close {
  border-radius: var(--radius-pill);
}

/* ─── Masonry grids ─────────────────────────────────────────────────────
   All project / asset grids tessellate by the natural varying aspect ratio
   of their media. CSS multi-column fills each column top-to-bottom then
   moves to the next, so items of different heights pack with minimal gaps. */

.three-d-grid,
.collection-grid,
.collection-list-grid,
.photo-grid {
  display: block;
  column-count: 3;
  column-gap: var(--grid-gutter-sm);
}

.collection-list-grid {
  margin-top: var(--space-xl);
}

/* Every card / figure in a masonry grid is a column item that avoids being
   split across columns and is snug to its own content (no forced height). */
.three-d-grid > *,
.collection-grid > *,
.collection-list-grid > *,
.photo-grid > * {
  break-inside: avoid;
  margin: 0 0 var(--grid-gutter-sm);
}

/* Snug parent card — hugs its media + text, no fixed min-height */
.collection-card,
.story-card {
  display: block;
  overflow: hidden;
}

.collection-card summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-lg) var(--space-md);
  font-size: clamp(1.05rem, 1.6vw, 1.75rem);
  line-height: var(--leading-heading);
  letter-spacing: var(--track-subhead);
  cursor: pointer;
  list-style: none;
}

.collection-card summary::-webkit-details-marker {
  display: none;
}

.collection-card summary::after {
  content: "+";
  font-size: 1.3rem;
  color: var(--accent);
}

.collection-card[open] summary::after {
  content: "−";
}

.project-detail-panel {
  padding: 0 var(--space-md) var(--space-md);
}

.project-detail-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--grid-gutter-sm);
  margin-top: var(--space-sm);
}

.project-detail-grid img {
  width: 100%;
  height: auto;
  object-fit: contain;
  border: 1px solid var(--line);
}

/* 3D project page gallery */
.three-d-gallery {
  align-items: start;
}

.three-d-gallery:not(.is-single-asset) {
  display: block;
  column-count: 3;
  column-gap: var(--grid-gutter-sm);
}

.three-d-asset-frame {
  overflow: hidden;
  margin: 0 0 var(--grid-gutter-sm);
  break-inside: avoid;
  background: #121519;
  cursor: zoom-in;
}

.three-d-asset-frame .three-d-asset {
  /* Fill the frame edge-to-edge and overhang it by one pixel on every side.
     The frame's `overflow: hidden` clips the overhang, so sub-pixel rounding at
     any device pixel ratio can never expose a seam of the frame's background
     along an edge. `border: 0` drops the 1px `--line` hairline that
     `.project-detail-grid img` would otherwise reach inside these galleries —
     a hairline sitting over the element's own dark background reads as a
     lighter or darker line of stray pixels exactly where the artwork should
     meet the box. The asset must sit flush, single-asset gallery or not. */
  width: calc(100% + 2px);
  max-width: none;
  height: auto;
  margin: -1px;
  object-fit: contain;
  background: #121519;
  border: 0;
}

.three-d-gallery .three-d-asset {
  border: 0;
}

/* Homepage 3D section: same treatment for the collection tiles, so the artwork
   fills its tile and overhangs it by a pixel — no seam between asset and box. */
.page-shell--home [id="3d-design"] .media-shell img,
.page-shell--home [id="3d-design"] .media-shell video,
.page-shell--home [id="3d-design"] .thumb-wrap img,
.page-shell--home [id="3d-design"] .thumb-wrap video {
  width: calc(100% + 2px);
  max-width: none;
  height: auto;
  margin: -1px;
}

.three-d-asset-frame video.three-d-asset {
  min-height: 0;
}

.three-d-gallery.is-single-asset {
  max-width: 100%;
}

/* Figcaption — consistent label tracking across all asset types */
.three-d-asset-frame figcaption {
  padding: var(--space-sm) var(--space-md);
  color: var(--muted);
  font-size: 0.7rem;
  letter-spacing: var(--track-label);
  line-height: var(--leading-label);
  text-transform: uppercase;
  max-width: none; /* override global 72ch cap — captions live beneath their image */
}

.three-d-project-overview {
  column-span: all;
  break-inside: avoid;
  margin-top: var(--space-lg);
}

.three-d-project-overview h2 {
  margin: 0;
  font-size: clamp(1.6rem, 2.2vw, 2.3rem);
  line-height: var(--leading-heading);
  letter-spacing: var(--track-heading);
  text-wrap: balance;
}

.asset-lightbox {
  width: min(96vw, 1800px);
  max-width: none;
  max-height: 96vh;
  padding: 3rem 1rem 1rem;
  border: 1px solid var(--line);
  background: rgba(11, 13, 16, 0.97);
  color: var(--text);
}

.asset-lightbox::backdrop {
  background: rgba(0, 0, 0, 0.86);
}

.asset-lightbox__media {
  display: block;
  width: 100%;
  max-height: calc(96vh - 4rem);
  object-fit: contain;
}

.asset-lightbox__close {
  position: absolute;
  top: 0.7rem;
  right: 0.8rem;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text);
  cursor: pointer;
}

/* Snug preview media — the box hugs the asset, no forced height / cover-crop.
   In a snug card the media keeps its natural aspect and fills column width. */
.collection-card__media {
  background: #121519;
}

/* Scrub hero (the graphic project pages): the same two-row construction as the
   homepage stage — the video owns row 1 and the arrow pair sits beneath it,
   flush with the video's left edge. */
.collection-card__media[data-graphic-scrub] {
  position: relative;
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: center;
  gap: var(--grid-gutter-sm);
  padding: var(--grid-gutter-sm);
}

.collection-card__media img,
.collection-card__media video {
  width: 100%;
  height: auto;
  object-fit: contain;
}

.collection-card__media[data-graphic-scrub] video {
  grid-row: 1;
  grid-column: 1 / -1;
  min-height: 0;
  background: #121519;
}

.collection-card__media[data-graphic-scrub] .slide-button {
  justify-self: center;
}

/* Arrows: row 2, side by side, flush with the left edge of the video above. */
.collection-card__media[data-graphic-scrub] .slide-button.prev {
  grid-row: 2;
  grid-column: 1;
}

.collection-card__media[data-graphic-scrub] .slide-button.next {
  grid-row: 2;
  grid-column: 2;
}

/* Homepage Egg Studio feature card — stacked, snug (media then copy) */
.feature-large {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.media-shell {
  background: #121519;
}

.media-shell video {
  width: 100%;
  height: auto;
  object-fit: contain;
  background: #121519;
}

/* Card copy — vertical padding kept minimal so the box hugs its media + copy:
   8px between the asset and the title, 16px under the copy, sides unchanged. */
.card-copy {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding: var(--space-xs) var(--space-lg) var(--space-md);
  gap: var(--space-xs);
}

.card-copy h3 {
  font-size: clamp(1.3rem, 1.7vw, 2.1rem);
  line-height: var(--leading-subhead);
  letter-spacing: var(--track-subhead);
  margin-bottom: var(--space-xs);
  text-wrap: balance;
}

.card-copy p {
  margin: 0;
  color: var(--muted);
  line-height: var(--leading-body);
  max-width: 52ch;
}

/* No trailing margin on the last element of a card: when the title is the only
   child (every project listing card), the heading→body gap must not survive as
   dead space — the parent box closes snugly under the title. */
.card-copy > :last-child {
  margin-bottom: 0;
}

.story-card {
  overflow: hidden;
}

.thumb-wrap {
  position: relative;
  overflow: hidden;
}

.thumb-wrap img,
.thumb-wrap video {
  width: 100%;
  height: auto;
  object-fit: contain;
  filter: saturate(0.92) contrast(1.02);
  background: #121519;
}

/* Compact card variant — used by the title-only project cards (homepage 3D,
   3D Design and Graphic Design listings). Vertical padding is minimal — 8px
   above the title, 12px below — so the box hugs its asset + title with no void. */
.compact {
  padding: var(--space-xs) var(--space-md) var(--space-sm);
  gap: var(--space-2xs);
}

.feature-stack {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: var(--space-xl) var(--space-lg);
}

.stack-header h3 {
  margin: 0 0 var(--space-md);
  font-size: clamp(1.3rem, 1.75vw, 1.95rem);
  line-height: var(--leading-subhead);
  letter-spacing: var(--track-subhead);
  text-wrap: balance;
}

.feature-stack ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-sm);
  color: var(--muted);
  font-size: 1rem;
  line-height: var(--leading-body);
}

.feature-stack li {
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--line);
}

/* Photo grid — masonry (natural aspect tessellation) */
.photo-grid {
  column-count: 4;
}

.collection-photo-grid {
  display: block;
  column-count: 4;
  column-gap: var(--grid-gutter-sm);
}

/* In masonry, every photo card is a snug column item at its natural aspect */
.photo-card {
  position: relative;
  overflow: hidden;
  margin: 0 0 var(--grid-gutter-sm);
  break-inside: avoid;
  background: #121519;
}

.collection-link,
.project-link {
  display: block;
  color: inherit;
}

.collection-link {
  transition: transform 220ms ease, border-color 220ms ease, box-shadow 220ms ease;
}

.collection-link:hover,
.collection-link:focus-visible {
  transform: translateY(-2px);
  border-color: rgba(255,255,255,0.16);
  box-shadow: var(--shadow-soft-lift), var(--glass-sheen);
}

/* Assets render at their natural aspect — snug, no fixed ratio / crop */
.photo-card img,
.photo-card video {
  width: 100%;
  height: auto;
  object-fit: contain;
}

.photo-card__zoomable {
  cursor: zoom-in;
}

.photo-card:hover img,
.photo-card:focus-within img {
  transform: scale(1.02);
  filter: saturate(1.02) contrast(1.02);
}

/* Photography — one continuous packed masonry in project order. The projects
   are no longer labelled or separated. The first frame of each project keeps
   its anchor id, so the deep-link scroll offset is applied per anchored frame. */
.photo-groups {
  margin-top: var(--space-xl);
}

.photo-masonry .photo-card[id] {
  scroll-margin-top: 120px;
}

/* Photography masonry — independent packed columns. Frames flow contiguously
   down each column so the grid is always flush (no holes) and tall portrait
   frames are never split. Images keep their natural aspect (object-fit:
   contain, height auto) so portrait shots render portrait. */
.photo-masonry {
  display: flex;
  align-items: flex-start;
  width: 100%;
}

.photo-masonry__col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.photo-masonry .photo-card {
  width: 100%;
  margin: 0;
}

.graphic-showcase {
  display: grid;
  grid-template-columns: 1.5fr 0.7fr;
  gap: var(--grid-gutter);
  align-items: start;
}

.graphic-project-list {
  margin-top: var(--space-2xl);
}

/* The stage is a two-row grid: the video owns row 1 and the two arrows sit
   together on row 2. The `auto auto 1fr` columns let the arrow pair hug the
   video's left edge (they occupy the auto-sized first two columns) while the
   1fr column absorbs the remainder. */
.project-stage {
  position: relative;
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: center;
  gap: var(--grid-gutter-sm);
  padding: var(--grid-gutter-sm);
  border: 1px solid var(--line);
  background: linear-gradient(180deg, rgba(255,255,255,0.015), rgba(255,255,255,0.025));
}

.project-stage video {
  grid-row: 1;
  grid-column: 1 / -1;
  width: 100%;
  height: auto;
  min-height: 0;
  object-fit: contain;
  background: #161a1d;
  border: 1px solid rgba(255,255,255,0.04);
}

/* Arrows: row 2, side by side (left-facing arrow left of the right-facing one),
   both flush with the left edge of the video above them. */
.project-stage .slide-button.prev {
  grid-row: 2;
  grid-column: 1;
}

.project-stage .slide-button.next {
  grid-row: 2;
  grid-column: 2;
}

.graphic-project-gallery {
  display: grid;
  gap: var(--grid-gutter);
}

/* IKEA: sit the description beside the asset grid so the secondary assets
   fill the space to the right of the text instead of leaving it empty. */
.graphic-project-gallery--split {
  grid-template-columns: minmax(0, 820px) minmax(0, 1fr);
}

.graphic-project-gallery--split .graphic-project-hero {
  grid-column: 1 / -1;
}

/* Present the secondary assets in row-major (filename) order — left to right,
   top to bottom — instead of the column-major flow of CSS multi-column, which
   visually reorders them away from the registry (filename) order. */
.graphic-project-gallery--split .graphic-asset-grid {
  columns: auto;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--grid-gutter-sm);
  align-items: start;
}

@media (max-width: 720px) {
  .graphic-project-gallery--split .graphic-asset-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 520px) {
  .graphic-project-gallery--split .graphic-asset-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 980px) {
  .graphic-project-gallery--split {
    grid-template-columns: minmax(0, 1fr);
  }

  .graphic-project-gallery--split .graphic-project-hero {
    grid-column: auto;
  }
}

/* Same two-row construction as `.project-stage`: the video owns row 1 and the
   arrow pair sits on row 2, flush with the video's left edge (the auto-sized
   first two columns hug the arrows). */
.graphic-project-hero {
  position: relative;
  display: grid;
  grid-template-columns: auto auto 1fr;
  align-items: center;
  gap: var(--grid-gutter-sm);
  padding: var(--grid-gutter-sm);
  border: 1px solid var(--line);
}

.graphic-project-hero video {
  grid-row: 1;
  grid-column: 1 / -1;
  width: 100%;
  height: auto;
  min-height: 0;
  object-fit: contain;
  background: #121519;
}

.graphic-project-hero .slide-button {
  justify-self: center;
}

.graphic-project-hero .slide-button.prev {
  grid-row: 2;
  grid-column: 1;
}

.graphic-project-hero .slide-button.next {
  grid-row: 2;
  grid-column: 2;
}

/* Published-outcome PDF: a small PDF icon pinned to the bottom right of the
   hero's parent box — `.graphic-project-hero` is `position: relative`, so the
   icon anchors to its corner and lands in the empty right-hand side of the
   arrow row, clear of the scrub arrows on the left. The icon artwork is black
   on transparency, so inverting it gives white; `brightness(0)` first makes
   that unconditional, exactly as the footer and software icons do. No plate —
   a drop shadow keeps it legible over bright frames of the hero video instead. */
.hero-pdf {
  position: absolute;
  right: var(--space-md);
  bottom: var(--space-md);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-sm);
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.55));
  transition: transform 180ms ease;
}

.hero-pdf:hover,
.hero-pdf:focus-visible {
  transform: translateY(-2px) scale(1.04);
}

.hero-pdf:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.hero-pdf img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: brightness(0) invert(1);
}

.graphic-asset-grid {
  columns: 3 240px;
  column-gap: var(--grid-gutter-sm);
}

.graphic-asset-frame {
  break-inside: avoid;
  margin: 0 0 var(--grid-gutter-sm);
  overflow: hidden;
  background: #121519;
  cursor: zoom-in;
}

.graphic-asset {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
}

/* Graphic figcaption — same label tracking as 3D */
.graphic-asset-frame figcaption {
  padding: var(--space-sm) var(--space-md);
  color: var(--muted);
  font-size: 0.7rem;
  letter-spacing: var(--track-label);
  line-height: var(--leading-label);
  text-transform: uppercase;
  max-width: none;
}

.project-detail-grid .collection-card__media img,
.project-detail-grid .collection-card__media video {
  width: 100%;
  height: auto;
  min-height: 0;
  object-fit: contain;
}

.slide-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  border: 1px solid var(--line);
  background: rgba(11, 13, 16, 0.65);
  color: var(--text);
  border-radius: 50%;
  cursor: pointer;
  transition: transform 180ms ease, border-color 180ms ease;
}

.slide-button:hover,
.slide-button:focus-visible {
  transform: translateY(-1px);
  border-color: rgba(255,255,255,0.18);
}

.slide-button span {
  font-size: 1.25rem;
}

.project-copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-sm);
}

.project-copy h3 {
  font-size: clamp(1.75rem, 2.4vw, 2.6rem);
  line-height: var(--leading-subhead);
  letter-spacing: var(--track-subhead);
  text-wrap: balance;
}

.project-copy p {
  margin: 0;
  color: var(--muted);
  line-height: var(--leading-body);
  max-width: 52ch;
}

.project-detail-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-top: var(--space-xs);
  color: var(--text);
  letter-spacing: var(--track-label);
  line-height: var(--leading-label);
  text-transform: uppercase;
  font-size: 0.7rem;
}

.site-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  padding-bottom: var(--space-3xl);
}

.site-footer h3 {
  font-size: clamp(1.5rem, 2.6vw, 2.6rem);
  line-height: var(--leading-heading);
  letter-spacing: var(--track-heading);
  max-width: 760px;
  text-wrap: balance;
}

/* The email pill only — the social icons that follow are anchors too, so this
   rule is scoped to the direct child rather than every `a` in the footer. */
.site-footer .footer-contact > a {
  padding: var(--space-md) var(--space-lg);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--text);
  font-size: 0.74rem;
  letter-spacing: var(--track-label);
  line-height: var(--leading-label);
  text-transform: uppercase;
  white-space: nowrap;
}

/* Contact block: the email pill with the social icons beneath it (stacked, so
   the icons sit below the email rather than beside it). The three icons are
   black artwork on transparency — measured at 0.000 mean luminance and zero
   saturation — so they take the same white normalisation as the software
   strip, which is what makes them readable on the dark page. */
.footer-contact {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
}

.social-icons {
  display: grid;
  grid-template-columns: repeat(4, 32px);
  gap: var(--space-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.social-icons li {
  aspect-ratio: 1;
  display: grid;
  place-items: center;
}

/* Each icon is a link. The three profiles open in a new tab (every one carries
   target="_blank" plus rel="noopener noreferrer" in the markup); the CV icon
   instead downloads the PDF, so it uses `download="ElanAfzaliCV.pdf"` and no
   target. The anchor fills its cell so the icon keeps its size, and it takes
   the site's usual hover lift plus a visible keyboard focus ring. */
.social-icons a {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  border-radius: var(--radius-sm);
  transition: transform 180ms ease;
}

.social-icons a:hover,
.social-icons a:focus-visible {
  transform: translateY(-2px);
}

.social-icons a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.social-icons img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: brightness(0) invert(1);
}

@media (max-width: 980px) {
  .topbar {
    flex-wrap: wrap;
    justify-content: center;
  }

  .section-intro,
  .graphic-showcase {
    grid-template-columns: 1fr;
  }

  .three-d-grid,
  .collection-grid,
  .collection-list-grid {
    column-count: 2;
  }

  .photo-grid,
  .collection-photo-grid {
    column-count: 3;
  }

  .site-footer {
    flex-direction: column;
    align-items: flex-start;
  }
}

@media (max-width: 720px) {
  .page-shell {
    width: min(100% - 24px, 1440px);
  }

  .page-hero {
    padding-left: max(20px, calc((100vw - 1440px) / 2 + 20px));
    padding-right: max(20px, calc((100vw - 1440px) / 2 + 20px));
  }

  .main-nav {
    width: 100%;
    justify-content: center;
    flex-wrap: wrap;
  }

  .hero {
    min-height: 72vh;
  }

  .hero-content {
    padding-left: 0;
  }

  .section-header {
    flex-direction: column;
    align-items: flex-start;
  }

  .three-d-grid,
  .collection-grid,
  .collection-list-grid {
    column-count: 2;
  }

  .photo-grid,
  .collection-photo-grid {
    column-count: 2;
  }

  .three-d-gallery:not(.is-single-asset) {
    column-count: 2;
  }

  .slide-button {
    width: 46px;
    height: 46px;
  }

  .project-stage video {
    min-height: 0;
  }
}

@media (max-width: 520px) {
  .three-d-grid,
  .collection-grid,
  .collection-list-grid {
    column-count: 1;
  }

  .photo-grid,
  .collection-photo-grid {
    column-count: 1;
  }

  .three-d-gallery:not(.is-single-asset) {
    column-count: 1;
  }
}

/* ---------------------------------------------------------------------------
   YouTube embeds

   Videos that are also published on the project's YouTube channel are embedded
   when they are shown at full size: a single-asset project gallery, or the
   enlarged lightbox view. Grid layouts keep playing the compressed local
   -grid.mp4 files, the homepage hero keeps its own local video, and the player
   stays interactive (no pointer-events override), so the embed behaves like a
   normal player where it appears (see youtube-embeds.js, 3d-gallery.js).
--------------------------------------------------------------------------- */

/* The player takes the same box the video it stands in for occupied; the base
   .three-d-asset-frame .three-d-asset rule already supplies the width, the
   one-pixel overhang and the frame background. */
.three-d-asset-frame iframe.three-d-asset {
  aspect-ratio: 16 / 9;
  height: auto;
  min-height: 0;
}

.asset-lightbox__media--embed {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  border: 0;
}

/* A player that is not 16:9 (the two square pieces) is sized by height in the
   enlarged view, so the video fills the box instead of being pillarboxed
   inside a wide one; its own aspect-ratio comes from youtube-embeds.js. */
.asset-lightbox__media--embed[data-aspect] {
  width: auto;
  height: calc(96vh - 4rem);
  margin-inline: auto;
}

/* A portrait player (a Short) is sized by height and centred wherever it
   appears, so it reads as a phone-shaped player rather than an enormous
   full-width tile. */
.three-d-asset-frame iframe.three-d-asset[data-orientation="portrait"] {
  display: block;
  width: auto;
  height: 80vh;
  max-width: 100%;
  margin-inline: auto;
}
