/* =========================================================
   VISUALI3D — Nordic design system
   A small, legible set of tokens. Change these, change the site.
   ========================================================= */

:root {
  /* Palette — warm stone neutrals + one restrained sage accent */
  --stone-50:  #f7f5f1;
  --stone-100: #efece5;
  --stone-200: #e3ded4;
  --stone-300: #d3ccbe;
  --ink:       #1b1a17;   /* near-black, warm */
  --ink-soft:  #47443d;
  --muted:     #8b857a;
  --line:      #d9d3c8;
  --accent:    #6f7a68;   /* muted sage — used sparingly */
  --accent-ink:#565f50;

  /* Type */
  --sans: "Inter", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  --serif: "Fraunces", Georgia, "Times New Roman", serif;

  /* Rhythm */
  --wrap: 1240px;
  --gutter: clamp(1.25rem, 4vw, 3.5rem);
  --section-y: clamp(4.5rem, 10vw, 9rem);
  --radius: 14px;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  font-family: var(--sans);
  color: var(--ink);
  background: var(--stone-50);
  line-height: 1.6;
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.075rem);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; cursor: pointer; }

/* Deter casual image/video extraction: no drag-out, no text-selection
   handles, no iOS long-press "Save Image" menu. Paired with the contextmenu
   guard in main.js. Not real protection — the files are still fetchable via
   devtools' network tab — just removes the one-click paths. */
img, video {
  -webkit-user-drag: none;
  user-select: none;
  -webkit-touch-callout: none;
}

::selection { background: var(--accent); color: var(--stone-50); }

.wrap { width: min(100% - 2 * var(--gutter), var(--wrap)); margin-inline: auto; }

.skip-link {
  position: absolute; left: 1rem; top: -3rem; z-index: 100;
  background: var(--ink); color: var(--stone-50);
  padding: 0.6rem 1rem; border-radius: 8px; transition: top 0.2s;
}
.skip-link:focus { top: 1rem; }

/* ---------- Shared type ---------- */
.eyebrow {
  font-size: 0.75rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--accent-ink); font-weight: 500; margin-bottom: 1.1rem;
}
.section-title {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(2rem, 1.3rem + 3vw, 3.4rem);
  line-height: 1.06;
  letter-spacing: -0.015em;
}
.section-title em { font-style: italic; color: var(--accent-ink); }

.rule { display: block; width: 42px; height: 1px; background: var(--ink); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.85rem 1.5rem; border-radius: 100px;
  font-size: 0.9rem; font-weight: 500; letter-spacing: 0.01em;
  border: 1px solid transparent; transition: all 0.35s var(--ease);
}
.btn-primary { background: var(--ink); color: var(--stone-50); }
.btn-primary:hover { background: var(--accent-ink); transform: translateY(-2px); }
.btn-ghost { border-color: var(--line); color: var(--ink); }
.btn-ghost:hover { border-color: var(--ink); transform: translateY(-2px); }

/* =========================================================
   HEADER
   ========================================================= */
.site-header {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  transition: background 0.4s var(--ease), border-color 0.4s var(--ease), backdrop-filter 0.4s;
  border-bottom: 1px solid transparent;
}
.site-header.is-scrolled {
  background: color-mix(in srgb, var(--stone-50) 82%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  border-bottom-color: var(--line);
}
.header-inner {
  display: flex; align-items: center; justify-content: space-between;
  height: 76px;
}
.brand {
  font-family: var(--serif); font-weight: 400; font-size: 1.5rem;
  letter-spacing: -0.02em; line-height: 1;
}
.brand-3 { font-style: italic; color: var(--accent-ink); }

/* Subpages have no video behind the header — keep it solid and dark-on-light */
.site-header--solid { background: var(--stone-50); border-bottom: 1px solid var(--line); }

/* Nav sits over the autoplay video on the home page by default, so it starts
   light with a shadow for legibility, then flips to dark ink once the header
   gains a solid background — either by scrolling, or on subpages where the
   header is always solid (no video to sit over). */
.nav { display: flex; align-items: center; gap: 0.2rem; }
.nav a {
  padding: 0.5rem 0.95rem; border-radius: 100px;
  font-size: 0.85rem; font-weight: 500;
  color: rgba(247, 245, 241, 0.92);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.35);
  transition: color 0.3s, background 0.25s, text-shadow 0.3s, border-color 0.3s;
}
.nav a:hover { background: rgba(255, 255, 255, 0.14); }
.nav .nav-cta {
  border: 1px solid rgba(247, 245, 241, 0.45); margin-left: 0.4rem;
}
.nav .nav-cta:hover { background: rgba(255, 255, 255, 0.16); border-color: rgba(247, 245, 241, 0.75); }

.site-header.is-scrolled .nav a,
.site-header--solid .nav a {
  color: var(--ink-soft); text-shadow: none;
}
.site-header.is-scrolled .nav a:hover,
.site-header--solid .nav a:hover {
  background: color-mix(in srgb, var(--ink) 6%, transparent); color: var(--ink);
}
.site-header.is-scrolled .nav .nav-cta,
.site-header--solid .nav .nav-cta { border-color: var(--line); }
.site-header.is-scrolled .nav .nav-cta:hover,
.site-header--solid .nav .nav-cta:hover { border-color: var(--ink); }

/* Right-hand cluster: nav (desktop), theme switch, hamburger (mobile) */
.header-actions { display: flex; align-items: center; gap: 0.6rem; }

/* Light/dark switch — a pill with a sliding knob and sun/moon glyphs.
   Like the nav, it starts light (over the hero video) and flips to dark ink
   once the header gains a solid background (scrolled, or on subpages). */
