/* ============================================================
   TENNYSON LOWRY — AVANT-GARDE MINIMALIST PORTFOLIO
   Absolute monochrome. Structural type. Asymmetric editorial grid.
   Motion register: quiet & surgical — one axis, one easing family.
   ============================================================ */

:root {
  --bg: #000000;
  --fg: #ffffff;
  --muted: #7a7a7a;
  --line: rgba(255, 255, 255, 0.18);
  --gutter: clamp(1.25rem, 4vw, 4rem);
  --font-display: "Anton", "Arial Narrow", sans-serif;
  --font-mono: "Space Mono", "Courier New", monospace;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}

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

html {
  scroll-behavior: smooth;
}

/* Lenis runs its own smooth scrolling. Leaving native smooth scroll on as
   well makes the two fight over scrollTop on every anchor jump, which reads
   as stuttering and overshoot — so hand scrolling entirely to Lenis when
   it is active. main.js sets this class. */
html.has-lenis {
  scroll-behavior: auto;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

/* Anchor targets clear the floating nav on the no-Lenis path. */
section[id],
footer[id] {
  scroll-margin-top: clamp(5rem, 12vh, 7rem);
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 0.875rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

::selection {
  background: var(--fg);
  color: var(--bg);
}

/* Custom cursor active (added by JS on hover-capable devices) */
body.has-cursor,
body.has-cursor a {
  cursor: none;
}

.mono {
  font-family: var(--font-mono);
}

.muted {
  color: var(--muted);
}

main {
  width: 100%;
}

/* ============================================================
   PRELOADER — mono counter, wipes up to reveal the site
   Shown only when JS runs (html.js); never for reduced motion.
   ============================================================ */

.loader {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: var(--bg);
  flex-direction: column;
  justify-content: space-between;
  padding: var(--gutter);
}

html.js .loader {
  display: flex;
}

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

.loader-head {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
}

.loader-count {
  font-family: var(--font-display);
  font-size: clamp(3rem, 8vw, 6rem);
  line-height: 1;
  letter-spacing: 0.02em;
  align-self: flex-start;
}

/* ============================================================
   CUSTOM CURSOR — wireframe circle, inverts over targets
   .cursor is a 0×0 point tracked to the pointer; ring + label
   center on it so size changes stay centered.
   ============================================================ */

.cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 9999;
  mix-blend-mode: difference;
  display: none;
}

body.has-cursor .cursor {
  display: block;
}

.cursor-ring {
  position: absolute;
  top: 0;
  left: 0;
  width: 28px;
  height: 28px;
  transform: translate(-50%, -50%);
  border: 1px solid var(--fg);
  border-radius: 50%;
  transition: width 0.35s var(--ease-out), height 0.35s var(--ease-out),
              background-color 0.35s ease;
}

.cursor.is-view .cursor-ring {
  width: 96px;
  height: 96px;
}

.cursor.is-link .cursor-ring {
  width: 44px;
  height: 44px;
  background: var(--fg);
}

.cursor-label {
  position: absolute;
  top: 0;
  left: 0;
  transform: translate(-50%, -50%);
  font-size: 0.625rem;
  letter-spacing: 0.22em;
  color: var(--fg);
  opacity: 0;
  transition: opacity 0.25s ease;
  white-space: nowrap;
}

.cursor.is-view .cursor-label {
  opacity: 1;
}

/* ============================================================
   KINETIC TYPE — line masks + per-letter spans (built by JS)
   ============================================================ */

.line-mask {
  display: block;
  overflow: hidden;
}

.ch {
  display: inline-block;
  will-change: transform;
}

/* ============================================================
   MARQUEE — outline text bands, velocity-coupled drift
   ============================================================ */

.marquee {
  overflow: hidden;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 0.35em 0;
}

.marquee-track {
  display: inline-flex;
  white-space: nowrap;
  will-change: transform;
}

.marquee-track span {
  display: inline-block;
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 6vw, 5rem);
  line-height: 1.1;
  text-transform: uppercase;
  color: transparent;
  -webkit-text-stroke: 1px var(--muted);
}

@supports not (-webkit-text-stroke: 1px black) {
  .marquee-track span {
    color: var(--muted);
  }
}

