/* ==========================================================================
   projects.css — KINETIC EDITORIAL MONOLITH · Selected Works
   ========================================================================== */

:root {
  --paper: #f1eee8;
  /* Leaf green. --accent is tuned for the warm paper ground; --accent-inv is
     the same hue lifted so it stays legible on the black interlude. */
  --accent: #2e5c3c;
  --accent-inv: #a8c9a4;
  --accent-soft: rgba(46, 92, 60, .12);
  --line: rgba(10, 10, 10, .12);
  --line-inv: rgba(255, 255, 255, .16);
  --e-power: cubic-bezier(.22, 1, .36, 1);

  /* Papercut collage */
  --pc-paper: #fdfcfa;
  --pc-edge: #e6e2d8;
  --pc-ink: #1d2a1e;
}

/* ============================= HERO =============================
   Papercut collage. Three photographs cut out of paper and pinned to the
   sheet. Every vertical rhythm is svh/clamp so the whole composition —
   copy, cluster, cue — occupies exactly one viewport at any screen size.
   ================================================================= */
.pj-hero {
  position: relative;
  height: 100vh;
  height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  padding: calc(var(--nav-h) + 2.4svh) clamp(20px, 6vw, 96px) 0;
  overflow: hidden;
  background:
    radial-gradient(120% 78% at 50% 4%, #faf8f4 0%, var(--paper) 58%, #e7e2d8 100%);
  isolation: isolate;
}

/* The clip paths live in a zero-size svg; they are geometry, not artwork. */
.pj-tear-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* ---- Copy + pinned cluster ------------------------------------------ */
.pj-hero-inner {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1.06fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(20px, 4vw, 64px);
  max-width: 1240px;
  width: 100%;
  margin: 0 auto;
  min-height: 0;
}

.pj-hero-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
}

/* ---- The board ------------------------------------------------------
   Cards are absolutely placed inside their own grid column, so they can
   never land on top of the copy at any window size. Each width is
   min(% of board, svh) — the percentage keeps a card inside the board on
   narrow windows, the svh term shrinks the whole cluster on short ones. */
.pj-board {
  position: relative;
  align-self: stretch;
  min-height: 0;
  height: 100%;
}

/* Pin-to-pin stitching thread. A phone-only element: the desktop cluster is
   wide and loose enough that a connector would read as clutter, but a vertical
   cascade needs something to carry the eye down. The `d` is empty until
   projects.js measures the real pin centres — there is no sensible static
   fallback for a path through three randomly placed pins. */
.pj-thread { display: none; }

