/* ================================================================
   VICTOR IRTWANGE — "The Showroom & The Spec Sheet"
   Warm gallery paper · editorial serif · technical mono annotations
   ================================================================ */

/* ---------- Tokens ---------- */
:root {
  --paper: #f2efe7;
  --paper-2: #eae6da;
  --ink: #171511;
  --ink-2: #3a372f;
  --muted: #6f6a5d;
  --line: rgba(23, 21, 17, 0.16);
  --line-soft: rgba(23, 21, 17, 0.08);
  --accent: #cf9a35;
  --accent-deep: #a9761d;
  /* For big display italics on paper — brighter than deep, still grounded.
     #bd8a26 measured 2.68:1, under the 3:1 large-text floor; this clears it
     at 3.37:1. Darker, not brighter, so the "don't brighten to match" rule
     in CLAUDE.md still holds. */
  --accent-display: #a67a1f;
  /* Ochre for SMALL text on paper. Separate from --accent-deep for two
     reasons: deep is 3.45:1, under the 4.5:1 body-text floor, and project
     pages retint --accent-deep per project (js/project.js), which dragged
     the contact form's status message to 2.19:1 on the Source page. Chrome
     -- borders, fills, diamonds -- only needs 3:1 and can keep tinting. */
  --accent-text: #8a5f13;
  --card: #fbfaf6;

  --font-display: "Gloock", "Georgia", serif;
  --font-body: "Archivo", "Helvetica Neue", sans-serif;
  --font-mono: "IBM Plex Mono", "Consolas", monospace;

  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);

  --pad: clamp(1.25rem, 4vw, 3.5rem);
  --header-h: 72px;
  --radius: 2px;
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
html, body { height: 100%; }
body {
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.55;
  color: var(--ink);
  background-color: var(--paper);
  background-image: var(--grain);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}
img, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
::selection { background: var(--accent); color: var(--paper); }

/* Paper grain — baked into scrolling backgrounds, never a fixed overlay.
   (A fixed full-viewport overlay — animated, static, even layer-promoted —
   broke Chrome's scroll rasterization: gallery tiles rendered blank until
   hover. Backgrounds paint with the content, so they're free.) */

/* ---------- Type helpers ---------- */
.display {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 0.98;
  letter-spacing: -0.01em;
}
.mono {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.mono--ink { color: var(--ink); }
.mono--accent { color: var(--accent-text); }

/* Spec label: "▸ FIG. 01 — NAME" with rule line */
.spec {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.spec::before {
  content: "";
  width: 9px; height: 9px;
  border: 1px solid var(--accent-deep);
  transform: rotate(45deg);
  flex: none;
}
.spec::after {
  content: "";
  height: 1px;
  flex: 1;
  background: var(--line);
  min-width: 3rem;
}

/* Crosshair registration mark: a "+" centred on each corner of an embed
   frame (Victor's preferred look). -8px = half the 15px mark (7.5) plus the
   frame's 1px border (0.5), so the cross centre lands on the border line. */
.crosshair {
  --cx: -8px;
  position: absolute;
  width: 15px; height: 15px;
  pointer-events: none;
  opacity: 0.55;
}
.crosshair::before, .crosshair::after {
  content: "";
  position: absolute;
  background: var(--ink);
}
/* Translate so the 1px hairline centres on 50% instead of starting there —
   without it the four marks land a pixel apart from each other. */
.crosshair::before { left: 50%; top: 0; width: 1px; height: 100%; transform: translateX(-50%); }
.crosshair::after { top: 50%; left: 0; height: 1px; width: 100%; transform: translateY(-50%); }
.crosshair--tl { top: var(--cx); left: var(--cx); }
.crosshair--tr { top: var(--cx); right: var(--cx); }
.crosshair--bl { bottom: var(--cx); left: var(--cx); }
.crosshair--br { bottom: var(--cx); right: var(--cx); }

/* ---------- Preloader: "sorting the prints" ---------- */
.preloader {
  position: fixed; inset: 0;
  z-index: 900;
  background: var(--paper);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2.2rem;
  transition: opacity 0.7s var(--ease), visibility 0.7s;
}
body.loaded .preloader { opacity: 0; visibility: hidden; pointer-events: none; }

.preloader__stack {
  position: relative;
  width: min(46vw, 420px);
  aspect-ratio: 16 / 10;
}
.preloader__card {
  position: absolute; inset: 0;
  background-size: cover;
  background-position: center;
  background-color: var(--paper-2);
  border: 6px solid #fff;
  box-shadow: 0 18px 50px -18px rgba(23, 21, 17, 0.45);
  opacity: 0;
  transform: scale(0.94) rotate(0deg);
}
.preloader__card.is-in {
  animation: card-in 0.55s var(--ease) forwards;
}
@keyframes card-in {
  from { opacity: 0; transform: scale(1.06) rotate(var(--tilt, 0deg)) translateY(-14px); }
  to   { opacity: 1; transform: scale(1) rotate(var(--tilt, 0deg)) translateY(0); }
}

.preloader__meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1rem;
  width: min(46vw, 420px);
}
.preloader__row {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.preloader__name {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 2.4vw, 1.5rem);
  color: var(--ink);
}
.preloader__count { font-variant-numeric: tabular-nums; }
.preloader__bar {
  width: 100%;
  height: 2px;
  background: var(--line-soft);
  overflow: hidden;
}
.preloader__bar i {
  display: block;
  height: 100%;
  width: 0%;
  background: var(--ink);
  transition: width 0.35s var(--ease);
}

/* ---------- Header ---------- */
.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 500;
  height: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--pad);
  transition: background 0.4s var(--ease), box-shadow 0.4s var(--ease), transform 0.45s var(--ease);
}
.site-header.is-scrolled {
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(14px);
  box-shadow: 0 1px 0 var(--line-soft);
}
.site-header.is-hidden { transform: translateY(-100%); }

