/* Pilates Realm — Kinetic Resistance & Perfect Alignment
   Motion layer. Every rule here is an ENHANCEMENT: if the stylesheet or GSAP fails to
   load, the page must still read and every link must still work. Nothing below hides
   content by default except the two menu surfaces, which are inert until JS wakes them
   (the .links fallback menu stays in charge until then).
   Palette is inherited: --cream --olive --orange --terra --ink --mute --line. */

:root{
  /* a slow controlled stretch, not a bounce — the resistance of a spring under load */
  --ease-stretch: cubic-bezier(.16,.84,.28,1);
  --ease-release: cubic-bezier(.22,1.2,.36,1);
  --dur-drawer: .62s;
}

/* ─────────────────────────────────────────────── 1. THE MENU MATRIX ── */
/* Trigger. Replaces the burger's job on JS-capable browsers; the burger stays in the
   markup as the no-JS fallback and is hidden only once JS confirms it can take over. */
.js-motion .pr-trigger{
  display:inline-flex; align-items:center; gap:.55rem; order:3; margin-left:.5rem;
  background:none; border:0; cursor:pointer; padding:8px 4px; color:var(--ink);
  font:inherit; font-size:.86rem; letter-spacing:.14em; text-transform:uppercase;
}
.pr-trigger{display:none}
.pr-trigger__bars{display:block; width:22px}
.pr-trigger__bars i{
  display:block; height:2px; background:currentColor; margin:4px 0; border-radius:2px;
  transition:transform .45s var(--ease-stretch), width .45s var(--ease-stretch);
}
.pr-trigger:hover .pr-trigger__bars i:nth-child(1){width:14px}
.pr-trigger:hover .pr-trigger__bars i:nth-child(3){width:18px}
.pr-trigger__label{display:none}
@media(min-width:861px){ .pr-trigger__label{display:inline} }

/* Scrim shared by both surfaces */
.pr-scrim{
  position:fixed; inset:0; z-index:58; background:rgba(42,36,24,.42);
  backdrop-filter:blur(3px); opacity:0; visibility:hidden;
  transition:opacity .5s var(--ease-stretch), visibility .5s;
}
.pr-scrim.is-open{opacity:1; visibility:visible}

/* — Desktop: the Kinetic Drawer, full height, from the right edge — */
.pr-drawer{
  position:fixed; top:0; right:0; z-index:60; height:100dvh;
  width:min(410px, 88vw); background:var(--cream);
  border-left:1px solid var(--line); box-shadow:-24px 0 60px rgba(42,36,24,.14);
  transform:translate3d(100%,0,0); visibility:hidden;
  transition:transform var(--dur-drawer) var(--ease-stretch), visibility var(--dur-drawer);
  display:flex; flex-direction:column; padding:26px 30px 30px; overflow-y:auto;
  will-change:transform;
}
.pr-drawer.is-open{transform:translate3d(0,0,0); visibility:visible}
.pr-drawer__top{display:flex; justify-content:space-between; align-items:center; margin-bottom:26px}
.pr-drawer__close{
  background:none;border:0;cursor:pointer;padding:8px;color:var(--ink);
  font-size:.8rem;letter-spacing:.16em;text-transform:uppercase;
}
.pr-drawer__nav{display:flex; flex-direction:column; gap:2px}
.pr-drawer__nav a{
  display:block; padding:13px 0; text-decoration:none; color:var(--ink);
  font-size:1.42rem; line-height:1.2; letter-spacing:0;
  border-bottom:1px solid var(--line);
  transition:letter-spacing .5s var(--ease-stretch), color .3s ease, padding-left .5s var(--ease-stretch);
}
.pr-drawer__nav a:hover,.pr-drawer__nav a:focus-visible{
  letter-spacing:.045em; padding-left:.35rem; color:var(--orange);
}
.pr-drawer__nav a[aria-current=page]{color:var(--orange)}
.pr-drawer__foot{margin-top:auto; padding-top:26px; font-size:.9rem; color:var(--mute)}
.pr-drawer__foot a{color:var(--orange)}