/* ============================================================
   SECTION 1 — HERO / SCREEN-FILLING DROP
   ============================================================ */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: var(--gutter);
  padding-top: clamp(6.5rem, 12vh, 8rem);    /* clears the floating nav */
}

/* ASCII character field — injected by ascii-hero.js, sits under the type.
   Centred on the headline (roughly 50%, 30% of the hero box) with a wide,
   mostly-opaque core so TENNYSON LOWRY has real density to invert against;
   only the far corners taper off, so it still reads as atmosphere rather
   than a flat rectangle. */
.ascii-field {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  -webkit-mask-image: radial-gradient(140% 120% at 50% 30%, #000 45%, rgba(0, 0, 0, 0.7) 75%, rgba(0, 0, 0, 0.35) 100%);
          mask-image: radial-gradient(140% 120% at 50% 30%, #000 45%, rgba(0, 0, 0, 0.7) 75%, rgba(0, 0, 0, 0.35) 100%);
}

/* Lift the hero's own content clear of the field. Both are flex items, so
   z-index applies without changing their layout. */
.hero-head,
.hero-foot {
  position: relative;
  z-index: 1;
}

.portrait,
.hero-motion {
  z-index: 1;
}

.portrait {
  position: absolute;
  left: var(--gutter);
  bottom: var(--gutter);
  width: clamp(84px, 9vw, 140px);
  aspect-ratio: 1;
  border: 1px solid var(--line);
  overflow: hidden;
}

.portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1);
  transition: filter 0.5s var(--ease-out);
}

.portrait:hover img {
  filter: grayscale(0);
}

.hero-motion {
  position: absolute;
  top: var(--gutter);
  right: var(--gutter);
  width: clamp(140px, 18vw, 260px);
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border: 1px solid var(--line);
}

.hero-name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(4rem, 15vw, 14rem);
  line-height: 0.84;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  /* Same treatment as .neo-vert-col / .wall-title: the ASCII field passes
     directly behind the name, and difference-blend inverts wherever a
     glyph lands under a letterform instead of just sitting on top of it. */
  mix-blend-mode: difference;
}

.hero-sub {
  margin-top: clamp(1.25rem, 3vh, 2.5rem);
  display: flex;
  align-items: baseline;
  gap: clamp(1rem, 4vw, 3rem);
  flex-wrap: wrap;
}

.hero-role {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 2.6vw, 2rem);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.hero-foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 2rem;
}

.hero-about {
  max-width: 44ch;
  margin-left: auto;
  margin-right: clamp(0rem, 8vw, 10rem);
  text-transform: uppercase;
  font-size: 0.8125rem;
}

/* Solid white pill. Currently unused — the hero has no CTA, since adding one
   overflows the hero past 100svh on short viewports and CONTACT already lives
   in the nav. Kept styled so the button can be dropped back in as one <a>. */
.hero-cta {
  display: inline-block;
  margin-top: clamp(1.75rem, 4.5vh, 2.75rem);
  padding: 1.05rem 2.6rem;
  border-radius: 50px;
  background: var(--fg);
  color: var(--bg);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-decoration: none;
  white-space: nowrap;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55);
  transition: transform 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out);
}

.hero-cta:hover {
  transform: scale(1.05);
  box-shadow: 0 20px 44px rgba(0, 0, 0, 0.7);
}

.hero-scroll {
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  white-space: nowrap;
}

/* ============================================================
   FLOATING GLASS NAV
   backdrop-filter samples whatever is painted behind it, so the
   ASCII field blurs into a soft haze inside the pill while the
   glyphs stay crisp right up to its edge.
   ============================================================ */

.site-nav {
  position: fixed;
  z-index: 90;
  top: clamp(0.75rem, 2.5vh, 1.5rem);
  /* Centred with auto margins, not translateX — the entry animation writes
     its own transform here and would otherwise wipe the centring. */
  left: 0;
  right: 0;
  margin-inline: auto;
  width: min(1100px, calc(100% - 2 * var(--gutter)));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 0.85rem 1.5rem;
  border-radius: 50px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.16);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
          backdrop-filter: blur(18px) saturate(140%);
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.5);
}