/* Light-on-dark mode while sitting on a (dimmed) hero image */
.site-header.on-hero { color: #fff; }
.site-header.on-hero .brand__role { color: rgba(255, 255, 255, 0.75); }
.site-header.on-hero .site-nav a { color: rgba(255, 255, 255, 0.85); }
.site-header.on-hero .site-nav a:hover,
.site-header.on-hero .site-nav a[aria-current="page"] { color: #fff; }
.site-header.on-hero .site-nav a::after { background: #fff; }
.site-header.on-hero .nav-toggle { color: #fff; }
body.nav-open .site-header.on-hero { color: var(--ink); }
body.nav-open .site-header.on-hero .nav-toggle { color: var(--ink); }
body.nav-open .site-header.on-hero .site-nav a { color: var(--ink-2); }

.brand {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  z-index: 2;
}
.brand__name {
  font-family: var(--font-display);
  font-size: 1.25rem;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
/* Held back to 1150px (1060 before the 2026-08 label-tier size pass; the
   bigger nav ate the margin and the gap measured 2px at 1060): below that
   the role plus six nav items are
   wider than the bar, and both the name and the role were wrapping to two
   lines. Measured with the Interactive chip in place, they only stop crowding
   each other just under 1000px, so 1060 leaves a real gap rather than a
   hairline. The name alone clears 700px (the mobile breakpoint) easily. */
.brand__role { display: none; white-space: nowrap; }
@media (min-width: 1150px) {
  .brand__role { display: inline; }
}

.site-nav { display: flex; align-items: center; gap: clamp(0.9rem, 2.1vw, 2rem); }
.site-nav a {
  font-family: var(--font-mono);
  /* Bumped from 0.72rem: at mono's tracking the labels read as fine print
     next to the brand. The gap gives back the width this costs. */
  font-size: 0.88rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-2);
  position: relative;
  padding: 0.4rem 0;
}
.site-nav a::after {
  content: "";
  position: absolute;
  left: 0; right: 100%;
  bottom: 0;
  height: 1px;
  background: var(--accent-deep);
  transition: right 0.35s var(--ease);
}
.site-nav a:hover::after, .site-nav a[aria-current="page"]::after { right: 0; }
.site-nav a[aria-current="page"] { color: var(--ink); }

/* "Interactive" is the only nav item that leads to something running rather
   than something to read, so it is the one item that does not look like the
   others: an outlined chip -- the site's own .section__link shape -- led by a
   solid spec-sheet diamond breathing on the same 2.2s beat as the lab badges.
   Hover swaps the shared underline for the button device, gold wiping in from
   the left, which is why the ::after rule is suppressed. */
.site-nav a[data-nav="lab"] {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--accent-text);
  font-weight: 500;
  border: 1px solid var(--accent-deep);
  padding: 0.45rem 0.8rem;
  margin: 0 -0.15rem;
  overflow: hidden;
  background-repeat: no-repeat;
  background-size: 220% 100%;
  background-position: 100% 0;
  background-image: linear-gradient(90deg, var(--accent-deep) 0 50%, transparent 50% 100%);
  transition: background-position 0.55s var(--ease), color 0.35s var(--ease),
              border-color 0.35s var(--ease);
}
.site-nav a[data-nav="lab"]::after { display: none; }
.site-nav a[data-nav="lab"]::before {
  content: "";
  width: 8px; height: 8px;
  flex: none;
  background: var(--accent-deep);
  transform: rotate(45deg);
  animation: nav-live 2.2s ease-in-out infinite;
  transition: background 0.35s var(--ease), transform 0.5s var(--ease);
}
@keyframes nav-live {
  0%, 100% { opacity: 1; transform: rotate(45deg) scale(1); }
  50% { opacity: 0.32; transform: rotate(45deg) scale(0.78); }
}
/* Filled while you are inside the Interactive section (configurator, configure,
   showroom -- they carry data-active="lab"). The diamond keeps breathing: the
   chip is marking "you are here", not "this stopped being live". */
.site-nav a[data-nav="lab"]:hover,
.site-nav a[data-nav="lab"][aria-current] { background-position: 0 0; color: var(--paper); }
.site-nav a[data-nav="lab"][aria-current]::before { background: var(--paper); }
.site-nav a[data-nav="lab"]:hover::before { animation: none; background: var(--paper); transform: rotate(135deg); }
/* Over a hero the label stays white like its neighbours: the slides are not
   all dark, and gold type on a light render is the one place this chip could
   go unreadable. The gold does the marking -- border, diamond, hover fill. */
.site-header.on-hero .site-nav a[data-nav="lab"] {
  color: #fff;
  border-color: var(--accent);
  background-image: linear-gradient(90deg, var(--accent) 0 50%, transparent 50% 100%);
}
.site-header.on-hero .site-nav a[data-nav="lab"]::before { background: var(--accent); }
.site-header.on-hero .site-nav a[data-nav="lab"]:hover,
.site-header.on-hero .site-nav a[data-nav="lab"][aria-current] { color: var(--ink); }
.site-header.on-hero .site-nav a[data-nav="lab"]:hover::before,
.site-header.on-hero .site-nav a[data-nav="lab"][aria-current]::before { background: var(--ink); }
body.nav-open .site-header.on-hero .site-nav a[data-nav="lab"] {
  color: var(--accent-text);
  border-color: var(--accent-deep);
  background-image: linear-gradient(90deg, var(--accent-deep) 0 50%, transparent 50% 100%);
}
body.nav-open .site-header.on-hero .site-nav a[data-nav="lab"]::before { background: var(--accent-deep); }

.nav-toggle { display: none; }
@media (max-width: 700px) {
  .site-nav {
    position: fixed; inset: 0;
    background: var(--paper);
    flex-direction: column;
    justify-content: center;
    gap: 2rem;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.4s var(--ease), visibility 0.4s;
    z-index: 1;
  }
  body.nav-open .site-nav { opacity: 1; visibility: visible; }
  /* The menu is position:fixed but it lives INSIDE .site-header, and a
     backdrop-filter or a transform on an ancestor makes "fixed" resolve
     against that ancestor instead of the viewport. The header carries
     backdrop-filter past 40px of scroll and a transform while hiding, so the
     full-screen menu was collapsing into the 72px header strip with the page
     showing through -- and a reload "fixed" it only because that returns you
     to the top. Measured: 812px tall plain, 72px with either property.
     An IDENTITY transform counts too, so the hide/show transition triggers it
     as well; both are dropped for as long as the menu is open. */
  body.nav-open .site-header {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    transform: none;
    /* transform is dropped from the transition so it SNAPS to none. Left to
       animate, every frame of the 0.45s tween computes to a matrix, and a
       matrix -- even the identity -- still creates the containing block, so
       the menu stayed broken for the whole hide/show animation. Background
       and shadow keep their transitions. */
    transition: background 0.4s var(--ease), box-shadow 0.4s var(--ease);
  }
  .site-nav a { font-size: 1rem; }
  /* Roomier chip for the full-screen menu's 1rem type. The marker stays in
     flow here (it did not, while this was a bare label): the chip is its own
     centred box, so the label sitting a few pixels off its middle reads as
     part of the shape rather than as a broken axis. */
  .site-nav a[data-nav="lab"] { padding: 0.6rem 1.1rem; }
  .nav-toggle {
    display: block;
    z-index: 2;
    font-family: var(--font-mono);
    font-size: 0.85rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
  }
}

/* ---------- Hero ---------- */
.hero-scroller { position: relative; }
.hero {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: clip;
  background: var(--paper-2);
}
.hero__slides { position: absolute; inset: 0; }
.hero__slide {
  position: absolute; inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transform: scale(1.04);
  transition: opacity 1.1s var(--ease-io), transform 6s linear;
}
.hero__slide.active { opacity: 1; transform: scale(1); }

.hero__scrim {
  position: absolute; inset: 0;
  /* top band (nav legibility) + overall dim (many renders are near-white) + bottom band (title) */
  background:
    linear-gradient(to bottom, rgba(23,21,17,0.42) 0%, rgba(23,21,17,0) 20%),
    linear-gradient(to top, rgba(23,21,17,0.58) 0%, rgba(23,21,17,0.1) 34%, rgba(23,21,17,0) 55%),
    rgba(23,21,17,0.16);
}

.hero__content {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 0 var(--pad) clamp(1.2rem, 3.5vh, 2.6rem);
  color: #fff;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.hero__kicker { color: rgba(255,255,255,0.85); }
.hero__title {
  font-size: clamp(2.6rem, 9.5vw, 8.5rem);
  color: #fff;
  text-shadow: 0 2px 30px rgba(23,21,17,0.25);
  position: relative;
}
/* Highlighter stroke — the marked-up word of the spec sheet.
   Rationed to the hero + footer so the device stays special.
   No vertical padding: the title leading is tight, and any bleed
   makes the block collide with the line above. */
.hero__title em {
  font-style: italic;
  color: var(--paper);
  /* Drawn as a positioned gradient (not a full background) so the stroke
     starts below the line above — like a marker that missed the ascenders */
  background-image: linear-gradient(var(--accent-deep), var(--accent-deep));
  background-repeat: no-repeat;
  background-size: 100% 0.78em;
  background-position: 0 0.3em;
  padding: 0 0.16em;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/* Typewriter: invisible sizer reserves the final layout, typed layer fills in */
.hero__title-sizer { visibility: hidden; }
.hero__title-typed { position: absolute; inset: 0; }
.type-caret {
  display: inline-block;
  width: 0.045em;
  height: 0.82em;
  margin-left: 0.06em;
  background: currentColor;
  vertical-align: baseline;
  transform: translateY(0.08em);
  animation: caret-blink 1.05s steps(1) infinite;
}
@keyframes caret-blink { 50% { opacity: 0; } }
.hero__bottom {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 1rem;
  border-top: 1px solid rgba(255,255,255,0.35);
  padding-top: 0.85rem;
  margin-top: 0.6rem;
}
.hero__meta { color: rgba(255,255,255,0.9); font-variant-numeric: tabular-nums; }
.hero__scrollcue {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: rgba(255,255,255,0.9);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.hero__scrollcue i {
  display: block;
  width: 1px; height: 34px;
  background: rgba(255,255,255,0.7);
  overflow: hidden;
  position: relative;
}
.hero__scrollcue i::after {
  content: "";
  position: absolute;
  left: 0; top: -100%;
  width: 100%; height: 100%;
  background: #fff;
  animation: cue 1.8s var(--ease-io) infinite;
}
@keyframes cue { to { top: 100%; } }

/* Hero entrance after preloader */
.hero__content > * { opacity: 0; transform: translateY(26px); }
body.loaded .hero__content > * {
  animation: rise 0.9s var(--ease) forwards;
}
body.loaded .hero__content > *:nth-child(2) { animation-delay: 0.12s; }
body.loaded .hero__content > *:nth-child(3) { animation-delay: 0.24s; }
@keyframes rise { to { opacity: 1; transform: none; } }

/* ---------- About reveal (hero shrinks into spread stack) ---------- */
.about-reveal { position: relative; z-index: 2; }
.reveal-cards {
  position: fixed;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  display: none;
}
.reveal-cards.on { display: block; }
.reveal-card {
  position: absolute;
  top: 50%; left: 50%;
  width: 100vw; height: 100vh;
  transform: translate(-50%, -50%);
  background-size: cover;
  background-position: center;
  box-shadow: 0 30px 80px -30px rgba(23,21,17,0.5);
  will-change: transform, opacity, width, height;
}

.about::before {
  /* Soft paper gradient riding just above the sliding edge: prints close
     to the About card fade out bottom-up; anything further up stays clear. */
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: calc(-22vh + 1px);
  height: 22vh;
  background: linear-gradient(to top, var(--paper) 0%, rgba(242, 239, 231, 0) 100%);
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  .about::before { display: none; }
}

.about {
  position: relative;
  z-index: 4; /* slides over the sticky hero during the overlap */
  background-color: var(--paper);
  background-image: var(--grain);
  margin-top: -100vh; /* full-viewport slide-over; keep in sync with OVERLAP_VH in js/main.js */
  padding: clamp(5rem, 12vh, 9rem) var(--pad) clamp(4rem, 10vh, 7rem);
}
@media (prefers-reduced-motion: reduce) {
  .about { margin-top: 0; }
}
.about__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 3rem;
  max-width: 1300px;
  margin: 0 auto;
}
@media (min-width: 900px) {
  .about__grid { grid-template-columns: 5fr 4fr; align-items: start; }
}
.about__headline {
  font-size: clamp(2rem, 4.6vw, 3.8rem);
  margin: 1.2rem 0 1.6rem;
}
.about__headline em { font-style: italic; color: var(--accent-display); }
/* Reading text runs on its own scale, peaking at 26px on desktop. 16px body
   copy read as fine print against the display sizes around it, and visitors
   said so. Chrome, spec labels and mono metadata deliberately stay small:
   the contrast between big reading text and small labels IS the system. */
.about__bio p {
  color: var(--ink-2);
  max-width: 46ch;
  font-size: clamp(1.2rem, 1.02rem + 0.75vw, 1.625rem);
  line-height: 1.55;
}
.about__bio p + p { margin-top: 1em; }

.about__photo-wrap {
  position: relative;
  /* Sized against the 26px reading tier: at 420px the print read small and
     unbalanced beside the bigger bio column. 560 lets it fill the 4fr column,
     which tops out around 556px at the grid's 1300px max. */
  max-width: 560px;
  justify-self: center;
}
@media (min-width: 900px) {
  .about__photo-wrap {
    justify-self: end;
    /* Start the print with the headline rather than the Fig. label above it,
       which also lands its bottom edge close to where the bio ends. */
    margin-top: clamp(2rem, 4vw, 3.5rem);
  }
}
.about__photo {
  filter: grayscale(1) contrast(1.04);
  border: 8px solid #fff;
  box-shadow: 0 24px 60px -24px rgba(23,21,17,0.45);
  transform: rotate(1.6deg);
}
.about__photo-caption {
  position: absolute;
  left: 50%;
  bottom: -1.6rem;
  transform: translateX(-50%) rotate(-1deg);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.35rem 0.8rem;
  white-space: nowrap;
}

.about__stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  border-top: 1px solid var(--line);
  border-left: 1px solid var(--line);
  margin-top: 3.4rem;
}
@media (min-width: 760px) { .about__stats { grid-template-columns: repeat(4, 1fr); } }
.stat {
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 1.4rem 1.2rem 1.2rem;
}
.stat b {
  display: block;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.8rem, 3.4vw, 2.8rem);
  line-height: 1;
  margin-bottom: 0.45rem;
}
.stat span { font-size: 0.95rem; color: var(--muted); }

.about__cols {
  display: grid;
  gap: 3rem;
  margin-top: 3.4rem;
  max-width: 1300px;
  margin-left: auto;
  margin-right: auto;
}
@media (min-width: 900px) { .about__cols { grid-template-columns: 1fr 1fr; } }

/* Spec-sheet fold: the capability/track-record tables collapse to zero
   height (0fr grid row) and unfold on the toggle — no JS measuring */
/* A full-width "fold line" — reads as the crease of the spec sheet,
   spans the whole column so it can't be scrolled past unnoticed */
.about__toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.8rem;
  width: 100%;
  max-width: 1300px;
  margin: 2.8rem auto 0;
  font-family: var(--font-mono);
  font-size: 0.88rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 1.1rem 1.6rem;
  border: 1px dashed var(--accent-deep);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--ink);
  cursor: pointer;
  transition: background 0.3s var(--ease), color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.about__toggle::before {
  content: "";
  width: 9px;
  height: 9px;
  border: 1.5px solid var(--accent-deep);
  transform: rotate(45deg);
  transition: border-color 0.3s var(--ease);
}
.about__toggle:hover { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.about__toggle:hover::before { border-color: var(--accent); }
.about__toggle-arr { transition: transform 0.4s var(--ease); }
.about__toggle[aria-expanded="true"] .about__toggle-arr { transform: rotate(180deg); }
.about__more {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.7s var(--ease);
}
.about__more.open { grid-template-rows: 1fr; }
/* visibility, not just zero height: collapsed to 0fr the panel still exposed
   ~1500 characters to a screen reader, which made the fold-bar meaningless to
   anyone not looking at it. Delayed so the content stays painted while the
   row animates open and closed. */
.about__more-inner {
  overflow: hidden;
  min-height: 0;
  visibility: hidden;
  transition: visibility 0.7s;
}
.about__more.open .about__more-inner { visibility: visible; }

.skills { display: grid; gap: 1.6rem; margin-top: 1.4rem; }
.skills__group b {
  display: block;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-text);
  margin-bottom: 0.5rem;
}
.skills__group ul { list-style: none; display: flex; flex-wrap: wrap; gap: 0.45rem; }
.skills__group li {
  font-size: 0.98rem;
  border: 1px solid var(--line);
  padding: 0.28rem 0.7rem;
  border-radius: 99px;
  background: var(--card);
}

.xp { margin-top: 1.4rem; border-top: 1px solid var(--line); }
.xp__row {
  display: grid;
  grid-template-columns: 9.5rem 1fr;
  gap: 1rem;
  padding: 1rem 0;
  border-bottom: 1px solid var(--line);
}
.xp__row time {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  color: var(--muted);
  padding-top: 0.25rem;
}
.xp__row b { display: block; font-weight: 600; font-size: 1.05rem; }
.xp__row i { font-style: normal; color: var(--accent-text); font-size: 0.95rem; }
.xp__row p { color: var(--muted); font-size: 0.95rem; margin-top: 0.25rem; }

/* ---------- Generic sections ---------- */
.section { position: relative; padding: clamp(4rem, 10vh, 8rem) var(--pad); }
.section__head {
  display: flex;
  flex-wrap: wrap;
  /* Bottom-aligned, not baseline: the left column's first baseline is the
     little Fig. kicker, which parked the link up beside it instead of down
     on the heading's line. */
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: clamp(1.8rem, 4vh, 3rem);
  max-width: 1300px;
  margin-left: auto;
  margin-right: auto;
}
.section__title { font-size: clamp(1.9rem, 4.2vw, 3.4rem); }
.section__title em { font-style: italic; color: var(--accent-display); }
.section__link {
  font-family: var(--font-mono);
  font-size: 0.88rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  border: 1px solid var(--ink);
  padding: 0.75em 1.2em;
  /* Same left-to-right colour wipe as .btn */
  overflow: hidden;
  background-repeat: no-repeat;
  background-size: 220% 100%;
  background-position: 100% 0;
  background-image: linear-gradient(90deg, var(--ink) 0 50%, transparent 50% 100%);
  transition: background-position 0.55s var(--ease), color 0.35s var(--ease), border-color 0.35s;
}
.section__link .arr {
  display: inline-block;
  transition: transform 0.35s var(--ease);
}
.section__link:hover { background-position: 0 0; color: var(--paper); }
.section__link:hover .arr { transform: translate(0.18em, -0.18em); }
/* In a section head the link sits on the heading's line, so it is scaled to
   stand at the heading's height rather than the kicker's. Scoped: the
   end-of-section .section__link--lg keeps its own size. */
.section__head .section__link {
  font-size: clamp(0.85rem, 1.15vw, 1.15rem);
  padding: 0.9em 1.6em;
}
/* End-of-section variant: bigger, centered under the grid */
.section__foot {
  max-width: 1300px;
  margin: clamp(2.2rem, 5vh, 3.2rem) auto 0;
  display: flex;
  justify-content: center;
}
.section__link--lg { font-size: 1rem; padding: 1.05em 2em; }

/* ---------- Project marquee ---------- */
.marquee-section { padding-left: 0; padding-right: 0; overflow: clip; }
.marquee-section .section__head, .marquee-section .spec { padding: 0 var(--pad); }
.marquee {
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  touch-action: pan-y; /* horizontal swipes drive the carousel on touch */
  /* The strip dissolves at its edges instead of guillotining mid-card. A mask
     rather than gradient overlays, so it works over any background and keeps
     working while the track drags underneath it. */
  -webkit-mask-image: linear-gradient(to right,
      transparent 0, #000 clamp(2rem, 7vw, 7rem),
      #000 calc(100% - clamp(2rem, 7vw, 7rem)), transparent 100%);
  mask-image: linear-gradient(to right,
      transparent 0, #000 clamp(2rem, 7vw, 7rem),
      #000 calc(100% - clamp(2rem, 7vw, 7rem)), transparent 100%);
}
.marquee:active { cursor: grabbing; }
.marquee__track {
  display: flex;
  gap: clamp(1rem, 2vw, 1.8rem);
  width: max-content;
  will-change: transform;
  padding: 0.5rem 0 1rem;
}
.pcard {
  position: relative;
  width: clamp(260px, 34vw, 460px);
  flex: none;
  display: block;
  transition: transform 0.5s var(--ease);
}
/* The whole card swells on hover (the image slightly faster inside it), the
   same come-here the Harry site's carousel has. z-index lets the grown card
   sit over its neighbours instead of sliding under the next one. */
.pcard:hover { transform: scale(1.03); z-index: 2; }
.pcard__img-wrap {
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--paper-2);
}
.pcard__img-wrap img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.65s var(--ease);
}
.pcard:hover .pcard__img-wrap img { transform: scale(1.08); }
.pcard__meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--line);
  margin-top: 0.7rem;
}
.pcard__name { font-family: var(--font-display); font-size: clamp(1.35rem, 1.1rem + 0.9vw, 1.8rem); }
.pcard__idx { font-variant-numeric: tabular-nums; }
.pcard__kicker { font-size: 0.92rem; color: var(--muted); margin-top: 0.15rem; }