.theme-toggle {
  position: relative; flex: none;
  width: 52px; height: 28px; padding: 0;
  border-radius: 100px; border: 1px solid rgba(247, 245, 241, 0.45);
  background: rgba(255, 255, 255, 0.12);
  cursor: pointer;
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
}
.theme-toggle-knob {
  position: absolute; top: 50%; left: 3px; transform: translateY(-50%);
  width: 20px; height: 20px; border-radius: 50%;
  background: #f7f5f1;
  display: grid; place-items: center;
  transition: transform 0.35s var(--ease), background 0.3s var(--ease);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
.theme-toggle[aria-checked="true"] .theme-toggle-knob { transform: translate(24px, -50%); }
.theme-toggle-knob svg { width: 12px; height: 12px; }
.theme-toggle-knob .icon-sun { display: block; color: #b8892f; }
.theme-toggle-knob .icon-moon { display: none; color: #2a2a33; }
.theme-toggle[aria-checked="true"] .theme-toggle-knob .icon-sun { display: none; }
.theme-toggle[aria-checked="true"] .theme-toggle-knob .icon-moon { display: block; }

.site-header.is-scrolled .theme-toggle,
.site-header--solid .theme-toggle {
  border-color: var(--line); background: color-mix(in srgb, var(--ink) 6%, transparent);
}

.nav-toggle {
  display: none; width: 44px; height: 44px; border: none; background: none;
  flex-direction: column; align-items: center; justify-content: center; gap: 6px;
}
.nav-toggle span {
  display: block; width: 24px; height: 1.5px; background: var(--ink);
  transition: transform 0.3s var(--ease), opacity 0.3s;
}
.nav-toggle[aria-expanded="true"] span:first-child { transform: translateY(3.75px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:last-child { transform: translateY(-3.75px) rotate(-45deg); }

.mobile-nav {
  display: flex; flex-direction: column; gap: 0.25rem;
  padding: 0.5rem var(--gutter) 1.5rem;
  background: var(--stone-50); border-bottom: 1px solid var(--line);
}
/* The [hidden] attribute and the .mobile-nav class both have (0,1,0)
   specificity, so without this, "display: flex" above would win over the
   UA stylesheet's "[hidden] { display: none }" and the panel would never
   actually hide when JS toggles the hidden attribute. */
.mobile-nav[hidden] { display: none; }
.mobile-nav a {
  font-family: var(--serif); font-weight: 300; font-size: 1.6rem;
  padding: 0.5rem 0; color: var(--ink); border-bottom: 1px solid var(--line);
}
.mobile-nav a:last-child { border-bottom: none; }

/* =========================================================
   SPLIT HERO
   ========================================================= */
.hero { position: relative; }
.hero-split {
  display: grid;
  grid-template-columns: 1fr 1fr;   /* 50 / 50 */
  min-height: 100vh;
  min-height: 100svh;
  position: relative;
}
.hero-panel { position: relative; overflow: hidden; }

.hero-text {
  display: flex; align-items: center;
  padding: clamp(2rem, 6vw, 5rem) clamp(1.5rem, 5vw, 5rem);
  background: var(--stone-50);
}
.hero-text-inner { max-width: 34rem; }
.hero-title {
  font-family: var(--serif); font-weight: 300;
  font-size: clamp(2.6rem, 1.4rem + 5.2vw, 5.4rem);
  line-height: 0.98; letter-spacing: -0.025em;
  margin-bottom: 1.6rem;
}
.hero-title em { font-style: italic; color: var(--accent-ink); }
.hero-sub {
  color: var(--ink-soft); font-size: 1.075rem; max-width: 30rem;
  margin-bottom: 2.2rem;
}
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }

.hero-video { background: var(--stone-200); }

/* Poster: shown immediately, sits behind the player until it starts */
.hero-poster {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 45%; z-index: 1;
}

/* Video fades in over the poster once it actually starts playing */
.hero-video-frame {
  position: absolute; inset: 0; overflow: hidden; z-index: 2;
  opacity: 0; transition: opacity 0.7s var(--ease);
}
.hero-video.is-playing .hero-video-frame { opacity: 1; }
.hero-reel {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
  object-fit: cover; object-position: 50% 45%; display: block;
}
.hero-tag {
  position: absolute; left: 1.25rem; bottom: 1.25rem; z-index: 3;
  font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--stone-50); background: color-mix(in srgb, var(--ink) 55%, transparent);
  backdrop-filter: blur(6px); padding: 0.4rem 0.8rem; border-radius: 100px;
}

.hero-scroll {
  position: absolute; left: clamp(1.5rem, 5vw, 5rem); bottom: 1.75rem;
  display: flex; align-items: center; gap: 0.75rem; z-index: 4;
  font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted);
}
.hero-scroll-line {
  width: 46px; height: 1px; background: var(--muted); position: relative; overflow: hidden;
}
.hero-scroll-line::after {
  content: ""; position: absolute; inset: 0; width: 40%; background: var(--ink);
  animation: scrollcue 2.4s var(--ease) infinite;
}
@keyframes scrollcue { 0% { transform: translateX(-120%); } 60%,100% { transform: translateX(320%); } }

/* =========================================================
   MANIFESTO
   ========================================================= */
.manifesto { padding: var(--section-y) 0; border-bottom: 1px solid var(--line); }
.manifesto-lead {
  font-family: var(--serif); font-weight: 300;
  font-size: clamp(1.7rem, 1rem + 3.4vw, 3.6rem);
  line-height: 1.12; letter-spacing: -0.02em; max-width: 20ch;
}
.manifesto-lead span { color: var(--muted); }
.manifesto-meta {
  display: flex; gap: 1.5rem; align-items: flex-start;
  margin-top: 2.5rem; max-width: 44rem; margin-left: auto;
}
.manifesto-meta .rule { margin-top: 0.7rem; flex: none; }
.manifesto-meta p { color: var(--ink-soft); }

/* ---------- Pitch (studio statement, stacked blocks) ---------- */
/* Set on a faint stone band so it reads as a distinct "about" passage. */
.pitch { background: var(--stone-100); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
/* .section.pitch (not just .pitch) so this padding-top reliably beats the
   shared .section rule below regardless of source order. */
.section.pitch { padding-top: clamp(1.5rem, 4vw, 3rem); }
.pitch-grid {
  display: flex; flex-direction: column;
  gap: clamp(3rem, 7vw, 5rem);
}
.pitch-block { max-width: 74rem; margin-inline: auto; }

/* Head row: large faint index numeral + sage eyebrow label, over a hairline */
.pitch-head {
  display: flex; align-items: baseline; gap: 1rem;
  padding-bottom: 0.9rem; margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--line);
}
.pitch-index {
  font-family: var(--serif); font-weight: 300;
  font-size: clamp(1.7rem, 1rem + 2.2vw, 2.7rem);
  line-height: 1; letter-spacing: -0.02em; color: var(--stone-300);
}
.pitch-eyebrow {
  font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase;
  font-weight: 500; color: var(--accent-ink);
}

.pitch-title {
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.35rem);
  line-height: 1.2; letter-spacing: -0.015em;
  margin-bottom: 1.1rem;
}
.pitch-title em { font-style: italic; color: var(--accent-ink); }
.pitch-block p { color: var(--ink-soft); font-size: 1rem; line-height: 1.8; }
.pitch-block p em { font-style: italic; color: var(--accent-ink); }

/* =========================================================
   SECTIONS (shared)
   ========================================================= */
.section { padding: var(--section-y) 0; }
.section-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 2rem; flex-wrap: wrap; margin-bottom: clamp(2.5rem, 5vw, 4rem);
}

