/* ============================================================================
   MALÉMÉ — deep-black editorial portfolio
   Design language: oversized light-weight display type, hairline rules,
   monochrome grid, edge-cropped wordmark.
   No CSS framework, no JS animation library.
   ========================================================================== */

/* ---------- 1. TOKENS ---------------------------------------------------- */
:root {
  /* colour */
  --canvas:      #161615;   /* lifted off pure black; muted text still 5.1:1 */
  --canvas-lift: #1E1E1C;   /* cards, raised surfaces */
  --ink:         #F5F5F3;
  --ink-muted:   #908F8C;   /* nudged up with the canvas to hold AA contrast */
  --rule:        #33322F;
  --rule-strong: #4A4946;
  --scrim:       rgba(10, 10, 9, 0.96);

  /* type */
  --font: 'Archivo', 'Helvetica Neue', Arial, system-ui, sans-serif;

  --t-display: clamp(3.2rem, 13vw, 12rem);
  --t-h1:      clamp(2.4rem, 7vw, 5.5rem);
  --t-h2:      clamp(1.8rem, 4vw, 3rem);
  --t-h3:      clamp(1.15rem, 2vw, 1.5rem);
  --t-body:    1.0625rem;          /* 17px — never below 16 */
  --t-small:   0.9375rem;
  --t-micro:   0.6875rem;

  --track-micro:   0.18em;
  --track-display: -0.035em;

  /* spacing — spacious scale */
  --s1: 0.5rem;  --s2: 1rem;   --s3: 1.5rem;  --s4: 2.5rem;
  --s5: 4rem;    --s6: 6rem;   --s7: 9rem;    --s8: 12.5rem;

  /* layout */
  --gutter: clamp(1.25rem, 5vw, 5rem);
  --maxw: 1680px;

  /* motion — one rhythm for the whole site */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur-fast: 180ms;
  --dur: 320ms;
  --dur-slow: 620ms;

  --nav-h: 64px;            /* matches .nav__inner min-height; the pinned hero
                               sticks below this rather than behind it */
  --z-nav: 100;
  --z-lightbox: 1000;
}

/* ---------- 2. RESET / BASE --------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--t-body);
  font-weight: 300;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;          /* never a horizontal scrollbar */
}
img, svg { display: block; max-width: 100%; height: auto; }

/* class selectors below would otherwise out-rank the UA's [hidden] rule */
[hidden] { display: none !important; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
h1, h2, h3, h4 { font-weight: 200; line-height: 1.02; margin: 0; }
p { margin: 0 0 1.15em; }

::selection { background: var(--ink); color: var(--canvas); }

/* keyboard focus must be obvious on black */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 1px;
}
:focus:not(:focus-visible) { outline: none; }

.skip-link {
  position: absolute; left: var(--gutter); top: -100px;
  background: var(--ink); color: var(--canvas);
  padding: 0.75rem 1.25rem; z-index: 999;
  transition: top var(--dur-fast) var(--ease);
}
.skip-link:focus { top: 1rem; }

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

/* ---------- 3. LAYOUT PRIMITIVES ---------------------------------------- */
.shell { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: var(--s6); }
.section--tight { padding-block: var(--s5); }
.rule { height: 1px; background: var(--rule); border: 0; margin: 0; }

/* micro-label: uppercase, tracked, small */
.micro {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* split heading:  SELECTED ——————— WORKS
   This is ONE heading element; the words are spans so screen readers announce
   "Selected Works", not two separate headings. */
.split-head {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 4vw, 3rem);
  width: 100%;
  margin: 0;
  font-size: var(--t-h1);
  font-weight: 200;
  line-height: 1.02;
  letter-spacing: var(--track-display);
  text-transform: uppercase;
}
.split-head > span { flex: 0 0 auto; white-space: nowrap; }

/* Section headings resolve left to right: the first word wipes in horizontally,
   the rule draws across, then the second word wipes in — one continuous
   movement across the page. Negative insets stop accents being clipped. */