/* No backdrop-filter support: fall back to a solid pill rather than
   letting the glyphs run straight through the text. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-nav {
    background: rgba(0, 0, 0, 0.82);
  }
}

.site-nav a {
  font-family: var(--font-mono);
  text-decoration: none;
  color: rgba(255, 255, 255, 0.72);
  transition: color 0.3s var(--ease-out);
}

.site-nav a:hover {
  color: var(--fg);
}

.site-nav-logo {
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  color: var(--fg) !important;
}

.site-nav-links {
  display: flex;
  gap: clamp(1rem, 3vw, 2rem);
}

.site-nav-links a {
  font-size: 0.75rem;
  letter-spacing: 0.16em;
}

/* Colour easter egg: an invisible hit zone at the hero's mid-left, no
   label, no border, no fill — nothing marks it as interactive except
   finding it. Sized generously (a real target, just an unmarked one);
   focus-visible keeps it operable by keyboard without adding a hover
   affordance for the mouse. */
.color-egg {
  position: absolute;
  left: var(--gutter);
  /* Fallback only — ascii-hero.js measures the real gap between .hero-head
     and .portrait on load/resize and overrides top/width/height inline.
     This 70% just covers the instant before that runs. */
  top: 70%;
  transform: translateY(-50%);
  z-index: 2;
  width: clamp(64px, 8vw, 110px);
  height: clamp(64px, 8vw, 110px);
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  cursor: inherit;
}

.color-egg:focus-visible {
  outline: 1px solid var(--fg);
  outline-offset: 3px;
}

/* ============================================================
   PROJECTS — shared scaffolding
   ============================================================ */

.project {
  position: relative;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: clamp(0.75rem, 2vw, 1.5rem);
  padding: clamp(6rem, 18vh, 12rem) var(--gutter);
}

.p-meta {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  grid-column: 1 / -1;
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  border-top: 1px solid var(--line);
  padding-top: 0.75rem;
  margin-bottom: clamp(2.5rem, 6vh, 5rem);
}

.p-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(3rem, 9vw, 9rem);
  line-height: 0.86;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}

/* ---- Placeholder image slots (swap .ph-fill for <img> later) ---- */

.ph {
  position: relative;
}

.ph-fill {
  position: relative;
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  border: 1px solid var(--line);
  background-color: #050505;
  background-image:
    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.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)' opacity='0.16'/%3E%3C/svg%3E"),
    radial-gradient(ellipse at 30% 20%, rgba(255,255,255,0.06), transparent 60%);
}

/* corner ticks — registration-mark feel */
.ph-fill::before,
.ph-fill::after {
  content: "";
  position: absolute;
  width: 14px;
  height: 14px;
  pointer-events: none;
}

.ph-fill::before {
  top: 8px;
  left: 8px;
  border-top: 1px solid var(--fg);
  border-left: 1px solid var(--fg);
}

.ph-fill::after {
  bottom: 8px;
  right: 8px;
  border-bottom: 1px solid var(--fg);
  border-right: 1px solid var(--fg);
}

