/* ============================================================
   shutter kif. — black & white, gritty, minimal
   Composed sections sit on a 16:9 stage carrying the exact PSD
   coordinates in percent.
   ============================================================ */

@font-face{font-family:'Helv';src:url('../assets/fonts/Helvetica.woff') format('woff'),url('../assets/fonts/Helvetica.ttf') format('truetype');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'Helv';src:url('../assets/fonts/Helvetica-Oblique.woff') format('woff'),url('../assets/fonts/Helvetica-Oblique.ttf') format('truetype');font-weight:400;font-style:italic;font-display:swap}
@font-face{font-family:'Helv';src:url('../assets/fonts/Helvetica-Bold.woff') format('woff'),url('../assets/fonts/Helvetica-Bold.ttf') format('truetype');font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:'VCR';src:url('../assets/fonts/VCR.woff') format('woff'),url('../assets/fonts/VCR.ttf') format('truetype');font-weight:400;font-display:swap}

:root{
  --light:#ffffff;
  --dark:#000000;
  --on-light:#000000;
  --on-dark:#ffffff;

  /* paper = the tone actually painted behind the cut-outs.
     --el-inv / --el-blend are derived from it, never from a section name. */
  --paper:var(--dark);
  --el-inv:1;
  --el-blend:screen;

  --f:'Helv','Helvetica Neue',Helvetica,Arial,sans-serif;
  --f-vcr:'VCR','Helv',monospace;
  --ease:cubic-bezier(.16,1,.3,1);
  --gutter:clamp(1rem,3.5vw,3rem);
  --fade-t:7svh;        /* seam bleed, per side — 0 where there is no seam */
  --fade-b:7svh;
}

/* the angle is its own animatable property so the parallax translate on the
   same element stays live instead of being frozen into the keyframes */
@property --spin{syntax:'<angle>';inherits:false;initial-value:0deg}

/* invert — every surface and every cut-out flips together */
body.is-invert{
  --light:#000000;
  --dark:#ffffff;
  --on-light:#ffffff;
  --on-dark:#000000;
}

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--dark);color:var(--on-dark);
  font-family:var(--f);line-height:1.5;
  overflow-x:hidden;-webkit-font-smoothing:antialiased;
}
body.is-booting{overflow:hidden}
img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
::selection{background:currentColor;color:var(--light)}
:focus-visible{outline:2px solid currentColor;outline-offset:4px}

/* A single fixed backdrop behind everything has its tone interpolated on
   scroll, so where two sections differ the tones bleed instead of meeting at a
   hard seam. Each stage paints its own opaque paper over it and fades that
   paper out at the viewport edges, which is where the bleed shows through. */
.backdrop{position:fixed;inset:0;z-index:-1;background:var(--dark)}
.sec{position:relative;overflow:hidden}

/* ---- paper, and everything derived from it -------------------------------
   The cut-outs are dark artwork printed on an opaque white rectangle (star-a,
   star-b, dice, cards) or dark artwork on transparency (cd-a, cd-b, ic-mail).
   Either way the rule is the same: the artwork must end up LIGHTER than the
   paper and screen onto it, or DARKER than the paper and multiply into it.
   Anything else leaves the source rectangle sitting on top of the type — that
   was the black-box bug on the first and last pages.                        */
.sec--light{--paper:var(--light);color:var(--on-light);--el-blend:multiply;--el-inv:0}
.sec--dark {--paper:var(--dark); color:var(--on-dark); --el-blend:screen;  --el-inv:1}
body.is-invert .sec--light{--el-blend:screen;  --el-inv:1}
body.is-invert .sec--dark {--el-blend:multiply;--el-inv:0}

/* Tracking-overlay ink. The overlay composites with `difference` (see .trk),
   so one neutral grey reads the same faint grey over black paper, over white
   paper and over the cut-outs themselves — no per-paper theming, and it can
   never disappear against the white artwork the way a plain white hairline did.
   Same trick the controls use. */
:root{--trk:rgba(146,146,146,.42);--trk-hi:rgba(190,190,190,.52)}

.el,.boot__img,.lay--icons img{filter:invert(var(--el-inv));user-select:none;-webkit-user-drag:none}
.el{position:absolute;pointer-events:none;z-index:1;mix-blend-mode:var(--el-blend);transform:translateY(var(--ty,0))}

