/* CUD ALT-2 — shared custom styles on top of Tailwind utilities */

/* ── Colour tokens ──
   Channel triplets consumed by Tailwind (theme.js) as
   rgb(var(--x) / alpha). html.theme-dark swaps ink and bone —
   the whole site inverts. Set from Admin ▸ Appearance ▸ Theme. */
:root {
  --ink: 20 20 18;        /* #141412 */
  --bone: 244 242 237;    /* #F4F2ED */
  --concrete: 138 133 120;/* #8A8578 */
  /* Gray ramp — compiled Tailwind's neutral-* classes read these
     (see tailwind.config.js), so secondary text and hairlines adapt
     to the theme instead of shipping fixed hex.
     Light values: one step darker than stock so the 10–11px mono
     metadata clears WCAG AA on bone. */
  --n700: 58 57 52;       /* body copy        (#3A3934, 9.9:1 on bone) */
  --n600: 74 72 66;       /* strong secondary (#4A4842, 7.6:1) */
  --n500: 88 85 78;       /* mono metadata    (#58554E, 6.1:1) */
  --n400: 128 124 115;    /* faint numbers / hairline borders (#807C73) */
  --n300: 160 156 146;
}
html.theme-dark {
  --ink: 244 242 237;
  --bone: 20 20 18;
  /* Inverted ramp — same hierarchy, readable on ink. */
  --n700: 205 202 193;    /* body copy        (#CDCAC1, 9.6:1 on ink) */
  --n600: 182 178 168;    /* strong secondary (#B6B2A8, 7.5:1) */
  --n500: 162 158 148;    /* mono metadata    (#A29E94, 5.9:1) */
  --n400: 118 114 105;    /* faint / hairlines (#767269) */
  --n300: 90 87 80;
}

/* Native form controls (dropdown panels, scrollbars, date pickers) are
   drawn by the OS — color-scheme tells it which side of the theme we're
   on, so a dark page gets a dark dropdown list instead of pale text on
   a white panel. The explicit option colors are a belt-and-braces
   fallback for engines that ignore color-scheme. */
:root { color-scheme: light; }
html.theme-dark { color-scheme: dark; }
select option {
  background-color: rgb(var(--bone));
  color: rgb(var(--ink));
}
html { background: rgb(var(--bone)); }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}
::selection { background: rgb(var(--ink)); color: rgb(var(--bone)); }

/* Image frames — understated zoom + fade on hover, blueprint captions.
   Frames with .sketch-reveal opt out entirely: their layers carry their
   own filters, and this generic hover rule must never override them. */
.frame { position: relative; overflow: hidden; background: #1B1A17; }
.frame:not(.sketch-reveal) img, .frame:not(.sketch-reveal) .ph {
  transition: transform 700ms cubic-bezier(0.16, 1, 0.3, 1), filter 500ms ease;
  filter: saturate(0.82);
  will-change: transform;
}
.frame:not(.sketch-reveal):hover img, .frame:not(.sketch-reveal):focus-within img,
.frame:not(.sketch-reveal):hover .ph, .frame:not(.sketch-reveal):focus-within .ph { transform: scale(1.035); filter: saturate(1); }
.frame:not(.sketch-reveal)::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(20, 20, 18, 0.30), transparent 45%);
  opacity: 0; transition: opacity 500ms ease; pointer-events: none;
}
.frame:not(.sketch-reveal):hover::after, .frame:not(.sketch-reveal):focus-within::after { opacity: 1; }

/* Filter buttons — active state must follow the theme */
.filter-btn[aria-pressed="true"] { color: rgb(var(--ink)); }
.filter-btn[aria-pressed="true"] .tick { opacity: 1; }

/* Focus visibility everywhere */
a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, summary:focus-visible {
  outline: 2px solid rgb(var(--ink)); outline-offset: 3px; border-radius: 0;
}