.pj-cut {
  --pinx: 50%;
  --tear: url(#pj-tear-1);
  --tear-in: url(#pj-tear-1-in);
  position: absolute;
  margin: 0;
  transform-origin: var(--pinx) 0;
  will-change: transform;
}
.pj-cut-2 { --tear: url(#pj-tear-2); --tear-in: url(#pj-tear-2-in); --pinx: 33%; }
.pj-cut-3 { --tear: url(#pj-tear-3); --tear-in: url(#pj-tear-3-in); --pinx: 68%; }
/* The tear is clipped on the paper; the shadow lives one level up so it
   follows the torn silhouette instead of being clipped away with it
   (filter is applied before clip-path on the same element). */
.pj-cut-mount {
  display: block;
  position: relative;
  filter:
    drop-shadow(0 1px 0 rgba(255, 255, 255, .92))
    drop-shadow(0 9px 14px rgba(38, 52, 34, .2))
    drop-shadow(0 24px 38px rgba(38, 52, 34, .13));
}
/* Paper thickness: the same tear, a hair lower and tinted, so the torn edge
   shows the sheet's core the way a real hand-torn edge does. */
.pj-cut-mount::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  clip-path: var(--tear);
  background: #dcd7c9;
  transform: translate(1px, 2.5px);
}
.pj-cut-paper {
  display: block;
  position: relative;
  padding: clamp(8px, 1.3svh, 16px);
  padding-bottom: clamp(18px, 3svh, 36px);
  clip-path: var(--tear);
  background:
    linear-gradient(158deg, #fff 0%, var(--pc-paper) 44%, var(--pc-edge) 100%);
}
/* Paper tooth. Grayscale fractal noise, so it cannot introduce a warm cast. */
.pj-cut-paper::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: .5;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='.16'/%3E%3C/svg%3E");
}

/* height:auto — the photo keeps its natural ratio exactly. The inner tear is
   inset from the paper's, so the print reads as torn with it, and the frame
   still wraps the full image: nothing is cropped out of the composition. */
.pj-cut-img {
  display: block;
  position: relative;
  width: 100%;
  height: auto;
  clip-path: var(--tear-in);
}

.pj-cut-cap {
  margin-top: clamp(6px, 1.1svh, 12px);
  font-family: var(--sans);
  font-size: clamp(.52rem, .66vw, .6rem);
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}
.pj-cut-cap span {
  color: #6b7266;
  font-weight: 300;
}

/* ---- Pushpin ---------------------------------------------------------
   A domed head with an off-axis specular, a darker rim where the dome turns
   away, a metal collar under it, and a contact shadow cast down-right onto
   the paper. The pin sits at the card's transform-origin so the card swings
   from it, and --pinx moves both together. */
.pj-pin {
  --pin-d: clamp(13px, 1.9svh, 22px);
  position: absolute;
  top: calc(var(--pin-d) * -.42);
  left: var(--pinx, 50%);
  z-index: 4;
  width: var(--pin-d);
  height: var(--pin-d);
  margin-left: calc(var(--pin-d) / -2);
  border-radius: 50%;
  background:
    radial-gradient(circle at 34% 26%,
      #cfe3c8 0%, #8ab389 20%, var(--accent) 56%, #26492e 82%, #16301c 100%);
  box-shadow:
    inset 0 -1px 2px rgba(255, 255, 255, .3),
    inset 0 1px 1px rgba(255, 255, 255, .55),
    2px 5px 6px rgba(24, 38, 22, .34),
    5px 11px 14px rgba(24, 38, 22, .2);
}
/* Tight specular highlight — what actually sells the dome as plastic */
.pj-pin::before {
  content: '';
  position: absolute;
  top: 16%;
  left: 22%;
  width: 30%;
  height: 24%;
  border-radius: 50%;
  background: rgba(255, 255, 255, .82);
  filter: blur(.6px);
}
/* Metal collar emerging from under the dome, meeting the sheet. It sits almost
   entirely below the head — a negative z-index would NOT put it behind the
   dome (negative children still paint above their stacking context's own
   background), so the overlap is kept to a few percent instead. */
.pj-pin::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -28%;
  width: 26%;
  height: 32%;
  transform: translateX(-50%);
  border-radius: 0 0 42% 42%;
  background: linear-gradient(90deg, #6f7d6c, #e2e8dd 44%, #67755f);
  box-shadow: 1px 3px 3px rgba(24, 38, 22, .36);
}

/* Cascade: landscape top-left, portrait stepping down the right, the third
   below-left so the cluster reads as hand-placed, not gridded. The columns
   are deliberately NON-overlapping — card 3 used to reach across and bury
   card 2's caption, and z-order alone is a fragile way to protect text. */
/* transform-origin tracks --pinx so a card always swings from its own pin,
   wherever the pin was randomly placed. The rotate() values are a static
   fallback; projects.js overrides them with a fresh random tilt each load. */
.pj-cut-1 {
  left: 0;
  top: 0;
  width: min(50%, 28svh);
  transform: rotate(-3.2deg);
  z-index: 1;
}
.pj-cut-2 {
  left: 54%;
  top: 17%;
  width: min(38%, 21svh);
  transform: rotate(2.6deg);
  z-index: 3;
}
.pj-cut-3 {
  left: 5%;
  top: 58%;
  width: min(44%, 25svh);
  transform: rotate(-1.8deg);
  z-index: 2;
}

.pj-eyebrow {
  font-family: var(--sans);
  font-size: clamp(.56rem, .7vw, .64rem);
  font-weight: 500;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 clamp(12px, 2.2svh, 30px);
}
.pj-eyebrow .pj-char {
  display: inline-block;
  will-change: transform, opacity;
}

/* Title sizing is capped by viewport HEIGHT as well as width (the svh term),
   so a short laptop window can never push the cluster off-screen. The vw cap
   is tuned to the copy COLUMN, not the page — the hero is two columns now. */
.pj-hero-title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.6rem, min(6.6vw, 13svh), 7.2rem);
  line-height: .92;
  letter-spacing: -.02em;
  margin: 0;
  color: #1d2a1e;
}
.pj-line {
  display: block;
  overflow: hidden;
}
.pj-line:last-child {
  padding-left: clamp(16px, 4vw, 64px);
  font-style: italic;
  color: var(--accent);
}
.pj-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}
.pj-word-in {
  display: inline-block;
  will-change: transform;
}

.pj-hero-sub {
  font-family: var(--sans);
  font-size: clamp(.86rem, 1.15vw, 1.08rem);
  font-weight: 300;
  line-height: 1.65;
  color: #4c5348;
  max-width: 42ch;
  margin: clamp(16px, 3svh, 40px) 0 0;
}

.pj-hero-meta {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 26px);
  margin-top: clamp(16px, 3svh, 40px);
  padding-top: clamp(12px, 2svh, 22px);
  border-top: 1px solid var(--line);
  font-family: var(--sans);
  font-size: clamp(.6rem, .75vw, .68rem);
  text-transform: uppercase;
  letter-spacing: .16em;
  color: #5c6357;
}
.pj-meta-item {
  display: inline-flex;
  align-items: baseline;
  gap: .5em;
}
.pj-meta-num {
  font-family: var(--serif);
  font-size: 1.1rem;
  color: var(--accent);
  letter-spacing: 0;
}
.pj-meta-rule {
  flex: 1;
  height: 1px;
  background: var(--line);
}
.pj-meta-typo {
  color: #1d2a1e;
  opacity: .72;
}

.pj-scroll-cue {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding-bottom: clamp(12px, 2.4svh, 30px);
}
/* The cue sits on the paper gradient, not on a dark plate — white was left
   over from a dark hero and measured 1.24:1 against rgb(230,225,215), i.e.
   the word did not render at all. Ink, not paper. */
.pj-scroll-txt {
  font-family: var(--sans);
  font-size: .58rem;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: rgba(46, 66, 48, .78);
}
.pj-scroll-line {
  width: 1px;
  height: clamp(24px, 4svh, 46px);
  background: linear-gradient(to bottom, rgba(46, 66, 48, .8), transparent);
  transform-origin: top;
  animation: pjCueDrop 2.2s var(--ease-out-expo) infinite;
}
@keyframes pjCueDrop {
  0% { transform: scaleY(0); opacity: 0; }
  40% { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1); opacity: 0; transform-origin: bottom; }
}
/* ======================= FEATURED SPOTLIGHT ======================= */
.pj-spotlight {
  position: relative;
  padding: clamp(70px, 11vw, 150px) clamp(20px, 6vw, 96px) clamp(40px, 6vw, 80px);
  max-width: 1500px;
  margin: 0 auto;
  overflow: hidden;
}
.pj-spot-link {
  display: block;
  position: relative;
  z-index: 1;
  text-decoration: none;
  color: inherit;
}
/* NO CROPPING on this page. The frame carries the featured image's own 3:2
   ratio (hridayam-02 is exactly 6927x4618), so `contain` fits it edge to edge
   with neither a crop nor a letterbox bar.
   Deliberately NO max-height: any height cap would shrink the frame off the
   image's ratio, and `contain` would then letterbox inside it. */
.pj-spot-media {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: 6px;
  background: #e7e2d8;
  will-change: clip-path;
}
.pj-spot-media-inner {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
.pj-spot-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  will-change: transform;
  transition: transform 1s var(--e-power);
}
.pj-spot-link:hover .pj-spot-img {
  transform: scale(1.02);
}
.pj-spot-grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--line-inv) 1px, transparent 1px),
    linear-gradient(90deg, var(--line-inv) 1px, transparent 1px);
  background-size: 25% 33.33%;
  opacity: .5;
  mix-blend-mode: overlay;
}
.pj-spot-caption {
  position: relative;
  margin-top: -2.6em;
  margin-left: clamp(16px, 4vw, 64px);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}
