/* ============================================================
   Nick Roy — Portfolio landing
   Light editorial · warm paper · grotesque + italic serif
   ============================================================ */

:root {
  /* PORT NOTE: palette + font tokens now come from the theme (nr-type.css
     :root). The landing's duplicate tokens were renamed to the canonical
     theme names: --paper -> --colour-paper, --paper-alt -> --colour-rule-light,
     --ink -> --colour-ink, --text -> --colour-ink-2, --muted -> --colour-mid,
     --hairline -> --colour-rule, --font-sans -> --font-body,
     --font-serif -> --font-display. Only landing-specific tokens remain here. */

  --accent: #2e6f5e;                            /* deep Islay teal — kept (landing-only) */

  --gutter: clamp(20px, 4vw, 64px);             /* layout gutter — no theme equivalent, kept */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);   /* easing — no theme equivalent, kept */
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: auto; }

body {
  background: var(--colour-paper);
  color: var(--colour-ink-2);
  font-family: var(--font-body);
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Contain the landing's full-bleed motion (marquee etc.) here rather than on
   <body>: overflow-x on <body> sits between the sticky site-header and the
   viewport and breaks position: sticky (header gets cut off on scroll-up). */
.nr-landing {
  overflow-x: hidden;
  background: var(--colour-paper);   /* #F5F3EE */
}

/* Grain overlay */
body::after {
  content: "";
  position: fixed;
  inset: -50%;
  width: 200%;
  height: 200%;
  pointer-events: none;
  z-index: 999;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

em {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: inherit;
  letter-spacing: inherit;
}

/* Display type — Fraunces light at display optical size, as on the live site */
.hero__title,
.section-head__title,
.work__title,
.about__statement,
.about__stat-num,
.contact__cta {
  font-family: var(--font-display);
  font-variation-settings: "opsz" 144;
}

a { color: inherit; text-decoration: none; }

.nowrap { white-space: nowrap; }

/* Headings & display type use primary ink
   (marquee terms now colour themselves via .marquee__item) */
.hero__title,
.section-head__title,
.work__title,
.about__statement,
.about__stat-num,
.about__caps-list li,
.contact__cta,
.site-header {
  color: var(--colour-ink);
}

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

/* ------------------------------------------------------------
   Preloader
   ------------------------------------------------------------ */
.preloader {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: var(--colour-ink);
  color: var(--colour-paper);
  display: flex;
  align-items: center;
  justify-content: center;
}

.preloader__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(18px, 2.5vw, 28px);
  overflow: hidden;
}

.preloader__name {
  display: block;
  transform: translateY(115%);
  /* Fraunces at display optical size — same face as the site's footer wordmark */
  font-family: "Fraunces", Georgia, serif;
  font-optical-sizing: auto;
  font-weight: 300;
  font-size: clamp(3.2rem, 9vw, 7.5rem);
  letter-spacing: 0.005em;
  line-height: 1.1;
  color: var(--colour-paper);
  padding-bottom: 0.08em;
  margin-bottom: -0.08em;
}

.preloader__tag {
  font-size: clamp(0.7rem, 1.1vw, 0.8rem);
  color: rgba(245, 243, 238, 0.5);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  transform: translateY(200%);
}

.preloader__tag em {
  /* the accent word, lowercase Fraunces italic against the tracked caps */
  font-family: "Fraunces", Georgia, serif;
  font-style: italic;
  font-weight: 400;
  text-transform: lowercase;
  font-size: 1.5em;
  letter-spacing: 0.04em;
  color: rgba(245, 243, 238, 0.85);
}

/* ------------------------------------------------------------
   Header
   ------------------------------------------------------------ */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: clamp(16px, 2.4vw, 28px) var(--gutter);
  font-size: 0.95rem;
  font-weight: 500;
  mix-blend-mode: darken;
  transition: transform 0.5s var(--ease-out);
}

.site-header.is-hidden { transform: translateY(-110%); }

.site-header__logo img {
  height: 22px;
  width: auto;
  display: block;
}

.site-header__nav { display: flex; gap: clamp(16px, 3vw, 40px); }

.site-header__nav a {
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--colour-mid);
  transition: color 0.3s ease;
}

.site-header__nav a:hover { color: var(--colour-ink); }

.site-header__nav a,
.site-footer a,
.contact__links a {
  position: relative;
}