/* ============ LOADER ============ */
.boot{
  position:fixed;inset:0;z-index:200;background:var(--light);color:var(--on-light);
  --el-inv:0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1.6rem;
  transition:opacity .5s var(--ease),visibility .5s;
}
body.is-invert .boot{--el-inv:1}
.boot.is-done{opacity:0;visibility:hidden}
.boot__img{width:min(230px,44vw);image-rendering:pixelated}
.boot__bar{width:min(230px,44vw);height:2px;background:currentColor;opacity:.18;position:relative;overflow:hidden}
.boot__bar span{position:absolute;inset:0 100% 0 0;background:var(--on-light);transition:right .16s linear}
.boot__pct{font-family:var(--f-vcr);font-size:.95rem;letter-spacing:.14em;color:var(--on-light);opacity:.55}

/* ============ FIXED CONTROLS ============ */
.ctl{
  position:fixed;right:var(--gutter);bottom:calc(var(--gutter) - .3rem);z-index:95;
  display:flex;flex-direction:column;align-items:flex-end;gap:.25rem;
  mix-blend-mode:difference;
  opacity:0;transition:opacity .8s var(--ease) .2s;
}
.is-lit .ctl{opacity:1}
.ctl__b{
  background:none;border:0;padding:.18rem 0;cursor:pointer;
  font-family:var(--f);font-size:clamp(.72rem,1vw,.86rem);color:#fff;
  line-height:1.35;position:relative;
}
.ctl__b::after{content:'';position:absolute;left:0;right:100%;bottom:.1rem;height:1px;background:currentColor;transition:right .35s var(--ease)}
.ctl__b:hover::after,.ctl__b[aria-pressed="true"]::after{right:0}

/* ============ COMP STAGE ============
   COVER, not contain: the comps are full-bleed, so on a window wider than 16:9
   the stage grows past the viewport and the section crops it. Contain left bare
   backdrop down both sides — that was the "zoomed out" bug.
   The stage also carries the paper the cut-outs blend against, because
   container-type creates a stacking context and isolates the blend from
   anything painted outside it. The mask is anchored to the VIEWPORT, not to the
   stage box, so the seam fade stays on screen no matter how much of the stage
   is cropped away — --vtop/--vbot are where the viewport edges land inside the
   stage, and they change with how the stage is aligned in its section. */
/* Absolutely placed, not grid-aligned: once the stage overflows its section,
   Chrome falls back to `start` for align-self and the crop anchor silently
   moves to the top — which is what cropped the contact marks off the bottom. */
.stage{
  position:absolute;inset:0;margin:auto;   /* centred WITHOUT transform — the
     data-rise reveal owns `transform` on this element and would cancel it */
  width:max(100vw, calc(100svh * 16 / 9));
  height:max(100svh, calc(100vw * 9 / 16));
  container-type:size;
  background:var(--paper);
  --vtop:calc(50% - 50svh);
  --vbot:calc(50% + 50svh);
  -webkit-mask-image:linear-gradient(to bottom,
      transparent var(--vtop),
      #000 calc(var(--vtop) + var(--fade-t)),
      #000 calc(var(--vbot) - var(--fade-b)),
      transparent var(--vbot));
          mask-image:linear-gradient(to bottom,
      transparent var(--vtop),
      #000 calc(var(--vtop) + var(--fade-t)),
      #000 calc(var(--vbot) - var(--fade-b)),
      transparent var(--vbot));
}
/* First and last pages have no seam above / below them, so they get the full
   frame instead of fading into nothing. */
.hero .stage{--fade-t:0}
.contact .stage{--fade-b:0}
.lay{position:absolute;margin:0;z-index:5;line-height:1;letter-spacing:-.02em;font-weight:400;transform:translateY(var(--ty,0))}

/* tracking overlay — above the cut-outs, under the type, never interactive */
.trk{
  position:absolute;inset:0;width:100%;height:100%;z-index:3;
  pointer-events:none;mix-blend-mode:difference;
  opacity:0;transition:opacity 1.6s var(--ease) .25s;
}
.trk.is-on{opacity:1}

/* ---- 01 front ---- */
.hero{height:100svh;display:grid;place-items:center}
.el--star-a{left:66.30%;top:-12.41%;width:31.46%}
.el--star-b{left:-10.78%;top:12.13%;width:60.78%}
/* the stars turn, very slowly and against each other — one revolution is over
   two minutes, so it reads as drift rather than as an animation */
.el--star-a,.el--star-b{transform:translateY(var(--ty,0)) rotate(var(--spin))}
.el--star-a{animation:spin-cw 128s linear infinite}
.el--star-b{animation:spin-ccw 173s linear infinite}
@keyframes spin-cw {to{--spin:360deg}}
@keyframes spin-ccw{to{--spin:-360deg}}
.el--dice  {left:32.45%;top:19.26%;width:35.10%}
.lay--shutter{left:28.33%;top:43.0%;font-size:4cqw;font-style:italic}
.lay--kif    {left:59.84%;top:43.0%;font-size:4cqw;font-style:italic}