.pj-spot-label {
  font-family: var(--sans);
  font-size: .64rem;
  font-weight: 500;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--white);
  background: rgba(10, 10, 10, .5);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 8px 16px;
  border-radius: 30px;
  border: 1px solid rgba(255, 255, 255, .18);
  margin-bottom: 14px;
}
.pj-spot-title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(3rem, 9vw, 8rem);
  line-height: .95;
  letter-spacing: -.02em;
  margin: 0;
  color: var(--black);
}
.pj-spot-sub {
  font-family: var(--sans);
  font-size: .8rem;
  font-weight: 300;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--grey);
  margin: 14px 0 0;
}
.pj-spot-cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: clamp(20px, 3vw, 30px);
  font-family: var(--sans);
  font-size: .74rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--black);
}
.pj-spot-cta-txt {
  position: relative;
  padding-bottom: 4px;
}
.pj-spot-cta-txt::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .5s var(--e-power);
}
.pj-spot-link:hover .pj-spot-cta-txt::after {
  transform: scaleX(1);
}
.pj-spot-cta-arrow {
  color: var(--accent);
  transition: transform .4s var(--e-power);
}
.pj-spot-link:hover .pj-spot-cta-arrow {
  transform: translateX(8px);
}
/* ======================= FILTER / INDEX ======================= */
.pj-filter-wrap {
  position: relative;
  z-index: 10;
  max-width: 1400px;
  margin: clamp(40px, 7vw, 90px) auto 0;
  padding: 0 clamp(20px, 6vw, 96px);
  display: flex;
  flex-direction: column;
  gap: 20px;
}
.pj-filter-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--line);
}
.pj-filter-eyebrow {
  font-family: var(--sans);
  font-size: .62rem;
  font-weight: 500;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--grey);
}
.pj-filter-count {
  font-family: var(--serif);
  font-size: clamp(1.4rem, 2.6vw, 2.2rem);
  color: var(--black);
  line-height: 1;
}
.pj-fc-num { color: var(--accent); }
.pj-fc-sep { opacity: .35; margin: 0 .1em; }
.pj-fc-total { opacity: .5; }