.split-head > span:not(.split-head__line) {
  clip-path: inset(-8% 100% -8% 0);
  transform: translateX(-0.1em);
  transition: clip-path 1.5s var(--ease-out), transform 1.5s var(--ease-out);
}
.split-head.is-revealed > span:not(.split-head__line) {
  clip-path: inset(-8% -8% -8% 0);
  transform: none;
}
/* the second half waits for the rule to cross before it starts */
.split-head.is-revealed > span:not(.split-head__line):last-child {
  transition-delay: 0.75s;
}
/* the wipe carries the motion, so drop the generic reveal's vertical shift */
.split-head[data-reveal] { transform: none; }

.no-js .split-head > span:not(.split-head__line) { clip-path: none; transform: none; }
/* The rule draws itself left to right as the heading scrolls in — an echo of
   the mark-making in the work. Starts slightly behind the words so it reads as
   a consequence of them rather than a separate event. */
.split-head__line {
  flex: 1 1 auto !important; height: 1px; background: var(--rule-strong);
  transform: scaleX(0);
  transform-origin: left;
  /* Starts as the first word finishes and lands as the second begins, so the
     three parts read as one movement crossing the page rather than three events. */
  transition: transform 1.1s var(--ease-out) 0.35s;
}
.split-head.is-revealed .split-head__line { transform: scaleX(1); }
@media (max-width: 640px) {
  /* Both halves are nowrap, so a long pair like "What is / Scratchboard?"
     overflows the screen side by side. Stack them instead: the rule becomes a
     full-width divider between the two lines and keeps its draw animation. */
  .split-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.55rem;
    font-size: clamp(1.9rem, 9vw, 3rem);
  }
  .split-head > span:not(.split-head__line) { white-space: normal; max-width: 100%; }
  .split-head__line { flex: 0 0 1px !important; align-self: stretch; width: 100%; }
}

/* ---------- 4. NAV ------------------------------------------------------- */
.nav {
  position: sticky; top: 0; z-index: var(--z-nav);
  background: color-mix(in srgb, var(--canvas) 82%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--rule);
}
.nav__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3);
  min-height: 64px;
}
.nav__brand {
  font-size: 1rem; font-weight: 400;
  letter-spacing: 0.22em; text-transform: uppercase;
  padding-block: 0.75rem;
}
.nav__links { display: flex; gap: clamp(1.25rem, 3vw, 2.75rem); align-items: center; }
.nav__link {
  position: relative;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-muted);
  padding: 1.25rem 0;                 /* ≥44px touch target */
  transition: color var(--dur-fast) var(--ease);
}
.nav__link::after {
  content: ''; position: absolute; left: 0; bottom: 1rem;
  width: 100%; height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: right;
  transition: transform var(--dur) var(--ease-out);
}
.nav__link:hover, .nav__link:focus-visible { color: var(--ink); }
.nav__link:hover::after { transform: scaleX(1); transform-origin: left; }
.nav__link[aria-current='page'] { color: var(--ink); }
.nav__link[aria-current='page']::after { transform: scaleX(1); }

@media (max-width: 600px) {
  /* The brand already links home, so "Index" is redundant — dropping it is
     what keeps the bar on one line at 375px instead of overflowing by 13px. */
  .nav__link--index { display: none; }
  .nav__links { gap: 0.95rem; }
  .nav__link { font-size: 0.625rem; letter-spacing: 0.1em; }
  .nav__brand { letter-spacing: 0.16em; }
}

/* ---------- 5. HERO ------------------------------------------------------ */
/* Sits high, just under the nav. The wordmark animation is scrubbed against
   scroll, so the higher it starts the longer it stays in view while resolving —
   push it down and it leaves the screen before the animation finishes. */
.hero { padding-block: clamp(1rem, 3vw, 2.25rem) var(--s5); }

/* --- pinned hero ---
   The stage sticks to the top while the taller wrapper scrolls past it, so the
   hero holds still and your scroll drives the wordmark instead. Native sticky,
   not intercepted scroll events: the keyboard, trackpad momentum and screen
   readers all keep working, and nobody can get trapped here.
   JS sets the wrapper's height; without JS there is no height and no pin. */
