/* ══════════════════════════════════════════════════════════
   Tyce — hair & barber
   Art direction: her own line, "there is no such thing as bad
   hair", set as a manifesto. Violet pulled from her existing
   logo mark and pushed to saturation so the brand reads
   defiant rather than sweet. Ember red is reserved almost
   entirely for the strike-throughs on the "too much" wall.
   ══════════════════════════════════════════════════════════ */

/* ---------- tokens ---------- */
:root{
  --ink:#17121C;
  --ink-2:#1F1826;
  --ink-3:#2C2136;
  --bone:#F6F1F4;
  --bone-2:#EAE1E7;
  --orchid:#FF3DBE;
  --orchid-dim:#D633A9;
  --plum:#33153F;
  --plum-2:#4A1E5C;
  --ember:#FF5E5B;
  --clay:#E8C8B0;

  --display:'Bricolage Grotesque',system-ui,sans-serif;
  --serif:'Instrument Serif',Georgia,serif;
  --body:'Work Sans',system-ui,sans-serif;

  --pad:clamp(1.25rem,5vw,5.5rem);
  --maxw:1440px;
  --ease:cubic-bezier(.22,.61,.36,1);
}

/* ---------- reset ---------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--ink);
  color:var(--bone);
  font-family:var(--body);
  font-size:clamp(1rem,.55vw + .9rem,1.15rem);
  line-height:1.6;
  font-weight:350;
  overflow-x:hidden;
}
img{display:block;max-width:100%;height:auto}
h1,h2,h3{margin:0;font-weight:inherit}
p{margin:0}
ul{margin:0;padding:0;list-style:none}
a{color:inherit}
button{font:inherit;color:inherit;border:0;background:none;cursor:pointer}

/* focus — visible everywhere, never removed */
:focus-visible{
  outline:2.5px solid var(--orchid);
  outline-offset:3px;
  border-radius:2px;
}

/* screen-reader-only: readable by AT, invisible on screen. Not display:none —
   that would hide it from assistive tech too. */
.sronly{
  position:absolute;width:1px;height:1px;
  margin:-1px;padding:0;border:0;
  clip-path:inset(50%);overflow:hidden;white-space:nowrap;
}

.skip{
  position:absolute;left:-9999px;top:0;z-index:200;
  background:var(--orchid);color:var(--ink);
  padding:.8rem 1.2rem;font-weight:600;
}
.skip:focus{left:.5rem;top:.5rem}

/* ══════════ reveal-on-scroll
   Deliberate: content is VISIBLE by default. JS only opts in
   to the pre-animation state, and only when it can guarantee
   it will also take it back out. The old site hid every block
   at opacity:0 in CSS and never fired the reveal — that is
   the bug this structure exists to make impossible. ══════════ */
.js-anim .reveal{opacity:0;transform:translateY(22px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
.js-anim .reveal.in{opacity:1;transform:none}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
  .js-anim .reveal{opacity:1;transform:none}
  /* park the looping gradient on a good frame rather than a random one */
  .thesis{background-position:38% 50%!important}
  .hero__aurora span{animation:none!important}
}


/* ══════════ drifting section meshes ══════════
   The mesh lives on an oversized ::before and moves on transform only, so it
   stays on the compositor. isolation:isolate keeps z-index:-1 above the
   section's own base color but under every child. */
@keyframes meshdrift{
  from{transform:translate3d(-2.2%,-1.8%,0) scale(1)}
  to{transform:translate3d(2.4%,2.2%,0) scale(1.09)}
}
.wall,.work,.vows,.chairs,.school,.menuband{position:relative;isolation:isolate;overflow:hidden}
.wall::before,.work::before,.vows::before,.chairs::before,.school::before,.menuband::before{
  content:"";position:absolute;inset:-26%;z-index:-1;pointer-events:none;
  animation:meshdrift 44s ease-in-out infinite alternate;
  will-change:transform;
}
.wall::before{
  animation-duration:38s;
  background:
    radial-gradient(52rem 30rem at 18% 8%,rgba(255,61,190,.17),transparent 62%),
    radial-gradient(44rem 26rem at 88% 46%,rgba(232,200,176,.26),transparent 58%),
    radial-gradient(30rem 20rem at 55% 96%,rgba(255,94,91,.08),transparent 60%);
}
.work::before{
  animation-duration:48s;animation-delay:-12s;
  background:
    radial-gradient(60rem 34rem at 6% 22%,rgba(74,30,92,.5),transparent 62%),
    radial-gradient(46rem 30rem at 94% 82%,rgba(255,61,190,.13),transparent 60%);
}
.vows::before{
  animation-duration:42s;animation-delay:-24s;
  background:radial-gradient(42rem 26rem at 82% 12%,rgba(255,61,190,.22),transparent 60%);
}
.chairs::before{
  animation-duration:52s;animation-delay:-8s;
  background:
    radial-gradient(52rem 30rem at 50% 4%,rgba(74,30,92,.5),transparent 62%),
    radial-gradient(36rem 24rem at 10% 92%,rgba(255,61,190,.11),transparent 60%);
}
.school::before{
  animation-duration:40s;animation-delay:-30s;
  background:
    radial-gradient(46rem 28rem at 88% 10%,rgba(232,200,176,.32),transparent 58%),
    radial-gradient(50rem 30rem at 10% 92%,rgba(255,61,190,.13),transparent 60%);
}
.menuband::before{animation-duration:46s;animation-delay:-18s}

/* ---------- shared bits ---------- */
.sec-head{max-width:var(--maxw);margin:0 auto 3.5rem}
.sec-head__h{
  font-family:var(--display);
  font-size:clamp(2.4rem,6.5vw,5.5rem);
  font-weight:700;
  line-height:.92;
  letter-spacing:-.035em;
}
.sec-head__note{
  margin-top:1.4rem;max-width:46ch;
  font-size:1.02rem;line-height:1.65;opacity:.68;
}
.sec-head__note strong{color:var(--orchid);font-weight:600}

.btn{
  position:relative;overflow:hidden;isolation:isolate;
  display:inline-flex;align-items:center;gap:.5rem;
  padding:1rem 1.9rem;
  font-family:var(--body);font-size:.9rem;font-weight:600;
  letter-spacing:.09em;text-transform:uppercase;
  text-decoration:none;border-radius:999px;
  /* A pill must never wrap. Squeezed into the header on a 360px phone, "Book a
     chair" broke onto THREE lines and grew the bar to 74px tall. */
  white-space:nowrap;
  transition:transform .35s var(--ease),box-shadow .35s var(--ease),
             border-color .35s var(--ease),color .35s var(--ease);
}
/* a light sweeps across on hover — one gesture, not a light show */
.btn::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(105deg,transparent 30%,rgba(255,255,255,.55) 50%,transparent 70%);
  transform:translateX(-130%);
  transition:transform .75s var(--ease);
}
.btn:hover::after{transform:translateX(130%)}

.btn--solid{
  color:#160F1E;
  background:linear-gradient(135deg,#FFA1E1 0%,#FF3DBE 46%,#9A2BF0 100%);
  box-shadow:0 6px 24px -6px rgba(255,61,190,.6),
             inset 0 1px 0 rgba(255,255,255,.45);
}
.btn--solid:hover{
  transform:translateY(-2px);
  box-shadow:0 12px 34px -8px rgba(255,61,190,.8),
             inset 0 1px 0 rgba(255,255,255,.55);
}
.btn--ghost{
  border:1px solid rgba(246,241,244,.22);
  color:var(--bone);
  background:linear-gradient(180deg,rgba(246,241,244,.08),rgba(246,241,244,.02));
  backdrop-filter:blur(10px);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.08);
}
.btn--ghost:hover{
  border-color:rgba(255,61,190,.6);color:#fff;transform:translateY(-2px);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.14),0 8px 26px -10px rgba(255,61,190,.5);
}
.btn--ghost::after{background:linear-gradient(105deg,transparent 30%,rgba(255,61,190,.3) 50%,transparent 70%)}
.btn--sm{padding:.6rem 1.15rem;font-size:.72rem}
.btn--wide{width:100%;justify-content:center}

/* ══════════════════ top bar ══════════════════ */
/* floating glass pill, detached from the top edge — the photo runs under it */
.bar{
  position:fixed;top:clamp(.6rem,1.6vh,1.1rem);left:50%;transform:translateX(-50%);
  z-index:100;
  display:flex;align-items:center;gap:clamp(.9rem,2.2vw,2rem);
  width:min(1440px,calc(100% - 1.5rem));
  padding:.55rem .6rem .55rem 1.4rem;
  border-radius:999px;
  border:1px solid rgba(246,241,244,.15);
  background:rgba(23,18,28,.55);
  backdrop-filter:blur(18px) saturate(1.4);
  box-shadow:0 12px 40px -14px rgba(0,0,0,.65),inset 0 1px 0 rgba(255,255,255,.09);
}
/* Over the white hero the dark pill reads as a gray slab, so the bar inverts
   while it sits on the hero and returns to glass once the curtain covers it.
   JS adds the class; without JS the dark bar stays — legible either way. */
.bar--light{
  background:rgba(255,255,255,.72);
  border-color:rgba(23,18,28,.1);
  box-shadow:0 14px 40px -20px rgba(10,7,14,.3),inset 0 1px 0 rgba(255,255,255,.9);
}
.bar--light .bar__name,.bar--light .bar__nav a{color:var(--ink)}
.bar--light .bar__sub{color:var(--ink);opacity:.55}
.bar--light .bar__now{
  color:rgba(23,18,28,.68);
  border-color:rgba(23,18,28,.13);
  background:linear-gradient(180deg,rgba(23,18,28,.05),rgba(23,18,28,.02));
}
.bar--light .navtoggle span{background:var(--ink)}

/* fixed header: keep anchor jumps from landing underneath it */
section[id]{scroll-margin-top:5.5rem}
.bar__mark{display:flex;align-items:baseline;gap:.55rem;text-decoration:none;flex-shrink:0}
.bar__name{
  font-family:var(--display);font-size:1.28rem;font-weight:800;
  letter-spacing:-.04em;line-height:1;
}
.bar__sub{
  font-size:.6rem;letter-spacing:.22em;text-transform:uppercase;
  opacity:.5;
}
.bar__nav{display:flex;gap:clamp(.65rem,1vw,1.1rem);margin-left:.5rem}
.bar__nav a{
  font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;
  text-decoration:none;opacity:.62;transition:opacity .25s,color .25s;
}
.bar__nav a:hover{opacity:1;color:var(--orchid)}
.bar__now{
  margin-left:auto;display:flex;align-items:center;gap:.5rem;
  padding:.4rem .85rem;border-radius:999px;
  font-size:.7rem;letter-spacing:.05em;
  color:rgba(246,241,244,.7);
  border:1px solid rgba(246,241,244,.13);
  background:linear-gradient(180deg,rgba(246,241,244,.07),rgba(246,241,244,.02));
  white-space:nowrap;
}
.bar__now::before{
  content:"";width:6px;height:6px;border-radius:50%;
  background:var(--orchid);flex-shrink:0;
  box-shadow:0 0 0 0 rgba(255,61,190,.6);
  animation:pip 2.6s ease-out infinite;
}
@keyframes pip{
  0%{box-shadow:0 0 0 0 rgba(255,61,190,.55)}
  70%{box-shadow:0 0 0 9px rgba(255,61,190,0)}
  100%{box-shadow:0 0 0 0 rgba(255,61,190,0)}
}

/* The CTA is the conversion — it must never be the thing that gets squeezed.
   Without this the 9-item nav compressed it from 131px to 97px and clipped the
   label. The nav absorbs the pressure instead (and below 1360px it is replaced
   by the burger entirely). */
.bar .btn--sm{flex-shrink:0}
/* The status pill and the tagline are the two things in the bar that can go
   without losing function, so they yield first as the width tightens. */
/* The "Usually at Mill Valley right now" pill is retired from the bar. With
   "Book an appointment" (196px vs the old 131px) plus 9 nav items it no longer
   fits at ANY width — .bar is width-capped at 1440px, so a bigger screen gives
   it nothing, and above 1580px it forced the nav to wrap. It was already hidden
   below 1580px, so almost nobody ever saw it. Keeping the markup so it can be
   restored elsewhere. */
.bar__now{display:none}
@media (max-width:1240px){ .bar__sub{display:none} }
@media (max-width:1220px){
  .bar__nav,.bar__now{display:none}
  .bar{justify-content:space-between}
  .bar .btn--sm{margin-left:auto}
}
/* Phones: the "hair & barber" tagline costs ~90px the bar cannot spare once the
   wordmark, the CTA and the burger all have to share 336px. Dropping it is what
   lets the nowrap button above actually fit instead of overflowing the pill —
   the name alone still reads as the mark, and the tagline is in the hero anyway. */
@media (max-width:560px){
  .bar__sub{display:none}
  .bar{padding-left:1rem}
  .bar .btn--sm{padding:.55rem .95rem;font-size:.68rem;letter-spacing:.07em}
}

/* ══════════════════ hero : the manifesto ══════════════════ */
/* Sky-Estate structure: the photo fills the whole hero, the type sits on it.
   Layers: bg photo (0) → scrim (1) → aurora (2) → grain (3) → body/tag (4). */
/* The hero is PINNED: it stays parked at the top while .curtain (everything
   after it) scrolls up and over it like a sheet. Sticky, not fixed, so it is
   still released once <main> ends and never leaks behind the footer. */