.cue{position:absolute;bottom:1.5rem;left:50%;transform:translateX(-50%);z-index:6;padding:.6rem}
.cue i{display:block;width:1px;height:34px;background:currentColor;opacity:.3;position:relative;overflow:hidden}
.cue i::after{content:'';position:absolute;inset:0;background:currentColor;animation:sd 2s cubic-bezier(.65,0,.35,1) infinite}
@keyframes sd{0%{transform:translateY(-100%)}60%,100%{transform:translateY(100%)}}

/* ---- 02 work ---- */
.machine{height:100svh;display:flex;flex-direction:column;position:relative}
/* The flower sits in its own stage BEHIND the WebGL canvas, which is now
   genuinely transparent. Being an ordinary cut-out is the whole point: it keys
   off the paper like every other one — dark on white paper, light on black,
   never a white box on black — and it takes the rotation and the tracker for
   free. It replaced a leopard photograph that could do none of that. */
.machine .stage{z-index:1}
/* Sized and centred in container units, never with a centring transform or the
   independent `translate` property — the tracker reads offsetLeft/offsetTop
   plus the computed `transform`, and either of those would desync the contour
   from the artwork. Source is 1248x1152, so height = width / 1.0833. */
/* Sized against the VIEWPORT, not the stage: the stage overflows on a cover
   crop, so a stage-relative size would bleed the petals off screen. Source is
   1248x1152, so width = height * 1.0833 and half of that is 0.5417.
   Never centred with a transform or the independent `translate` property — the
   tracker reads offsetLeft/offsetTop plus the computed `transform`, and either
   would desync the contour from the artwork. */
.el--flower{
  height:104svh;width:auto;
  left:calc(50cqw - 56.3svh);
  top:calc(50cqh - 52svh);
  transform:translateY(var(--ty,0)) rotate(var(--spin));
  animation:spin-cw 214s linear infinite;
}
.machine__stage{position:relative;flex:1;min-height:0;z-index:2}
#pspCanvas{position:absolute;inset:0;width:100%;height:100%;display:block;touch-action:pan-y}
/* deliberately NOT inverted: the bloom and the screen are emitted light,
   and inverting turns the highlights into black smears */