.hero-pin { position: relative; }
.hero-pin__stage {
  position: sticky;
  /* Stick BELOW the sticky nav, not behind it. At top:0 the two compete and
     the hero drifts upward under the bar before pinning engages. */
  top: var(--nav-h);
  min-height: calc(100svh - var(--nav-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* Short or landscape phones: let the hero be its own height rather than
   forcing a full screen and clipping the statement. */
@media (max-height: 620px) {
  .hero-pin__stage { min-height: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-pin { height: auto !important; }
  .hero-pin__stage { position: static; min-height: 0; }
}
.hero__display {
  font-size: var(--t-display);
  font-weight: 200;
  letter-spacing: var(--track-display);
  text-transform: uppercase;
  line-height: 0.88;
  text-align: center;
  /* Tracking is applied after the final glyph too, so the ink overhangs the
     line box by one step and centring lands half a step off. Correct by half. */
  text-indent: calc(var(--track-display) / 2);
}
.hero__meta {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5);
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
}
/* stacked and centred under the centred wordmark */
.hero__meta--centred {
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s4);
  padding-top: var(--s4);
}
.hero__meta--centred .hero__statement {
  max-width: 54ch;          /* a touch wider than the left-aligned measure,
                               so a centred block does not run too tall */
  margin-inline: auto;
}
.hero__meta--centred p { margin-bottom: 0; }
.hero__statement {
  max-width: 46ch;                    /* keeps line length readable */
  font-size: clamp(1.05rem, 1.6vw, 1.35rem);
  font-weight: 200;
  line-height: 1.55;
  color: var(--ink);
}

/* ---------- 5b. SCROLL-DRIVEN WORDMARK ---------------------------------- */
/* Three effects, choreographed rather than stacked: a band of light wipes up
   across the letters, the blur resolves, and the letters draw together.
   JS writes these properties every frame; the values here are the p=0 state.

   Gated on `html:not(.no-js)` so the name is NEVER hidden unless the script
   that reveals it is actually running. */
html:not(.no-js) .hero__display--anim {
  -webkit-mask-image: linear-gradient(to top, #000 -26%, rgba(0,0,0,.45) -12%, rgba(0,0,0,0) -1%);
          mask-image: linear-gradient(to top, #000 -26%, rgba(0,0,0,.45) -12%, rgba(0,0,0,0) -1%);
  filter: blur(10px);
  transform: scale(1.11);
  will-change: filter, transform;
}
/* Letters are wrapped in spans by JS and moved with translateX. Animating
   letter-spacing instead would re-layout the text on every single frame. */
.hero__display--anim span { display: inline-block; will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  html:not(.no-js) .hero__display--anim {
    -webkit-mask-image: none !important;
            mask-image: none !important;
    filter: none !important;
    transform: none !important;
  }
  .hero__display--anim span { transform: none !important; }
}

/* ---------- 6. WORK GRID ------------------------------------------------- */
.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(1.5rem, 3vw, 3.5rem) clamp(1.25rem, 2.5vw, 2.5rem);
}
/* editorial asymmetry on desktop */
.grid--editorial > *:nth-child(6n+1) { grid-column: span 7; }
.grid--editorial > *:nth-child(6n+2) { grid-column: span 5; align-self: end; }
.grid--editorial > *:nth-child(6n+3) { grid-column: 2 / span 5; }
.grid--editorial > *:nth-child(6n+4) { grid-column: span 6; align-self: center; }
.grid--editorial > *:nth-child(6n+5) { grid-column: span 6; }
.grid--editorial > *:nth-child(6n+6) { grid-column: 4 / span 8; }

.grid--even > * { grid-column: span 4; }

@media (max-width: 1024px) {
  .grid--editorial > *, .grid--even > * { grid-column: span 6 !important; }
}
@media (max-width: 640px) {
  .grid--editorial > *, .grid--even > * { grid-column: span 12 !important; }
}

/* ---------- 7. WORK CARD ------------------------------------------------- */
.work { position: relative; }
.work__btn {
  display: block; width: 100%; text-align: left;
  padding: 0;
  -webkit-tap-highlight-color: transparent;
}
.work__frame {
  position: relative;
  overflow: hidden;
  /* No lifted panel behind the work. The pieces are black-grounded, so any
     grey backing shows as a visible rectangle around them before and during
     the reveal. Transparent lets the page itself sit behind the artwork. */
  background: transparent;
}
/* "Develop" reveal — the work emerges upward out of black with its contrast
   settling as it lands, which is how the pieces are actually made.
   Note this uses the standalone `scale` property, NOT transform: scale().
   `transform` is reserved for the hover push below, and the two would
   otherwise overwrite each other. */
.work__img {
  width: 100%; height: auto;
  clip-path: inset(100% 0 0 0);
  scale: 1.06;
  filter: contrast(1.45) brightness(0.55);
  transition: clip-path 2s var(--ease-out),
              scale     2s var(--ease-out),
              filter    2s var(--ease-out),
              transform var(--dur-slow) var(--ease-out),
              opacity   var(--dur) var(--ease);
  /* No will-change here on purpose. It would hold a permanent compositor
     layer for every work in the catalogue — real memory, on a page whose
     reveal only ever runs once. Browsers promote during the transition
     anyway, which is exactly when it is needed. */
}
.work.is-revealed .work__img {
  clip-path: inset(0 0 0 0);
  scale: 1;
  filter: none;
}
.work__btn:hover .work__img,
.work__btn:focus-visible .work__img { transform: scale(1.035); }

/* thin frame line that draws in on hover */
/* No frame. Just the work, and the page behind it before the reveal. */
.work__frame::after {
  content: ''; position: absolute; inset: 0;
  border: 1px solid var(--ink);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
  pointer-events: none;
}
.work__btn:hover .work__frame::after,
.work__btn:focus-visible .work__frame::after { opacity: 0.5; }

/* "view" cue — hidden from touch devices where hover is meaningless */
.work__cue {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%) scale(0.9);
  width: 84px; height: 84px; border-radius: 50%;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--canvas) 72%, transparent);
  backdrop-filter: blur(6px);
  border: 1px solid rgba(245,245,243,0.35);
  opacity: 0;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease-out);
  pointer-events: none;
}
.work__cue span {
  font-size: var(--t-micro); font-weight: 500;
  letter-spacing: var(--track-micro); text-transform: uppercase;
}
@media (hover: hover) and (pointer: fine) {
  .work__btn:hover .work__cue,
  .work__btn:focus-visible .work__cue { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

.work__caption {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--s2);
  margin-top: var(--s2);
  padding-top: 0.75rem;
  border-top: 1px solid var(--rule);
}
.work__title { font-size: var(--t-h3); font-weight: 300; letter-spacing: -0.01em; }
.work__year { font-size: var(--t-micro); letter-spacing: var(--track-micro); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.work__medium { font-size: var(--t-small); color: var(--ink-muted); margin-top: 0.25rem; }

/* ---------- 8. FILTER CHIPS --------------------------------------------- */
.filters {
  display: flex; flex-wrap: wrap; gap: 0.625rem;
  margin-block: var(--s4) var(--s5);
}
.chip {
  min-height: 44px;                 /* touch target */
  padding: 0.6rem 1.35rem;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-muted);
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}
.chip:hover { color: var(--ink); border-color: var(--ink-muted); }
.chip[aria-pressed='true'] {
  background: var(--ink); color: var(--canvas); border-color: var(--ink);
}

/* ---------- 9. LIGHTBOX / DEEP ZOOM ------------------------------------- */
.lightbox {
  position: fixed; inset: 0; z-index: var(--z-lightbox);
  background: var(--scrim);
  display: none;
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.lightbox[data-open] { display: block; }
.lightbox[data-visible] { opacity: 1; }

.lightbox__stage {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  overflow: hidden;
  cursor: zoom-in;
  touch-action: none;               /* we handle pinch ourselves */
}
.lightbox__stage[data-zoomed] { cursor: grab; }
.lightbox__stage[data-panning] { cursor: grabbing; }

.lightbox__img {
  max-width: 92vw;
  max-height: 82vh;
  /* The width/height ATTRIBUTES set on this element are presentational hints,
     so without these two lines the computed size is not `auto`, max-width and
     max-height clamp each axis independently, and the artwork is stretched.
     With `auto`, the two limits resolve together and the ratio is preserved. */
  width: auto;
  height: auto;
  /* Belt and braces: even if something ever forces a fixed box, the bitmap
     itself is letterboxed rather than distorted. Artwork must never stretch. */
  object-fit: contain;
  box-shadow: 0 0 0 1px rgba(245, 245, 243, 0.14);   /* same reason as the grid */
  transform-origin: center center;
  transition: transform var(--dur-slow) var(--ease-out);
  user-select: none; -webkit-user-drag: none;
}
.lightbox__stage[data-panning] .lightbox__img { transition: none; }

/* top bar */
.lightbox__bar {
  position: absolute; top: 0; left: 0; right: 0;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s2);
  padding: var(--s2) var(--gutter);
  z-index: 2;
}
.lightbox__count { font-variant-numeric: tabular-nums; }

.lb-btn {
  min-width: 44px; min-height: 44px;
  display: grid; place-items: center;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  color: var(--ink-muted);
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.lb-btn:hover, .lb-btn:focus-visible { color: var(--ink); border-color: var(--ink-muted); }
.lb-btn[disabled] { opacity: 0.3; cursor: not-allowed; }
.lb-btn svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.4; }

.lightbox__tools { display: flex; gap: 0.5rem; align-items: center; }

/* prev / next */
.lightbox__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  z-index: 2;
}
.lightbox__nav--prev { left: clamp(0.75rem, 2vw, 2rem); }
.lightbox__nav--next { right: clamp(0.75rem, 2vw, 2rem); }