/* ---------- Masonry (landing gallery + gallery page) ---------- */
.masonry { position: relative; max-width: 1500px; margin: 0 auto; }
.masonry__item {
  position: absolute;
  top: 0; left: 0;
  /* NOTE: no will-change here — promoting 50+ tiles to GPU layers
     exhausts raster memory and Chrome paints them blank. */
}
.masonry__item figure { position: relative; overflow: hidden; background: var(--paper-2); }
.masonry__item img {
  width: 100%; height: auto;
  transition: transform 0.7s var(--ease);
}
.masonry__item:hover img { transform: scale(1.035); }
.masonry__item figcaption {
  position: absolute;
  left: 0.6rem; bottom: 0.6rem;
  background: color-mix(in srgb, var(--ink) 82%, transparent);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.25rem 0.55rem;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.3s var(--ease), transform 0.3s var(--ease);
  pointer-events: none;
}
.masonry__item:hover figcaption { opacity: 1; transform: none; }
.masonry__item { cursor: zoom-in; }

/* ---------- Interactive teaser strip (configurator/walkthrough placeholder) ---------- */
.lab {
  background-color: var(--ink);
  background-image: var(--grain);
  color: var(--paper);
}
/* Deeper than a stock .section: the extra air is half of what makes the band
   read as an event rather than another row of content. */