.hero{
  position:sticky;top:0;z-index:0;
  /* EXACTLY one viewport. A pinned hero taller than the screen can never
     scroll — its overflow would be permanently unreachable, which silently
     ate the CTAs the first time this was built. */
  height:100svh;min-height:32rem;
  /* body sits on the FLOOR: centred, the lone CTA floated in empty white
     with nothing to anchor it and read as unplaced. */
  display:flex;align-items:flex-end;
  overflow:hidden;
  /* Must match the LOOP's DECODED background or a faint rectangle shows around
     the video. Verify after ANY re-encode by drawing a video frame to a canvas
     and reading a corner pixel — the value moves with the encode settings (an
     Historical: while the hero held an OPAQUE video composited with a blend
     mode, this had to match the loop's DECODED white exactly — a single level of
     difference drew a visible rectangle around her, because the video's white
     always loses to a brighter page under darken. That constraint is GONE: the
     hero is now a still with a real alpha channel, so there is no white box to
     match and this is simply the page's white. If an opaque video ever comes
     back, re-measure the decoded corners via canvas and match them here. */
  background:rgb(255,255,255);
}
.curtain{
  position:relative;z-index:1;
  background:var(--ink);   /* opaque floor: nothing may show the hero through a gap */
  /* no leading-edge shadow: it was built to lift the curtain off a DARK hero.
     Over the white one it smears a dirty gray band across her jacket. */
}
/* The curtain effect REQUIRES the hero to fit one viewport exactly. Where the
   copy can't be trusted to fit — narrow phones, very short windows — unpin it
   and let it scroll normally. A pinned hero taller than the screen would hide
   its own bottom (and with it the CTAs) with no way to scroll to them. */
@media (max-width:940px),(max-height:640px){
  .hero{position:static;height:auto;min-height:100svh}
}
/* She is a cutout, not a backdrop: anchored to the bottom-right so she always
   STANDS on the hero's floor at any viewport. object-fit:cover was cropping
   her mid-torso whenever the hero's ratio drifted from the photo's. */
.hero__bg{
  position:absolute;inset:0;z-index:2;margin:0;
  pointer-events:none;
  /* NO blend mode — the media carries a real alpha channel now.
     Why this had to change: darken keeps whichever layer is DARKER, so a
     vibrant wordmark (pink is luma ~134) beat every blonde highlight (~230) and
     the letters showed straight THROUGH her hair. Making the text pale enough
     to always lose means making it nearly white. Vibrant text and opaque hair
     are mutually exclusive under ANY separable blend mode — it needs alpha.
     Do not reintroduce mix-blend-mode here while the assets have alpha. */
}
/* the loop sits exactly on top of the still and cross-fades in once playing */
.hero__vid{
  opacity:0;transition:opacity .9s var(--ease);
  pointer-events:none;
}
.hero__vid.is-live{opacity:1}
@media (prefers-reduced-motion:reduce){.hero__vid{display:none}}

/* ══════════ scattered work polaroids ══════════
   Sits ABOVE the wipe divider (z 6 vs 5) so the split line passes behind them
   and they read as being in front — a full-height line slicing through every
   photo looks like a mistake. Placed only in the empty columns either side of
   her; keep them clear of the CTA block on the floor and the corner labels. */
.hero__scatter{position:absolute;inset:0;z-index:6;pointer-events:none}
.polaroid{
  position:absolute;left:var(--x);top:var(--y);width:var(--w);margin:0;
  transform:rotate(var(--r));
  border-radius:2px;overflow:hidden;
  box-shadow:0 14px 36px -16px rgba(23,18,28,.45),
             0 2px 8px -4px rgba(23,18,28,.18);
  /* no animation here — the intro timeline owns when these arrive. Base state
     is simply "present", so with no JS the collage just renders. */
}
.polaroid img{
  display:block;width:100%;height:auto;
  aspect-ratio:3/4;object-fit:cover;
}
@keyframes polaroidin{
  from{opacity:0;transform:rotate(var(--r)) translateY(14px) scale(.96)}
  to  {opacity:1;transform:rotate(var(--r)) translateY(0) scale(1)}
}
/* Below the wide breakpoint the side columns don't exist — the photos would
   land on top of her and the copy. */
@media (max-width:1080px){ .hero__scatter{display:none} }


/* the hero creed: bottom-right, deliberately quiet. It is the counterweight to
   the CTA block on the floor-left, and it sits in the only part of the hero the
   centred media never reaches. */
.hero__creed{
  flex:0 1 auto;margin:0;
  max-width:min(34vw,26rem);text-align:right;
  padding-top:clamp(.9rem,2vh,1.3rem);
  border-top:1px solid rgba(23,18,28,.18);
}
.hero__creed p{
  font-family:var(--serif);
  font-size:clamp(1.2rem,1.85vw,1.72rem);
  line-height:1.3;color:rgba(23,18,28,.82);
  text-wrap:balance;
}
.hero__creed em{font-style:italic;color:var(--plum-2)}
.hero__creed cite{
  display:block;margin-top:.75rem;font-style:normal;
  font-size:.64rem;letter-spacing:.24em;text-transform:uppercase;
  color:rgba(23,18,28,.4);
}
/* no room for it beside the media once the columns collapse */
@media (max-width:1080px){ .hero__creed{display:none} }

/* ══════════ THE INTRO ══════════
   The before/after is no longer something the visitor has to find by hovering —
   it is the opening sequence. On load the hero holds on the untreated hair with
   a line over it, swipes left-to-right to the finished hair, pulls focus while
   the wordmark sweeps in behind, then sets the collage and the CTA.

   BASE CSS IS THE FINAL STATE. `.hero--intro` (added by initIntro) layers the
   animations on top, every one with `both` fill so it lands exactly on that
   final state. So with no JS, or under reduced motion, the hero simply renders
   finished — nothing is stranded invisible. This is the same principle as the
   .reveal system: never hide content in CSS you can't guarantee to un-hide.

   TIMELINE (ms)   0 ── quote in ── 1600 out
                1800 ── swipe ── 2700
                2700 ── focus pull ── 3600
                2900 ── wordmark
                3100 ── collage (staggered)
                3300 ── vow + CTA
*/
.hero__layer{position:absolute;inset:0;pointer-events:none}
.hero__layer--after {z-index:1; clip-path:inset(0)}
.hero__layer--before{z-index:2; clip-path:inset(0 0 0 100%)}   /* swiped away */

/* Complementary clips: the finished hair grows from the left exactly as the
   untreated hair retreats from it. Clipping only ONE would let the layer
   underneath show through every transparent pixel of the other — around the
   silhouette and between the strands — as a halo. */
@keyframes introAfter { from{clip-path:inset(0 100% 0 0)} to{clip-path:inset(0)} }
@keyframes introBefore{ from{clip-path:inset(0)} to{clip-path:inset(0 0 0 100%)} }
.hero-intro .hero__layer--after {animation:introAfter  .8s var(--ease) 3.05s both}
.hero-intro .hero__layer--before{animation:introBefore .8s var(--ease) 3.05s both}