.pj-filter {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  align-self: flex-start;
}
.pj-filter-pill {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  border-radius: 40px;
  background: var(--accent-soft);
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: transform .55s var(--e-power), width .55s var(--e-power), opacity .3s;
}
.pj-filter-pill.ready { opacity: 1; }
.pj-filter-btn {
  position: relative;
  z-index: 1;
  background: transparent;
  border: none;
  font-family: var(--sans);
  font-size: .78rem;
  font-weight: 400;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: .7rem 1.3rem;
  min-height: 44px;
  border-radius: 40px;
  cursor: pointer;
  color: var(--grey);
  transition: color .35s var(--e-power);
}
.pj-filter-btn:hover { color: var(--black); }
.pj-filter-btn.active {
  color: var(--accent);
  font-weight: 500;
}

/* ========================= WORKS GRID ========================= */
.pj-grid-section {
  padding: clamp(36px, 5vw, 60px) clamp(20px, 6vw, 96px) clamp(80px, 12vw, 140px);
  max-width: 1400px;
  margin: 0 auto;
}
.pj-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(2.4rem, 4vw, 4rem) clamp(1.6rem, 3vw, 2.6rem);
  align-items: start;
}

.pj-card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  position: relative;
  will-change: transform, opacity;
}
.pj-card.is-hidden { display: none !important; }

/* Column spans only — the media frame takes its ratio from the image itself
   (see .pj-card-media below), so no image is ever cropped to fit a box. */
.pj-span-7 { grid-column: span 7; }
.pj-span-6 { grid-column: span 6; }
.pj-span-5 { grid-column: span 5; margin-top: clamp(0px, 4vw, 60px); }
/* Portrait sources get a narrower column so their full height stays sane
   without ever capping (and therefore letterboxing) the frame. */
.pj-portrait { grid-column: span 4; }

/* NO CROPPING: the frame has no fixed aspect-ratio and no height cap. The
   <img> flows at its natural ratio and the frame wraps it, so every project
   photo is shown whole — portrait, square, or wide. A max-height here would
   be self-defeating: it shrinks the frame off the image's ratio and `contain`
   letterboxes inside it. Tall portraits are kept reasonable by giving them a
   NARROW column instead (see .pj-portrait below). */
.pj-card-media {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: 6px;
  background: #e7e2d8;
  will-change: clip-path;
  line-height: 0;
}
.pj-card-img {
  width: 100%;
  height: auto;
  display: block;
  transition: transform .8s var(--e-power);
}
.pj-card:hover .pj-card-img { transform: scale(1.03); }

