:root{
  /* Ink and lead. Printer's blacks, vermilion as the one voice. */
  --base:#0f0f0f;
  --surface:#1a1a1a;
  --text:#e8e6e1;
  --subtle:#b5b0a8;
  --muted:#8a8681;
  --edge:rgba(232,230,225,.11);
  --accent:#d4472f;

  /* 4px scale. Nothing off it. */
  --s1:4px; --s2:8px;  --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:48px; --s8:64px; --s9:96px;

  --r1:2px; --r2:6px;

  --gut:var(--s6);
  --hdh:var(--s8);
  --pulse:0;
  --wght:600; --slnt:0; --casl:0; --ls:-0.03em;
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{
  margin:0;background:var(--base);color:var(--text);
  font-family:Recursive,system-ui,sans-serif;
  font-variation-settings:"MONO" 0,"CASL" 0,"slnt" 0,"CRSV" 0;
  font-size:15px;line-height:1.6;overflow-x:hidden;
}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}

/* --- ambient ------------------------------------------------------------ */
#bg{
  position:fixed;inset:0;z-index:0;pointer-events:none;
  background:
    radial-gradient(58% 48% at 20% 16%, color-mix(in srgb,var(--accent) 11%,transparent), transparent 66%),
    radial-gradient(90% 70% at 50% 50%, transparent 38%, rgba(0,0,0,.5) 100%),
    var(--base);
  transition:background .9s linear;
}
#lamp{
  position:fixed;inset:0;z-index:1;pointer-events:none;
  background:radial-gradient(var(--spread,540px) circle at var(--lx,50%) var(--ly,40%),
    rgba(255,255,255,.07),rgba(255,255,255,.016) 46%,transparent 72%);
}
#grain{
  position:fixed;inset:-60px;z-index:9;pointer-events:none;
  opacity:.1;mix-blend-mode:screen;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  animation:grainDrift 6s steps(1) infinite;
}
@keyframes grainDrift{
  0%{transform:translate(0,0)}          20%{transform:translate(-18px,9px)}
  40%{transform:translate(11px,-14px)}  60%{transform:translate(-7px,-8px)}
  80%{transform:translate(15px,12px)}   100%{transform:translate(0,0)}
}

.stage{position:relative;z-index:2}
.stage[hidden]{display:none}

/* --- header ------------------------------------------------------------- */
.hd{
  position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:var(--s5);
  height:var(--hdh);padding:0 var(--gut);
  background:rgba(15,15,15,calc(var(--hp,0)*.9));
  backdrop-filter:blur(calc(var(--hp,0)*16px));
  -webkit-backdrop-filter:blur(calc(var(--hp,0)*16px));
  border-bottom:1px solid rgba(232,230,225,calc(var(--hp,0)*.11));
}
.hd .mark{font-size:17px;font-weight:600;letter-spacing:-.03em;line-height:1;
  transform-origin:0 50%;will-change:transform}
.hd .slot{flex:1;min-width:0;height:1px}
.hd .spacer{flex:1}
.hd .navs{display:flex;gap:var(--s4)}
.hd .navs a{
  font-variation-settings:"MONO" 1;font-size:11px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--muted);transition:color .3s;
}
.hd .navs a:hover{color:var(--accent)}

/* zero blur: an overprint misregistration, not a glow */
.livemark{
  display:inline-block;transform-origin:50% 60%;will-change:transform;
  transform:perspective(620px)
    rotateY(calc(var(--mx,0)*var(--tilt,1)*17deg))
    rotateX(calc(var(--my,0)*var(--tilt,1)*-12deg))
    scale(calc(1 + var(--mp,0)*.05));
  text-shadow:calc(var(--mx,0)*var(--tilt,1)*-7px) calc(var(--my,0)*var(--tilt,1)*-6px) 0 rgba(212,71,47,.32); /* unslop-ignore */
}
.dot{
  font-style:normal;color:var(--accent);display:inline-block;
  transform:scale(calc(1 + var(--pulse)*.34));
  transition:color .3s;
}