/* the blade travels with the seam and dies as it lands */
.hero__wipe{
  position:absolute;top:0;bottom:0;left:0;z-index:5;
  width:2px;margin-left:-1px;pointer-events:none;opacity:0;
  background:linear-gradient(180deg,rgba(255,61,190,0) 0%,#FF3DBE 18%,#9A2BF0 82%,rgba(154,43,240,0) 100%);
}
@keyframes introBlade{
  0%{left:0;opacity:0} 10%{opacity:1} 88%{opacity:1} 100%{left:100%;opacity:0}
}
.hero-intro .hero__wipe{animation:introBlade .8s var(--ease) 3.05s both}

@keyframes introRise{ from{opacity:0;transform:translateY(16px)} to{opacity:1;transform:none} }
.hero-intro .hero__marquee{animation:introRise .8s var(--ease) 4.0s both}
.hero-intro .polaroid{animation:polaroidin .7s var(--ease) calc(4.2s + var(--i) * .1s) both}
.hero-intro .hero__vow{animation:introRise .7s var(--ease) 4.4s both}
.hero-intro .hero__cta{animation:introRise .7s var(--ease) 4.55s both}
.hero-intro .hero__creed{animation:introRise .7s var(--ease) 4.7s both}

/* ── the opening line ──
   Sits over the untreated hair, so it needs its own ground: a soft white bloom
   behind it. A text-shadow can't do that job here — the glyphs are filled by a
   background-clip gradient, and a shadow on transparent text paints a solid
   ghost of the letterforms. */
.hero__quote{
  position:absolute;left:50%;top:46%;z-index:7;
  transform:translate(-50%,-50%);
  margin:0;width:min(94vw,58rem);text-align:center;
  display:grid;place-items:center;
  pointer-events:none;opacity:0;
  font-family:var(--display);font-weight:600;
  font-size:clamp(2.7rem,7.2vw,6.2rem);
  line-height:1.02;letter-spacing:-.035em;
}
.hero__quote span{
  grid-area:1/1;width:100%;opacity:0;
  background-image:linear-gradient(100deg,#7B2BE8 0%,#C13BFF 28%,#FF3DBE 55%,#C13BFF 80%,#7B2BE8 100%);
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
}
/* The pool has to be strong: at .94 falling off at 72% the second line sat on
   the mid-tones of the hair and went muddy. Near-opaque out to 48%, then a long
   fade so there is no visible disc edge. */
/* The two lines land one after the other, then leave together. The IN is per
   line so they can be staggered; the OUT lives on the parent, because two
   separately-delayed animations would otherwise also exit separately. */
/* Each line rises in, holds, and clears BEFORE the next arrives — one complete
   cycle per line, staggered by exactly one duration so they never share the
   screen. The parent no longer animates; it is just the stage. */
@keyframes introLine{
  0%{opacity:0;transform:translateY(20px)}
  26%{opacity:1;transform:none}
  74%{opacity:1;transform:none}
  100%{opacity:0;transform:translateY(-14px)}
}
.hero-intro .hero__quote{opacity:1}
.hero-intro .hero__quote span:first-child{animation:introLine 1.45s var(--ease) .15s both}
.hero-intro .hero__quote span:last-child {animation:introLine 1.45s var(--ease) 1.60s both}

.hero__vow{
  /* text-wrap:balance stops "welcome." being orphaned on its own line — the
     browser evens the two lines out instead of filling the first greedily. */
  margin:0 0 clamp(.75rem,1.8vh,1.15rem);
  max-width:30ch;text-wrap:balance;   /* 21ch was sized for the old shorter vow;
                                         her new line broke to three cramped rows */
  font-family:var(--display);font-weight:600;
  font-size:clamp(1.1rem,1.6vw,1.55rem);
  line-height:1.22;letter-spacing:-.015em;
  color:var(--ink);
}

/* Reduced motion: no sequence at all. Base CSS is already the finished state,
   so simply refusing to animate lands in exactly the right place. */
@media (prefers-reduced-motion:reduce){
  .hero-intro .hero__layer--after,
  .hero-intro .hero__layer--before,
  .hero-intro .hero__wipe,
  .hero-intro .hero__bg,
  .hero-intro .hero__marquee,
  .hero-intro .polaroid,
  .hero-intro .hero__vow,
  .hero-intro .hero__cta,
  .hero-intro .hero__creed,
  .hero-intro .hero__quote,
  .hero-intro .hero__quote span{animation:none}
  .hero__quote{display:none}
}

/* ══════════ the scrolling wordmark, and the hair that passes in FRONT of it ══
   The loop is an opaque white RECTANGLE, not a cutout with alpha. Stacked
   normally it would slice the letters along a dead-straight invisible edge.
   mix-blend-mode:multiply fixes that with no re-encode and no keying: against a
   white page, white x anything = anything (the backdrop survives untouched),
   while the dark hair multiplies down and reads as genuinely in front.

   This is WHY the wordmark must stay PALE. Multiply darkens the hair by exactly
   the wordmark's own value, so a saturated colour here would eat the blonde
   ribbons wherever they cross a letter. At ~#EFDCEA the hair only picks up a
   faint rose cast — on-brand for a rose balayage — and the highlights survive.
   If this colour is ever deepened, check the highlights over a letter first. */
.hero{isolation:isolate}   /* keep the blend inside the hero, off the page */
.hero__marquee{
  position:absolute;inset-inline:0;top:14vh;z-index:1;
  overflow:hidden;pointer-events:none;
  /* FULL strength now. The headline it used to compete with is gone, so the
     wordmark is the hero's only type and carries the brand colour outright. */
  opacity:1;
}
.marquee__track{
  display:flex;width:max-content;white-space:nowrap;
  will-change:transform;
  /* 38s over a 6-copy track ~= 105px/s. The old 30s only looked slow because
     the shortened wordmark made each half tiny (~35px/s); duration alone is
     meaningless here — what matters is half-width / duration. */
  animation:marquee 38s linear infinite;
  /* Held until the webfont is in. translateX(-50%) is only seamless while the
     two halves are pixel-identical; if Bricolage swaps in mid-cycle the track
     width changes underneath the animation and the loop visibly jumps. */
  animation-play-state:paused;
}
.fonts-ready .marquee__track{animation-play-state:running}
/* the scale lives on the SPANS, never the track — the track's transform is the
   scroll animation and one element gets one transform property. The track's
   padding-block buys room for the stretch, otherwise the taller glyphs get
   sliced top and bottom by .hero__marquee's overflow:hidden. */
.marquee__track{padding-block:4.4vh}
.marquee__track span{
  padding-right:6vw;
  transform:scaleY(1.38);transform-origin:center;
  font-family:var(--display);font-weight:600;
  font-size:18vh;line-height:1;letter-spacing:-.04em;
  /* Same gradient as the headline. First and last stops are the SAME violet, so
     the seam where span 2 begins matches where span 1 ended.
     NB: these MUST stay inside this rule. A previous edit inserted the mobile
     font-size override by closing the rule early, which orphaned every
     declaration below — the whole gradient silently became invalid CSS and the
     wordmark rendered as flat ink. */
  background-image:linear-gradient(100deg,
    #7B2BE8 0%,#A855F7 12%,#C13BFF 24%,#F03DFF 36%,#FF3DBE 48%,
    #F03DFF 60%,#C13BFF 72%,#A855F7 86%,#7B2BE8 100%);
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
}
/* phones: 18vh is oversized on a narrow screen. Still leaves each half far
   wider than the viewport, so the seamless wrap is unaffected. */
@media (max-width:720px){
  .marquee__track span{font-size:13.5vh}
}
/* exactly one half-track, so the seam lands on identical pixels */
@keyframes marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){
  /* the one animation on the page that never ends — must actually stop */
  .marquee__track{animation:none;transform:none}
}

.hero__bg img,.hero__bg .hero__vid{
  /* BOTH layers are anchored identically. They were once positioned by different
     mechanisms — the still by flex, the loop by bottom:0 — and the composition
     jumped ~87px the instant the loop faded in. Assert they still match after
     ANY change here: Math.abs(vidRect.top - imgRect.top) < 1.

     Sized by WIDTH with height:auto, never the other way round. The box then
     inherits the file's exact aspect, so object-fit can never letterbox it and
     silently shift the framing. The svh arm is the real control — it is just
     "% of the hero's height" re-expressed as a width (x 3:4 for this file) —
     and the vw arm takes over on wide-but-short windows.

     THIS frame is a complete portrait: crown at 2.6%, hair ending ~92%, and she
     is centred inside the file. So unlike the previous tight crop it needs no
     horizontal nudge and no downward drop — the whole frame is shown, standing
     on the hero's floor. 69svh puts the box at ~92% of the hero's height, which
     clears the floating nav and lets her white top fade out into the page below
     the hair. Set --hero-nudge if a future frame is off-centre in its file. */
  /* 89svh = ~91% of the hero's height for THIS file's 1642x1672 aspect (x .982).
     Re-derive the svh arm whenever the source aspect changes, or she resizes. */
  --hero-nudge:0%;
  width:min(52vw,89svh);height:auto;
  max-width:none;max-height:none;
  position:absolute;left:50%;bottom:0;
  margin:0;
  transform:translate(calc(-50% + var(--hero-nudge)),0);
  filter:saturate(1.04) contrast(1.01);
  /* No ken-burns scale here. It was built for a full-bleed backdrop photo; on a
     standing cutout it visibly grows her, and it would drift the still out of
     register with the loop layered on top. The hair motion IS the movement. */
}
/* the dull layer: desaturates and dims everything OUTSIDE the hair window.
   backdrop-filter grades the real pixels — no AI touch-up of her work — and
   the mask cuts a clear opening over the hair. Browsers without support just
   fall through to the scrim. */
/* The dull-white grade is baked into the photo itself now (wall recolored,
   subject protected) — a live backdrop-filter would just gray the white. */
.hero__dull{display:none}

/* light hero: the photo IS the surface — no fades, per the user. The type
   flips to ink and carries its own contrast on the dull-white wall. */
.hero__scrim{display:none}
.hero__body{
  position:relative;z-index:4;
  width:100%;max-width:var(--maxw);margin:0 auto;
  padding:clamp(5.5rem,11vh,7.5rem) var(--pad) clamp(2rem,5vh,3.25rem);
  /* Both hero corners live in ONE row, bottom-aligned. They used to be a flow
     block and an absolutely-positioned quote, which left them 26px out of
     alignment and needing a magic number to reconcile. */
  display:flex;align-items:flex-end;justify-content:space-between;
  gap:clamp(1.5rem,5vw,4rem);
  pointer-events:none;              /* the hero media behind stays reachable */
}
.hero__body > *{pointer-events:auto}
.hero__col{max-width:36ch}
/* ── aurora: three slow-drifting blooms. transform/opacity only, so it
   stays on the compositor and costs nothing on scroll. ── */
.hero__aurora{
  display:none;   /* screen-blend glows are invisible over the white wall */
  position:absolute;inset:0;z-index:2;overflow:hidden;pointer-events:none;
  mix-blend-mode:screen;
}
.hero__aurora span{
  position:absolute;display:block;border-radius:50%;
  filter:blur(70px);will-change:transform;
}
/* pooled in the gap between the type and the photo, not behind the headline —
   sitting under the white lines it ate their contrast */
.hero__aurora span:nth-child(1){
  width:44vw;height:44vw;top:-14%;left:40%;
  background:radial-gradient(circle,rgba(255,61,190,.09),transparent 68%);
  animation:drift-a 22s ease-in-out infinite alternate;
}
.hero__aurora span:nth-child(2){
  width:34vw;height:34vw;bottom:-16%;left:6%;
  background:radial-gradient(circle,rgba(74,30,92,.62),transparent 70%);
  animation:drift-b 27s ease-in-out infinite alternate;
}
.hero__aurora span:nth-child(3){
  width:24vw;height:24vw;top:34%;left:52%;
  background:radial-gradient(circle,rgba(255,94,91,.04),transparent 68%);
  animation:drift-c 19s ease-in-out infinite alternate;
}
@keyframes drift-a{from{transform:translate3d(0,0,0) scale(1)}to{transform:translate3d(-7%,9%,0) scale(1.16)}}
@keyframes drift-b{from{transform:translate3d(0,0,0) scale(1.1)}to{transform:translate3d(11%,-8%,0) scale(.92)}}
@keyframes drift-c{from{transform:translate3d(0,0,0) scale(.9)}to{transform:translate3d(-13%,-11%,0) scale(1.22)}}

/* fine grain — the texture that reads "finished" rather than "flat CSS" */
.hero__grain{
  position:absolute;inset:0;z-index:3;pointer-events:none;
  opacity:.22;mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E");
}

/* glass chip naming the hero photo's service — bottom right, over the hair */
/* credit line, not a badge: plain type so it reads as a caption and stays out
   of the contest between the headline and her */
.hero__tag{
  position:absolute;z-index:4;
  /* bottom-LEFT, aligned to the type column: bottom-right put it on her denim
     where dark caption text was unreadable */
  left:max(var(--pad),calc((100vw - var(--maxw))/2 + var(--pad)));
  right:auto;bottom:clamp(1.25rem,3.5vh,2.25rem);
  display:inline-flex;align-items:center;gap:.4rem;
  padding:0;border:0;border-radius:0;background:none;
  backdrop-filter:none;box-shadow:none;
  font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;font-weight:600;
  color:rgba(23,18,28,.36);
}
.hero__tag span{color:rgba(23,18,28,.52);font-weight:700}

/* Scoped with .hero deliberately: the base .hero__eyebrow rule is declared
   further down this file, so a bare .hero__eyebrow override here would tie on
   specificity and lose to it on source order. */
@media (max-width:560px){
  /* The chip can't hold one line at this width, and a two-line pill reads as
     a mistake. Keep the lit dot, drop the chrome — plain caps are cleaner. */
  .hero .hero__eyebrow{
    display:flex;padding:0;border:0;background:none;box-shadow:none;
    backdrop-filter:none;border-radius:0;
    font-size:.63rem;letter-spacing:.14em;line-height:1.5;
    gap:.35rem .55rem;color:rgba(23,18,28,.62);
  }
  .hero .hero__sep{display:none}
  /* three large blurs is a lot of fill rate for a phone */
  .hero .hero__aurora span{filter:blur(46px)}
  .hero .hero__aurora span:nth-child(3){display:none}
  .hero .hero__grain{opacity:.16}
}

/* Laptop-height screens: the pinned hero is capped at one viewport, so the
   type has to come down or the CTAs fall off the bottom edge. */
/* Every selector here is scoped with .hero on purpose: the base .thesis,
   .hero__retort, .hero__eyebrow and .hero__foot rules are declared FURTHER
   DOWN this file, so bare overrides would tie on specificity and lose on
   source order — the same trap already noted on .hero__eyebrow below. */
@media (min-width:941px) and (max-height:1000px){
  .hero .thesis{font-size:clamp(3rem,9vw,9rem)}
  .hero .hero__retort{font-size:clamp(1.05rem,2.2vw,1.6rem);margin-top:1.1rem}
  .hero .hero__eyebrow{margin-bottom:1.3rem}
  .hero .hero__foot{margin-top:1.4rem;padding-top:1.15rem}
  .hero__body{padding-top:5rem;padding-bottom:1.5rem}
}
@media (min-width:941px) and (max-height:840px){
  .hero .thesis{font-size:clamp(2.6rem,7.4vw,7.25rem)}
  .hero .hero__eyebrow{margin-bottom:.9rem}
  .hero .hero__foot{margin-top:1rem;padding-top:.85rem}
  .hero .hero__lede{font-size:.98rem;line-height:1.55}
  .hero__body{padding-top:4.5rem;padding-bottom:1.25rem}
}

@media (max-width:940px){
  .hero{min-height:100svh}
  .hero__body{padding-top:5.75rem;padding-bottom:3.25rem}
  /* phones: she sits along the bottom edge and the type moves to the top, so
     the two never share the same band of the screen */
  /* The body is just the CTA now, so park it on the floor — at flex-start it
     landed inside the wordmark band and collided with it. */
  .hero{align-items:flex-end}
  /* The .3 fade existed to keep the big headline readable over her. That
     headline is gone, so fading her only made her a ghost — full strength. */
  .hero__bg{opacity:1}
  /* the desktop drop would push a third of her off a static, auto-height hero.
     On phones she is just a faint backdrop: fit the width, sit on the floor. */
  /* NOT centred here. The frame is cut hard along its right edge (she runs to
     99.7% of it), so centring on a narrow viewport exposes that cut as a
     vertical seam mid-screen. Flush right hides it against the viewport edge;
     the left edge is safe because the frame carries ~12% white margin there. */
  .hero__bg img,.hero__bg .hero__vid{
    width:min(96vw,34rem);
    left:auto;right:0;transform:none;
  }
  .hero__tag{right:auto;left:1rem;bottom:1rem}

  /* Bring the creed BACK. It was display:none under 1080px for want of room
     beside the media, which meant her own line — "hair trauma heals… your new
     hair journey starts" — was invisible to the phone traffic that arrives from
     her Instagram, i.e. almost everyone. Restacked UNDER the CTA instead of
     beside it. Scoped to 940px and below on purpose: that is exactly where the
     hero is unpinned and auto-height (see the max-width:940px rule above), so
     the extra block grows the hero instead of pushing the CTA off a pinned
     100svh screen where it could never be scrolled to. */
  .hero__body{flex-direction:column;align-items:flex-start}
  .hero__creed{
    display:block;width:100%;max-width:34ch;
    text-align:left;
    margin-top:clamp(1.1rem,2.6vh,1.7rem);
  }
  .hero__creed p{font-size:clamp(1.02rem,4vw,1.3rem)}
  .hero__creed cite{margin-top:.55rem}
}
/* No pill: on the white hero the only things allowed to carry weight are the
   words and her. The eyebrow is plain caps with a single lit dot. */
.hero__eyebrow{
  display:inline-flex;align-items:center;gap:.55rem;flex-wrap:wrap;
  margin-bottom:clamp(1.35rem,3.5vh,2.25rem);
  padding:0;border:0;border-radius:0;background:none;
  backdrop-filter:none;box-shadow:none;
  font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;font-weight:600;
  color:rgba(23,18,28,.48);
  animation:rise .9s var(--ease) backwards;
}
.hero__pip{
  width:6px;height:6px;border-radius:50%;background:var(--orchid);
  box-shadow:0 0 10px 1px rgba(255,61,190,.85);
  animation:pip 2.6s ease-out infinite;
}
.hero__sep{
  width:1px;height:.85em;background:rgba(23,18,28,.22);
}

/* The hero is ONE statement now, so the headline carries the whole frame:
   bigger, and filled with the brand's violet→pink rather than ink. */
.thesis{
  font-family:var(--display);
  font-size:clamp(3.2rem,9.6vw,9.5rem);
  /* 600, not 800: at this size extra-bold reads loud and casual. Dropping the
     weight and opening the tracking a touch is what makes big type feel
     editorial rather than shouty. */
  font-weight:600;
  line-height:.88;
  letter-spacing:-.035em;
  text-wrap:balance;
  /* ONE continuous gradient across the whole headline, drifting slowly. First
     and last stop are the same violet so the loop has no seam. */
  background-image:linear-gradient(100deg,
    #7B2BE8 0%, #A855F7 12%, #C13BFF 24%, #F03DFF 36%, #FF3DBE 48%,
    #F03DFF 60%, #C13BFF 72%, #A855F7 86%, #7B2BE8 100%);
  /* 200% + travel to 200% is the ONLY seamless pair here. background-position
     percentages resolve against (container - image), so at size 200% one cycle
     moves exactly one tile width and the repeat lines up perfectly. At 240% it
     moved 1.4 tiles and snapped back visibly at the loop point. */
  background-size:200% 100%;
  background-position:0% 50%;
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  animation:thesisflow 6s linear infinite;
}
@keyframes thesisflow{
  from{background-position:0% 50%}
  to{background-position:200% 50%}
}
/* Each line is a clipping window; the inner span is what moves, so the type
   rises out of nothing instead of sliding over the background. */
.thesis__l{display:block;overflow:hidden;padding-bottom:.06em}
.thesis__l > span{
  display:block;
  animation:unmask 1.05s var(--ease) backwards;
  animation-delay:calc(var(--i) * .11s);
}
@keyframes unmask{
  from{opacity:0;transform:translateY(105%) rotate(2.5deg);filter:blur(9px)}
  60%{opacity:1;filter:blur(0)}
  to{opacity:1;transform:none;filter:blur(0)}
}
/* no text-shadow: the glyphs are transparent windows onto the gradient, so a
   shadow would paint a solid ghost of the letters underneath */
.thesis__l:not(.thesis__l--hit) > span{text-shadow:none}

/* ── the accent phrase: gradient fill that flows, with a bloom behind it ──
   This line opts out of the clipping mask so the 26px bloom isn't sliced off,
   and swaps the slide-up for a settle so nothing tracks over the copy below. */
.thesis__l--hit{overflow:visible}
.thesis__l--hit > span{animation-name:settle;animation-duration:1.15s}
@keyframes settle{
  from{opacity:0;transform:translateY(.18em) scale(.985)}
  to{opacity:1;transform:none}
}
.thesis__l--hit em{
  position:relative;
  display:inline-block;
  font-family:var(--serif);
  font-style:italic;
  font-weight:400;
  letter-spacing:-.01em;
  padding:0 .06em;

  /* Deliberately carries NO gradient of its own — it inherits the transparent
     fill so the h1's single moving gradient runs straight through it. Two
     separate gradients here made the last line read as a different element. */
}
/* The text-shaped bloom is OFF on the light hero — a 24px halo behind the
   phrase was the loudest thing on the screen and competed with her. */
.thesis__l--hit em::before{
  display:none;
  content:attr(data-text);
  position:absolute;left:.06em;top:0;z-index:-1;
  background-image:inherit;
  background-size:inherit;
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  filter:blur(24px) saturate(1.9);
  opacity:.85;
  animation:flow 11s linear infinite;
  pointer-events:none;
}
@keyframes flow{to{background-position:230% 50%}}

/* the drawn strike: cancels the phrase "bad hair" itself */
.thesis__l--hit em::after{
  content:"";position:absolute;left:0;top:54%;z-index:1;
  height:clamp(3px,.42vw,6px);width:100%;
  background:#FF4E4B;
  border-radius:99px;
  /* no glow, no dark seam — on white the bar just needs to be a clean line */
  transform:scaleX(0);transform-origin:left center;
  animation:strike .9s var(--ease) 1.25s forwards;
}
@keyframes strike{to{transform:scaleX(1)}}

@keyframes rise{
  from{opacity:0;transform:translateY(.5em)}
  to{opacity:1;transform:none}
}

.hero__retort{
  font-family:var(--serif);font-style:italic;
  font-size:clamp(1.15rem,2.6vw,1.9rem);
  color:var(--ink);opacity:.66;margin-top:1.5rem;
  animation:rise .95s var(--ease) .62s backwards;
}

/* hugs the left column — a divider running across the whole photo cut it up */
.hero__foot{
  display:flex;flex-wrap:wrap;gap:clamp(1.25rem,3vw,2.25rem);
  align-items:flex-end;
  max-width:46rem;
  margin-top:clamp(1.75rem,4vh,2.5rem);
  padding-top:clamp(1.25rem,2.5vh,1.75rem);
}
.hero__lede{max-width:44ch;color:var(--ink);opacity:.78;line-height:1.65}
/* secondary action stays a hairline outline — no fill, no glass, so the pink
   CTA is the only button carrying weight */
.hero .btn--ghost{
  border-color:rgba(23,18,28,.22);color:var(--ink);
  background:none;backdrop-filter:none;box-shadow:none;
}
.hero .btn--ghost:hover{color:var(--ink);border-color:rgba(255,61,190,.65)}
.hero__cta{
  display:flex;flex-wrap:wrap;gap:.75rem;
  margin-top:clamp(2rem,5vh,3.25rem);
}

/* ══════════════════ the "too much" wall — SIGNATURE ══════════════════ */
.wall{
  background:var(--bone);
  color:var(--ink);
  padding:clamp(7rem,16vh,10.5rem) 0 clamp(4rem,10vh,7.5rem);
  overflow:hidden;
}
/* dusk band: the hero's night settles into the bone through the brand violet,
   so the dark→light cut reads as a horizon instead of a seam */
.wall::after{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  /* the hero is light now, so the curtain leads with a pink dawn breath
     instead of the old night-into-day ink ramp */
  background:linear-gradient(180deg,
      rgba(255,61,190,.14) 0%,
      rgba(255,161,225,.08) 45%,
      rgba(246,241,244,0) 100%)
    top/100% clamp(4rem,10vh,7rem) no-repeat;
  /* --dusk is scrubbed 0→1 by Motion as the section arrives; the default of 1
     means no JS = band simply sits in place */
  transform:translateY(calc(-5rem * (1 - var(--dusk,1))));
  opacity:calc(.3 + .7 * var(--dusk,1));
}
.wall__h{
  max-width:var(--maxw);margin:0 auto clamp(1.75rem,4vh,2.75rem);
  padding:0 var(--pad);
  font-size:.72rem;letter-spacing:.22em;text-transform:uppercase;
  font-weight:600;opacity:.45;
}
.wall__marquee{display:grid;gap:clamp(.1rem,.6vw,.5rem);user-select:none}
.wall__track{
  display:flex;gap:clamp(1.25rem,3vw,3rem);width:max-content;
  animation:slide 44s linear infinite;
}
.wall__track--rev{animation-direction:reverse;animation-duration:56s}
.wall__track span{
  position:relative;
  font-family:var(--display);
  font-size:clamp(2.4rem,8.5vw,7rem);
  font-weight:700;letter-spacing:-.04em;line-height:1.05;
  color:#C9BCC6;                      /* the words are muted... */
  white-space:nowrap;
}
.wall__track span::after{           /* ...and every one is struck out */
  content:"";position:absolute;left:-.02em;right:-.02em;top:52%;
  height:clamp(3px,.55vw,7px);
  background:var(--ember);border-radius:99px;
  opacity:.9;
}
@keyframes slide{to{transform:translateX(-50%)}}

.wall__quote{
  max-width:min(56rem,var(--maxw));margin:clamp(3rem,8vh,5.5rem) auto 0;
  padding:0 var(--pad);
}
.wall__quote p{
  font-family:var(--serif);
  font-size:clamp(1.6rem,4.2vw,3.1rem);
  line-height:1.16;letter-spacing:-.015em;
}
.wall__quote em{font-style:italic;color:var(--plum-2)}
.wall__quote-tail{
  font-family:var(--body)!important;
  font-size:1.02rem!important;line-height:1.65!important;
  letter-spacing:0!important;
  margin-top:1.75rem;max-width:52ch;opacity:.62;
}

/* ══════════════════ the work ══════════════════ */
/* rails bleed to the viewport edge, so the section carries no side padding;
   heads and rails share the same inset so the first card aligns with the text */
.work{
  /* no top padding: the intro veil opens on the wall's bone, so any gap here
     reads as a stray ink stripe between two light surfaces */
  padding:0 0 clamp(2.25rem,4.5vh,3.25rem);
  background:var(--ink);
}
.work .sec-head{
  max-width:none;
  padding-inline:max(var(--pad),calc((100vw - var(--maxw))/2));
}

/* ── lookbook rails ── */
.railgroup{margin-bottom:clamp(2.75rem,6vh,4.25rem)}
.railgroup:last-child{margin-bottom:0}
.railgroup__head{
  display:flex;align-items:flex-end;justify-content:space-between;gap:1rem;
  padding-inline:max(var(--pad),calc((100vw - var(--maxw))/2));
  margin-bottom:1.1rem;
}
/* quiet, editorial: a lit dot + small caps, not display type */
.railgroup__title{
  display:flex;align-items:center;gap:.6rem;
  font-family:var(--body);
  font-size:.8rem;font-weight:600;
  letter-spacing:.22em;text-transform:uppercase;line-height:1;
}
.railgroup__title::before{
  content:"";width:7px;height:7px;border-radius:50%;
  background:var(--orchid);box-shadow:0 0 9px rgba(255,61,190,.8);
}
.railgroup__range{font-size:.82rem;opacity:.5;margin-top:.5rem}
.railgroup__nav{display:flex;gap:.5rem}
.railbtn{
  width:2.6rem;height:2.6rem;border-radius:50%;
  display:grid;place-items:center;
  font-size:1.5rem;line-height:0;color:var(--bone);
  border:1px solid rgba(246,241,244,.2);
  background:linear-gradient(180deg,rgba(246,241,244,.07),rgba(246,241,244,.02));
  transition:border-color .25s,color .25s,transform .25s;
}
.railbtn:hover{border-color:var(--orchid);color:var(--orchid);transform:translateY(-1px)}

.rail{
  display:grid;grid-auto-flow:column;
  grid-auto-columns:clamp(17.5rem,27vw,25rem);
  gap:clamp(1.1rem,2.4vw,2.1rem);
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;
  scroll-padding-inline:max(var(--pad),calc((100vw - var(--maxw))/2));
  padding-inline:max(var(--pad),calc((100vw - var(--maxw))/2));
  padding-block:.25rem 1rem;
  scrollbar-width:none;
}
.rail::-webkit-scrollbar{display:none}

/* editorial: flat image, two-line museum placard below — the cut's name,
   then the price. No chrome on the photo itself. */
.tile{
  position:relative;margin:0;
  display:flex;flex-direction:column;
  scroll-snap-align:start;
  cursor:zoom-in;
}
.tile img{
  order:0;
  width:100%;aspect-ratio:3/4;object-fit:cover;
  background:var(--ink-2);
  transition:opacity .45s var(--ease),transform .6s var(--ease);
}
.tile::after{ /* the cut */
  order:1;
  content:attr(data-service);
  padding:.85rem .1rem 0;
  font-size:.7rem;font-weight:600;letter-spacing:.16em;text-transform:uppercase;
  line-height:1.35;
  color:rgba(246,241,244,.78);
  /* WRAP, don't truncate. `white-space:nowrap` + ellipsis suited the wide reel
     frames, but the Known-for grid is 165px per tile on a 393px phone and it
     chopped "Cool blonde balayage" to "COOL BLONDE BALAYA…". The cut's NAME is
     the one thing on the placard that has to survive. Two lines, then clamp. */
  display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;
  overflow:hidden;
  transition:color .3s;
}
.tile::before{ /* the price */
  order:2;
  content:attr(data-price);
  padding:.2rem .1rem 0;
  font-family:var(--display);
  font-size:.95rem;font-weight:700;letter-spacing:-.01em;
  font-variant-numeric:tabular-nums;
  color:var(--orchid);
}
.tile:hover img,.tile:focus-within img{opacity:.88;transform:translateY(-4px)}
.tile:hover::after,.tile:focus-within::after{color:var(--bone)}

/* ── the reel-intro band: Higgsfield strands clip under the section title,
   melting into the ink the rails sit on ── */
/* A transition BAND, not a picture. It used to hold an abstract 3D-ribbon clip
   that read as a blurry blue wave — nothing to do with her, and it put a hard
   photographic edge between the wall's bone and this section's ink. Now the
   handoff is pure gradient, and the band is short enough to read as a deliberate
   seam rather than an empty hero. */
.work__intro{
  position:relative;overflow:hidden;
  display:flex;align-items:flex-end;
  min-height:clamp(15rem,36vh,21rem);
  margin-bottom:clamp(2.5rem,6vh,4rem);
  /* Overlap the wall by 1px. The band's own top row still antialiases against
     whatever sits behind it, and that is .work's ink — which drew the same dark
     line one level out. Pulled up, it blends bone-on-bone against the wall. */
  margin-top:-1px;
  /* The gradient is the element's OWN background — not a child overlay. Both
     section boundaries land on fractional pixels, so a child veil antialiases
     against its parent at top AND bottom: backing it with ink drew a dark line
     at the top, backing it with bone drew a light one at the bottom. One
     element with one background has no interior edge to blend against. */
  /* STARTS AT INK, not bone. This band originally opened on the wall's bone and
     was continuous with it. Inserting the (dark) Transformations section above
     left a light band butted straight against ink — a hard horizontal line. The
     first stop now matches whatever sits above, and the light horizon simply
     moves a little lower into the band. If a LIGHT section is ever put back
     above .work, change the 0% stop back to var(--bone). */
  background:linear-gradient(to bottom,
    var(--ink) 0%,
    rgba(52,26,66,.98) 5%,
    rgba(198,140,220,.92) 15%,
    rgba(240,220,242,.98) 26%,   /* horizon sits HIGH: the heading is bottom-
                                    aligned, and a light peak behind it washed
                                    out the violet gradient type */
    rgba(198,140,220,.92) 44%,
    rgba(116,54,140,.95) 64%,
    rgba(52,26,66,.98) 86%,
    var(--ink) 100%);
}


.work__intro .sec-head{
  position:relative;z-index:2;
  width:100%;margin:0;
  padding-bottom:clamp(1.25rem,3.5vh,2rem);
}

@media (max-width:720px){
  .rail{grid-auto-columns:min(70vw,19rem)}
  .railgroup__nav{display:none}   /* swipe is the control on touch */
}

/* ══════════════════ story ══════════════════ */
.story{
  position:relative;isolation:isolate;overflow:hidden;
  display:grid;
  grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);
  gap:clamp(2rem,6vw,5.5rem);
  align-items:center;
  padding:clamp(2.75rem,6vh,4.25rem) var(--pad) clamp(4rem,11vh,8rem);
  max-width:var(--maxw);margin:0 auto;
  background:
    radial-gradient(ellipse 70% 50% at 15% 40%,rgba(74,30,92,.5),transparent 70%);
}

.story__figures{position:relative}
.story__portrait{margin:0;border-radius:4px;overflow:hidden}
.story__portrait img{width:100%}
.story__inset{
  position:absolute;right:-1.5rem;bottom:-2rem;
  width:min(44%,15rem);margin:0;
  border:6px solid var(--ink);border-radius:4px;overflow:hidden;
  box-shadow:0 20px 50px rgba(0,0,0,.5);
}
.story__text .sec-head__h{margin-bottom:1.75rem}
.story__lead{
  font-family:var(--serif);
  font-size:clamp(1.35rem,2.8vw,2rem);
  line-height:1.28;margin-bottom:1.5rem;
}
.story__text p:not(.story__lead):not(.story__pull){
  opacity:.76;margin-bottom:1.15rem;max-width:52ch;line-height:1.7;
}
.story__text strong{color:var(--bone);font-weight:600}
.story__text em{font-style:italic;color:var(--orchid)}
.story__pull{
  margin-top:2.25rem;padding-top:1.5rem;
  border-top:1px solid rgba(255,61,190,.3);
  font-family:var(--display);
  font-size:clamp(1.15rem,2.2vw,1.6rem);
  font-weight:600;letter-spacing:-.02em;line-height:1.2;
  color:var(--orchid);
}
@media (max-width:860px){
  .story{grid-template-columns:1fr;gap:3.5rem}
  .story__figures{max-width:26rem}
  .story__inset{right:-1rem;bottom:-1.5rem}
}

/* ══════════════════ vows ══════════════════ */
.vows{
  /* eases in from the story's ink, with a faint orchid horizon where the
     hairline rule used to sit — a glow reads as flow, a border reads as a cut */
  background:
    linear-gradient(180deg,var(--ink) 0%,rgba(23,18,28,0) 20%),
    radial-gradient(60rem 9rem at 50% 0,rgba(255,61,190,.13),transparent 70%),
    linear-gradient(168deg,var(--plum) 0%,var(--ink) 78%);
  padding:clamp(4rem,10vh,7rem) var(--pad);
}
.vows{
  display:grid;
  grid-template-columns:minmax(0,.72fr) minmax(0,1.28fr);
  gap:clamp(1.5rem,5vw,4.5rem);
  /* STRETCH, not start. With align-items:start the left cell collapsed to the
     heading's own height, so position:sticky had nowhere to travel and the
     heading just pinned to the top leaving a dead column beneath it. */
  align-items:stretch;
  max-width:calc(var(--maxw) + 2 * 5.5rem);margin:0 auto;
}
.vows__side{position:relative}
/* the sticky element must be a CHILD of the stretched cell, never the cell */
.vows__sticky{position:sticky;top:clamp(5.5rem,20vh,9rem)}
.vows__h{
  font-family:var(--display);
  font-size:clamp(2rem,5.5vw,4.25rem);
  font-weight:700;letter-spacing:-.035em;line-height:.95;
}
.vows__sub{
  margin-top:clamp(1rem,2.4vh,1.6rem);
  max-width:24ch;
  font-size:.95rem;line-height:1.6;opacity:.52;
}
.vows__list{display:grid;gap:0}
@media (max-width:900px){
  .vows{grid-template-columns:1fr;gap:2rem}
  .vows__sticky{position:static}
  .vows__sub{max-width:none}
}
.vows__list li{
  padding:clamp(1.5rem,3.5vh,2.25rem) 0;
  border-top:1px solid rgba(246,241,244,.13);
  max-width:74ch;line-height:1.62;opacity:.8;
  font-size:1.03rem;
}
.vows__list li:last-child{border-bottom:1px solid rgba(246,241,244,.13)}
.vows__key{
  display:block;
  font-family:var(--display);
  font-size:clamp(1.2rem,2.6vw,1.8rem);
  font-weight:700;letter-spacing:-.025em;line-height:1.15;
  color:var(--bone);opacity:1;
  margin-bottom:.55rem;
}

/* ══════════════════ prices : clickable photo panels ══════════════════ */
.menuband{padding:clamp(4rem,10vh,7.5rem) var(--pad);background:var(--ink)}
.menuband::before{
  animation-duration:46s;animation-delay:-18s;
  background:
    radial-gradient(48rem 30rem at 12% 10%,rgba(74,30,92,.45),transparent 60%),
    radial-gradient(40rem 26rem at 90% 90%,rgba(255,61,190,.12),transparent 58%);
}
.menuband__grid{
  display:grid;grid-template-columns:repeat(3,1fr);
  gap:clamp(6px,.8vw,12px);
  max-width:var(--maxw);margin:0 auto;
}
.panel{
  position:relative;display:flex;flex-direction:column;justify-content:flex-end;
  min-height:clamp(28rem,70vh,40rem);
  overflow:hidden;text-decoration:none;color:var(--bone);
}
.panel__img{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;object-fit:cover;
  transition:transform .9s var(--ease);
}
.panel::before{ /* menu-card scrim */
  content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(to top,
    rgba(19,13,24,.93) 0%,rgba(19,13,24,.72) 38%,
    rgba(19,13,24,.22) 66%,rgba(19,13,24,.34) 100%);
  transition:background .5s var(--ease);
}
/* big vertical label, reads bottom-up like a spine */
.panel__label{
  position:absolute;top:1.15rem;left:1.1rem;z-index:2;
  writing-mode:vertical-rl;transform:rotate(180deg);
  font-family:var(--display);
  font-size:clamp(1.9rem,3.2vw,2.9rem);
  font-weight:700;letter-spacing:-.02em;line-height:1;
  color:var(--bone);
  text-shadow:0 2px 22px rgba(10,7,14,.6);
}
.panel .rates{position:relative;z-index:2;margin:0;padding:0 clamp(1.1rem,2vw,1.6rem)}
.panel__note{
  position:relative;z-index:2;
  padding:.6rem clamp(1.1rem,2vw,1.6rem) 0;
  font-size:.78rem;opacity:.55;
}
.panel__cta{
  position:relative;z-index:2;align-self:flex-start;
  margin:1.15rem clamp(1.1rem,2vw,1.6rem) clamp(1.1rem,2vw,1.5rem);
  display:inline-flex;align-items:center;gap:.45rem;
  padding:.65rem 1.3rem;border-radius:999px;
  font-size:.74rem;font-weight:600;letter-spacing:.11em;text-transform:uppercase;
  border:1px solid rgba(246,241,244,.3);
  background:rgba(19,13,24,.35);backdrop-filter:blur(8px);
  transition:border-color .3s,background .3s,transform .3s;
}
.panel:hover .panel__img,.panel:focus-visible .panel__img{transform:scale(1.05)}
.panel:hover .panel__cta,.panel:focus-visible .panel__cta{
  border-color:var(--orchid);background:rgba(255,61,190,.18);transform:translateX(3px);
}
.panel:focus-visible{outline-offset:-3px}

.rates{margin:0}
.rate{
  display:flex;align-items:baseline;gap:.75rem;
  padding:.66rem 0;
  border-bottom:1px solid rgba(246,241,244,.16);
}
.rate dt{flex:1;font-size:.92rem}
.rate dd{
  margin:0;
  font-family:var(--display);font-size:1.05rem;font-weight:700;
  font-variant-numeric:tabular-nums;letter-spacing:-.02em;
  color:#FFA1E1;
}
.menuband__fine{
  max-width:var(--maxw);margin:1.75rem auto 0;
  font-size:.85rem;opacity:.5;
}
@media (max-width:900px){
  .menuband__grid{grid-template-columns:1fr}
  .panel{min-height:24rem}
}

/* ══════════════════ two chairs ══════════════════ */
.chairs{
  padding:clamp(4rem,10vh,7.5rem) var(--pad);
  background:var(--ink);
}
.chairs__grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,17.5rem),1fr));
  gap:clamp(1rem,2.5vw,1.75rem);
  max-width:var(--maxw);margin:0 auto;
}
.chair{
  display:flex;flex-direction:column;
  padding:clamp(1.5rem,3vw,2.25rem);
  background:var(--ink-2);
  border:1px solid rgba(246,241,244,.11);
  border-radius:6px;
  transition:border-color .35s var(--ease),transform .35s var(--ease);
}
.chair:hover{border-color:rgba(255,61,190,.45);transform:translateY(-3px)}
.chair__when{
  font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--orchid);font-weight:600;
}
.chair__where{
  font-family:var(--display);
  font-size:clamp(1.7rem,3.4vw,2.5rem);
  font-weight:700;letter-spacing:-.035em;line-height:1;
  margin:.65rem 0 1rem;
}
.chair__addr{font-style:normal;opacity:.68;line-height:1.55;font-size:.95rem}
.chair__hours{
  display:flex;align-items:center;gap:.5rem;
  margin:1.1rem 0 1.75rem;font-size:.85rem;opacity:.6;
}
.chair__dot{width:5px;height:5px;border-radius:50%;background:var(--clay);flex-shrink:0}
.chair__talk{opacity:.68;line-height:1.6;font-size:.95rem;margin:0 0 1.75rem}
.chair .btn{margin-top:auto}
.chair__via{
  margin-top:.85rem;font-size:.76rem;opacity:.42;line-height:1.5;
}
.chair--talk{background:transparent;border-style:dashed}
.chair.is-open{border-color:rgba(255,61,190,.55);background:var(--ink-3)}
.chair.is-open .chair__dot{background:var(--orchid);box-shadow:0 0 8px var(--orchid)}