/* =========================================================
   PROJECTS — full-bleed row of overlay cards
   ========================================================= */
/* Row breaks out of .wrap to sit edge-to-edge; it is a direct child of the
   full-width section, so 100% already equals the viewport width. */
.project-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.25rem, 2.5vw, 2rem);            /* more air between cards */
  /* capped to the page's content width (like .wrap) so the cards sit smaller
     and edge-aligned with the section heading, not full-bleed */
  width: min(100% - 2 * var(--gutter), var(--wrap));
  margin-inline: auto;
}

.pcard { position: relative; overflow: hidden; }
.pcard-media {
  display: block; position: relative; overflow: hidden;
  aspect-ratio: 1 / 1; background: var(--stone-200);   /* home-page previews: square */
}
.pcard-media img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 45%;
  filter: saturate(0.85);   /* previews sit at -15% saturation, full colour on hover */
  transition: transform 1s var(--ease), filter 0.6s var(--ease);
}
.pcard:hover .pcard-media img { transform: scale(1.045); filter: saturate(1); }
/* "Weitere Projekte" covers get the same treatment */
.more-card img { filter: saturate(0.85); }
.more-card:hover img { filter: saturate(1); }

/* Stacked variant (home previews): caption below the image, like the
   "Weitere Projekte" cards — dark serif title + muted town, always visible.
   Two-class selectors so these beat the overlay's white/uppercase/mobile-size
   rules regardless of source order. */
.pcard-caption { padding-top: 0.85rem; }
.pcard--stacked .pcard-title {
  color: var(--ink); text-transform: none; letter-spacing: 0;
  font-family: var(--serif); font-weight: 400;
  font-size: 1.05rem; line-height: 1.3;
}
.pcard--stacked .pcard-sub { color: var(--muted); font-size: 0.8rem; margin-top: 0.15rem; }

/* Optional hover-reveal clip (e.g. Green Haven's flythrough). preload="none"
   in the markup keeps it from downloading until the pointer actually enters —
   these files run tens of MB. JS starts/stops playback; this just crossfades. */
.pcard-video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 45%;
  opacity: 0; transition: opacity 0.4s var(--ease);
  pointer-events: none;
}
.pcard.is-video-active .pcard-video { opacity: 1; }

/* Bottom-left caption, sitting directly on the image — no scrim.
   Pinned white in both themes; a soft shadow carries it over bright renders. */
.pcard-overlay {
  /* No `right`, so the box shrinks to its content and sits bottom-left only.
     max-width keeps a long name from running past the image. */
  position: absolute; left: 0.75rem; bottom: 0.75rem;
  max-width: calc(100% - 1.5rem);
  display: flex; flex-direction: column; gap: 0.1rem;
  padding: 0.72rem 0.86rem;
  color: #fff;
  background: rgba(18, 17, 14, 0.6);
  backdrop-filter: blur(12px) saturate(1.1);
  border-radius: 3px;
}
.pcard-row {
  display: flex; align-items: center; justify-content: space-between; gap: 0.9rem;
}
.pcard-title {
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(0.9rem, 0.81rem + 0.36vw, 1.125rem);
  line-height: 1.15;
  text-transform: uppercase; letter-spacing: 0.09em;
}
.pcard-arrow {
  width: 15px; height: 15px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  transition: transform 0.8s var(--ease);
}
.pcard:hover .pcard-arrow { transform: translateX(3px); }

/* Hover reveal — only where hovering exists. On touch the caption stays put,
   otherwise it would be unreachable. */
@media (hover: hover) and (pointer: fine) {
  .pcard-overlay {
    opacity: 0; transform: translateY(14px);
    transition: opacity 0.8s var(--ease), transform 0.9s var(--ease);
  }
  .pcard:hover .pcard-overlay,
  .pcard:focus-within .pcard-overlay { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .pcard-overlay { transition: opacity 0.2s linear; transform: none; }
}
.pcard-sub { font-size: 0.765rem; color: var(--ink-soft); }
.pcard-link {
  margin-top: 0.6rem; font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
  padding-bottom: 3px;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 100% 1px; background-position: 0 100%; background-repeat: no-repeat;
  transition: background-size 0.4s var(--ease);
}
.pcard:hover .pcard-link { background-size: 0% 1px; }

/* =========================================================
   PROJECT DETAIL PAGE — compact: title band, meta band, text | slider
   ========================================================= */
.project-page { padding-top: 76px; }

.back-link {
  display: inline-flex; align-items: center; gap: 0.55rem;
  font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted); padding: 1.5rem 0 0;
  transition: color 0.3s var(--ease);
}
.back-link:hover { color: var(--ink); }
.back-link span { transition: transform 0.3s var(--ease); }
.back-link:hover span { transform: translateX(-4px); }

/* --- Title band: centred, framed by hairlines --- */
.pj-head { text-align: center; padding: clamp(1.5rem, 3.5vw, 2.75rem) 0; }
.pj-head-inner {
  display: inline-block; padding: clamp(0.85rem, 1.8vw, 1.35rem) clamp(1.5rem, 5vw, 4rem);
  border-top: 1px solid var(--ink); border-bottom: 1px solid var(--ink);
}
.pj-title, .pj-ort {
  font-family: var(--serif); font-weight: 300;
  text-transform: uppercase; letter-spacing: 0.14em; line-height: 1.35;
}
.pj-title { font-size: clamp(1.35rem, 0.9rem + 1.9vw, 2.35rem); }
.pj-ort { font-size: clamp(1rem, 0.8rem + 1vw, 1.6rem); color: var(--muted); }

