/* ============================================================
   Mangraviti — Skybox mechanic prototype
   ============================================================ */

/* ---- Aeonik (TRIAL) — swapped in to preview the typeface -----------------
   Trial cut: only covers A–Z a–z 0–9 space , - .  Everything else
   (em dash, ·, /, %, ×, …, apostrophes) falls back to the stack below.     */
@font-face{font-family:"Aeonik";src:url("fontset%20-%20aeonik/AeonikTRIAL-Light.otf") format("opentype");font-weight:300;font-style:normal;font-display:swap}
@font-face{font-family:"Aeonik";src:url("fontset%20-%20aeonik/AeonikTRIAL-LightItalic.otf") format("opentype");font-weight:300;font-style:italic;font-display:swap}
@font-face{font-family:"Aeonik";src:url("fontset%20-%20aeonik/AeonikTRIAL-Regular.otf") format("opentype");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Aeonik";src:url("fontset%20-%20aeonik/AeonikTRIAL-RegularItalic.otf") format("opentype");font-weight:400;font-style:italic;font-display:swap}
@font-face{font-family:"Aeonik";src:url("fontset%20-%20aeonik/AeonikTRIAL-Medium.otf") format("opentype");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"Aeonik";src:url("fontset%20-%20aeonik/AeonikTRIAL-MediumItalic.otf") format("opentype");font-weight:500;font-style:italic;font-display:swap}
@font-face{font-family:"Aeonik";src:url("fontset%20-%20aeonik/AeonikTRIAL-SemiBold.otf") format("opentype");font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"Aeonik";src:url("fontset%20-%20aeonik/AeonikTRIAL-Bold.otf") format("opentype");font-weight:700;font-style:normal;font-display:swap}

:root{
  --ink:#12151a;
  --ink-2:#41474f;
  --ink-3:#878e97;
  --paper:255,255,255;
  --hair:rgba(18,21,26,.10);
  --sans:"Aeonik","Helvetica Neue",Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",Arial,sans-serif;
  --mono:"Aeonik",ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace;
  --ease:cubic-bezier(.16,1,.3,1);
  --ease-io:cubic-bezier(.65,0,.35,1);
  /* the ramp: eases in as well as out, so nothing starts at full speed */
  --ramp:cubic-bezier(.52,.02,.12,1);
  /* travel between pages: eases in and out, never overshoots (both control points
     stay inside 0-1), so it glides in and settles rather than launching and braking */
  --snapEase:cubic-bezier(.38,.02,.16,1);
  --meterTop:2.2cqh;      /* bar and close ride high; hover words drop below them */
}

*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;background:#07090c;color:var(--ink);
  font-family:var(--sans);-webkit-font-smoothing:antialiased;
  overflow:hidden;
}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}

/* ---------- stage / harness ---------- */
.stage{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;gap:38px;padding:0}
.stage.is-split{padding:76px 34px 34px;gap:34px}
.stage.is-mobile{padding:44px 20px 66px}

