/* ═══════════════════════════════════════════════════════════════════════════
   Motion layer.  Everything here is opt-in via data-attributes so the base
   stylesheet stays readable, and everything switches off under
   prefers-reduced-motion (see the block at the bottom).

   Techniques borrowed and rebuilt in vanilla:
     · smooth scroll + scrub reveals   → cantor8.io (GSAP/ScrollTrigger/Lenis)
     · spring-ish entrance staggers    → laundryloftnyc.com (Framer)
     · marquee, blur-to-focus, tilt    → smile-craft-dental
     · sticky stacking, clip wipes,
       line-by-line reveals            → FNK effects-showcase
   ═══════════════════════════════════════════════════════════════════════════ */

:root{
  --ease-out:cubic-bezier(.16,1,.3,1);
  --ease-spring:cubic-bezier(.22,1.3,.36,1);
  --dur:.9s;
}

/* ─── scroll progress hairline ─────────────────────────────────────────── */
.progress{position:fixed;inset:0 0 auto 0;height:2px;z-index:80;pointer-events:none;
  background:var(--accent);transform:scaleX(0);transform-origin:0 50%;
  will-change:transform}

/* ─── header condenses once you leave the hero ─────────────────────────── */
.hdr{transition:min-height .3s var(--ease-out),box-shadow .3s var(--ease-out),
     background-color .3s var(--ease-out)}
.hdr__in{transition:min-height .3s var(--ease-out)}
.hdr.is-stuck{box-shadow:0 10px 30px -22px rgba(20,28,39,.5)}
.hdr.is-stuck .hdr__in{min-height:58px}
.hdr .lockup img{transition:width .3s var(--ease-out)}
.hdr.is-stuck .lockup img{width:32px}
.hdr .lockup .wm span{transition:opacity .25s var(--ease-out),max-height .3s var(--ease-out)}
.hdr.is-stuck .lockup .wm span{opacity:0;max-height:0;overflow:hidden}

/* ─── entrance animations ──────────────────────────────────────────────── */
.js [data-anim]{opacity:0;will-change:transform,opacity}
[data-anim="up"]     {transform:translateY(28px)}
[data-anim="left"]   {transform:translateX(-30px)}
[data-anim="right"]  {transform:translateX(30px)}
[data-anim="scale"]  {transform:scale(.955)}
[data-anim="blur"]   {filter:blur(14px);transform:scale(1.03)}
/* Clip reveals are applied to the CHILD, never to the observed box:
   a clip-path on the target itself collapses its intersection rectangle to
   zero, so IntersectionObserver never reports it as visible and the animation
   never starts. */
[data-anim="clip"],[data-anim="wipe"],[data-anim="circle"]{opacity:1}
.js [data-anim="clip"]>*  {clip-path:inset(0 0 100% 0)}
.js [data-anim="wipe"]>*  {clip-path:inset(0 100% 0 0)}
.js [data-anim="circle"]>*{clip-path:circle(0% at 50% 55%)}

[data-anim].is-in{
  opacity:1;transform:none;filter:none;
  transition:opacity var(--dur) var(--ease-out),
             transform var(--dur) var(--ease-out),
             filter var(--dur) var(--ease-out);
  transition-delay:var(--d,0ms);
}
[data-anim="clip"].is-in>*,[data-anim="wipe"].is-in>*{clip-path:inset(0 0 0 0)}
[data-anim="circle"].is-in>*{clip-path:circle(140% at 50% 55%)}
[data-anim="clip"]>*,[data-anim="wipe"]>*,[data-anim="circle"]>*{
  transition:clip-path 1.1s var(--ease-out);transition-delay:var(--d,0ms)}
[data-anim="up"].is-in,[data-anim="scale"].is-in{transition-timing-function:var(--ease-spring)}

/* stagger: the engine writes --d on each child */
.js [data-stagger]>*{opacity:0;transform:translateY(22px);will-change:transform,opacity}
[data-stagger].is-in>*{opacity:1;transform:none;
  transition:opacity .8s var(--ease-out),transform .8s var(--ease-spring);
  transition-delay:var(--d,0ms)}

/* ─── line-by-line heading reveal ──────────────────────────────────────── */
.split-line{display:block;overflow:hidden}
.js .split-line>i{display:block;font-style:inherit;transform:translateY(104%);
  transition:transform 1s var(--ease-out);transition-delay:var(--d,0ms)}