.chairs__cancel{
  max-width:var(--maxw);margin:2.5rem auto 0;
  font-size:.88rem;opacity:.5;
}

/* ══════════════════ hair school ══════════════════ */
.school{
  background:var(--bone);
  color:var(--ink);
  padding:clamp(7rem,16vh,10.5rem) var(--pad) clamp(8.5rem,19vh,12rem);
}
/* same horizon treatment on both edges: the chairs' ink eases in through
   violet up top, and the bottom settles back into the dark the FAQ sits on */
.school::after{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:
    linear-gradient(180deg,
      var(--ink) 0%,rgba(35,20,44,.94) 26%,rgba(94,52,122,.4) 58%,
      rgba(255,61,190,.12) 78%,rgba(246,241,244,0) 100%)
      top/100% clamp(6rem,14vh,9rem) no-repeat,
    linear-gradient(0deg,
      var(--ink) 0%,rgba(35,20,44,.94) 26%,rgba(94,52,122,.4) 58%,
      rgba(255,61,190,.12) 78%,rgba(246,241,244,0) 100%)
      bottom/100% clamp(6rem,14vh,9rem) no-repeat;
  opacity:calc(.25 + .75 * var(--dusk,1));
}
.school .sec-head__note{opacity:.62}
.school__grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,17rem),1fr));
  gap:clamp(1.75rem,4vw,3rem);
  max-width:var(--maxw);margin:0 auto;
}
.lesson{padding-top:1.25rem;border-top:2px solid var(--ink)}
.lesson h3{
  font-family:var(--display);
  font-size:clamp(1.3rem,2.4vw,1.75rem);
  font-weight:700;letter-spacing:-.03em;margin-bottom:.9rem;
}
.lesson p{font-size:.95rem;line-height:1.65;opacity:.72;margin-bottom:.85rem}
.lesson em{font-style:italic;color:var(--plum-2);font-weight:500}
.lesson__kick{
  font-family:var(--serif)!important;font-style:italic;
  font-size:1.2rem!important;opacity:1!important;
  color:var(--plum-2);margin-top:1.25rem!important;
}