.ph-fill img,
.ph-fill video {
  /* .ph-fill is a centred grid; without an explicit stretch a media element
     that knows its own aspect ratio (via width/height attrs) sizes to that
     ratio instead of the box, and spills out the bottom. */
  place-self: stretch;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.ph-label {
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  color: var(--muted);
  text-align: center;
  padding: 0 1rem;
}

.ph figcaption {
  margin-top: 0.6rem;
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
}

/* ============================================================
   PROJECT 01 — NEO4IC
   Replaces the existing "PROJECT 01 — NEO4IC" block in styles.css
   (lines 577–637, from the banner comment down to and including the
   @media (max-width: 768px) rule). .p-vertical below it stays as-is.
   ============================================================ */

/* Full-bleed stack: the logotypes run edge to edge with only a hairline
   between them. No frames, ticks or captions — the marks are the content.
   The dense sheets lead at large size; single marks follow, paired where a
   band alone would sit thin. */

.p-neo4ic {
  display: block;
  position: relative;
  padding: clamp(6rem, 18vh, 12rem) 0;
  --neo-hairline: clamp(4px, 0.5vw, 8px);
}

/* Tighter than the site-wide .p-meta gap (clamp(2.5rem, 6vh, 5rem)) — the
   hero plate is a big, high-contrast mass right below it, and the full
   gap read as a dead pause before it rather than a breath. */
.p-neo4ic .p-meta {
  margin: 0 var(--gutter) clamp(1.25rem, 3vh, 2rem);
}

/* ---- Hero plate ----
   The only white ground on the site. Its top and bottom edges dissolve
   through a real halftone screen: the mask PNG is a 45deg dot lattice whose
   dot AREA ramps to zero at the edge, so the plate breaks up into shrinking
   dots the way a printed screen does. An alpha gradient here reads as a
   CSS fade; this reads as ink. The mask tiles on X (its 1496px width is a
   whole number of 11px cells) so the dots stay circular at any viewport. */
.neo-hero {
  position: relative;
  margin: 0 0 var(--neo-hairline);
  background: #fff;
  aspect-ratio: 1.504;
  overflow: hidden;
  -webkit-mask-image: url(assets/halftone-mask.png);
          mask-image: url(assets/halftone-mask.png);
  -webkit-mask-size: auto 100%;
          mask-size: auto 100%;
  -webkit-mask-repeat: repeat-x;
          mask-repeat: repeat-x;
}

.neo-hero img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.neo-stack {
  display: flex;
  flex-direction: column;
  gap: var(--neo-hairline);
}

/* The band masters are 1262x443 with the mark floating in empty black. Each
   --ar is the tightest centred crop that clips no ink in that particular
   file — measured per mark, not guessed — so the logotypes fill the band
   without a stroke ever being sliced by the frame. */
.neo-item {
  aspect-ratio: var(--ar, 3);
  overflow: hidden;
}

.neo-item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* Sheets carry dozens of marks at once — cropping one loses work, so they
   letterbox instead. The masters are black-ground, so the letterbox is
   invisible against the page. */
.neo-item--contain img {
  object-fit: contain;
}

.neo-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--neo-hairline);
}

.neo-pair .neo-item {
  min-width: 0;
}

/* The wordmark rides over the stack in difference blend, so it inverts
   against whichever mark is behind it instead of colliding with it. It
   starts below the hero plate — over the plate it would fight the chrome
   NEO4IC already there. */
.neo-vert-col {
  position: absolute;
  left: var(--gutter);
  top: calc(clamp(6rem, 18vh, 12rem) + 100vw / 1.504 + clamp(1.25rem, 3vh, 2rem));
  bottom: clamp(6rem, 18vh, 12rem);
  z-index: 3;
  pointer-events: none;
  mix-blend-mode: difference;
}

.neo-vert-col .p-vertical {
  position: sticky;
  top: clamp(7rem, 20vh, 11rem);
}

@media (max-width: 768px) {
  .neo-vert-col {
    display: none;
  }

  /* Cap the widest bands: a 4:1 crop at phone width collapses to a thin
     sliver, so let the tallest ratios relax and show more of the mark. */
  .neo-item {
    aspect-ratio: min(var(--ar, 3), 3);
  }

  /* Paired sheets stack rather than shrink to unreadable halves. */
  .neo-pair {
    grid-template-columns: 1fr;
  }
}

.p-vertical {
  position: sticky; /* quiet pin while the section scrolls past */
  top: clamp(4rem, 14vh, 8rem);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(3rem, 8vw, 8rem);
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
}






/* ============================================================
   PROJECT 02 — DESIGN (mismatched trio, async parallax)
   ============================================================ */

.p-design .p-title {
  grid-column: 7 / -1;
  grid-row: 2;
  text-align: right;
  z-index: 2;
  pointer-events: none;
}

/* ============================================================
   PROJECT 02 — SELECTED WORKS
   Filterable masonry with a lightbox, following the layout of the
   525 designs page. That page keys its hovers off an orange accent;
   here the accent role is white, to hold the monochrome system.
   ============================================================ */

.p-design {
  display: block;
  padding: clamp(6rem, 18vh, 12rem) var(--gutter);
}