/* Forms — flat, hairline, sharp */
.field {
  width: 100%; background: transparent; border: 0;
  border-bottom: 1px solid rgba(120, 113, 108, 0.4);
  padding: 0.75rem 0; font-size: 15px; color: rgb(var(--ink));
}
.field::placeholder { color: rgb(var(--n400)); }
.field:focus { outline: none; border-bottom-color: rgb(var(--ink)); }
select.field { appearance: none; -webkit-appearance: none; cursor: pointer; }

/* Mobile menu */
#mobileMenu { display: none; }
#mobileMenu.open { display: block; }

/* Filmstrip: scrolls without showing a scrollbar */
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
.no-scrollbar::-webkit-scrollbar { display: none; }

/* ── 3D massing level labels (homepage hero) ── */
.m-lbl {
  position: absolute; transform: translate(-50%, -50%);
  font-family: 'JetBrains Mono', monospace; font-size: 9px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: rgb(var(--ink)); background: rgb(var(--bone) / 0.85);
  border: 1px solid rgba(120, 113, 108, 0.4);
  padding: 3px 7px; white-space: nowrap;
  opacity: 0; transition: opacity 400ms ease;
}
.m-lbl.show { opacity: 1; }
@media (max-width: 640px) { .m-lbl { display: none; } }

/* ── Sketch-reveal (about page) ──
   The image reads as a pencil drawing; a soft circular lens (~2cm)
   under the cursor reveals the finished render. The sketch is made
   from the render itself: grayscale base + inverted blurred copy in
   color-dodge = classic pencil-sketch compositing, no second asset. */
@property --sr-r {
  syntax: '<length>';
  initial-value: 0px;
  inherits: true;
}
/* Tunable from Admin ▸ Appearance ▸ Sketch reveal:
   --sr-radius (lens size), --sr-soft (edge feather),
   and .sr-invert flips the direction (render at rest, sketch in lens). */
.sketch-reveal { cursor: crosshair; --mx: 50%; --my: 50%; --sr-r: 0px; --sr-radius: 76px; --sr-soft: 12px; transition: --sr-r 450ms cubic-bezier(0.16, 1, 0.3, 1); }
.sketch-reveal:hover { --sr-r: var(--sr-radius); }
.sr-sketch { position: absolute; inset: 0; background: rgb(var(--bone)); }
.sr-sketch img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.sr-sketch .sr-base { filter: grayscale(1) contrast(1.08) brightness(1.04); }
.sr-sketch .sr-dodge { filter: grayscale(1) invert(1) blur(3px); mix-blend-mode: color-dodge; }
/* Default: resting state = full pencil sketch; the circle around the
   cursor is a hole cut in the sketch layer, revealing the render. */