/* --- hero --------------------------------------------------------------- */
.hero{padding:var(--s9) var(--gut) var(--s8)}
.hero h1,.dockfx{
  margin:0;font-size:56px;font-weight:600;line-height:1.03;letter-spacing:var(--ls);
  font-variation-settings:"MONO" 0,"CRSV" 0,"CASL" var(--casl),"slnt" var(--slnt),"wght" var(--wght);
}
.hero h1 .ln{display:block}
.sub{margin:var(--s5) 0 0;max-width:44ch;color:var(--subtle);font-size:15px;
  line-height:1.6;opacity:var(--subOp,1)}
.ph{visibility:hidden}
.dockfx{
  position:fixed;left:0;top:0;transform-origin:0 0;z-index:55;
  pointer-events:none;white-space:nowrap;will-change:transform;
}
/* max-content so offsetWidth is the line's own text, not the widest line —
   the dock tween measures line one to place line two beside it */
.dockfx .ln{display:block;width:max-content;will-change:transform}

/* --- index list --------------------------------------------------------- */
.split{display:flex;gap:var(--s5);padding:0 var(--gut) var(--s9);align-items:flex-start}

.panel{
  flex:1 1 56%;min-width:0;border-radius:var(--r2);
  background:transparent;border:1px solid transparent;
  transition:flex-basis .72s cubic-bezier(.2,.85,.24,1),background .5s,
    border-color .5s,padding .5s;
}
.split.open .panel{
  flex-basis:43%;padding:var(--s4);
  background:rgba(232,230,225,.04);
  border-color:var(--edge);
  backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);
}
.back{
  display:block;max-height:0;overflow:hidden;opacity:0;
  font-variation-settings:"MONO" 1;font-size:10.5px;letter-spacing:.13em;
  text-transform:uppercase;color:var(--muted);
  transition:max-height .5s,opacity .35s,color .25s;
}
.back:hover{color:var(--accent)}
.split.open .back{max-height:var(--s5);opacity:1;margin-bottom:var(--s3)}

.list{border-top:1px solid var(--edge)}
.split.open .list{border-top-color:transparent}

.row{
  --pa:var(--accent); --l:0; --fade:1;
  position:relative;display:block;width:100%;text-align:left;
  padding:var(--s3) 0;border-bottom:1px solid var(--edge);opacity:var(--fade);
  transition:padding-left .4s cubic-bezier(.2,.85,.24,1),background .4s,
    opacity .55s cubic-bezier(.3,.8,.3,1);
}
.row::before{
  content:"";position:absolute;left:-2px;top:14%;bottom:14%;width:2px;
  background:var(--pa);transform:scaleY(0);
  transition:transform .45s cubic-bezier(.2,.85,.24,1);
}
.row-top{display:flex;align-items:baseline;gap:var(--s3);min-width:0}
.row-name{
  flex:0 0 auto;font-size:19px;font-weight:400;letter-spacing:-.014em;
  opacity:calc(.5 + var(--l)*.5);transition:color .35s,opacity .2s;
}
.row-blurb{
  flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;
  font-size:12.5px;line-height:1.3;color:var(--subtle);
  clip-path:inset(0 100% 0 0);transform:translateX(-12px);opacity:0;
  mask-image:linear-gradient(90deg,#000 84%,transparent 100%);
  -webkit-mask-image:linear-gradient(90deg,#000 84%,transparent 100%);
  transition:clip-path .55s cubic-bezier(.18,.85,.24,1),
    transform .55s cubic-bezier(.18,.85,.24,1),opacity .34s;
}
.row-meta{
  flex:0 0 auto;margin-left:auto;font-variation-settings:"MONO" 1;
  font-size:10px;letter-spacing:.11em;text-transform:uppercase;
  color:var(--muted);transition:color .35s;
}
.row:hover,.row.live{padding-left:var(--s3);
  background:color-mix(in srgb,var(--pa) 8%,transparent)}
.row:hover::before,.row.live::before{transform:scaleY(1)}
.row:hover .row-name,.row.live .row-name{color:var(--pa);opacity:1}
.row:hover .row-meta{color:color-mix(in srgb,var(--pa) 70%,var(--muted))}
/* the blurb is a hover teaser for browsing — once a row is open the facts
   block says the same thing with more structure, so it steps back rather
   than sitting redundantly (and getting clipped) alongside it */
.row:not(.live):hover .row-blurb{clip-path:inset(0 0 0 0);transform:none;opacity:1}
.row:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

/* grid rows animate 0fr to 1fr, so there is no max-height for copy to outgrow */
.detail{display:grid;grid-template-rows:0fr;opacity:0;
  transition:grid-template-rows .7s cubic-bezier(.2,.85,.24,1),opacity .4s .14s}
.detail-in{overflow:hidden;min-height:0}
.row.live .detail{grid-template-rows:1fr;opacity:1}
.tools{display:flex;flex-wrap:wrap;gap:var(--s2);margin-top:var(--s3)}
.tool{
  font-variation-settings:"MONO" 1;font-size:10px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--pa);
  border:1px solid color-mix(in srgb,var(--pa) 45%,transparent);
  border-radius:100px;padding:var(--s1) var(--s3);
}

.facts{display:block}
.fact{display:flex;gap:var(--s3);padding:var(--s1) 0;font-size:12.5px;line-height:1.5}
.fact-k{
  flex:0 0 88px;font-variation-settings:"MONO" 1;font-size:10px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--pa);padding-top:2px;
}
.fact-v{flex:1;min-width:0;color:var(--subtle)}