/* ══════════════════ faq ══════════════════ */
.faq{padding:clamp(4rem,10vh,7.5rem) var(--pad);max-width:var(--maxw);margin:0 auto}
.faq .sec-head__h{margin-bottom:2.75rem}
.faq__list{max-width:52rem}
.qa{border-top:1px solid rgba(246,241,244,.15)}
.qa:last-child{border-bottom:1px solid rgba(246,241,244,.15)}
.qa summary{
  display:flex;align-items:center;justify-content:space-between;gap:1.5rem;
  padding:1.4rem 0;cursor:pointer;list-style:none;
  font-family:var(--display);
  font-size:clamp(1.05rem,2vw,1.35rem);
  font-weight:600;letter-spacing:-.02em;line-height:1.25;
  transition:color .25s;
}
.qa summary::-webkit-details-marker{display:none}
.qa summary::after{
  content:"+";flex-shrink:0;
  font-size:1.5rem;font-weight:400;color:var(--orchid);
  transition:transform .35s var(--ease);
}
.qa[open] summary::after{transform:rotate(45deg)}
.qa summary:hover{color:var(--orchid)}
.qa__body{padding:0 0 1.6rem;max-width:60ch}
.qa__body p,.qa__body li{opacity:.72;line-height:1.7;font-size:.98rem}
.qa__body ul{display:grid;gap:.6rem}
.qa__body li{padding-left:1.1rem;position:relative}
.qa__body li::before{
  content:"";position:absolute;left:0;top:.7em;
  width:5px;height:5px;border-radius:50%;background:var(--orchid);
}
.qa__body strong{color:var(--bone);font-weight:600}