.p-design .p-meta {
  margin-bottom: clamp(2.5rem, 6vh, 5rem);
}

.gallery-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 2rem;
  flex-wrap: wrap;
  margin-bottom: clamp(2.5rem, 7vh, 5rem);
}

.filter-bar {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.filter-btn {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.2em;
  padding: 0.5rem 1rem;
  border: 1px solid var(--line);
  background: transparent;
  color: rgba(255, 255, 255, 0.5);
  cursor: pointer;
  transition: color 0.3s var(--ease-out), border-color 0.3s var(--ease-out),
              background-color 0.3s var(--ease-out);
}

body.has-cursor .filter-btn {
  cursor: none;
}

.filter-btn:hover,
.filter-btn.is-active {
  color: var(--fg);
  border-color: rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.06);
}

.filter-btn:focus-visible {
  outline: 1px solid var(--fg);
  outline-offset: 3px;
}

.grid {
  columns: 3;
  column-gap: clamp(0.75rem, 2vw, 1.5rem);
}

@media (max-width: 1100px) {
  .grid { columns: 2; }
}

@media (max-width: 640px) {
  .grid { columns: 1; }
}

.card {
  display: block;
  position: relative;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  break-inside: avoid;
  margin-bottom: clamp(0.75rem, 2vw, 1.5rem);
  overflow: hidden;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

body.has-cursor .card {
  cursor: none;
}

.card[hidden] {
  display: none;
}

.card img {
  display: block;
  width: 100%;
  height: auto;
  filter: grayscale(20%) brightness(0.9);
  transition: transform 0.8s cubic-bezier(0.16, 1, 0.3, 1),
              filter 0.6s var(--ease-out);
}

.card:hover img,
.card:focus-visible img {
  transform: scale(1.06);
  filter: grayscale(0%) brightness(1);
}

.card-border {
  position: absolute;
  inset: 0;
  border: 1px solid transparent;
  transition: border-color 0.4s var(--ease-out);
  pointer-events: none;
}

.card:hover .card-border,
.card:focus-visible .card-border {
  border-color: rgba(255, 255, 255, 0.75);
}

.card-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: flex-start;
  text-align: left;
  padding: 1.25rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.9) 0%, transparent 55%);
  opacity: 0;
  transition: opacity 0.5s var(--ease-out);
  pointer-events: none;
}

.card:hover .card-overlay,
.card:focus-visible .card-overlay {
  opacity: 1;
}

.card-num {
  font-size: 0.625rem;
  letter-spacing: 0.2em;
  color: rgba(255, 255, 255, 0.65);
  margin-bottom: 0.3rem;
  transform: translateY(12px);
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.05s;
}

.card-title {
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--fg);
  transform: translateY(12px);
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

.card:hover .card-num,
.card:hover .card-title,
.card:focus-visible .card-num,
.card:focus-visible .card-title {
  transform: translateY(0);
}

/* ---- Lightbox ---- */

#lb {
  position: fixed;
  inset: 0;
  z-index: 20000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.97);
  -webkit-backdrop-filter: blur(20px);
          backdrop-filter: blur(20px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s var(--ease-out);
}

#lb.is-open {
  opacity: 1;
  pointer-events: auto;
}

#lb img {
  max-width: 90vw;
  max-height: 82vh;
  object-fit: contain;
  transform: scale(0.94);
  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

#lb.is-open img {
  transform: scale(1);
}

#lb button {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  padding: 0.5rem 1rem;
  border: 1px solid var(--line);
  background: transparent;
  color: rgba(255, 255, 255, 0.55);
  cursor: pointer;
  transition: color 0.3s var(--ease-out), border-color 0.3s var(--ease-out);
}

body.has-cursor #lb button {
  cursor: none;
}

#lb button:hover {
  color: var(--fg);
  border-color: rgba(255, 255, 255, 0.6);
}

#lb-close {
  position: absolute;
  top: clamp(1rem, 3vh, 2rem);
  right: clamp(1rem, 3vw, 2rem);
}

#lb-nav {
  position: absolute;
  bottom: clamp(1rem, 3vh, 2rem);
  left: 0;
  right: 0;
  margin-inline: auto;
  width: fit-content;
  display: flex;
  gap: 1rem;
  align-items: center;
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
}