.site-header__nav a::after,
.site-footer a::after,
.contact__links a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -3px;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.45s var(--ease-out);
}

.site-header__nav a:hover::after,
.site-footer a:hover::after,
.contact__links a:hover::after {
  transform: scaleX(1);
  transform-origin: left;
}

.site-header__meta {
  color: var(--colour-mid);
  font-weight: 400;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* ------------------------------------------------------------
   Hero
   ------------------------------------------------------------ */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: calc(clamp(16px, 2.4vw, 28px) + 70px) var(--gutter) clamp(28px, 4vh, 56px);
}

.hero__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.hero__meta {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: auto;
  padding-top: clamp(24px, 5vh, 64px);
  font-size: 0.95rem;
}

.hero__status {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--colour-rule);
  border-radius: 999px;
  padding: 8px 16px;
  background: rgba(245, 243, 238, 0.6);
  backdrop-filter: blur(4px);
}

.hero__status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #3d8c4f;
  animation: pulse 2.2s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(61, 140, 79, 0.45); }
  50% { box-shadow: 0 0 0 7px rgba(61, 140, 79, 0); }
}

.hero__role {
  text-align: right;
  color: var(--colour-mid);
  line-height: 1.35;
}

.hero__title {
  position: relative;
  font-size: clamp(3.05rem, 11vw, 10.5rem);
  font-weight: 300;
  letter-spacing: -0.02em;
  line-height: 1.02;
  text-transform: none;
  margin: clamp(24px, 6vh, 80px) 0;
}

/* the italic line is all x-height — pull it up so the rhythm reads even */
.hero__title .line:last-child { margin-top: -0.2em; }

/* generous padding so descenders (g, y) aren't clipped by the mask */
.line { display: block; overflow: hidden; padding-bottom: 0.16em; margin-bottom: -0.16em; }
.line__inner { display: inline-block; will-change: transform; }

.hero__foot {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 40px;
}

.hero__intro {
  max-width: 46ch;
  font-size: clamp(1rem, 1.4vw, 1.2rem);
  line-height: 1.55;
  color: var(--colour-ink-2);
}

.hero__scroll {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  font-size: 0.8rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--colour-mid);
  flex-shrink: 0;
}

.hero__scroll-line {
  width: 1px;
  height: 48px;
  background: var(--colour-mid);
  transform-origin: top;
  animation: scrollLine 2s var(--ease-out) infinite;
}

@keyframes scrollLine {
  0% { transform: scaleY(0); transform-origin: top; }
  45% { transform: scaleY(1); transform-origin: top; }
  55% { transform: scaleY(1); transform-origin: bottom; }
  100% { transform: scaleY(0); transform-origin: bottom; }
}

/* ------------------------------------------------------------
   Marquee
   Slender Fraunces, as the display type uses it: opsz 144 at
   weight 300. Hover springs the wght / SOFT / WONK axes and the
   colour from nr-landing.js; opsz is held so the terms never
   narrow. Vertical padding is the headroom for the pop, which
   peaks near 1.21x once the spring overshoots.
   ------------------------------------------------------------ */
.marquee {
  overflow: hidden;
  border-top: 1px solid var(--colour-rule);
  border-bottom: 1px solid var(--colour-rule);
  padding: clamp(26px, 3vw, 40px) 0;
  white-space: nowrap;
}

.marquee__track { display: inline-flex; will-change: transform; }

.marquee__group {
  display: inline-flex;
  align-items: center;
  /* Wide enough that a term at peak scale clears the dots either side. */
  gap: clamp(34px, 3.8vw, 64px);
  padding-right: clamp(34px, 3.8vw, 64px);
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 2.3vw, 1.9rem);
  letter-spacing: -0.015em;
  line-height: 1.3;
  font-variation-settings: "opsz" 144, "wght" 300, "SOFT" 0, "WONK" 0;
}

/* Rests at --colour-mid and inks in as the dock factor rises, so a
   neighbour catching the edge of the falloff darkens with it. */
.marquee__item {
  display: inline-block;
  color: var(--colour-mid);
  color: color-mix(in oklab, var(--colour-mid), var(--colour-ink) calc(var(--nr-f, 0) * 100%));
}

.marquee__item > span { display: inline-block; will-change: transform; }

