/* ===================================================================
   Maryam Portfolio — main stylesheet
   Design system: Bebas Neue / Manrope, no italics, no gradients —
   solid color fields only. The palette below is intentionally wide
   (a "vibrant" art-direction pass widened it from the original
   muted-accent set) — every color here is meant to be seen at real
   size somewhere on the site, not held back as a tiny accent.
   =================================================================== */
:root{
  /* The site palette. Warm cream is the canvas; the saturated colours are
     accents and "events", never the ground. */
  --cream:#F6F1E7;       /* warm cream — the dominant background */
  --paper:#FAF9F6;       /* secondary light surface; light text on dark fields */
  --black:#17120F;       /* deep ink */
  --deep:#17120F;        /* deep ink (text on colour fields) */
  --muted:#55534F;       /* muted text */
  --border:#D5D0C6;      /* hairlines */
  --dark:#1C1C1A;        /* dark section (footer) */
  --pink:#F05A8A;
  --purple:#5146C7;      /* purple / blue */
  --yellow:#F3C94B;
  --lime:#B7C93D;        /* olive */
  --berry:#94163F;
  --brown:#765844;       /* earth */
  /* The nav is drawn with mix-blend-mode:difference (so it stays legible
     when big ink type scrolls under it). Difference of a page colour P and a
     text colour C is |P - C|, so each value below is |cream - wanted|: over
     the cream page it renders as EXACTLY the palette colour named, and it
     inverts (rather than vanishing) over ink glyphs or dark fields. */
  --nav-ink:#DFDFD8;     /* renders #17120F on cream */
  --nav-pink:#06975D;    /* renders #F05A8A on cream */
  --nav-berry:#62DBA8;   /* renders #94163F on cream */
  /* Projects card colours, in card order (see projects.php, which also
     sets each card's text, secondary text, panel tint and CTA hover). */
  --proj-cream:#F6F1E7;
  --proj-yellow:#F3C94B;
  --proj-purple:#5146C7;
  --proj-pink:#F05A8A;
  --proj-olive:#B7C93D;
  --proj-berry:#94163F;
  --proj-brown:#765844;
  --proj-near-black:#17120F;
  --proj-white:#FAF9F6;
  --display:'Bebas Neue', Impact, sans-serif;
  --body:'Manrope', Arial, sans-serif;
}
*{ margin:0; padding:0; box-sizing:border-box; font-style:normal; }
html{ scroll-behavior:smooth; }
body{
  background:var(--cream); color:var(--black); font-family:var(--body);
  overflow-x:hidden; -webkit-font-smoothing:antialiased;
}
h1,h2,h3,.display{ font-family:var(--display); text-transform:uppercase; letter-spacing:.01em; font-weight:400; font-style:normal; }
a{ color:inherit; text-decoration:none; }
a:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible{ outline:2px solid var(--purple); outline-offset:2px; }
::selection{ background:var(--yellow); color:var(--deep); }
img,svg{ display:block; max-width:100%; }

/* ---------------- site grain ----------------
   ONE global fixed layer (see footer.php) that gives the whole site a
   faint printed-paper / analogue-film surface. It is deliberately not a
   colour treatment: the texture is zero-mean — black and white specks in
   equal measure, each with its own transparency — so laid over any
   surface it adds tooth without lightening or darkening it on average,
   and the saturated project colours stay clean. Normal blending at a very
   low opacity (no mix-blend-mode, which would muddy those colours and
   cost a blended composite on every frame of the GSAP stack underneath).
   Strength was tuned by measurement, not by eye alone: the layer adds
   about 1.5 levels (of 255) of pixel-to-pixel deviation on every surface
   — felt as tooth, not seen as noise — and the specks' own transparency
   is capped at .6 so this layer's opacity can sit in a normal, readable
   range. Raising it to .045 (about 2x stronger) already dulls the cream.
   It generates itself (inline SVG feTurbulence — no image file, no
   canvas), never intercepts input, and nothing in JS touches it.
   The tiny stepped shift (8 fixed positions, about 8 per second) is what
   makes it read as grain rather than a fixed screen; it is zero-mean too,
   so the shifts don't flicker, and it is switched off entirely under
   prefers-reduced-motion, leaving the same static texture. */
.site-noise{
  position:fixed; inset:0; z-index:9999; pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='256' height='256'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='2.5' intercept='-.75'/%3E%3CfeFuncG type='linear' slope='2.5' intercept='-.75'/%3E%3CfeFuncB type='linear' slope='2.5' intercept='-.75'/%3E%3CfeFuncA type='linear' slope='2.5' intercept='-.75'/%3E%3C/feComponentTransfer%3E%3CfeComponentTransfer%3E%3CfeFuncR type='discrete' tableValues='0 1'/%3E%3CfeFuncG type='discrete' tableValues='0 1'/%3E%3CfeFuncB type='discrete' tableValues='0 1'/%3E%3CfeFuncA type='table' tableValues='.6 0 .6'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size:256px 256px;
  opacity:.03;
  user-select:none; -webkit-user-select:none;
  animation:siteNoiseShift .96s steps(1,end) infinite;
}
@keyframes siteNoiseShift{
  0%{ background-position:0px 0px; }
  12.5%{ background-position:-37px 23px; }
  25%{ background-position:51px -19px; }
  37.5%{ background-position:-14px -62px; }
  50%{ background-position:68px 41px; }
  62.5%{ background-position:-55px -8px; }
  75%{ background-position:22px 57px; }
  87.5%{ background-position:-71px -33px; }
}
@media (prefers-reduced-motion:reduce){ .site-noise{ animation:none; } }
@media print{ .site-noise{ display:none; } }

/* ---------------- preloader ---------------- */
.preloader{
  position:fixed; inset:0; z-index:200; background:var(--black); color:var(--cream);
  display:flex; align-items:center; justify-content:center; text-align:center; padding:0 6vw;
}
.pre-line{
  position:absolute; font-family:var(--display); font-size:clamp(1.8rem,5vw,3.2rem);
  letter-spacing:.02em; opacity:0;
}

/* ---------------- nav ---------------- */
html.maryam-intro-lock, html.maryam-intro-lock body{ overflow:hidden !important; height:100%; }

/* Returning-within-session visitors, and every non-front-page
   template: the class is added synchronously in <head> (see
   header.php) before first paint, so the preloader/greeting never
   flash and the nav is correct from frame one. */
html.intro-skip .preloader,
html.intro-skip .greeting-flip{ display:none !important; }
html.intro-skip nav{ opacity:1 !important; pointer-events:auto !important; }

nav{
  position:fixed; top:0; left:0; right:0; z-index:90; opacity:0; pointer-events:none;
  display:flex; justify-content:space-between; align-items:center;
  padding:1.4rem 2.4rem; mix-blend-mode:difference;
}
/* No band, blur or border behind the nav — it sits directly on the cream
   canvas (the .is-scrolled class is still toggled by main.js, it just no
   longer paints anything). */
nav .brand{ font-family:var(--display); font-size:clamp(.85rem,1.4vw,1.15rem); color:var(--nav-ink); letter-spacing:.02em; line-height:1.15; max-width:260px; display:inline-block; }
nav .links{ display:flex; gap:1.8rem; font-size:.78rem; letter-spacing:.05em; color:var(--nav-ink); }
nav .links a{ transition:color .25s ease; position:relative; }
nav .links a.is-active{ color:var(--nav-berry); }
nav .links a:hover, nav .links a:focus-visible{ color:var(--nav-pink); }
nav .links a::after{
  content:''; position:absolute; left:0; bottom:-4px; width:100%; height:1px;
  background:currentColor; transform:scaleX(var(--u, 0)); transform-origin:left; pointer-events:none;
}

/* Mobile menu trigger — invisible above the mobile breakpoint (desktop
   keeps its existing two-item nav row, .brand and .links, untouched).
   Shares .brand/.links' own colour (var(--nav-ink)) and sits inside the
   same blend-mode:difference nav, so it reads correctly against
   whatever is behind it, exactly like the rest of the closed bar. */
.nav-toggle{
  display:none; align-items:center; justify-content:center; gap:.5rem;
  min-width:44px; min-height:44px; padding:.5rem .6rem; margin:-0.5rem -0.6rem;
  background:none; border:0; cursor:pointer; color:var(--nav-ink);
  font-family:var(--body); font-size:.72rem; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  -webkit-tap-highlight-color:transparent;
}
.nav-toggle-icon{ position:relative; width:18px; height:2px; background:currentColor; display:block; flex-shrink:0; }
.nav-toggle-icon::before, .nav-toggle-icon::after{
  content:''; position:absolute; left:0; width:18px; height:2px; background:currentColor;
  transition:transform .25s ease, top .25s ease, opacity .25s ease;
}
.nav-toggle-icon::before{ top:-6px; }
.nav-toggle-icon::after{ top:6px; }
nav.is-nav-open .nav-toggle-icon{ background:transparent; }
nav.is-nav-open .nav-toggle-icon::before{ top:0; transform:rotate(45deg); }
nav.is-nav-open .nav-toggle-icon::after{ top:0; transform:rotate(-45deg); }
@media (prefers-reduced-motion:reduce){
  .nav-toggle-icon::before, .nav-toggle-icon::after{ transition:none; }
}