.lab { padding-top: clamp(5.5rem, 14vh, 10rem); padding-bottom: clamp(5.5rem, 14vh, 10rem); }
/* A gold rule announcing the band, with a glint travelling across it on a
   slow loop -- the same "this one is running" signal as the pulsing badges,
   at the scale of the whole section. Reduced motion parks it. */
.lab::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background:
    linear-gradient(90deg, rgba(242,239,231,0.85) 0%, rgba(242,239,231,0) 14%) 0 0 / 260% 100% no-repeat,
    linear-gradient(90deg, var(--accent) 0%, rgba(207,154,53,0.45) 46%, rgba(207,154,53,0.12) 100%);
  animation: lab-glint 9s linear infinite;
}
@keyframes lab-glint {
  0% { background-position: -60% 0, 0 0; }
  100% { background-position: 160% 0, 0 0; }
}
.lab .spec { color: rgba(242,239,231,0.6); }
/* Bigger than a stock .section__title -- this is the one section asking to be
   clicked rather than read. --accent-display is tuned for paper, so on the ink
   band the italic takes the bright accent (same rule as .lab .spec::before). */
.lab .section__title { font-size: clamp(2.2rem, 5.4vw, 4.4rem); }
.lab .section__title em { color: var(--accent); }
.lab .spec::after { background: rgba(242,239,231,0.25); }
/* On dark bands the diamond takes the bright accent — deep ochre goes muddy on ink */
.lab .spec::before,
.footer .spec::before,
.project-hero .spec::before { border-color: var(--accent); }
/* One card per row, deliberately: real visitors scroll-flicked past the
   two-up band without noticing it. Full rows give the section twice the
   scroll timeline and each product a hero-sized still. */