/* Unlike a transform, the SOFT and WONK axes change glyph widths and so
   reflow. JS locks each item at its rest size and adds .is-docked, which
   lifts the span out of flow; the morphing term then can't shove its
   neighbours along the strip. Applied on fine pointers only. */
.marquee.is-docked .marquee__item { position: relative; overflow: visible; }
.marquee.is-docked .marquee__item > span {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.marquee__item:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 6px;
}

.marquee__group i {
  font-style: normal;
  color: var(--accent);
  font-size: 0.4em;
  line-height: 1;
}

/* ------------------------------------------------------------
   Sections shared
   ------------------------------------------------------------ */
.section-head {
  padding: clamp(80px, 14vh, 160px) var(--gutter) clamp(32px, 6vh, 72px);
}

.section-head__label {
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--colour-mid);
  margin-bottom: 1.2em;
}

.section-head__label::before {
  content: "";
  display: inline-block;
  width: 32px;
  height: 1px;
  background: var(--accent);
  vertical-align: middle;
  margin-right: 14px;
}

.section-head__title {
  font-size: clamp(2.2rem, 5.5vw, 4.6rem);
  font-weight: 300;
  letter-spacing: -0.015em;
  line-height: 1.08;
}

/* ------------------------------------------------------------
   Work
   ------------------------------------------------------------ */
.work { position: relative; }
.work__stage { position: relative; }
.work__list { list-style: none; }

/* ------------------------------------------------------------
   Base (touch, narrow, reduced motion)

   Each project is a full-bleed image band with its title laid over it:
   the same language as the desktop stage, minus the pin. Pinning on iOS
   fights the address-bar resize, so scroll here is just scroll.
   ------------------------------------------------------------ */
.work__item {
  position: relative;
  background: var(--colour-ink);
}

.work__thumb {
  position: relative;
  display: block;
  margin: 0;
  aspect-ratio: 4 / 3;
  overflow: hidden;
}

.work__thumb img,
.work__thumb svg { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The Narracje source is the only portrait one, and its subject (the lit facade)
   sits low in the frame, so a centred crop would keep dark sky and cut the
   building. Anchor the crop low. Matched on the filename rather than position,
   so it holds if the running order changes. */
.work__thumb img[src*="Building-detail"] { object-position: 50% 85%; }

/* Scrim so the title reads whatever the source does. This stacked treatment is
   the reduced-motion fallback; the pinned stage below is what normally runs.
   The row sits in the bottom band, so that band takes a fixed floor plus the
   per-project strength: at Narracje's 0.20 a purely scaled band leaves white
   type on bright projection. */
.work__thumb { --scrim: 0.55; }

.work__thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(14, 13, 11, calc(0.5 + var(--scrim) * 0.6)) 0%,
    rgba(14, 13, 11, calc(0.28 + var(--scrim) * 0.5)) 30%,
    rgba(14, 13, 11, calc(var(--scrim) * 0.2)) 56%,
    rgba(14, 13, 11, 0) 74%
  );
}

/* The row sits over its own image, anchored to the bottom of the band. */
.work__row {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  grid-template-columns: 3.5rem 1fr;
  grid-template-areas:
    "index title"
    ".     cat";
  align-content: end;
  gap: 6px clamp(12px, 2vw, 32px);
  padding: var(--gutter);
  color: var(--colour-paper);
}

.work__index { grid-area: index; }
.work__title { grid-area: title; }
.work__cat   { grid-area: cat; }
.work__year  { display: none; }

.work__index {
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  color: rgba(245, 243, 238, 0.65);
  transition: color 0.4s var(--ease-out);
}

.work__title {
  font-size: clamp(1.6rem, 5vw, 2.6rem);
  font-weight: 300;
  letter-spacing: -0.01em;
  line-height: 1.1;
  /* Overrides the ink set by the display-type rule at the top of this file. */
  color: var(--colour-paper);
  transition: transform 0.45s var(--ease-out), color 0.45s var(--ease-out), opacity 0.45s var(--ease-out);
}

.work__cat {
  font-size: 0.9rem;
  color: rgba(245, 243, 238, 0.65);
  transition: color 0.4s var(--ease-out);
}

.work__media { display: none; }

/* Archive row — a peer of the projects, not a fifth project. Smaller title, no
   index number, and its eyebrow carries what the caption used to say. */
.work__item--more .work__index { opacity: 0.55; }