@media (max-width:767px){
  /* Safe-area aware, and a touch tighter than desktop's 2.4rem side
     padding so MARYAM and the trigger both sit comfortably clear of a
     narrow phone's edges. Only the top inset is a real-world concern
     here (notches/status bars are a top-of-screen thing); env() with
     the max() fallback is a no-op on a device without one. */
  nav{
    padding:max(1rem, env(safe-area-inset-top)) 1.2rem 1rem;
  }
  .nav-toggle{ display:flex; }
  /* Closed by default: not just visually hidden but actually removed
     from layout, tab order and the accessibility tree (display:none),
     so a screen-reader or keyboard user never lands on a link that
     isn't currently reachable/visible — see MOBILE MENU in main.js for
     the open state, which switches this to display:flex only then. */
  nav .links{
    display:none; position:absolute; top:100%; left:0; right:0;
    flex-direction:column; align-items:flex-start; gap:1.1rem;
    padding:1.4rem 1.5rem calc(1.6rem + env(safe-area-inset-bottom));
    font-size:.95rem;
  }
  nav .links a::after{ display:none; } /* the desktop underline cue is redundant in a stacked list */
  /* The open panel needs a real, readable background of its own — which
     a difference-blended nav (see above) can't give it without the
     background itself getting inverted too, so blend-mode is switched
     off and every element gets its real ink colour explicitly for as
     long as the panel is open. Subtle translucency, not an opaque
     block: the page is still faintly visible through it. */
  nav.is-nav-open{ mix-blend-mode:normal; }
  nav.is-nav-open .links{
    display:flex; background:rgba(246,241,231,.9); backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  }
  nav.is-nav-open .brand,
  nav.is-nav-open .nav-toggle{ color:var(--black); }
  nav.is-nav-open .links,
  nav.is-nav-open .links a{ color:var(--black); }
  nav.is-nav-open .links a.is-active{ color:var(--berry); }
  nav.is-nav-open .links a:hover,
  nav.is-nav-open .links a:focus-visible{ color:var(--pink); }
}

.greeting-flip{
  position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
  z-index:95; mix-blend-mode:difference; pointer-events:none; text-align:center;
}
.greeting-flip .g-full{
  font-family:var(--display); font-weight:600; white-space:nowrap;
  color:var(--nav-ink); font-size:clamp(2.4rem,7vw,6.5rem);
}
.greeting-flip .gchar{ display:inline-block; opacity:0; }

/* ---------------- hero ---------------- */
.hero{
  min-height:100vh; display:flex; flex-direction:column; justify-content:center; align-items:center;
  padding:clamp(8rem,14vw,13rem) 6vw clamp(4rem,6vw,6rem); position:relative; text-align:center;
  background:var(--cream); color:var(--black);
}
/* Each line is a block; SplitText (main.js) wraps its lines in overflow-clip
   masks, so the reveal masking lives there — no wrapper spans needed. */
.headline .line{ display:block; transform-origin:center; }
.headline .line.lede{ font-size:clamp(1.8rem,3.8vw,3.6rem); font-weight:600; color:var(--black); margin-bottom:.4rem; }
.headline .line.impact{ font-size:clamp(3.8rem,10.5vw,13rem); line-height:.84; }
.headline .hword{ display:inline-block; }
.hero .headline, .hero .role{ position:relative; z-index:2; }
.hero .cue{ z-index:2; }

/* ---- hero 3D scene ----
   Four cut-outs of one rendered puzzle set, sitting BEHIND the type in a
   shared perspective. Each piece's box (.hp) is exactly the piece's own
   body — the artwork's baked studio shadow is padding around it, hung
   outside the box by the img offsets below — so left/top/width place the
   piece itself, and rotations pivot on its centre.
   Layer chain per piece (all transform-style:preserve-3d so z survives the
   nesting and every layer is projected by the scene's one perspective):
     .hp entrance + hero scroll · .hp__s spare (unanimated) · .hp__p idle + pointer
   Layout properties are only ever set here, in CSS; GSAP animates
   transforms and opacity only.
   --hu is the composition unit: 1vw, but capped by height on wide/short
   windows so the pieces never outgrow the type block. Portrait windows
   (phones, tablets held upright) get their own composition below rather
   than a shrunken desktop one. */
.hero{ --hu:min(1vw,1.5vh); }
/* Clipped sideways only (overflow-x:clip — no horizontal scroll), never
   vertically: a vertical clip cuts the bottom pieces flat along the hero's
   edge, which reads as a hard divider between the hero and the next
   section. Left open, the bottom pieces and their shadows simply continue
   into the cream below. The hero sits above the following sections
   (z-index) so that overhang is painted, not covered. */
.hero{ z-index:1; }
.hero-scene{
  position:absolute; inset:0; z-index:0; pointer-events:none; overflow-x:clip; overflow-y:visible;
  perspective:1600px; perspective-origin:50% 42%;
}
/* First visit: the puzzle does not exist until the preloader and the greeting
   are done. main.js reveals the scene as the first act of the entrance (an
   inline visibility). Returning visitors (intro-skip) see it from frame one. */
html:not(.intro-skip) .hero-scene{ visibility:hidden; }
.hp, .hp__s, .hp__p{ transform-style:preserve-3d; }
.hp{ position:absolute; width:var(--w); aspect-ratio:var(--ar); }
.hp__s, .hp__p{ position:absolute; inset:0; }
.hp img{
  position:absolute; display:block; max-width:none; height:auto;
  width:var(--iw); left:var(--il); top:var(--it);
  user-select:none; -webkit-user-select:none; pointer-events:none;
}
.hp--ul{ --ar:493/430; --iw:144.62%; --il:-22.31%; --it:-25.58%; --w:calc(var(--hu)*32);   left:calc(var(--hu)*-5.6); top:calc(var(--hu)*3.6); }
.hp--ur{ --ar:479/494; --iw:145.93%; --il:-22.96%; --it:-22.27%; --w:calc(var(--hu)*31);   right:calc(var(--hu)*-8.4); top:calc(var(--hu)*3.6); }
.hp--ll{ --ar:456/524; --iw:148.25%; --il:-24.12%; --it:-20.99%; --w:calc(var(--hu)*30);   left:calc(var(--hu)*-4.2); bottom:calc(var(--hu)*-3.5); }
.hp--lr{ --ar:517/426; --iw:142.55%; --il:-21.28%; --it:-25.82%; --w:calc(var(--hu)*35.2); right:calc(var(--hu)*-4.9); bottom:calc(var(--hu)*-2.1); }
/* Portrait — tablets held upright and phones. The type is a narrow stack in
   the middle of a tall screen, so the pieces move to the free bands above
   and below it (top pair / bottom pair) instead of the desktop's left/right
   flanks. Sizes are in vw, capped by height so a short phone can't push a
   piece into the type; vertical placement is a share of the hero's height so
   it tracks the type block, which is centred in it. Kept clear of the fixed
   nav (top) and the "scroll" cue (bottom, centred). */
@media (max-aspect-ratio:1/1){
  .hero{ --hu:min(1vw,.9vh); }
  .hp--ul{ --w:calc(var(--hu)*40); left:calc(var(--hu)*-6);   top:9%;   }
  .hp--ur{ --w:calc(var(--hu)*39); right:calc(var(--hu)*-7.5); top:6.5%; }
  .hp--ll{ --w:calc(var(--hu)*34); left:calc(var(--hu)*-6);   bottom:7%; }
  .hp--lr{ --w:calc(var(--hu)*41); right:calc(var(--hu)*-6);  bottom:6.5%; }
}
@media (max-aspect-ratio:1/1) and (max-width:640px){
  .hero{ --hu:min(1vw,.58vh); }
  .hp--ul{ --w:calc(var(--hu)*52); left:calc(var(--hu)*-8);   top:11.5%; }
  .hp--ur{ --w:calc(var(--hu)*49); right:calc(var(--hu)*-9);  top:9.5%; }
  .hp--ll{ --w:calc(var(--hu)*40); left:calc(var(--hu)*-7);   bottom:8%; }
  .hp--lr{ --w:calc(var(--hu)*47); right:calc(var(--hu)*-8);  bottom:9%; }
}
/* Short phones (SE-class): the type block plus the scroll cue leave much
   less free height, so the pair above the type shrinks and the pair below
   it sits lower and smaller, tucked either side of the cue. */
@media (max-aspect-ratio:1/1) and (max-width:640px) and (max-height:780px){
  .hero{ --hu:min(1vw,.5vh); }
  .hp--ul{ --w:calc(var(--hu)*44); left:calc(var(--hu)*-7);  top:11%; }
  .hp--ur{ --w:calc(var(--hu)*42); right:calc(var(--hu)*-8); top:9%; }
  .hp--ll{ --w:calc(var(--hu)*31); left:calc(var(--hu)*-6);  bottom:-1%; }
  .hp--lr{ --w:calc(var(--hu)*34); right:calc(var(--hu)*-7); bottom:-2%; }
}
.hero .role{
  margin-top:3rem; font-size:clamp(.95rem,1.1vw,1.15rem); font-weight:600; letter-spacing:.06em;
  text-transform:uppercase; color:var(--brown); max-width:28ch; opacity:0; margin-inline:auto;
}
.hero .cue{
  position:absolute; bottom:2.4rem; left:50%; transform:translateX(-50%); font-size:.75rem; letter-spacing:.08em;
  text-transform:uppercase; color:var(--brown); opacity:0;
}

/* ---------------- process ---------------- */
.process{ background:var(--cream); color:var(--deep); position:relative; overflow:hidden; height:1736vh; }
/* align-items:flex-start (not the more typical :center) is deliberate:
   #pathWrap is short relative to the 100vh pin at every breakpoint, so
   centring it leaves dead space both above AND below the curve — and
   because .process-pin sits flush against the chapter title's own
   bottom edge while scrolling into place, that top margin reads as a
   gap between "THE PROCESS" and the curve rather than just empty pin
   space. Top-aligning starts the curve right at the pin's top edge,
   immediately below the title; main.js then sets a small dynamic
   padding-top (clear of the fixed nav) and a matching clip-path (the
   actual guarantee — once the whole path is drawn, during the closing
   tail, the curve is continuous enough to always reach whatever y the
   frame's own top edge lands on, so padding alone can't keep it out
   from under the nav; the clip-path can, regardless of framing). */