/* ══════════════════ closer ══════════════════ */
.closer{
  position:relative;overflow:hidden;
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);
  gap:clamp(2rem,5vw,4.5rem);align-items:center;
  padding:clamp(4rem,10vh,7rem) max(var(--pad),calc((100vw - var(--maxw))/2));
  /* fallback mesh — also what reduced-motion and no-JS visitors see */
  background:
    radial-gradient(48rem 30rem at 80% 16%,rgba(74,30,92,.6),transparent 62%),
    radial-gradient(40rem 26rem at 6% 96%,rgba(255,61,190,.12),transparent 60%),
    var(--ink);
}
/* Higgsfield silk loop — decorative, lazy-loaded by JS near the viewport */
.closer__loop{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;object-fit:cover;
  opacity:.45;
}
.closer__veil{
  position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(180deg,rgba(23,18,28,.78),rgba(23,18,28,.5) 48%,rgba(23,18,28,.85));
}
.closer__img,.closer__text{position:relative;z-index:2}
@media (prefers-reduced-motion:reduce){.closer__loop{display:none}}
.closer__img{margin:0;border-radius:4px;overflow:hidden}
.closer__text h2{
  font-family:var(--display);
  font-size:clamp(2.2rem,6vw,4.75rem);
  font-weight:800;letter-spacing:-.04em;line-height:.93;
}
.closer__text p{margin:1.5rem 0 2.25rem;opacity:.7;max-width:38ch}
@media (max-width:800px){
  .closer{grid-template-columns:1fr}
  .closer__img{max-width:22rem}
}

/* ══════════════════ footer ══════════════════ */
.foot{
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,13rem),1fr));
  gap:clamp(1.75rem,4vw,3rem);
  padding:clamp(3rem,7vh,5rem) var(--pad) 2rem;
  max-width:var(--maxw);margin:0 auto;
  border-top:1px solid rgba(246,241,244,.14);
}
.foot__mark{
  font-family:var(--display);font-size:2rem;font-weight:800;letter-spacing:-.04em;
}
.foot__tag{font-size:.82rem;opacity:.5;margin-top:.4rem;line-height:1.5}
.foot__col h2{
  font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;
  font-weight:600;opacity:.45;margin-bottom:.9rem;
}
.foot__col p{font-size:.92rem;opacity:.72;line-height:1.6;margin-bottom:.55rem}
.foot__col a{text-decoration:none;border-bottom:1px solid rgba(255,61,190,.4);transition:color .25s}
.foot__col a:hover{color:var(--orchid)}
.foot__fine{font-size:.78rem!important;opacity:.42!important;margin-top:.9rem}
.foot__base{
  grid-column:1/-1;margin-top:1.5rem;padding-top:1.5rem;
  border-top:1px solid rgba(246,241,244,.1);
  font-size:.75rem;opacity:.4;
}

/* ══════════════════ mobile nav (built by main.js) ══════════════════ */
.navtoggle{
  display:grid;gap:5px;padding:.6rem;margin-left:.25rem;order:9;
}
.navtoggle span{
  display:block;width:22px;height:1.5px;background:var(--bone);
  transition:transform .3s var(--ease),opacity .3s var(--ease);
}
.navtoggle.is-x span:first-child{transform:translateY(3.25px) rotate(45deg)}
.navtoggle.is-x span:last-child{transform:translateY(-3.25px) rotate(-45deg)}

.navsheet{
  position:fixed;inset:0;z-index:99;
  background:rgba(23,18,28,.97);
  backdrop-filter:blur(18px);
  display:grid;place-items:center;
}
/* REQUIRED, not tidiness. main.js closes the sheet with `sheet.hidden = true`,
   but the hidden attribute only carries display:none in the UA stylesheet — any
   author `display` beats it. Without this rule the sheet NEVER hides, and every
   viewport under 1081px loads with the whole site behind an opaque menu (the
   min-width:1081px block below is the only reason desktop looked fine).
   Any element given a `display` must restate [hidden] if JS toggles .hidden. */
.navsheet[hidden]{display:none}
.navsheet nav{display:grid;gap:.5rem;text-align:center}
.navsheet a{
  font-family:var(--display);
  font-size:clamp(2rem,9vw,3.25rem);
  font-weight:700;letter-spacing:-.035em;line-height:1.1;
  text-decoration:none;padding:.35rem 1rem;
  transition:color .25s;
}
.navsheet a:hover{color:var(--orchid)}
/* MUST stay the exact complement of the `max-width:1300px` rule that hides
   .bar__nav. If these two breakpoints ever drift apart, the widths between them
   get NO navigation at all — inline nav hidden AND burger hidden. */
@media (min-width:1221px){
  .navtoggle,.navsheet{display:none}
}

/* ══════════════════ the work: showcase + reel ══════════════════
   Two halves. Four tall category panels edge-to-edge (each photo gets a whole
   column and a vertical word), then a slow-drifting ribbon of everything else.
   Replaces a 27-tile rail set, then a single-stage spotlight — the panels give
   the work scale, the ribbon gives it volume, without one fighting the other. */
.showcase{
  display:grid;grid-template-columns:repeat(4,1fr);
  gap:2px;margin-bottom:clamp(3rem,7vh,5rem);
}
.show{
  position:relative;display:block;overflow:hidden;
  height:clamp(30rem,80vh,52rem);
  text-decoration:none;color:var(--bone);
  isolation:isolate;
}
.show__img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  transition:transform 1.1s var(--ease),filter .6s var(--ease);
  filter:saturate(.92) brightness(.82);
}
.show::after{                       /* legibility floor for the label */
  content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(180deg,rgba(23,18,28,.15) 0%,rgba(23,18,28,0) 38%,rgba(23,18,28,.72) 100%);
}
.show:hover .show__img,.show:focus-visible .show__img{transform:scale(1.06);filter:saturate(1) brightness(.9)}

/* the vertical word — the whole point of the layout */
.show__label{
  position:absolute;left:clamp(.9rem,1.6vw,1.6rem);bottom:clamp(4.6rem,9vh,6rem);z-index:2;
  writing-mode:vertical-rl;transform:rotate(180deg);
  font-family:var(--display);font-weight:600;
  font-size:clamp(2.9rem,5.2vw,5.2rem);
  line-height:1;letter-spacing:-.035em;
  text-transform:lowercase;
  text-shadow:0 2px 30px rgba(23,18,28,.5);
}
.show__pill{
  position:absolute;left:clamp(.9rem,1.6vw,1.6rem);bottom:clamp(1.5rem,3vh,2.2rem);z-index:2;
  display:inline-flex;align-items:center;gap:.45rem;
  padding:.5rem 1rem;border-radius:999px;
  background:rgba(246,241,244,.92);color:var(--ink);
  font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;font-weight:600;
  transition:background .3s var(--ease),gap .3s var(--ease);
}
.show:hover .show__pill{background:#fff;gap:.75rem}

/* ── the reel: a curved, drag-anywhere gallery ──
   A real horizontal scroller (so trackpad, shift-wheel, touch and keyboard all
   work for free) with the arc applied per-frame in JS. perspective on the
   scroller is what makes the rotateY read as a curve rather than a skew.

   NOTE: no cursor change on purpose — Brandon asked for drag without the
   pointer swapping to a grab hand. */
.reel__title{
  margin:0 var(--pad) clamp(.4rem,1vh,.9rem);
  font-family:var(--display);font-weight:600;
  font-size:clamp(1.6rem,3.2vw,2.8rem);
  line-height:1;letter-spacing:-.03em;
}

.reel{
  position:relative;
  overflow-x:auto;overflow-y:hidden;
  scrollbar-width:none;
  -webkit-overflow-scrolling:touch;
  overscroll-behavior-x:contain;
  perspective:1400px;
  padding:clamp(2.2rem,5vh,3.6rem) 0 clamp(1rem,2.5vh,1.6rem);
}
.reel::-webkit-scrollbar{display:none}
.reel__track{
  display:flex;align-items:center;
  gap:clamp(.7rem,1.4vw,1.15rem);
  width:max-content;
  /* half a viewport of lead-in/out so the first and last frames can reach the
     centre of the arc instead of being stuck at the edge */
  padding:0 42vw;
  transform-style:preserve-3d;
}
.reel .tile{
  flex:0 0 auto;margin:0;
  width:clamp(11rem,24vw,22rem);
  border-radius:6px;overflow:hidden;
  transform-style:preserve-3d;
  will-change:transform;
  /* set by initReel; the transition only smooths the hover lift, the curve
     itself is written every scroll frame and must not lag */
  transition:box-shadow .4s var(--ease),filter .4s var(--ease);
  box-shadow:0 22px 46px -26px rgba(0,0,0,.75);
  filter:brightness(.86);
}
.reel .tile img{display:block;width:100%;aspect-ratio:3/4;object-fit:cover;pointer-events:none}
.reel .tile.is-front{filter:brightness(1);box-shadow:0 34px 66px -26px rgba(0,0,0,.85)}
/* while a drag is in progress, kill click-through on the frames */
.reel.is-dragging .tile{pointer-events:none}

.reel__hint{
  margin:clamp(1.1rem,2.6vh,1.7rem) var(--pad) 0;
  font-size:.68rem;letter-spacing:.2em;text-transform:uppercase;
  opacity:.38;
}
@media (max-width:860px){
  .showcase{grid-template-columns:repeat(2,1fr)}
  .show{height:clamp(20rem,52vh,28rem)}
}
@media (prefers-reduced-motion:reduce){
  .reel__track{animation:none}
  .reel{overflow-x:auto}
}

/* ══════════ brand gradient on every major heading ══════════
   One rule, so a new section heading inherits it automatically instead of
   needing a bespoke block. First and last stops are the SAME violet, so the
   sweep loops without a snap (see the seamless-loop maths on the wordmark).
   Deliberately NOT applied to small type — .wall__h (.72rem), .post__title,
   .menu__cat and .chair__where stay solid, because a gradient across a few
   hundred pixels of small text just reads as muddy colour. */
.sec-head__h,
.vows__h,
.reel__title{
  background-image:linear-gradient(100deg,
    #7B2BE8 0%,#A855F7 14%,#C13BFF 28%,#F03DFF 42%,#FF3DBE 50%,
    #F03DFF 58%,#C13BFF 72%,#A855F7 86%,#7B2BE8 100%);
  background-size:200% 100%;
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  animation:thesisflow 9s linear infinite;
}
@media (prefers-reduced-motion:reduce){
  .sec-head__h,.vows__h,.reel__title{animation:none;background-position:40% 50%}
}

/* ── the wall quote, given weight ──
   Bigger, and framed by an outline that tilts WITH it (the tilt is applied to
   this same element, so the frame turns as one object rather than the text
   sliding inside a static box). */
.wall__quote{
  position:relative;
  padding:clamp(2rem,4.5vw,3.4rem) clamp(1.8rem,4vw,3.2rem);
  border:1px solid rgba(23,18,28,.16);
  border-radius:3px;
  background:linear-gradient(150deg,rgba(255,255,255,.5),rgba(255,255,255,.14));
  backdrop-filter:blur(2px);
  box-shadow:0 30px 70px -45px rgba(74,30,92,.55);
}
/* a second, offset rule — the "outline that follows it" reads as depth once the
   block turns, because the two frames separate as it rotates */
.wall__quote::before{
  content:"";position:absolute;inset:-9px;z-index:-1;
  border:1px solid rgba(154,43,240,.22);
  border-radius:5px;
  transform:translateZ(-18px);
  pointer-events:none;
}
.wall__quote p{
  font-size:clamp(1.9rem,3.6vw,3.1rem);
  line-height:1.12;letter-spacing:-.02em;
}
.wall__quote .wall__quote-tail{
  font-family:var(--body);
  font-size:clamp(.98rem,1.25vw,1.14rem);
  line-height:1.62;letter-spacing:0;
  margin-top:clamp(1.1rem,2.4vh,1.7rem);
  opacity:.62;max-width:44ch;
}

/* cursor tilt: the transition is only present at REST, so returning to centre
   eases but tracking the pointer stays instant — a transition during tracking
   makes the element lag the cursor and feel broken. */
[data-tilt]{
  transform-style:preserve-3d;
  transition:transform .55s var(--ease);
  will-change:transform;
}
[data-tilt].is-tilting{transition:none}

/* ══════════════════ price menu ══════════════════
   Typeset like a real menu — leader dots, aligned prices, two columns — rather
   than three photo panels each carrying a long rate list. The photos were doing
   no work here: the WORK rails already show her results, so repeating them
   behind prices just made the section heavy. */
.menu{
  display:grid;gap:clamp(2.5rem,6vw,5rem);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,22rem),1fr));
  max-width:var(--maxw);margin:0 auto;
}
.menu__cat{
  font-family:var(--serif);font-style:italic;font-weight:400;
  font-size:clamp(1.6rem,2.6vw,2.15rem);
  color:var(--orchid);
  margin-bottom:clamp(1.1rem,2.4vh,1.7rem);
}
.menu__list{display:grid;gap:.05rem}