/* caption */
.lightbox__caption {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: var(--s3) var(--gutter);
  display: flex; flex-wrap: wrap; align-items: flex-end;
  justify-content: space-between; gap: var(--s2) var(--s4);
  background: linear-gradient(to top, rgba(6,6,6,0.92), transparent);
  z-index: 2;
}
.lightbox__meta { max-width: 60ch; }
.lightbox__meta h2 { font-size: var(--t-h2); font-weight: 200; margin-bottom: 0.35rem; }
.lightbox__specs { font-size: var(--t-small); color: var(--ink-muted); }
.lightbox__hint { font-size: var(--t-micro); letter-spacing: var(--track-micro); text-transform: uppercase; color: var(--ink-muted); margin-top: 0.6rem; }

@media (max-width: 640px) {
  .lightbox__img { max-width: 96vw; max-height: 68vh; }
  .lightbox__caption { padding-bottom: calc(var(--s3) + env(safe-area-inset-bottom)); }
  .lightbox__bar { padding-top: calc(var(--s2) + env(safe-area-inset-top)); }
  .lightbox__nav { top: auto; bottom: 50%; }
}

/* ---------- 10. BUTTONS -------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: 0.75rem;
  min-height: 48px;
  padding: 0.85rem 1.75rem;
  border: 1px solid var(--ink);
  border-radius: 999px;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:hover, .btn:focus-visible { background: var(--ink); color: var(--canvas); }
.btn--ghost { border-color: var(--rule-strong); color: var(--ink-muted); }
.btn--ghost:hover, .btn--ghost:focus-visible { background: transparent; color: var(--ink); border-color: var(--ink); }
/* A state, not a control: nothing happens on hover and the cursor stays normal,
   so it never reads as something you can click. */