.process-pin{ position:sticky; top:0; height:100vh; width:100%; display:flex; align-items:flex-start; justify-content:center; overflow:hidden; background:var(--cream); }

.path-wrap{ position:relative; width:min(92vw, 1200px); height:min(80vh, 800px); overflow:hidden; }
.path-wrap svg{ width:100%; height:100%; overflow:visible; }
.story-line{ fill:none; stroke:var(--deep); stroke-width:3.5; stroke-linecap:round; stroke-linejoin:round; }
.marker{ fill:var(--yellow); opacity:.15; }

.center-stage{ position:absolute; inset:0; pointer-events:none; z-index:2; }
.stage-text{
  position:absolute; top:52%; transform:translateY(-50%);
  width:min(28vw, 460px); display:flex; flex-direction:column;
}
.stage-text.side-right{ left:calc(50% + 95px); text-align:left; align-items:flex-start; }
.stage-text.side-left{ right:calc(50% + 95px); text-align:right; align-items:flex-end; }
.stage-text .tag{ font-family:var(--display); font-size:clamp(1.1rem,1.6vw,1.5rem); letter-spacing:.04em; font-weight:400; margin-bottom:.6rem; display:block; color:var(--yellow); }
.stage-text h3{ font-size:clamp(4rem,7vw,7rem); line-height:.82; margin-bottom:1.1rem; color:var(--deep); }
.stage-text p{ font-size:clamp(1.05rem,1.35vw,1.35rem); line-height:1.55; color:rgba(23,18,15,.72); }
.stage-text .chips{ margin-top:1.3rem; display:flex; gap:.5rem; flex-wrap:wrap; }
.stage-text.side-left .chips{ justify-content:flex-end; }
.stage-text .chip{ font-size:.72rem; letter-spacing:.04em; padding:.32rem .8rem; border-radius:999px; border:1px solid rgba(23,18,15,.3); color:var(--deep); }

/* Below 900px the curve/node (see MOBILE_FX in main.js) is framed
   toward whichever edge is opposite this stage's text instead of
   dead-centre, so the line runs down the free edge rather than
   through the tag/title/paragraph/chips. Text anchors to the other
   edge (side-right text sits right, side-left sits left) instead of
   the centred placement this used before, which sat directly on top
   of the centred node. */
@media (max-width:900px){
  /* Far shorter than desktop's 1736vh: that figure is shared scroll
     distance for the section's entire pinned journey, and at desktop
     proportions it took roughly 5 scroll gestures to cross from one
     stage's reading phase to the next on a phone — this brings that
     down to the requested 1–2, paired with the tighter mobile timing
     constants in main.js (which divide this shorter distance between
     approach/read/exit rather than changing how much total scrolling
     it takes).
     Deliberately calc(px + 100vh), not a plain vh value: with
     end:'bottom bottom', the scrub distance ScrollTrigger actually
     uses is (this height − one viewport), so a plain vh figure leaves
     a leftover of exactly one viewport's worth of already-finished,
     now-static content between the pin releasing and the next section
     — small next to desktop's huge total, but a large fraction of this
     much shorter mobile total (it was the "huge blank gap" from
     Process into Projects). Sizing it as scrub-distance + 100vh makes
     that leftover exactly zero. */
  .process{ height:calc(2400px + 100vh); }
  /* .process-pin's padding-top and clip-path (nav clearance) are now
     computed dynamically in main.js from the actual measured nav
     height, for every breakpoint — not just mobile — so no per-tier
     CSS values are needed here any more. */
  .path-wrap{ width:96vw; height:82vh; }
  .stage-text{
    top:auto; bottom:16%; transform:none;
    text-align:left !important; align-items:flex-start !important; width:66vw;
  }
  .stage-text.side-right{ left:auto !important; right:5vw; }
  .stage-text.side-left{ left:5vw; right:auto !important; }
  .stage-text .chips{ justify-content:flex-start !important; }
  .stage-text h3{ font-size:clamp(2rem,8vw,3rem); line-height:.88; }
  .stage-text p{ font-size:clamp(.95rem,3.2vw,1.1rem); max-width:28ch; }
}

/* ---------------- chapter-title ----------------
   The big editorial section markers (THE PROCESS / PROJECTS / ABOUT /
   CONTACT / ALL PROJECTS). Each word is sized so that IT specifically
   spans ~93% of the content width on one line — not one shared size,
   because the words are very different lengths (ABOUT is 1.97em wide,
   THE PROCESS is 3.99em), so a shared size would leave ABOUT at a third
   of the line. The size is pure CSS clamp():

     font-size = clamp( floor, fill * content-width / word-width-in-em, ceiling )

   where content-width is 100vw minus the 3vw side padding on each side
   (94vw), --chapter-em is the word's measured natural width in em in
   the real display font (set per title below), and the ceiling is a
   height-based safety cap so a very wide, short window can't produce a
   heading taller than most of the screen. That gives a correct, fluid
   size from the first paint and with no JS. initChapterTitle() in
   main.js then refines it to the exact pixel width once the real font
   has loaded (the em ratios below are for the loaded font; a fallback
   font has different widths), using the same fill and ceiling.
   The title has its own full-width container: no max-width, only the
   responsive side padding — body copy elsewhere keeps its own measure.
   Vertical padding is deliberately asymmetric: at line-height .85 the
   line box already leaves about .125em of empty space BELOW the
   capitals (the descender zone) but only ~.025em above them, so the top
   needs real padding to give the title breathing room under the fixed
   nav, while the bottom needs very little to look the same. */
.chapter-title{
  --chapter-em: 3;
  --chapter-fill: .93;
  --chapter-lead: 0px;   /* extra air above a title that follows a heavy block (see #chapterAbout) */
  padding: calc(clamp(1.5rem,4.2vw,4.75rem) + var(--chapter-lead)) 3vw clamp(.25rem,.9vw,1rem);
}
.chapter-title .chapter-word{
  display:inline-block; font-family:var(--display); text-transform:uppercase;
  white-space:nowrap; line-height:.85; color:var(--deep);
  font-size:clamp(2.5rem, calc(var(--chapter-fill) * 94vw / var(--chapter-em)), min(78vh, 90rem));
}
#chapterProcess{ background:var(--cream); --chapter-em:3.988; }
#chapterProjects, #chapterArchive{ background:var(--cream); }
#chapterProjects{ --chapter-em:2.932; }
#chapterArchive{ --chapter-em:4.181; }
/* ABOUT follows the Projects stack, whose last card ends close to the bottom
   of its pinned screen (how close depends on the viewport's height). A fixed
   top padding left anywhere from ~275px of air down to almost none between that
   card and the ABOUT letters, so it gets a height-based lead: the heading now
   opens with clear cream space at every size. */
#chapterAbout{ background:var(--cream); --chapter-em:1.971; --chapter-lead:clamp(2rem,6vh,4.5rem); }
#chapterContact{ background:var(--cream); --chapter-em:2.676; }
#chapterContact .chapter-word{ color:var(--black); }

/* ---------------- projects ----------------
   A pinned, scroll-driven stack of large solid-colour panels
   (.proj-card), art-directed after the live Brand Appart "What we
   ship" case-study section — corner radius, typography scale and the
   non-uniform bottom-anchored stack geometry below started from
   measurements taken directly from brandappart.com at 1440×900, then
   the panel itself was deliberately sized larger/wider than that raw
   measurement for a stronger editorial presence (see .projects-stack).
   All absolutely positioned on top of one another inside one shared
   box (.projects-stack); the whole stack pins inside .projects-pin
   while main.js's single scrubbed timeline drives each card's
   position (see PROJECTS STACK there) — no separate reveal
   animation, no onEnter/onLeave, fully reversible by scroll position
   alone.
   Color is per-card (assigned in projects.php) and FIXED for its
   entire scroll lifecycle — it never changes on hover or on scroll.
   GSAP here only ever touches transform (scaleX/scaleY/y, never
   rotation — the panels stay level) — every card in the stack,
   including the one actively exiting, stays fully opaque; the
   exiting card's own real translateY is what separates it from the
   incoming card, not a fade. */
/* The scroll length follows the number of cards (set on the section by projects.php as
   --proj-count): 150vh per card on desktop, 105vh on mobile, so every project gets the
   same pacing whether there are four or ten. Four cards = the approved 600vh / 420vh. */
.projects{ background:var(--proj-cream); position:relative; height:calc(var(--proj-count,4) * 150vh); }
/* padding-top reserves room for the fixed nav within the pin's own
   100vh box (border-box, so this shrinks the flex-centered area
   rather than growing the pin past the viewport) — without it, a
   short viewport (tablet landscape, small laptop, mobile) centers the
   stack right under/behind the nav instead of below it. Nav height is
   ~63-67px across breakpoints; 72px keeps a safe margin on every one. */