.work__item--more .work__title {
  font-size: clamp(1.05rem, 3.6vw, 1.5rem);
  font-weight: 400;
  letter-spacing: 0;
}

.work__item--more .work__cat { max-width: 44ch; }

/* The project rows are absolute overlays on their own image. The archive row
   has no image, so left as an overlay it has nothing to give it height and the
   row collapses to nothing. Put it back in flow, on its own ink band. */
.work__item--more .work__row {
  position: static;
  padding-block: clamp(28px, 6vh, 56px);
}

/* ------------------------------------------------------------
   Pinned full-bleed stage — desktop, motion allowed.

   The section fills the viewport and goes ink; the featured images crossfade
   behind the list as JS advances the active index with scroll. Scoped to a
   media query rather than a JS class so the first paint is already correct.

   Desktop only, and deliberately so. iOS resizes the viewport as Safari's bars
   collapse mid-scroll, and there is no way to opt out of it: svh and lvh are
   each correct in only one bar state, dvh lags the animation, and the pin
   freezes a pixel height at build time that none of them can correct
   afterwards. Anything sized to the viewport inherits that. The base layout
   below sizes to aspect ratio instead, so it cannot have the problem, and it is
   what phones get. Reduced motion falls back to the same place.
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: no-preference) and (min-width: 901px) {
  .work__stage {
    /* lvh, not svh: lvh is the toolbars-collapsed height, and plain 100vh is
       the same measurement, for anything without lvh. Desktop-only now, so
       neither unit has a mobile bar state to get wrong. */
    height: 100vh;
    height: 100lvh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: hidden;
    background: var(--colour-ink);
  }

  .work__media {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 0;
  }

  .work__media-item {
    --scrim: 0.55;
    position: absolute;
    inset: 0;
    margin: 0;
    opacity: 0;
    transform: scale(1.06);
    transition: opacity 1s var(--ease-out), transform 1.8s var(--ease-out);
  }

  .work__media-item.is-active { opacity: 1; transform: scale(1); }

  .work__media-item img,
  .work__media-item svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }

  /* The slide used to carry two defocused copies of the image over the sharp
     one, masked away through the middle, for a depth-of-field falloff that held
     the type off the picture. The glass rows do that now, so the falloff was
     blur under blur: removed here and in front-page.php, along with the SVG
     filter definitions it needed. */

  /* Vignette. Darkest at the edges, where the titles and the metadata sit, and
     open through the middle of the row where nothing is set. It lives on each
     slide rather than on the container, so --scrim can differ per image and the
     scrim crossfades with its own picture. */
  .work__media-item::after {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(
      115% 88% at 50% 50%,
      rgba(14, 13, 11, calc(var(--scrim) * 0.1)) 0%,
      rgba(14, 13, 11, calc(var(--scrim) * 0.45)) 40%,
      rgba(14, 13, 11, calc(var(--scrim) * 1)) 74%,
      rgba(14, 13, 11, calc(var(--scrim) * 1.2)) 100%
    );
  }

  .work__list {
    position: relative;
    z-index: 2;
    width: 100%;
  }

  .work__item {
    background: none;
    border: 0;
  }

  .work__row {
    position: static;
    display: grid;
    grid-template-columns: 3.5rem 1fr auto 5rem;
    grid-template-areas: none;
    align-items: baseline;
    align-content: normal;
    gap: clamp(12px, 2vw, 32px);
    padding: clamp(16px, 2.2vw, 30px) var(--gutter);
  }

  /* Each row as a lozenge of glass: not a flat pane laid over the image but a
     solid piece with thickness, thickest through the middle and tapering at the
     top and bottom edges. Same principle as the shelf in
     macklowe-viewer-v3.html, where the glass stays near clear through the
     centre and does its work at the rim.

     What the domed section changes, versus a flat strip:

     - Refraction ramps rather than banding. On a dome the path length through
       the glass grows steadily toward the tapered edge, so ::before's mask
       eases from clear at the centre to full at the edges instead of switching
       on at a boundary.
     - The body tint is a vertical gradient. A crown catches light and the base
       sits in its own shadow, so the ink is lightest at the top and deepest at
       the bottom.
     - The specular sits just inside the top edge, not on it, because the
       brightest point of a curved surface is where it turns toward the light,
       not at its boundary. A broad sheen falls away beneath it, and a fainter
       bounce catches the underside near the base.
     - Both edges carry a thin dark line where the glass turns away.

     The specular lines vary across the width so the highlight travels and falls
     off along the row. Held flat, a 2000px edge reads as a drawn rule.

     Three dials. --row-blur is edge thickness, character rather than
     legibility, since the centre stays clear. --row-core is the blur through
     the middle, which is what the type sits on, and raising it trades glass
     back for frost. --row-tint is the ink, contrast without softening. */
  .work__row {
    --row-tint: 0.1;
    --row-core: 3px;
    --row-blur: 10px;
    --row-edge: 0.35;
    position: relative;
    isolation: isolate;
    background: linear-gradient(
      to bottom,
      rgba(14, 13, 11, calc(var(--row-tint) * 0.5)) 0%,
      rgba(14, 13, 11, calc(var(--row-tint) * 0.82)) 40%,
      rgba(14, 13, 11, calc(var(--row-tint) * 1.45)) 100%
    );
    backdrop-filter: blur(var(--row-core)) saturate(100%) brightness(1.02);
    -webkit-backdrop-filter: blur(var(--row-core)) saturate(100%) brightness(1.02);
    transition: background 0.6s var(--ease-out), backdrop-filter 0.6s var(--ease-out);
  }

  /* Rim refraction, eased from the centre out so it reads as thickening glass
     rather than a blurred band with an edge of its own. */
  .work__row::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    /* Saturation and a touch of contrast, not just blur. Real glass gets its
       edge colour from path length: the longer the light travels through the
       tapered part, the more of the scene's own colour it carries. Blur alone
       refracts the image grey. This pulls the colour through, so each row picks
       up whatever is behind it and the edges differ per project without any of
       it being specified. */
    backdrop-filter: blur(var(--row-blur)) saturate(1.75) contrast(1.06);
    -webkit-backdrop-filter: blur(var(--row-blur)) saturate(1.75) contrast(1.06);
    -webkit-mask: linear-gradient(
      to bottom,
      #000 0%, rgba(0, 0, 0, 0.74) 11%, rgba(0, 0, 0, 0.36) 24%,
      rgba(0, 0, 0, 0.1) 36%, transparent 46%, transparent 54%,
      rgba(0, 0, 0, 0.1) 64%, rgba(0, 0, 0, 0.36) 76%,
      rgba(0, 0, 0, 0.74) 89%, #000 100%
    );
    mask: linear-gradient(
      to bottom,
      #000 0%, rgba(0, 0, 0, 0.74) 11%, rgba(0, 0, 0, 0.36) 24%,
      rgba(0, 0, 0, 0.1) 36%, transparent 46%, transparent 54%,
      rgba(0, 0, 0, 0.1) 64%, rgba(0, 0, 0, 0.36) 76%,
      rgba(0, 0, 0, 0.74) 89%, #000 100%
    );
    transition: backdrop-filter 0.6s var(--ease-out);
  }

  /* The lit surface: turn line, crown specular, sheen, underside bounce, base
     turn line. Listed top layer first, as backgrounds paint in reverse. */
  .work__row::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
      /* dispersion: the crown specular splits, cool on the leading edge and
         warm a pixel under it, the way a steep glass angle fans wavelengths
         apart. Alphas stay tiny; this is a fringe, not a colour. */
      linear-gradient(
        90deg,
        rgba(188, 222, 236, 0) 0%,
        rgba(188, 222, 236, calc(var(--row-edge) * 0.34)) 30%,
        rgba(188, 222, 236, calc(var(--row-edge) * 0.1)) 58%,
        rgba(188, 222, 236, calc(var(--row-edge) * 0.26)) 80%,
        rgba(188, 222, 236, 0) 100%
      ) 0 1px / 100% 1px no-repeat,
      linear-gradient(
        90deg,
        rgba(236, 206, 158, 0) 0%,
        rgba(236, 206, 158, calc(var(--row-edge) * 0.3)) 34%,
        rgba(236, 206, 158, calc(var(--row-edge) * 0.08)) 60%,
        rgba(236, 206, 158, calc(var(--row-edge) * 0.24)) 82%,
        rgba(236, 206, 158, 0) 100%
      ) 0 3.5px / 100% 1px no-repeat,
      /* crown specular, just inside the top edge */
      linear-gradient(
        90deg,
        rgba(245, 243, 238, 0) 0%,
        rgba(245, 243, 238, calc(var(--row-edge) * 0.5)) 11%,
        rgba(245, 243, 238, var(--row-edge)) 33%,
        rgba(245, 243, 238, calc(var(--row-edge) * 0.28)) 57%,
        rgba(245, 243, 238, calc(var(--row-edge) * 0.78)) 81%,
        rgba(245, 243, 238, 0) 100%
      ) 0 2px / 100% 1.5px no-repeat,
      /* where the glass turns away at the top */
      linear-gradient(
        90deg,
        rgba(14, 13, 11, 0) 0%,
        rgba(14, 13, 11, calc(var(--row-tint) * 2.4)) 30%,
        rgba(14, 13, 11, calc(var(--row-tint) * 2.4)) 70%,
        rgba(14, 13, 11, 0) 100%
      ) 0 0 / 100% 1px no-repeat,
      /* sheen falling off the crown */
      linear-gradient(
        to bottom,
        rgba(245, 243, 238, calc(var(--row-edge) * 0.14)) 0%,
        rgba(245, 243, 238, 0) 100%
      ) 0 3.5px / 100% 26% no-repeat,
      /* light wrapping the underside, warmed: it has travelled the length of the
         body to get there and carries the glass's own colour with it */
      linear-gradient(
        90deg,
        rgba(233, 208, 168, 0) 0%,
        rgba(233, 208, 168, calc(var(--row-edge) * 0.24)) 24%,
        rgba(233, 208, 168, calc(var(--row-edge) * 0.07)) 52%,
        rgba(233, 208, 168, calc(var(--row-edge) * 0.32)) 79%,
        rgba(233, 208, 168, 0) 100%
      ) 0 calc(100% - 3px) / 100% 1px no-repeat,
      /* and where it turns away at the base */
      linear-gradient(
        90deg,
        rgba(14, 13, 11, 0) 0%,
        rgba(14, 13, 11, calc(var(--row-tint) * 3)) 30%,
        rgba(14, 13, 11, calc(var(--row-tint) * 3)) 70%,
        rgba(14, 13, 11, 0) 100%
      ) 0 100% / 100% 1px no-repeat;
    transition: background 0.6s var(--ease-out);
  }

  /* The row's own cells sit above every glass layer. */
  .work__row > * { position: relative; z-index: 1; }

  .work__item.is-active .work__row {
    --row-tint: 0.24;
    --row-core: 7px;
    --row-blur: 22px;
    --row-edge: 0.85;
  }

  .work__index,
  .work__title,
  .work__cat,
  .work__year { grid-area: auto; }

  .work__year {
    display: block;
    font-size: 0.85rem;
    text-align: right;
    font-variant-numeric: tabular-nums;
  }

  .work__title { font-size: clamp(1.4rem, 3vw, 2.9rem); opacity: 0.4; }
  .work__index,
  .work__cat,
  .work__year { color: rgba(245, 243, 238, 0.4); }

  /* Active row — set by scroll position, not by the cursor. */
  .work__item.is-active .work__title { opacity: 1; transform: translateX(10px); }
  .work__item.is-active .work__index,
  .work__item.is-active .work__cat,
  .work__item.is-active .work__year { color: rgba(245, 243, 238, 0.75); }

  /* Hover and keyboard focus lift the row without hijacking the image, which
     stays under scroll control so the two inputs cannot fight. */
  .work__row:hover .work__title,
  .work__row:focus-visible .work__title { opacity: 1; }

  .work__thumb { display: none; }

  /* Archive row on the dark stage. Excluded from the scroll mapping, so it
     never takes the active state and reads at full strength throughout. */
  .work__item--more .work__title {
    font-size: clamp(1.05rem, 1.6vw, 1.45rem);
    opacity: 0.85;
  }
  .work__item--more .work__row:hover .work__title,
  .work__item--more .work__row:focus-visible .work__title {
    opacity: 1;
    transform: translateX(10px);
  }
}