/* --- preview ------------------------------------------------------------ */
.prev{
  flex:1 1 44%;position:relative;overflow:hidden;border-radius:var(--r1);
  transition:flex-basis .72s cubic-bezier(.2,.85,.24,1),
    height .72s cubic-bezier(.2,.85,.24,1),
    transform .6s cubic-bezier(.2,.85,.24,1),
    opacity .3s ease;
}
/* the side preview is the only place images show on desktop — it stays
   visible and grows when a row opens, and centres in the viewport's
   vertical middle rather than tracking the row's own position (see
   centerPreview in main.js) — switching between open rows re-centres on
   the same spot instead of visibly jumping to meet each row */
.split.open .prev{flex-basis:57%}
.prev .frame{position:absolute;inset:0;opacity:0;transition:opacity .42s ease}
.prev .frame.on{opacity:1}
.frame{position:relative;width:100%;height:100%;overflow:hidden}
.art{position:absolute;inset:0;cursor:zoom-in}
/* resting values lifted from .15/.52 — real photography has detail to lose
   where the placeholder gradients had none */
.prev .art,.prev .cslide{filter:saturate(calc(.28 + var(--l,0)*.85)) brightness(calc(.62 + var(--l,0)*.42))}

/* multi-image projects: a single-frame carousel instead of a wrapped grid —
   cover-cropped to the same project ratio as the single-image .art case, so
   prev's height math in main.js never needs to special-case slide count.
   the lightbox is the escape hatch for the uncropped original. */
.carousel{position:absolute;inset:0}
.cslides{position:absolute;inset:0}
/* object-position anchors top rather than centre — these are page screenshots,
   tall and various ratios, and the top (hero/header) is reliably the meaningful
   part of a cover-crop; a centred crop can land mid-page arbitrarily */
.cslide{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center top;
  opacity:0;cursor:zoom-in;transition:opacity .5s ease}
.cslide.on{opacity:1}
.cnav{
  position:absolute;top:50%;translate:0 -50%;z-index:2;width:32px;height:32px;
  display:flex;align-items:center;justify-content:center;font-size:19px;line-height:1;
  color:var(--text);background:rgba(15,15,15,.55);border-radius:100px;
  opacity:0;transition:opacity .25s,background .25s;
}
.carousel:hover .cnav{opacity:1}
.cnav:hover{background:rgba(15,15,15,.82)}
.cnav.prev{left:var(--s3)}
.cnav.next{right:var(--s3)}
.cdots{position:absolute;bottom:var(--s3);left:50%;translate:-50% 0;z-index:2;display:flex;gap:6px}
.cdot{width:6px;height:6px;border-radius:100px;background:rgba(232,230,225,.4);
  cursor:pointer;transition:background .25s,transform .25s}
.cdot.on{background:var(--text);transform:scale(1.3)}