.projects-pin{
  position:sticky; top:0; height:100vh; height:100svh; display:flex; align-items:center; justify-content:center;
  overflow:hidden; background:var(--proj-cream); padding-top:72px;
}
/* The stack is a dedicated STAGE, not just a card wrapper: it is
   deliberately sized taller than the active card itself (card height
   + --proj-peek) so the next 1-3 cards can sit slightly lower and
   smaller behind the front one and still be genuinely visible at
   rest — a real physical shelf of cards, not a single panel with
   nothing behind it. --proj-card-h is a visual TARGET (68-74vh), not
   a hard box: the card below uses it as a min-height, never a fixed
   height, so real content is never forced to overflow it. Because
   viewport height varies a lot in practice (short laptop windows, the
   wp-admin bar, mobile), main.js also measures each card's actual
   rendered height on load/resize and grows this stage to fit the
   tallest one — see the STAGE SIZING comment in main.js — so this
   value is only ever the *starting* height, not the final word.
   Because the stage is also smaller than the 100vh pin it sits in,
   the pin's own flex-centering automatically leaves clean cream
   margin above AND below the whole stack at every viewport size — no
   separate spacing rule needed. Every layer in this chain (.projects,
   .projects-pin, .projects-stack) shares the exact same --proj-cream
   background, so there is no seam where a different colour (or unset/
   white) could show through for even one frame during a scrub-driven
   transition. */
.projects-stack{
  --proj-card-h: clamp(440px,62vh,800px);
  --proj-peek: 92px;
  --proj-shadow-top: clamp(14px,2.4vw,34px);
  --proj-shadow-side: clamp(16px,4vw,56px);
  position:relative;
  width:calc(min(82vw,1240px) + 2 * var(--proj-shadow-side));
  height:calc(var(--proj-card-h) + var(--proj-shadow-top) + var(--proj-peek));
  overflow:visible;
  clip-path:inset(0 -240px -240px -240px);
}
/* The stage is the mask, not the individual cards — clip-path here is
   what stops an exiting card from rendering freely above the frame
   once it's translated past the frame's own top edge. It is an inset
   clip that is flush at the TOP only (where cards must be cut off) and
   deliberately extends past the sides and bottom, so a card's layered
   shadow is never sliced by the stage on those three sides, whatever
   the viewport (overflow:hidden here cut the shadow with a hard edge
   wherever the shadow's tail was wider than the margin below). The
   stage itself is deliberately sized LARGER than the visible card —
   by --proj-shadow-top above it and --proj-shadow-side on each side,
   in addition to the existing --proj-peek room below — purely so each
   card's own box-shadow (varied by depth in main.js, so the active
   card reads with a stronger lift than the ones behind it) has real
   pixel room to render into before it ever reaches the stage's own
   clipping edge. Content clipping and shadow room are deliberately
   separate concerns here: the CARD's own border-radius still clips
   its own content tightly, while the STAGE around it clips only once
   a card has genuinely exited, never the resting shadow. The stage
   itself paints no visible background of its own (cream comes from
   .projects-pin directly behind it) — it is a clip boundary only, not
   a fifth colored layer.
   min-height (never height) + no explicit height on .proj-card-inner
   below is what makes the card ONE stable, content-sized panel: if a
   project's real content (longer description, more skills tags) needs
   more room than the --proj-card-h target, the card itself grows to
   fit it instead of clipping it or letting it spill out past its own
   painted background into the card resting behind it — which is what
   actually caused the broken, overlapping look this replaces. */
/* display:flex here (not block) is what makes .proj-card-inner — its
   only child — stretch to fill whichever height .proj-card actually
   resolves to (the min-height floor when content is short, or a
   taller content-driven height when it's not), via ordinary flex
   cross-axis stretch. That's what keeps .proj-card-panels' own
   margin-top:auto (below) working correctly in both cases: real
   leftover space to distribute when content is short, gracefully
   zero leftover space (content sits flush, nothing overflows) when a
   project's content is long enough to need the card's full grown
   height. */
.proj-card{
  position:absolute; top:var(--proj-shadow-top); left:var(--proj-shadow-side); right:var(--proj-shadow-side);
  min-height:var(--proj-card-h);
  display:flex; border-radius:20px;
  background-color:var(--card-bg); color:var(--card-fg,var(--proj-white));
  text-decoration:none; will-change:transform;
}
.proj-card-inner{
  position:relative; display:flex; flex-direction:column; flex:1;
  padding:clamp(24px,min(3.6vw,5.2vh),48px);
}
/* Role/number sit at the top-left; the CTA sits at the top-right of
   the same row — a secondary action anchored to the upper
   composition rather than a footer button, per spec. Both are real
   flow content (never position:absolute) so the CTA always has its
   own reserved space and can never escape the card or collide with
   anything below it. */
.proj-card-toprow{
  display:flex; align-items:flex-start; justify-content:space-between; gap:1.5rem; flex-wrap:wrap;
  margin-bottom:clamp(18px,min(2.4vw,3.4vh),32px);
}
.proj-card-type{
  display:block; font-family:var(--body); font-size:clamp(11px,.8vw,14px); font-weight:700; letter-spacing:.08em;
  text-transform:uppercase; opacity:.86; margin-bottom:.7rem;
}
.proj-card-count{
  display:block; font-family:var(--body); font-size:clamp(1rem,1.4vw,1.4rem); font-weight:600;
}
.proj-card-title{
  font-family:var(--display); text-transform:uppercase; line-height:.94; letter-spacing:-.01em;
  font-size:clamp(2.375rem,5vw,4.75rem); max-width:800px; margin-bottom:clamp(.85rem,1.6vw,1.3rem);
  transition: transform .4s cubic-bezier(.22,.61,.36,1);
}
.proj-card.is-clickable:hover .proj-card-title{ transform:translateX(6px); }
/* A long title (an organisation's full name) adapts to the card, never the
   card to the title: it is set at a smaller responsive size so it wraps
   inside the card's own width, and it reserves exactly the one-line title
   area every other card has (min-height = one standard title line), so it
   cannot make its card any taller or shorter than the rest. Class is added in
   projects.php for titles over 32 characters. */
.proj-card-title--long{
  font-size:clamp(1.7rem,2.9vw,2.65rem);
  min-height:calc(clamp(2.375rem,5vw,4.75rem) * .94);
}
.proj-card-desc{
  font-family:var(--body); font-size:clamp(15px,1.2vw,19px); line-height:1.55;
  max-width:700px; color:var(--card-fg-2,inherit); opacity:var(--card-fg-2-op,.85);
}
/* The lower info panels are pushed to the bottom of the card
   (margin-top:auto) rather than pinned to a fixed offset, so the
   generous mid-card whitespace between the description and the
   lower content happens naturally regardless of how long a given
   project's description runs — real reserved space via normal flow.
   No internal divider line — hierarchy comes from spacing and the
   panels' own tonal contrast, not a rule. A real two-column grid (not
   flex-wrap) is what lets both panels span the full width of the
   card in equal halves rather than sizing to their own content. */
.proj-card-panels{
  margin-top:auto; padding-top:clamp(28px,min(4.6vw,6vh),52px); display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:clamp(14px,2vw,28px);
}
.proj-card-cta{
  display:inline-flex; align-items:center; gap:.6rem; flex-shrink:0;
  font-family:var(--body); font-size:clamp(.78rem,.85vw,.85rem); font-weight:700; letter-spacing:.05em;
  text-transform:uppercase; padding:clamp(.8rem,1.4vw,1rem) clamp(1.3rem,2.2vw,1.8rem); border-radius:8px;
  transition: background-color .25s ease, color .25s ease, border-color .25s ease;
}
/* Button colour states communicate the project's state, never interaction:
   completed = paper with ink text, and it stays exactly that on hover (only
   the arrow's small nudge below moves); ongoing / upcoming = a fixed gray
   status label — not a link, no hover response, no pointer cursor. */
.proj-card-cta--active{ background:var(--proj-white); color:var(--proj-near-black); }
.proj-card-cta--muted{
  background:var(--border); color:var(--muted); border:1px solid var(--border);
  cursor:default; pointer-events:none;
}
/* The arrow's nudge (and the button's lift / press) is GSAP's, in main.js —
   no CSS transform or transition here, so the two never fight. */
.proj-card-arrow{ display:inline-block; }

/* The two info panels — "What I Solved" / "Skills + Tools" — read as
   large integrated editorial zones (not compact dashboard widgets):
   the grid above gives each an equal half of the card's full width,
   and min-height (not a fixed height) guarantees a substantial,
   intentional-looking panel even for the shortest content while
   still letting a longer list grow taller naturally. A translucent
   dark-blue tint (not the cream/espresso pairing used elsewhere on
   the site) keeps them tonally distinct from the saturated project
   colour behind them without introducing an unrelated colour. */
.proj-info-panel{
  min-height:clamp(120px,15vw,176px); background:var(--card-panel,rgba(23,18,15,.26)); color:inherit;
  border-radius:10px; padding:clamp(1.1rem,1.7vw,1.5rem) clamp(1.35rem,2vw,1.85rem);
}
.proj-info-panel-label{
  display:block; font-family:var(--body); font-size:.72rem; font-weight:700;
  letter-spacing:.08em; text-transform:uppercase; opacity:.78; margin-bottom:1rem;
}
.proj-info-panel-points{ list-style:none; display:flex; flex-direction:column; gap:.55rem; }
.proj-info-panel-points li{
  font-family:var(--body); font-size:.9rem; line-height:1.45; padding-left:1.2rem; position:relative;
}
.proj-info-panel-points li::before{ content:'\2192'; position:absolute; left:0; opacity:.7; }
.proj-info-panel-tags{ list-style:none; display:flex; flex-wrap:wrap; gap:.4rem .6rem; }
.proj-info-panel-tags li{ font-family:var(--body); font-size:.9rem; line-height:1.45; opacity:.92; }
.proj-info-panel-tags li:not(:last-child)::after{ content:'\00B7'; margin-left:.6rem; opacity:.5; }