.is-in .split-line>i{transform:none}

/* ─── hero load sequence ───────────────────────────────────────────────── */
.hero{--hero-shift:0px}
.hero__bg{position:absolute;inset:-6% 0 0;z-index:-2;background-size:cover;
  background-position:26% 34%;transform:scale(1.07) translate3d(0,var(--hero-shift),0);
  transition:transform 2.4s var(--ease-out);will-change:transform}
.is-ready .hero__bg{transform:scale(1) translate3d(0,var(--hero-shift),0)}

.js .pane{opacity:0;transform:translateY(26px) scale(.985);
  transition:opacity 1s var(--ease-out) .18s,transform 1.1s var(--ease-spring) .18s}
.is-ready .pane{opacity:1;transform:none}

/* the glass catches a slow highlight sweep, once, after it lands */
.pane::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:linear-gradient(105deg,transparent 38%,rgba(255,255,255,.55) 50%,transparent 62%);
  transform:translateX(-120%);opacity:0}
.is-ready .pane::after{animation:sheen 1.6s 1.1s var(--ease-out) forwards}
@keyframes sheen{0%{transform:translateX(-120%);opacity:.9}
                 100%{transform:translateX(120%);opacity:0}}

/* ─── parallax targets (engine writes --py) ────────────────────────────── */
[data-parallax]{will-change:transform}
[data-parallax] img,img[data-parallax]{transform:translate3d(0,var(--py,0px),0) scale(1.08)}

/* ─── infinite marquee ─────────────────────────────────────────────────── */
.marquee{overflow:hidden;padding-block:26px;border-block:1px solid rgba(20,24,27,.12);
  background:#E5DFD4;-webkit-mask-image:linear-gradient(90deg,transparent,#000 9%,#000 91%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 9%,#000 91%,transparent)}
.marquee__track{display:flex;width:max-content;animation:marquee 34s linear infinite}
.marquee:hover .marquee__track{animation-play-state:paused}
.marquee__group{display:flex;align-items:center;gap:44px;padding-right:44px;flex:none}
.marquee__group b{font-size:clamp(17px,2.2vw,24px);font-weight:300;letter-spacing:-.02em;
  color:var(--ink);white-space:nowrap}
.marquee__group span{width:7px;height:7px;border-radius:50%;background:var(--accent);flex:none}
@keyframes marquee{to{transform:translateX(-50%)}}

/* ─── sticky stacking cards ────────────────────────────────────────────── */
/* The space between cards is grid gap, NOT margin. A sticky box may not be
   positioned so that its *margin box* leaves the containing block, so a bottom
   margin gets subtracted from the travel: with margins, all five cards were
   clamped to the same line near the end of the scene and the stack collapsed
   exactly as it did before. Gap is not margin and does not enter that
   calculation. The ::after is real content, so it extends the content box and
   with it the sticky range — padding would not, since the containing block for
   the children is the content box. It holds the finished stack on screen for a
   moment instead of releasing all five the instant the last one lands. */
.stack-scene{position:relative;display:grid;gap:clamp(78px,10vh,116px)}
.stack-scene::after{content:"";display:block;height:clamp(40px,7vh,90px)}
.stack-card{position:sticky;top:var(--stick,96px);background:var(--surface);
  border:1px solid var(--line);border-radius:var(--r-lg);padding:clamp(24px,3.4vw,40px);
  box-shadow:var(--shadow-m);margin:0;
  transform:scale(var(--s,1)) translateY(var(--ty,0px));
  transform-origin:50% 0;transition:transform .35s linear}
.stack-card__n{display:inline-flex;align-items:center;justify-content:center;
  width:34px;height:34px;border-radius:50%;background:var(--surface-2);
  font-size:12px;font-weight:800;color:var(--accent-ink);letter-spacing:.04em;
  font-variant-numeric:tabular-nums;margin-bottom:16px}
.stack-card h3{margin-bottom:8px}
.stack-card p{color:var(--ink-2);font-size:16px;line-height:1.6;max-width:58ch}

/* ─── tilt / magnetic hover ────────────────────────────────────────────── */
[data-tilt]{transform:perspective(900px) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))
            translateY(var(--lift,0px));transition:transform .45s var(--ease-out)}