/* Project artwork. Order matches the projects array in main.js.
   MikFlix (a3) is multi-image now, rendered as a .carousel instead — see
   the projects array's images list rather than a background rule here. */
.a1 {background:url("/assets/img/dewlora.webp")   center/cover}
.a2 {background:url("/assets/img/truspray.webp")  center/cover}
.a4 {background:url("/assets/img/goodhands.webp") center/cover}
.a5 {background:url("/assets/img/kronan.webp")    center/cover}
.a6 {background:url("/assets/img/emery.webp")     center/cover}
.a7 {background:url("/assets/img/discrete.webp")  center/cover}
.a8 {background:url("/assets/img/taptec.webp")    center/cover}
.a9 {background:url("/assets/img/minreid.webp")   center/cover}
.a10{background:url("/assets/img/hittit.webp")    center/cover}
.a11{background:url("/assets/img/koru.png")       center/cover}

/* --- detail gallery ------------------------------------------------------
   images that expand in above the copy when a row/band is opened, rather
   than a persistent thumbnail that has to survive every filter/state combo.
   default (desktop rows, and single-image bands): wraps side by side,
   natural ratio, nothing cropped away — there's room for that on desktop. */
.gallery{display:flex;flex-wrap:wrap;gap:var(--s3);margin-bottom:var(--s3)}
.gallery:empty{display:none;margin:0}
.shot{
  flex:1 1 260px;max-width:100%;border-radius:var(--r1);
  overflow:hidden;background:var(--surface);
  outline:1px solid color-mix(in srgb,var(--pa) 30%,transparent);outline-offset:-1px;
}
.shot img{width:100%;height:auto;display:block}

/* mobile only: multiple images ride one swipeable strip instead of
   wrapping — a scroll carousel reads as a mobile pattern, and a fixed
   crop keeps the cards a consistent size as you scroll between them */
.band .gallery.multi{
  flex-wrap:nowrap;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;
  margin:0 calc(var(--gut) * -1) var(--s3);padding:0 var(--gut);
}
.band .gallery.multi::-webkit-scrollbar{display:none}
.band .gallery.multi .shot{flex:0 0 78%;aspect-ratio:16/10;scroll-snap-align:start}
.band .gallery.multi .shot img{height:100%;object-fit:cover}

/* --- specimen ----------------------------------------------------------- */
.spec-hero{padding:var(--s7) var(--gut) var(--s6);text-align:center;
  border-bottom:1px solid var(--edge)}
.spec-mark{
  font-size:104px;font-weight:700;line-height:.86;letter-spacing:var(--ls);
  font-variation-settings:"MONO" 0,"CRSV" 0,"CASL" var(--casl),"slnt" var(--slnt),"wght" var(--wght);
}
.spec-hero .sub{margin:var(--s5) auto 0;text-align:center;font-size:14px}
.slot-spec{flex:0 0 auto;width:var(--s8)}
.band{
  --pa:var(--accent); --l:0;
  position:relative;display:block;width:100%;
  text-align:left;padding:var(--s4) var(--gut);border-bottom:1px solid var(--edge);
  background:color-mix(in srgb,var(--pa) calc(var(--l)*12%),transparent);
}
.band::before{content:"";position:absolute;left:0;top:10%;bottom:10%;width:3px;
  background:var(--pa);transform:scaleY(var(--l))}
.band-top{display:flex;align-items:center;gap:var(--s4);width:100%}
.band .txt{flex:1;min-width:0}
.band h3{margin:0 0 var(--s1);font-size:19px;font-weight:400;letter-spacing:-.015em;
  color:color-mix(in srgb,var(--pa) calc(var(--l)*90%),var(--text))}
.band .meta{font-variation-settings:"MONO" 1;font-size:9.5px;letter-spacing:.11em;
  text-transform:uppercase;color:var(--muted)}
.band .detail{margin-top:0}
.band.open .detail{grid-template-rows:1fr;opacity:1;margin-top:var(--s3)}
.band:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

footer.pg{
  padding:var(--s6) var(--gut) var(--s8);border-top:1px solid var(--edge);
  color:var(--muted);font-variation-settings:"MONO" 1;
  font-size:10.5px;letter-spacing:.11em;text-transform:uppercase;
}