/* Panels only drop to one column below a content-based threshold
   (not the same breakpoint as the general mobile reflow) — two
   columns simply don't have comfortable width under this, regardless
   of whether the rest of the card has already reflowed to its mobile
   order. Stacking both panels vertically (rather than side by side)
   roughly doubles the vertical room they need, which on a real phone
   viewport competes directly with the pinned stack's own fixed 100vh
   frame — so panel padding/gaps/description tighten specifically
   here too, enough that the two stacked panels' real content
   comfortably fits a phone screen without the outer pin ever having
   to clip a too-tall resting card. Still visibly padded and readable,
   just not as roomy as the desktop two-column layout, which never
   needs this. */
@media (max-width:800px){
  .proj-card-panels{ grid-template-columns:1fr; padding-top:clamp(16px,5vw,32px); gap:clamp(8px,2vw,16px); }
  .proj-info-panel{ min-height:0; padding:12px 16px; }
  .proj-info-panel-label{ margin-bottom:.4rem; }
  .proj-info-panel-points{ gap:.35rem; }
  .proj-info-panel-points li, .proj-info-panel-tags li{ line-height:1.35; }
  .proj-card-desc{ font-size:14px; line-height:1.4; }
}
@media (max-width:899px){
  .projects{ height:calc(var(--proj-count,4) * 105vh); }
  /* .proj-card-toprow becomes display:contents here — its two
     children (meta, cta) are promoted to be direct flex items of
     .proj-card-inner alongside title/desc/panels, so every element
     can be reordered independently with `order` (CTA moves to the
     very end, after the panels, per spec) without touching the DOM
     or duplicating any content. Typography, padding and panel/gap
     sizing all stay on the same fluid clamp()s defined above — they
     already scale down naturally at these widths, so nothing here
     re-declares a mobile-specific size for any of that. */
  .projects-stack{
    width:calc(93vw + 2 * var(--proj-shadow-side));
    --proj-card-h: clamp(420px,63vh,640px);
    --proj-peek: 60px;
    --proj-shadow-top: 10px;
    --proj-shadow-side: 14px;
  }
  .proj-card{ border-radius:16px; }
  .proj-card-inner{ padding:clamp(20px,4vw,56px); }
  .proj-card-toprow{ display:contents; }
  .proj-card-meta{ order:1; margin-bottom:.4rem; }
  .proj-card-count{ margin-bottom:0; }
  .proj-card-title{ order:2; max-width:100%; margin-bottom:.6rem; }
  .proj-card-desc{ order:3; max-width:none; }
  .proj-card-panels{ order:4; }
  .proj-card-cta{ order:5; margin-top:.5rem; align-self:flex-start; }
}

/* Tablet band — same architecture as desktop (2-column panels, CTA
   still in the header row), just a wider proportion of the viewport
   and a shorter, less aggressive resting peek, per the responsive
   spec: "reduce padding/title size/spacing but preserve the same
   architecture." Sits between the general mobile reflow above (which
   still applies at this width for the CTA/title reordering) and full
   desktop. Deliberately placed after that block so its width wins the
   cascade at this width range. */
@media (min-width:701px) and (max-width:899px){
  .projects-stack{ width:calc(90vw + 2 * var(--proj-shadow-side)); }
}

/* A short viewport (small phones in portrait, and landscape phones/
   small windows generally) is the one case where the pinned stack's
   fixed 100vh frame is the actual limiting dimension, not the width —
   trims a little further off the already-tightened mobile spacing
   above so the whole card is guaranteed to fit without the pin ever
   clipping it, on the shortest realistic screens. Deliberately placed
   after the width-based mobile query above so it always wins the
   cascade when both match (e.g. a small phone in portrait matches
   both) — main.js reads --proj-peek back out of CSS at runtime
   (see STAGE SIZING), so this is the single source of truth for peek
   room at every breakpoint, not just a visual fallback. */
@media (max-height:700px){
  .projects-pin{ padding-top:66px; }
  .projects-stack{ --proj-peek:44px; --proj-shadow-top:8px; }
  .proj-card-inner{ padding:14px; }
  .proj-card-meta{ margin-bottom:.25rem; }
  .proj-card-title{ margin-bottom:.3rem; }
  .proj-card-desc{ line-height:1.3; }
  .proj-card-panels{ padding-top:6px; gap:4px; }
  .proj-info-panel{ padding:7px 10px; }
  .proj-card-cta{ margin-top:.15rem; }
}

/* ---------------- projects archive (/projects/) ----------------
   The homepage Projects section above was rewritten into the
   text-only editorial index (.projects-index); the separate "All
   Projects" archive page (archive-project.php) was not part of that
   change and still uses this original image-card grid.

   It reuses the .projects class for its cream background, but that
   class also carries the home page's pinned-scroll-stack height
   (height:calc(var(--proj-count,4) * 150vh); position:relative) —
   harmless on the home page, but on this page it left a huge empty
   void below the grid. The rule right below undoes only that, scoped
   strictly to .projects-archive so the home page stack is untouched.

   Small local spacing system (values unchanged from what already
   shipped; just named, so the rhythm reads as one system instead of
   scattered numbers): --pa-gap is the one gap used for both grid
   columns and grid rows; --pa-space-1/2/3 are the image->title,
   title->description and description->tags/tags->button steps. */
.projects-archive .projects{
  height:auto;
  position:static;
  padding:clamp(3rem,6vw,5rem) 6vw clamp(4.5rem,9vw,8rem);
}
.projects-archive{
  --pa-gap:clamp(2.5rem,4vw,3.5rem);
  --pa-space-1:1.5rem;
  --pa-space-2:.7rem;
  --pa-space-3:1rem;
  --pa-space-4:1.2rem;
}
.projects-grid{
  display:grid; grid-template-columns:repeat(4, 1fr);
  gap:var(--pa-gap); max-width:1800px; margin-inline:auto;
}
.project-feature{ display:flex; flex-direction:column; gap:var(--pa-space-1); }
.mockup-link{ display:block; }
.mockup{ position:relative; aspect-ratio:4/3; border-radius:4px; overflow:hidden; background:var(--paper); }
.mockup .chrome{ height:10px; background:var(--deep); }
.mockup .body{ height:calc(100% - 10px); }
/* object-position:top, not the default center: a UI screenshot's most
   identifying content (header, search, nav) sits at its top, and a
   portrait screenshot cropped into this landscape card would otherwise
   lose it to a centered crop. Has no effect on a landscape screenshot,
   whose crop is already horizontal only. */
.mockup .body img{ width:100%; height:100%; object-fit:cover; object-position:top; display:block; }
.project-placeholder{
  width:100%; height:100%; display:flex; align-items:center; justify-content:center;
  background:var(--accent); opacity:.55;
}
.project-placeholder span{ font-size:.8rem; font-weight:700; letter-spacing:.05em; text-transform:uppercase; color:var(--deep); opacity:.8; }

.info{ display:flex; flex-direction:column; flex:1 1 auto; }
.info h3{ font-size:clamp(1.6rem,2.4vw,2.2rem); line-height:1.05; margin-bottom:var(--pa-space-2); color:var(--deep); }
.info p{ font-size:.95rem; color:var(--muted); line-height:1.55; margin-bottom:var(--pa-space-3); }
.info .chips{ display:flex; flex-wrap:wrap; gap:.4rem; margin-bottom:var(--pa-space-4); }
.info .chips span{ font-size:.68rem; letter-spacing:.03em; padding:.3rem .7rem; border-radius:999px; border:1px solid rgba(23,18,15,.25); color:var(--brown); }
/* The status/action control — visually distinct from the chips row above it
   (skills/tags), never mixed into it. Completed: a real link, dark pill,
   paper text. Ongoing/Upcoming: a plain <span> (never an anchor), the same
   gray/static treatment the home page's .proj-card-cta--muted already uses,
   so "not clickable" looks intentional rather than a disabled button. */
.info .cta{
  display:inline-block; padding:.75rem 1.6rem; border-radius:999px; font-size:.76rem; font-weight:700;
  letter-spacing:.05em; text-transform:uppercase; align-self:flex-start; margin-top:auto;
}
.info .cta--active{ background:var(--deep); color:var(--cream); }
.info .cta--muted{ background:var(--border); color:var(--muted); cursor:default; }

@media (max-width:1023px){
  .projects-grid{ grid-template-columns:repeat(2, 1fr); }
}
@media (max-width:767px){
  .projects-archive .projects{ padding:clamp(2.5rem,8vw,3.5rem) 6vw clamp(3.5rem,10vw,5rem); }
  .projects-grid{ grid-template-columns:1fr; gap:clamp(2.5rem,9vw,3.5rem); }
}

/* ---------------- about ----------------
   A flat, draggable horizontal card deck — no canvas, no WebGL. Four
   real HTML cards (.about-card) sit in one continuous flex track
   (.ab-deck-track); GSAP Draggable moves the track along x, and the
   pinned panel navigation in about-cards.js (ScrollTrigger + Observer)
   maps a wheel gesture / vertical swipe / key press to exactly one
   card, always settling on a card's center. Card scale/rotation/opacity/y is a
   continuous function of each card's distance from viewport-center,
   recomputed every frame the track moves — see about-cards.js.

   Each card's own content (.about-card__content) is a centered
   column — title, body, note, no numbering, no decorative graphics.
   Color, typography and spacing carry the visual interest; the only
   exception is card 04, which keeps the real sticker assets in
   .about-card__visual--stickers. .about-card__* is deliberately
   BEM-shaped so a later pass can keep targeting title/body/sticker
   pieces individually without touching this CSS. */
.about{ background:var(--cream); position:relative; overflow:hidden; }

