/* ==========================================================================
   Ballet by Siri
   --------------------------------------------------------------------------
   TOP: the film, pinned while scroll drives its playhead; the blend-mode
   title; the living rose-to-apricot dissolve. (Approved, do not touch.)

   UNDERNEATH: tbp.studio's grammar, measured from the live site: warm
   off-white ground, Inter Tight at weight 300 in place of their KMR
   Melange, their electric blue accent played in Siri's rose. Then a panel
   of moving pool-water colour, a CTA band, and the footer wordmark on its
   own flowing field.
   ========================================================================== */

:root{
  --rose:        #a8515e;
  /* what a chosen time wears, and the back control: a frosted tab, and
     WHITE — no teal in it */
  --pick:        rgba(255, 255, 255, .62);
  --rose-deep:   #8f4250;
  --apricot:     #f0bd9d;
  --paper:       #f7f6f5;   /* tbp's ground, measured rgb(247,246,245) */
  --ink:         #000;
  --ink-30:      rgba(0, 0, 0, .3);
  --hairline:    rgba(0, 0, 0, .18);
  --steel:       #c2a9ae;   /* tbp's footer steel, shifted to Siri's hue */
  --off:         #f7f6f5;

  --font:  'Inter Tight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  /* THE handwritten face, chosen 5 Aug after five rounds: it sets the
     page names, the four corner names, the sign-off and the reviewers.
     --scriptscale is the one knob for all of them at once; every script
     size is a calc() off it. */
  --script: 'Parisienne', cursive;
  --scriptscale: .82;
  --serif: 'Instrument Serif', 'Times New Roman', serif;

  /* the measured tbp reveal curve (Webflow outQuart, 1s) */
  --outquart: cubic-bezier(.165, .84, .44, 1);
  --inout:    cubic-bezier(.42, 0, .58, 1);
  /* the accordion's easing. JS eases the colour on the same curve, so
     the tone and the box arrive together — keep the two in step. */
  --acc:      cubic-bezier(.65, 0, .35, 1);
  /* the long, unhurried curve the page name travels on */
  --glide:    cubic-bezier(.22, .8, .24, 1);
  /* the unhurried curve everything on the card fades on, and how long
     it takes. main.js reads --pagefade so its timings cannot drift. */
  --gentle:   cubic-bezier(.4, 0, .2, 1);
  --pagefade: 1.35s;

  --gut: 24px;               /* tbp page gutter */
  --hp: 3.5vw;               /* the margin round the film card */

  /* the introduction paragraph's fitted size, republished by
     fitStatement() in main.js; this only stands in until the fit runs */
  --stmt: clamp(20px, 5.6vw, 48px);
  --markh: 90px;             /* the fitted wordmark's height, from main.js */


  /* Scroll maths, set from main.js. */
  --markout: 0;              /* 0..1, the wordmark leaving, from the scroll */

  /* PLAIN STARTING VALUES FOR EVERY REGISTERED PROPERTY BELOW.
     @property is Safari 16.4+ (March 2023). WITHOUT it those
     properties have NO value at all, so `--ff:var(--fold)` is invalid,
     `--h1` is invalid, and `.panel{ height:var(--h1) }` falls back to
     auto — which, with every child absolutely positioned, is ZERO.
     The result was a COMPLETELY WHITE page that still scrolled its
     full length, because .introstage's own height is a plain value.
     Reproduced exactly on an iPad profile with the @property rules
     stripped: panel 753x0, 100% white, document height unchanged.
     That is one visitor's iPad on iPadOS 15 or 16.0-16.3 — including
     every iPad that cannot update past 15 (Air 2, mini 4, 5th gen).
     These declarations cost modern browsers nothing: @property still
     registers the types for smooth interpolation, and animations and
     JS-set values both override a declared value as they always did. */
  --fold: 0;
  --fogging: 0;
  --stream: 0;
  --o1: 1;
  --o2: 0;
  --o3: 0;
  --o4: 0;
  --hold: 60;
  --copyrun: 150;
  --intropin: 105;
  --morphrun: 130;
}

*, *::before, *::after{ margin:0; padding:0; box-sizing:border-box; }

[hidden]{ display:none !important; }

html{
  background:#fff;   /* what shows past the ends of the page */
  -webkit-text-size-adjust:100%;
}