/* the footer link and the flying #emailFx clone share this sweep — a bright
   band crosses the letters once via a text-clipped gradient sliding through */
@keyframes sweep{from{background-position:140% 0}to{background-position:-40% 0}}
.contact-email{
  display:inline-block;color:inherit;transition:color .3s;
}
.contact-email:hover{
  background:linear-gradient(100deg,var(--text) 35%,var(--accent) 50%,var(--text) 65%);
  background-size:260% 100%;-webkit-background-clip:text;background-clip:text;
  color:transparent;animation:sweep .9s ease;
}

/* --- lightbox --------------------------------------------------------------
   full-res, uncropped view of whatever image was just clicked (art frame,
   carousel slide, or a mobile band's gallery shot). [hidden] blocks the
   opacity transition from ever playing, so main.js clears it a frame before
   adding .on, and re-adds it only after the fade-out transition finishes. */
.lightbox{
  position:fixed;inset:0;z-index:90;display:flex;align-items:center;justify-content:center;
  background:rgba(10,10,10,.92);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  opacity:0;transition:opacity .3s ease;
}
.lightbox[hidden]{display:none}
.lightbox.on{opacity:1}
.lightbox img{max-width:92vw;max-height:88vh;object-fit:contain;border-radius:var(--r1);
  box-shadow:0 30px 80px rgba(0,0,0,.5)}
.lb-close{
  position:absolute;top:var(--s5);right:var(--s5);width:36px;height:36px;
  display:flex;align-items:center;justify-content:center;font-size:24px;line-height:1;
  color:var(--text);background:rgba(255,255,255,.08);border-radius:100px;transition:background .25s;
}
.lb-close:hover{background:rgba(255,255,255,.18)}

/* --- contact fx --------------------------------------------------------------
   a free-floating clone of the footer email, flown to screen-centre when
   Contact is clicked in either nav (see flyEmailToCenter in main.js). Both
   ends of the flight are set inline from JS (not via a class) — browsers
   interpolate the computed pixel value of left/top even when the JS target
   is a percentage string, so no rect-lerp like the header dock's tween is
   needed, but the target has to be inline too, or the class would lose to
   the inline styles the flight's start position already set. */
.emailfx{
  position:fixed;left:0;top:0;z-index:80;pointer-events:none;white-space:nowrap;
  font-variation-settings:"MONO" 1;letter-spacing:.13em;text-transform:uppercase;
  opacity:0;transform:translate(0,0) scale(1);
  transition:left .85s cubic-bezier(.2,.85,.24,1),top .85s cubic-bezier(.2,.85,.24,1),
    transform .85s cubic-bezier(.2,.85,.24,1),opacity .3s;
}
.emailfx.sweep{
  background:linear-gradient(100deg,var(--text) 35%,var(--accent) 50%,var(--text) 65%);
  background-size:260% 100%;-webkit-background-clip:text;background-clip:text;
  color:transparent;animation:sweep .9s ease;
}

/* --- narrow --------------------------------------------------------------
   kept in sync with the JS `narrow` breakpoint — portrait only, so a
   landscape phone gets the full desktop sizing along with the desktop stage */
@media (max-width:860px) and (orientation:portrait){
  :root{--gut:var(--s5);--hdh:var(--s7)}
  .hero{padding:var(--s7) var(--gut) var(--s6)}
  .hero h1,.dockfx{font-size:34px}
  .spec-mark{font-size:76px}
}

/* a phone in landscape still gets the desktop stage, but a full-width
   row is tight — the index/kind/year drop below the title instead of
   fighting the blurb for space and running into the preview column */
@media (orientation:landscape) and (max-width:900px){
  .row-top{flex-wrap:wrap}
  .row-meta{flex-basis:100%;margin-left:0;margin-top:2px}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  #lamp{display:none}
  #grain{animation:none}
  .livemark{transform:none;text-shadow:none}
  .row-blurb{clip-path:none;transform:none;opacity:1}
  .prev .art,.prev .cslide{filter:saturate(.9) brightness(.92) !important}
  .cslide{transition:none}
  .emailfx{transition:opacity .2s}
  .contact-email:hover,.emailfx.sweep{animation:none}
}