.ab-deck{
  --card-w: 54vw; --card-h: clamp(470px,62vh,620px); --card-gap: clamp(40px,3.2vw,70px);
  position:relative; padding-block: 0 clamp(1rem,2vw,1.5rem);
}
/* The viewport needs to clip horizontally (so the wider-than-screen
   draggable track never causes page overflow), but it must NOT clip
   vertically — box-shadow on a descendant (.about-card) is real ink
   painted outside that card's own box, and an ancestor's
   overflow:hidden clips that too, not just child content. Splitting
   the axes here technically computes overflow-y to `auto` (a browser
   quirk: pairing `hidden` on one axis forces the other away from
   `visible`), but `auto` only clips once something exceeds this
   element's own box — so the padding below exists specifically to
   give the card's shadow room inside that box, which both prevents
   any clipping AND supplies the nav-to-card / card-to-counter
   breathing room in one place. */
.ab-deck-viewport{
  overflow-x:hidden; overflow-y:visible; cursor:grab; touch-action:pan-y;
  /* the nav is position:fixed (~67px tall) and overlays rather than
     pushing content down, so the padding here has to be the target
     visible gap PLUS the nav's own height, or the card renders
     directly under the nav with barely any breathing room. */
  padding-block: clamp(138px,calc(10vh + 68px),178px) clamp(55px,8vh,80px);
}
.ab-deck-viewport.is-grabbing{ cursor:grabbing; }
.ab-deck-track{
  display:flex; align-items:center; gap:var(--card-gap);
  padding-inline: calc(50% - (var(--card-w) / 2));
  will-change:transform;
}

.about-card{
  position:relative; flex:0 0 var(--card-w); height:var(--card-h);
  padding: clamp(1.75rem,3vw,3rem);
  display:flex; align-items:center; justify-content:center;
  will-change:transform; user-select:none; -webkit-user-select:none;
  transform-origin:center bottom; overflow:hidden;
  border-radius:12px;
  box-shadow:
    0 8px 20px rgba(23,18,15,.08),
    0 20px 40px rgba(23,18,15,.08);
  transition: background-color .5s cubic-bezier(.22,.61,.36,1), box-shadow .4s ease;
}
.about-card :is(h3,p,span){ user-select:text; -webkit-user-select:text; }
.about.is-dragging .about-card{ user-select:none !important; -webkit-user-select:none !important; }

/* Four distinct color identities — deliberately not four versions of
   the same card. Each also names its own hover color (section 6):
   the transition is declared once on .about-card/__title/__body p/
   __note above/below and simply fires whenever :hover matches, which
   works safely alongside the drag deck's own transform system since
   this CSS only ever touches background-color/color/box-shadow — the
   hover lift itself is a small relative GSAP tween on transform, in
   about-cards.js, so it composes with whatever base transform the
   deck has already set rather than fighting it. */
/* One family, four members: the same type, spacing, radius, layout and
   motion — only these five values change per card. The order is the
   family's sequence, yellow -> olive -> cobalt -> pink (personality, the
   non-linear path, technology x healthcare, the personal side). Secondary
   text is the muted ink on yellow; on olive and pink (where that grey falls
   under readable contrast) it is deep ink at 86%; on cobalt it is warm
   paper. --ac-accent is the small marker dot before each card's note (and
   the x in "technology x healthcare"); on cobalt it is yellow, the one
   palette colour that keeps that large x readable (4.3:1, pink is 2.1:1). */
.about-card{ background:var(--ac-bg); color:var(--ac-ink); }
.about-card:hover{ background:var(--ac-hover); }
/* The card's colour is a palette choice made in wp-admin (About Cards -> Card
   colour), so each accent has its own set; the first four are the approved
   yellow / olive / cobalt / pink cards, berry and earth complete the palette. */