/* --- Meta band: dark, four framed cells --- */
.pj-meta { background: var(--ink); padding: clamp(1.25rem, 2.5vw, 2rem) 0; }
.pj-meta-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: clamp(0.6rem, 1.2vw, 1rem);
}
.pj-meta-grid > div {
  border: 1px solid rgba(247, 245, 241, 0.22);
  padding: clamp(0.75rem, 1.4vw, 1.1rem) 0.75rem;
  display: flex; flex-direction: column; justify-content: center; gap: 0.35rem;
  text-align: center;
}
.pj-meta-grid dt {
  font-size: 0.6rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: rgba(247, 245, 241, 0.5);
}
.pj-meta-grid dd { font-size: 0.8rem; line-height: 1.4; color: var(--stone-50); }

/* --- Split: description | slider --- */
.pj-split {
  display: grid; grid-template-columns: 1fr 1fr;
  min-height: clamp(480px, 68vh, 760px);
}
.pj-text {
  background: var(--stone-100);
  display: flex; align-items: center; justify-content: center;
  padding: clamp(1.75rem, 4vw, 3.5rem); text-align: center;
}
.pj-text-inner { max-width: 34rem; }
.pj-statement {
  font-family: var(--serif); font-weight: 300;
  text-transform: uppercase; letter-spacing: 0.07em;
  font-size: clamp(1.15rem, 0.9rem + 1.1vw, 1.85rem); line-height: 1.3;
}
.pj-rule { height: 1px; background: var(--line); margin: clamp(1rem, 2vw, 1.6rem) auto; }
.pj-text p {
  color: var(--ink-soft); font-size: 0.9rem; line-height: 1.7; margin-bottom: 0.8rem;
}
.pj-text p:last-child { margin-bottom: 0; }
.pj-text p a { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 0.15em; }
.pj-text p a:hover { color: var(--ink); }

/* --- Slider --- */
/* A definite height of its own, not just "stretch to fill the grid row":
   .pj-split only sets min-height, so without this, a percentage-height <img>
   inside a portrait slide falls back to the image's own intrinsic aspect
   ratio and inflates the whole row — the slot must stay the same size for
   every project regardless of what a given slide's image looks like. */
.pslider { position: relative; overflow: hidden; background: var(--stone-200);
  height: clamp(480px, 68vh, 760px); }
.pslider:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pslider-track {
  display: flex; height: 100%;
  transition: transform 0.7s var(--ease);
  will-change: transform;
}
.pslide { flex: 0 0 100%; height: 100%; }
/* Landscape slides (the vast majority) fill the fixed slot edge to edge —
   that's what "cover" is for, and the slot's own height no longer depends on
   image content (see .pslider below), so this never bloats the layout.
   Portrait slides would just get cropped by "cover", so main.js/build_site.py
   tag each <figure> with its real orientation at build time (a JPEG's own
   aspect ratio, not guessed from CSS) and only THOSE get "contain" — fit
   inside, letterboxed, never stretched or cropped. */
.pslide img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pslide--portrait img { object-fit: contain; }

.pslider-btn {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 46px; height: 46px; border: none; border-radius: 50%;
  background: none; color: var(--stone-50); cursor: pointer;
  display: grid; place-items: center; z-index: 2;
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.45));
  transition: background 0.3s var(--ease), transform 0.3s var(--ease);
}
.pslider-btn:hover { background: rgba(27, 26, 23, 0.35); }
.pslider-btn.prev { left: 0.5rem; }
.pslider-btn.next { right: 0.5rem; }
.pslider-btn.prev:hover { transform: translateY(-50%) translateX(-3px); }
.pslider-btn.next:hover { transform: translateY(-50%) translateX(3px); }
.pslider-btn svg { width: 26px; height: 26px; fill: none; stroke: currentColor; stroke-width: 1.5; }

.pslider-count {
  position: absolute; right: 1rem; bottom: 1rem; z-index: 2;
  font-size: 0.7rem; letter-spacing: 0.1em; color: var(--stone-50);
  background: rgba(27, 26, 23, 0.5); backdrop-filter: blur(6px);
  padding: 0.3rem 0.7rem; border-radius: 100px;
  font-variant-numeric: tabular-nums;
}

/* --- More projects (compact) --- */
.more-projects { border-top: 1px solid var(--line); padding: clamp(2.5rem, 5vw, 4rem) 0; }
.more-grid {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 2vw, 1.75rem); margin-top: 1.75rem;
}
.more-card figure {
  overflow: hidden; aspect-ratio: 3 / 2; background: var(--stone-200); margin-bottom: 0.8rem;
}
.more-card img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform 0.9s var(--ease), filter 0.6s var(--ease);
}
.more-card:hover img { transform: scale(1.045); }
.more-card h3 { font-family: var(--serif); font-weight: 400; font-size: 1.05rem; }
.more-card p { font-size: 0.78rem; color: var(--muted); }

/* =========================================================
   PROJECT DETAIL — editorial layout (test)
   Full-bleed hero + title overlay, meta band, text|image, then the cover
   again clean, then the remaining shots stacked full-bleed.
   ========================================================= */
.project-page--editorial { padding-top: 0; }