.pj-card-tag {
  position: absolute;
  top: 14px;
  left: 14px;
  z-index: 2;
  font-family: var(--sans);
  font-size: .62rem;
  font-weight: 400;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: #fff;
  background: rgba(10, 10, 10, .42);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 6px 12px;
  border-radius: 30px;
  border: 1px solid rgba(255, 255, 255, .18);
}

.pj-card-foot {
  padding-top: 1.2rem;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  position: relative;
  border-top: 1px solid var(--line);
  margin-top: 1.1rem;
}
.pj-card-text {
  position: relative;
  padding-bottom: 4px;
}
.pj-card-text::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .55s var(--e-power);
}
.pj-card:hover .pj-card-text::after { transform: scaleX(1); }
.pj-card-title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  line-height: 1.1;
  margin: 0 0 .4rem;
  color: var(--black);
  transition: color .3s;
}
.pj-card:hover .pj-card-title { color: var(--accent); }
.pj-card-sub {
  font-family: var(--sans);
  font-size: .72rem;
  font-weight: 300;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--grey);
  margin: 0;
}
.pj-card-arrow {
  flex-shrink: 0;
  font-size: 1.15rem;
  color: var(--accent);
  opacity: .5;
  transition: opacity .3s, transform .3s var(--e-power);
}
.pj-card:hover .pj-card-arrow {
  opacity: 1;
  transform: translateX(6px);
}

.pj-grid-empty {
  grid-column: 1 / -1;
  text-align: center;
  font-family: var(--sans);
  font-size: .85rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--grey);
  padding: 40px 0;
}
/* ====================== SIGNATURE INTERLUDE ======================
   Studio philosophy, the last full-bleed plate before the close. It still
   claims exactly one viewport — label, quote, 3 metrics, CTA all fit at any
   screen size via svh + clamp — but it is no longer the end of the page;
   footer.close follows it. Its own CTA and the close's contact block are
   deliberately not the same offer: this one invites, the close transacts.
   =============================================== */
.pj-manifesto {
  position: relative;
  background: var(--black);
  color: #fff;
  padding: 0 clamp(20px, 6vw, 96px);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  height: 100svh;
}
.pj-manifesto-bg {
  position: absolute;
  left: 0;
  right: 0;
  top: -14%;
  height: 128%;
  background-image: url('/assets/projects/hridayam/hridayam-05.webp');
  background-size: cover;
  background-position: center;
  opacity: .2;
  filter: grayscale(100%);
  will-change: transform;
}
.pj-manifesto-veil {
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at center, rgba(10, 10, 10, .45) 0%, rgba(10, 10, 10, .92) 100%);
}
.pj-manifesto-inner {
  position: relative;
  z-index: 2;
  max-width: 900px;
  margin: 0 auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(16px, 2.8svh, 32px);
}
.pj-manifesto-label {
  font-family: var(--sans);
  font-size: .66rem;
  font-weight: 500;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .55);
}
/* No .pj-manifesto-arch — removed. */
.pj-manifesto-quote {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.5rem, 3.4vw + 1svh, 3.2rem);
  line-height: 1.32;
  margin: 0;
  color: #fff;
}
.pj-manifesto-quote .pj-word,
.pj-manifesto-quote .pj-word-in { line-height: 1.32; }
.pj-manifesto-metrics {
  display: flex;
  justify-content: center;
  gap: clamp(20px, 5vw, 56px);
  width: 100%;
  padding-top: clamp(16px, 2.6svh, 36px);
  border-top: 1px solid var(--line-inv);
}
.pj-metric {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.pj-metric-num {
  font-family: var(--serif);
  font-size: clamp(2rem, 4.2vw, 3.2rem);
  line-height: 1;
  color: var(--accent-inv);
}
.pj-metric-lbl {
  font-family: var(--sans);
  font-size: .66rem;
  font-weight: 300;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .55);
}
.pj-manifesto-cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: clamp(8px, 1.6svh, 16px);
  font-family: var(--sans);
  font-size: .76rem;
  font-weight: 400;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: #fff;
  text-decoration: none;
  padding: 14px 32px;
  border-radius: 40px;
  border: 1px solid rgba(255, 255, 255, .26);
  background: rgba(255, 255, 255, .04);
  transition: background .4s var(--e-power), color .4s var(--e-power), border-color .4s;
}
.pj-manifesto-cta:hover {
  background: #fff;
  color: var(--black);
  border-color: #fff;
}
.pj-manifesto-cta-txt {
  display: inline-block;
}
.pj-manifesto-cta-arrow {
  transition: transform .4s var(--e-power);
}
.pj-manifesto-cta:hover .pj-manifesto-cta-arrow {
  transform: translateX(6px);
}