.btn--status {
  border-color: var(--rule-strong);
  color: var(--ink-muted);
  cursor: default;
  user-select: none;
}
.btn--status:hover { background: transparent; color: var(--ink-muted); }
.btn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.4; }

/* ---------- 11. EDITORIAL / PROSE --------------------------------------- */
.prose { max-width: 68ch; font-weight: 300; }
.prose p { font-size: clamp(1.0625rem, 1.3vw, 1.2rem); line-height: 1.75; }
.prose .lead {
  font-size: clamp(1.35rem, 2.4vw, 2rem);
  font-weight: 200; line-height: 1.4;
  margin-bottom: var(--s4);
}

/* two-column editorial split */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr);
  gap: clamp(2rem, 6vw, 6rem);
  align-items: start;
}
@media (max-width: 900px) { .split { grid-template-columns: 1fr; } }

/* CV / exhibition list */
.cv { border-top: 1px solid var(--rule); }
.cv__row {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr);
  gap: var(--s3);
  padding-block: 1.1rem;
  border-bottom: 1px solid var(--rule);
}
.cv__year { font-variant-numeric: tabular-nums; color: var(--ink-muted); font-size: var(--t-small); }
.cv__body { font-size: var(--t-small); }
.cv__body strong { font-weight: 500; }
/* standalone links in a CV row still need a real tap target */
.cv__body > a { display: inline-flex; align-items: center; min-height: 44px; }
.cv__place { color: var(--ink-muted); }
@media (max-width: 560px) { .cv__row { grid-template-columns: 4rem minmax(0, 1fr); gap: var(--s2); } }