/* Hero fills the viewport below the fixed solid header (76px) */
.pj-hero {
  position: relative; overflow: hidden;
  margin-top: 76px; height: calc(100vh - 76px); height: calc(100svh - 76px);
  min-height: 520px; display: flex; align-items: center;
}
.pj-hero-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Light legibility filter: darker on the left where the title sits */
.pj-hero-shade {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg, rgba(18,17,14,0.58) 0%, rgba(18,17,14,0.30) 42%, rgba(18,17,14,0.06) 78%);
}
.pj-hero-back {
  position: absolute; top: 1.25rem; left: var(--gutter); z-index: 3;
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase;
  color: rgba(255,255,255,0.85);
}
.pj-hero-back:hover { color: #fff; }
.pj-hero-caption {
  position: relative; z-index: 2; color: #fff;
  width: min(100% - 2 * var(--gutter), var(--wrap)); margin-inline: auto;
}
.pj-hero-caption .eyebrow { color: rgba(255,255,255,0.82); margin-bottom: 0.9rem; }
.pj-hero-title {
  font-family: var(--serif); font-weight: 300; color: #fff;
  font-size: clamp(2.6rem, 1.4rem + 5vw, 6rem); line-height: 1.0; letter-spacing: -0.02em;
}
.pj-hero-ort {
  font-family: var(--serif); font-weight: 300; color: rgba(255,255,255,0.88);
  font-size: clamp(1rem, 0.8rem + 0.8vw, 1.5rem); letter-spacing: 0.06em; margin-top: 0.7rem;
}

/* Text | next image — equal columns, both stretched to the same height (the
   text block's natural height); the image crops to fill it via cover. */
.pj-ed-feature { padding: var(--section-y) 0; }
.pj-ed-feature-grid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 4.5rem); align-items: stretch;
}
.pj-ed-text { max-width: 34rem; }
.pj-ed-text .pj-statement { text-align: left; }
.pj-ed-text .pj-rule { margin-left: 0; margin-right: 0; }
.pj-ed-text p { color: var(--ink-soft); font-size: 0.98rem; line-height: 1.8; margin-bottom: 0.9rem; }
.pj-ed-text p:last-child { margin-bottom: 0; }
.pj-ed-text p a { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 0.15em; }
.pj-ed-text p a:hover { color: var(--ink); }
.pj-ed-figure { margin: 0; height: 100%; }
.pj-ed-figure img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Continuous marquee of project info (replaces the meta band in editorial).
   Edge-faded, scrolls left→right, pauses on hover. */
.pj-marquee {
  position: relative; overflow: hidden;
  padding: clamp(1.1rem, 2.4vw, 1.75rem) 0;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 7%, #000 93%, transparent);
}
.pj-marquee-track {
  display: flex; width: max-content; align-items: baseline;
  animation: pj-marquee 46s linear infinite;
}
.pj-marquee:hover .pj-marquee-track { animation-play-state: paused; }
@keyframes pj-marquee { from { transform: translateX(-50%); } to { transform: translateX(0); } }
.pj-mq-item {
  display: inline-flex; align-items: baseline; gap: 0.7rem;
  padding: 0 clamp(1.5rem, 4vw, 3rem); white-space: nowrap;
}
.pj-mq-item::after { content: "·"; color: var(--muted); margin-left: clamp(1.5rem, 4vw, 3rem); }
.pj-mq-label {
  font-size: 0.66rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent-ink);
}
.pj-mq-value {
  font-family: var(--serif); font-weight: 300; color: var(--ink);
  font-size: clamp(1rem, 0.9rem + 0.5vw, 1.45rem);
}
@media (prefers-reduced-motion: reduce) { .pj-marquee-track { animation: none; } }

/* Cover reappearance + remaining perspectives: a carousel (reuses .pslider)
   with an expanding thumbnail strip underneath — click a thumb to jump to
   it, the active one widens to the full 16:9 frame, the rest collapse thin. */
/* Same width formula as .wrap (not just a similar max-width) so this lines
   up exactly with the segments above and below, instead of by coincidence. */
.pj-ed-carousel { width: min(100% - 2 * var(--gutter), var(--wrap)); margin: 0 auto; padding-bottom: var(--section-y); }
.pj-ed-carousel .pslider { height: auto; aspect-ratio: 16 / 10; }
.pj-thumbs {
  display: flex; gap: 4px; height: 84px; margin-top: 0.7rem; overflow: hidden;
}
.pj-thumbs button {
  flex: 1 1 0%; min-width: 0; height: 100%; padding: 0; border: 0;
  background: var(--stone-200); overflow: hidden; cursor: pointer;
  opacity: 0.55; transition: flex-grow 0.5s var(--ease), opacity 0.4s var(--ease);
}
.pj-thumbs button.is-active { flex-grow: 5; opacity: 1; }
.pj-thumbs button img { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }

@media (max-width: 900px) {
  .pj-meta-grid { grid-template-columns: repeat(2, 1fr); }
  .pj-ed-feature-grid { grid-template-columns: 1fr; gap: 2rem; }
  .pj-split { grid-template-columns: 1fr; min-height: 0; }
  .pj-text { order: 2; }
  /* height: auto overrides the desktop clamp() above — without it, the
     fixed height and aspect-ratio fight over which dimension is derived
     from which, and the slider computes wider than its 1-column track. */
  .pslider { order: 1; height: auto; aspect-ratio: 4 / 3; }

  /* Marquee → static 2-col grid on mobile. Not just a bandwidth call: a lot
     of phones run with reduced-motion on (iOS "Reduce Motion", Android
     battery savers), which freezes the animation via the rule below — and a
     frozen marquee only ever shows its first item, with the other three
     permanently clipped by overflow:hidden. A static grid has no such
     failure mode regardless of the visitor's motion setting. */
  .pj-marquee {
    -webkit-mask-image: none; mask-image: none; overflow: visible;
    padding: clamp(1rem, 4vw, 1.5rem) var(--gutter);
  }
  .pj-marquee-track {
    animation: none; width: 100%;
    display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem 1rem;
  }
  .pj-mq-item {
    display: flex; flex-direction: column; align-items: flex-start; gap: 0.25rem;
    padding: 0; white-space: normal;
  }
  .pj-mq-item::after { content: none; }
  /* The track repeats the 4 real pairs 6× for a seamless loop — on the
     static grid only the first, real set should show. */
  .pj-mq-item:nth-child(n+5) { display: none; }
}
@media (max-width: 620px) {
  .more-grid { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .pslider-track { transition: none; }
}

/* =========================================================
   SERVICES — each one its own full-height section that stacks
   All three are sticky at top:0 inside .svc-stack, so the first one stays
   put while the next scrolls up over it. Later siblings paint on top, so
   each card just needs an opaque background — no z-index bookkeeping.
   ========================================================= */
.services { background: var(--stone-100); border-block: 1px solid var(--line); }

.svc-stack { position: relative; }

.svc {
  position: sticky;
  top: 0;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: clamp(1.5rem, 4vw, 4rem);
  padding-block: clamp(3rem, 6vw, 5rem);
  /* Opaque, or the card underneath would show through as it is covered */
  background: var(--stone-100);
  border-top: 1px solid var(--line);
  /* Makes the incoming edge readable as it slides over the previous card */
  box-shadow: 0 -30px 70px -40px rgba(0, 0, 0, 0.55);
}

.svc-media, .svc-text { position: relative; }

.svc-media figure { overflow: hidden; background: var(--stone-200); }
.svc-media img, .svc-media video { width: 100%; height: 100%; object-fit: cover; display: block; }
.svc-media > figure { aspect-ratio: 16 / 10; }

/* Two images side by side */
.svc-media--pair { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.svc-media--pair figure { aspect-ratio: 4 / 5; }

.svc-vr { aspect-ratio: 16 / 10; background: var(--stone-200); overflow: hidden; }
.svc-vr iframe { width: 100%; height: 100%; border: 0; display: block; }

.svc-text { max-width: 34rem; }
.svc--media-left  .svc-text { padding-right: var(--gutter); }
.svc--media-right .svc-text { padding-left: var(--gutter); justify-self: end; }

.svc-title {
  font-family: var(--serif); font-weight: 300;
  font-size: clamp(1.9rem, 1.2rem + 2.4vw, 3.2rem);
  line-height: 1; text-transform: uppercase; letter-spacing: 0.03em;
}
.svc-kicker {
  font-size: 0.72rem; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--accent-ink); margin-top: 0.6rem;
}
.svc-quote {
  font-family: var(--serif); font-weight: 300; font-style: italic;
  font-size: clamp(1.15rem, 0.9rem + 1vw, 1.6rem); line-height: 1.3;
  margin: 1.5rem 0 1.1rem;
}
.svc-text p:last-child { color: var(--ink-soft); }

@media (max-width: 900px) {
  .svc {
    grid-template-columns: 1fr;
    gap: 1.75rem;
    min-height: 0;            /* stacking a tall card on a phone traps the reader */
    position: static;
    box-shadow: none;
  }
  .svc-text { padding-inline: var(--gutter) !important; justify-self: start !important; }
  .svc--media-right .svc-text { order: -1; }
}

@media (prefers-reduced-motion: reduce) {
  .svc { position: static; min-height: 0; }
}

/* =========================================================
   STUDIO
   ========================================================= */
.studio-grid {
  display: grid; grid-template-columns: 0.9fr 1.1fr;
  gap: clamp(2rem, 6vw, 5rem); align-items: center;
}
.studio-media { overflow: hidden; border-radius: var(--radius); aspect-ratio: 4 / 5; }
.studio-media img { width: 100%; height: 100%; object-fit: cover; }
.studio-text .section-title { margin-bottom: 1.5rem; }
.studio-text p { color: var(--ink-soft); margin-bottom: 1rem; max-width: 34rem; }

.stats {
  display: flex; gap: clamp(1.5rem, 4vw, 3rem); margin-top: 2.25rem;
  padding-top: 2rem; border-top: 1px solid var(--line);
}
.stats dt {
  font-family: var(--serif); font-weight: 300; font-size: clamp(1.8rem, 3vw, 2.6rem);
  line-height: 1; letter-spacing: -0.02em;
}
.stats dd { font-size: 0.8rem; color: var(--muted); margin-top: 0.35rem; }

/* =========================================================
   CONTACT
   ========================================================= */
.contact { background: var(--ink); color: var(--stone-100); text-align: center; }
.contact .eyebrow { color: color-mix(in srgb, var(--accent) 70%, white 30%); }
.contact-title {
  font-family: var(--serif); font-weight: 300;
  font-size: clamp(3rem, 1.5rem + 8vw, 8rem); line-height: 0.95; letter-spacing: -0.03em;
  color: var(--stone-50); margin-bottom: 1.25rem;
}
.contact-sub { color: color-mix(in srgb, var(--stone-100) 70%, var(--ink)); max-width: 34rem; margin: 0 auto 2.5rem; }
.contact-mail {
  display: inline-block; font-family: var(--serif); font-style: italic;
  font-size: clamp(1.4rem, 1rem + 2vw, 2.4rem); color: var(--stone-50);
  padding-bottom: 0.35rem; background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 100% 1px; background-position: 0 100%; background-repeat: no-repeat;
  transition: background-size 0.4s var(--ease);
}
.contact-mail:hover { background-size: 0% 1px; }
.contact-links {
  display: flex; gap: 1.5rem; justify-content: center; margin-top: 2.5rem;
  font-size: 0.85rem; letter-spacing: 0.05em;
}
.contact-links a { color: color-mix(in srgb, var(--stone-100) 65%, var(--ink)); transition: color 0.3s; }
.contact-links a:hover { color: var(--stone-50); }

/* =========================================================
   FOOTER
   ========================================================= */
.site-footer { padding: 3rem 0; background: var(--stone-50); }
.footer-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem; flex-wrap: wrap;
}
.brand--footer { font-size: 1.3rem; }
.footer-nav { display: flex; gap: 1.25rem; font-size: 0.85rem; color: var(--ink-soft); }
.footer-nav a:hover { color: var(--ink); }
.footer-copy { font-size: 0.78rem; color: var(--muted); }

/* =========================================================
   REVEAL ANIMATION
   ========================================================= */
.reveal { opacity: 0; transform: translateY(22px); transition: opacity 0.8s var(--ease), transform 0.8s var(--ease); }
.reveal.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .hero-scroll-line::after { animation: none; }
}

/* =========================================================
   RESPONSIVE
   ========================================================= */