/* the leader: a flexible dotted rule that eats whatever space is left, so the
   price always sits hard right no matter how long the service name runs */
.line{
  display:flex;align-items:baseline;gap:.6rem;
  padding:.62rem 0;
  border-bottom:1px solid rgba(246,241,244,.09);
}
.line__name{flex:0 1 auto;font-size:1rem;line-height:1.35}
.line__name em{
  display:block;font-style:normal;
  font-size:.76rem;letter-spacing:.11em;text-transform:uppercase;
  opacity:.5;margin-top:.16rem;
}
.line__dots{
  flex:1 1 auto;min-width:1.5rem;
  border-bottom:1px dotted rgba(246,241,244,.28);
  transform:translateY(-.28em);
}
.line__price{
  flex:0 0 auto;white-space:nowrap;
  font-family:var(--display);font-weight:600;
  font-size:1.05rem;letter-spacing:-.01em;
}
.line__price small{
  font-family:var(--body);font-weight:400;
  font-size:.6rem;letter-spacing:.14em;text-transform:uppercase;
  opacity:.45;margin-left:.28rem;
}
/* the second location reads as a variant, not a second product */
.line__alt{margin-left:.55rem;opacity:.62;font-size:.92em}

.menu__fine{
  margin-top:clamp(1.4rem,3vh,2rem);
  max-width:42ch;font-size:.92rem;line-height:1.62;opacity:.6;
}
.menu__fine strong{color:var(--orchid);font-weight:600}
.menu__cta{margin-top:clamp(1.5rem,3.4vh,2.2rem)}

/* ══════════════════ client love ══════════════════
   Real reviews. The lead quote is set large and carries [data-tilt] so it turns
   toward the cursor like the wall quote — the two biggest claims on the site
   (hers, then a client's) behave the same way on purpose. */
.love{
  position:relative;isolation:isolate;
  padding:clamp(4.5rem,11vh,8rem) var(--pad) clamp(5rem,12vh,8.5rem);
  max-width:var(--maxw);margin:0 auto;
}
.say{margin:0}
.say blockquote{margin:0}
.say figcaption{
  margin-top:clamp(.8rem,1.8vh,1.2rem);
  font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--orchid);opacity:.85;
}

/* the lead: a client saying the site's own thesis back to it */
.say--lead{
  margin:clamp(1rem,3vh,2rem) auto clamp(3rem,7vh,5rem);
  text-align:center;
  transform-style:preserve-3d;
}
/* the measure lives HERE, not on the figure: `ch` resolves against the element's
   own font-size, and the figure inherits 16px body type — 26ch on it collapsed
   this to a 210px ribbon. On the <p> the same unit reads the 3.6rem serif. */
.say--lead blockquote p{
  font-family:var(--serif);
  font-size:clamp(1.9rem,4.6vw,3.6rem);
  line-height:1.1;letter-spacing:-.02em;
  text-wrap:balance;
  max-width:24ch;margin-inline:auto;
}
.say--lead em{
  font-style:italic;
  background-image:linear-gradient(100deg,#C13BFF 0%,#FF3DBE 50%,#C13BFF 100%);
  background-size:200% 100%;
  -webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;
  animation:thesisflow 9s linear infinite;
}
.say--lead figcaption{font-size:.74rem}

/* There are FOUR cards, so only 1, 2 or 4 columns are allowed — `auto-fit` was
   landing on 3 at laptop widths (the grid is 1130px inside --maxw + --pad, and
   4×17rem + gaps needs 1178px), which stranded the last card alone beside two
   empty slots. Explicit breakpoints instead. Revisit the counts if she sends a
   fifth review: an odd number orphans at 2-up the same way. */
.love__grid{
  display:grid;gap:clamp(1rem,2.4vw,1.9rem);
  grid-template-columns:1fr;
  align-items:start;
}
@media (min-width:34rem){.love__grid{grid-template-columns:repeat(2,1fr)}}
@media (min-width:100rem){.love__grid{grid-template-columns:repeat(4,1fr)}}
.love__grid .say{
  padding:clamp(1.4rem,3vw,2.1rem);
  border:1px solid rgba(246,241,244,.13);
  border-radius:4px;
  background:linear-gradient(155deg,rgba(246,241,244,.05),rgba(246,241,244,.015));
  transition:border-color .4s var(--ease),transform .4s var(--ease);
}
.love__grid .say:hover{border-color:rgba(255,61,190,.4);transform:translateY(-4px)}
.love__grid blockquote p{
  font-family:var(--serif);
  font-size:clamp(1.05rem,1.5vw,1.28rem);
  line-height:1.45;opacity:.9;
}
.love__grid em{font-style:italic;color:var(--orchid)}
/* stagger alternate cards so the row does not read as a flat strip — but only
   once there is more than one column, since in a single stack it just reads as
   an inconsistent gap */
@media (min-width:34rem){
  .love__grid .say:nth-child(even){transform:translateY(clamp(.8rem,2vh,1.6rem))}
  .love__grid .say:nth-child(even):hover{transform:translateY(calc(clamp(.8rem,2vh,1.6rem) - 4px))}
}

/* ══════════════════ known for ══════════════════
   Her two signatures, each given its own labelled row. Photos run larger than
   the reel's because this is the section she asked for by name — the point is
   that these read as a portfolio, not as filler. */
.known{
  position:relative;isolation:isolate;
  padding:clamp(4.5rem,11vh,8rem) var(--pad) clamp(5rem,12vh,8.5rem);
  max-width:var(--maxw);margin:0 auto;
}
.known__group{margin-top:clamp(2.5rem,6vh,4.25rem)}
.known__head{
  display:flex;align-items:baseline;gap:clamp(.9rem,2.5vw,2rem);flex-wrap:wrap;
  padding-bottom:clamp(.9rem,2vh,1.3rem);
  border-bottom:1px solid rgba(246,241,244,.14);
  margin-bottom:clamp(1.2rem,3vh,2rem);
}
.known__label{
  font-family:var(--serif);font-style:italic;font-weight:400;
  font-size:clamp(1.7rem,3.2vw,2.6rem);
  color:var(--orchid);line-height:1;
}
.known__note{flex:1 1 22ch;min-width:0;font-size:.96rem;line-height:1.6;opacity:.55}

/* rows size themselves from how many photos they hold, so adding a third mullet
   needs no CSS change — just the extra <figure> and data-count */
.known__row{display:grid;gap:clamp(.7rem,1.5vw,1.15rem)}
.known__row[data-count="2"]{grid-template-columns:repeat(2,1fr)}
.known__row[data-count="3"]{grid-template-columns:repeat(3,1fr)}
.known__row[data-count="4"]{grid-template-columns:repeat(4,1fr)}
/* SIX goes 3×2, not 6-across: six columns inside --maxw is ~172px a tile, which
   is smaller than the reel's thumbnails — the wrong direction for the one
   section she asked for by name. Three columns keeps them showcase-sized. */
.known__row[data-count="6"]{grid-template-columns:repeat(3,1fr)}
/* eight -> 4x2; the row keeps showcase-sized tiles instead of a thin strip */
.known__row[data-count="7"],
.known__row[data-count="8"]{grid-template-columns:repeat(4,1fr)}
.known .tile{
  margin:0;border-radius:6px;overflow:hidden;cursor:pointer;
  box-shadow:0 24px 50px -28px rgba(0,0,0,.8);
  transition:transform .5s var(--ease),box-shadow .5s var(--ease),filter .5s var(--ease);
  filter:brightness(.92);
}
.known .tile img{display:block;width:100%;aspect-ratio:3/4;object-fit:cover}
.known .tile:hover{transform:translateY(-8px);filter:brightness(1);box-shadow:0 34px 62px -26px rgba(0,0,0,.9)}

@media (max-width:860px){
  .known__row[data-count="3"],.known__row[data-count="4"],
  .known__row[data-count="6"],.known__row[data-count="7"],
  .known__row[data-count="8"]{grid-template-columns:repeat(2,1fr)}
}

/* ══════════════════ the blog ══════════════════
   Sits on the dark base after Hair School, so that section's existing violet
   horizon band still blends bone -> dark exactly as it did into the FAQ.
   Posts expand in place with <details> — no routing, no build step, and it
   still works with JS off, which is the whole reason not to use a script. */
.journal{
  position:relative;isolation:isolate;
  padding:clamp(5.5rem,13vh,9rem) var(--pad) clamp(6rem,14vh,9.5rem);
  max-width:var(--maxw);margin:0 auto;
}
.journal__list{
  display:grid;gap:clamp(1.6rem,3.4vw,2.75rem);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,20rem),1fr));
}
.post{
  padding-top:1.15rem;
  border-top:2px solid rgba(246,241,244,.22);
}
.post__meta{
  display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;
  margin-bottom:.9rem;
  font-size:.7rem;letter-spacing:.18em;text-transform:uppercase;
}
.post__tag{
  padding:.3rem .7rem;border-radius:999px;
  background:linear-gradient(120deg,rgba(255,61,190,.22),rgba(154,43,240,.22));
  border:1px solid rgba(255,61,190,.32);
  color:#FFA1E1;
}
.post__meta time{opacity:.5}
.post__title{
  font-family:var(--display);font-weight:600;
  font-size:clamp(1.35rem,2.2vw,1.85rem);
  line-height:1.15;letter-spacing:-.02em;
  margin-bottom:.7rem;
}
.post__excerpt{font-size:1rem;line-height:1.62;opacity:.72}

/* the disclosure. summary::marker is removed in favour of a drawn caret so it
   can carry the brand colour and animate. */