/* ============================================================
   PROJECT 03 — ARCHIVE WALL
   Every piece at once, in columns drifting upward at different
   speeds. The title sits over the top in difference blend, so it
   inverts against whatever image is passing behind it.
   ============================================================ */

.p-wall {
  display: block;
  position: relative;
  padding: clamp(6rem, 18vh, 12rem) 0;
}

.p-wall .p-meta {
  margin: 0 var(--gutter) clamp(2.5rem, 6vh, 5rem);
}

.wall {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(0.5rem, 1.5vw, 1rem);
  height: 100svh;
  overflow: hidden;
  padding: 0 var(--gutter);
  /* Fade the ends so columns emerge and vanish rather than hard-cutting. */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 9%, #000 91%, transparent);
          mask-image: linear-gradient(to bottom, transparent, #000 9%, #000 91%, transparent);
}

.wall-col {
  overflow: hidden;
  min-width: 0;
}

.wall-track {
  display: flex;
  flex-direction: column;
  gap: clamp(0.5rem, 1.5vw, 1rem);
  will-change: transform;
}

.wall-track img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  background: #050505;
}

.wall-title {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  text-align: center;
  mix-blend-mode: difference;
  pointer-events: none;
}

@media (max-width: 900px) {
  /* Wrap to 2x2 rather than hiding columns — hiding two would drop half
     the archive off the page entirely. */
  .wall {
    grid-template-columns: repeat(2, 1fr);
    grid-auto-rows: 50svh;
  }
}

/* ============================================================
   PROJECT 01 — MURALS
   Full-bleed timelapse hero with a glass headline (the blur layer
   is clipped to the glyphs), then a horizontal track of walls.
   Block flow, not grid: the hero and track run edge to edge.
   ============================================================ */

.p-murals {
  display: block;
  padding: clamp(6rem, 18vh, 12rem) 0 0;
}

.p-murals .p-meta {
  display: flex;
  margin-left: var(--gutter);
  margin-right: var(--gutter);
}

/* ---- Hero ---- */

.mural-hero {
  position: relative;
  width: 100%;
  height: min(88svh, 900px);
  overflow: hidden;
  background: #050505;
}

.mural-hero-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.mural-hero-scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.55), transparent 42%, rgba(0, 0, 0, 0.25));
}

/* The glass itself: same recipe as the floating nav, but clipped to the
   letterforms instead of a pill, so the wall behind blurs inside the type. */