.lab__grid {
  display: grid;
  gap: 2.5rem;
  max-width: 1300px;
  margin: 0 auto;
}
.lab__card {
  border: 1px solid rgba(242,239,231,0.2);
  padding: 2rem;
  position: relative;
  overflow: hidden;
}
.lab__card h3 { font-family: var(--font-display); font-weight: 400; font-size: clamp(1.7rem, 1.3rem + 1.2vw, 2.3rem); margin: 0.8rem 0 0.6rem; }
.lab__card p { color: rgba(242,239,231,0.88); font-size: clamp(1.05rem, 0.92rem + 0.5vw, 1.35rem); line-height: 1.55; max-width: 46ch; }
/* Render peeking through an ink scrim — the cards read as portals, not copy */
.lab__card-bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0.5;
  transform: scale(1.01);
  transition: opacity 0.5s var(--ease), transform 0.7s var(--ease);
}
.lab__card-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(23,21,17,0.92) 0%, rgba(23,21,17,0.72) 55%, rgba(23,21,17,0.16) 100%);
}
.lab__card > :not(.lab__card-bg) { position: relative; z-index: 1; }
.lab__badge {
  display: inline-flex;
  align-items: center;
  margin-top: auto;
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--accent);
  border: 1px solid var(--accent);
  padding: 0.5rem 1rem;
  transition: background 0.3s var(--ease), color 0.3s var(--ease), border-color 0.3s var(--ease);
}
.lab__badge::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  margin-right: 0.55rem;
  animation: lab-pulse 2.2s ease-in-out infinite;
}
@keyframes lab-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}
.lab__card--live {
  display: flex;
  flex-direction: column;
  min-height: 360px;
  transition: border-color 0.3s var(--ease), transform 0.4s var(--ease);
}
.lab__card--live p { margin-bottom: 1.4rem; }
/* Registration brackets: the card reads as a plate being lined up on a light
   table. Same crop-mark language as .crosshair on the embed frames, but as
   pseudo-elements so the two cards need no extra markup. They sit quiet at
   rest and push out to the card's edge on hover, so the portal "opens". */
.lab__card--live::before,
.lab__card--live::after {
  content: "";
  position: absolute;
  width: 30px; height: 30px;
  z-index: 2;
  pointer-events: none;
  border: 2px solid var(--accent);
  opacity: 0.8;
  transition: opacity 0.4s var(--ease), transform 0.5s var(--ease);
}
/* Inset, and they stay inset: hover settles them 6px inward to 12px rather
   than out onto the card's edge, which read as the frame coming apart. They
   never crowd the "01 ." spec label despite starting inside the 2rem padding
   -- the arms run at 12px while the label starts at 33px, and an L leaves its
   own corner empty. */
.lab__card--live::before {
  top: 12px; left: 12px;
  border-right: 0; border-bottom: 0;
  transform: translate(6px, 6px);
}
.lab__card--live::after {
  bottom: 12px; right: 12px;
  border-left: 0; border-top: 0;
  transform: translate(-6px, -6px);
}
.lab__card--live:hover::before,
.lab__card--live:hover::after { opacity: 1; transform: none; }
.lab__card--live:hover { border-color: var(--accent); transform: translateY(-4px); }
.lab__card--live:hover .lab__badge { background: var(--paper); border-color: var(--paper); color: var(--ink); }
.lab__card--live:hover .lab__card-bg { opacity: 0.68; transform: scale(1.045); }
/* Full-row treatment: the card is now wide enough that text and render can
   share it side by side, so the scrim turns horizontal — ink protecting the
   copy on the left, the render nearly clear on the right — and the image
   runs brighter than the stacked-card 0.5. Below 900px the cards are narrow
   again and the vertical scrim above still applies. */
@media (min-width: 900px) {
  .lab__card--live { min-height: clamp(400px, 52vh, 560px); padding: 2.5rem; }
  /* The image layer runs near-full strength: the ink protecting the copy is
     all in the scrim's left half, so the render's right side stays bright —
     there is no text over there to protect. */
  .lab__card-bg { opacity: 0.88; }
  .lab__card-bg::after {
    background: linear-gradient(to right,
      rgba(23,21,17,0.95) 0%, rgba(23,21,17,0.86) 34%,
      rgba(23,21,17,0.05) 68%, rgba(23,21,17,0) 100%);
  }
  /* Base opacity now exceeds the stacked-layout hover value, which would
     otherwise DARKEN the card on hover. */
  .lab__card--live:hover .lab__card-bg { opacity: 1; }
}

/* ---------- Showroom (360° walkthrough) ---------- */
.showroom-frame {
  position: relative;
  max-width: 1300px;
  margin: 0 auto;
  border: 1px solid var(--ink);
  background: var(--ink);
}
.showroom-frame iframe {
  display: block;
  width: 100%;
  height: clamp(420px, 78vh, 860px);
  border: 0;
}
.showroom-bar {
  max-width: 1300px;
  margin: 1rem auto 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.showroom-notes {
  max-width: 1300px;
  margin: 0 auto;
  display: grid;
  gap: 2.5rem;
  border-top: 1px solid var(--line);
  padding-top: clamp(1.8rem, 4vh, 2.6rem);
}
@media (min-width: 900px) { .showroom-notes { grid-template-columns: 1fr 1fr 1fr; } }
.showroom-notes p:not(.spec) { color: var(--ink-2); font-size: 0.95rem; margin-top: 0.9rem; max-width: 44ch; }

/* ---------- Product configurator (hub + embed pages) ---------- */
.cfg-grid {
  max-width: 1300px;
  margin: 0 auto;
  display: grid;
  gap: clamp(1.2rem, 2.5vw, 2rem);
}
@media (min-width: 760px) { .cfg-grid { grid-template-columns: 1fr 1fr; } }
.cfg-card {
  display: block;
  border: 1px solid var(--ink);
  background: var(--card);
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
}
.cfg-card:hover { transform: translateY(-4px); box-shadow: 0 18px 40px rgba(23,21,17,0.14); }
.cfg-card__media {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--paper);
  border-bottom: 1px solid var(--ink);
  overflow: hidden;
}
.cfg-card__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.6s var(--ease);
}
.cfg-card:hover .cfg-card__media img { transform: scale(1.03); }
/* Styled placeholder until Victor supplies product imagery */
.cfg-card__placeholder {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 0.9rem;
}
.cfg-card__placeholder::before {
  content: "";
  width: 15px; height: 15px;
  border: 1.5px solid var(--accent-deep);
  transform: rotate(45deg);
}
.cfg-card__placeholder span {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.cfg-card__body { padding: clamp(1.3rem, 2.5vw, 1.8rem); }
.cfg-card__body h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  margin: 0.7rem 0 0.5rem;
}
.cfg-card__body > p { color: var(--muted); font-size: clamp(1rem, 0.9rem + 0.4vw, 1.25rem); line-height: 1.55; max-width: 52ch; }
.cfg-card .lab__badge { margin-top: 1.2rem; }
.cfg-card:hover .lab__badge { background: var(--accent-deep); border-color: var(--accent-deep); color: var(--paper); }