/* — Mobile: the Elastic Bottom Sheet, thumb-reachable — */
@media(max-width:860px){
  .pr-drawer{
    top:auto; right:0; left:0; bottom:0; width:auto; height:auto;
    max-height:82dvh; border-left:0; border-top:1px solid var(--line);
    border-radius:22px 22px 0 0; box-shadow:0 -18px 50px rgba(42,36,24,.2);
    padding:12px 22px calc(22px + env(safe-area-inset-bottom));
    transform:translate3d(0,100%,0);
  }
  .pr-drawer.is-open{transform:translate3d(0,0,0)}
  .pr-drawer__grab{
    width:44px;height:4px;border-radius:99px;background:var(--line);
    margin:2px auto 16px; flex:0 0 auto;
  }
  /* 48px minimum touch target — thumbs, not cursors */
  .pr-drawer__nav a{font-size:1.16rem; padding:15px 2px; min-height:48px}
  .pr-drawer__nav a:hover{letter-spacing:0; padding-left:2px}
}
@media(min-width:861px){ .pr-drawer__grab{display:none} }

body.pr-locked{overflow:hidden}

/* ──────────────────────────────────── 2. THE ALIGNMENT SCROLL TIMELINE ── */
.pr-align{position:relative; overflow:hidden}
.pr-align__grid{
  position:absolute; inset:0; pointer-events:none; z-index:0; opacity:0;
}
.pr-align__grid span{
  position:absolute; background:var(--terra); opacity:.38;
}
.pr-align__grid .v{top:0; bottom:0; width:1px; transform:scaleY(0); transform-origin:top}
.pr-align__grid .h{left:0; right:0; height:1px; transform:scaleX(0); transform-origin:left}
.pr-align > *:not(.pr-align__grid){position:relative; z-index:1}

/* ─────────────────────────────────────── 3. THE KINETIC TEXT MARQUEE ── */
.pr-marquee{
  overflow:hidden; border-block:1px solid var(--line);
  padding:.85rem 0; background:var(--cream);
  --pr-marquee-x:0px;
}
.pr-marquee__track{
  display:flex; width:max-content; gap:0;
  transform:translate3d(var(--pr-marquee-x),0,0);
  will-change:transform;
}
.pr-marquee__item{
  display:inline-flex; align-items:center; white-space:nowrap;
  font-size:clamp(.92rem,2.1vw,1.16rem); letter-spacing:.02em; color:var(--olive-2);
  padding:0 1.1rem;
}
.pr-marquee__item::after{
  content:""; width:5px; height:5px; border-radius:50%;
  background:var(--terra); margin-left:1.15rem; flex:0 0 auto;
}

/* ──────────────────────────────── 4. ELASTIC RESISTANCE MICRO-INTERACTIONS ── */
.pr-magnetic{
  position:relative; will-change:transform;
  transition:transform .45s var(--ease-release), box-shadow .35s ease;
}
.pr-magnetic > .pr-magnetic__t{
  display:inline-block; will-change:transform;
  transition:transform .45s var(--ease-release);
}
/* thumb press: drop, then spring back on release */
.pr-magnetic:active{transform:scale(.96); transition:transform .09s ease-out}
@media(hover:none){
  .pr-magnetic:active{transform:scale(.96)}
}

/* ───────────────────────────────────────────────── 5. REVEALS ── */
.js-motion .pr-rise{opacity:0; transform:translate3d(0,30px,0)}

/* ───────────────────────── 6. THE FLOOR: motion is never load-bearing ── */
@media(prefers-reduced-motion:reduce){
  .pr-drawer,.pr-scrim{transition:none}
  .pr-marquee__track{transform:none !important}
  .js-motion .pr-rise{opacity:1 !important; transform:none !important}
  .pr-align__grid{display:none}
  .pr-magnetic,.pr-magnetic > .pr-magnetic__t{transition:none}
  .pr-magnetic:active{transform:none}
}


/* ============================ LAYOUT ADDITIONS - 10 September 2026 ==
   Her own photographs arrived, so the homepage stops opening on type alone. */