[data-tilt].is-tilting{transition:transform .12s linear}
.btn[data-magnet]{transform:translate3d(var(--mx,0px),var(--my,0px),0)}

/* ─── count-up numbers ─────────────────────────────────────────────────── */
[data-count]{font-variant-numeric:tabular-nums}

/* ─── door cards: image drifts, label rises ────────────────────────────── */
.door__in>*{transition:transform .5s var(--ease-out)}
.door:hover .door__in>*{transform:translateY(-4px)}

/* ─── smooth-scroll wrapper ────────────────────────────────────────────── */
html.has-smooth{scroll-behavior:auto}

/* ─── the whole layer stands down when asked ───────────────────────────── */
@media (prefers-reduced-motion:reduce){
  [data-anim],[data-anim]>*,[data-stagger]>*,.pane{opacity:1!important;transform:none!important;
    filter:none!important;clip-path:none!important;transition:none!important}
  .split-line>i{transform:none!important;transition:none!important}
  .hero__bg{transform:none!important;transition:none!important}
  .pane::after{display:none}
  .marquee__track{animation:none;transform:none}
  /* the gap only exists to give the sticky sequence room; with the cards
     static it is 1.9m of empty band for five short paragraphs */
  .stack-scene{gap:20px}
  .stack-scene::after{display:none}
  .stack-card{position:static;transform:none!important;transition:none}
  [data-tilt]{transform:none!important}
  [data-parallax] img,img[data-parallax]{transform:none!important}
  .progress{display:none}
}

/* ═══════════════════════════════════════════════════════════════════════════
   SECOND PASS — the effects from the references that were still missing.
   Catalogue: FNK effects-showcase (13 scrolling / 16 text / 14 gallery),
   smile-craft-dental (lightbox, slider, float, pulse, mesh).
   Only the ones that suit a clinic are here — no glitch, neon or rainbow.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── page transition ───────────────────────────────────────────────────────
   There used to be a full-screen logo here, held for 1.05s and faded out over
   another 0.55s, on top of a 300ms fade on the way out. Two seconds of nothing
   per page change, on 25KB pages that are already cached. A preloader is for
   covering a real wait; there was none to cover.

   The arriving half is CSS only. No script runs, nothing is scheduled, it
   starts as soon as the browser has markup, and if the stylesheet never loads
   the page simply appears. Everything is scoped to .js so a failed script also
   leaves the page visible rather than stuck at opacity 0. */
/* `backwards`, not `both`. With `both` the last keyframe stays applied, and a
   transform that interpolated to `none` is kept as the identity matrix rather
   than the keyword. An identity matrix still makes the element the containing
   block for every position:fixed descendant, which stretched the lightbox
   inside <main> to the height of the document instead of the screen.
   `backwards` holds the first keyframe before the animation starts and then
   hands the element back to its own styles, where there is no transform. */
.js main{animation:pageIn .42s cubic-bezier(.22,1,.36,1) backwards}
@keyframes pageIn{from{opacity:0;transform:translate3d(0,16px,0)}
                  to{opacity:1;transform:none}}
.js .hdr{animation:pageInHdr .34s ease backwards}
@keyframes pageInHdr{from{opacity:0}to{opacity:1}}

/* The leaving half is the only part anyone waits for: 200ms, and it moves the
   opposite way so the two read as one continuous motion. */
.js body.is-leaving main{animation:none;opacity:0;transform:translate3d(0,-14px,0);
  transition:opacity .2s ease,transform .2s cubic-bezier(.4,0,1,1)}
.js body.is-leaving .hdr{opacity:0;transition:opacity .18s ease}

/* ── letter-by-letter fade (FNK text 2) ────────────────────────────────── */
.js [data-letters] i{display:inline-block;font-style:inherit;opacity:0;transform:translateY(8px);
  transition:opacity .5s var(--ease-out),transform .5s var(--ease-out);transition-delay:var(--d,0ms)}
[data-letters].is-in i{opacity:1;transform:none}