.machine__loading{position:absolute;inset:auto 0 50% 0;text-align:center;z-index:3;transition:opacity .5s}
.machine__loading.is-off{opacity:0;pointer-events:none}
.spin{display:inline-block;width:11px;height:11px;border:1px solid currentColor;border-top-color:transparent;border-radius:50%;opacity:.5;animation:spin .9s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

.rail{
  position:absolute;left:var(--gutter);bottom:clamp(1.6rem,5vh,3.2rem);z-index:6;
  display:flex;align-items:baseline;gap:.45rem;font-family:var(--f-vcr);color:var(--on-dark);
}
.rail b{font-size:clamp(1.8rem,4vw,3rem);font-weight:400;line-height:1;font-variant-numeric:tabular-nums}
.rail i{font-style:normal;font-size:.9rem;opacity:.45}
.rail__open{
  margin-left:.7rem;align-self:center;width:36px;height:36px;display:grid;place-items:center;
  border:1px solid currentColor;font-size:1rem;line-height:1;
  transition:background .25s,color .25s,transform .4s var(--ease);
}
.rail__open:hover{background:var(--on-dark);color:var(--dark);transform:translate(2px,-2px)}
.rail.is-swap b{animation:flick .3s steps(2) both}
@keyframes flick{0%{opacity:0}50%,100%{opacity:1}}

.keys{position:absolute;right:var(--gutter);bottom:clamp(1.6rem,5vh,3.2rem);z-index:6;display:flex;gap:.3rem;color:var(--on-dark)}
kbd{font-family:var(--f-vcr);font-size:.8rem;border:1px solid currentColor;opacity:.45;padding:.1rem .35rem;min-width:24px;text-align:center}

/* ---- 03 about ---- */
.about{height:100svh;display:grid;place-items:center}
.el--cd-a  {left:-5.99%;top:-23.61%;width:41.35%}
.el--cd-b  {left:82.08%;top:45.83%;width:29.90%}
.lay--abouth{left:50%;top:26%;transform:translate(-50%,var(--ty,0));font-family:var(--f-vcr);font-size:3.3cqw;letter-spacing:.06em}
.lay--bio{
  left:50%;top:38%;width:44%;margin-left:-22%;
  font-size:1.78cqw;line-height:1.3;letter-spacing:0;
  text-align:justify;text-justify:inter-word;hyphens:auto;
}
.lay--bio p{margin:0}
.lay--bio p+p{margin-top:.15em}

/* ---- 04 contact ---- */
/* Bottom-aligned, not centred: the comp puts the mail and Instagram marks at
   89.7% of a 1080-tall canvas, so centring the cover crop ate them. Cropping
   off the top instead costs nothing — the cards already start above the frame. */
.contact{height:100svh;display:grid;place-items:center}
.contact .stage{
  top:auto;bottom:0;margin-block:0;
  --vtop:calc(100% - 100svh);--vbot:100%;
}
.el--cards{left:26.67%;top:-3.52%;width:46.67%}
.lay--contact{left:23.54%;top:40.0%;font-size:3.78cqw}
.lay--me     {left:64.27%;top:40.0%;font-size:3.78cqw}
.lay--icons{left:45.94%;top:89.7%;display:flex;align-items:center;gap:1.1%}
.lay--icons img{width:100%;transition:transform .4s var(--ease)}
.lay--icons a:first-child{width:3.70cqw}
.lay--icons a:last-child{width:3.02cqw}
.lay--icons a:hover img{transform:translateY(-4px)}
.icons__ig,body.is-invert .icons__ig{filter:none}   /* the IG mark keeps its colour */

/* ============ MOTION ============
   Nothing here is a page transition — each piece drifts at its own rate as you
   scroll, so a section reads as a moving composition rather than a flat still. */
.el,.lay{will-change:transform}
.rev{opacity:0;filter:blur(6px)}
.rev.is-shown{opacity:1;filter:none;visibility:visible;
  transition:opacity 1.1s var(--ease),filter 1.1s var(--ease)}
/* cut-outs keep their invert while revealing — a bare `filter:none` here would
   drop the polarity and bring the black boxes straight back */
.el.rev{filter:invert(var(--el-inv)) blur(9px)}
.el.rev.is-shown{filter:invert(var(--el-inv));
  transition:opacity 1.1s var(--ease),filter 1.1s var(--ease)}
.rev.is-shown.d1{transition-delay:.08s}
.rev.is-shown.d2{transition-delay:.16s}

/* ============ REVEAL ============ */
[data-rise]{opacity:0;transform:translateY(18px);filter:blur(6px)}
.is-lit [data-rise]{opacity:1;transform:none;filter:none;transition:opacity .9s var(--ease) .1s,transform 1s var(--ease) .1s,filter .9s var(--ease) .1s}

/* ============ NARROW SCREENS ============
   The 16:9 stage would shrink the type past legibility, so let it fill the
   width and grow taller instead. The section grows with it, and the
   viewport-anchored mask no longer makes sense, so it comes off. */
@media (max-width:820px){
  .keys{display:none}
  .hero,.about,.contact{height:auto;min-height:100svh;place-items:center}
  .stage,.contact .stage{
    position:relative;inset:auto;margin:0;
    width:100vw;height:auto;min-height:100svh;container-type:normal;
    -webkit-mask-image:none;mask-image:none;
  }
  .lay--shutter{left:6%;top:36%;font-size:9vw}
  .lay--kif{left:62%;top:54%;font-size:9vw}
  .el--dice{left:20%;top:40%;width:58%}
  .el--star-a{left:54%;top:1%;width:54%}
  .el--star-b{left:-26%;top:64%;width:92%}
  .lay--abouth{left:50%;top:18%;transform:translate(-50%,var(--ty,0));font-size:6.4vw}
  .lay--bio{left:6%;top:32%;width:88%;margin-left:0;font-size:3.9vw;text-align:left}
  .el--cd-a{left:-16%;top:-5%;width:58%}
  .el--cd-b{left:62%;top:80%;width:48%}
  .lay--contact{left:6%;top:33%;font-size:9vw}
  .lay--me{left:60%;top:57%;font-size:9vw}
  .el--cards{left:19%;top:34%;width:64%}
  .el--flower{width:130%;left:-15%;top:22%}
  .lay--icons{left:39%;top:90%;gap:3%}
  .lay--icons a:first-child{width:9vw}
  .lay--icons a:last-child{width:7.4vw}
}

/* ============ REDUCED MOTION ============ */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .cue i::after,.rail.is-swap b{animation:none}
  .el--star-a,.el--star-b,.el--flower{animation:none;transform:translateY(var(--ty,0))}
  .rev{opacity:1;filter:none;visibility:visible;transition:none}
  .el.rev,.el.rev.is-shown{opacity:1;filter:invert(var(--el-inv));transition:none}
  .el,.lay{--ty:0 !important}
  [data-rise]{opacity:1;transform:none;filter:none;transition:none}
  .lay--icons img,.rail__open,.ctl__b::after{transition:none}
}