/* ---------- 12. FORM ----------------------------------------------------- */
.form { display: grid; gap: var(--s3); max-width: 40rem; }
.field { display: grid; gap: 0.5rem; }
.field label {
  font-size: var(--t-micro); font-weight: 500;
  letter-spacing: var(--track-micro); text-transform: uppercase;
  color: var(--ink-muted);
}
.field label .req { color: var(--ink); }
.field input, .field textarea, .field select {
  width: 100%;
  min-height: 48px;                 /* ≥44px touch target */
  padding: 0.85rem 1rem;
  background: var(--canvas-lift);
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  color: var(--ink);
  font: inherit;
  font-size: 1rem;                  /* 16px — stops iOS zoom-on-focus */
  transition: border-color var(--dur-fast) var(--ease);
}
.field textarea { min-height: 9rem; resize: vertical; line-height: 1.6; }
.field input:focus, .field textarea:focus, .field select:focus { border-color: var(--ink); }
.field__help { font-size: var(--t-small); color: var(--ink-muted); }
.field__error {
  font-size: var(--t-small);
  color: var(--ink);
  display: none;
  align-items: center; gap: 0.4rem;
}
.field[data-invalid] .field__error { display: flex; }
.field[data-invalid] input,
.field[data-invalid] textarea { border-color: var(--ink); border-width: 2px; }
.field__error::before {
  content: ''; width: 14px; height: 14px; flex: none;
  background: currentColor;
  /* exclamation mark — colour is not the only error signal */
  -webkit-mask: var(--icon-alert) center / contain no-repeat;
  mask: var(--icon-alert) center / contain no-repeat;
}
:root {
  --icon-alert: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2 1 21h22L12 2zm0 6v7m0 3v.5' fill='none' stroke='%23000' stroke-width='2'/%3E%3C/svg%3E");
}

.form__status { font-size: var(--t-small); min-height: 1.5rem; }

/* ---------- 12b. INSTAGRAM LINK ----------------------------------------- */
.ig-link {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 44px;                 /* touch target */
  transition: color var(--dur-fast) var(--ease);
}
.ig-icon {
  width: 20px; height: 20px; flex: none;
  stroke: currentColor; fill: none; stroke-width: 1.5;
  transition: transform var(--dur) var(--ease-out);
}
.ig-link:hover .ig-icon,
.ig-link:focus-visible .ig-icon { transform: scale(1.1); }
/* in the footer the links are stacked blocks, so keep it on its own line */
.footer__col .ig-link { display: inline-flex; margin-bottom: 0.5rem; }

/* ---------- 13. FOOTER WORDMARK ----------------------------------------- */
.footer { border-top: 1px solid var(--rule); margin-top: var(--s6); }
.footer__cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  gap: var(--s4);
  padding-block: var(--s5);
}
.footer__col h3 { font-size: var(--t-micro); font-weight: 500; letter-spacing: var(--track-micro); text-transform: uppercase; color: var(--ink-muted); margin-bottom: var(--s2); }
.footer__col p { display: block; font-size: var(--t-small); margin-bottom: 0.5rem; }
/* A second, quieter label within a column: same treatment as the column
   headings, so "Gallery" reads as a label and the name below it stays white. */
.footer__col .footer__sublabel {
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-top: var(--s3);
  margin-bottom: 0.35rem;
}
/* The gallery name is a link but must stay white, unlike the muted nav links
   beside it. Underline on hover so it still reads as clickable. */
.footer__col .footer__gallery {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 0.25em;
  transition: text-decoration-color var(--dur-fast) var(--ease);
}
.footer__col .footer__gallery:hover,
.footer__col .footer__gallery:focus-visible { text-decoration-color: currentColor; }
.footer__col a {
  display: flex; align-items: center;
  min-height: 44px;                 /* standalone tap target */
  font-size: var(--t-small);
  color: var(--ink-muted);
  transition: color var(--dur-fast) var(--ease);
}
.footer__col a:hover, .footer__col a:focus-visible { color: var(--ink); }