/* ── animated gradient text (FNK text 6) ───────────────────────────────── */
.grad-text{background:linear-gradient(100deg,var(--ink) 12%,var(--accent) 38%,#6A86C4 60%,var(--ink) 86%);
  background-size:260% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;
  animation:gradientFlow 9s ease-in-out infinite}
@keyframes gradientFlow{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}

/* ── stroke that fills (FNK text 10) ───────────────────────────────────── */
.stroke-text{-webkit-text-stroke:1.4px var(--ink);color:transparent;
  transition:color 1s var(--ease-out) .2s}
.is-in .stroke-text,.stroke-text.is-in{color:var(--ink)}

/* ── photograph inside the letters (FNK text 15) ───────────────────────── */
.mask-text{-webkit-background-clip:text;background-clip:text;color:transparent;
  background-size:cover;background-position:center 38%;
  font-weight:800;letter-spacing:-.055em;line-height:.86;
  font-size:clamp(58px,15vw,210px);text-align:center;display:block}

/* ── blur to clear (FNK scrolling 8 / text 13) ─────────────────────────── */
[data-anim="blur"]{filter:blur(16px);transform:scale(1.04)}

/* ── zoom in on scroll (FNK scrolling 5) ───────────────────────────────── */
[data-zoom]{overflow:hidden}
.js [data-zoom]>img{transform:scale(var(--z,1.16));transition:transform 1.4s var(--ease-out)}
[data-zoom].is-in>img{transform:scale(1)}

/* ── progress bars (FNK scrolling 11) ──────────────────────────────────── */
.bars{list-style:none;margin:0;padding:0;display:grid;gap:22px}
.bars li{display:grid;gap:9px}
.bars .bar__top{display:flex;justify-content:space-between;align-items:baseline;gap:16px}
.bars b{font-size:15.5px;font-weight:700;letter-spacing:-.01em}
.bars small{font-size:13px;font-weight:600;color:var(--muted);font-variant-numeric:tabular-nums}
.bars .bar{height:5px;border-radius:5px;background:var(--surface-3);overflow:hidden}
.js .bars .bar i{display:block;height:100%;width:0;border-radius:5px;background:var(--ink);
  transition:width 1.3s var(--ease-out);transition-delay:var(--d,0ms)}
.bars.is-in .bar i{width:var(--w,50%)}
.bars li:first-child .bar i{background:var(--accent)}

/* ── stat counters (FNK scrolling 10) ──────────────────────────────────── */
.stats{display:grid;gap:1px;background:var(--line);border-block:1px solid var(--line);
  grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}
.stats div{background:var(--surface);padding:clamp(26px,4vw,42px) 24px}
.stats .n{font-size:clamp(38px,5.4vw,62px);font-weight:300;letter-spacing:-.045em;line-height:1;
  font-variant-numeric:tabular-nums}
.stats .k{display:block;margin-top:12px;font-size:13.5px;font-weight:600;color:var(--muted);
  line-height:1.45;max-width:22ch}

/* ── horizontal scroll section (FNK scrolling 1; the cantor8 signature) ── */
.hscroll{position:relative}
.hscroll__pin{position:sticky;top:0;height:100vh;display:flex;align-items:center;overflow:hidden}
.hscroll__track{display:flex;gap:clamp(16px,2.4vw,26px);padding-inline:var(--pad);
  will-change:transform;transform:translate3d(var(--hx,0px),0,0)}
.hscroll__card{flex:none;width:min(78vw,380px);background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-lg);padding:28px 26px 30px;display:flex;flex-direction:column;
  min-height:min(58vh,420px)}
.hscroll__card .n{font-size:12px;font-weight:800;color:var(--accent);letter-spacing:.1em;
  font-variant-numeric:tabular-nums}
.hscroll__card h3{margin:14px 0 10px}
.hscroll__card p{color:var(--ink-2);font-size:15.5px;line-height:1.6;flex:1}
.hscroll__card .tagline{margin-top:18px;font-size:12.5px;font-weight:700;color:var(--muted)}

/* ── flip cards (FNK gallery 4) ────────────────────────────────────────── */
.flip{perspective:1200px;min-height:236px}
.flip__in{position:relative;width:100%;height:100%;min-height:236px;
  transition:transform .75s var(--ease-out);transform-style:preserve-3d}
.flip:hover .flip__in,.flip:focus-within .flip__in{transform:rotateY(180deg)}
.flip__face{position:absolute;inset:0;backface-visibility:hidden;border-radius:var(--r);
  border:1px solid var(--line);padding:26px 24px;display:flex;flex-direction:column;
  justify-content:flex-end;background:var(--surface)}