/* Embed page: the widget sits in a spec-sheet frame like the showroom */
.cfg-frame {
  position: relative;
  max-width: 1300px;
  margin: 0 auto;
  border: 1px solid var(--ink);
  background: var(--card);
  padding: clamp(0.8rem, 2vw, 1.6rem);
}
.cfg-frame product-configurator { display: block; }

/* Loading panel for the widget embed. Without it the frame is one line of
   fallback text tall and then snaps open by the widget's full height. The
   showroom deliberately does NOT use this: its iframe already has a fixed
   height, and the tour carries its own branded loader. */
.cfg-frame.is-loading { min-height: clamp(420px, 68vh, 760px); }
.frame-loader {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: var(--paper);
  transition: opacity 0.6s var(--ease), visibility 0.6s;
}
.frame-loader.is-done { opacity: 0; visibility: hidden; pointer-events: none; }
/* The frame grows to the widget's full height while still covered, so the
   panel can end up taller than the screen. Sticky keeps the label in view. */
.frame-loader__inner {
  position: sticky;
  top: 0;
  height: min(100vh, 100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.15rem;
}
.frame-loader__label {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-text);
}
.frame-loader__bar {
  position: relative;
  width: min(240px, 42%);
  height: 1px;
  background: var(--line);
  overflow: hidden;
}
.frame-loader__bar i {
  position: absolute;
  inset: 0 auto 0 0;
  width: 38%;
  background: var(--accent-deep);
  animation: frame-sweep 1.35s var(--ease-io) infinite;
}
@keyframes frame-sweep {
  0%   { transform: translateX(-110%); }
  100% { transform: translateX(360%); }
}
.cfg-bar {
  max-width: 1300px;
  margin: 1rem auto 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

/* Project-page CTA banner (e.g. "Walk the showroom") */
.project-cta {
  /* Lives inside .project-summary, which already carries the page
     padding — no horizontal padding here or the cards drift inward */
  max-width: 1300px;
  margin: clamp(2.5rem, 6vh, 4rem) auto 0;
}
.project-cta__inner {
  border: 1px solid var(--ink);
  background: var(--card);
  padding: clamp(1.4rem, 3.5vw, 2.2rem);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.2rem;
}
.project-cta__inner b {
  display: block;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.4rem, 2.8vw, 2rem);
  margin-bottom: 0.3rem;
}
.project-cta__inner p { color: var(--muted); font-size: clamp(1rem, 0.9rem + 0.4vw, 1.25rem); line-height: 1.55; max-width: 52ch; }

/* Live-experience portal cards (showroom / configurator) — ink cards with
   a render glowing through, same language as the landing Interactive band */
.cta-cards { display: grid; gap: 1.2rem; }
@media (min-width: 800px) { .cta-cards { grid-template-columns: 1fr 1fr; } }
.cta-card {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-height: 260px;
  padding: 1.8rem;
  background-color: var(--ink);
  color: var(--paper);
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
}
.cta-card:hover { transform: translateY(-4px); box-shadow: 0 18px 40px rgba(23,21,17,0.25); }
.cta-card__bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0.5;
  transform: scale(1.01);
  transition: opacity 0.5s var(--ease), transform 0.7s var(--ease);
}
.cta-card__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(23,21,17,0.92) 0%, rgba(23,21,17,0.72) 55%, rgba(23,21,17,0.16) 100%);
}
.cta-card > :not(.cta-card__bg) { position: relative; z-index: 1; }
.cta-card b {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.3rem, 2.4vw, 1.7rem);
  margin-bottom: 0.5rem;
}
/* Doubled selector: .project-summary p would otherwise out-order this
   and paint the note dark-on-dark at its big summary size */
.cta-card p,
.project-summary .cta-card p {
  color: rgba(242,239,231,0.88);
  font-size: 0.9rem;
  line-height: 1.5;
  max-width: 52ch;
  margin-bottom: 1.4rem;
}
.cta-card:hover .cta-card__bg { opacity: 0.68; transform: scale(1.045); }
.cta-card:hover .lab__badge { background: var(--paper); border-color: var(--paper); color: var(--ink); }

/* ---------- Footer / big CTA ---------- */
.footer {
  background-color: var(--ink);
  background-image: var(--grain);
  color: var(--paper);
  padding: clamp(4rem, 12vh, 8rem) var(--pad) 2rem;
  position: relative;
}
.footer__cta { max-width: 1300px; margin: 0 auto; }
.footer__title {
  font-size: clamp(2.6rem, 7.5vw, 6.5rem);
  margin: 1.2rem 0 2rem;
}
.footer__title i { font-style: italic; }
.footer__title em {
  font-style: italic;
  color: var(--paper);
  background-image: linear-gradient(var(--accent-deep), var(--accent-deep));
  background-repeat: no-repeat;
  background-size: 100% 0.78em;
  background-position: 0 0.3em;
  padding: 0 0.16em;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
.footer__actions { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
/* Buttons wipe their colour in from the left rather than cross-fading: a
   220%-wide two-stop gradient parked on its right (rest) half and slid over
   to its left (hover) half. Same device as the Harry Apata site. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.95rem 1.6rem;
  border: 1px solid var(--paper);
  overflow: hidden;
  background-repeat: no-repeat;
  background-size: 220% 100%;
  background-position: 100% 0;
  background-image: linear-gradient(90deg, var(--paper) 0 50%, transparent 50% 100%);
  transition: background-position 0.55s var(--ease), color 0.35s var(--ease),
              border-color 0.35s var(--ease), box-shadow 0.45s var(--ease);
}
.btn:hover { background-position: 0 0; color: var(--ink); }

.btn--accent {
  border-color: var(--accent);
  color: var(--accent);
  background-image: linear-gradient(90deg, var(--accent) 0 50%, transparent 50% 100%);
}
.btn--accent:hover { background-position: 0 0; color: var(--ink); }

.btn--solid {
  color: var(--paper);
  border-color: var(--ink);
  background-image: linear-gradient(90deg, var(--accent-deep) 0 50%, var(--ink) 50% 100%);
}
.btn--solid:hover {
  background-position: 0 0;
  color: var(--paper);
  border-color: var(--accent-deep);
  box-shadow: 0 18px 38px -16px rgba(23,21,17,0.4);
}

/* The bare .btn is drawn for dark bands (paper border, wipes to paper). This
   is the same button for a paper background. */
.btn--ink {
  border-color: var(--ink);
  color: var(--ink);
  background-image: linear-gradient(90deg, var(--ink) 0 50%, transparent 50% 100%);
}
.btn--ink:hover { background-position: 0 0; color: var(--paper); }

.footer__base {
  max-width: 1300px;
  margin: clamp(3rem, 8vh, 6rem) auto 0;
  padding-top: 1.2rem;
  border-top: 1px solid rgba(242,239,231,0.2);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.8rem;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(242,239,231,0.6);
}
.footer__base a:hover { color: var(--accent); }

/* ---------- Contact drawer ---------- */
.contact-overlay {
  position: fixed; inset: 0;
  background: rgba(23,21,17,0.45);
  backdrop-filter: blur(3px);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.45s var(--ease), visibility 0.45s;
  z-index: 800;
}
body.contact-open .contact-overlay { opacity: 1; visibility: visible; }

.contact-panel {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: min(94vw, 480px);
  background: var(--paper);
  z-index: 810;
  transform: translateX(calc(100% + 40px));
  transition: transform 0.6s var(--ease);
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  border-left: 1px solid var(--line);
  padding: 1.6rem clamp(1.4rem, 3vw, 2.4rem) 2rem;
  /* Sliding off-screen hides the drawer from sight but not from the browser:
     its ten controls stayed in the tab order of every page and its heading
     stayed in every page's outline. visibility takes it out of both, delayed
     so it is still painted while the slide-out animates. Matches the
     .contact-overlay rule above. */
  visibility: hidden;
  transition: transform 0.6s var(--ease), visibility 0.6s;
}
body.contact-open .contact-panel { transform: none; visibility: visible; }
.contact-panel__top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.8rem;
}
.contact-panel__close {
  width: 40px; height: 40px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 50%;
  transition: background 0.3s, color 0.3s, transform 0.4s var(--ease);
}
.contact-panel__close:hover { background: var(--ink); color: var(--paper); transform: rotate(90deg); }
.contact-panel h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.7rem, 3.2vw, 2.2rem);
  line-height: 1.05;
  margin-bottom: 0.8rem;
}
.contact-panel__sub { color: var(--muted); font-size: 0.92rem; margin-bottom: 1.8rem; }