.sketch-reveal .sr-sketch {
  -webkit-mask-image: radial-gradient(circle at var(--mx) var(--my), transparent 0 calc(var(--sr-r) - var(--sr-soft)), #000 var(--sr-r));
          mask-image: radial-gradient(circle at var(--mx) var(--my), transparent 0 calc(var(--sr-r) - var(--sr-soft)), #000 var(--sr-r));
}
/* Inverted direction: render at rest, the lens shows the sketch. */
.sketch-reveal.sr-invert .sr-sketch {
  -webkit-mask-image: radial-gradient(circle at var(--mx) var(--my), #000 0 calc(var(--sr-r) - var(--sr-soft)), transparent var(--sr-r));
          mask-image: radial-gradient(circle at var(--mx) var(--my), #000 0 calc(var(--sr-r) - var(--sr-soft)), transparent var(--sr-r));
}
/* the sketch layers are decorative; hover styling stays on the figure */
.sr-sketch, .sr-sketch img { pointer-events: none; }

/* ── FULL-BLEED HERO (strip-wallpaper mode) ──
   The media fills the entire hero edge-to-edge behind the headline;
   #heroTint sits between wallpaper and text for contrast. */
#hero.hero-full > .grid { position: relative; min-height: 88svh; }
#hero.hero-full #heroMedia {
  position: absolute; inset: 0; z-index: 0;
  aspect-ratio: auto; border: 0;
}
#hero.hero-full #heroCopy {
  position: relative; z-index: 10;
  border-right: 0;
  justify-content: center;
}
#heroTint { position: absolute; inset: 0; pointer-events: none; }
/* keep the caption above the tint */
#hero.hero-full #heroCaption { z-index: 20; }

/* ── HERO STRIP WALLPAPER (Admin ▸ Appearance ▸ Hero media) ──
   Vertical columns of project covers behind a hairline rhythm, each
   drifting slowly; odd columns run the opposite way. Content is
   duplicated once inside .hstrip-run, so -50% loops seamlessly. */
#heroStrips { display: flex; }
.hstrip-col {
  flex: 1 1 0; min-width: 0; overflow: hidden; position: relative;
  border-right: 1px solid rgb(var(--n400) / 0.4);
}
.hstrip-col:last-child { border-right: 0; }
.hstrip-run { display: flex; flex-direction: column; }
.hstrip-col .hstrip-run { animation: hstripUp linear infinite; animation-duration: inherit; }
.hstrip-col.hstrip-rev .hstrip-run { animation-name: hstripDown; }
.hstrip-col { animation-duration: 80s; }   /* overridden inline per column */
.hstrip-run img {
  width: 100%; aspect-ratio: 4/5; object-fit: cover; display: block;
  border-bottom: 1px solid rgb(var(--n400) / 0.4);
  filter: saturate(0.82);
}
@keyframes hstripUp { from { transform: translateY(0); } to { transform: translateY(-50%); } }
@keyframes hstripDown { from { transform: translateY(-50%); } to { transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) {
  .hstrip-col .hstrip-run { animation: none; }
}

/* ── STARTUP SCREEN ──
   #splash carries critical styles inline (paints before CSS loads);
   everything visual lives here. One sp-<effect> class on #splash picks
   the motion treatment — chosen in Admin ▸ Appearance ▸ Intro screen. */
#splash { transition: opacity 700ms ease; }
#splash.splash-out { opacity: 0; pointer-events: none; }
.splash-lockup { display: flex; flex-direction: column; align-items: center; gap: 22px; position: relative; }
.splash-mark { height: 96px; width: auto; }
.splash-word { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 19px; letter-spacing: 0.34em; color: #F4F2ED; white-space: nowrap; }
.splash-word span { display: inline-block; }
.splash-sub { font-family: 'JetBrains Mono', monospace; font-size: 9px; letter-spacing: 0.32em; text-transform: uppercase; color: #8A8578; }
@media (max-width: 640px) {
  .splash-mark { height: 72px; }
  .splash-word { font-size: 13px; letter-spacing: 0.22em; }
  .splash-sub { font-size: 8px; letter-spacing: 0.2em; }
}

/* 01 · Fade in */
.sp-fade .splash-lockup { animation: spFade 1200ms ease both; }
@keyframes spFade { from { opacity: 0; } to { opacity: 1; } }

/* 02 · Shimmer — a light sheen sweeps the lockup */
.sp-shimmer .splash-lockup { animation: spFade 900ms ease both; }
.sp-shimmer .splash-lockup::after {
  content: ''; position: absolute; inset: -20%; pointer-events: none;
  background: linear-gradient(105deg, transparent 42%, rgba(244, 242, 237, 0.34) 50%, transparent 58%);
  transform: translateX(-120%);
  animation: spSheen 1500ms cubic-bezier(0.4, 0, 0.2, 1) 500ms both;
}
@keyframes spSheen { to { transform: translateX(120%); } }

/* 03 · Turning — the mark turns in on its vertical axis */
.sp-turn .splash-lockup { animation: spFade 800ms ease both; }
.sp-turn .splash-mark { animation: spTurn 1400ms cubic-bezier(0.16, 1, 0.3, 1) both; }
@keyframes spTurn { from { transform: perspective(700px) rotateY(100deg); opacity: 0; } to { transform: perspective(700px) rotateY(0); opacity: 1; } }

/* 04 · Swivel — overshoots and settles */
.sp-swivel .splash-lockup { animation: spFade 700ms ease both; }
.sp-swivel .splash-mark { animation: spSwivel 1700ms ease-out both; }
@keyframes spSwivel {
  0% { transform: perspective(700px) rotateY(-85deg); opacity: 0; }
  55% { transform: perspective(700px) rotateY(18deg); opacity: 1; }
  78% { transform: perspective(700px) rotateY(-8deg); }
  100% { transform: perspective(700px) rotateY(0); }
}

/* 05 · Rise — lifts in from below */
.sp-rise .splash-lockup { animation: spRise 1300ms cubic-bezier(0.16, 1, 0.3, 1) both; }
@keyframes spRise { from { transform: translateY(46px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }

/* 06 · Zoom — scales up from small */
.sp-zoom .splash-lockup { animation: spZoom 1300ms cubic-bezier(0.16, 1, 0.3, 1) both; }
@keyframes spZoom { from { transform: scale(0.3); opacity: 0; } to { transform: scale(1); opacity: 1; } }

/* 07 · Focus — sharpens out of blur */
.sp-blur .splash-lockup { animation: spBlur 1500ms ease both; }
@keyframes spBlur { from { filter: blur(18px); opacity: 0; } to { filter: blur(0); opacity: 1; } }

/* 08 · Wipe — revealed bottom-to-top like a print pull */
.sp-wipe .splash-lockup { animation: spWipe 1400ms cubic-bezier(0.16, 1, 0.3, 1) both; }
@keyframes spWipe { from { clip-path: inset(100% 0 0 0); opacity: 1; } to { clip-path: inset(0 0 0 0); opacity: 1; } }

/* 09 · Pulse — a confident heartbeat */
.sp-pulse .splash-lockup { animation: spPulse 1600ms ease both; }
@keyframes spPulse {
  0% { transform: scale(0.88); opacity: 0; }
  45% { transform: scale(1.05); opacity: 1; }
  70% { transform: scale(0.97); }
  100% { transform: scale(1); }
}

/* 10 · Glitch — brutalist signal jitter, then locks */
.sp-glitch .splash-lockup { animation: spGlitch 1100ms steps(2, jump-none) both; }
@keyframes spGlitch {
  0% { transform: translate(-14px, 3px); opacity: 0; clip-path: inset(20% 0 40% 0); }
  20% { transform: translate(9px, -5px); opacity: 1; clip-path: inset(55% 0 10% 0); }
  40% { transform: translate(-6px, 2px); clip-path: inset(5% 0 65% 0); }
  60% { transform: translate(4px, -2px); clip-path: inset(35% 0 25% 0); }
  80% { transform: translate(-2px, 1px); clip-path: inset(0 0 0 0); }
  100% { transform: translate(0, 0); clip-path: inset(0 0 0 0); }
}

/* 11 · Letters — the wordmark types itself in */
.sp-letters .splash-mark { animation: spFade 1000ms ease both; }
.sp-letters .splash-sub { animation: spFade 900ms ease 1300ms both; }
.sp-letters .splash-word span { opacity: 0; animation: spLetter 420ms ease both; animation-delay: calc(300ms + var(--i) * 52ms); }
@keyframes spLetter { from { opacity: 0; transform: translateY(0.5em); } to { opacity: 1; transform: translateY(0); } }

/* 12 · Orbit — swings in with a slow rotation */
.sp-orbit .splash-lockup { animation: spFade 600ms ease both; }
.sp-orbit .splash-mark { animation: spOrbit 1600ms cubic-bezier(0.16, 1, 0.3, 1) both; }
@keyframes spOrbit { from { transform: rotate(-200deg) scale(0.5); opacity: 0; } to { transform: rotate(0) scale(1); opacity: 1; } }

/* ── ?preview=1: bare 3D model for the Admin panel's live preview ── */
.preview3d #site-header, .preview3d #site-footer,
.preview3d #heroCopy, .preview3d #massingHint,
.preview3d main > section:not(#hero) { display: none !important; }
.preview3d #hero { border: none; }
.preview3d #hero > .grid { min-height: 100svh; }
.preview3d #heroMedia { position: fixed; inset: 0; aspect-ratio: auto; }