body{
  font-family:var(--font);
  font-weight:300;
  font-size:16px;
  line-height:1.25;
  color:var(--ink);
  background:#fff;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

::selection{ background:var(--apricot); color:var(--ink); }
:focus-visible{ outline:2px solid var(--rose); outline-offset:2px; }

button{
  font:inherit; letter-spacing:inherit; color:inherit;
  background:none; border:0; cursor:pointer; touch-action:manipulation;
  /* iOS paints a grey box over any tapped button by default. It has no
     place on top of the film. */
  -webkit-tap-highlight-color:transparent;
  -webkit-user-select:none; user-select:none;
  appearance:none; -webkit-appearance:none;
}
button:focus{ outline:none; }
a{ color:inherit; text-decoration:none; }
img{ display:block; max-width:100%; }

.acc{ color:var(--rose); }

/* ------------------------------------------------------------------ sheet */

.sheet{
  position:relative; z-index:2;
  background:none;
}

/* ============ THE INTRODUCTION, WHICH BECOMES THE CARDS =============
   One sticky stage holds four cards. The panel that carries the words
   is the same element that folds down into the first of them, so the
   colour never restarts: the words fade, white arrives underneath, the
   panel shrinks into its slot, and the other three travel in.

   AFTERWARDS THEY WORK AS AN ACCORDION: exactly one is open (tall, on
   the deep teal), the rest are closed (short, on the lit teal), and
   tapping a closed one hands the open state over.

   TWO NUMBERS DRIVE EVERYTHING, and they must not be the same number:
     --fold  0 to 1, written by the SCROLL, for the morph
     --o1..4 0 or 1, written on TAP and eased by a transition
   Every box is plain calc() off those, which is the whole point — an
   animation and a transition cannot share a property (the animation
   always wins), so the scroll owns one variable, the tap owns another,
   and neither touches `top`, `height` or `inset` directly. */

@property --markout{ syntax:'<number>'; inherits:true; initial-value:0; }
@property --fogging{ syntax:'<number>'; inherits:true; initial-value:0; }
@property --stream { syntax:'<number>'; inherits:true; initial-value:0; }
@property --cin { syntax:'<number>'; inherits:false; initial-value:1; }
@property --cout{ syntax:'<number>'; inherits:false; initial-value:1; }
@property --fold{ syntax:'<number>'; inherits:true; initial-value:0; }
@property --o1  { syntax:'<number>'; inherits:true; initial-value:1; }
@property --o2  { syntax:'<number>'; inherits:true; initial-value:0; }
@property --o3  { syntax:'<number>'; inherits:true; initial-value:0; }
@property --o4  { syntax:'<number>'; inherits:true; initial-value:0; }
/* registered as lengths so getComputedStyle hands JS real pixels: the
   canvases need the box they are ABOUT to become, not the one they are
   leaving, or the field smears for the length of the transition */
@property --p   { syntax:'<length>'; inherits:true; initial-value:0px; }
@property --gv  { syntax:'<length>'; inherits:true; initial-value:0px; }
@property --ch  { syntax:'<length>'; inherits:true; initial-value:0px; }
@property --sh  { syntax:'<length>'; inherits:true; initial-value:0px; }

.introwrap{ position:relative; }


.introstage{
  position:sticky; top:0;
  height:100vh; height:100svh;
  overflow:clip;                 /* the arriving cards fly in from outside */
  isolation:isolate;             /* the words blend with the colour only */
  background:#fff;

  /* Four cards down the screen on one rhythm: --p is the margin round
     the group, --gv the gap between them, the open one takes half the
     height and the three closed ones share the rest. The min()s only
     bind on wide, short screens, where a vw padding would otherwise
     grow until the label no longer fits the closed cards. */
  --p:6vw;
  --gv:calc(var(--p) / 2);
  /* the closed cards are 30% slimmer than an even split of the half
     screen the open one used to take, and the open one absorbs every
     pixel that frees up */
  --shbase:calc((50svh - 2 * var(--p) - 3 * var(--gv)) / 3);
  --sh:calc(var(--shbase) * .7);
  --ch:calc(100svh - 2 * var(--p) - 3 * var(--gv) - 3 * var(--sh));
  /* the same inside every card, and it is the CLOSED ones that set the
     ceiling: enough for the label with a little air either side */
  --cardpad:min(7vw, calc((var(--sh) - var(--stmt)) * .45));
  /* a closed card's label sits a touch smaller than the open one's, and
     grows into full size as the card opens */
  --shutlab:.88;
  /* set at exactly the size of the introduction's body copy, and never
     taller than a closed card can hold */
  --cardlab:min(var(--stmt), calc(var(--sh) - 2 * var(--cardpad)));

  /* the morph, cut into the moments it used to run as separate ranges */
  /* the whole compression is the fold now: it used to be one slice of a
     longer morph, and that old .08-.44 window was making the wordmark
     step down while the card was still closing */
  --ff:var(--fold);
  --lab-in:clamp(0, calc((var(--fold) - .34) / .16), 1);
  --in2:clamp(0, calc((var(--fold) - .50) / .24), 1);  /* and the three   */
  --in3:clamp(0, calc((var(--fold) - .58) / .24), 1);  /* arriving, one   */
  --in4:clamp(0, calc((var(--fold) - .66) / .24), 1);  /* after another   */

  /* the group's frame, and the closed height, both grow with the fold,
     so the open card simply takes whatever is left: one screen at the
     start, --ch by the end */
  /* it begins exactly where the film card was, and closes to the card
     frame as it folds — so the swap is a change of contents, not a new
     box arriving */
  --pf:var(--hp);            /* the frame never moves; only the height */
  --bandpad:6vw;             /* above and below the wordmark in the band */
  /* The compression happens in two parts. First the card closes down
     around the wordmark while the film fogs; then, once it is a band at
     the top, the wordmark steps down 20% and the band closes the rest of
     the way to meet it. That last stop is its resting place. */
  /* ONE continuous move. The card closes, the film goes to glass and the
     wordmark shrinks all together and all the way, and the three of them
     arrive at the same moment — the band's resting place. It was in two
     stages, with the type stepping down only after the band had landed;
     he wanted the reduction constant the whole way instead. */
  --fogging:var(--ff);
  --markscale:calc(1 - var(--ff) * .36);   /* ends at .64 of full */
  --bandh:calc(var(--markh) * var(--markscale) + 2 * var(--bandpad));
  --fullh:calc(100svh - 2 * var(--hp));
  --gf:calc(var(--gv) * var(--ff));
  --sf:calc(var(--sh) * var(--ff));
  --cf:calc(100svh - 2 * var(--pf) - 3 * var(--sf) - 3 * var(--gf));

  /* full card, closing on to a band that is always exactly the height
     of the wordmark as it stands plus its padding. The width never
     changes. */
  --h1:calc(var(--fullh) + (var(--bandh) - var(--fullh)) * var(--ff));
  --h2:calc(var(--sf) + (var(--cf) - var(--sf)) * var(--o2));
  --h3:calc(var(--sf) + (var(--cf) - var(--sf)) * var(--o3));
  --h4:calc(var(--sf) + (var(--cf) - var(--sf)) * var(--o4));

  transition:--o1 .62s var(--acc), --o2 .62s var(--acc),
             --o3 .62s var(--acc), --o4 .62s var(--acc);
}
@media (prefers-reduced-motion: reduce){
  .introstage{ transition:none; }
}
/* without scroll-driven animations there is nothing to write --fold, so
   land on the finished layout rather than a panel that never folds */
/* NO SPECIAL LAYOUT FOR BROWSERS WITHOUT SCROLL-DRIVEN ANIMATIONS.
   There used to be a body.nosdt fallback here (and before that a
   :not(.sdt) one) setting --fold:1 — the PARKED accordion's folded
   end-state. On iPhones and iPads whose Safari lacks scroll timelines
   it crushed the whole site into a strip at the top of a white page:
   the second wave of live "condensed at the top" reports, photographed
   from a visitor's phone on 8 Aug. In the current design the right
   answer for those browsers is NOTHING SPECIAL: the film full screen,
   and the time-based auto-fog (a plain CSS animation, no timeline
   needed) brings the fog and the names after the first loop. */

/* every card: the same box maths, differing only in which slot it sits
   in and which of the four open-values is its own */
.card{
  position:absolute;
  left:var(--pf); right:var(--pf);
  --r:16px;
  overflow:clip;
  border-radius:var(--r);
  /* SAFARI DOES NOT CLIP A COMPOSITED CHILD to the parent's
     border-radius, and the film is composited because it carries a
     blur. It painted straight over the corners and the card looked
     square on his phone while Chrome showed it correctly. clip-path
     does clip composited children, so the rounding is done with that
     and the radius above is only the fallback. */
  clip-path:inset(0 round var(--r));
  isolation:isolate;
}
.panel        { --o:var(--o1); top:var(--pf); height:var(--h1); }
.mcard--reviews{ --o:var(--o2); height:var(--h2);
  top:calc(var(--pf) + var(--h1) + var(--gf)); }
.mcard--lessons{ --o:var(--o3); height:var(--h3);
  top:calc(var(--pf) + var(--h1) + var(--h2) + 2 * var(--gf)); }
.mcard--pricing{ --o:var(--o4); height:var(--h4);
  top:calc(var(--pf) + var(--h1) + var(--h2) + var(--h3) + 3 * var(--gf)); }

/* PARKED, 4 Aug, while the top of the page is settled: the three cards,
   the lit backdrop behind them, and the card label. Delete this rule to
   bring the whole accordion back exactly as it was. */
.mcard, .stagebg, .card__label{ display:none; }

/* the three arrive from alternating sides once the fold has finished */
.mcard--reviews{ transform:translateX(calc((var(--in2) - 1) * 100vw)); }
.mcard--lessons{ transform:translateX(calc((1 - var(--in3)) * 100vw)); }
.mcard--pricing{ transform:translateX(calc((var(--in4) - 1) * 100vw)); }

/* what the cards sit on: in at once as the fold begins, not a lingering
   tint. The white is only what shows before its canvas paints. */
.stagebg{
  position:absolute; inset:0;
  background:#fff;
  opacity:0;
}
.stagebg .panelflow{
  border-radius:0;
  background:linear-gradient(160deg, #abd3d3, #c0e3e5 50%, #d7ecf1);
}
@keyframes stage-white{ from{ opacity:0; } to{ opacity:1; } }
body.sdt .stagebg{ animation:stage-white linear both; animation-timeline:scroll(root block); }

.introhold{ height:calc(var(--hold) * 1svh); }
/* nothing follows the band yet, so the page ends with it */

/* THE PANEL: the introduction, and afterwards the first of the four.
   Its side padding travels with the fold, from the wide measure the
   paragraph is set to down to the card's own. */
.panel{
  display:grid; place-items:center;
  padding-inline:calc(12vw + (var(--cardpad) - 12vw) * var(--ff));
}

/* THE FILM, in the card, fogging out where it stands. Scaled up as it
   goes so the blur always has material at the edges, and gone by the
   time the card has formed — the teal beneath it is what a card is. */
.film{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  /* the first frame as a plain background, so a PICTURE paints before
     any script runs — the poster attribute alone does not render while
     the element has no src, which it does not until applyVariant().
     The playing video covers this completely the moment it starts. */
  background:url(assets/first-frame.jpg) center / cover no-repeat;
  /* It keeps playing all the way into the band, but it goes to glass on
     the way: blurred back until the dancer has no hard definition left,
     while every bit of her colour still comes through. Scaled up a touch
     so the blur has material at the edges instead of fading out. */
  transform:scale(calc(1 + var(--fogging) * .06));
  filter:blur(calc(var(--fogging) * 15px)) saturate(calc(1 - var(--fogging) * .12));
}
/* one number carries the blur AND the corner names: fog-run runs on
   the panel and --fogging inherits down to both */
@keyframes fog-run{ from{ --fogging:0; } to{ --fogging:1; } }
body.sdt .panel{
  animation:fog-run linear both; animation-timeline:scroll(root block);
}

/* WHEN THE PAGE FOGS ITSELF it does NOT scroll: the fog animates on
   time instead, the scroll stays at zero, and the lock that follows
   collapses the runway with nothing to clamp — which is what made the
   wordmark glitch on his iPhone (the clamp from 716 to 0 recomposited
   the blend mid-flight). Later in the file, body.locked still wins. */
@keyframes fog-in{ to{ --fogging:1; } }
body.autofog .panel{ animation:fog-in 1.6s var(--gentle) both; }

/* THE PAGE NAMES, one to a corner, in the serif — the elegant voice
   against the wordmark's caps. They share the wordmark's blend, so the
   film colours them as it moves, and they arrive with the blur. */
.pagename{
  position:absolute; z-index:3;
  font-family:var(--script);
  font-style:normal;
  font-weight:400;
  font-size:calc(43.2px * var(--scriptscale));   /* 48px, 10% down */
  letter-spacing:.01em;
  /* ROOM FOR THE INK. A script face's glyphs run to about 1.45em tall
     and their swashes past the advance width, and Safari CLIPS a
     <button> at its content box — which is what was cutting the final t
     off Contact and the top off the l in Socials. The box is given
     enough that nothing ever reaches its edge; the negative margins put
     the letters back where they optically sat. */
  line-height:1.6;
  padding:0 .48em;    /* Delafield's swashes are the widest: .24em was
                         still 6.7px short of them at 38px */
  margin:0 -.48em;
  overflow:visible;
  color:#f5f6f4;
  mix-blend-mode:difference;
  opacity:var(--fogging);
  white-space:nowrap;
  /* They only fade — nothing travels any more. The delays are written
     per tag from main.js, which is what makes them leave in order. */
  transition:opacity var(--pagefade) var(--gentle);
}
.pagename.is-going{ opacity:0; pointer-events:none; }

/* the wordmark goes after them, and comes back before them */
.mark{ transition:opacity var(--pagefade) var(--gentle); }
body.page-open .mark{ opacity:0; }

/* THE PAGE'S NAME. A DIRECT CHILD OF THE PANEL, like the corner names,
   which colour correctly on iOS. THE LESSON, learned three times now:
   while the name FADES Safari composites it alone and the blend runs,
   but the moment the fade settles it folds the layer into its parent —
   and if that parent is anything but the panel, the blend flattens to
   plain white ("right colour, then switches white within a second").
   It happened with a wrapper, then again inside the flex stack. So the
   name sits ON the panel; `.titlespace`, inside the stack, reserves its
   slot in the centred flow and placeTitle() pins the name over it. */
.pagetitle{
  position:absolute;
  left:0; right:0; top:24%;          /* placeTitle() overrides top */
  text-align:center;
  font-family:var(--script);
  font-size:calc(84px * var(--scriptscale));
  color:#f5f6f4;
  mix-blend-mode:difference;
  line-height:1.5;                   /* ink room: Delafield overruns its box */
  white-space:nowrap;
  overflow:visible;
  opacity:0; pointer-events:none;
  transition:opacity var(--pagefade) var(--gentle);
}
.pagetitle.is-in{ opacity:1; }
.titlespace{
  flex:0 0 auto;
  height:126px;              /* placeTitle() replaces both of these with
                                the word's real ink height and a gap
                                measured from the NAME's size, not this
                                element's inherited 16px */
  margin-bottom:28px;
}
.titlespace.is-gone{ display:none; }

.pagename--tl{ left:6.5vw;  top:calc(5.2vw + env(safe-area-inset-top)); }
.pagename--tr{ right:6.5vw; top:calc(5.2vw + env(safe-area-inset-top)); }
.pagename--bl{ left:6.5vw;  bottom:5.2vw; }
.pagename--br{ right:6.5vw; bottom:5.2vw; }

/* the glass itself: a breath of white over the film, never over the
   wordmark, which sits in the positive layer above it */
.panel::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background:rgba(255, 255, 255, calc(.14 * var(--fogging)));
}
/* BATTERY SAVER puts a large play glyph over any video it has refused
   to autoplay — squarely over the wordmark. These are the documented
   WebKit pseudo-elements for exactly that overlay; hiding them leaves
   our own still photograph showing cleanly instead. */
.film::-webkit-media-controls-start-playback-button{
  display:none !important;
  -webkit-appearance:none;
}
.film::-webkit-media-controls{ display:none !important; }

/* There used to be `body.is-loading .film{ opacity:0 }` here, hiding
   the element until the video's first frame arrived. That made the
   12MB download gate the ENTIRE picture: on a slow connection the
   panel sat pure white for as long as the video took, poster and all,
   with the wordmark ghost-blending against nothing — the second half
   of the "not loading properly" reports. The poster background above
   paints immediately now, and the video simply appears over it. */

/* PARKED with the cards below. This is the teal the card used to become,
   and it was ALWAYS PAINTED underneath the film: any moment the film did
   not cover the card to the pixel — the phone turning, Safari's bars
   sliding — it showed through, which read as something else playing back
   there. Nothing sits behind the film now. */
.flow{
  position:absolute; inset:0;
  width:100%; height:100%;
  display:none;
}

/* The wordmark's own box, so the panel's wide text padding does not
   squeeze it. NO z-index: a positioned element with one is a stacking
   context, and that traps the wordmark's difference blend so it stops
   reading the film behind it and goes flat white. It sits above the
   film on DOM order alone. */
.markbox{ position:absolute; inset:0; pointer-events:none; }


/* BALLET BY SIRI, on the card, held dead centre and simply fading out
   before the film fogs. It does not travel anywhere. */
.mark{
  position:absolute; left:50%; top:50%; z-index:3;
  transform:translate(-50%, -50%);
  color:#f5f6f4;
  mix-blend-mode:difference;
  font-weight:500;
  font-size:calc(var(--markfs, 11vw) * var(--markscale));
  line-height:1;
  letter-spacing:-.035em;
  text-transform:uppercase;
  white-space:nowrap;
  text-align:center;
  pointer-events:none;
  /* it stays: it rides the compression, centred in the film the whole
     way, and ends centred in the band at the top */
}
.mark--stack .mark__line{
  display:block;
  line-height:.94;
}

/* the photograph over that colour, blurred to fog. Scaled up a touch so
   the blur has material to work with at the edges instead of fading to
   nothing, and gone by the time the panel is a card. */
.fog{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  transform:scale(1.12);
  filter:blur(17px) saturate(.72);
  opacity:calc(.68 * (1 - var(--ff)));
  pointer-events:none;
}

.introstage::after{
  content:""; position:absolute; inset:0; pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  opacity:.07; mix-blend-mode:overlay;
}

/* the words, which fade out as the panel starts to fold */
.panel .wrap{
  padding-inline:0;
  width:100%;
}
/* The fade goes on the words themselves. Putting it on their wrapper
   gives that wrapper a stacking layer, and the blends then work against
   it instead of the colour, which flattens the whole treatment. */
@keyframes words-out{ from{ opacity:1; } to{ opacity:0; } }
/* The closing line has to arrive as well as leave, now that the card it
   sits on is on screen from the very top of the page. Two animations, so
   two ranges — assignMorphRanges writes both.
   THEY MUST NOT BOTH ANIMATE OPACITY: where two animations touch the
   same property the LAST one wins outright, so the exit simply erased
   the entrance. Each drives its own number instead, and the opacity is
   the product. (A wrapper with its own opacity would work too, and is
   the obvious fix, but it would give the line a stacking context and its
   difference blend would stop reading the colour behind it.) */
@keyframes closer-in { from{ --cin:0; }  to{ --cin:1; } }
@keyframes closer-out{ from{ --cout:1; } to{ --cout:0; } }


/* every card's label sits in its top left corner; the arrow sits
   opposite, in the bottom right, and belongs to the OPEN one only */
.card__label{
  position:absolute; z-index:2;
  left:var(--cardpad); right:var(--cardpad);
  text-align:left;
  color:#fff;
  opacity:calc(var(--lab-in) * .92 * (1 - var(--o)));
  /* the introduction's own treatment, from .statement. Now that the
     size matches the body copy, -.03em is the RIGHT tracking: it is
     what that paragraph renders at its own size. (The greeting is the
     odd one out — being 1.75em, it inherits a tracking computed against
     the smaller body size, so it renders looser than it declares.) */
  font-weight:500; line-height:1;
  letter-spacing:-.01em; text-transform:uppercase;
  mix-blend-mode:overlay;
  font-size:calc(var(--cardlab) *
              (var(--shutlab) + (1 - var(--shutlab)) * var(--o)));
  max-width:16ch;
  /* top left while the card is open, centred while it is shut, and
     interpolated between the two because both terms hang off --o */
  top:calc(var(--cardpad) + (100% - 2 * var(--cardpad)) * (1 - var(--o)) / 2);
  transform:translateY(calc(-50% * (1 - var(--o))));
}
/* The OPEN card's label is SOLID white — no blend, nothing of the field
   showing through — while the closed ones keep the softer blended
   white above. It is a SECOND label sitting exactly over that one
   (cloned in main.js), the two crossfading on --o, because a blend mode
   cannot be interpolated. Keep it a SIBLING, not a child: a blended
   parent becomes the backdrop its child blends against. */
.card__label--open{
  color:#fff;
  mix-blend-mode:normal;
  opacity:calc(var(--lab-in) * var(--o));
}

/* the paragraph, set like the hero title and blended into the colour */
.statement{
  color:#fff;
  mix-blend-mode:overlay;
  font-weight:500;
  font-size:5vw;                 /* replaced by the JS fit */
  line-height:1.12;
  letter-spacing:-.03em;
  text-transform:uppercase;
  text-align:right;              /* the body sits ragged right ... */
  text-wrap:balance;
  max-width:min(26ch, 100%);
  will-change:transform;
}
/* ... under the greeting, which is bigger and ranged left */
.statement .ph1{
  display:block;
  text-align:left;
  font-size:1.75em;
  line-height:1;
  margin-bottom:.5em;
}
@media (min-width:821px){
  .statement{ max-width:min(32ch, 100%); }
}

/* the greeting arrives from the left, the body words stream in from the
   right, one after another (ranges per element from main.js) */
@keyframes iv-left { from{ opacity:0; transform:translateX(-26vw); } to{ opacity:1; transform:none; } }
@keyframes iv-right{ from{ opacity:0; transform:translateX(22vw);  } to{ opacity:1; transform:none; } }
/* the per-word entrance belongs to the STREAM option now; see below */

/* the closing line, in the hero title's blend so it separates */
.closer{
  margin-top:.9em;
  color:#f5f6f4;
  mix-blend-mode:difference;
  font-weight:500;
  line-height:1.05;
  letter-spacing:-.03em;
  text-transform:uppercase;
  text-align:left;
  max-width:min(18ch, 100%);
  will-change:transform;
}

/* THE THREE BENEATH: same box maths as the panel, set out above, and
   now the same deep colour too. All they carry here is a fallback for
   the moment before the canvas paints. */
.mcard{
  border:0;
  background:linear-gradient(160deg, #459c9e, #72c0c4 50%, #a5d5e0);
}

.panelflow{
  position:absolute; inset:0;
  width:100%; height:100%;
  border-radius:inherit;
  background:linear-gradient(160deg, #459c9e, #72c0c4 50%, #a5d5e0);
}
.mcard .panelflow{ background:none; }

/* only --fold rides the scroll now; the boxes are calc() off it */
@keyframes fold-run{ from{ --fold:0; } to{ --fold:1; } }
/* NOTE: the timeline is declared beside every `animation` shorthand,
   never in a shared rule of its own. The shorthand RESETS
   animation-timeline to auto, so a less specific rule setting it loses —
   which is exactly how the words' entrance was silently running on the
   document timeline, finishing instantly, and showing the paragraph
   whole from the very top of the page. */
/* NOTHING attaches fold-run: the card does not shrink any more, it
   simply scrolls up whole. Reattach it on .introstage to bring the
   compression back. */

/* the same composition on a wide screen, just a tighter frame */
@media (min-width:821px){
  .introstage{
    --p:2.4vw;
    --cardpad:min(3.2vw, calc((var(--sh) - var(--stmt)) * .45));
  }
}


/* ------------------------------------------------------------ the reveals */
/* tbp's slideInBottom, measured: rise 100px + fade, 1s outQuart. */

/* --------------------------------------------------------------- motion */

@media (prefers-reduced-motion: reduce){
  body.sdt .mark, body.sdt .statement{ animation:none; }
  .pill{ transition:none; }
}


/* ================= HER INTRODUCTION, ON THE WHITE ===================
   It sits below the band, inside the same stage, so the film goes on
   playing above it while it arrives. Dark on white now, not white on
   colour, so it carries no blend: the treatment is the same otherwise —
   the greeting large and ranged left, the body ragged right beneath it,
   the closing line on its own. */

/* PARKED, 4 Aug: the corner menu replaced the letter and the socials
   buttons below the film. Delete this rule to bring them back. */
.copywrap{ display:none; }

.copywrap{ position:relative; }

/* THE SOCIALS. Brand-coloured buttons — deliberately NOT the wordmark's
   blend treatment: Instagram's gradient and WhatsApp's green, each with
   the real glyph in white. They sit at the top left of this stage, so
   they come up from the bottom left as the film leaves and end at the
   top left once the stage has docked. */
.socials{
  position:absolute; z-index:2;
  left:10vw; top:calc(20px + env(safe-area-inset-top));
  display:flex; gap:14px;
}
.social{
  display:grid; place-items:center;
  width:clamp(46px, 12.5vw, 60px);
  aspect-ratio:1;
  border-radius:50%;
  color:#fff;
}
.social svg{ display:block; width:52%; height:auto; }
.social--ig{
  background:radial-gradient(circle at 30% 107%,
    #fdf497 0%, #fdf497 5%, #fd5949 45%, #d6249f 60%, #285aeb 90%);
}
.social--wa{ background:#25d366; }
.copystage{
  position:sticky; top:0;
  height:100vh; height:100svh;
  display:grid; align-content:center;
  background:#fff;
}
.copyhold{ height:calc(var(--copyrun) * 1svh); }
.copy{
  padding-inline:10vw;           /* a good margin, equal on both sides */
  --slice:.28;                   /* how much of the run one word takes */
}
.copy .statement,
.copy .closer{
  /* one treatment for the whole block: sentence case, lighter, a couple
     of steps smaller, all one colour, all one size, all centred */
  color:#111;
  mix-blend-mode:normal;
  opacity:1;
  transform:none;
  animation:none;
  font-weight:400;
  text-transform:none;
  letter-spacing:-.01em;
  line-height:1.28;
  text-align:left;
  max-width:none;
}
.copy .closer{ margin-top:1.1em; }
.copy .statement, .copy .closer{ max-width:34ch; }
/* The greeting is no longer set apart in SIZE — same size, weight and
   colour as everything else — but it still takes its own line, or it
   runs straight into the sentence after it. */
.copy .statement .ph1{
  display:block;
  font-size:inherit;
  line-height:inherit;
  margin-bottom:.7em;
  text-align:inherit;
}

/* ---- how it arrives: a word at a time, from the right ---- */

/* It assembles itself a word at a time, greeting and closing line
   included, on one continuous stagger.

   ONE ANIMATION FOR THE WHOLE BLOCK, NOT ONE PER WORD. Each word used to
   carry its own scroll-driven animation — forty-three of them running at
   once — and Safari quietly dropped a scattering of them, so words all
   through the paragraph simply never arrived while Chrome showed every
   one. Now the scroll writes a single number and each word works out its
   own share of it from its index. One animation, and nothing to drop. */
@keyframes stream-run{ from{ --stream:0; } to{ --stream:1; } }
body.sdt .copy{
  animation:stream-run linear both; animation-timeline:scroll(root block);
}
.copy .w{
  /* how far THIS word has come: its slice of the run, clamped */
  --wp:clamp(0, calc((var(--stream) - var(--i) / var(--n) * (1 - var(--slice)))
                     / var(--slice)), 1);
  display:inline-block;
  opacity:var(--wp);
  transform:translateX(calc((1 - var(--wp)) * 7vw));
}


/* ================== THE PAGES THEMSELVES ============================
   Everything here sits on the blurred film and takes the wordmark's
   blend, so it is legible whatever is behind it and it shifts with her
   as she moves. Set in the wordmark's own family, thin. */

/* THE CENTRED STACK. inset:0 with a flex column, NOT top:50% +
   translateY: a transform is a stacking context and it would trap the
   page name's difference blend. No z-index and no opacity here either,
   for the same reason — the stack paints over the film on DOM order
   alone, and the fades live on the children. */
.pagestack{
  position:absolute; inset:0;
  display:flex; flex-direction:column; justify-content:center;
  /* SYMMETRIC on purpose. The safe-area insets are NOT equal top and
     bottom on a phone, and any difference lands straight on the gap he
     measures. The card is already inset from the screen. */
  padding:9vh 10vw;
  pointer-events:none;
}
@media (min-width:821px){
  /* the panel is most of the screen wide out here; the words hold a
     centred column instead of stretching with it */
  .pagestack{ padding-inline:calc((100% - 520px) / 2); }
}
.pagebody, .booking{
  min-height:0;                 /* or a scrolling child sizes the flex
                                   item from its full content */
  color:#16232a;
  font-family:var(--font);
  font-weight:300;
  font-size:17px;
  line-height:1.55;
  letter-spacing:.002em;
  text-align:center;
}
.booking{
  opacity:0;
  transition:opacity var(--pagefade) var(--gentle);
}
.pagebody.is-in, .booking.is-in{ pointer-events:auto; }
.booking.is-in{ opacity:1; }
/* TWO CLASSES, NOT ONE: is-open runs display, is-in runs the fade. One
   class doing both puts display:none -> block in the same style pass as
   the opacity change, so the transition never runs — the "comes in very
   abruptly" bug. fadeBlock() in main.js adds is-open, waits two frames,
   then is-in; on the way out it drops is-in first and is-open only once
   the fade has finished. */
.pagebody, .booking{ display:none; }
.pagebody.is-open, .booking.is-open{ display:block; }

.pagebody .pane{
  display:none;
  min-height:0;
  opacity:0;
  transition:opacity var(--pagefade) var(--gentle);
}
.pagebody .pane.is-on{ display:block; }
.pagebody.is-in .pane.is-on{ opacity:1; }
.pagebody p + p{ margin-top:.85em; }

/* THE CARD EVERY WORD SITS ON. Frosted over the already-blurred film —
   a second blur — with solid ink on it, so nothing is left to the mercy
   of whatever passage of the film happens to be behind. The page NAME
   and BALLET BY SIRI keep the difference blend; body copy does not. */
.tile{
  padding:2.1em 1.82em;   /* sides 30% up on 1.4, his ask */
  border-radius:22px;
  background:rgba(255, 255, 255, .42);
  -webkit-backdrop-filter:blur(18px) saturate(1.15);
  backdrop-filter:blur(18px) saturate(1.15);
  text-align:center;
}

/* the one action on a page */
.cta{
  margin-top:2.4em;
  padding:.72em 1.5em;
  font-family:var(--font);
  font-weight:400; font-size:15px;
  letter-spacing:.06em; text-transform:uppercase;
  color:#16232a;
  border:1px solid rgba(22, 35, 42, .5);
  border-radius:999px;
}


/* the steps cross-fade rather than cutting */
/* one step at a time, cross-fading */
.booking .step{ display:none; opacity:0; transition:opacity .6s var(--gentle); }
.booking[data-step="when"] .step--when,
.booking[data-step="ask"]  .step--ask,
.booking[data-step="ask2"] .step--ask2,
.booking[data-step="done"] .step--done{ display:block; }
.booking .step.is-in{ opacity:1; }

/* ---- ONE BUTTON SHAPE FOR EVERY CHOICE ------------------------------
   The outlined pill from "Find a time", reused for the times and for
   the questions: he preferred it to the frosted tabs. Chosen = the ink
   filled in, which on a frosted card reads at a glance. */
.pick{
  padding:.6em 1.1em;
  font-family:var(--font); font-weight:400; font-size:14px;
  letter-spacing:.05em;
  color:#16232a;
  background:none;
  border:1px solid rgba(22, 35, 42, .38);
  border-radius:999px;
  transition:background .38s var(--gentle), color .38s var(--gentle),
             border-color .38s var(--gentle);
}
.pick.is-on{
  background:#16232a; color:#f4f6f6; border-color:#16232a;
}

/* ---- the times: three a day, every tab the same size ---- */
.days{ display:block; }

/* the strip of days you slide through */
.dayslide{
  display:flex; gap:6px;
  overflow-x:auto; scrollbar-width:none;
  padding:2px 0 4px;
  scroll-snap-type:x proximity;
}
.dayslide::-webkit-scrollbar{ display:none; }
.daychip{
  flex:0 0 auto; scroll-snap-align:center;
  display:flex; flex-direction:column; align-items:center; gap:1px;
  min-width:52px; padding:.5em .55em .55em;
  font-family:var(--font); color:#16232a;
  border:1px solid rgba(22, 35, 42, .3);
  border-radius:16px;
  transition:background .34s var(--gentle), color .34s var(--gentle),
             border-color .34s var(--gentle);
}
.daychip span{ font-size:9.5px; letter-spacing:.11em; opacity:.7; }
.daychip b{ font-weight:400; font-size:18px; line-height:1.1; }
.daychip.is-on{
  background:#16232a; color:#f4f6f6; border-color:#16232a;
}
.daychip.is-on span{ opacity:.75; }

/* the day it is, then that day's times revealing themselves */
.day__name{
  font-size:11px; letter-spacing:.11em; text-transform:uppercase;
  opacity:.6; margin:1.5em 0 .7em; text-align:center;
}
.daytimes{
  overflow:hidden;
  opacity:1;
  transition:opacity .26s var(--gentle), height .4s var(--gentle);
}
.daytimes.is-turning{ opacity:0; }
.day__times{ display:flex; gap:5px; justify-content:center; flex-wrap:nowrap; }
.day__times button{
  /* four to a row sharing the width equally, so every tab is exactly
     the same size whatever the time reads */
  flex:1 1 0; min-width:0; max-width:88px;
  padding:.55em .2em; font-size:12.5px; letter-spacing:.02em;
  font-variant-numeric:tabular-nums;
}

/* ---- the questions ---- */
.ask{ border:0; margin-bottom:1.5em; text-align:center; }
.ask:last-of-type{ margin-bottom:0; }
.ask legend{
  font-size:11.5px; letter-spacing:.11em; text-transform:uppercase;
  opacity:.6; margin-bottom:.65em; width:100%; text-align:center;
}
/* the note page asks in the same plain sentence voice as the lead on
   the questions page; the FIELDS answer in the site's small caps */
.step--ask2 .ask legend{
  font-family:var(--font);
  font-size:16px; font-weight:300; line-height:1.55;
  letter-spacing:.002em; text-transform:none;
  opacity:1; margin-bottom:.55em;
}
/* THE PROMPT IS IN CAPITALS, WHAT YOU TYPE IS NOT. The capitals are
   written into the placeholder text itself rather than applied with
   text-transform, because a transform on the field would carry over to
   whatever you typed. Typed text is the legend's own face, two steps
   down from its 16px. */
.step--ask2 .ask input,
.step--ask2 .ask textarea{
  font-family:var(--font); font-weight:300;
  font-size:14px; line-height:1.6;
  letter-spacing:.002em; text-transform:none;
}
.step--ask2 .ask ::placeholder{
  font-size:11.5px; letter-spacing:.1em; line-height:1.9;
  opacity:1; color:rgba(22, 35, 42, .42);
}
.opts{ display:flex; flex-wrap:wrap; gap:6px; justify-content:center; }
.ask input, .ask textarea{
  display:block; width:100%;
  padding:.75em 1em;
  font:inherit; font-size:16px; color:inherit;
  background:rgba(255, 255, 255, .55);
  border:1px solid transparent;
  border-radius:14px; resize:none;
  transition:border-color .3s var(--gentle);
}
/* the global focus ring is a hard rose box, which on frosted glass
   looks like an error; the field firms its edge instead */
.ask input:focus, .ask textarea:focus,
.ask input:focus-visible, .ask textarea:focus-visible{
  outline:none;
  border-color:rgba(22, 35, 42, .4);
}
.ask ::placeholder{ color:rgba(22, 35, 42, .38); }
.ask__lead{ margin:.5em 0 1.5em; }
.step--ask2 .ask__when{ margin-bottom:1.5em; }

/* ---- the confirmation ---- */
.step--done .tile{ padding-block:2.9em; }
.done__hand{
  font-family:var(--script);
  font-size:56px; line-height:1.15;
  white-space:nowrap;                /* fitHand() sizes it to the card */
  margin-bottom:.45em;               /* on its own, up the top */
}
/* the confirmation, as a ticket: the day small over the hour large,
   with a torn edge beneath it */
.done--ticket{
  margin:.1em 0 1.1em; padding-bottom:1.1em;
  border-bottom:1px dashed rgba(22, 35, 42, .35);
}
.done__bigday{
  font-size:13px; letter-spacing:.16em; text-transform:uppercase;
  opacity:.75;
}
.done__bigtime{
  font-size:44px; font-weight:300; line-height:1.1; margin-top:.05em;
}
.doneprint{
  margin-top:1.6em;
  font-size:11.5px; letter-spacing:.11em; text-transform:uppercase;
  opacity:.6; line-height:1.7;
}

/* the way back, settled: the long-tailed arrow in the actions\' pill,
   its outline the arrow\'s own colour, on a breath of frost */
.backlink{
  position:absolute; z-index:3;
  left:5.5vw; top:calc(5.2vw + env(safe-area-inset-top));
  display:grid; place-items:center;
  height:34px; padding:0 1em;      /* the same box the Instagram mark has */
  color:#16232a;
  border:0;
  border-radius:999px;
  background:rgba(255, 255, 255, .4);
  -webkit-backdrop-filter:blur(12px) saturate(1.15);
  backdrop-filter:blur(12px) saturate(1.15);
  opacity:0; pointer-events:none;
  transition:opacity var(--pagefade) var(--gentle);
}
.backlink .bk{ display:block; width:24px; }   /* the tail arrow, 10% down */
.backlink.is-in{ opacity:1; pointer-events:auto; }
@media (min-width:821px){
  .backlink{ left:2.4vw; top:2.2vw; }
}

/* her Instagram, top right of the contact page, in its own colours */
.iglink{
  position:absolute; z-index:3;
  right:5.5vw; top:calc(5.2vw + env(safe-area-inset-top));
  display:grid; place-items:center;
  color:#16232a;                    /* the text's own ink, no brand colour */
  opacity:0; pointer-events:none;
  transition:opacity var(--pagefade) var(--gentle);
}
/* the mark alone, no shape around it, standing exactly as tall as the
   back button's pill */
.iglink svg{ display:block; width:34px; height:34px; }
.iglink.is-in{ opacity:1; pointer-events:auto; }
@media (min-width:821px){
  .iglink{ right:2.4vw; top:2.2vw; }
}


/* ---- REVIEWS: they turn themselves over, on a cross-fade ----
   Every review sits in the SAME grid cell, so the block is as tall as
   the longest of them and nothing jumps as they change. */
.revs{
  display:grid; align-items:start;
  touch-action:pan-y;        /* a sideways drag is ours to read */
}
.rev{
  grid-area:1 / 1;
  /* Each card is only as tall as its own words, and they all START at
     the top of the cell — so the gap under "Reviews" is the same
     whichever one is showing. This is deliberately NOT centred like
     every other page: his call, for this page only. The cell stays as
     tall as the longest review, so nothing jumps as they turn over. */
  align-self:start;
  opacity:0; pointer-events:none;
  transition:opacity 1.4s var(--gentle);
}
.rev.is-on{ opacity:1; pointer-events:auto; }
.rev blockquote{ font-size:16px; line-height:1.55; text-align:left; }
.rev figcaption{
  margin-top:.7em;
  font-size:calc(36.5px * var(--scriptscale));   /* the sign-off less 15% */
}

/* ---- LESSONS: the rates as a small chart ---- */
.grid__h sup{ font-size:1em; vertical-align:baseline; opacity:.7; }
/* one rule right across the chart, not a border per cell: the joins
   between cells show against the film */
/* the footnote, set like the chart's own labels */
.note{
  margin-top:1.3em;
  font-size:10px; letter-spacing:.1em; text-transform:uppercase;
  line-height:1.7; opacity:.72;
}

/* the intro signs off in her own hand */
/* THE SIGNATURE, on intro and on every review. Its middle sits on the
   card's two-thirds mark, measured across the WHOLE card. 38% rather
   than a flat third because the padding is applied inside the tile's
   own side padding, which shifts the middle back by a couple of per
   cent (measured 64.4 at 33%, 66.6 at 38). */
.signoff, .rev figcaption{
  font-family:var(--script);
  font-size:calc(43px * var(--scriptscale)); font-weight:400;
  line-height:1.15; letter-spacing:0; text-transform:none;
  text-align:center;
  padding-left:38%;
  margin-top:.5em;
}


/* Phone: a step down in body copy — he found 18px too big on the card. */
@media (max-width:820px){
  /* the running text on the phone: 15% down from 16px, then 10% back
     up again — the pages' body copy and the reviews with it */
  .pagebody, .booking{ font-size:15px; }
  .rev blockquote{ font-size:15px; }
  .pagetitle{ font-size:calc(74px * var(--scriptscale)); }
  .tile{ padding:1.9em 1.56em; border-radius:20px; }
}

/* SHORT PHONES (iPhone SE and similar, ~667pt tall): the card is the
   full viewport height and nothing on a page scrolls, so a page with
   several paragraphs — About, currently the longest — can run past the
   bottom of the card with nothing to reveal what overflowed. Only the
   vertical rhythm tightens here; nothing changes in width, wording, or
   on a normal-height phone. */
@media (max-height:700px){
  .pagestack{ padding-block:6vh; }
  .pagebody, .booking{ line-height:1.4; }
  .pagebody p + p{ margin-top:.55em; }
  .tile{ padding-block:1.5em; }
}

/* the quiet line under a card's main point */
/* NO SCROLLBAR ANYWHERE. It is his standing rule and it keeps coming
   back when a block gets rewritten — it belongs here, at the root. */
html{ scrollbar-width:none; }
html::-webkit-scrollbar{ display:none; }

/* ONCE THE FILM HAS FOGGED, THAT IS WHERE IT STAYS. The page cannot be
   scrolled back up to a clear film, and no page scrolls at all: only a
   refresh returns to the start.

   THE RUNWAY IS TAKEN AWAY, NOT THE SCROLLING. `overflow:hidden` on the
   root is the obvious way to do this and it is WRONG ON iOS: Safari
   makes the body its own scroll container, the sticky stage loses the
   scrollport it was sticking to, and the film travels off the top of
   the screen leaving white. Collapsing the spacer instead leaves the
   document exactly one screen tall — there is simply nothing left to
   scroll, and the stage keeps sticking to the same pixels it was
   already on, so the collapse is invisible.

   --fogging must be frozen HERE at the same time: with no scrollable
   distance left, the scroll timeline would otherwise wind the fog back
   to nothing. */
body.locked .introhold{ height:0; }
body.locked .panel{ animation:none; --fogging:1; }

@media (max-width:820px){
  /* "On request" reads better stacked in a column this narrow */
  .grid__c--ask span{ display:block; }
}

/* the signature's line box carries a script face's descender space, so
   the card's equal padding LOOKS bigger below it; trimmed to match */
.pane[data-for="intro"] .tile{ padding-bottom:1.2em; }

/* THE PROSE RANGES LEFT. Only the running text: the chart, its labels,
   the footnote and every action stay centred. */
.pane[data-for="intro"]   .tile p:not(.signoff),
.pane[data-for="contact"] .tile p,
.pane[data-for="lessons"] .tile p:not([class]){ text-align:left; }


/* ---- LESSONS: the price in her own hand, the pound sign lifted ---- */
.pnum{
  display:flex; align-items:center; justify-content:space-between;
  gap:1em; padding:.35em .2em;
}
.pnum:first-of-type{ margin-top:2.2em; }   /* room off the paragraph */
.pnum + .pnum{ margin-top:.9em; }
.pnum b{
  font-family:var(--font);
  font-size:46px; font-weight:200; line-height:1;
  letter-spacing:-.02em;
}
.pnum b i{
  font-style:normal;
  font-size:.42em;              /* small, and up in the top left */
  font-weight:300;
  vertical-align:.95em;
  margin-right:.04em;
}
.pnum span{
  text-align:right;
  font-size:10.5px; letter-spacing:.11em; text-transform:uppercase;
  line-height:1.7;
  /* alpha in the COLOUR, not opacity: opacity on the parent would cap
     how solid the line below it can be */
  color:rgba(22, 35, 42, .7);
}
/* only where the lesson happens is heavier; "per hour" is unchanged */
.pnum em{
  font-style:normal;
  font-size:12px; font-weight:400;
  color:rgba(22, 35, 42, .95);
}
.pline--soft{
  text-align:center;
  font-size:10.5px; letter-spacing:.11em; text-transform:uppercase;
  opacity:.7; margin-top:1.2em;
}

/* ---- THE QUESTIONS: one at a time, and they change gently ---- */
.step--ask .ask{ display:none; }        /* the fieldsets are the data */
.askalt{
  overflow:hidden;
  opacity:1;
  transition:opacity .42s var(--gentle), height .45s var(--gentle);
}
.askalt.is-turning{ opacity:0; }
/* ONE rail across all five steps: the times, the three questions, and
   the name and email. paintRail() in main.js fills every copy. */
.rail5{
  display:flex; gap:6px; justify-content:center;
  margin-bottom:1.5em;
}
.rail5 span{
  width:20px; height:2px; border-radius:2px;
  background:rgba(22, 35, 42, .22);
  transition:background .34s var(--gentle);
}
.rail5 span.is-done{ background:#16232a; }
.wiz__lab{
  font-size:11.5px; letter-spacing:.11em; text-transform:uppercase;
  opacity:.6; margin-bottom:1.1em;
}
.wiz__opts{ display:flex; flex-wrap:wrap; gap:7px; justify-content:center; }
/* the options wear the CTA's voice: caps, tracked, in a rounded pill */
.wiz__opts .pick{
  padding:.7em 1.3em;
  font-size:12px; font-weight:400;
  letter-spacing:.06em; text-transform:uppercase;
}

/* ---- an action fills with ink before it carries you on ---- */
.cta{
  transition:background .32s var(--gentle), color .32s var(--gentle),
             border-color .32s var(--gentle);
}
.cta.is-on{ background:#16232a; color:#f4f6f6; border-color:#16232a; }

/* ---- THE COUNTDOWN round the review card's outline: a faint full
   track, the part elapsed drawn over it, and a sparkle riding the
   leading edge that goes out the moment the outline closes. ---- */
.rev .tile{ position:relative; }
.ring{
  position:absolute; inset:0; width:100%; height:100%;
  pointer-events:none; overflow:visible;
}
.ring path{ fill:none; vector-effect:non-scaling-stroke; }
.ring .base{ stroke:rgba(255, 255, 255, .3); stroke-width:1; }
.ring .arc{ stroke:rgba(255, 255, 255, .85); stroke-width:1.6; }

/* the sparkle: two four-point stars turning against each other, so it
   twinkles rather than reading as a dot sliding round */
.spark{ opacity:0; transition:opacity .45s var(--gentle); }
.spark.is-lit{ opacity:1; }
.spark path{
  fill:#fff; stroke:none;
  transform-box:fill-box; transform-origin:center;
  filter:drop-shadow(0 0 4px rgba(255, 255, 255, .95));
}
.spark .spark__a{ animation:sparkA 1.9s var(--inout) infinite; }
.spark .spark__b{ animation:sparkB 1.4s var(--inout) infinite; }
@keyframes sparkA{
  0%, 100%{ transform:rotate(0deg) scale(1); }
  50%     { transform:rotate(45deg) scale(.62); }
}
@keyframes sparkB{
  0%, 100%{ transform:rotate(45deg) scale(.68); opacity:.75; }
  50%     { transform:rotate(0deg) scale(1.05); opacity:1; }
}

/* the sign-off */
.signoff{ font-size:calc(52px * var(--scriptscale)); }

/* the honeypot: present for a robot to fill in, gone for everyone else */
.hp{
  position:absolute; left:-9999px; width:1px; height:1px;
  overflow:hidden; opacity:0; pointer-events:none;
}

/* only if the booking could not be sent */
.sendfail{
  display:none;
  margin-top:1em;
  font-size:13px; line-height:1.5;
  color:#8f4250;
}
.sendfail.is-in{ display:block; }

/* a quiet line of reassurance above Confirm, discreet on purpose:
   sentence case and low opacity, not the site's usual tracked caps */
.privacynote{
  margin-top:.9em;
  font-size:10.5px; line-height:1.5;
  opacity:.5;
  text-align:center;
}