/* Cart easter-egg variant */
.contact-panel__cart {
  display: none;
  padding: 1.1rem;
  margin-bottom: 1.6rem;
}
body.contact-cart .contact-panel__cart { display: block; }
.contact-panel__cart img {
  display: block;
  max-height: 180px;
  margin: 0 auto 0.8rem;
}
.contact-panel__cart-placeholder {
  aspect-ratio: 16/9;
  display: grid;
  place-items: center;
  background: repeating-linear-gradient(-45deg, transparent 0 10px, var(--line-soft) 10px 11px);
  margin-bottom: 0.8rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
  padding: 1rem;
}
.contact-panel__cart b { font-family: var(--font-display); font-weight: 400; font-size: 1.15rem; display: block; margin-bottom: 0.3rem; }
.contact-panel__cart p { font-size: 1.05rem; line-height: 1.55; color: var(--ink-2); }

.cform { display: grid; gap: 1rem; }
.cform label {
  display: grid;
  gap: 0.35rem;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.cform input, .cform textarea {
  font: inherit;
  /* 16px+ on purpose: below that, iOS Safari auto-zooms the page on focus */
  font-size: 1.05rem;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  padding: 0.85rem 0.95rem;
  border-radius: var(--radius);
  transition: border-color 0.25s, box-shadow 0.25s;
}
.cform input:focus, .cform textarea:focus {
  outline: none;
  border-color: var(--accent-deep);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}
.cform textarea { min-height: 130px; resize: vertical; }
.cform__status { font-size: 0.95rem; color: var(--accent-text); min-height: 1.2em; }
.contact-panel__alt {
  margin-top: auto;
  padding-top: 1.6rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}
/* Outlined buttons using the same wipe as "Start a conversation" in the
   footer: ink at rest, filling with deep ochre on hover. Deep rather than
   bright ochre because the drawer sits on paper. */
.contact-panel__alt a {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.7rem 1.05rem;
  border: 1px solid var(--ink);
  color: var(--ink);
  overflow: hidden;
  background-repeat: no-repeat;
  background-size: 220% 100%;
  background-position: 100% 0;
  background-image: linear-gradient(90deg, var(--accent-deep) 0 50%, transparent 50% 100%);
  transition: background-position 0.55s var(--ease), color 0.35s var(--ease),
              border-color 0.35s var(--ease);
}
.contact-panel__alt a:hover {
  background-position: 0 0;
  color: var(--paper);
  border-color: var(--accent-deep);
}

/* Side contact tab */
.contact-tab {
  position: fixed;
  right: 0;
  top: 50%;
  z-index: 790;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.8rem;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  padding: 1.3rem 0.8rem 1.5rem;
  transition: background 0.3s, transform 0.6s var(--ease), opacity 0.4s;
}
.contact-tab svg { width: 19px; height: 19px; flex: none; }
.contact-tab span { writing-mode: vertical-rl; }
.contact-tab:hover { background: var(--accent-deep); }
body.contact-open .contact-tab { transform: translateY(-50%) translateX(80px); opacity: 0; }
@media (max-width: 700px) { .contact-tab { display: none; } }

/* ---------- Lightbox ---------- */
.lightbox {
  position: fixed; inset: 0;
  z-index: 850;
  background: color-mix(in srgb, var(--ink) 92%, transparent);
  display: grid;
  place-items: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.4s var(--ease), visibility 0.4s;
}
.lightbox.open { opacity: 1; visibility: visible; }
.lightbox img {
  max-width: 92vw;
  max-height: 88vh;
  box-shadow: 0 40px 120px -30px rgba(0,0,0,0.7);
  transform-origin: center center;
  transition: opacity 0.28s var(--ease);
}
/* The print is lifted out of the page: JS starts it at the exact size and
   position of its tile, tilted and white-bordered like a photo in an album,
   then it straightens and grows in. Closing plays the same move in reverse. */
.lightbox img.is-flipping {
  transition: transform 0.62s var(--ease-io), box-shadow 0.62s var(--ease-io), opacity 0.28s var(--ease);
}
/* Blanked until the next picture has decoded, so the one you just closed can
   never flash inside the frame of the one you just opened. transition:none is
   the point: it must cut instantly on the way out and only fade on the way in.
   Declared last so it wins over the flipping rule above. */
.lightbox img.is-loading { opacity: 0; transition: none; }
/* pointer-events off: the overlay is fading but still hit-testable, so a
   click on the prev/next buttons would cancel the close mid-flight. */
.lightbox.open.is-closing { opacity: 0; transition: opacity 0.6s var(--ease-io); pointer-events: none; }
.lightbox__meta {
  position: absolute;
  bottom: 1.2rem; left: 50%;
  transform: translateX(-50%);
  color: rgba(242,239,231,0.75);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.lightbox__btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 52px; height: 52px;
  display: grid;
  place-items: center;
  color: var(--paper);
  border: 1px solid rgba(242,239,231,0.3);
  border-radius: 50%;
  transition: background 0.25s, border-color 0.25s;
}
.lightbox__btn:hover { background: var(--accent-deep); border-color: var(--accent-deep); }
.lightbox__btn--prev { left: clamp(0.6rem, 2vw, 2rem); }
.lightbox__btn--next { right: clamp(0.6rem, 2vw, 2rem); }
/* Close borrows the contact drawer's gesture: inverts and turns 90deg, so the
   cross reads as a plus on the way out. Prev/next keep the ochre fill. */
.lightbox__btn--close {
  top: clamp(0.6rem, 2vw, 2rem);
  right: clamp(0.6rem, 2vw, 2rem);
  transform: none;
  transition: background 0.3s, color 0.3s, border-color 0.3s, transform 0.4s var(--ease);
}
.lightbox__btn--close:hover {
  background: var(--paper);
  border-color: var(--paper);
  color: var(--ink);
  transform: rotate(90deg);
}
@media (max-width: 700px) { .lightbox__btn--prev, .lightbox__btn--next { display: none; } }

/* ---------- Page hero (inner pages) ---------- */
.page-hero { padding: calc(var(--header-h) + clamp(2.5rem, 8vh, 5rem)) var(--pad) clamp(2rem, 5vh, 3.5rem); }
.page-hero__inner { max-width: 1300px; margin: 0 auto; }
.page-hero h1 { font-size: clamp(2.6rem, 7vw, 6rem); margin-top: 1rem; }
.page-hero h1 em { font-style: italic; color: var(--accent-display); }
.page-hero__sub { color: var(--muted); max-width: 60ch; margin-top: 1.2rem; }

/* ---------- Projects index: one project per row ---------- */
.plist {
  max-width: 1300px;
  margin: 0 auto;
  padding: 0 var(--pad) clamp(4rem, 10vh, 7rem);
  border-top: 1px solid var(--line);
}
.prow {
  display: grid;
  gap: clamp(1.2rem, 3vw, 3rem);
  padding: clamp(1.6rem, 4vh, 2.6rem) 0;
  border-bottom: 1px solid var(--line);
  align-items: center;
}
@media (min-width: 760px) {
  .prow { grid-template-columns: 5fr 7fr; }
  .prow:nth-child(even) .prow__info { order: 2; }
}
.prow__idx { display: block; margin-bottom: 0.9rem; }
.prow__name {
  font-size: clamp(1.9rem, 4.2vw, 3.4rem);
  transition: color 0.3s var(--ease);
}
.prow:hover .prow__name { color: var(--accent-text); }
.prow__kicker { color: var(--muted); font-size: 0.92rem; margin-top: 0.5rem; max-width: 40ch; }
.prow__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  margin-top: 1.6rem;
  color: var(--ink-2);
}
.prow__cta .arr { display: inline-block; transition: transform 0.35s var(--ease); }
.prow:hover .prow__cta .arr { transform: translate(0.18em, -0.18em); }
.prow__img {
  overflow: hidden;
  aspect-ratio: 16 / 8;
  background: var(--paper-2);
}
.prow__img img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease);
}
.prow:hover .prow__img img { transform: scale(1.045); }