/* ======================== RESPONSIVE ======================== */
@media (max-width: 1024px) {
  .pj-grid { grid-template-columns: repeat(2, 1fr); gap: 3rem 2rem; }
  .pj-span-7, .pj-span-6, .pj-span-5 { grid-column: span 1; }
  .pj-span-5 { margin-top: 0; }
}

/* Below this the copy column is too narrow to sit beside the cluster, so the
   board drops underneath and the cards fan out in a row instead. No negative
   margins here: overlapping the cards also overlapped their captions, and a
   caption buried under the next photo is worse than a looser fan. */
@media (max-width: 900px) {
  .pj-hero-inner {
    grid-template-columns: 1fr;
    align-content: center;
    gap: clamp(14px, 3svh, 30px);
  }
  .pj-board {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    gap: clamp(10px, 3vw, 30px);
    height: auto;
    align-self: auto;
  }
  .pj-cut { position: static; flex: 0 0 auto; }
  .pj-cut-1 { width: min(30%, 19svh); }
  .pj-cut-2 { width: min(28%, 18svh); margin-top: 6%; }
  .pj-cut-3 { width: min(30%, 19svh); margin-top: 12%; }
}

@media (max-width: 768px) {
  .pj-line:last-child { padding-left: 0; }
  .pj-filter { align-self: stretch; }
  .pj-filter-btn { padding: .6rem 1rem; font-size: .72rem; }
  .pj-spot-caption { margin-left: 0; margin-top: 22px; }
  .pj-manifesto-metrics { gap: 18px; }
}

@media (max-width: 480px) {
  .pj-hero { padding-top: calc(var(--nav-h) + 1.6svh); }
  .pj-hero-sub { font-size: .8rem; line-height: 1.5; margin-top: clamp(12px, 2svh, 20px); }
  .pj-hero-meta { flex-wrap: wrap; gap: 6px 14px; margin-top: clamp(12px, 2svh, 20px); padding-top: 10px; }
  .pj-meta-rule { display: none; }
  /* The captions would wrap into the photo above them at this width */
  .pj-cut-cap span { display: none; }
  .pj-grid { grid-template-columns: 1fr; gap: 2.6rem; }
  .pj-spot-title { mix-blend-mode: normal; font-size: clamp(2.6rem, 12vw, 4rem); }
  .pj-filter-count { font-size: 1.3rem; }
  /* Metrics stay in a row so the section still fits one viewport —
     stacking them vertically here used to push the CTA off-screen. */
  .pj-manifesto-metrics { gap: 10px; }
  .pj-metric-num { font-size: clamp(1.5rem, 7vw, 2rem); }
  .pj-metric-lbl { font-size: .52rem; letter-spacing: .08em; }
  .pj-manifesto-label { font-size: .58rem; letter-spacing: .22em; }
  .pj-manifesto-cta { padding: 12px 22px; font-size: .68rem; }
}

/* Short landscape windows: keep the hero and the closing section on one screen */
@media (max-height: 620px) {
  .pj-hero-sub { display: none; }
  .pj-manifesto-quote { font-size: clamp(1.1rem, 2.6vw, 1.8rem); }
  .pj-metric-num { font-size: clamp(1.4rem, 3.4vw, 2rem); }
  .pj-manifesto-cta { padding: 10px 22px; }
}

/* Short AND narrow — the cluster has to give up the third card entirely */
@media (max-width: 900px) and (max-height: 620px) {
  .pj-cut-3 { display: none; }
  .pj-cut-1 { width: min(38%, 22svh); }
  .pj-cut-2 { width: min(34%, 20svh); }
}

