/* ==========================================================================
   stan.photo — styles
   Sections: fonts & tokens · base · top bar · titles · home stage · home HUD ·
   loader · album · cover focus · about · cursor · first-visit hint ·
   phones · touch · reduced motion
   ========================================================================== */

/* ---------- fonts & tokens ---------- */
/* Questrial (400 only), self-hosted. SIL Open Font License — see fonts/OFL.txt.
   The files are patched: round dots and a tighter proportional "1" (bump ?v= when they change) */
@font-face{font-family:"Questrial";font-style:normal;font-weight:400;font-display:swap;src:url(../fonts/questrial-latin-ext-400-normal.woff2?v=3) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:"Questrial";font-style:normal;font-weight:400;font-display:swap;src:url(../fonts/questrial-latin-400-normal.woff2?v=3) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
:root{
  --bg:#0b0b0b;--fg:#f3f1ec;--mute:rgba(243,241,236,.5);--line:rgba(243,241,236,.16);
  --ease:cubic-bezier(.16,1,.3,1);
  --pad:clamp(14px,1.5vw,26px);
  --title:clamp(52px,9.2vw,176px);--title-stroke:.03em;
  --text-md:clamp(15px,1.05vw,16px);--text-nav:var(--text-md);--text-nudge:clamp(18px,1.6vw,26px);
  --logo-h:clamp(22px,1.9vw,32px);
  --title-delay:.3s;--cred-delay:.5s;--cred-stagger:.07s;
  --shade:radial-gradient(farthest-side at 100% 100%,rgba(0,0,0,0.250) 0%,rgba(0,0,0,0.243) 10%,rgba(0,0,0,0.224) 20%,rgba(0,0,0,0.196) 30%,rgba(0,0,0,0.162) 40%,rgba(0,0,0,0.125) 50%,rgba(0,0,0,0.088) 60%,rgba(0,0,0,0.054) 70%,rgba(0,0,0,0.026) 80%,rgba(0,0,0,0.007) 90%,rgba(0,0,0,0.000) 100%);
  --shade-w:min(90vw,1200px);--shade-h:min(85vh,760px);
}

/* ---------- base ---------- */
html{font-synthesis:none}
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent} /* no blue/grey flash on tap (iOS, Android): taps already get their own feedback */
html,body{height:100%;background:var(--bg);color:var(--fg);overflow:hidden;overscroll-behavior:none}
body{font:500 13px/1.3 "Questrial",system-ui,sans-serif;letter-spacing:-.005em;-webkit-font-smoothing:antialiased;font-variant-numeric:tabular-nums;user-select:none;-webkit-user-select:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;letter-spacing:inherit}
a{color:inherit;text-decoration:none}
img{display:block;-webkit-user-drag:none}
img.ld{opacity:0!important}
img.fi{animation:imgIn .6s var(--ease) both}
@keyframes imgIn{from{opacity:0}} /* main.js fadeImg: images that weren't ready fade in once loaded */
/* custom cursor on: hide the system cursor everywhere, incl. pseudo-elements and scrollbars (Firefox shows the arrow over those) */
@media (hover:hover) and (pointer:fine){
  html.fx,html.fx *,html.fx *::before,html.fx *::after{cursor:none!important}
  html.fx.cr,html.fx.cr *,html.fx.cr *::before,html.fx.cr *::after{cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1' height='1'/%3E") 0 0,none!important} /* the same invisible cursor written another way: main.js flips between the two (.cr) after drags and window switches, which makes Firefox re-apply it (it sometimes brings the system arrow back and keeps it) */
  html.fx #about{scrollbar-width:none}
  html.fx #about::-webkit-scrollbar{display:none}
}
.only-series{display:none}
body.in-series .only-series{display:inline-block}
body.in-series .only-home{display:none}

/* ---------- top bar ---------- */
/* optical edge alignment: letters (cap tops / baselines) sit exactly --pad from the screen edges.
   Values are Questrial's measured line-box offsets, in em so they hold at every size. */
.bar{position:fixed;left:0;right:0;top:0;z-index:40;display:flex;justify-content:space-between;align-items:flex-start;padding:var(--pad);padding-top:calc(var(--pad) - 2px - .2555em);mix-blend-mode:difference;color:#fff;pointer-events:none}
.bar>*{pointer-events:auto}
.bar,#about .x,#about .home{font-size:var(--text-nav)} /* nav items (Index / About / Back) */
.brand{display:flex;gap:2.2em;margin-top:calc(2px + .2555em)}
.brand span{color:rgba(255,255,255,.55)}
/* logo: 'stan' + dot, Questrial outlines as inline SVG (media/site/logo.svg); --logo-h tall, its top/left sit exactly on --pad */
.lnk.logo{display:block;padding:0;line-height:0}
.logo .mark{display:block;height:var(--logo-h);width:auto}
.logo::after{display:none}
/* logo hover: each letter (and the dot) rolls up out of the frame and a copy rises from below, staggered left to right, echoing the album titles; click gives a small press. Each .L holds a letter plus its copy 760 units lower; the mark clips to its viewBox */
.logo .mark{overflow:hidden;transition:transform .08s cubic-bezier(.2,0,0,1)}
.logo .L{transition:transform .26s cubic-bezier(.2,0,0,1)}
.logo .L:nth-child(2){transition-delay:.04s}.logo .L:nth-child(3){transition-delay:.08s}.logo .L:nth-child(4){transition-delay:.12s}.logo .L:nth-child(5){transition-delay:.16s}
.logo:focus-visible .L{transform:translateY(-760px)}
@media (hover:hover){.logo:hover .L{transform:translateY(-760px)}}
.logo:active:not(.still) .mark{transform:scale(.94)}
.nav{display:flex;gap:1.6em;margin-top:calc((var(--logo-h) - .68em)/2)} /* nav sits on the logo's vertical middle: shift the cap-height (.68em in Questrial) down by half the difference */
body.ov .bar [data-act=about]{display:none} /* About only on Home: hidden in Index and in albums */
/* links: underline draws in on hover; bigger invisible hit areas, since the custom cursor costs a little pointing accuracy */
.lnk{position:relative;padding:2px 0}
.lnk::before{content:"";position:absolute;inset:-.3em -.35em}
.bar .lnk::before,#about .x::before,#about .home::before{inset:-.8em -.7em}
.lnk::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:1px;background:currentColor;transform:scaleX(0);transform-origin:right;transition:transform .45s var(--ease)}
@media (hover:hover){.lnk:hover::after{transform:scaleX(1);transform-origin:left}}

/* ---------- titles: letters split into .ch spans that rise in ---------- */
/* the descender line (y, p, g) sits on --pad, so nothing dips past the edge */
.t{display:flex;overflow:hidden;font-weight:500;font-size:var(--title);line-height:.86;letter-spacing:-.05em;padding-bottom:.1em;padding-top:.12em;margin-top:-.12em;margin-bottom:0;padding-inline:.1em;margin-inline:-.1em;margin-left:calc(-.1em - var(--lsb,0em));white-space:nowrap}
.t .ch{display:inline-block} /* no permanent will-change: the rise-in animation gets its own layer while it plays */
#hTitle,.series .t{-webkit-text-stroke:var(--title-stroke) currentColor;letter-spacing:.01em} /* faux bold: Questrial has one weight */
.t.anim .ch{animation:chUp .8s var(--ease) both;animation-delay:calc(var(--i)*20ms)}
.t.anim[data-dir="-1"] .ch{animation-name:chDown}
/* Home title: waits for the strip to settle. Its mask's top edge sits ~2.5px under the counter's baseline
   (counter: 13px text, .9em gap below), so letters dropping in never cut through the numbers; padding keeps the letters in place */
#hTitle{margin-top:calc(-.9*13px - 2px);padding-top:calc(.9*13px + 2px)}
#hTitle.anim .ch{animation-delay:calc(var(--title-delay) + var(--i)*20ms)}
/* long titles wrap between words (main.js words); each word masks its own letters so a rolling first line never shows through the second.
   The height eases when the line count changes (main.js titleHeight), carrying the counter and ticks with it */
#hTitle{flex-wrap:wrap;align-content:flex-start;row-gap:.1em;white-space:normal;transition:height .7s var(--ease)}
#hTitle .w{display:flex;align-items:baseline;white-space:nowrap;overflow:hidden;padding:.12em .1em .1em;margin:-.12em -.1em -.1em}
/* "open" arrow after the title: only without a mouse cursor; its foot sits on the baseline, drawn to the letters' stroke */
#hTitle .go{display:none;margin-left:.16em} /* holds a zero-width character, so it has the text's own baseline; the arrow sits on it as an inline image */
html.touch #hTitle .go{display:inline-block}
#hTitle .go svg{display:inline;width:.62em;height:.62em;vertical-align:-.027em;overflow:visible;fill:none;stroke:currentColor;stroke-width:2.1;stroke-linecap:square} /* -.027em: the stroke's pointed square end stops just short of the drawing's bottom edge */
.num{display:inline-flex;overflow:hidden}
.num .ch{display:inline-block}
.num.anim .ch{animation:chUp .6s var(--ease) both;animation-delay:calc(var(--i)*30ms)}
.num.anim[data-dir="-1"] .ch{animation-name:chDown}
@keyframes chUp{from{transform:translate3d(0,120%,0)}} /* clears the deeper descender room */
@keyframes chDown{from{transform:translate3d(0,-145%,0)}} /* extra travel: titles have .12em headroom above */

/* ---------- home stage: flat strip of covers, moved by JS (renderStage) ---------- */
#stage{position:fixed;inset:0;overflow:hidden;touch-action:none;will-change:transform}
#stage .rail{position:absolute;left:0;top:0;transform-origin:0 0;will-change:transform}
#stage .slide{position:absolute;top:0;overflow:hidden;background:#141414;will-change:transform}
#stage .slide img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;will-change:transform,filter}

/* ---------- home HUD: count, title, credits, index ticks ---------- */
/* .hud is position:absolute with no z-index on purpose: fixed or z-index would create a stacking context and stop
   the title blending with the photo. The page never scrolls, so absolute = viewport. */
.hud{position:absolute;inset:auto 0 0 0;padding:var(--pad);display:grid;grid-template-columns:1fr auto;align-items:end;gap:var(--pad);pointer-events:none;color:#fff;transition:opacity .35s var(--ease)}
.hud .meta{display:flex;gap:.9em;align-items:center;margin-bottom:.9em;color:rgba(255,255,255,.6)}
.hud .meta b{font-weight:500;color:#fff}
.right{display:flex;flex-direction:column;align-items:flex-end;gap:1.6em}
.credits{display:grid;grid-template-columns:auto auto;gap:.25em 1.4em;text-align:left;font-size:var(--text-md);transition:opacity .3s}
.credits dt{color:rgba(255,255,255,.5)}
.credits dd{color:#fff}
.ticks{display:flex;gap:5px;pointer-events:auto;margin-bottom:-6px} /* bottom of the column: the line sits near the title baseline */
.ticks button{width:18px;height:18px;position:relative}
.ticks button::before{content:"";position:absolute;left:0;right:0;top:8px;height:2px;background:rgba(255,255,255,.35);transition:background .3s,transform .45s var(--ease);transform-origin:left}
.ticks button.on::before{background:#fff;transform:scaleY(2)}
@media (hover:hover){.ticks button:hover::before{background:#fff}}
body.ov .hud .credits{opacity:0}
body.in-series .hud{opacity:0}
/* entrance: each credit line slides in from the right after the title; the ticks follow on the first load */
#hCred.fade>*{animation:credIn .7s var(--ease) calc(var(--cred-delay) + var(--i)*var(--cred-stagger)) both}
#hud.enter .ticks{animation:credIn .7s var(--ease) calc(var(--cred-delay) + 5*var(--cred-stagger)) both}
/* back from an album: the ticks slide in one after another, alongside the credits (main.js closeSeries) */
#hud.back .ticks button{animation:credIn .7s var(--ease) calc(var(--cred-delay) + var(--j)*.045s) both}
@keyframes credIn{from{opacity:0;transform:translate3d(1.5em,0,0)}}
/* the title keeps the colour-inverting blend; credits sit on a soft dark corner shade so they read on any photo (Home and album cover) */
.hud>*,.cover .cmeta>*{position:relative}
.hud>div:first-child,.cover .cmeta>div:first-child{mix-blend-mode:difference}
.hud>div.plain,.cover .cmeta>div.plain{mix-blend-mode:normal} /* over mid-tones difference turns grey: plain white instead (main.js setBlend) */
.hud::before,.cover .cmeta::before{content:"";position:fixed;right:0;bottom:0;width:var(--shade-w);height:var(--shade-h);background:var(--shade);pointer-events:none;transition:opacity .3s}
body.ov .hud::before{opacity:0}

/* ---------- loader ---------- */
#loader{position:fixed;inset:0;z-index:90;background:var(--bg);display:flex;align-items:flex-end;justify-content:space-between;padding:var(--pad);clip-path:inset(0 0 0 0)}
#loader .pct{font-size:var(--title);line-height:.86;letter-spacing:-.05em;font-variant-numeric:proportional-nums} /* Questrial digits default to equal widths, which leaves a wide gap around the 1 */
#loader i{position:absolute;left:0;top:0;height:2px;width:100%;background:var(--fg);transform:scaleX(0);transform-origin:left;transition:transform .4s var(--ease)}

/* ---------- album ---------- */
.series{position:fixed;inset:0;z-index:30;background:var(--bg);overflow:hidden;touch-action:none}
.track{display:flex;height:100%;gap:6px;will-change:transform}
.panel,.shot{position:relative;flex:0 0 auto;height:100%;overflow:hidden;background:#141414}
/* cover */
.cover{width:78vw} /* keep in sync with COVER_W in main.js */
.cover>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;will-change:transform}
.cover .cmeta{position:absolute;left:var(--pad);right:var(--pad);bottom:var(--pad);display:grid;grid-template-columns:1fr auto;align-items:end;gap:var(--pad);color:#fff}
.cover .cmeta::before{position:absolute;right:calc(-1*var(--pad));bottom:calc(-1*var(--pad))}
.cover .meta{display:flex;gap:.9em;margin-bottom:.9em;color:rgba(255,255,255,.6)}
.cover .meta b{color:#fff;font-weight:500}
.cover .credits{margin-bottom:calc(-.373em + .2285*var(--title))} /* last credit line sits on the title's baseline (the title box carries .1em padding + line-height space below its baseline) */
/* photos */
.shot{aspect-ratio:var(--a)}
.shot img{width:100%;height:100%;object-fit:cover;opacity:0;transform:scale(1.08);transition:opacity .6s var(--ease),transform 1.1s var(--ease)}
.shot.in img{opacity:1;transform:none}
.shot.now img{transition:none} /* ready before the album opened: no fade, it slides in with the cover */
.shot::after{content:"";position:absolute;left:0;top:0;width:300%;height:100%;pointer-events:none;background:linear-gradient(100deg,rgba(255,255,255,0) 35%,rgba(255,255,255,.05) 50%,rgba(255,255,255,0) 65%);animation:shimmer 1.8s cubic-bezier(.45,0,.55,1) infinite;transition:opacity .5s var(--ease)} /* loading: a soft light sweeps across the empty frame until the photo is ready (moved with transform, so it never repaints) */
.shot.in::after{opacity:0;animation-play-state:paused}
@keyframes shimmer{from{transform:translate3d(-66.667%,0,0)}to{transform:none}}
/* bottom bar: progress line and count, plain white (a difference blend posterised the photos) */
.s-hud{position:absolute;left:0;right:0;bottom:0;z-index:2;pointer-events:none;color:#fff}
.s-bar{height:2px;background:rgba(255,255,255,.18)}
.s-bar i{display:block;height:100%;background:#fff;transform-origin:left;transform:scaleX(0)}
.s-count{position:absolute;right:var(--pad);bottom:calc(var(--pad) + 4px);text-shadow:0 0 12px rgba(0,0,0,.35);opacity:0;transition:opacity .3s}
.series.scrolled .s-count{opacity:1}
/* hovering a photo shows its number at its bottom-left, on the same line and in the same style as the count */
.shot figcaption{position:absolute;left:var(--pad);bottom:calc(var(--pad) + 4px);color:#fff;text-shadow:0 0 12px rgba(0,0,0,.35);opacity:0;transform:translateY(6px);transition:opacity .4s var(--ease),transform .4s var(--ease);pointer-events:none}
.shot:hover figcaption{opacity:1;transform:none}
/* Next series panel: label, thumbnail and title grouped at the bottom right */
.next{width:min(70vw,980px);display:flex;flex-direction:column;justify-content:flex-end;align-items:flex-end;text-align:right;padding:var(--pad);background:var(--bg);cursor:pointer}
.next .lbl{color:var(--fg);font-size:var(--text-nav);margin-bottom:1em;display:flex;justify-content:flex-end;gap:1em}
.next .lbl span+span{color:var(--mute)} /* the number stays quiet */
.next .nthumb{width:38%;aspect-ratio:3/2;overflow:hidden;margin-bottom:1.2em}
.next .nthumb img{width:100%;height:100%;object-fit:cover;transform:scale(1.15);transition:transform .9s var(--ease)}
@media (hover:hover){.next:hover .nthumb img{transform:scale(1)}}
.next .t{justify-content:flex-end;margin-left:0;margin-right:-.1em}
/* hovering Next series sinks the current album's photos further into the dark; .leaving keeps them there
   (and ignores the mouse) while the next album grows in */
/* dimming the photos (hovering Next, leaving): a black veil fading to 75%, which looks the same as brightness(.25) but fades as plain opacity instead of re-filtering every photo each frame */
.series .shot::before,.series .cover::after{content:"";position:absolute;inset:0;z-index:6;background:#000;opacity:0;pointer-events:none;transition:opacity .6s var(--ease)}
@media (hover:hover){.series:has(.next:hover) .shot::before,.series:has(.next:hover) .cover::after{opacity:.75}}
.series.leaving .shot::before,.series.leaving .cover::after{opacity:.75}
.series.leaving{pointer-events:none}
.series.gl .cover>img,.series.gl .shot img{visibility:hidden} /* WebGL draws the album photos (main.js glAlbum); the frames go clear so its canvas shows through */
.series.gl .cover,.series.gl .shot{background:transparent}
.series.gl .shot{opacity:1!important} /* the spotlight dimming is drawn by WebGL; on the frame itself it would also fade the hover shading laid over the canvas */

/* ---------- cover Focus ---------- */
/* pull the focal point to the frame's middle (cq units = the frame's size), clamped so the photo still covers the frame */
:has(>img.fp){container-type:size}
img.fp{--wr:max(100cqw,calc(var(--ar)*100cqh));--hr:max(100cqh,calc(100cqw / var(--ar)));object-position:clamp(calc(100cqw - var(--wr)),calc(50cqw - var(--fx)*var(--wr)),0px) clamp(calc(100cqh - var(--hr)),calc(50cqh - var(--fy)*var(--hr)),0px)!important}

/* ---------- about ---------- */
#about{position:fixed;inset:0;z-index:50;background:var(--bg);color:var(--fg);display:none;clip-path:inset(0 0 100% 0);overflow:auto}
#about.open{display:block}
#about .in{min-height:100%;display:flex;flex-direction:column;justify-content:space-between;padding:calc(var(--pad) + var(--logo-h)) var(--pad) var(--pad)}
#about .x{position:fixed;top:calc(var(--pad) - 2px - .2555em + (var(--logo-h) - .68em)/2);right:var(--pad)}
#about .home{position:fixed;top:var(--pad);left:var(--pad)}
#about::before{content:"";position:fixed;z-index:1;left:0;right:0;top:0;height:calc(var(--pad)*2 + var(--logo-h));pointer-events:none;background:rgba(11,11,11,.5);-webkit-backdrop-filter:blur(14.4px) saturate(1.2);backdrop-filter:blur(14.4px) saturate(1.2)} /* About scrolls under the logo and Back, so they sit on a frosted-glass bar */
#about .x,#about .home{z-index:2}
#about h2{font-weight:500;font-size:clamp(40px,min(8.7vw,(100vh - 620px)/3.2),180px);line-height:1.06;letter-spacing:-.01em;-webkit-text-stroke:var(--title-stroke) currentColor} /* poster scale: as big as the width allows (longest line ~90% wide) while About still fits one screen with the columns below */
#about .in>div:first-child{flex:1 0 auto;display:flex;flex-direction:column;justify-content:center;padding-block:var(--pad)} /* headline + intro centred in the space between the logo and the columns; no scrolling on desktop */
#about h2 .ln{display:block;overflow:hidden;padding-bottom:.1em;margin-bottom:-.1em}
#about h2 .ln span{display:block}
#about h2 .ln.m{display:none} /* phones get their own, shorter line breaks for a bigger headline */
#about.anim h2 .ln span{animation:chUp .9s var(--ease) both;animation-delay:calc(120ms + var(--i)*60ms)}
#about .intro{margin-top:2.2em;max-width:30em;font-size:clamp(14px,1.15vw,19px);line-height:1.4;color:rgba(243,241,236,.55)}
#about.anim .intro{animation:fadeUp .9s var(--ease) .45s both}
@keyframes fadeUp{from{opacity:0;transform:translate3d(0,12px,0)}}
/* columns */
#about .cols{font-size:var(--text-md);display:grid;grid-template-columns:repeat(4,1fr);gap:var(--pad);margin-top:0;border-top:1px solid var(--line);padding-top:var(--pad)}
#about .cols .wide{grid-column:span 2}
#about h4{font-weight:500;color:rgba(243,241,236,.45);margin-bottom:.8em}
#about li{list-style:none;padding:.12em 0}
#about .ask{max-width:24em;line-height:1.4;margin-bottom:.9em}
#aCollab{columns:2;column-gap:var(--pad)}
#about a.lnk{display:inline-block}
#about a.lnk.ico{display:inline-flex;align-items:center;gap:.55em}
#about a.lnk.ico::after{left:1.7em}
#about a.lnk[hidden]{display:none}
#about .ico svg{width:1.15em;height:1.15em;flex:none;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
/* footer: '© 2026 Stanley Chang Photography | Controls' on one line, bottom left */
#about .foot{margin-top:calc(var(--pad)*3);display:flex;justify-content:flex-start;align-items:baseline;gap:0;flex-wrap:wrap}
#about .copy{margin:0;font-size:var(--text-md);color:rgba(243,241,236,.45)}
/* Controls: a small link that opens a light panel above it (Esc or click outside closes) */
#about .sc{position:relative;font-size:var(--text-md)}
#about .sc::before{content:'|';margin:0 .9em;color:rgba(243,241,236,.25)}
#about .sc-btn{color:rgba(243,241,236,.45);transition:color .3s}
#about .sc.open .sc-btn{color:var(--fg)}
@media (hover:hover){#about .sc-btn:hover{color:var(--fg)}} /* hover styles only for real hover: on touch screens a tap leaves :hover stuck on (e.g. Index stayed underlined after tapping it and Back) */
#about .sc-panel{position:absolute;left:0;bottom:calc(100% + 1em);margin:0;padding:1.1em 1.3em;background:var(--fg);color:var(--bg);display:grid;gap:.6em;white-space:nowrap;opacity:0;transform:translateY(.5em);pointer-events:none;transition:opacity .3s var(--ease),transform .4s var(--ease)}
#about .sc.open .sc-panel{opacity:1;transform:none;pointer-events:auto}
#about .sc-panel div{display:grid;grid-template-columns:8em auto;gap:1.5em}
#about .sc-panel dt{color:rgba(11,11,11,.5)}
#about .sc-panel dd{margin:0}

/* ---------- cursor (driven by main.js cursorFrame) ---------- */
#cur{position:fixed;left:0;top:0;z-index:100;pointer-events:none;mix-blend-mode:difference;display:none;transition:opacity .35s var(--ease)}
html.fx #cur{display:block}
#cur.out{opacity:0}
#cur .fl{position:absolute;left:0;top:0} /* wrapper that flips when the label changes */
#cur .lq{position:absolute;left:0;top:0;will-change:transform} /* liquid stretch along the motion */
#cur .dot{position:absolute;width:56px;height:56px;margin:-28px 0 0 -28px;color:#fff;transform:scale(.14);transition:transform .22s var(--ease)} /* quick: links should feel instant */
#cur .dot svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
#cur .dot path{fill:currentColor;stroke:none;stroke-width:1;vector-effect:non-scaling-stroke} /* main.js liquid() reshapes it */
#cur .lab{position:absolute;transform:translate(-50%,-50%);color:#000;font-size:13px;font-weight:600;letter-spacing:.02em;opacity:0;transition:opacity .2s;white-space:nowrap}
/* with a label: solid black disc + white text, no inverting blend (blended text edges showed a halo on Windows) */
#cur.big{mix-blend-mode:normal}
#cur.big .dot{transform:scale(1);color:#0b0b0b}
#cur.big .dot path,#cur.dk .dot path{stroke:rgba(255,255,255,.28)}
#cur.big .lab{opacity:1;color:#fff}
#cur.dk{mix-blend-mode:normal} /* shrinking out of the labelled state (main.js setBig) */
#cur.dk .dot{color:#0b0b0b}
#cur.link:not(.big) .dot{transform:scale(.3)} /* over anything clickable: the dot grows a little, confirming the target */
#cur.down .dot{transform:scale(.8)}

/* ---------- first-visit hint ---------- */
/* an animated mouse with its word underneath (or 'Swipe ›' on touch) at the right edge, centred on the viewport's middle;
   gone after the first strip move (once per session). It slides in from the right edge with a small overshoot */
.nudge{position:fixed;right:var(--pad);top:50%;z-index:35;display:flex;flex-direction:column;gap:.55em;align-items:center;font-size:var(--text-nudge);color:#fff;mix-blend-mode:difference;pointer-events:none;opacity:0;transform:translate(3em,-50%);transition:opacity .5s var(--ease),transform .9s cubic-bezier(.34,1.5,.64,1)}
body.show-nudge:not(.in-series):not(.ov) .nudge{opacity:1;transform:translate(0,-50%)}
.nudge .mouse{width:var(--text-nudge);height:calc(var(--text-nudge)*1.6);fill:none;stroke:currentColor;stroke-width:1.5;overflow:visible}
/* one 4.4s loop: the wheel rolls down (scroll), then the left button presses and the mouse slides left and right (drag) */
.nudge .wheel{stroke-width:2.2;stroke-linecap:round;animation:wheel 4.4s cubic-bezier(.65,0,.35,1) infinite}
.nudge .btn{fill:currentColor;stroke:none;opacity:0;animation:btn 4.4s linear infinite}
.nudge .body{animation:slide 4.4s cubic-bezier(.65,0,.35,1) infinite}
@keyframes wheel{0%{transform:translateY(0);opacity:0}6%{opacity:1}26%{transform:translateY(9px);opacity:0}100%{transform:translateY(9px);opacity:0}}
@keyframes btn{0%,44%{opacity:0}47%,90%{opacity:.85}93%,100%{opacity:0}}
@keyframes slide{0%,50%{transform:translateX(0)}64%{transform:translateX(-7px)}80%{transform:translateX(7px)}92%,100%{transform:translateX(0)}}
/* words follow the loop: 'Scroll' while the wheel rolls, then it slides away and 'Drag' slides in for the drag */
.nudge .lbl{position:relative;display:block;height:1.2em;width:3.6em;font-size:var(--text-md);line-height:1.2em;text-align:center}
.nudge .lbl b{position:absolute;inset:0;font-weight:400;opacity:0}
.nudge .l-scroll{animation:lScroll 4.4s cubic-bezier(.65,0,.35,1) infinite}
.nudge .l-drag{animation:lDrag 4.4s cubic-bezier(.65,0,.35,1) infinite}
@keyframes lScroll{0%{opacity:0;transform:translateY(.5em)}6%,36%{opacity:1;transform:none}44%,100%{opacity:0;transform:translateY(-.5em)}}
@keyframes lDrag{0%,40%{opacity:0;transform:translateY(.5em)}48%,88%{opacity:1;transform:none}96%,100%{opacity:0;transform:translateY(-.5em)}}
.nudge .touch{display:none;gap:.5em;align-items:center}
.nudge i{font-style:normal;display:inline-block;animation:nudgeX 1.6s ease-in-out infinite}
@keyframes nudgeX{50%{transform:translateX(.3em)}}
/* right caret: thin chevron drawn with the same stroke style as the contact icons */
.caret{display:inline-block;width:.42em;height:.68em;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;vertical-align:-.02em;overflow:visible}

/* ---------- phones ---------- */
@media (max-width:760px){
  :root{--title:clamp(48px,17vw,120px)}
  .brand span,.hud .credits{display:none}
  .hud{grid-template-columns:1fr}
  .hud::before,.cover .cmeta::before{display:none}
  .right{align-items:flex-start;order:-1}
  .series{overflow-y:auto;touch-action:pan-y;-webkit-overflow-scrolling:touch}
  .track{flex-direction:column;height:auto;gap:4px;transform:none!important}
  .cover{width:100%;height:78svh} /* keep in sync with COVER_H in main.js: the next photo shows underneath */
  .cover .cmeta{grid-template-columns:1fr}
  .cover .cmeta{gap:1.1em}
  .cover .credits{margin-bottom:0;font-size:13px;grid-template-columns:6.2em 1fr;gap:.2em 0;position:relative;isolation:isolate} /* phones: credits sit on the cover, under the title */
  #about .in{padding-top:calc(var(--pad) + var(--logo-h))}
  #about h2{font-size:min(42.5px,calc((100vw - 2*var(--pad))/7.9))} /* ~25% bigger, shrinking on very narrow phones so "like memories you" never wraps */
  #about h2 .ln.d{display:none}
  #about h2 .ln.m{display:block}
  #about .in>div:first-child{min-height:calc(.8*(80svh - var(--pad) - var(--logo-h)) + 36px);display:flex;flex-direction:column;justify-content:center}
  #about .cols{margin-top:0} /* headline + intro sit centred between the logo and the divider, with equal space above and below: 80% of what it takes to put the divider at 4/5 of the screen (36px = a fifth of the ~178px headline + intro block, which stays put) */
  .cover .credits::before{content:"";position:absolute;z-index:-1;left:calc(-1*var(--pad));right:calc(-1*var(--pad));top:-4.5em;bottom:calc(-1*var(--pad));background:linear-gradient(rgba(0,0,0,0) 0%,rgba(0,0,0,0.015) 10%,rgba(0,0,0,0.05) 20%,rgba(0,0,0,0.11) 30%,rgba(0,0,0,0.19) 40%,rgba(0,0,0,0.28) 50%,rgba(0,0,0,0.37) 60%,rgba(0,0,0,0.45) 70%,rgba(0,0,0,0.52) 80%,rgba(0,0,0,0.57) 90%,rgba(0,0,0,0.6) 100%);pointer-events:none} /* a soft eased fade behind the credits only (it starts below the title, so the title blend is untouched): small text needs it over busy covers */
  .shot{width:100%;height:auto}
  .shot img{transform:none;transition:opacity .5s var(--ease)} /* phones: a plain fade in once loaded */
  .shot figcaption{display:none} /* the bottom-left count already says which photo this is */
  .s-hud{position:fixed}
  .s-count{left:var(--pad);right:auto;bottom:calc(var(--pad) + 6px)}
  .s-stick{position:sticky;bottom:0;height:0;margin-top:-4px;z-index:7;pointer-events:none} /* the count's holder (main.js buildSeries): stuck to the screen bottom until the last photo's end comes up; -4px cancels the track gap so it ends exactly there */
  .next{width:100%;height:70svh}
  .next .nthumb{width:55%} /* a bigger preview and tap target under the thumb */
  #about .cols{grid-template-columns:1fr;row-gap:calc(var(--pad)*2.2)} /* one full-width column: Contact first (the main action), then Let's make something, then Collaborators (its list keeps two columns) */
  #about .cols>div:nth-child(2){order:-1}
  #about .cols .wide{grid-column:auto}
  #about .ask{max-width:none}
}

/* ---------- touch screens ---------- */
@media (hover:none),(pointer:coarse){#cur{display:none!important}}
@media (hover:none){
  #about .sc{display:none}
  .nudge .lbl,.nudge .mouse{display:none}
  .nudge .touch{display:flex}
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  .nudge i,.nudge .wheel,.nudge .btn,.nudge .body{animation:none}
  .nudge .lbl{display:none}
  .t.anim .ch,.num.anim .ch,#about.anim h2 .ln span,#about.anim .intro{animation:none}
  .shot img{transition:opacity .3s;transform:none}
  .shot::after{animation:none}
  .logo .L{transition:none!important}
}