/* ------------------------------------------------------------
   About
   ------------------------------------------------------------ */
.about__statement {
  padding: 0 var(--gutter);
  max-width: 26ch;
  font-size: clamp(1.7rem, 4.2vw, 3.6rem);
  font-weight: 300;
  letter-spacing: -0.015em;
  line-height: 1.16;
}

.about__statement .w { opacity: 0.12; transition: none; }

.about__stats {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(20px, 3vw, 48px);
  padding: clamp(56px, 10vh, 120px) var(--gutter) 0;
}

.about__stats li {
  border-top: 1px solid var(--colour-rule);
  padding-top: 18px;
}

.about__stat-num {
  font-size: clamp(2.6rem, 6vw, 5rem);
  font-weight: 300;
  letter-spacing: -0.02em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.about__stat-suffix {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1.3rem, 2.6vw, 2.2rem);
  color: var(--accent);
}

.about__stat-label {
  display: block;
  margin-top: 0.8em;
  font-size: 0.92rem;
  color: var(--colour-mid);
  line-height: 1.45;
  max-width: 30ch;
}

.about__caps {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: clamp(24px, 4vw, 64px);
  padding: clamp(72px, 12vh, 140px) var(--gutter) 0;
}

.about__caps-title {
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--colour-mid);
}

.about__caps-list { list-style: none; }