.frame{position:relative;flex:0 0 auto;transform-origin:center center}
.frame-label{
  position:absolute;left:0;top:-26px;font:500 10px/1 var(--mono);
  letter-spacing:.22em;text-transform:uppercase;color:#8a939f;
}
.frame-inner{position:relative;overflow:hidden;background:#05070a}
.frame.device .frame-inner{border-radius:44px;box-shadow:0 0 0 10px #1b1f26,0 0 0 11px #33393f,0 40px 90px -20px #000}
.frame.laptop .frame-inner{border-radius:10px;box-shadow:0 0 0 1px #23282f,0 40px 90px -20px #000}
.frame.device .notch{position:absolute;left:50%;top:10px;translate:-50% 0;width:104px;height:26px;border-radius:16px;background:#000;z-index:60;pointer-events:none;opacity:.85}

/* the plain-text outline of the site: present for crawlers and screen readers, never seen */
.outline{position:absolute;left:-10000px;top:0;width:1px;height:1px;overflow:hidden}
.ctrl select{width:100%;background:#151a21;color:#eef2f7;border:1px solid #232a33;border-radius:8px;padding:6px 8px;font:500 11px var(--mono)}

/* the public page carries none of the harness */
.public .chrome,.public .panel{display:none!important}

/* ---------- chrome ---------- */
/* harness chrome is out of the way while the design is being reviewed —
   press H to bring it back */
body:not(.chrome-on) .chrome{opacity:0;pointer-events:none}
.chrome{
  transition:opacity .3s ease;
  position:fixed;left:0;right:0;bottom:0;z-index:200;height:54px;
  display:flex;align-items:center;gap:16px;padding:0 14px;
  background:linear-gradient(#0a0d1100,#0a0d11ee 60%);pointer-events:none;
}
.chrome > *{pointer-events:auto}
.chrome-brand{font:500 10px/1 var(--mono);letter-spacing:.24em;color:#9aa4b0;text-transform:uppercase}
.chrome-brand span{color:#59616c;margin-left:6px}
.chrome-actions{margin-left:auto;display:flex;gap:8px}
.seg{display:flex;background:#151a21;border:1px solid #232a33;border-radius:999px;padding:3px}
.seg button{
  padding:5px 13px;border-radius:999px;font-size:11px;letter-spacing:.08em;
  text-transform:uppercase;color:#8d97a3;transition:.2s
}
.seg button.on{background:#eef2f7;color:#0d1116}
.seg.small button{padding:4px 10px;font-size:10px}
.ghost{
  padding:6px 13px;border-radius:999px;border:1px solid #232a33;background:#151a21;
  font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#9aa4b0;transition:.2s
}
.ghost:hover{color:#eef2f7;border-color:#3b444f}

/* ---------- tuning panel ---------- */
.panel{
  position:fixed;right:14px;top:14px;z-index:210;width:284px;max-height:calc(100vh - 82px);
  display:flex;flex-direction:column;
  background:#0f1319f2;border:1px solid #232a33;border-radius:16px;
  backdrop-filter:blur(14px);color:#c6ced8;
  box-shadow:0 30px 80px -20px #000;
}
.panel[hidden]{display:none}
.panel-head{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid #1c222a}
.panel-head strong{font:500 10px/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:#eef2f7}
.panel-body{overflow:auto;padding:10px 14px 14px}
.ctrl{margin:11px 0}
.ctrl label{display:flex;justify-content:space-between;font:500 10px/1.4 var(--mono);letter-spacing:.1em;text-transform:uppercase;color:#8d97a3;margin-bottom:6px}
.ctrl label b{color:#eef2f7;font-weight:500}
.ctrl input[type=range]{width:100%;accent-color:#eef2f7;height:16px}
.ctrl.check{display:flex;align-items:center;gap:8px}
.ctrl.check label{margin:0;flex:1}
.panel-foot{display:flex;gap:8px;padding:10px 14px;border-top:1px solid #1c222a}
.panel-foot .ghost{flex:1;text-align:center}
.panel-note{margin:0;padding:0 14px 12px;font-size:10px;line-height:1.7;color:#6c7581}
kbd{display:inline-block;padding:1px 5px;border:1px solid #2b323b;border-radius:4px;font:10px var(--mono);margin-right:2px;color:#9aa4b0}

/* ============================================================
   SKY — one self-contained instance
   ============================================================ */
.sky{
  position:relative;width:100%;height:100%;overflow:hidden;isolation:isolate;
  background:#05070a;touch-action:none;user-select:none;
  font-family:var(--sans);
}

/* ---------- background scenes ---------- */
.scenes{position:absolute;inset:0;z-index:0}
.scene{position:absolute;inset:0;opacity:0;transition:opacity .6s var(--ramp);will-change:opacity}
.scene.on{opacity:1;z-index:2}
/* the sky on its way out holds at full opacity beneath the incoming one, then drops
   without a transition once it is covered — no half-transparent midpoint, ever */
.scene.was{opacity:1;z-index:1;transition:none}
/* real sky photo — covers the procedural layers of its scene when mapped in content.js */
.scene-photo{position:absolute;inset:0;background-size:cover;background-position:center;background-repeat:no-repeat}
.vignette{position:absolute;inset:0;z-index:1;pointer-events:none;transition:opacity 1.4s var(--ramp);
  background:radial-gradient(120% 90% at 50% 40%,transparent 40%,rgba(0,0,0,.42) 100%)}
/* Grain sits over the sky as a plain, viewport-sized layer. It used to be a blended
   layer four times the viewport (inset:-50%, mix-blend-mode:overlay); WebKit repaints
   the edge tiles of such layers late while a fade runs beneath them, which is what left
   the corners holding the previous sky while the rest of the frame changed. */
.grain{position:absolute;inset:0;z-index:2;pointer-events:none;opacity:.11;transition:opacity 1.4s var(--ramp);
  background-image:var(--noise);background-size:180px 180px}


/* ---------- shared card surface ---------- */
.card{
  background:rgb(var(--paper));
  border-radius:var(--r);
  overflow:hidden;
}

/* ---------- hero card ---------- */
/* ---------- index: free in the skybox ---------- */
.scrim{position:absolute;inset:0;z-index:3;pointer-events:none;opacity:var(--hubP,0);
  transition:opacity .5s linear;
  /* A flat wash. Any gradient here darkens the middle more than the corners, so as it
     fades in the corners keep the sky's colour while the centre goes down — the
     "coloured corners" that never quite matched. Uniform tone fades the whole frame
     together; the vignette underneath still shapes the edges, and it never changes. */
  background:rgba(3,8,18,.46)}
.hub{
  position:absolute;left:50%;top:50%;z-index:18;
  width:var(--w,80%);height:var(--h,80%);
  transform:translate(-50%,calc(-50% + var(--ty,0px))) scale(var(--s,1));
  container-type:size;display:flex;align-items:center;justify-content:center;
  opacity:var(--hubO,1);
  transition:transform var(--snapMs) var(--ramp),opacity .7s var(--ramp),filter .7s var(--ramp);
  will-change:transform;
}
.sky[data-cardopen="1"] .hub{opacity:calc(var(--hubO,1) * (1 - .7*var(--recede)));filter:blur(calc(4px*var(--recede)));--s:calc(1 - .04*var(--recede))}
.sky.dragging .hub{transition:opacity .5s var(--ramp),filter .5s var(--ramp)}

.index{display:flex;align-items:baseline;justify-content:center;gap:4.4cqw;width:100%}
.chapter{
  display:flex;align-items:baseline;gap:1.1cqw;color:#fff;white-space:nowrap;
  text-shadow:0 1px 40px rgba(2,8,20,.55),0 1px 3px rgba(2,8,20,.25);
  opacity:.86;transition:opacity .35s var(--ramp);
}
.chapter:hover{opacity:1}
.chapter .num{font:500 clamp(8px,1.1cqw,13px)/1 var(--mono);letter-spacing:.24em;opacity:.62}
.chapter .name{
  position:relative;font-size:clamp(24px,8cqw,132px);line-height:.95;letter-spacing:-.04em;font-weight:500;
}
.chapter .name::after{
  content:"";position:absolute;left:0;right:0;bottom:-.16em;height:1px;background:currentColor;
  transform:scaleX(0);transform-origin:left;transition:transform .5s var(--ramp);opacity:.7
}
.chapter:hover .name::after{transform:scaleX(1)}

.hero{
  position:absolute;left:50%;top:50%;z-index:20;
  width:var(--w,100%);height:var(--h,100%);
  transform:translate(calc(-50% + var(--tx,0px)),calc(-50% + var(--ty,0px))) scale(var(--sx,1),var(--sy,1));
  container-type:size;
  /* The card's width and height NEVER change — it is always its settled size and the
     intro is a pure transform. Animating width/height on a container-query container
     made every cq-sized child (the wordmark, the bio, the portrait) re-resolve and
     re-round every frame, which is what the settle jitter was. A transform is composited
     and touches no layout, so nothing inside is measured twice. */
  transition:border-radius var(--introMs) var(--ramp),transform var(--introMs) var(--ramp),
             opacity .7s var(--ramp),filter .7s var(--ramp);
  will-change:transform;
}
.sky[data-phase="settled"] .hero{transition:border-radius .7s var(--ramp),transform var(--heroSnapMs,760ms) var(--ramp),opacity .7s var(--ramp),filter .7s var(--ramp)}   /* width/height are never animated — see .hero */

.hero-page{position:absolute;inset:0;display:flex;flex-direction:column;padding:6cqh 4.5cqw}

/* page A — everything locked to the wordmark's width, scaling with it */
.page-a{align-items:center;justify-content:center}
/* The lockup is sized off the card's WIDTH, but the portrait hangs above it, so on a
   wide card — which is also a short one — the photo climbs toward the top edge. The
   cqh term caps it against the card's height so the clearance holds at any aspect.
   On a 16:10 laptop the height term used to bind hard (61cqw=738 vs 88cqh=585), which
   collapsed the lockup and left ~240px of dead white down each side. The cap now only
   takes over on genuinely tall frames, so width leads on every normal desktop. */
.lockup{position:relative;width:min(var(--lock,70cqw),112cqh)}
.wordmark{position:relative;z-index:1;line-height:0;width:100%;aspect-ratio:var(--wm-ar,4.858)}
.wordmark > img{display:block;width:100%;height:auto}
/* the mark assembles in vertical bands, each passing through grey on its way to black */
.wm-b{
  position:absolute;inset:0;
  clip-path:inset(0 calc(100% - (var(--i) + 1) * (100% / var(--bands))) 0 calc(var(--i) * (100% / var(--bands))));
}
.wm-b img{position:absolute;inset:0;width:100%;height:100%;display:block}
.sky[data-phase="curtain"] .wm-b{
  animation:wm-in calc(var(--holdMs) * .5) var(--ramp) both;
  animation-delay:calc(var(--i) * var(--holdMs) * .04);
}
/* ---- intro treatments (Tune → Intro treatment, or ?intro=name) ----------------
   `bands` is the original: nine vertical bands recolour left to right. The others
   either re-time the bands or animate the mark as one piece. Nothing else changes. */
/* cascade: the bands recolour from the middle outward */
.sky[data-intro="cascade"][data-phase="curtain"] .wm-b{animation-delay:calc(var(--c) * var(--holdMs) * .075)}
/* type: letter by letter, hard on, no ramp */
.sky[data-intro="type"][data-phase="curtain"] .wm-b{animation:wm-type 1ms steps(1,end) both;animation-delay:calc(var(--i) * var(--holdMs) * .07 + var(--holdMs) * .08)}
@keyframes wm-type{0%{opacity:0}100%{opacity:1}}
/* fall: each band drops the last few pixels into place as it darkens */
.sky[data-intro="fall"][data-phase="curtain"] .wm-b{animation:wm-fall calc(var(--holdMs) * .45) var(--ramp) both;animation-delay:calc(var(--i) * var(--holdMs) * .045)}
@keyframes wm-fall{0%{opacity:0;transform:translateY(-.12em)}100%{opacity:1;transform:none}}
/* one-piece treatments: the bands sit still and the mark itself does the work */
.sky[data-intro="wash"][data-phase="curtain"] .wm-b,
.sky[data-intro="wipe"][data-phase="curtain"] .wm-b,
.sky[data-intro="rise"][data-phase="curtain"] .wm-b,
.sky[data-intro="settle"][data-phase="curtain"] .wm-b,
.sky[data-intro="focus"][data-phase="curtain"] .wm-b{animation:none;opacity:1}
/* wash: the mark comes up from nothing as one tone, like ink through paper */
.sky[data-intro="wash"][data-phase="curtain"] .wordmark{animation:wm-wash calc(var(--holdMs) * .7) var(--ramp) both;animation-delay:calc(var(--holdMs) * .12)}
@keyframes wm-wash{0%{opacity:0}100%{opacity:1}}
/* wipe: revealed left to right by a straight edge */
.sky[data-intro="wipe"][data-phase="curtain"] .wordmark{animation:wm-wipe calc(var(--holdMs) * .62) var(--ramp) both;animation-delay:calc(var(--holdMs) * .14)}
@keyframes wm-wipe{0%{clip-path:inset(-5% 100% -5% 0)}100%{clip-path:inset(-5% 0 -5% 0)}}
/* rise: fades in while lifting the last half-line into place */
.sky[data-intro="rise"][data-phase="curtain"] .wordmark{animation:wm-rise calc(var(--holdMs) * .6) var(--ramp) both;animation-delay:calc(var(--holdMs) * .12)}
@keyframes wm-rise{0%{opacity:0;transform:translateY(.22em)}100%{opacity:1;transform:none}}
/* settle: lands from very slightly larger, like type pressed into the sheet */
.sky[data-intro="settle"][data-phase="curtain"] .wordmark{animation:wm-settle calc(var(--holdMs) * .7) var(--ramp) both;animation-delay:calc(var(--holdMs) * .08)}
@keyframes wm-settle{0%{opacity:0;transform:scale(1.06)}100%{opacity:1;transform:scale(1)}}
/* focus: pulls from a soft blur to sharp */
.sky[data-intro="focus"][data-phase="curtain"] .wordmark{animation:wm-focus calc(var(--holdMs) * .75) var(--ramp) both;animation-delay:calc(var(--holdMs) * .08)}
@keyframes wm-focus{0%{opacity:0;filter:blur(.18em);transform:scale(.985)}40%{opacity:1}100%{opacity:1;filter:blur(0);transform:none}}

/* recolour only — the bands never move */
@keyframes wm-in{
  0%  {opacity:0}
  20% {opacity:.07}
  40% {opacity:.17}
  60% {opacity:.32}
  80% {opacity:.56}
  100%{opacity:1}
}
/* portrait rides behind the wordmark, off its top-right corner */
/* 34.5% wide (a quarter more than the 27.6% it started at, all of it to the left —
   the right edge stays put) and a tenth taller, at 10:11. The translate keeps its
   bottom edge exactly where the 4:5 one sat, so the extra height goes upward. */
.portrait{
  position:absolute;z-index:0;right:1.5%;top:0;translate:0 -78.2%;
  width:34.5%;aspect-ratio:10/9.4;border-radius:0;overflow:hidden;
  background:linear-gradient(150deg,#c9d3e0,#8e9bad 55%,#5f6b7d);
  clip-path:inset(100% 0 0 0);          /* zero-height band at the bottom edge */
  transition:clip-path .62s var(--ramp) .06s;
}
/* the portrait and the foot start arriving while the card is still shrinking, a little
   past halfway through the settle, rather than waiting for it to finish */
.sky[data-phase="settle"] .portrait{clip-path:inset(0 0 0 0);transition-delay:calc(var(--introMs) * .55)}
.sky[data-phase="settled"] .portrait{clip-path:inset(0 0 0 0)}
/* the photo is 4:5; the wider box shows 88% of its height, taken from a little below the
   top so the shoes and roof keep a foothold rather than the sky above the cap */
.portrait.has-photo{background-size:cover;background-position:center 55%;background-repeat:no-repeat}
.portrait::after{content:"";position:absolute;inset:0;background:
  radial-gradient(50% 38% at 50% 32%,rgba(255,255,255,.55),transparent 70%),
  radial-gradient(60% 44% at 50% 96%,rgba(20,28,42,.5),transparent 70%)}
/* the photo carries its own gradient at the base — no scrim on top of it */
.portrait.has-photo::after{background:none}

.hero-foot{
  position:absolute;top:100%;left:0;right:0;
  display:flex;align-items:flex-end;justify-content:space-between;gap:5%;
  margin-top:4.2%;opacity:0;translate:0 14px;
  transition:opacity .5s var(--ramp) .08s,translate .56s var(--ramp) .08s;
}
.sky[data-phase="settle"] .hero-foot{opacity:1;translate:0 0;transition-delay:calc(var(--introMs) * .65)}
.sky[data-phase="settled"] .hero-foot{opacity:1;translate:0 0}
.bio{
  flex:1 1 auto;max-width:52%;margin:0;text-align:left;
  font-size:clamp(10px,1.42cqw,16px);line-height:1.42;color:var(--ink-2);letter-spacing:.004em;
}
.socials{display:flex;align-items:center;gap:1.5cqw;list-style:none;margin:0 0 .2em;padding:0;flex:0 0 auto}
.socials a{display:block;color:var(--ink-3);transition:color .25s var(--ramp),transform .25s var(--ramp)}
.socials a:hover{color:var(--ink);transform:translateY(-2px)}
.socials svg{display:block;width:clamp(15px,1.95cqw,24px);height:auto}

/* ---------- section card ---------- */
.section-card{
  position:absolute;z-index:30;display:none;
  transform:translate(var(--ex,0px),var(--ey,0px));
}
.section-card.live{display:block}
/* Promote to its own layer ONLY while the card is actually moving. The card is built
   off-screen and flown in; a layer created off-screen and then left promoted for good
   can sit unrastered until some input event invalidates it — which is why the first page
   arrived blank and needed a second tap to appear. Dropping .anim when the entry lands
   collapses the layer and forces the content to re-raster where it actually is. */
.section-card.anim{transition:transform var(--entryMs) var(--ramp);will-change:transform}

.card-window{position:absolute;overflow:hidden}
.panel-track{position:absolute;inset:0;will-change:transform;
  transition:transform var(--dragMs,110ms) linear}
.panel-track.snap{transition:transform var(--snapMs) var(--snapEase)}
/* under a finger the content tracks it exactly — the wheel's smoothing would restart on
   every move and stutter behind the drag */
.panel-track.finger,.gal-track.finger{transition:none}
.pane{
  position:absolute;padding:9cqh 5cqw 9cqh 2cqw;display:flex;gap:4cqw;container-type:size;
  transition:opacity .5s var(--ramp);
}
.section-card[data-axis="x"] .pane{flex-direction:row;align-items:stretch}
.section-card[data-axis="y"] .pane{flex-direction:column;justify-content:space-between}
.pane-media{
  flex:0 0 46%;border-radius:calc(var(--r) * .5);overflow:hidden;position:relative;
  height:100%;background:var(--m,linear-gradient(140deg,#dfe6ef,#9fb0c6));
}
.section-card[data-axis="y"] .pane{padding:9cqh 17cqw 9cqh 2cqw;gap:3cqh}
/* Story reads text-first: header + caption on top, media below */
.section-card[data-axis="y"] .pane-media{order:2;flex:0 0 46%;width:100%;height:46%}
.section-card[data-axis="y"] .pane-body{order:1;flex:0 1 auto;display:flex;flex-direction:column;min-height:0;justify-content:flex-start}
.section-card[data-axis="y"] .pane-body .pane-list{margin-top:2.2cqh}
.section-card[data-axis="y"] .pane-list{margin-top:1.6cqh}
.section-card[data-axis="y"] .pane-title{font-size:clamp(16px,3cqw,34px)}
.pane-media::after{content:"";position:absolute;inset:0;background:
  radial-gradient(80% 60% at 30% 20%,rgba(255,255,255,.4),transparent 60%),
  linear-gradient(to top,rgba(8,14,26,.42),transparent 55%)}
.pane-media .tag{
  position:absolute;left:0;bottom:0;z-index:2;margin:1.6cqh 1.4cqw;
  font:500 clamp(7px,.9cqw,10px)/1 var(--mono);letter-spacing:.22em;text-transform:uppercase;color:#fff;opacity:.9
}
.pane-body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center}
.pane-index{font:500 clamp(7px,.95cqw,10px)/1 var(--mono);letter-spacing:.26em;color:var(--ink-3);margin-bottom:1.6cqh}
.pane-title{margin:0 0 1.4cqh;font-size:clamp(17px,3.4cqw,40px);line-height:1.02;letter-spacing:-.025em;font-weight:500}
.pane-text{margin:0;font-size:clamp(10px,1.6cqw,16px);line-height:1.6;color:var(--ink-2);max-width:34em}
.pane-list{margin:2.2cqh 0 0;padding:0;list-style:none;border-top:1px solid var(--hair)}
.section-card[data-axis="x"] .pane-body .pane-list{margin-top:auto}
.pane-list li{
  display:flex;justify-content:space-between;gap:2cqw;padding:1.5cqh 0;border-bottom:1px solid var(--hair);
  font-size:clamp(9px,1.25cqw,13px);color:var(--ink-2);letter-spacing:.01em
}
.pane-list li span:last-child{color:var(--ink-3);font-family:var(--mono);font-size:.9em}

/* section intro page — title + paragraph left, full-bleed image right */
.pane-intro{
  flex-direction:row!important;align-items:center;gap:0;
  padding:0!important;overflow:hidden;
}
.intro-media{
  position:absolute;right:5cqw;top:9cqh;bottom:9cqh;width:38%;
  border-radius:0;                      /* squared corners, inset from the card edges */
  background:var(--m,linear-gradient(140deg,#dfe6ef,#9fb0c6));
}
.intro-body{
  width:53%;align-self:flex-start;display:flex;align-items:flex-start;gap:6cqw;
  padding:9cqh 0 0 2cqw;               /* same top + left gutter every page uses */
}
.intro-title{
  flex:0 0 auto;margin:-.09em 0 0;white-space:nowrap;   /* pull the cap up to the image's top edge */
  font-size:clamp(26px,7.5cqw,104px);line-height:.9;letter-spacing:-.04em;font-weight:500;
}
.intro-text{
  flex:1 1 auto;min-width:0;margin:.2em 0 0;
  font-size:clamp(11px,1.75cqw,20px);line-height:1.5;color:var(--ink-2);
}
/* ---- overview with projects: bigger title, logo tag right, menu bottom-left ----
   Only sections that actually have projects take this layout (.has-menu), so About
   and Ventures keep the plain title + paragraph + image overview. */
/* the overview sits on the same 5cqw content box as the progress bar and every card:
   the title's left edge on the bar's left end, the paragraph's right edge on its right */
.pane-intro.has-menu .intro-body{
  width:62%;flex-direction:column;align-items:flex-start;gap:2.2cqh;
  padding:9cqh 5cqw 0;
}
.pane-intro.has-menu .intro-title{
  margin:-.09em 0 0;font-size:clamp(30px,9.4cqw,132px);
}
.pane-intro.has-menu .intro-text{
  margin:0;max-width:30em;font-size:clamp(11px,1.55cqw,18px);
}

/* On a wide card — 7:5 and wider — the paragraph would sit in the menu's path, so it
   moves to the top right, aligned with the mark's right edge and the title's top. The
   left column keeps the title and the list; the menu also tightens a little. */
@container (min-aspect-ratio: 7/5){
  .pane-intro.has-menu .intro-text{position:absolute;top:9cqh;right:5cqw;width:32%;margin:0;max-width:none}
  .pane-intro.has-menu .intro-body{width:58%}
  .pane-intro.has-menu .menu-item button{padding:.7cqh 1.2cqw}
}

/* the logo tag — one slot per project, cross-faded; a small mark, not a picture */
.intro-mark{
  position:absolute;right:5cqw;bottom:7cqh;z-index:3;
  width:16%;aspect-ratio:1;pointer-events:none;
}
.mark-slot{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:flex-end;
  opacity:0;transition:opacity .42s var(--ramp);
}
.mark-slot[data-on="1"]{opacity:1}
.mark-slot img{max-width:100%;max-height:100%;object-fit:contain;object-position:right bottom;
  filter:brightness(0)}
.mark-word{
  font:500 clamp(9px,1.15cqw,14px)/1 var(--mono);
  letter-spacing:.2em;text-transform:uppercase;color:var(--ink-3);text-align:right;
}

/* the menu */
.intro-menu{position:absolute;left:5cqw;right:24cqw;bottom:7cqh;z-index:4}
.menu-list{counter-reset:proj;position:relative;list-style:none;margin:0;padding:0;max-width:34em}
.menu-hl{
  position:absolute;left:0;top:0;width:100%;height:0;z-index:0;
  background:rgba(18,21,26,.055);opacity:0;pointer-events:none;
  transition:transform .34s var(--ramp),height .34s var(--ramp),opacity .22s var(--ramp);
  will-change:transform;
}
.menu-hl[data-on="1"]{opacity:1}
.menu-item{counter-increment:proj;position:relative;z-index:1}
.menu-item button{
  display:flex;align-items:center;gap:1.1cqw;width:100%;
  background:none;border:0;margin:0;padding:.85cqh 1.2cqw;
  font:inherit;text-align:left;color:var(--ink-3);cursor:pointer;
  transition:color .2s var(--ramp);
}
.menu-item button::before{
  content:counter(proj,decimal-leading-zero);flex:0 0 auto;
  font:500 clamp(9px,1.12cqw,14px)/1 var(--mono);letter-spacing:.18em;
  opacity:.3;transition:opacity .16s ease-in;
}
.menu-item.is-active button{color:var(--ink)}
.menu-item.is-active button::before{opacity:1}
.menu-dot{
  flex:0 0 auto;width:.34em;height:.34em;border-radius:50%;background:currentColor;
  font-size:clamp(13px,min(2.15cqw,3.4cqh),26px);   /* em tracks the name, so it scales with it */
  opacity:0;transform:scale(.5);
  transition:opacity .24s var(--ramp),transform .24s var(--ramp);
}
.menu-item.is-active .menu-dot{opacity:1;transform:scale(1)}
.menu-name{font-size:clamp(13px,min(2.15cqw,3.4cqh),26px);line-height:1.2;letter-spacing:-.015em}

/* The marker beside the overview title: the section's other face (Work ⇄ Gallery).
   One small mono line, arrow and word at the same size, set up at the title's cap
   height like a footnote mark rather than hanging off its baseline. Hover draws a
   hairline under it, the way the index items do. */
.intro-head{display:flex;align-items:flex-start;gap:2.2cqw;max-width:100%}
.intro-swap{
  position:relative;display:inline-flex;align-items:baseline;gap:.55em;padding:0 0 .25em;margin:0;
  margin-top:calc(clamp(30px,9.4cqw,132px) * .13);   /* lands on the cap line of the title */
  background:none;border:0;color:var(--ink-3);cursor:pointer;
  font:500 clamp(9px,1.15cqw,14px)/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;
  transition:color .25s var(--ramp);
}
.intro-swap .arrow{font-family:var(--sans);font-weight:400;letter-spacing:0;font-size:1.5em;line-height:0;position:relative;top:.06em}   /* a stemless chevron, sized to the label's cap height */
.intro-swap::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:currentColor;
  transform:scaleX(0);transform-origin:left;transition:transform .4s var(--ramp);
}
.intro-swap:hover,.intro-swap:focus-visible{color:var(--ink);outline:none}
.intro-swap:hover::after,.intro-swap:focus-visible::after{transform:scaleX(1)}
.sky[data-mode="mobile"] .intro-head{gap:4.5cqw}
.sky[data-mode="mobile"] .intro-swap{font-size:clamp(10px,3cqw,12px);margin-top:calc(clamp(34px,14.5cqw,62px) * .14)}
.sky[data-mode="mobile"] .intro-swap::after{display:none}   /* no hover on a handset */

/* ---- the swap: Work and Gallery are two faces of one card ----
   The card holds its geometry and its contents change hands — the outgoing face slides
   off towards where you came from, the incoming one arrives from where you're going, so
   Work->Gallery and Gallery->Work read as reverses of each other.
   `translate` is deliberate: .section-card owns `transform` for its own positioning, and
   the transition sits on .swap-move so it outranks .section-card.anim, whose transform-
   only shorthand was cancelling this animation entirely. */
.section-card.swap-in .panel-track{translate:var(--swapDX,100%) 0}
.section-card.swap-move .panel-track{transition:translate var(--swapMs,620ms) var(--ramp),
                                                transform var(--snapMs) var(--snapEase)}
.section-card.swap-out.swap-move .panel-track{translate:calc(var(--swapDX,100%) * -1) 0}
.section-card.swap-out{pointer-events:none}
/* The arriving card's own surface is transparent for the duration, so the departing
   contents are visible sliding out underneath it rather than being covered. It turns
   opaque again the moment the travel ends, while the old card is still there — so the
   hand-off is between two identical white surfaces and nothing flashes. */
.section-card.swap-enter{background:transparent}
/* one meter and one close, not two stacked */
.section-card.swap-out .card-ui{visibility:hidden}

/* trailing blank page — the card retracts on an empty frame, not on the last study */
.pane-outro{align-items:center!important;justify-content:center;padding:7cqh 5cqw}
.outro-note{
  font-size:clamp(17px,2.9cqw,36px);letter-spacing:.005em;color:#98A0AA;
  font-style:italic;
}
/* Same treatment as the intro's scroll hint — a gentle bob and a bright pulse — but held
   back a couple of seconds, so it only starts nagging if you actually linger here. */
.pane-outro.on .outro-note{
  display:inline-block;
  animation:bob 2.4s var(--ease-io) infinite 2.2s,hint-flash 2.4s ease-in-out infinite 2.2s;
}
@media (prefers-reduced-motion:reduce){.pane-outro.on .outro-note{animation:none}}
.section-card[data-at="0"] .rail,.section-card[data-at="0"] .meter{opacity:1}
.section-card[data-edge="1"][data-at]:not([data-at="0"]) .rail{opacity:0;pointer-events:none}
.rail,.meter{transition:opacity .5s var(--ramp)}

/* card head + rail */
.card-head{position:absolute;z-index:6;left:0;right:0;top:0;height:0;pointer-events:none}
/* the close sits in the card's outer corner: the far side from where the card came in.
   Work enters from the left and Story from the bottom, so both sit top-right;
   Ventures enters from the right, so it sits top-left. */
.section-card[data-from="right"] .card-head{justify-content:flex-start}

/* the section label is parked — coming back, but not in this form */
.card-head .k{display:none;position:absolute;font:500 clamp(7px,.95cqw,10px)/1 var(--mono);letter-spacing:.26em;text-transform:uppercase;color:var(--ink-3)}
/* the close sits on the meter's centreline, out in the margin between the content
   box edge and the card edge, so the meter can span the content exactly */
.card-close{
  position:absolute;right:2.4cqw;top:calc(var(--meterTop) + 1px);transform:translate(50%,-50%);
  display:flex;align-items:center;justify-content:center;width:1.6em;height:1.6em;
  pointer-events:auto;font:400 clamp(13px,1.5cqw,19px)/1 var(--sans);color:var(--ink-3);
  transition:color .25s var(--ramp),transform .25s var(--ramp)}
.card-close:hover{color:var(--ink);transform:translate(50%,-50%) rotate(90deg)}
.section-card[data-from="right"] .card-close{right:auto;left:2.4cqw;transform:translate(-50%,-50%)}
.section-card[data-from="right"] .card-close:hover{transform:translate(-50%,-50%) rotate(90deg)}

/* the index rail is parked — the space stays reserved while it gets rethought */
.rail{position:absolute;z-index:5;display:flex;gap:0;pointer-events:none;visibility:hidden}
.section-card[data-axis="x"] .rail{left:5cqw;right:5cqw;bottom:3cqh;flex-direction:row;border-top:1px solid var(--hair)}
.section-card[data-axis="y"] .rail{right:2.2cqw;top:26cqh;bottom:10cqh;flex-direction:column;justify-content:center;gap:.9cqh;border-left:1px solid var(--hair);padding-left:1.4cqw}
.rail button{
  position:relative;flex:1;text-align:left;padding:1.4cqh .8cqw 0;
  font:500 clamp(7px,.92cqw,10px)/1.3 var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);transition:color .3s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis
}
.section-card[data-axis="y"] .rail button{flex:0 0 auto;padding:.5cqh 0 .5cqh 0}
.rail button::before{content:"";position:absolute;left:0;top:-1px;width:100%;height:1px;background:var(--ink);transform:scaleX(0);transform-origin:left;transition:transform .4s var(--ease)}
.section-card[data-axis="y"] .rail button::before{left:calc(-1.4cqw - 1px);top:0;width:1px;height:100%;transform:scaleY(0);transform-origin:top;transition:transform .4s var(--ease)}
.rail button.on{color:var(--ink)}
.rail button.on::before{transform:scale(1)}
.rail button:hover{color:var(--ink)}

/* progress meter */
.meter{position:absolute;z-index:5;background:rgba(18,21,26,.10);overflow:hidden}
/* the meter is inset from every edge, and stops short of whichever corner the close sits in */
.section-card[data-axis="x"] .meter{left:5cqw;right:5cqw;top:var(--meterTop);height:2px;border-radius:2px}

/* the vertical card's meter rides the close's own line — the right margin, on the
   close's centre — and starts just below it, the way the horizontal one shares the
   close's line and stops short of it */
.section-card[data-axis="y"] .meter{left:auto;right:2.4cqw;top:calc(var(--meterTop) + 3.4cqh);bottom:5cqh;width:2px;border-radius:2px;transform:translateX(50%)}
.meter i{display:block;background:var(--ink);transform-origin:left top}
/* the fill travels the way the content does: Work's strip is laid out leftward,
   so its progress runs right-to-left. Ventures and Story already run with theirs. */
.section-card[data-from="left"] .meter i{transform-origin:right top}
.section-card[data-axis="x"] .meter i{height:100%;width:100%;transform:scaleX(var(--pp,0))}
.section-card[data-axis="y"] .meter i{width:100%;height:100%;transform:scaleY(var(--pp,0))}

/* ---------- hint ---------- */
.hint{
  position:absolute;left:50%;top:calc(var(--heroBottom,86%) + 20px);translate:-50% 0;z-index:35;
  font:500 13px/1 var(--mono);letter-spacing:.26em;text-transform:lowercase;
  color:#9aa2ad;opacity:0;transition:opacity .7s var(--ramp);pointer-events:none;
}
/* grey by default, with a bright pulse every couple of seconds — and a gentle bob */
.sky[data-hint="1"] .hint{opacity:1;animation:bob 2.4s var(--ease-io) infinite,hint-flash 2.4s ease-in-out infinite}
@keyframes hint-flash{0%,52%,100%{color:#9aa2ad}64%,86%{color:#FFD400}}
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(5px)}}

/* ---------- curtain phase ---------- */
.sky[data-phase="curtain"] .vignette,.sky[data-phase="curtain"] .grain{opacity:0}

@media (prefers-reduced-motion:reduce){
  .hero,.section-card.anim,.scene,.panel-track.snap{transition-duration:.001s!important}
}

/* ---------- drag / snap states (set by the engine) ---------- */
.sky.dragging .hero{transition:opacity .5s var(--ramp),filter .5s var(--ramp)}
.card-window{position:absolute;overflow:hidden;z-index:3}
.card-ui{position:absolute;z-index:6;container-type:size;pointer-events:none}
.card-ui .rail,.card-ui .card-close{pointer-events:auto}


/* ============================================================
   MOBILE — same mechanic, restacked
   ============================================================ */

.sky[data-mode="mobile"] .hero-page{padding:5cqh 7cqw}
.sky[data-mode="mobile"] .index{flex-direction:column;align-items:flex-start;gap:1.6cqh;padding:0 7cqw}

/* index + hero lockup on mobile */
.sky[data-mode="mobile"] .chapter .name{font-size:clamp(38px,18cqw,76px);letter-spacing:-.04em}
.sky[data-mode="mobile"] .chapter .num{font-size:clamp(9px,2.8cqw,12px)}
/* no hover underline on a handset — a tap would leave it stuck on */
.sky[data-mode="mobile"] .chapter .name::after{display:none}
.sky[data-mode="mobile"] .lockup{width:var(--lock,78cqw)}
.sky[data-mode="mobile"] .bio{font-size:clamp(10px,2.9cqw,13px)}
.sky[data-mode="mobile"] .socials svg{width:clamp(18px,5.6cqw,24px)}
/* The portrait comes out from behind the mark and sits above it, right-aligned. The
   foot joins the flow too, so portrait + mark + bio centre in the card as one block —
   with the mark alone centred, the top sat empty and the bio crowded the bottom edge. */
/* As on desktop, the mark is the centred element and everything hangs off it: the
   portrait above it (right-aligned, out from behind it), the bio below at the mark's
   full width, and the socials at the foot of the card. The lockup is only as tall as
   the mark, so centring it centres the mark. */
.sky[data-mode="mobile"] .portrait{
  position:absolute;right:0;top:auto;bottom:100%;translate:none;width:64%;margin:0 0 2.5% 0;
  aspect-ratio:4/3}    /* wider and shorter than the desktop 4:5, its bottom edge held where it was */
/* the photo is 4:5, so a 4:3 box shows its middle three-fifths: cap through shoes, a
   sliver of the roof — not the sky above the head that `top` would keep */
.sky[data-mode="mobile"] .portrait.has-photo{background-position:center 42%}
.sky[data-mode="mobile"] .hero-foot{position:absolute;top:100%;left:0;right:0;margin-top:4%}
.sky[data-mode="mobile"] .bio{max-width:none;width:100%}
/* the socials sit 6cqh off the card's bottom edge. Their containing block is the foot,
   whose top is the card's centre + half the mark's height (78cqw / 4.858 / 2) + the 4%
   margin — hence the arithmetic. */
.sky[data-mode="mobile"] .socials{
  position:absolute;left:0;right:0;top:calc(44cqh - 11.15cqw);transform:translateY(-100%);
  margin:0;justify-content:center;gap:9cqw}
.sky[data-mode="mobile"] .pane-intro{flex-direction:column!important;align-items:stretch;justify-content:flex-start;gap:0;padding:0!important}
.sky[data-mode="mobile"] .intro-body{width:100%;flex-direction:column;align-items:flex-start;gap:2.4cqh;padding:9cqh 7cqw 0}
.sky[data-mode="mobile"] .intro-title{white-space:normal}
.sky[data-mode="mobile"] .intro-text{margin:0}
.sky[data-mode="mobile"] .intro-media{left:0;right:0;top:auto;bottom:0;width:auto;height:38%}
.sky[data-mode="mobile"] .intro-title{font-size:clamp(32px,13cqw,56px)}
.sky[data-mode="mobile"] .pane-intro.has-menu .intro-body{width:100%;padding:9cqh 6cqw 0}
.sky[data-mode="mobile"] .pane-intro.has-menu .intro-title{font-size:clamp(34px,14.5cqw,62px)}
.sky[data-mode="mobile"] .intro-mark{right:6cqw;top:auto;bottom:calc(7cqh + 1em);width:34%}
.sky[data-mode="mobile"] .intro-menu{left:6cqw;right:6cqw;bottom:4cqh}
/* No hover on a handset, so the menu is simply a list you tap: no numbers, no dot, no
   sliding highlight — the title itself darkens under a press. The logo tag keeps the
   first project's mark. */
.sky[data-mode="mobile"] .menu-name{font-size:clamp(14px,4.2cqw,18px)}
.sky[data-mode="mobile"] .menu-item button{gap:0;padding:1.1cqh 0;color:var(--ink-2)}
.sky[data-mode="mobile"] .menu-item button::before,
.sky[data-mode="mobile"] .menu-dot,
.sky[data-mode="mobile"] .menu-hl{display:none}
.sky[data-mode="mobile"] .menu-item button:active,
.sky[data-mode="mobile"] .menu-item.is-active button{color:var(--ink)}

.sky[data-mode="mobile"] .intro-text{font-size:clamp(11px,3.2cqw,14px);max-width:none}

/* panes stack on mobile regardless of travel axis */
.sky[data-mode="mobile"] .pane:not(.pane-intro){flex-direction:column!important;justify-content:flex-start;gap:2.4cqh;padding:9cqh 7cqw 9cqh!important}
.sky[data-mode="mobile"] .pane-media{flex:0 0 34%;width:100%;height:34%;aspect-ratio:auto}
.sky[data-mode="mobile"] .section-card[data-axis="y"] .pane:not(.pane-intro){justify-content:space-between}
.sky[data-mode="mobile"] .pane-title{font-size:clamp(19px,7cqw,30px)}
.sky[data-mode="mobile"] .pane-text{font-size:clamp(11px,3.4cqw,14px)}
.sky[data-mode="mobile"] .pane-index{font-size:clamp(7px,2.1cqw,9px)}
.sky[data-mode="mobile"] .pane-list li{font-size:clamp(9px,2.8cqw,12px);padding:1.2cqh 0}
.sky[data-mode="mobile"] .card-head{padding:3cqh 7cqw 0}
/* no pre-promoted layers for the content tracks on a handset: WebKit can leave one
   built off-screen unpainted after the card flies in (see openCard's repaint) */
.sky[data-mode="mobile"] .panel-track,
.sky[data-mode="mobile"] .gal-track{will-change:auto}
/* the clipped window composites on its own: WebKit's standard remedy for content that
   fails to paint inside a rounded, clipped, transformed container */
.sky[data-mode="mobile"] .section-card.anim .card-window{transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden}
.sky[data-mode="mobile"] .card-head .k{font-size:clamp(15px,4.2cqw,20px)}
/* A close sized for a thumb. Its centre sits on the content box's edge (6cqw in), so
   the meter stops short of it instead of running underneath. */
.sky[data-mode="mobile"] .section-card{--meterTop:3.2cqh}
.sky[data-mode="mobile"] .card-close{
  right:6cqw;width:1.5em;height:1.5em;font-size:clamp(22px,6.6cqw,28px)}
.sky[data-mode="mobile"] .section-card[data-from="right"] .card-close{right:auto;left:6cqw}
.sky[data-mode="mobile"] .section-card[data-axis="x"] .meter,
.sky[data-mode="mobile"] .section-card[data-axis="x"] .meter-nav{left:6cqw;right:calc(6cqw + 30px)}
.sky[data-mode="mobile"] .section-card[data-from="right"] .meter,
.sky[data-mode="mobile"] .section-card[data-from="right"] .meter-nav{left:calc(6cqw + 30px);right:6cqw}
/* on a handset the margins are too tight for a vertical bar, so the bottom card takes the
   same bar along the top as the others; its fill runs left to right as the pages go down */
.sky[data-mode="mobile"] .section-card[data-axis="y"] .meter{
  left:6cqw;right:calc(6cqw + 30px);top:var(--meterTop);bottom:auto;width:auto;height:2px;transform:none}
.sky[data-mode="mobile"] .section-card[data-axis="y"] .meter i{width:100%;height:100%;transform:scaleX(var(--pp,0));transform-origin:left top}
/* rail: numbers only, always along the bottom */
.sky[data-mode="mobile"] .section-card .rail{left:7cqw;right:7cqw;bottom:3cqh;top:auto;
  flex-direction:row;border-left:0;border-top:1px solid var(--hair);padding-left:0;gap:0}
.sky[data-mode="mobile"] .section-card .rail button{flex:1;padding:1.6cqh 0 0;font-size:clamp(7px,2.2cqw,10px)}
.sky[data-mode="mobile"] .section-card .rail button .rt{display:none}
.sky[data-mode="mobile"] .section-card .rail button::before{left:0;top:-1px;width:100%;height:1px;transform:scaleX(0);transform-origin:left}
.sky[data-mode="mobile"] .section-card .rail button.on::before{transform:scaleX(1)}

/* ============================================================
   WORK — project card, the rise, and the gallery
   ============================================================ */

/* ---- the card: signature media over a fixed title/spec strip ---- */
.pane-project{flex-direction:column!important;align-items:stretch;padding:0!important;gap:0}

/* Inside the content box: logo + paragraph on the left two-fifths, image on the
   right three. The box itself stays inset from the card edge on every side. */
.proj-media{
  position:relative;flex:1 1 auto;min-height:0;
  display:flex;gap:4%;padding:5cqh 5cqw 4cqh;
}
.proj-media[data-pad="inset"]{padding:8cqh 5cqw 6cqh}
.proj-aside{flex:0 0 34%;display:flex;flex-direction:column;min-width:0}
/* The mark box is 12.83cqw tall. A square mark fills a square; a wide wordmark (Heim)
   may run up to 2.4× that width so it isn't reduced to a sliver — the height is the
   constant, so every mark carries the same weight. */
.proj-logo{
  flex:0 0 auto;align-self:flex-start;height:12.83cqw;width:auto;min-width:12.83cqw;max-width:calc(12.83cqw * 2.4);
  display:flex;align-items:center;justify-content:flex-start;
  border-radius:2px;
  font:500 clamp(7px,.85cqw,10px)/1 var(--sans);letter-spacing:.18em;text-transform:uppercase;
  color:var(--ink-3);overflow:hidden;
}
.proj-logo img{display:block;height:100%;width:auto;max-width:100%;object-fit:contain;object-position:left center;
  filter:brightness(0)}
.proj-logo:not(:has(img)){width:12.83cqw;justify-content:center;border:1px solid var(--hair)}   /* the text fallback keeps its square frame */
.proj-blurb{
  margin:7cqh 0 0;max-width:21em;
  font-size:clamp(9px,1.12cqw,13px);line-height:1.45;color:var(--ink-2);
}
.proj-shot{
  flex:1 1 auto;position:relative;min-width:0;
  background-size:cover;background-position:var(--pos,center);overflow:hidden;
}
/* Risen, the shot is short and wide and crops differently from the tall full-size
   frame, so a project may want its own focal point for that state. */
.proj-stage .proj-shot{
  background-position:var(--posr,var(--pos,center));
}
.proj-open{position:absolute;inset:0;z-index:3;cursor:pointer;background:none;border:0}

/* An arriving page carries its image at full height with the specs held back; once it
   lands the image drops and the specs come in. Title, logo and paragraph stay put. */
.pane-project .proj-media{transition:margin-bottom .6s var(--ramp)}
.pane-project:not(.on):not([data-seen]) .proj-media{margin-bottom:calc(-1 * var(--stripH,0px))}
.pane-project .proj-meta{transition:opacity .4s var(--ramp) .2s,transform .45s var(--ramp) .2s}
.pane-project:not(.on):not([data-seen]) .proj-meta{opacity:0;transform:translateY(12px);transition-delay:0s}

/* the strip: title + paragraph, then specs, then mediums last */
.proj-strip{
  flex:0 0 auto;position:relative;z-index:4;background:transparent;
  padding:3.4cqh 5cqw 5.6cqh;display:flex;align-items:flex-end;justify-content:space-between;gap:5cqw;
}
.proj-title{margin:0;flex:0 1 auto;min-width:0;font-size:clamp(28px,6.4cqw,86px);line-height:.86;letter-spacing:-.035em;font-weight:500}
/* past eighteen characters a title steps down and may take a second line, rather than
   running off the strip — "Princeton University Museum of Art" is the case in point */
.proj-title[data-len="long"]{font-size:clamp(24px,4.6cqw,62px);line-height:.92}
.proj-meta{display:flex;flex-direction:column;align-items:flex-end;gap:1.8cqh;flex:0 0 auto}
.proj-specs{display:flex;align-items:baseline;gap:.9cqw;margin:0;padding:0;
  font-size:clamp(8px,1.02cqw,12px);color:var(--ink);font-family:var(--mono)}
.proj-specs i{font-style:normal;color:var(--ink-3)}
.proj-specs i.l{margin-right:.45em}     /* a label before its value: Founded 2022 */
/* an outbound link — the one thing on a card that leaves the site */
.proj-link{
  display:inline-flex;align-items:baseline;gap:.4em;color:var(--ink);text-decoration:none;
  font:500 clamp(8px,1.02cqw,12px)/1 var(--mono);letter-spacing:.06em;
  border-bottom:1px solid var(--ink-3);padding-bottom:.15em;transition:border-color .25s var(--ramp),color .25s var(--ramp)}
.proj-link:hover{border-bottom-color:var(--ink)}
.proj-link::after{content:"\2197";font-size:.9em}
.sky[data-mode="mobile"] .proj-link{font-size:clamp(10px,2.9cqw,12px)}

/* ---- medium tags: monochrome, segment rules drawn in behind you ---- */
.med-row{display:flex;gap:1cqw;align-items:flex-end;flex-wrap:wrap}
.proj-stage .med{cursor:pointer}     /* on the risen card a tag is a jump to its frames */
.med{display:flex;align-items:stretch}
/* borderless: a faint track, and the ink simply fills it in as you move through the
   gallery — no outline, no ring on the active one, the fill is the whole signal */
.med-box{
  position:relative;overflow:hidden;isolation:isolate;--f:0%;
  width:auto;min-width:clamp(58px,7.4cqw,100px);height:clamp(17px,2.5cqh,26px);
  border:0;border-radius:2px;background:rgba(18,21,26,.06);
  transition:background-color .3s var(--ramp);
}
.med[data-on="1"] .med-box{background:rgba(18,21,26,.10)}
.med-fill{position:absolute;z-index:1;left:0;top:0;bottom:0;width:var(--f,0%);background:var(--ink);
  transition:width .45s var(--ramp)}
.med-label{
  position:absolute;z-index:3;inset:0;display:flex;align-items:center;justify-content:center;
  padding:0 1cqw;white-space:nowrap;
  font:500 clamp(7px,.9cqw,10px)/1 var(--sans);letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-3);pointer-events:none;
}
/* a spacer copy in the flow gives the box its width, so long labels never clip */
.med-label.sizer{position:relative;visibility:hidden;z-index:0}
.med[data-on="1"] .med-label:not(.k){color:var(--ink)}
.med .med-label.k{z-index:4;color:#fff;clip-path:inset(0 calc(100% - var(--f,0%)) 0 0);
  transition:clip-path .45s var(--ramp)}

/* ---- the risen stage ---- */
.proj-stage{position:absolute;inset:0;z-index:45}
.proj-lift{position:absolute;inset:0;z-index:2}   /* card + bracket climb as one */
.proj-lift.anim,.proj-stage.closing .proj-lift{transition:transform var(--entryMs) var(--ramp)}
.proj-bracket{
  position:absolute;z-index:0;background:rgb(var(--paper));
  transform-origin:right center;transform:scaleX(1);will-change:transform;
}
.proj-lift .proj-card{z-index:1}   /* card paints over the bracket's overlap */
.proj-stage.closing .proj-bracket{transition:transform calc(var(--entryMs) * .3) var(--ramp)}

.proj-card{
  position:absolute;
  overflow:hidden;display:flex;flex-direction:column;
  container-type:size;transform:translateY(0);
}
.proj-card.anim{transition:transform var(--entryMs) var(--ramp),
                           left var(--entryMs) var(--ramp),
                           width var(--entryMs) var(--ramp)}
.proj-stage.closing .proj-card{transition:transform var(--entryMs) var(--ramp),
                               left var(--entryMs) var(--ramp),
                               width var(--entryMs) var(--ramp)}
.sky[data-risen="1"] .section-card{opacity:0;transition:none}

/* ---- the gallery: an infinite strip that snaps ---- */
.proj-gallery{position:absolute;inset:0;overflow:hidden;z-index:1;
  opacity:0;translate:0 var(--galIn,5%);
  transition:opacity var(--entryMs) var(--ramp),translate var(--entryMs) var(--ramp)}
.proj-gallery.in{opacity:1;translate:0 0}
/* leaving, it carries on the way the card went rather than dropping back — the frame is
   clipped, so the drift never opens a seam against the sky */
.proj-stage.out .proj-gallery{opacity:0;translate:0 var(--galOut,-3.5%);
  transition:opacity var(--outMs,.8s) var(--ramp),translate var(--outMs,.8s) var(--ramp)}
.gal-track{position:absolute;inset:0;will-change:transform;
  transition:transform var(--dragMs,110ms) linear}
.gal-track.snap{transition:transform var(--snapMs) var(--snapEase)}
.gal-sec{position:absolute;top:0;bottom:0;overflow:hidden}
.gal-shot{position:absolute;inset:0;background-size:cover;background-position:center;
  transition:inset .55s var(--ramp)}
/* on the way out, the frame you are on expands to the full page */
.gal-shot.fill{inset:0!important;background-size:cover!important}
/* anything that does not fill the frame sits on the card's own paper, never on sky */
.gal-sec{background:rgb(var(--paper))}
/* …and starts below --safeTop, so no partial image ever passes behind the risen card */
.gal-sec[data-layout="white"] .gal-shot{
  top:var(--safeTop,16%);bottom:12%;left:21%;right:21%;background-size:cover}
/* a tall (portrait) frame: shown whole on paper on desktop, in the white frame's box;
   on a phone it simply fills the screen, which is the shape it was made for */
.gal-sec[data-layout="tall"] .gal-shot{
  top:var(--safeTop,16%);bottom:12%;left:21%;right:21%;
  background-size:contain;background-repeat:no-repeat;background-position:center}
/* the whole panorama, contained in one screen and clear of the card */
.gal-sec[data-layout="pano"] .gal-shot{
  top:var(--safeTop,16%);bottom:9%;left:6%;right:6%;
  background-size:contain;background-repeat:no-repeat;background-position:center}
.gal-cap{
  position:absolute;left:0;bottom:0;z-index:2;display:flex;align-items:center;gap:8px;
  margin:22px 26px;font:500 10px/1 var(--mono);letter-spacing:.2em;text-transform:uppercase;color:#fff;
  mix-blend-mode:difference;
}
.gal-sec[data-layout="white"] .gal-cap{color:var(--ink-3);mix-blend-mode:normal}
.gal-cap i{width:7px;height:7px;border-radius:50%;background:var(--c);display:block}

/* ---- mobile: the project card restacked ------------------------------------
   Logo top-left, the Main Image full-width in 16:9 beneath it, the paragraph under
   that, then whatever room is left before the strip at the foot. The strip runs
   stats first (year / type / roles, medium tags) and the title beneath them — the
   same block the risen card keeps, so nothing re-flows when it lifts.
   Everything sits on one 6cqw content box, as the desktop does on 5cqw.          */
.sky[data-mode="mobile"] .pane.pane-project{padding:0!important;gap:0}
.sky[data-mode="mobile"] .proj-media,
.sky[data-mode="mobile"] .proj-media[data-pad="inset"]{
  flex-direction:column;gap:0;padding:7cqh 6cqw 0;
}
.sky[data-mode="mobile"] .proj-aside{display:contents}    /* logo and paragraph join the column */
.sky[data-mode="mobile"] .proj-logo{order:1;font-size:clamp(9px,2.6cqw,11px)}
.sky[data-mode="mobile"] .mark-word{font-size:clamp(12px,3.8cqw,17px)}   /* a text mark reads at thumb distance */
.sky[data-mode="mobile"] .proj-shot{order:2;flex:0 0 auto;width:100%;aspect-ratio:16/9;margin-top:2.6cqh;
  background-position:var(--posm,center)}   /* the phone's own focal point, else centre */
.sky[data-mode="mobile"] .proj-blurb{
  order:3;margin:2.6cqh 0 0;max-width:none;
  font-size:clamp(11px,3.3cqw,13px);line-height:1.5;
}
.sky[data-mode="mobile"] .proj-strip{
  flex-direction:column;align-items:flex-start;gap:2.6cqh;   /* stats sit clear of the title */
  padding:4cqh 6cqw 4.5cqh;
}
.sky[data-mode="mobile"] .section-card[data-axis="y"] .proj-strip{padding-bottom:9cqh}
.sky[data-mode="mobile"] .proj-meta{order:1;align-items:flex-start;width:100%;gap:1.6cqh}
.sky[data-mode="mobile"] .proj-title{order:2;font-size:clamp(28px,10.4cqw,42px);line-height:.92}
.sky[data-mode="mobile"] .proj-title[data-len="long"]{font-size:clamp(22px,7.4cqw,30px);line-height:.96}
.sky[data-mode="mobile"] .proj-specs{justify-content:flex-start;flex-wrap:wrap;gap:2.6cqw;font-size:clamp(10px,2.9cqw,12px)}
.sky[data-mode="mobile"] .med-row{gap:2.4cqw}
.sky[data-mode="mobile"] .med-box{width:auto;min-width:clamp(44px,16cqw,68px);height:clamp(16px,2.9cqh,20px)}
.sky[data-mode="mobile"] .med-label{font-size:clamp(7px,2.1cqw,9px);padding:0 2.2cqw}

/* ---- mobile: the rise --------------------------------------------------------
   The card climbs until its bottom edge sits --riseH down the frame (Risen card
   height in the Tune panel) — the top third. The stats slide up to the head of that
   third (--metaLift, set by the engine) and the title stays at its foot. The
   paragraph and logo fade out as it climbs, so nothing but the strip shows.      */
.sky[data-mode="mobile"] .proj-card .proj-blurb,
.sky[data-mode="mobile"] .proj-card .proj-logo{transition:opacity calc(var(--entryMs) * .45) var(--ramp)}
.sky[data-mode="mobile"] .proj-card.anim .proj-blurb,
.sky[data-mode="mobile"] .proj-card.anim .proj-logo{opacity:0}
.sky[data-mode="mobile"] .proj-stage.closing .proj-card .proj-blurb,
.sky[data-mode="mobile"] .proj-stage.closing .proj-card .proj-logo{opacity:1;transition-delay:calc(var(--entryMs) * .4)}
.sky[data-mode="mobile"] .proj-card .proj-meta{transition:transform var(--entryMs) var(--ramp)}
.sky[data-mode="mobile"] .proj-card.anim .proj-meta{transform:translateY(var(--metaLift,0px))}
.sky[data-mode="mobile"] .proj-stage.closing .proj-card .proj-meta{transform:translateY(0)}

/* ---- mobile: the gallery -----------------------------------------------------
   The same paper as the desktop: full frames fill the whole screen behind the risen
   card; panoramas and white frames sit on the paper in a 16:9 area centred below it
   (--band*, set by the engine), so nothing but a full frame passes behind the card.
   Leaving past the end expands the frame you are on to full screen, as on desktop. */
.sky[data-mode="mobile"][data-risen="1"] .hub{opacity:0}   /* never shows through a fading-in gallery */
.sky[data-mode="mobile"] .gal-sec .gal-shot{
  transition:top .55s var(--ramp),left .55s var(--ramp),width .55s var(--ramp),height .55s var(--ramp)}
.sky[data-mode="mobile"] .gal-sec[data-layout="pano"] .gal-shot,
.sky[data-mode="mobile"] .gal-sec[data-layout="white"] .gal-shot{
  inset:auto;top:var(--bandT,40%);left:var(--bandL,0px);width:var(--bandW,100%);height:var(--bandH,30%);
  background-repeat:no-repeat;background-position:center;
}
.sky[data-mode="mobile"] .gal-sec[data-layout="pano"] .gal-shot{background-size:contain}
/* a tall frame is the shape of the phone: it fills the screen like a full one */
.sky[data-mode="mobile"] .gal-sec[data-layout="tall"] .gal-shot{
  inset:0;width:auto;height:auto;border-radius:0;background-size:cover;background-position:center}
.sky[data-mode="mobile"] .gal-sec[data-layout="white"] .gal-shot{
  left:calc(var(--bandL,0px) + var(--bandW,100%) * .1);width:calc(var(--bandW,100%) * .8);
  background-size:contain}
.sky[data-mode="mobile"] .gal-shot.fill{
  top:0!important;left:0!important;width:100%!important;height:100%!important;background-size:cover!important}


/* ---- progress-bar navigation ------------------------------------------------
   Hovering a segment animates its name out underneath — no tick, no rule, just the
   word — and clicking jumps straight there. */
.meter-nav{position:absolute;z-index:7;height:15px;pointer-events:auto}
.section-card[data-axis="x"] .meter-nav{
  left:5cqw;right:5cqw;top:calc(var(--meterTop) + 1px);transform:translateY(-50%)}
.section-card[data-axis="y"] .meter-nav{display:none}
.mnav{position:absolute;top:0;height:100%;background:none;border:0;padding:0;cursor:pointer}
/* letter-spacing adds a trailing space after the last glyph, so centring the BOX
   leaves the visible word sitting half a letter-space to the left. The .05em nudge
   puts the glyphs themselves on the segment's centre line. */
.mnav-label{
  position:absolute;left:50%;top:calc(50% + 4px);white-space:nowrap;text-align:center;
  font:500 clamp(7px,.82cqw,11px)/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink);opacity:0;pointer-events:none;
  transform:translate(calc(-50% + .05em),-5px);
  transition:opacity .3s var(--ramp),transform .34s var(--ramp)}
.mnav:hover .mnav-label{opacity:1;transform:translate(calc(-50% + .05em),0)}

/* ---- gallery hint: only shown when someone is visibly stuck ---- */
.gal-hint{
  position:absolute;z-index:8;left:50%;bottom:7%;transform:translate(-50%,10px);
  padding:10px 18px;border-radius:999px;
  background:rgba(12,16,24,.72);backdrop-filter:blur(10px);
  font:500 11px/1 var(--mono);letter-spacing:.18em;text-transform:uppercase;color:#fff;
  opacity:0;pointer-events:none;
  transition:opacity .4s var(--ramp),transform .45s var(--ramp);
}
.gal-hint.on{opacity:1;transform:translate(-50%,0)}