@media (max-width: 900px) {
  /* Solid header on mobile so the mark stays legible above the video */
  .site-header { background: var(--stone-50); border-bottom: 1px solid var(--line); }
  .nav { display: none; }
  .nav-toggle { display: flex; }

  .hero-split { grid-template-columns: 1fr !important; min-height: 0; padding-top: 72px; }
  .hero-text { order: 2; padding-block: clamp(2.5rem, 8vw, 4rem); }
  /* Square box == the video's own 1:1 aspect, so object-fit:cover fills it
     with no crop and no zoom hacks (which the old Vimeo iframe needed). */
  .hero-video { order: 1; aspect-ratio: 1 / 1; }
  .hero-scroll { display: none; }

  .studio-grid { grid-template-columns: 1fr; }
  .studio-media { order: 2; aspect-ratio: 16 / 10; }

  .manifesto-meta { flex-direction: column; gap: 1rem; }

  .pitch-grid { gap: 2.5rem; }

  .project-row { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 620px) {
  .project-row { grid-template-columns: 1fr; }
  .pcard-media { aspect-ratio: 5 / 3; }   /* was 4/3; 20% shorter, matching the desktop change */
  .pcard-title { font-size: 0.55rem; }
  .pcard-sub { font-size: 0.5rem; }
  .pcard-overlay { padding: 0.45rem 0.55rem; }
  .service { grid-template-columns: auto 1fr; }
  .service-arrow { display: none; }
  .section-head { flex-direction: column; align-items: flex-start; }
  .footer-inner { flex-direction: column; align-items: flex-start; }
}


/* =========================================================
   PROJECT OVERLAY (lightbox) — project page content, in place
   ========================================================= */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

body.is-locked { overflow: hidden; }

.pmodal { position: fixed; inset: 0; z-index: 90; display: grid; place-items: center; }
.pmodal[hidden] { display: none; }

.pmodal-backdrop {
  position: absolute; inset: 0;
  background: rgba(14, 13, 11, 0.8);
  backdrop-filter: blur(3px);
  opacity: 0; transition: opacity 0.4s var(--ease);
}
.pmodal.is-open .pmodal-backdrop { opacity: 1; }

/* Panel width is derived from the image itself: the renders are 4:3, so a
   height budget of 62vh fixes the width at 62vh * 4/3. That way the image
   fills the panel edge to edge — no side rails, no crop, no distortion —
   and the panel never depends on how long the project title happens to be. */
.pmodal-shell {
  --img-h: 72vh;
  position: relative; z-index: 1;
  width: min(calc(var(--img-h) * 4 / 3), 92vw);
  max-height: 92vh;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--stone-50);
  box-shadow: 0 40px 120px -30px rgba(0, 0, 0, 0.6);
  opacity: 0; transform: translateY(14px) scale(0.985);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}
.pmodal.is-open .pmodal-shell { opacity: 1; transform: none; }

.pmodal-close {
  position: absolute; top: 0.7rem; right: 0.7rem; z-index: 3;
  width: 40px; height: 40px; border: none; border-radius: 50%;
  background: rgba(247, 245, 241, 0.85); color: var(--ink);
  display: grid; place-items: center;
  transition: background 0.3s var(--ease), transform 0.4s var(--ease);
}
.pmodal-close:hover { background: #fff; transform: rotate(90deg); }
.pmodal-close svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.6; }