.flip__face--back{transform:rotateY(180deg);background:var(--ink);border-color:var(--ink)}
.flip__face h3{margin-bottom:8px}
.flip__face p{color:var(--ink-2);font-size:15px;line-height:1.6}
/* These two must come AFTER the generic .flip__face rules: same specificity,
   so source order decides. Above them, the back of every card was --ink-2 on
   --ink, 1.68:1, unreadable. */
.flip__face--back h3{color:#fff}
.flip__face--back p{color:#C3CCD8;font-size:15px;line-height:1.6}
.flip__hint{position:absolute;top:20px;right:22px;width:8px;height:8px;border-radius:50%;
  background:var(--accent)}

/* ── masonry gallery + zoom + slide-up overlay (FNK gallery 1, 2, 3) ───── */
.gallery{columns:3 260px;column-gap:16px}
.gallery figure{break-inside:avoid;margin:0 0 16px;position:relative;overflow:hidden;
  border-radius:var(--r);cursor:zoom-in;background:var(--surface-3)}
.gallery img{width:100%;display:block;transition:transform .8s var(--ease-out)}
.gallery figure:hover img{transform:scale(1.06)}
.gallery figcaption{position:absolute;inset:auto 0 0 0;padding:34px 18px 16px;color:#fff;
  font-size:14px;font-weight:700;letter-spacing:-.01em;
  background:linear-gradient(180deg,transparent,rgba(20,28,39,.82));
  transform:translateY(101%);transition:transform .45s var(--ease-out)}
.gallery figure:hover figcaption,.gallery figure:focus-within figcaption{transform:none}
.gallery figure.is-hidden{display:none}

/* ── filter chips (FNK gallery 9) ──────────────────────────────────────── */
.filters{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:26px}
.filters button{padding:9px 17px;border-radius:999px;border:1px solid var(--line);
  background:var(--surface);font-size:13.5px;font-weight:700;cursor:pointer;
  transition:background .2s,color .2s,border-color .2s}
.filters button[aria-pressed="true"]{background:var(--ink);color:#fff;border-color:var(--ink)}

/* ── lightbox (FNK gallery 13, smile-craft modal) ──────────────────────── */
.lightbox{position:fixed;inset:0;z-index:150;display:none;place-items:center;padding:24px;
  background:rgba(12,17,24,.9);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}
.lightbox.is-open{display:grid;animation:lightboxFadeIn .3s var(--ease-out)}
@keyframes lightboxFadeIn{from{opacity:0}to{opacity:1}}
.lightbox img{max-width:min(1100px,92vw);max-height:82vh;border-radius:var(--r);
  animation:modalIn .45s var(--ease-spring)}
@keyframes modalIn{from{opacity:0;transform:scale(.94) translateY(14px)}to{opacity:1;transform:none}}
.lightbox__cap{position:absolute;left:0;right:0;bottom:26px;text-align:center;color:#D6DEE8;
  font-size:14px;font-weight:600}
.lightbox button{position:absolute;top:20px;right:22px;width:46px;height:46px;border-radius:50%;
  border:0;background:rgba(255,255,255,.14);color:#fff;font-size:22px;cursor:pointer;line-height:1}
.lightbox .nav-prev,.lightbox .nav-next{top:50%;transform:translateY(-50%);width:52px;height:52px}
.lightbox .nav-prev{left:18px;right:auto}
.lightbox .nav-next{right:18px}

/* ── carousel with dots (FNK gallery 14, smile-craft slider) ───────────── */
.carousel{position:relative;overflow:hidden;border-radius:var(--r-lg)}
.carousel__track{display:flex;transition:transform .7s var(--ease-out)}
.carousel__slide{flex:none;width:100%;position:relative}
.carousel__slide img{width:100%;aspect-ratio:16/10;object-fit:cover}
.carousel__cap{position:absolute;left:0;right:0;bottom:0;padding:60px 26px 24px;color:#fff;
  background:linear-gradient(180deg,transparent,rgba(20,28,39,.85))}
.carousel__cap b{display:block;font-size:19px;font-weight:700;letter-spacing:-.02em}
.carousel__cap span{font-size:14px;color:rgba(255,255,255,.8)}
.carousel__dots{display:flex;gap:8px;justify-content:center;margin-top:18px}
.carousel__dots button{width:9px;height:9px;padding:0;border-radius:50%;border:0;cursor:pointer;
  background:var(--surface-3);transition:background .25s,width .25s}
.carousel__dots button[aria-current="true"]{background:var(--accent);width:26px;border-radius:5px}

/* ── mesh gradient band (smile-craft meshMove) ─────────────────────────── */
.mesh{position:relative;overflow:hidden;background:var(--ink);isolation:isolate}
/* One slow warm drift over ink. Saturated multi-hue mesh read as a toy on a
   clinic — the colour here is barely above the ground on purpose. */
.mesh::before{content:"";position:absolute;inset:-40%;z-index:-1;opacity:.9;
  background:radial-gradient(46% 58% at 76% 28%,rgba(255,90,60,.22),transparent 66%),
             radial-gradient(50% 62% at 16% 76%,rgba(122,152,166,.20),transparent 66%);
  animation:meshMove 26s ease-in-out infinite alternate}
@keyframes meshMove{0%{transform:translate(0,0) rotate(0deg) scale(1)}
                    100%{transform:translate(4%,-5%) rotate(9deg) scale(1.14)}}
.mesh h2{color:#F2EFEA}
.mesh p{color:#8B949B}

/* ── float, pulse, scroll cue (smile-craft float / pulse-dot / bounce) ─── */
.float{animation:float 6.5s ease-in-out infinite}
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-9px)}}

.pulse{display:inline-flex;align-items:center;gap:8px}
.pulse::before{content:"";width:8px;height:8px;border-radius:50%;background:#2CB67D;flex:none;
  box-shadow:0 0 0 0 rgba(44,182,125,.6);animation:pulse-dot 2.2s infinite}
@keyframes pulse-dot{0%{box-shadow:0 0 0 0 rgba(44,182,125,.55)}
                     70%{box-shadow:0 0 0 11px rgba(44,182,125,0)}
                     100%{box-shadow:0 0 0 0 rgba(44,182,125,0)}}

.scrollcue{position:absolute;left:50%;bottom:22px;transform:translateX(-50%);z-index:3;
  display:flex;flex-direction:column;align-items:center;gap:8px;
  font-size:10.5px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:#fff;
  text-shadow:0 1px 12px rgba(0,0,0,.72),0 0 3px rgba(0,0,0,.45)}
.scrollcue i{display:block;width:22px;height:34px;border:1.6px solid rgba(255,255,255,.75);
  border-radius:12px;position:relative}
.scrollcue i::after{content:"";position:absolute;left:50%;top:7px;width:3px;height:6px;
  border-radius:2px;background:#fff;transform:translateX(-50%);animation:scroll-bounce 1.9s infinite}
@keyframes scroll-bounce{0%{opacity:0;transform:translate(-50%,0)}
                         35%{opacity:1}100%{opacity:0;transform:translate(-50%,13px)}}

/* ─── the scroll hint, on a phone ──────────────────────────────────────────
   The upright "mouse" is 59px tall and the cue sits centred, so below 820px,
   where the glass pane stretches the full width instead of sitting on the
   right, it landed 35px inside the pane and across the headline. Laid out
   sideways with a chevron instead of a mouse it is about 20px tall and clears
   the pane. A mouse outline on a touch screen was the wrong picture anyway. */
/* Short viewports need it too: at 1024x768 the pane is tall enough that the
   upright cue cleared it by a single pixel, which is not clearance, it is luck. */
@media (max-width:819px),(max-height:820px){
  .scrollcue{flex-direction:row;gap:8px;bottom:12px;font-size:10px;letter-spacing:.14em}
  .scrollcue i{width:15px;height:15px;border:0;border-radius:0}
  .scrollcue i::after{left:50%;top:3px;width:7px;height:7px;border-radius:1px;
    background:none;border-right:1.8px solid #fff;border-bottom:1.8px solid #fff;
    animation:cue-nudge 1.9s infinite}
  @keyframes cue-nudge{
    0%{opacity:0;transform:translate(-50%,-4px) rotate(45deg)}
    35%{opacity:1}
    100%{opacity:0;transform:translate(-50%,5px) rotate(45deg)}}
}

@media (max-width:820px){
  .hscroll__pin{position:static;height:auto;overflow-x:auto;padding-block:8px;
    scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch}
  .hscroll__track{transform:none!important}
  .hscroll__card{scroll-snap-align:center}
  .gallery{columns:2 150px}
}

@media (prefers-reduced-motion:reduce){
  main,.hdr,body.is-leaving main,body.is-leaving .hdr{
    animation:none;opacity:1;transform:none;transition:none}
  [data-letters] i{opacity:1!important;transform:none!important}
  .grad-text{animation:none;background:none;-webkit-background-clip:initial;background-clip:initial;
    color:var(--ink)}
  .stroke-text{color:var(--ink);-webkit-text-stroke:0}
  [data-zoom]>img{transform:none!important}
  .bars .bar i{width:var(--w,50%)!important;transition:none}
  .hscroll__pin{position:static;height:auto;overflow-x:auto}
  .hscroll__track{transform:none!important}
  .flip__in,.gallery img,.gallery figcaption,.carousel__track{transition:none}
  .gallery figcaption{transform:none}
  .mesh::before,.float,.pulse::before,.scrollcue i::after{animation:none}
  .scrollcue{display:none}
}


/* ─── mobile: touch sizes and touch behaviour ──────────────────────────── */
@media (max-width:1159px){
  /* these live here because motion.css loads after site.css */
  .carousel__dots{gap:2px}
  .carousel__dots button{width:44px;height:44px;min-height:44px;background:none;position:relative;
    box-sizing:border-box;padding:0;border:0;flex:none}
  .carousel__dots button::after{content:"";position:absolute;left:50%;top:50%;
    transform:translate(-50%,-50%);width:9px;height:9px;border-radius:50%;
    background:var(--surface-3);transition:background .25s,width .25s}
  .carousel__dots button[aria-current="true"]{width:52px}
  .carousel__dots button[aria-current="true"]::after{background:var(--accent);width:26px;border-radius:5px}

  .flip__face p{font-size:16px}
  .flip__face{padding:22px 20px}
  /* there is no hover on a phone, so the card flips on tap instead */
  .flip{cursor:pointer}
  .flip.is-flipped .flip__in{transform:rotateY(180deg)}
  .flip:hover .flip__in{transform:none}
  .flip.is-flipped:hover .flip__in{transform:rotateY(180deg)}
  .flip__hint::after{content:"";position:absolute;inset:-14px;border-radius:50%}

  /* a phone scrolls a section this tall for a long time, so the cards get
     less room each — still enough to read one before the next covers it */
  .stack-scene{gap:clamp(60px,8vh,90px)}

  /* swipeable rows get proper snapping and a nudge past the edge */
  .hscroll__pin{scroll-padding-inline:var(--pad)}
  .hscroll__card{scroll-snap-align:start}
}


/* ═══ mobile motion ════════════════════════════════════════════════════════
   Phones get their own set. Not the desktop animations scaled down: different
   ones, for a screen held in one hand and driven by a thumb. */
@media (max-width:1159px){

  /* ── the header stays ─────────────────────────────────────────────────
     It used to slide away on scroll-down to give back 66px. That trade was
     wrong for this site: nine pages, and the header is the only way to reach
     any of them, so hiding it means an extra upward swipe every time someone
     wants to go elsewhere. 66px of 844 is 8% of the screen, and navigation
     that is always in reach is worth more than 8%. */

  /* ── the drawer opens instead of appearing ────────────────────────────
     display:none to display:block is a cut. Nine links landing at once give
     the eye nowhere to start, so they arrive in sequence, 40ms apart, which
     reads as one gesture rather than nine. */
  .js .drawer{display:block;max-height:0;overflow:hidden;visibility:hidden;
    transition:max-height .44s cubic-bezier(.22,1,.36,1),visibility 0s .44s}
  .js .drawer.open{max-height:min(78vh,660px);overflow-y:auto;visibility:visible;
    transition:max-height .48s cubic-bezier(.22,1,.36,1)}
  .js .drawer li,.js .drawer .btns{opacity:0;transform:translateY(9px);
    transition:opacity .3s ease,transform .34s cubic-bezier(.22,1,.36,1)}
  .js .drawer.open li,.js .drawer.open .btns{opacity:1;transform:none}
  .js .drawer.open li:nth-child(1){transition-delay:.05s}
  .js .drawer.open li:nth-child(2){transition-delay:.09s}
  .js .drawer.open li:nth-child(3){transition-delay:.13s}
  .js .drawer.open li:nth-child(4){transition-delay:.17s}
  .js .drawer.open li:nth-child(5){transition-delay:.21s}
  .js .drawer.open li:nth-child(6){transition-delay:.25s}
  .js .drawer.open li:nth-child(7){transition-delay:.29s}
  .js .drawer.open li:nth-child(8){transition-delay:.33s}
  .js .drawer.open li:nth-child(9){transition-delay:.37s}
  .js .drawer.open .btns{transition-delay:.42s}

  /* ── the swipeable row says so ────────────────────────────────────────
     A row of cards clipped by the screen edge reads as broken, not as
     swipeable. One 12px nudge the first time it comes into view is the whole
     affordance. It runs once and never again: repeating it would be noise. */
  .js .hscroll__pin.is-hinting{animation:swipeHint 1.15s cubic-bezier(.4,0,.3,1) .15s 2}
  /* the settle stays on the negative side: a positive overshoot, even 2px,
     widens the document and gives the page a horizontal scrollbar */
  @keyframes swipeHint{
    0%,100%{transform:translateX(0)}
    36%{transform:translateX(-14px)}
    70%{transform:translateX(-3px)}
  }

  /* ── the card under the thumb is the one in focus ─────────────────────
     Six cards at equal weight on a 390px screen is six things competing. The
     one nearest the middle of the viewport carries full contrast; the rest sit
     back. Tracks the swipe, so the emphasis follows the thumb. */
  .js .hscroll__card{transition:opacity .32s ease,transform .32s cubic-bezier(.22,1,.36,1)}
  .js .hscroll__pin.is-tracking .hscroll__card{opacity:.52;transform:scale(.955)}
  .js .hscroll__pin.is-tracking .hscroll__card.is-focus{opacity:1;transform:none}

  /* ── position in a long row ───────────────────────────────────────────
     Προβλήματα is eighteen entries. A hairline under the row that fills as you
     swipe answers "how much of this is there" without adding a control. */
  .hscroll{position:relative}
  .js .hscroll__rule{position:absolute;left:var(--pad);right:var(--pad);bottom:6px;height:2px;
    border-radius:2px;background:currentColor;opacity:.14}
  .js .hscroll__rule i{position:absolute;inset:0 auto 0 0;border-radius:2px;
    background:var(--accent);width:22%;transform-origin:0 50%}
}

/* ── scroll-driven, where the browser has it ────────────────────────────
   animation-timeline runs on the compositor, so the hairline stays exact
   during momentum scrolling on iOS, where a rAF loop drifts behind the
   fingertip. Chrome 115+, Safari 18+, Firefox 132+; everyone else keeps the
   JavaScript path, which is unchanged. */
@supports (animation-timeline:scroll()){
  @media (prefers-reduced-motion:no-preference){
    .js .progress{animation:sdProgress linear both;animation-timeline:scroll(root block)}
    @keyframes sdProgress{from{transform:scaleX(0)}to{transform:scaleX(1)}}
  }
}

@media (max-width:1159px) and (prefers-reduced-motion:reduce){
  .js .drawer{transition:none}
  .js .drawer li,.js .drawer .btns{opacity:1;transform:none;transition:none}
  .js .hscroll__pin.is-hinting{animation:none}
  .js .hscroll__pin.is-tracking .hscroll__card{opacity:1;transform:none;transition:none}
}


/* ─── carousel dots: a 9px circle is a 9px target ──────────────────────────
   The dot stays 9px to look at; the button around it grows to 26px so a mouse
   can hit it (WCAG 2.5.8 asks for 24). Phones get 44 in the block above. */
@media (min-width:1160px){
  .carousel__dots{gap:0}
  .carousel__dots button{width:26px;height:26px;background:none;position:relative;
    box-sizing:border-box;padding:0;border:0;flex:none;border-radius:50%}
  .carousel__dots button::after{content:"";position:absolute;left:50%;top:50%;
    transform:translate(-50%,-50%);width:9px;height:9px;border-radius:50%;
    background:var(--surface-3);transition:background .25s,width .25s}
  .carousel__dots button[aria-current="true"]{width:43px;background:none}
  .carousel__dots button[aria-current="true"]::after{background:var(--accent);width:26px;border-radius:5px}
}