.about-card--yellow{ --ac-bg:var(--yellow); --ac-ink:var(--deep);  --ac-ink2:var(--muted);          --ac-accent:var(--brown);  --ac-hover:var(--pink); }
.about-card--olive{  --ac-bg:var(--lime);   --ac-ink:var(--deep);  --ac-ink2:rgba(23,18,15,.86);    --ac-accent:var(--berry);  --ac-hover:var(--yellow); }
.about-card--cobalt{ --ac-bg:var(--purple); --ac-ink:var(--paper); --ac-ink2:#E9E4DA;               --ac-accent:var(--yellow); --ac-hover:var(--berry); }
.about-card--pink{   --ac-bg:var(--pink);   --ac-ink:var(--deep);  --ac-ink2:rgba(23,18,15,.86);    --ac-accent:var(--yellow); --ac-hover:var(--yellow); }
.about-card--berry{  --ac-bg:var(--berry);  --ac-ink:var(--paper); --ac-ink2:#E9E4DA;               --ac-accent:var(--yellow); --ac-hover:var(--purple); }
.about-card--earth{  --ac-bg:var(--brown);  --ac-ink:var(--paper); --ac-ink2:#E9E4DA;               --ac-accent:var(--yellow); --ac-hover:var(--berry); }
/* yellow -> pink on hover: the muted grey would fall under readable contrast on pink */
.about-card--yellow:hover .about-card__body p,
.about-card--yellow:hover .about-card__note{ color:rgba(23,18,15,.86); }

.about-card:hover{
  box-shadow:
    0 10px 24px rgba(23,18,15,.10),
    0 28px 55px rgba(23,18,15,.13);
}

/* ---- the centered content column, shared by every card — title,
   body, note only. No label, no numbering: the section-wide progress
   marker below the deck is the only place a number appears. ---- */
.about-card__content{
  position:relative; z-index:2; display:flex; flex-direction:column; align-items:center;
  text-align:center; gap:1.2rem; max-width:46ch;
}

.about-card__title{
  font-family:var(--display); text-transform:uppercase; line-height:.94;
  font-size:clamp(2.2rem,4.8vw,3.8rem); color:var(--ac-ink);
  transition: color .4s ease, transform .4s cubic-bezier(.22,.61,.36,1);
}
.about-card__title--split{ display:flex; align-items:center; flex-wrap:wrap; justify-content:center; gap:.15em .35em; }
.about-card__x{ color:var(--ac-accent); font-size:.82em; }
.about-card:hover .about-card__title{ transform:translateY(-3px); }

.about-card__body p{
  font-family:var(--body); font-size:clamp(.96rem,1.05vw,1.08rem); line-height:1.7; margin-bottom:.8rem;
  color:var(--ac-ink2);
  transition: color .4s ease, transform .4s cubic-bezier(.22,.61,.36,1);
}
.about-card__body p:last-child{ margin-bottom:0; }
.about-card:hover .about-card__body p{ transform:translateY(-1px); }

.about-card__note{
  font-family:var(--body); font-weight:700; font-size:.68rem; letter-spacing:.1em; text-transform:uppercase;
  color:var(--ac-ink2); margin-top:.3rem; transition:color .4s ease;
}
.about-card__note::before{
  content:''; display:inline-block; width:.6em; height:.6em; border-radius:50%;
  background:var(--ac-accent); margin-right:.7em; vertical-align:.02em;
}

/* the sticker layer (card 04 only) never captures pointer events —
   it's an accent, not a control. */
.about-card__visual{ position:absolute; inset:0; z-index:1; pointer-events:none; }

/* ---- card 04: outside of work — the real stickers as large,
   irregular die-cut pieces (clip-path silhouettes, not squares),
   scattered at different scales rather than a tidy row. ---- */
.about-card--has-stickers .about-card__content{ position:relative; z-index:2; }
.about-card__visual--stickers{ inset:0; }
.about-card__sticker{
  position:absolute; display:flex; align-items:center; justify-content:center;
  background:var(--cream); box-shadow:0 14px 30px rgba(23,18,15,.16);
}
.about-card__sticker-img{ width:56%; height:56%; object-fit:contain; display:block; }
.about-card__sticker--cut-scallop{ clip-path:polygon(50% 0%,63% 6%,74% 2%,80% 14%,93% 15%,92% 28%,100% 38%,90% 46%,96% 58%,84% 62%,86% 76%,72% 74%,66% 88%,54% 78%,42% 92%,36% 78%,20% 82%,22% 68%,6% 64%,14% 52%,2% 40%,14% 32%,8% 18%,22% 18%,26% 4%,38% 12%); }
.about-card__sticker--cut-burst{ clip-path:polygon(50% 0%,58% 20%,78% 6%,72% 28%,96% 22%,80% 40%,100% 50%,80% 60%,96% 78%,72% 72%,78% 94%,58% 80%,50% 100%,42% 80%,22% 94%,28% 72%,4% 78%,20% 60%,0% 50%,20% 40%,4% 22%,28% 28%,22% 6%,42% 20%); }
.about-card__sticker--cut-organic{ clip-path:polygon(18% 2%,52% 0%,80% 12%,98% 40%,100% 66%,86% 88%,60% 100%,34% 96%,10% 78%,0% 52%,4% 24%); }
.about-card__sticker--cut-angular{ clip-path:polygon(28% 0%,72% 4%,100% 30%,92% 66%,100% 82%,64% 100%,30% 96%,0% 70%,8% 34%); }
.about-card__sticker--gym{ width:clamp(112px,13vw,180px); height:clamp(112px,13vw,180px); top:-9%; left:-4%; transform:rotate(-6deg); }
.about-card__sticker--coffee{ width:clamp(92px,10.2vw,140px); height:clamp(92px,10.2vw,140px); top:-3%; right:-6%; transform:rotate(5deg); }
.about-card__sticker--ideas{ width:clamp(84px,9.4vw,128px); height:clamp(84px,9.4vw,128px); bottom:2%; left:6%; transform:rotate(4deg); }
.about-card__sticker--laptop{ width:clamp(98px,11vw,152px); height:clamp(98px,11vw,152px); bottom:-2%; right:2%; transform:rotate(-4deg); }

/* ---- progress marker ---- */
.ab-deck-progress{
  display:flex; justify-content:center; align-items:baseline; gap:.4em; margin-top: 0;
  font-family:var(--body); font-size:.72rem; font-weight:700; letter-spacing:.16em; color:var(--brown);
}
.ab-deck-progress-sep{ opacity:.5; }
.ab-deck-progress-total{ opacity:.55; }

@media (max-width:1023px){
  .ab-deck{ --card-w: 64vw; --card-h: clamp(400px,52vh,560px); --card-gap: 3.5vw; }
  .about-card__content{ gap:1rem; }
}
@media (max-width:767px){
  .ab-deck{ --card-w: 84vw; --card-h: clamp(410px,58vh,520px); --card-gap: 4vw; }
  .ab-deck-viewport{ padding-block: clamp(103px,calc(7vh + 63px),133px) clamp(35px,6vh,55px); }
  .about-card{ padding: clamp(1.5rem,5vw,2.25rem); }
  /* The card height above is a floor, not a fixed size: the copy wraps
     naturally, and on a short phone where it needs more than that floor the
     card grows to fit (the four stay equal — the track stretches them)
     instead of shrinking the type or clipping the last line. Wherever the copy
     already fits, nothing changes. */
  .about-card{ height:auto; min-height:var(--card-h); }
  .ab-deck-track{ align-items:stretch; }
  .about-card__content{ max-width:34ch; gap:.9rem; }
  .about-card__title{ font-size:clamp(1.9rem,8vw,2.7rem); }
  .about-card__sticker--gym{ width:clamp(78px,19vw,110px); height:clamp(78px,19vw,110px); top:-1%; left:-2%; }
  .about-card__sticker--coffee{ width:clamp(64px,16vw,90px); height:clamp(64px,16vw,90px); top:2%; right:-3%; }
  .about-card__sticker--ideas{ width:clamp(66px,17vw,96px); height:clamp(66px,17vw,96px); bottom:1%; left:4%; }
  .about-card__sticker--laptop{ width:clamp(78px,20vw,112px); height:clamp(78px,20vw,112px); bottom:-2%; right:1%; }
}
@media (max-width:430px){
  .ab-deck{ --card-w: calc(100vw - 56px); --card-h: clamp(400px,58vh,500px); }
  .about-card__body p{ font-size:.92rem; }
}
/* Card height is viewport-HEIGHT-driven (clamp(...,vh,...)), but the
   sticker positions/sizes above are tuned per width breakpoint —
   those two don't always line up. A width like 1024px reads as
   "desktop" and gets the largest sticker sizes, but paired with a
   short 768px-tall viewport (a common laptop resolution) the card
   itself is much shorter than a typical desktop card, so those
   stickers no longer have room. This catches any short viewport,
   independent of width, and pulls the stickers in a bit. */
@media (max-height:820px){
  .about-card__sticker--gym{ top:0%; left:0%; width:clamp(70px,9vw,102px); height:clamp(70px,9vw,102px); }
  .about-card__sticker--coffee{ top:4%; right:0%; width:clamp(58px,7vw,82px); height:clamp(58px,7vw,82px); }
  .about-card__sticker--ideas{ bottom:1%; left:6%; width:clamp(48px,5.6vw,70px); height:clamp(48px,5.6vw,70px); }
  .about-card__sticker--laptop{ bottom:-1%; right:3%; width:clamp(54px,6.2vw,78px); height:clamp(54px,6.2vw,78px); }
}

/* ---------------- CTA ----------------
   The strongest color moment on the page — a real climax after the
   Process/Projects/About sequence, and a deliberately smooth handoff
   from About's own final (also sun-yellow) card. */
.cta-block{ background:var(--cream); color:var(--deep); padding:clamp(7rem,14vw,13rem) 6vw; text-align:center; }
.cta-block h2{ font-size:clamp(2.6rem,7vw,7.5rem); line-height:.95; max-width:18ch; margin-inline:auto; color:var(--deep); }
.cta-block .cta-accent{ color:var(--pink); }
/* Buttons: a small physical lift, a colour change to berry, and a soft
   ink shadow that grows with the lift — pressed back down on :active.
   The lift is a relative-position nudge (top), not a transform: these
   elements are revealed by GSAP, which owns their inline transform (and
   writes translate:none), so a CSS transform/translate hover is ignored.
   A relative offset moves nothing around it. */
.cta-block .btn:hover, .cta-block .btn:focus-visible{ background:var(--pink); color:var(--deep); top:-3px; box-shadow:0 14px 26px rgba(23,18,15,.2); }
.cta-block .btn:active{ top:-1px; box-shadow:0 6px 12px rgba(23,18,15,.18); }
.cta-block p{ font-size:clamp(1.05rem,1.3vw,1.35rem); max-width:40ch; margin:2rem auto 2.6rem; font-weight:500; line-height:1.6; color:var(--muted); }
.cta-block .btn{
  display:inline-block; position:relative; top:0; background:var(--deep); color:var(--paper); padding:1.15rem 2.8rem; border-radius:999px; font-size:.9rem; letter-spacing:.05em; font-weight:700; text-transform:uppercase;
  transition:top .35s cubic-bezier(.22,.61,.36,1), background-color .3s ease, color .3s ease, box-shadow .35s ease;
}

/* ---------------- contact ---------------- */
.contact{ padding:clamp(6rem,12vw,11rem) 6vw; background:var(--cream); color:var(--black); }
.contact-inner{ display:grid; grid-template-columns:1fr 1fr; gap:4rem; max-width:1800px; margin-inline:auto; }
.contact .left h2{ font-size:clamp(2rem,5vw,4.2rem); }
.contact .left p{ margin-top:1.4rem; color:var(--muted); max-width:32ch; line-height:1.6; font-size:clamp(1rem,1.1vw,1.15rem); }
.contact form{ display:flex; flex-direction:column; gap:1.4rem; }
.honeypot-field{ position:absolute !important; left:-9999px; width:1px; height:1px; overflow:hidden; }
.check-group{ display:flex; flex-direction:column; gap:.6rem; padding-top:.4rem; }
/* A <fieldset> (used for the "preferred contact method" radios, so the
   group has a real accessible name) carries a browser-default border and
   padding a plain <div> doesn't — reset only that, nothing else. */
fieldset.check-group{ border:0; margin:0; padding-inline:0; min-width:0; }
.check-group-label{ font-size:.85rem; color:var(--black); font-weight:600; margin-bottom:.2rem; }
legend.check-group-label{ padding:0; }
.check-item{ display:flex; align-items:center; gap:.6rem; font-size:.95rem; color:var(--black); cursor:pointer; }
.check-item input{ width:16px; height:16px; accent-color:var(--purple); cursor:pointer; }
.field{ position:relative; }
.field input, .field textarea{
  width:100%; background:transparent; border:none; border-bottom:1px solid rgba(23,18,15,.3);
  padding:.8rem 0; font-family:var(--body); font-size:1rem; color:var(--black); outline:none;
}
.field label{ position:absolute; left:0; top:.8rem; color:var(--brown); font-size:1rem; pointer-events:none; transition:transform .25s ease, font-size .25s ease, color .25s ease; }
.field input:focus ~ label, .field input:not(:placeholder-shown) ~ label,
.field textarea:focus ~ label, .field textarea:not(:placeholder-shown) ~ label{ transform:translateY(-1.3rem); font-size:.72rem; color:var(--purple); }
.field input:focus, .field textarea:focus{ border-color:var(--purple); }
.contact button{
  align-self:flex-start; background:var(--black); color:var(--paper); border:none; padding:1rem 2.4rem;
  border-radius:999px; font-size:.85rem; letter-spacing:.05em; font-weight:700; text-transform:uppercase; cursor:pointer;
  position:relative; top:0;
  transition: background-color .35s ease, color .35s ease, top .35s cubic-bezier(.22,.61,.36,1), box-shadow .35s ease;
}
.contact button:not(:disabled):hover, .contact button:not(:disabled):focus-visible{ background:var(--pink); color:var(--deep); top:-2px; box-shadow:0 10px 20px rgba(23,18,15,.18); }
.contact button:not(:disabled):active{ top:0; box-shadow:0 4px 8px rgba(23,18,15,.16); }
.contact button:disabled{ opacity:.6; cursor:wait; }
.contact .thanks{ font-size:.9rem; font-weight:600; opacity:0; height:0; overflow:hidden; }
.contact .thanks.is-visible{ opacity:1; height:auto; margin-top:.5rem; }
.contact .thanks-success{ color:var(--purple); }
.contact .thanks-error{ color:var(--berry); }
@media (max-width:860px){ .contact-inner{ grid-template-columns:1fr; } }

footer{ background:var(--dark); color:rgba(246,241,231,.7); padding:2.2rem 6vw; display:flex; flex-direction:column; gap:1.2rem; font-size:.75rem; letter-spacing:.04em; }
footer a{ color:inherit; transition:color .25s ease; }
footer a:hover, footer a:focus-visible{ color:var(--yellow); }
.footer-top{ display:flex; justify-content:space-between; }
/* Optional footer links (Portfolio Content -> Menu & Footer); not drawn while empty. */
.footer-links{ display:flex; flex-wrap:wrap; gap:.4rem 1.6rem; }
.footer-credit{
  font-family:var(--body); text-transform:uppercase; letter-spacing:.06em;
  font-size:.68rem; color:rgba(246,241,231,.45); padding-top:1.1rem;
  border-top:1px solid rgba(246,241,231,.12);
}

/* ===================================================================
   SINGLE PROJECT (case study page)
   =================================================================== */
.single-project{ background:var(--cream); }
.proj-hero{ padding:clamp(9rem,14vw,12rem) 6vw clamp(3rem,5vw,4rem); max-width:1800px; margin-inline:auto; }
.proj-hero-top{ max-width:70ch; margin-bottom:clamp(2.5rem,5vw,4rem); }
.proj-meta{ display:flex; flex-wrap:wrap; gap:.6rem 1rem; font-size:.8rem; letter-spacing:.04em; text-transform:uppercase; color:var(--brown); font-weight:700; margin-bottom:1.2rem; }
.proj-title{ font-size:clamp(3.2rem,9vw,8rem); line-height:.88; color:var(--deep); margin-bottom:1.4rem; }
.proj-dek{ font-size:clamp(1.1rem,1.6vw,1.5rem); color:var(--muted); line-height:1.6; max-width:48ch; margin-bottom:1.8rem; }
.proj-live-btn{
  display:inline-block; background:var(--deep); color:var(--paper); padding:1rem 2.2rem; border-radius:999px;
  font-size:.82rem; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
  position:relative; top:0;
  transition:top .35s cubic-bezier(.22,.61,.36,1), background-color .3s ease, color .3s ease, box-shadow .35s ease;
}
.proj-live-btn:hover, .proj-live-btn:focus-visible{ background:var(--pink); color:var(--deep); top:-2px; box-shadow:0 10px 20px rgba(23,18,15,.18); }
/* Ongoing/Upcoming: a plain <span> in the same slot, same pill shape, but
   the gray/static status treatment used everywhere else on the site for a
   non-clickable state — no hover response, since it isn't a link. */
.proj-live-btn--muted{ background:var(--border); color:var(--muted); cursor:default; }
.proj-live-btn--muted:hover, .proj-live-btn--muted:focus-visible{ background:var(--border); color:var(--muted); top:0; box-shadow:none; }
.proj-hero-image{ width:100%; aspect-ratio:16/9; border-radius:8px; overflow:hidden; background:var(--paper); }
.proj-hero-image img, .proj-hero-image .project-placeholder{ width:100%; height:100%; object-fit:cover; }

.proj-section{ padding:clamp(3.5rem,7vw,6.5rem) 6vw; max-width:1800px; margin-inline:auto; display:grid; grid-template-columns:1fr 2fr; gap:clamp(2rem,5vw,5rem); border-top:1px solid var(--border); }
.proj-section-label{ font-family:var(--display); font-size:clamp(1.6rem,3vw,2.4rem); color:var(--deep); line-height:1; }
.proj-section-body{ font-size:clamp(1.05rem,1.3vw,1.3rem); color:var(--muted); line-height:1.75; max-width:62ch; }
.proj-section-body p{ margin-bottom:1.1em; }
.proj-section-body p:last-child{ margin-bottom:0; }
/* "What I Solved" / "Current Work" — same .proj-section-body typography,
   as a plain reset list with a small bullet dot instead of paragraphs. */
.proj-solved-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:.7em; }
.proj-solved-list li{ padding-left:1.4em; position:relative; }
.proj-solved-list li::before{ content:''; position:absolute; left:0; top:.65em; width:6px; height:6px; border-radius:50%; background:currentColor; opacity:.5; }
/* "Skills + Tools" — the same pill-chip language already used for the
   Projects archive's category/tag chips (.info .chips span), reused here
   so every project's skills read identically wherever they appear. */
.proj-skills-list{ list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:.6rem; }
.proj-skills-list li{ font-size:.8rem; letter-spacing:.03em; padding:.45rem 1rem; border-radius:999px; border:1px solid var(--border); color:var(--brown); }

.proj-next{
  padding:clamp(4rem,9vw,8rem) 6vw; max-width:1800px; margin-inline:auto; border-top:1px solid var(--border);
  display:flex; flex-wrap:wrap; align-items:baseline; justify-content:space-between; gap:1.5rem;
}
.proj-next .proj-section-label{ width:100%; margin-bottom:.6rem; }
.proj-next-link{ font-family:var(--display); font-size:clamp(2.4rem,6vw,5rem); line-height:.95; color:var(--deep); display:inline-block; position:relative; left:0; transition:color .3s ease, left .4s cubic-bezier(.22,.61,.36,1); }
.proj-next-link:hover, .proj-next-link:focus-visible{ color:var(--pink); left:6px; }
.proj-back-link{ font-size:.85rem; font-weight:700; letter-spacing:.04em; text-transform:uppercase; color:var(--black); transition:color .25s ease; }
.proj-back-link:hover, .proj-back-link:focus-visible{ color:var(--pink); }

/* Project Media / Case Study Gallery — screenshots as editorial sections
   (image, short title, 1-2 sentence description), not a plain gallery. */
.proj-media{ padding:clamp(3.5rem,7vw,6.5rem) 6vw clamp(1rem,2vw,2rem); max-width:1800px; margin-inline:auto; border-top:1px solid var(--border); }
.proj-media-row{ margin-bottom:clamp(2.5rem,5vw,4.5rem); }
.proj-media-row:last-child{ margin-bottom:0; }
.proj-media-row--grid{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(1.5rem,3vw,2.5rem); align-items:start; }
.proj-media-item{ margin:0; }
.proj-media-trigger{ display:block; width:100%; padding:0; border:0; background:none; cursor:zoom-in; }
.proj-media-img{ width:100%; height:auto; display:block; border-radius:8px; }
.proj-media-caption{ margin-top:1rem; }
.proj-media-title{ display:block; font-family:var(--display); font-size:clamp(1.2rem,2vw,1.6rem); letter-spacing:.02em; text-transform:uppercase; color:var(--deep); margin-bottom:.4rem; }
.proj-media-desc{ font-size:clamp(1rem,1.15vw,1.15rem); color:var(--muted); line-height:1.65; max-width:60ch; }
.proj-media-note{ display:block; margin-top:.5rem; font-size:.78rem; color:var(--brown); letter-spacing:.02em; }

/* Lightbox: one shared full-screen viewer for every image on the page. */
html.lightbox-open, html.lightbox-open body{ overflow:hidden !important; }
.proj-lightbox{ position:fixed; inset:0; z-index:150; background:rgba(23,18,15,.92); display:flex; align-items:center; justify-content:center; padding:clamp(1.5rem,5vw,4rem); }
.proj-lightbox[hidden]{ display:none; }
.proj-lightbox-figure{ margin:0; max-width:100%; max-height:100%; display:flex; flex-direction:column; align-items:center; gap:1rem; }
.proj-lightbox-img{ max-width:100%; max-height:78vh; width:auto; height:auto; object-fit:contain; border-radius:6px; }
.proj-lightbox-caption{ color:var(--paper); text-align:center; max-width:60ch; }
.proj-lightbox-caption .proj-media-title{ color:var(--paper); }
.proj-lightbox-caption .proj-media-desc{ color:rgba(250,249,246,.75); }
.proj-lightbox-close{
  position:absolute; top:clamp(1rem,3vw,2rem); right:clamp(1rem,3vw,2rem); width:44px; height:44px; border-radius:50%;
  border:1px solid rgba(250,249,246,.3); background:rgba(250,249,246,.08); color:var(--paper); font-size:1.6rem; line-height:1;
  display:flex; align-items:center; justify-content:center; cursor:pointer; transition:background-color .25s ease;
}
.proj-lightbox-close:hover{ background:rgba(250,249,246,.18); }
.proj-lightbox-nav{
  position:absolute; top:50%; transform:translateY(-50%); width:48px; height:48px; border-radius:50%;
  border:1px solid rgba(250,249,246,.3); background:rgba(250,249,246,.08); color:var(--paper); font-size:1.4rem;
  display:flex; align-items:center; justify-content:center; cursor:pointer; transition:background-color .25s ease;
}
.proj-lightbox-nav:hover{ background:rgba(250,249,246,.18); }
.proj-lightbox-prev{ left:clamp(.5rem,3vw,2rem); }
.proj-lightbox-next{ right:clamp(.5rem,3vw,2rem); }
.proj-lightbox-nav:disabled{ opacity:0; pointer-events:none; }

@media (max-width:860px){
  .proj-section{ grid-template-columns:1fr; gap:1.2rem; }
  .proj-media-row--grid{ grid-template-columns:1fr; }
}

@media (max-width:600px){
  .proj-lightbox-close{ width:38px; height:38px; font-size:1.3rem; }
  .proj-lightbox-nav{ width:38px; height:38px; }
}

/* ===================================================================
   PROJECTS ARCHIVE
   =================================================================== */
.projects-archive{ padding-top:0; }
#chapterArchive{ padding-top:clamp(6rem,12vw,9rem); }

/* ===================================================================
   404
   =================================================================== */
.error-404-page{ min-height:100vh; display:flex; align-items:center; }
.error-404{ padding:clamp(8rem,14vw,11rem) 6vw; max-width:1000px; margin-inline:auto; text-align:center; }
.error-404-code{ display:block; font-family:var(--display); font-size:clamp(4rem,14vw,9rem); color:var(--pink); line-height:1; }
.error-404-title{ font-size:clamp(1.8rem,5vw,3.2rem); color:var(--deep); margin:1rem 0; line-height:1; }
.error-404-copy{ color:var(--muted); font-size:1.05rem; margin-bottom:2rem; }
.error-404-link{ display:inline-block; background:var(--deep); color:var(--paper); transition:background-color .3s ease, color .3s ease; padding:1rem 2.2rem; border-radius:999px; font-size:.82rem; font-weight:700; letter-spacing:.05em; text-transform:uppercase; }
.error-404-link:hover, .error-404-link:focus-visible{ background:var(--pink); color:var(--deep); }

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