/* ============================================================================
   PHONES ONLY — the vertical cascade
   ============================================================================
   Everything above 600px keeps the layouts already defined; this block is the
   last word for phones and deliberately re-declares (rather than edits) the
   <=900px rules, so the tablet fan is untouched.

   Why a different composition instead of a scaled-down one: the <=900px rule
   lays the three cutouts out as a centred row, which on a 390px screen is
   three ~115px stamps side by side — the widest thing in the layout is the
   text, and the photography, which is the actual subject, is the smallest.
   A portrait viewport wants the opposite. So on phones the board reclaims all
   the leftover vertical space, the cards go back to absolute placement and
   descend it in a left/right/left zig-zag at 2-3x the area, and a stitched
   thread runs pin to pin to carry the eye down.

   Fit is guaranteed by container query units, not by arithmetic. `.pj-board`
   becomes a size container, so `Ncqh` is N% of whatever height is actually
   left over after the copy — which is what makes one composition hold at both
   390x844 and 360x640 without a per-device breakpoint. Widths stay
   `min(%, Ncqh)`: the % keeps a card from spanning the screen on a wide phone,
   the cqh keeps it from outgrowing the board on a short one, and because only
   width is ever set, `height:auto` still derives every height from the image's
   own ratio — nothing is cropped. */