/* Arrows sit outside the panel, over the dark backdrop */
.pmodal-nav {
  position: fixed; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 52px; height: 52px; border: none; border-radius: 50%;
  background: none; color: rgba(247, 245, 241, 0.8);
  display: grid; place-items: center;
  transition: color 0.3s var(--ease), transform 0.3s var(--ease);
}
.pmodal-nav:hover { color: #fff; }
.pmodal-nav.prev { left: max(0.75rem, calc(50vw - 625px)); }
.pmodal-nav.next { right: max(0.75rem, calc(50vw - 625px)); }
.pmodal-nav.prev:hover { transform: translateY(-50%) translateX(-4px); }
.pmodal-nav.next:hover { transform: translateY(-50%) translateX(4px); }
.pmodal-nav svg { width: 30px; height: 30px; fill: none; stroke: currentColor; stroke-width: 1.4; }

.pmodal-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1.1rem clamp(1.25rem, 3vw, 2rem);
  border-top: 1px solid var(--line);
}
.pmodal-full {
  font-size: 0.72rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink);
  padding-bottom: 2px;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 100% 1px; background-position: 0 100%; background-repeat: no-repeat;
  transition: background-size 0.4s var(--ease);
}
.pmodal-full:hover { background-size: 0% 1px; }
.pmodal-pos {
  font-size: 0.72rem; letter-spacing: 0.1em; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Injected content: title band, then the cover image.
   The title stays deliberately small here — the image is the point. */
.pmodal-content { display: flex; flex-direction: column; min-height: 0; }
.pmodal .pj-head {
  flex: none;
  padding: clamp(0.9rem, 1.6vw, 1.25rem) clamp(1rem, 2.5vw, 1.5rem) clamp(0.75rem, 1.3vw, 1rem);
}
.pmodal .pj-head-inner { padding: clamp(0.45rem, 0.9vw, 0.7rem) clamp(1rem, 3vw, 2rem); }
.pmodal .pj-title {
  font-size: clamp(0.9rem, 0.75rem + 0.55vw, 1.25rem);
  letter-spacing: 0.12em;
}

/* Image spans the full panel width; height follows its own 4:3 ratio.
   Not a flex child — a flex parent would stretch and distort it. */
.pmodal-figure { flex: none; }
.pmodal-figure img { display: block; width: 100%; height: auto; }

@media (max-width: 900px) {
  .pmodal-shell { width: 94vw; max-height: 90vh; }
  .pmodal-nav { width: 44px; height: 44px; }
  .pmodal-nav.prev { left: 0.35rem; }
  .pmodal-nav.next { right: 0.35rem; }
}

@media (prefers-reduced-motion: reduce) {
  .pmodal-backdrop, .pmodal-shell { transition: none; }
  .pmodal-shell { transform: none; }
}


/* =========================================================
   PORTFOLIO LINK + ALL-PROJECTS PAGE
   ========================================================= */
.projects-more { text-align: center; margin-top: clamp(2rem, 4vw, 3rem); }
.projects-more .btn span { transition: transform 0.3s var(--ease); }
.projects-more .btn:hover span { transform: translateX(4px); }

.projects-count {
  font-size: 0.8rem; letter-spacing: 0.08em; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.more-all { text-align: center; margin-top: clamp(1.75rem, 3vw, 2.5rem); }


/* =========================================================
   DARK THEME
   Flips the tokens; every component reads them, so it follows.
   Home page toggles `.dark` on scroll; the portfolio ships with it.
   ========================================================= */
.dark {
  --stone-50:  #14130f;
  --stone-100: #1b1a17;
  --stone-200: #26241f;
  --stone-300: #33302a;
  --ink:       #f3f0ea;
  --ink-soft:  #b6b1a6;
  --muted:     #8d8779;
  --line:      #34312b;
  --accent:    #9aa691;
  --accent-ink:#aab59f;
}

/* The contact band is inverted by design (dark on a light page). Flipping the
   tokens would turn it light, so give it its own surface instead. */
.dark .contact { background: #0c0b09; }
.dark .contact-title, .dark .contact-mail { color: var(--ink); }
.dark .contact-sub { color: var(--ink-soft); }
.dark .contact-links a { color: var(--muted); }
.dark .contact-links a:hover { color: var(--ink); }

/* Chrome that sits on images / light chips keeps its own colours */
.dark .pmodal-close { color: #1b1a17; }
.dark .hero-tag { color: #f7f5f1; }

/* The soft crossfade — scoped, not universal. A "*" selector forces the
   browser to evaluate this transition on every element in the DOM; that's
   cheap on the six-card home page, but the portfolio grid's 25 cards (with
   up to 7 autoplaying videos) turned it into a visible stutter on toggle,
   since every one of those extra nodes — including the <video> elements
   themselves — competed for the same frame budget as video decode/composite.
   `color` inherits, so transitioning it once on <body> already crossfades
   plain text everywhere for free; only elements with their own background or
   border need a rule of their own. Class-level rules (buttons, links) keep
   their own snappier hover transitions, unaffected by this. */
body {
  transition: background-color 0.7s var(--ease), color 0.7s var(--ease);
}
.site-header, .site-footer, .services, .contact, .pj-meta, .pitch,
.pmodal-shell, .pmodal-backdrop, .mobile-nav {
  transition: background-color 0.7s var(--ease), color 0.7s var(--ease),
              border-color 0.7s var(--ease);
}
/* .pcard-media/.studio-media/.more-card figure deliberately excluded: their
   background is only a pre-image-load placeholder, invisible once the <img>
   covers it — but transitioning it still forces a 0.7s repaint on every card,
   which in turn forces the overlay's backdrop-filter: blur() to keep
   resampling behind it. Across 25 portfolio cards with playing video behind
   several of them, that resample was the actual stutter, worse than the
   number of transitioning elements itself. They now snap instantly — no
   visible difference, since that background is essentially never seen. */

/* =========================================================
   PORTFOLIO — rows of three/four, plus blocks where a portrait
   runs the full height of two rows.

   Two systems, one look:
   .pf-row   single row. Columns are the tiles' own aspect ratios
             (1.6fr / 0.8fr), so every tile resolves to the same height.
   .pf-block two rows on equal columns. The landscapes' 16:10 sets the row
             height; a .pf-tall portrait spans both rows and simply fills.
             The maths works out: a 4:5 portrait over two rows is almost
             exactly as wide as a 16:10 landscape over one — so equal
             columns hold both without distortion.
   ========================================================= */
.pf-rows {
  display: flex; flex-direction: column;
  gap: 6px;
  padding-inline: clamp(1rem, 3vw, 3.5rem);
}

.pf-row { display: grid; gap: 6px; }        /* columns come from the inline style */
.pf-block {
  display: grid; gap: 6px;
  grid-template-columns: repeat(3, 1fr);
}

.pf-l { aspect-ratio: 16 / 10; }            /* landscape — never flatter than 16:10 */
.pf-p { aspect-ratio: 4 / 5; }              /* portrait, one row high */
.pf-tall { grid-row: span 2; }              /* portrait, two rows high — fills the span */

/* Cards fill their tile instead of forcing the card's own ratio */
.pf-row .pcard-media,
.pf-block .pcard-media { aspect-ratio: auto; height: 100%; }
.pf-tall, .pf-tall .pcard-media { height: 100%; }

/* The narrow tiles keep their caption from crowding */
.pf-p .pcard-title { font-size: 0.855rem; }
.pf-p .pcard-overlay { padding: 0.585rem 0.72rem; }

@media (max-width: 820px) {
  .pf-row, .pf-block { grid-template-columns: 1fr !important; }
  .pf-tall { grid-row: auto; aspect-ratio: 4 / 5; }
  /* Match the home page's mobile card-title size exactly. */
  .pcard-title, .pf-p .pcard-title { font-size: 0.55rem; }
  .pcard-sub { font-size: 0.5rem; }
  .pcard-overlay, .pf-p .pcard-overlay { padding: 0.45rem 0.55rem; }
}

/* =========================================================
   "WRITTEN" TITLE ANIMATION
   ========================================================= */
.write-char {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.45em) rotate(2deg);
  animation: writeIn 0.62s var(--ease) forwards;
  animation-delay: calc(var(--i) * 45ms);
}
.write-space { display: inline-block; width: 0.3em; }
@keyframes writeIn {
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .write-char { opacity: 1; transform: none; animation: none; }
  *, *::before, *::after { transition: none; }
}


/* =========================================================
   CAPTION PIN — must come last
   Captions sit in a dark panel over the render, never on the page
   background, so they stay white in both themes. Declared at the end of
   the file so they win over the theme-driven rules above.
   ========================================================= */
.pcard-title { color: #fff; }
.pcard-sub   { color: rgba(255, 255, 255, 0.78); }



/* =========================================================
   PORTFOLIO HERO — big title + intro paragraph, light page
   ========================================================= */
.pf-hero { padding-block: clamp(1.25rem, 2.5vw, 2rem) clamp(1.5rem, 3vw, 2.25rem); }
.pf-hero-title {
  font-family: var(--serif); font-weight: 600;
  font-size: clamp(2.6rem, 1.6rem + 5vw, 5.2rem);
  line-height: 0.98; letter-spacing: -0.01em;
  text-transform: uppercase;
  margin-bottom: clamp(1.25rem, 2.5vw, 1.75rem);
}
.pf-hero-lead {
  font-size: clamp(1rem, 0.92rem + 0.4vw, 1.2rem);
  line-height: 1.6; color: var(--ink-soft);
  max-width: 62ch;
}