/* ---------- Project detail ---------- */
.project-hero {
  position: relative;
  min-height: 72vh;
  display: flex;
  align-items: flex-end;
  background-size: cover;
  background-position: center;
  color: #fff;
}
.project-hero::after {
  content: "";
  position: absolute; inset: 0;
  background:
    linear-gradient(to bottom, rgba(23,21,17,0.45) 0%, rgba(23,21,17,0) 22%),
    linear-gradient(to top, rgba(23,21,17,0.62), rgba(23,21,17,0.05) 55%),
    rgba(23,21,17,0.1);
}
.project-hero__inner {
  position: relative;
  z-index: 1;
  width: 100%;
  padding: calc(var(--header-h) + 3rem) var(--pad) 2.2rem;
  max-width: 1300px;
  margin: 0 auto;
}
.project-hero h1 { font-size: clamp(2.6rem, 7vw, 5.6rem); color: #fff; margin-top: 0.8rem; }
.project-hero .spec { color: rgba(255,255,255,0.85); }
.project-hero .spec::after { background: rgba(255,255,255,0.35); }

.project-meta {
  border-bottom: 1px solid var(--line);
}
.project-meta__inner {
  max-width: 1300px;
  margin: 0 auto;
  padding: 1.6rem var(--pad);
  display: grid;
  gap: 1.2rem;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.project-meta dt {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.3rem;
}
.project-meta dd { font-size: 1rem; }
.project-summary {
  max-width: 1300px;
  margin: 0 auto;
  padding: clamp(2.5rem, 6vh, 4rem) var(--pad) 0;
}
.project-summary p {
  font-size: clamp(1.2rem, 1.02rem + 0.75vw, 1.625rem);
  line-height: 1.5;
  max-width: 34em;
  color: var(--ink-2);
}
.project-summary p::first-letter { }

.psection { padding: clamp(2.6rem, 7vh, 4.5rem) var(--pad) 0; }
.psection:last-of-type { padding-bottom: clamp(4rem, 9vh, 6rem); }
.psection__head { max-width: 1300px; margin: 0 auto clamp(1.4rem, 3vh, 2.2rem); }
.psection__note { color: var(--muted); font-size: 0.95rem; margin-top: 0.4rem; }

.pmasonry { position: relative; max-width: 1300px; margin: 0 auto; }
.pgrid-uniform {
  display: grid;
  gap: 1rem;
  max-width: 1300px;
  margin: 0 auto;
  grid-template-columns: repeat(auto-fill, minmax(min(240px, 45vw), 1fr));
}
.pgrid-uniform figure { overflow: hidden; background: var(--paper-2); cursor: zoom-in; }
.pgrid-uniform img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 1/1; transition: transform 0.7s var(--ease); }
.pgrid-uniform figure:hover img { transform: scale(1.04); }

.project-nav {
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: 1fr 1fr;
}
.project-nav a {
  padding: clamp(1.8rem, 5vh, 3rem) var(--pad);
  display: grid;
  gap: 0.4rem;
  transition: background 0.35s var(--ease);
}
.project-nav a:hover { background: var(--paper-2); }
.project-nav a + a { border-left: 1px solid var(--line); text-align: right; }
/* Two configurator products means prev and next are the same page, so only
   one link is drawn and it takes the full width instead of half a row. */
.project-nav--single { grid-template-columns: 1fr; }
.project-nav b { font-family: var(--font-display); font-weight: 400; font-size: clamp(1.2rem, 2.6vw, 1.8rem); }

/* ---------- Transition tile (auto-cycling colorway/position loop) ----------
   Hard cuts (no fade), and hover has no effect on the loop. */
.transition-tile img { transition: none; }
.transition-tile:hover img { transform: none; } /* stacked layers — no hover zoom */
.transition-tile .xfade {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
}
.transition-tile .xfade.on { opacity: 1; }
.transition-tile figcaption { z-index: 1; }

/* ---------- Reveal on scroll ---------- */
.reveal { opacity: 0; transform: translateY(34px); transition: opacity 0.9s var(--ease), transform 0.9s var(--ease); }
.reveal.in { opacity: 1; transform: none; }

/* ---------- Arrange mode ---------- */
body.arranging .masonry__item { cursor: grab; }
body.arranging .masonry__item:active { cursor: grabbing; }
body.arranging .masonry__item.dragging { z-index: 50; opacity: 0.92; }
body.arranging .masonry__item.dragging figure { outline: 2px solid var(--accent-deep); box-shadow: 0 30px 60px -20px rgba(23,21,17,0.5); }
body.arranging .masonry__item figcaption { display: none; }
.arrange-bar {
  position: fixed;
  left: 50%;
  bottom: 1.2rem;
  transform: translateX(-50%);
  z-index: 700;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  background: var(--ink);
  color: var(--paper);
  padding: 0.7rem 1rem;
  box-shadow: 0 20px 50px -18px rgba(23,21,17,0.6);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.arrange-bar button {
  border: 1px solid rgba(242,239,231,0.35);
  padding: 0.45rem 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  transition: background 0.25s, color 0.25s;
}
.arrange-bar button:hover { background: var(--accent); color: var(--ink); border-color: var(--accent); }

/* ---------- Utilities ---------- */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Skip link: off-screen until focused, then a paper chip over the nav.
   z-index clears the sticky header (100) and the contact tab (800). */
.skip-link {
  position: fixed;
  top: 0; left: 50%;
  transform: translate(-50%, -110%);
  z-index: 900;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--ink);
  border-top: 0;
  padding: 0.7em 1.4em;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  transition: transform 0.25s var(--ease);
}
.skip-link:focus { transform: translate(-50%, 0); }

/* ---------- Mobile refinements ---------- */
@media (max-width: 700px) {
  /* Long spec kickers (project pages) must wrap — nowrap dragged the
     whole page wider than the viewport */
  .spec { white-space: normal; }
  /* Badges: tighter type so "Live — configure a product ↗" holds one line */
  .lab__badge { font-size: 0.72rem; letter-spacing: 0.08em; padding: 0.45rem 0.75rem; }
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001s !important;
  }
  .reveal { opacity: 1; transform: none; }
}