@media (max-width: 600px) {
  .pj-hero {
    padding-left: clamp(18px, 5.6vw, 26px);
    padding-right: clamp(18px, 5.6vw, 26px);
  }

  /* `display: contents` promotes the copy's children to grid items of the
     hero, which is what lets the board sit BETWEEN the title and the
     supporting text. Without it the board can only ever come before or after
     the whole copy block, and the image cascade stops being the centre of
     gravity. No semantics are lost — .pj-hero-copy is a plain wrapper. */
  .pj-hero-inner {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto 1fr auto auto;
    align-content: stretch;
    align-items: stretch;
    gap: 0;
  }
  .pj-hero-copy { display: contents; }
  .pj-eyebrow { grid-row: 1; margin-bottom: clamp(10px, 1.6svh, 16px); }
  .pj-hero-title { grid-row: 2; }
  .pj-board { grid-row: 3; }
  .pj-hero-sub { grid-row: 4; }
  .pj-hero-meta { grid-row: 5; }

  /* The peak: the wordmark is the largest thing on the screen, and the second
     line keeps its indent (the <=768px rule flattens it) so the two lines
     still step. */
  .pj-hero-title { font-size: clamp(2.9rem, 14.5vw, 4.4rem); line-height: .9; }
  .pj-line:last-child { padding-left: 12%; }

  .pj-hero-sub {
    max-width: none;
    margin-top: clamp(10px, 1.6svh, 16px);
    font-size: .78rem;
    line-height: 1.5;
  }
  /* wrap is declared here, not only at <=480px: between 481 and 600 the row
     is still narrower than the count plus the four typologies. */
  .pj-hero-meta {
    flex-wrap: wrap;
    margin-top: clamp(10px, 1.5svh, 15px);
    padding-top: 9px;
    gap: 4px 12px;
  }
  .pj-meta-rule { display: none; }
  .pj-meta-num { font-size: 1rem; }

  /* The hero's own grid is `1fr auto`, so the cue row is only as tall as its
     content and sat hard against the meta line — in a real render "SCROLL"
     landed on top of the typology list. The gap is the fix; the word also
     comes down to a hairline mark, since a phone user needs no instruction
     that the page scrolls. */
  .pj-scroll-cue {
    gap: 5px;
    padding-top: clamp(9px, 1.6svh, 16px);
    padding-bottom: clamp(8px, 1.6svh, 18px);
  }
  .pj-scroll-txt { font-size: .5rem; letter-spacing: .26em; opacity: .78; }
  .pj-scroll-line { height: clamp(18px, 2.6svh, 28px); }

  /* ---- the board -------------------------------------------------------
     container-type: size needs a definite height, which it has: the hero is
     100svh with rows 1fr/auto, the inner grid's third row is 1fr, so this
     box is fully resolved before its children are laid out.

     margin-top is not decoration. `line-height: .9` means the title's painted
     glyphs overhang its own block box (`.pj-line`'s overflow:hidden clips the
     paint, but the glyph geometry is still there), and a card's box reaches
     ABOVE its `top` — the pin hangs over the edge and rotating about a point
     on the top edge lifts one corner. Both eat into the same few pixels, so
     the gap is what keeps the cascade off the wordmark. */
  .pj-board {
    display: block;
    position: relative;
    height: auto;
    min-height: 0;
    margin-top: clamp(12px, 2.2svh, 22px);
    align-self: stretch;
    container-type: size;
  }

  .pj-cut { position: absolute; flex: none; margin: 0; }

  /* Insets, not flush edges, and columns that do not touch.
     A card's client box is the AABB of rotated content: `w·cos + h·sin`, and
     the sway adds up to ~3deg on top of the resting tilt plus 2.5px of x
     drift. On the tall portrait that is ~14px of width the declared value
     doesn't show. The left column therefore stops around 55% and the right
     one starts around 65% — the ~30px between them is that margin, not
     wasted space, and it is what stops card 1's caption sliding under
     card 2's photograph. */
  .pj-cut-1 { left: 2%;  right: auto; top: 1%;  width: min(46%, 38cqh); transform: rotate(-2.4deg); z-index: 1; }
  /* Tops carry the cascade to ~93% of the board, not ~83%: at 52% card 3
     stopped well short of the space the board had claimed and left a visible
     void between its caption and the supporting line. Measured slack at the
     bottom is now 21-41px across the four phone sizes. */
  .pj-cut-2 { left: auto; right: 2%;  top: 26%; width: min(35%, 27cqh); transform: rotate(2deg);    z-index: 3; }
  .pj-cut-3 { left: 5%;  right: auto; top: 60%; width: min(44%, 32cqh); transform: rotate(-1.6deg); z-index: 2; }

  /* Reclaim ~10px per card: the desktop paper margin is generous because the
     cards are large there. The tear amplitude is derived from this padding in
     JS, so the deckle scales down with it automatically. */
  .pj-cut-paper {
    padding: clamp(6px, 1svh, 11px);
    padding-bottom: clamp(11px, 1.9svh, 20px);
  }

  /* The category word returns (the <=480px rule drops it) but as its own
     line, so a caption can never be wider than the card it belongs to. */
  .pj-cut-cap {
    white-space: normal;
    font-size: .5rem;
    letter-spacing: .16em;
    line-height: 1.25;
  }
  .pj-cut-cap span {
    display: block;
    margin-top: 1px;
    letter-spacing: .1em;
    opacity: .85;
  }
  .pj-cut-2 .pj-cut-cap { text-align: right; }

  .pj-thread {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
  }
  /* .38 alpha at 1.3px was invisible against the paper in a real render — a
     dashed stroke only has ~40% of its length actually painting, so it reads
     far lighter than its alpha suggests. Heavier and darker, plus a white
     under-glow so it holds on the paper's mid-tone the way real thread does. */
  .pj-thread path {
    fill: none;
    stroke: rgba(38, 78, 50, .72);
    stroke-width: 1.7;
    stroke-linecap: round;
    /* dashed = stitches. The draw-in is a clip-path wipe in projects.js, so
       the dash pattern stays a pattern instead of being consumed by it. */
    stroke-dasharray: 5 6;
    filter: drop-shadow(0 1px 0 rgba(255, 255, 255, .85));
  }
}

/* Short phones: the sub-line is the one piece of the hero that is purely
   supporting, and dropping it hands ~85px back to the cascade — the
   difference between 97px-wide cards and 135px-wide ones at 360x640. */
@media (max-width: 600px) and (max-height: 700px) {
  .pj-hero-sub { display: none; }
}

/* The <=900px rules give up the third card on short windows because a centred
   row has no vertical room to put it. The cascade does, so take it back. */
@media (max-width: 600px) and (max-height: 620px) {
  .pj-cut-3 { display: block; }
  .pj-cut-1 { width: min(46%, 38cqh); }
  .pj-cut-2 { width: min(35%, 27cqh); }
}

/* ==================== PREFERS REDUCED MOTION ==================== */
@media (prefers-reduced-motion: reduce) {
  .pj-scroll-line { animation: none; }
  .pj-spot-img,
  .pj-card-img,
  .pj-spot-cta-arrow,
  .pj-card-arrow,
  .pj-manifesto-cta-arrow { transition: none; }
}