.pr-hero{
  display:grid; grid-template-columns:1.05fr .95fr; align-items:stretch;
  gap:clamp(20px,4vw,54px); align-content:center;
}
.pr-hero__art{position:relative; border-radius:var(--r); overflow:hidden; background:#EFEADC}
.pr-hero__art picture,.pr-hero__art img{display:block; width:100%; height:100%}
.pr-hero__art img{
  object-fit:cover;
  object-position:50% 26%;
  min-height:min(74vh,620px);
}
@media(max-width:860px){
  .pr-hero{grid-template-columns:1fr; gap:22px}
  .pr-hero__art{order:-1}
  .pr-hero__art img{min-height:0; aspect-ratio:4 / 5}
}

.pr-cap{margin-top:10px; font-size:.82rem; letter-spacing:.02em; color:var(--mute)}

.pr-figure{border-radius:var(--r); overflow:hidden; background:#EFEADC; margin:0}
.pr-figure picture,.pr-figure img{display:block; width:100%; height:auto}

.pr-triptych{display:grid; grid-template-columns:repeat(3,1fr); gap:clamp(10px,1.6vw,18px)}
.pr-triptych figure{margin:0; border-radius:var(--r); overflow:hidden; background:#EFEADC}
.pr-triptych img{display:block; width:100%; height:100%; aspect-ratio:4 / 5}
.pr-triptych img{object-fit:cover}
@media(max-width:700px){
  .pr-triptych{grid-template-columns:1fr 1fr; gap:10px}
  .pr-triptych figure:nth-child(3){grid-column:1 / -1}
  .pr-triptych figure:nth-child(3) img{aspect-ratio:16 / 10}
}

.pr-bio{border-left:2px solid var(--terra); padding-left:clamp(16px,3vw,30px); max-width:60ch}
.pr-bio p{
  font-size:clamp(1.02rem,1.5vw,1.14rem); line-height:1.62; color:var(--olive-2);
  margin:0 0 14px;
}
.pr-bio p:last-child{margin-bottom:0}


/* ================== CROP CORRECTIONS - measured, not eyeballed ==================
   A DOM probe over every cover-fitted image found 130-166px being taken off the top of
   several portrait photographs, because the site's .split rule forces a 4:3 landscape box
   and the triptych asked for 4:5. On the mermaid frame that clipped the model's reaching
   HAND, which is the whole subject of the picture (6 Sep, Warriorz: a people photo in a
   fixed-ratio box needs an explicit focus, and the render pass must measure every slot). */

/* the alignment figure: a full-body vertical reach must not sit in a landscape box */
.pr-align__art img{aspect-ratio:4 / 5 !important; object-position:50% 30%}
@media(max-width:860px){ .pr-align__art img{aspect-ratio:4 / 5 !important} }

/* the triptych: taller boxes, and the crop takes from the floor rather than from
   heads and reaching hands */
.pr-triptych img{aspect-ratio:2 / 3; object-position:50% 32%}
@media(max-width:700px){
  .pr-triptych figure:nth-child(3) img{aspect-ratio:16 / 10; object-position:50% 50%}
}

/* The gallery assets are now cut AT the box ratio (assets/g, built by build_gallery.py),
   so cover has nothing left to discard and no object-position guess is needed. Keeping one
   would re-introduce exactly the cropping the 11 Sep corrections removed. */
.sty__f img{object-position:50% 50%}

/* the middle tile is the arm-arc frame; it needs a touch more headroom than its siblings */
.pr-triptych figure:nth-child(2) img{object-position:50% 24%}


/* ===================== THE STUDIO FILM - 10 September 2026 =====================
   Her own 4K footage of a session on the Cadillac. The source is 2160x3840 portrait,
   so this is shaped to the footage rather than the footage squeezed into a wide band.
   The poster is a frame cut from the same film, so a visitor who never gets the video
   (slow connection, Save-Data, reduced motion) still sees the real thing. */
.pr-film{position:relative; border-radius:var(--r); overflow:hidden; background:#EFEADC}
.pr-film__v{
  display:block; width:100%; height:auto; aspect-ratio:1080 / 1190;
  object-fit:cover; background:#EFEADC;
}
.pr-film .pr-cap{padding:0 2px}
@media(prefers-reduced-motion:reduce){
  /* the poster stays; the film never starts */
  .pr-film__v{pointer-events:none}
}


/* ============== SOCIAL MARKS - 11 September 2026 ==============
   Director: show the logos rather than spelling the handles out, in her palette. Cream on
   the olive footer, terra on hover — the same two colours the footer already uses, so the
   marks read as part of the brand rather than as borrowed platform badges. The label stays
   in the DOM for screen readers and for anything that cannot draw the SVG. */
.pr-social{display:flex; gap:14px; align-items:center; margin-top:14px}
.pr-social a{
  display:inline-flex; align-items:center; justify-content:center;
  width:42px; height:42px; border-radius:50%;
  color:#EDE7DA; border:1px solid rgba(237,231,218,.28);
  text-decoration:none;
  transition:color .35s var(--ease-stretch), border-color .35s var(--ease-stretch),
             transform .45s var(--ease-release), background-color .35s ease;
}
.pr-social a:hover,.pr-social a:focus-visible{
  color:var(--olive); background:var(--terra); border-color:var(--terra);
  transform:translateY(-2px);
}
.pr-social svg{width:19px; height:19px; fill:currentColor; display:block}
.pr-social .vh{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
@media(prefers-reduced-motion:reduce){ .pr-social a{transition:none} .pr-social a:hover{transform:none} }


/* ===================== EDITORIAL TRACKS - 11 September 2026 =====================
   Track 1 runs down the "Someone is actually watching" section: her own modelling frames,
   used for the LINE in them rather than as decoration. Track 2 frames prices and philosophy.
   Every effect degrades to a plain fade on a phone or under reduced motion, because none of
   this is allowed to cost a visitor the content. */

/* ---- Track 1: the parallax alignment masks ---- */
.pr-track1{display:grid; gap:clamp(18px,3vw,34px); margin-top:clamp(26px,4vw,46px)}
@media(min-width:861px){ .pr-track1{grid-template-columns:1fr 1fr; align-items:start} }
.pr-mask{
  position:relative; overflow:hidden; border-radius:var(--r);
  background:#EFEADC; will-change:transform;
}
.pr-mask img{display:block; width:100%; height:100%; object-fit:cover}
.pr-mask--axis{aspect-ratio:4 / 5}
.pr-mask--ext{aspect-ratio:62 / 100}
@media(min-width:861px){
  .pr-mask--ext{margin-top:clamp(30px,6vw,90px)}   /* the offset is what makes parallax read */
}
/* the circle, floating over the copy grid */
.pr-orb{
  width:clamp(150px,22vw,230px); aspect-ratio:1; border-radius:50%;
  overflow:hidden; background:#EFEADC; margin:clamp(20px,3vw,30px) 0 0;
  box-shadow:0 14px 40px rgba(42,36,24,.16);
}
.pr-orb img{display:block; width:100%; height:100%; object-fit:cover}
@media(min-width:861px){
  .pr-orb{position:absolute; right:2%; bottom:-6%; z-index:2; margin:0}
}

/* ---- Track 2: the resistance reveal ---- */
.pr-reveal{
  position:relative; overflow:hidden; border-radius:var(--r);
  aspect-ratio:4 / 5; background:#EFEADC; cursor:ew-resize; --pr-reveal:50%;
  /* 900px source, so 450 CSS is 1:1 on a retina screen. Without this the block filled its
     column at 1096 CSS px and was being upscaled — the same softness as the films. */
  width:min(450px,100%); margin-inline:auto;
}
.pr-reveal picture{display:contents}
.pr-reveal img{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
/* The wrapper MUST be positioned and full-size. As a static div whose only child is absolutely
   positioned it collapsed to 1096x0, and clip-path against a zero-height box hid the top image
   completely — so both sides of the drag showed the base image. Measured on the live site. */
.pr-reveal__top{
  position:absolute; inset:0; z-index:1;
  clip-path:inset(0 0 0 var(--pr-reveal));
}
.pr-reveal__line{
  position:absolute; top:0; bottom:0; left:var(--pr-reveal); width:1px;
  background:rgba(245,247,236,.85); pointer-events:none;
  box-shadow:0 0 0 1px rgba(42,36,24,.12);
}
.pr-reveal__line::after{
  content:""; position:absolute; top:50%; left:50%; width:34px; height:34px;
  transform:translate(-50%,-50%); border-radius:50%;
  border:1px solid rgba(245,247,236,.9); background:rgba(42,36,24,.22);
  backdrop-filter:blur(2px);
}
/* phone: a swipe carousel instead of a cursor gesture a thumb cannot make */
.pr-swipe{
  display:grid; grid-auto-flow:column; grid-auto-columns:82%; gap:12px;
  overflow-x:auto; scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch;
  padding-bottom:6px; scrollbar-width:none;
}
.pr-swipe::-webkit-scrollbar{display:none}
.pr-swipe > figure{margin:0; scroll-snap-align:center; border-radius:var(--r); overflow:hidden}
.pr-swipe img{display:block; width:100%; height:100%; aspect-ratio:4 / 5; object-fit:cover}

/* ---- Track 2: the texture card ---- */
.pr-card{position:relative; border-radius:var(--r); overflow:hidden; isolation:isolate}
.pr-card__bg{position:absolute; inset:0; z-index:0}
.pr-card__bg img{width:100%; height:100%; object-fit:cover; display:block}
.pr-card__veil{
  position:absolute; inset:0; z-index:1; background:rgba(245,247,236,.80);
  backdrop-filter:blur(0px); transition:backdrop-filter .6s var(--ease-stretch),
                                        background-color .6s var(--ease-stretch);
}
.pr-card:hover .pr-card__veil,.pr-card:focus-within .pr-card__veil{
  backdrop-filter:blur(8px); background:rgba(245,247,236,.72);
}
.pr-card__body{position:relative; z-index:2; padding:clamp(24px,4vw,44px)}

@media(prefers-reduced-motion:reduce){
  .pr-card__veil{transition:none}
  .pr-mask{transform:none !important}
}


/* ============ THE SESSION FILMS - 11 September 2026 (v2, sharpness pass) ============
   The first version put film 1 across the full viewport. Measured on a retina screen that is
   560 CSS px = 1120 device pixels drawn from a 720px source: a 1.56x upscale of an already
   heavily compressed file, which is exactly the softness the Director saw. The fix is not a
   bigger file, it is a smaller window.

   EVERY SIZE HERE IS HALF ITS ENCODED WIDTH, so a DPR-2 screen gets 1:1 pixels and a DPR-1
   screen gets a downscale. If you change a max-width, change the encoder in
   build_session_films_v2.py to match — the two are one contract.
       canopy 480 CSS <- 960 encoded   macro 380 <- 760   chrono 450 <- 900              */

/* ---- 1. the arched window. Never full bleed. ---- */
.pr-canopy{
  position:relative; background:var(--olive); overflow:hidden;
  padding:clamp(40px,7vw,90px) 0 clamp(46px,8vw,100px);
}
.pr-canopy::before{            /* the luxury-neutral ground the brief asks for */
  content:""; position:absolute; inset:auto 0 0 0; height:38%;
  background:linear-gradient(180deg,rgba(245,247,236,0),rgba(245,247,236,.06));
  pointer-events:none;
}
.pr-canopy__frame{
  position:relative; margin:0 auto; overflow:hidden;
  width:min(480px, 86vw);                    /* HARD CEILING - 960px source, DPR2 = 1:1 */
  aspect-ratio:3 / 4;
  border-radius:min(240px,43vw) min(240px,43vw) 18px 18px;   /* the arch */
  box-shadow:0 30px 80px rgba(20,17,12,.45);
  will-change:transform;
}
.pr-canopy video,.pr-canopy img.pr-canopy__poster{
  width:100%; height:100%; object-fit:cover; display:block;
}
.pr-canopy__cap{
  position:relative; z-index:2; text-align:center; color:#EDE7DA;
  margin:clamp(20px,3vw,30px) auto 0; max-width:44ch; padding:0 18px; background:none;
}
.pr-canopy__cap p{margin:8px 0 0; font-size:.95rem; opacity:.82}
@media(max-width:860px){ .pr-canopy__frame{width:min(420px,86vw)} }

/* ---- 2. the blur-gradient reveal ---- */
.pr-peek{
  position:relative; border-radius:var(--r); overflow:hidden;
  background:#EFEADC; width:min(380px,100%); aspect-ratio:1 / 1;  /* 760px source */
  margin:0 auto clamp(18px,3vw,30px);
  border:1px solid var(--line);
}
.pr-peek video,.pr-peek img{width:100%; height:100%; object-fit:cover; display:block}
.pr-peek__media{filter:blur(12px) contrast(.9); will-change:filter}
.pr-peek__hint{
  position:absolute; inset:auto 0 0 0; padding:12px 14px; z-index:2;
  color:#EDE7DA; font-size:.76rem; letter-spacing:.08em; text-transform:uppercase;
  background:linear-gradient(180deg,rgba(30,27,24,0),rgba(30,27,24,.62));
  transition:opacity .6s ease;
}
.pr-peek.is-near .pr-peek__hint{opacity:0}
@media(hover:none){                        /* touch: crisp, bordered, never blurred */
  .pr-peek__media{filter:none}
  .pr-peek__hint{display:none}
}

/* ---- 3. the chrono matrix: locked square ---- */
.pr-scrub{position:relative; background:var(--cream)}
.pr-scrub__stage{position:sticky; top:0; height:100vh; display:grid; place-items:center; overflow:hidden}
.pr-scrub__stage video,.pr-scrub__stage img{
  width:min(450px,88vw); aspect-ratio:1 / 1;      /* 900px source, DPR2 = 1:1 */
  object-fit:cover; border-radius:var(--r); display:block;
  box-shadow:0 20px 50px rgba(42,36,24,.14);
}
.pr-scrub__rail{height:220vh}
.pr-scrub__note{
  position:absolute; left:50%; bottom:5vh; transform:translateX(-50%); z-index:2;
  font-size:.78rem; letter-spacing:.14em; text-transform:uppercase; color:var(--mute);
}
@media(max-width:860px){
  .pr-scrub__rail{height:auto}
  .pr-scrub__stage{position:static; height:auto; padding:clamp(20px,5vw,40px) 0}
}

@media(prefers-reduced-motion:reduce){
  .pr-peek__media{filter:none !important}
  .pr-canopy__frame{transform:none !important}
}


/* ================== FILM GRAIN, AS A LAYER - 11 September 2026 ==================
   The brief wants heavy Gaussian grain so compression artefacts read as intentional film
   texture. Baked into the video with ffmpeg's noise filter it does the opposite: random
   per-frame noise is incompressible, inter-frame prediction collapses, and the 21s 960px
   intermediate measured 542MB with the WebM past 227MB before I killed it.

   The same look, layered over the footage instead of encoded into it, costs one inline SVG
   and nothing per frame. It also keeps working if the video never loads, because it sits over
   the poster too. This is how it is actually done. */
.pr-grain{position:relative}
.pr-grain::after{
  content:""; position:absolute; inset:0; z-index:3; pointer-events:none;
  opacity:.24; mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'>\
<filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.86' numOctaves='3' stitchTiles='stitch'/>\
<feColorMatrix type='saturate' values='0'/></filter>\
<rect width='180' height='180' filter='url(%23n)' opacity='0.55'/></svg>");
  background-size:180px 180px;
  animation:pr-grain-shift 640ms steps(3) infinite;
}
@keyframes pr-grain-shift{
  0%{transform:translate3d(0,0,0)}
  33%{transform:translate3d(-3%,2%,0)}
  66%{transform:translate3d(2%,-3%,0)}
  100%{transform:translate3d(0,0,0)}
}
/* a still grain is still grain — only the flicker is motion */
@media(prefers-reduced-motion:reduce){ .pr-grain::after{animation:none} }