.about__caps-list li {
  display: flex;
  align-items: baseline;
  gap: 20px;
  border-bottom: 1px solid var(--colour-rule);
  padding: clamp(14px, 1.8vw, 22px) 0;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.2vw, 1.8rem);
  font-weight: 350;
  letter-spacing: -0.01em;
}

.about__caps-list li:first-child { border-top: 1px solid var(--colour-rule); }

.about__caps-list span {
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------
   Contact
   ------------------------------------------------------------ */
.contact {
  text-align: center;
  padding: clamp(120px, 22vh, 240px) var(--gutter) clamp(80px, 14vh, 160px);
}

.contact__kicker {
  font-size: clamp(1rem, 1.6vw, 1.3rem);
  color: var(--colour-mid);
  margin-bottom: 0.6em;
}

.contact__cta {
  display: inline-block;
  font-size: clamp(3.4rem, 13vw, 12rem);
  font-weight: 300;
  letter-spacing: -0.02em;
  line-height: 1.05;
}

.contact__cta em { transition: color 0.4s var(--ease-out); }
.contact__cta:hover em { color: var(--accent); }

/* The reveal line clips with overflow:hidden; the big italic "talk" glyph ink
   overhangs its advance box on the right, so give the clip box symmetric inline
   padding (keeps the CTA optically centred) to stop the "k" being cut off. */
.contact__cta .line { padding-inline: 0.16em; }

.contact__links {
  list-style: none;
  display: flex;
  justify-content: center;
  gap: clamp(20px, 4vw, 56px);
  margin-top: clamp(36px, 6vh, 72px);
  font-size: 1rem;
  font-weight: 500;
}

/* ------------------------------------------------------------
   Footer
   ------------------------------------------------------------ */
.site-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: clamp(20px, 3vh, 32px) var(--gutter);
  border-top: 1px solid var(--colour-rule);
  font-size: 0.88rem;
  color: var(--colour-mid);
}