/* the edge-cropped wordmark — both reference sites do this */
.wordmark {
  display: block;
  width: 100%;
  line-height: 1;                 /* was 0.78, which cropped the accents */
  padding-block: var(--s2) var(--s3);
  text-align: center;
}
.wordmark span {
  display: block;
  font-size: clamp(3.4rem, 17vw, 18rem);
  font-weight: 200;
  letter-spacing: -0.045em;
  text-transform: uppercase;
  white-space: nowrap;
  /* tracking is applied after the final glyph too, so centring lands half a
     step off without this — same correction as the hero wordmark */
  text-indent: -0.0225em;
}
.footer__base {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4);
  justify-content: space-between;
  padding-block: var(--s3);
  border-top: 1px solid var(--rule);
}

/* ---------- 13b. SCRATCH TRAIL + SOUND TOGGLE --------------------------- */
/* A canvas the visitor scratches light onto, mirroring how the work is made.
   Sits above the page background but beneath all content, and never
   intercepts a click. */
.scratch {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  display: block;
}
/* The canvas is at z-index 0, so content has to be lifted above it or the
   trail would draw over the text. */
.nav, main, .footer { position: relative; z-index: 1; }

.sound-toggle {
  position: fixed;
  left: 1rem;
  bottom: 1rem;
  z-index: 90;
  width: 44px; height: 44px;          /* full touch target */
  display: grid; place-items: center;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  color: var(--ink-muted);
  background: color-mix(in srgb, var(--canvas) 78%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.sound-toggle:hover, .sound-toggle:focus-visible { color: var(--ink); border-color: var(--ink-muted); }
.sound-toggle[aria-pressed='true'] { color: var(--ink); border-color: var(--ink); }
.sound-toggle svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.5; }
.sound-toggle .off-slash { display: block; }
.sound-toggle[aria-pressed='true'] .off-slash { display: none; }
.sound-toggle .on-waves { display: none; }
.sound-toggle[aria-pressed='true'] .on-waves { display: block; }

@media (prefers-reduced-motion: reduce) { .scratch { display: none; } }

/* ---------- 14. SCROLL REVEAL (0 KB, IntersectionObserver-driven) ------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal].is-revealed { opacity: 1; transform: none; }
/* if JS never runs, content must still be visible. Both the develop reveal and
   the rule draw start from a hidden state, so they need explicit resets here or
   the artwork and every section rule stay invisible forever. */
.no-js [data-reveal] { opacity: 1; transform: none; }
.no-js .work__img { clip-path: none; scale: 1; filter: none; }
.no-js .split-head__line { transform: none; }

/* ---------- 14b. NO-JS FALLBACK NOTICE ---------------------------------- */
.noscript-note {
  border: 1px solid var(--rule-strong);
  padding: var(--s3);
  margin-block: var(--s4);
  max-width: 62ch;
}
.noscript-note p { margin-bottom: 0.75rem; }
.noscript-note p:last-child { margin-bottom: 0; }
.noscript-note a { text-decoration: underline; text-underline-offset: 3px; }

/* ---------- 15. REDUCED MOTION ------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  [data-reveal] { opacity: 1 !important; transform: none !important; }
  .work__btn:hover .work__img { transform: none; }
  /* land every reveal on its finished state rather than animating */
  .work__img { clip-path: none !important; scale: 1 !important; filter: none !important; }
  .split-head__line { transform: none !important; }
  .split-head > span:not(.split-head__line) { clip-path: none !important; transform: none !important; }
}

/* ---------- 16. PAGE TRANSITIONS (progressive enhancement) -------------- */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation: vt-out 160ms var(--ease) both; }
  ::view-transition-new(root) { animation: vt-in 300ms var(--ease-out) both; }
}
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; transform: translateY(10px); } }

/* ---------- 17. PRINT ---------------------------------------------------- */
@media print {
  .nav, .lightbox, .work__cue, .footer__cols { display: none !important; }
  body { background: #fff; color: #000; }
  [data-reveal] { opacity: 1 !important; transform: none !important; }
}