.post__more{margin-top:1.1rem}
.post__more > summary{
  display:inline-flex;align-items:center;gap:.55rem;
  cursor:pointer;list-style:none;
  font-size:.74rem;letter-spacing:.16em;text-transform:uppercase;font-weight:600;
  color:var(--orchid);
  transition:color .25s var(--ease);
}
.post__more > summary::-webkit-details-marker{display:none}
.post__more > summary::after{
  content:"";width:.42rem;height:.42rem;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:rotate(45deg) translateY(-2px);
  transition:transform .25s var(--ease);
}
.post__more[open] > summary::after{transform:rotate(-135deg) translateY(-2px)}
.post__more > summary:hover{color:#FFA1E1}
.post__full{padding-top:1rem}
.post__full p{margin-bottom:.85rem;font-size:.98rem;line-height:1.68;opacity:.78}
.post__full p:last-child{margin-bottom:0}
.post__full em{color:var(--orchid);font-style:normal;font-weight:600}

/* ══════════════════ lightbox ══════════════════ */
.lb{
  border:0;padding:0;max-width:100vw;max-height:100vh;width:100%;height:100%;
  background:rgba(12,9,15,.96);color:var(--bone);
}
.lb::backdrop{background:rgba(12,9,15,.92)}
.lb__fig{
  margin:0;height:100%;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1.25rem;padding:clamp(2.5rem,7vh,4.5rem) clamp(3.5rem,8vw,6rem);
}
.lb__fig img{
  max-width:100%;max-height:78vh;width:auto;
  object-fit:contain;border-radius:3px;
}
.lb__cap{display:flex;align-items:baseline;gap:.9rem;flex-wrap:wrap;justify-content:center}
.lb__service{font-size:.95rem;letter-spacing:.03em}
.lb__price{
  font-family:var(--display);font-weight:700;font-size:1.15rem;
  color:var(--orchid);font-variant-numeric:tabular-nums;
}
.lb__x{
  position:absolute;top:1rem;right:1.25rem;z-index:2;
  width:2.75rem;height:2.75rem;border-radius:50%;
  display:grid;place-items:center;font-size:1.1rem;
  background:rgba(246,241,244,.1);transition:background .25s;
}
.lb__x:hover{background:rgba(246,241,244,.22)}
.lb__nav{
  position:absolute;top:50%;transform:translateY(-50%);z-index:2;
  width:3rem;height:3rem;border-radius:50%;
  display:grid;place-items:center;font-size:2rem;line-height:0;
  background:rgba(246,241,244,.08);transition:background .25s;
}
.lb__nav:hover{background:rgba(246,241,244,.2)}
.lb__nav--prev{left:.75rem}
.lb__nav--next{right:.75rem}
@media (max-width:560px){
  .lb__fig{padding:4rem 1rem 2rem}
  .lb__nav{width:2.5rem;height:2.5rem;font-size:1.5rem}
}

/* ══════════════════ before & after ══════════════════
   Her request. The AFTER is the layer on TOP, clipped from the LEFT edge to the
   handle — so the finished hair is what you meet, and dragging RIGHT uncovers
   where it started ("the after before the before"). Base CSS parks the handle at
   72%, which means with JS off you still get a legible, labelled split rather
   than a dead image. */
.ba{
  position:relative;isolation:isolate;
  padding:clamp(4.5rem,11vh,8rem) var(--pad) clamp(4rem,10vh,7rem);
  max-width:var(--maxw);margin:0 auto;
}
.ba__feature{margin:clamp(2rem,5vh,3.5rem) auto clamp(3rem,7vh,5rem);max-width:46rem}
/* side by side — same component, same job, so neither should read as secondary */
/* FIVE pairs -> 3 across (3+2). Two across would leave a single orphan on
   the last row. Straight to one column below 1024 so the middle sizes never
   land on the orphaning 2-up.
   Six half-columns rather than three whole ones: each pair spans 2, so the
   widths come out identical to a 3-up, but the last row can start on column 2
   and sit centred instead of hanging off the left edge. */
.ba__grid{
  display:grid;grid-template-columns:repeat(6,1fr);
  gap:clamp(1rem,2.6vw,2.25rem);
  align-items:start;
  margin-top:clamp(2rem,5vh,3.5rem);
}
.ba__grid .ba__feature{max-width:none;margin:0;grid-column:span 2}
/* nudges the 4th in by half a column; the 5th follows it. */
.ba__grid .ba__feature:nth-child(4){grid-column:2 / span 2}
@media (max-width:1024px){
  .ba__grid{grid-template-columns:1fr;gap:clamp(2rem,5vh,3rem)}
  .ba__grid .ba__feature,
  .ba__grid .ba__feature:nth-child(4){grid-column:auto}
}
.ba__compare{
  --split:72%;
  position:relative;overflow:hidden;border-radius:8px;
  aspect-ratio:3/4;
  cursor:ew-resize;
  /* pan-y, not none: a phone must still be able to scroll the page vertically
     with a finger that happens to land on the comparison. */
  touch-action:pan-y;
  user-select:none;-webkit-user-select:none;
  box-shadow:0 30px 70px -30px rgba(0,0,0,.85);
}
.ba__img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
.ba__clip{position:absolute;inset:0;clip-path:inset(0 calc(100% - var(--split)) 0 0)}

.ba__tag{
  position:absolute;top:.9rem;z-index:3;
  padding:.35rem .7rem;border-radius:999px;
  font-size:.62rem;letter-spacing:.18em;text-transform:uppercase;font-weight:600;
  color:var(--bone);background:rgba(23,18,28,.6);
  border:1px solid rgba(246,241,244,.16);
  backdrop-filter:blur(8px);
  pointer-events:none;
}
.ba__tag--a{left:.9rem}
.ba__tag--b{right:.9rem}

.ba__handle{
  position:absolute;top:0;bottom:0;z-index:4;
  left:var(--split);transform:translateX(-50%);
  width:2.75rem;padding:0;border:0;background:none;cursor:ew-resize;
}
.ba__handle::before{        /* the seam */
  content:"";position:absolute;top:0;bottom:0;left:50%;width:2px;margin-left:-1px;
  background:linear-gradient(180deg,rgba(255,255,255,.4),#fff 18%,#fff 82%,rgba(255,255,255,.4));
}
.ba__handle span{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:2.5rem;height:2.5rem;border-radius:50%;
  background:linear-gradient(135deg,#FFA1E1,#FF3DBE 46%,#9A2BF0);
  box-shadow:0 6px 22px -6px rgba(255,61,190,.85),inset 0 1px 0 rgba(255,255,255,.5);
}
.ba__handle span::before,.ba__handle span::after{
  content:"";position:absolute;top:50%;left:50%;width:.4rem;height:.4rem;
  border-top:2px solid #17121C;border-right:2px solid #17121C;
}
.ba__handle span::before{transform:translate(-50%,-50%) translateX(-.34rem) rotate(-135deg)}
.ba__handle span::after {transform:translate(-50%,-50%) translateX(.34rem)  rotate(45deg)}
.ba__handle:focus-visible span{outline:2px solid var(--orchid);outline-offset:4px}

.ba figcaption{
  margin-top:clamp(.9rem,2vh,1.3rem);text-align:center;
  font-size:clamp(.86rem,1.1vw,.95rem);line-height:1.5;
  color:rgba(246,241,244,.62);
}
.ba figcaption strong{color:var(--bone);font-weight:600}
.ba figcaption span{color:var(--orchid);font-weight:600;white-space:nowrap}

/* Second pair: a plain labelled side-by-side. Those two frames were shot at
   different distances and angles, so a wipe across them reads as two photos
   spliced together rather than one head — the comparison has to be honest about
   that or it undermines the featured one above it. */
.ba__pair{
  display:grid;grid-template-columns:repeat(2,1fr);
  gap:clamp(.6rem,1.5vw,1rem);
  max-width:46rem;margin:0 auto;
}
.ba__pair figcaption{grid-column:1/-1}
.ba__side{position:relative;border-radius:8px;overflow:hidden}
.ba__side img{display:block;width:100%;aspect-ratio:3/4;object-fit:cover}
.ba__side .ba__tag{top:.7rem;left:.7rem;right:auto}

/* ══════════════════ hero: full-bleed studio spotlight ══════════════════
   Modelled on the reference page: two registered frames stacked full-bleed, the
   second revealed inside a soft radial mask that eases toward the pointer.
   The frames are shot with the subject in the RIGHT third against pure white,
   which is the whole reason black copy stays legible on the left over a
   full-bleed image — no scrim, no overlay, no card. */
.hero__reveal{
  position:absolute;inset:0;z-index:1;margin:0;
  overflow:hidden;pointer-events:none;
}
.hero__rev{
  position:absolute;inset:0;
  width:100%;height:100%;
  object-fit:cover;object-position:72% center;
  display:block;
}
.hero__rev--b{
  /* JS paints the radial-gradient mask. Until it does — and on touch, and with
     JS off — this layer stays hidden and the visitor simply sees frame A.
     Never a broken half-state. */
  opacity:0;
  -webkit-mask-size:100% 100%;mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
}
.hero--reveal-on .hero__rev--b{opacity:1}

/* The wordmark rides OVER the photo, not under it: frame A is opaque white on
   the left, so a marquee behind it would simply be invisible. */
.hero__marquee{z-index:2}
.hero__body{z-index:4}

@media (max-width:940px){
  /* A 16:9 frame cover-cropped into a tall phone hero shows almost nothing but
     hair, and the copy would sit on top of it. Push the crop hard to the white
     side and lay a soft white wash under the text. */
  /* Centre HER on the screen, not the file. The frame is 16:9 and the phone
     hero is tall, so cover crops the width hard; `center` would centre the
     IMAGE, whose middle is empty backdrop, and shove the subject off the right
     edge. Measured: she spans 56-93% of the frame, centre 74.5%. */
  .hero__rev{object-position:74.5% center}
  .hero__reveal::after{
    content:"";position:absolute;inset:0;
    background:linear-gradient(180deg,rgba(255,255,255,0) 38%,rgba(255,255,255,.86) 72%,#fff 100%);
  }
}

/* The hero copy is now a single left-hand column over the frame's white third,
   so the row must pack to the START. With space-between and one child it was
   fine, but the creed rejoining the column made it two and threw the block to
   the right, straight onto the hair. */
.hero__body{justify-content:flex-start}
.hero__col{max-width:min(44ch,46vw)}
.hero__creed{
  flex:none;max-width:34ch;text-align:left;
  margin-top:clamp(1.1rem,2.6vh,1.7rem);
}
.hero__creed p{font-size:clamp(1.05rem,1.5vw,1.4rem)}
@media (min-width:941px){ .hero__creed{display:block} }
@media (max-width:940px){
  /* 46vw is a desktop measure — on a 393px phone it squeezed the column to
     181px and clipped the CTA label to "START YOUR NEW". */
  .hero__col{max-width:none}
  .hero__creed{max-width:none}
}

/* ── touch devices: sweep between the two frames automatically ──
   There is no cursor to drive the spotlight, so the reveal would simply never
   happen. Instead the before/after wipes back and forth on a loop, with a
   travelling seam so it reads as one image being uncovered rather than two
   images cross-fading. Holds at each end so both states are actually legible. */
@media (hover:none),(pointer:coarse),(max-width:940px){
  .hero__rev--b{
    opacity:1;
    -webkit-mask-image:none;mask-image:none;
    /* 7.2s delay: the intro owns the first 7s. Without it the loop is already
       mid-cycle when .hero-intro drops and the handover visibly jumps. Its 0%
       keyframe fully clips the before, same as the intro's end state, so the
       two meet with no visual change. */
    animation:heroSweep 8s cubic-bezier(.65,0,.35,1) 7.2s infinite both;
  }
}
/* frame B is the BEFORE, so inset(0 100% 0 0) hides it and the AFTER shows */
@keyframes heroSweep{
  0%,10%   {clip-path:inset(0 100% 0 0)}
  45%,55%  {clip-path:inset(0 0 0 0)}
  90%,100% {clip-path:inset(0 100% 0 0)}
}
@keyframes heroIntroSeam{
  0%,18%{left:0;opacity:0}
  30%   {opacity:1}
  88%   {opacity:1}
  100%  {left:100%;opacity:0}
}
@keyframes heroSeam{
  0%,10%   {left:0;opacity:0}
  20%      {opacity:1}
  45%,55%  {left:100%;opacity:1}
  80%      {opacity:1}
  90%,100% {left:0;opacity:0}
}
/* a loop the visitor cannot stop is exactly what reduced-motion is for */
@media (prefers-reduced-motion:reduce){
  .hero__rev--b{animation:none;opacity:0}
  .hero__seam{animation:none;display:none}
}

/* tells the visitor the hero is interactive — it is not discoverable otherwise */
.hero__hint{
  display:flex;align-items:center;gap:.5rem;
  margin:clamp(1rem,2.4vh,1.5rem) 0 0;
  font-size:.62rem;letter-spacing:.2em;text-transform:uppercase;
  color:rgba(23,18,28,.38);
}
.hero__hint span{
  width:6px;height:6px;border-radius:50%;background:var(--orchid);
  box-shadow:0 0 0 0 rgba(255,61,190,.6);
  animation:pip 2.6s ease-out infinite;flex-shrink:0;
}
/* touch gets the automatic sweep, so "hover" would be a lie there */
@media (hover:none),(pointer:coarse),(max-width:940px){ .hero__hint{display:none} }

/* second pair is now a swipe too — same component, slightly smaller so the
   first one still reads as the featured transformation */
/* was sized down when it sat underneath; in the grid both are equal */
.ba__feature--second{max-width:none;margin-top:0}

/* the lookbook hint speaks the right verb for the input device */
.reel__hint--touch{display:none}
@media (hover:none),(pointer:coarse),(max-width:940px){
  .reel__hint--fine{display:none}
  .reel__hint--touch{display:inline}
}

/* "Not ready to commit?" — a soft second door next to the before/afters, in the
   section's own voice rather than another loud pill button */
.ba__consult{
  margin:clamp(.6rem,1.6vh,1rem) 0 0;
  font-size:clamp(.9rem,1.15vw,1rem);
  color:rgba(246,241,244,.62);
}
.ba__consult a{
  color:var(--orchid);text-decoration:none;font-weight:600;
  border-bottom:1px solid rgba(255,61,190,.35);
  padding-bottom:1px;
  transition:color .25s var(--ease),border-color .25s var(--ease);
}
.ba__consult a:hover{color:#fff;border-color:#fff}

/* "Book an appointment" needs 176px; a 360px phone bar has 117px for it once the
   wordmark and burger are placed. Short label below 560px — the aria-label keeps
   the full wording for screen readers. */
.btn__short{display:none}
@media (max-width:560px){
  .btn__full{display:none}
  .btn__short{display:inline}
}

/* ── intro: the transformation swipes, THEN the pointer takes over ──
   The hero opens holding on the BEFORE, a wipe clears it left-to-right to
   reveal her work, and from then on the cursor spotlight is the control.
   Frame B is the BEFORE, so inset(0 0 0 0) shows it and inset(0 0 0 100%)
   clears it — the end state matches the resting CSS exactly, so nothing moves
   when .hero-intro is dropped. */
@keyframes heroIntroSwipe{
  0%,18%   {clip-path:inset(0 0 0 0)}
  100%     {clip-path:inset(0 0 0 100%)}
}
.hero-intro .hero__rev--b{
  opacity:1;
  -webkit-mask-image:none;mask-image:none;
  animation:heroIntroSwipe 2.6s var(--ease) 3.6s both;
}


/* ── the wipe seam is a REAL element, not a pseudo ──
   .hero__reveal::after is already the mobile white wash that keeps the hero copy
   readable over the photo. Reusing it for the seam wiped the gradient out, and
   because the wash sets inset:0 the "2px" seam inherited left:0/right:0 and
   painted full-width. Three things needed a pseudo and only two exist. */
.hero__seam{
  position:absolute;top:0;bottom:0;z-index:5;
  width:2px;margin-left:-1px;left:0;
  display:none;pointer-events:none;
  background:linear-gradient(180deg,rgba(255,255,255,.4),#fff 18%,#fff 82%,rgba(255,255,255,.4));
  box-shadow:0 0 16px rgba(23,18,28,.3);
}
/* intro owns it first — 2 classes, so this beats the mobile loop rule below */
.hero-intro .hero__seam{display:block;animation:heroIntroSeam 2.6s var(--ease) 3.6s both}
/* mobile: after the intro, it rides the back-and-forth loop */
@media (hover:none),(pointer:coarse),(max-width:940px){
  .hero__seam{display:block;animation:heroSeam 8s cubic-bezier(.65,0,.35,1) 7.2s infinite both}
}
/* pointer takes over on desktop: no seam, the spotlight is the control */
.hero--reveal-on .hero__seam{display:none;animation:none}
@media (prefers-reduced-motion:reduce){ .hero__seam{display:none} }

/* phones: narrower frames so the ones either side are actually on screen */
@media (max-width:720px){
  .reel .tile{width:min(56vw,13rem)}
}