.site-footer a { color: var(--colour-ink); font-weight: 500; }

/* ------------------------------------------------------------
   Reveal defaults (JS animates; hidden only when JS present)
   ------------------------------------------------------------ */
.js [data-reveal] { opacity: 0; transform: translateY(28px); }
.js .line__inner { transform: translateY(115%); }

/* ------------------------------------------------------------
   Responsive
   ------------------------------------------------------------ */
@media (max-width: 900px) {
  .about__stats { grid-template-columns: 1fr; gap: 28px; }
  .about__caps { grid-template-columns: 1fr; }
  .hero__role { display: none; }
}

@media (max-width: 700px) {
  .site-header__meta { display: none; }

  .hero__foot { flex-direction: column; align-items: flex-start; gap: 28px; }
  .hero__scroll { flex-direction: row; align-items: center; }
  .hero__scroll-line { width: 48px; height: 1px; animation: none; }

  /* No hover scaling on touch — reclaim the headroom */
  .marquee { padding: clamp(14px, 2vw, 22px) 0; }

  .site-footer { flex-wrap: wrap; }
}

/* ------------------------------------------------------------
   Reduced motion
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1; transform: none; }
  .js .line__inner { transform: none; }
  .hero__scroll-line, .hero__status-dot { animation: none; }
  .about__statement .w { opacity: 1; }
}