.mural-glass {
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: rgba(255, 255, 255, 0.07);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
          backdrop-filter: blur(18px) saturate(140%);
  -webkit-clip-path: url(#mural-glyph-clip);
          clip-path: url(#mural-glyph-clip);
}

/* No backdrop-filter: fall back to a solid tint so the word still reads. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .mural-glass {
    background: rgba(255, 255, 255, 0.22);
  }
}

.mural-hero-title {
  position: absolute;
  inset: 0;
  z-index: 4;
  margin: 0;
  pointer-events: none;
}

.mural-hero-title svg {
  display: block;
  overflow: visible;
}

/* Solid-type fallback for the clipped glass headline: clip-path: url() over
   backdrop-filter drops out in iOS Safari, leaving the word invisible there.
   Hidden by default; mobile.css shows it in place of the SVG version. */
.mural-hero-word {
  display: none;
}

/* x / y / font-size are written by murals.js from the hero box. */
.mural-glyph {
  font-family: var(--font-display);
  text-transform: uppercase;
}

.mural-hero-foot {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: clamp(1.25rem, 4vw, 2rem);
  z-index: 5;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 1.5rem;
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  mix-blend-mode: difference;
}

.mural-hero-toggle {
  appearance: none;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 50px;
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.2em;
  padding: 0.5rem 1rem;
  cursor: pointer;
  transition: border-color 0.3s var(--ease-out);
}

body.has-cursor .mural-hero-toggle,
body.has-cursor .mural-nav button {
  cursor: none;
}

.mural-hero-toggle:hover {
  border-color: var(--fg);
}

/* ---- Intro ---- */

.mural-intro {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: clamp(0.75rem, 2vw, 1.5rem);
  padding: clamp(3.5rem, 10vh, 7rem) var(--gutter) clamp(2.5rem, 6vh, 4rem);
}

.mural-intro p {
  grid-column: 1 / span 5;
  font-size: 0.9375rem;
  line-height: 1.75;
  color: #c9c9c9;
  text-wrap: pretty;
}

.mural-intro-note {
  grid-column: 9 / -1;
  align-self: end;
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-align: right;
}

/* ---- Horizontal track ---- */

.mural-track {
  display: flex;
  align-items: flex-end;
  gap: clamp(0.75rem, 2vw, 1.5rem);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  padding: 0 var(--gutter) 0.25rem;
  cursor: grab;
  outline: none;
  scrollbar-width: none;
}

.mural-track::-webkit-scrollbar {
  display: none;
}

.mural-track.is-dragging {
  cursor: grabbing;
}

.mural-frame {
  flex: 0 0 auto;
  scroll-snap-align: center;
}

.mural-shot {
  position: relative;
  height: min(66svh, 680px);
  aspect-ratio: var(--ar, 0.75);
  border: 1px solid var(--line);
  background: #050505;
  overflow: hidden;
}

.mural-shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.mural-frame figcaption {
  display: flex;
  justify-content: space-between;
  gap: 1.5rem;
  margin-top: 0.75rem;
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
}

.mural-track-tail {
  flex: 0 0 var(--gutter);
  height: 1px;
}

/* ---- Progress bar ---- */

.mural-bar {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2rem);
  margin: clamp(2rem, 5vh, 3rem) var(--gutter) 0;
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
}

.mural-rail {
  position: relative;
  flex: 1;
  height: 1px;
  background: var(--line);
}

.mural-rail-fill {
  position: absolute;
  top: -1px;
  left: 0;
  height: 3px;
  width: 0;
  background: var(--fg);
  transition: width 0.15s linear;
}

.mural-nav {
  display: flex;
  gap: 0.5rem;
}

.mural-nav button {
  appearance: none;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 50px;
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  padding: 0.4rem 0.85rem;
  cursor: pointer;
  transition: border-color 0.3s var(--ease-out);
}

.mural-nav button:hover {
  border-color: var(--fg);
}

.mural-marquee {
  margin-top: clamp(4rem, 10vh, 7rem);
}

@media (max-width: 900px) {
  .mural-intro p,
  .mural-intro-note {
    grid-column: 1 / -1;
    text-align: left;
  }

  .mural-intro-note {
    margin-top: 1.5rem;
  }

  .mural-shot {
    height: min(52svh, 520px);
  }
}

/* ============================================================
   SECTION 3 — BRUTALIST FOOTER BREAK
   ============================================================ */

.footer {
  padding: clamp(4rem, 12vh, 8rem) var(--gutter) clamp(2rem, 5vh, 3rem);
}

.rule {
  border: none;
  border-top: 1px solid var(--fg);
  margin-bottom: clamp(3rem, 10vh, 6rem);
}

.footer-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(4.5rem, 17vw, 16rem);
  line-height: 0.84;
  letter-spacing: -0.02em;
  text-transform: uppercase;
}

.footer-links {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.25rem;
  margin-top: clamp(3rem, 8vh, 5rem);
}

.footer-links a {
  color: var(--fg);
  text-decoration: none;
  font-size: 0.875rem;
  letter-spacing: 0.1em;
  border-bottom: 1px solid var(--line);
  transition: border-color 0.25s ease;
}

.footer-links a:hover {
  border-color: var(--fg);
}

.footer-mail {
  font-size: clamp(1rem, 2.4vw, 1.5rem) !important;
}

.footer-social {
  display: flex;
  gap: 2.5rem;
}

/* ---- link roll: duplicate label slides up on hover ---- */

.roll {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.roll-inner {
  display: block;
  transition: transform 0.5s var(--ease-out);
}

.roll-inner span {
  display: block;
}

.roll:hover .roll-inner {
  transform: translateY(-50%);
}

.colophon {
  margin-top: clamp(4rem, 10vh, 6rem);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
}

