/* ============================================================
   THRIVV STUDIO
   Three colours only: black type, cream page, white cards.
   ============================================================ */

:root{
  --cream:      #F8F4EE;   /* page — sampled from the logo background */
  --black:      #0A0A0A;   /* all type */
  --white:      #FFFFFF;   /* cards */
  --rule:       #E2DCD2;   /* hairlines — cream, darkened */
  --rule-dark:  rgba(248,244,238,.16);  /* hairlines on black */

  /* cream at reduced strength — the only way to get "quieter" text on the
     black footer without introducing a grey. Kept close to full cream:
     at .72/.55 the footer was legible by contrast maths and still hard to
     actually read, because the type down there is also the smallest on the
     page. Hierarchy now comes mostly from size, not from fading the text. */
  --on-black:       rgba(248,244,238,.92);
  --on-black-muted: rgba(248,244,238,.78);
  --black-lift:     #141412;   /* footer legal bar — one step off --black */

  /* the hamburger bar and the monogram ring are the same stroke.
     Declared once so they can never drift apart. */
  --stroke: 3px;

  --serif: "Cormorant Garamond", "Didot", "Times New Roman", serif;
  --sans:  "Jost", "Avenir Next", "Helvetica Neue", sans-serif;

  --ease: cubic-bezier(.32,.72,0,1);
  --gutter: clamp(1.25rem, 5vw, 5rem);
  --section-y: clamp(2.75rem, 6vw, 5.5rem);  /* twice tightened, 2026-08-18 */
  --measure: 68rem;

  --btn-radius: .5rem;         /* every appointment button, and the pinned tab */
  --photo-radius: 1rem;        /* the stylist frames and the services photo */

  --z-grain: 60;
  --z-fab: 65;                 /* over the grain, under the header and overlay */
  --z-header: 70;
  --z-overlay: 80;
}

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

/* .btn sets display:inline-block, which outranks the [hidden] attribute's own
   display:none — without this, anything hidden in HTML still renders */
[hidden]{ display:none !important; }

/* Off-screen but still announced and still crawled. Used by the hero's <h1>:
   the wordmark is an image, so this carries the page's only top-level heading.
   Deliberately NOT display:none or visibility:hidden — both would drop it from
   the accessibility tree and from what Google counts as a heading. */
.sr-only{
  position:absolute;
  width:1px; height:1px;
  margin:-1px; padding:0;
  overflow:hidden;
  clip:rect(0 0 0 0);
  clip-path:inset(50%);
  white-space:nowrap;
  border:0;
}

html{ scroll-behavior:smooth; -webkit-text-size-adjust:100%; }

/* the header is fixed and, with the 100px monogram, ~160px tall — without
   this every anchor jump lands with its heading hidden behind it */
#top,#about,#services,#extensions,#booking,#contact{
  scroll-margin-top:clamp(6.5rem, 12vw, 10.5rem);
}

body{
  margin:0;
  background:var(--cream);
  color:var(--black);
  font-family:var(--sans);
  font-weight:300;
  font-size:clamp(.95rem, .35vw + .88rem, 1.0625rem);
  line-height:1.75;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
}

body.is-locked{ overflow:hidden; }

img{ max-width:100%; height:auto; display:block; }
a{ color:inherit; }
ul{ list-style:none; margin:0; padding:0; }
h1,h2,h3,p{ margin:0; }

::selection{ background:var(--black); color:var(--cream); }

/* film grain — fixed layer, never on a scrolling container ---- */
body::after{
  content:"";
  position:fixed; inset:0;
  z-index:var(--z-grain);
  pointer-events:none;
  opacity:.035;
  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)'/%3E%3C/svg%3E");
}

.skip-link{
  position:absolute; left:-9999px; top:0;
  background:var(--black); color:var(--cream);
  padding:.75rem 1.25rem; z-index:100;
  font-size:.8rem; letter-spacing:.12em; text-transform:uppercase;
}
.skip-link:focus{ left:0; }

:focus-visible{ outline:1px solid var(--black); outline-offset:4px; }
.nav-overlay :focus-visible,
.site-footer :focus-visible{ outline-color:var(--cream); }

/* ============================================================
   type
   ============================================================ */

.display{
  font-family:var(--serif);
  font-weight:300;
  font-size:clamp(2.4rem, 6.2vw, 4.9rem);
  line-height:1.03;
  letter-spacing:-.015em;
}

/* ============================================================
   buttons

   Curved, not the reference's hard rectangle — the owner asked for it on
   2026-08-17. --btn-radius is the one place to change it; the pinned
   booking tab reads it too, so the two can never drift apart.
   ============================================================ */

.btn{
  display:inline-block;
  font-family:var(--sans);
  font-size:.75rem;
  font-weight:400;
  text-transform:uppercase;
  letter-spacing:.2em;
  text-decoration:none;
  padding:1.15rem 2.5rem;
  border:1px solid var(--black);
  border-radius:var(--btn-radius);
  transition:background .5s var(--ease), color .5s var(--ease), transform .25s var(--ease);
}
.btn--dark{ background:var(--black); color:var(--cream); }
.btn--dark:hover{ background:transparent; color:var(--black); }

.btn--light{ background:transparent; color:var(--cream); border-color:var(--rule-dark); }
.btn--light:hover{ background:var(--cream); color:var(--black); border-color:var(--cream); }

.btn:active{ transform:scale(.98); }

/* ============================================================
   header
   ============================================================ */

.site-header{
  position:fixed; top:0; left:0; right:0;
  z-index:var(--z-header);
  display:flex; align-items:center; justify-content:space-between;
  padding:clamp(1.1rem,2.4vw,1.9rem) var(--gutter);
  transition:background .6s var(--ease), backdrop-filter .6s var(--ease);
}
/* over the hero photograph the monogram and the burger are cream; once the
   header sticks to the cream page they go back to black */
.site-header:not(.is-stuck) .monogram{ border-color:var(--white); color:var(--white); }
.site-header:not(.is-stuck) .monogram:hover{ background:var(--white); color:var(--black); }
.site-header:not(.is-stuck) .burger span{ background:var(--white); }

.site-header.is-stuck{
  /* white, not cream: the owner asked for the bar itself to be white on
     2026-08-17. Still translucent, so the page reads through it. */
  background:rgba(255,255,255,.82);
  backdrop-filter:blur(14px);
  -webkit-backdrop-filter:blur(14px);
}

.monogram{
  width:clamp(4.5rem,8vw,6.25rem);        /* 72 → 100px */
  aspect-ratio:1;
  border:var(--stroke) solid var(--black);
  border-radius:50%;
  display:grid; place-items:center;
  text-decoration:none;
  transition:background .5s var(--ease), color .5s var(--ease);
}
.monogram__letter{
  font-family:var(--serif);
  font-size:clamp(1.75rem,3.4vw,2.35rem);
  line-height:1;
  letter-spacing:.08em;
  transform:translateX(.04em);
}
.monogram:hover{ background:var(--black); color:var(--cream); }

/* hamburger — three bars, 40px wide, morphing into an X.
   The stack is 3 × var(--stroke) + two 8px gaps; the outer bars each travel
   half the stack height to meet in the middle, so the offset is derived
   rather than hand-tuned. */
.burger{
  --bar-gap: 8px;
  width:40px;
  height:calc(3 * var(--stroke) + 2 * var(--bar-gap));
  background:none; border:0; padding:0; cursor:pointer;
  display:flex; flex-direction:column; justify-content:space-between;
}
.burger span{
  display:block; width:40px; height:var(--stroke);
  background:var(--black);
  transition:transform .55s var(--ease), opacity .3s var(--ease), background .3s var(--ease);
}

.burger.is-open span{ background:var(--white); }   /* the X, over the overlay */
.burger.is-open span:nth-child(2){ opacity:0; }
.burger.is-open span:nth-child(1){ transform:translateY(calc(var(--stroke) + var(--bar-gap))) rotate(45deg); }
.burger.is-open span:nth-child(3){ transform:translateY(calc(-1 * (var(--stroke) + var(--bar-gap)))) rotate(-45deg); }

/* ============================================================
   nav overlay
   ============================================================ */

.nav-overlay{
  position:fixed; inset:0;
  z-index:var(--z-overlay);
  background:var(--black);
  color:var(--cream);
  display:grid; place-items:center;
  opacity:0;
  transition:opacity .6s var(--ease);
}
.nav-overlay[hidden]{ display:none; }
.nav-overlay.is-open{ opacity:1; }

.nav-close{
  position:absolute;
  top:clamp(1.1rem,2.4vw,1.9rem); right:var(--gutter);
  width:2.75rem; height:2.75rem;
  background:none; border:0; cursor:pointer; padding:0;
}
.nav-close svg{ width:1.35rem; height:1.35rem; stroke:var(--white); stroke-width:1; fill:none; }

.nav-overlay__inner{
  text-align:center;
  padding:var(--gutter);
  width:100%;
}

.nav-overlay__mark{
  font-family:var(--serif);
  font-size:1.6rem;
  letter-spacing:.42em;
  text-indent:.42em;
  margin-bottom:clamp(2rem,5vw,3.5rem);
}

.nav-list{
  display:flex; flex-direction:column;
  gap:clamp(.6rem,1.6vw,1.1rem);
  margin-bottom:clamp(2.4rem,5vw,3.5rem);
}
/* Pricing is a <button> because it opens a view instead of going anywhere;
   it has to be indistinguishable from its neighbours all the same */
.nav-list a,
.nav-list__btn{
  font-family:var(--serif);
  font-size:clamp(1.75rem,4.8vw,2.9rem);
  font-weight:300;
  line-height:1.1;
  text-decoration:none;
  display:inline-block;
  position:relative;
  padding-bottom:.1em;
  color:inherit;
  background:none;
  border:0;
  cursor:pointer;
}
.nav-list a::after,
.nav-list__btn::after{
  content:"";
  position:absolute; left:50%; bottom:0;
  width:0; height:1px; background:var(--cream);
  transform:translateX(-50%);
  transition:width .55s var(--ease);
}
.nav-list a:hover::after,
.nav-list__btn:hover::after{ width:100%; }

/* marks alone, larger than the footer's — they are the only thing in this
   row now that the @handle is gone */
.nav-overlay__social{ display:flex; align-items:center; justify-content:center; gap:1.5rem; }
.nav-overlay__ig{
  display:flex; align-items:center; justify-content:center;
  text-decoration:none;
}
/* the dimming lives on the icon, not the anchor: the stagger below animates
   the anchor's own opacity to 1, which would cancel it */
/* colour, not fill: .social-icon paints with currentColor, and setting fill
   here would beat .social-icon--ig's fill:none on specificity and turn the
   Instagram frame into a solid white blob */
.nav-overlay__ig{ color:var(--white); }
.nav-overlay__ig .social-icon{ width:2rem; height:2rem; }

/* the three brand marks — Instagram, Facebook, TikTok — as filled glyphs in
   currentColor. Filled, not stroked: the Facebook f and the TikTok note only
   read correctly as solids, and a stroked Instagram beside two solids looks
   like a different icon set. One class, so all three are always the same size
   wherever they appear; the per-context sizes below are the only overrides. */
.social-icon{
  width:1.05rem; height:1.05rem;
  fill:currentColor;
}

/* Instagram is the one mark that has to be drawn rather than filled: the
   camera is a rounded-square FRAME with a lens ring and a dot, and a solid
   glyph reads as a blob at this size — which is exactly what it was doing
   before 2026-08-18. Strokes scale with the icon, so it stays correct at the
   footer's 22px and the menu's 32px alike. */
.social-icon--ig{
  fill:none;
  stroke:currentColor;
  stroke-width:1.9;
  stroke-linejoin:round;
}
.social-icon--ig .ig-dot{ fill:currentColor; stroke:none; }

/* staggered reveal of the overlay contents */
.nav-overlay__mark,
.nav-list li,
.nav-overlay__social{
  opacity:0;
  transform:translateY(1.4rem);
}
.nav-overlay.is-open .nav-overlay__mark,
.nav-overlay.is-open .nav-list li,
.nav-overlay.is-open .nav-overlay__social{
  opacity:1;
  transform:none;
  transition:opacity .7s var(--ease), transform .7s var(--ease);
}
.nav-overlay.is-open .nav-overlay__mark{ transition-delay:.08s; }
.nav-overlay.is-open .nav-list li:nth-child(1){ transition-delay:.14s; }
.nav-overlay.is-open .nav-list li:nth-child(2){ transition-delay:.19s; }
.nav-overlay.is-open .nav-list li:nth-child(3){ transition-delay:.24s; }
.nav-overlay.is-open .nav-list li:nth-child(4){ transition-delay:.29s; }
.nav-overlay.is-open .nav-list li:nth-child(5){ transition-delay:.34s; }
.nav-overlay.is-open .nav-overlay__social{ transition-delay:.4s; }

/* ============================================================
   pricing panel — the overlay's second view

   The full price list lives here and nowhere else on the site: the owner
   asked for pricing to be found only in the menu. It is the six cards that
   used to sit under the services gallery plus the whole hand-tied
   extensions section, moved here rather than deleted.
   ============================================================ */

.nav-pricing{
  width:100%;
  max-height:100dvh;
  overflow-y:auto;
  padding:clamp(4.5rem,10vw,6rem) var(--gutter) clamp(2.5rem,6vw,4rem);
  -webkit-overflow-scrolling:touch;
}
.nav-pricing__inner{ max-width:62rem; margin-inline:auto; }

.nav-pricing__back{
  display:inline-flex; align-items:center; gap:.5rem;
  background:none; border:0; padding:0;
  color:var(--on-black);
  font-family:var(--sans);
  font-size:.6875rem; text-transform:uppercase; letter-spacing:.2em;
  cursor:pointer;
  transition:color .4s var(--ease);
}
.nav-pricing__back svg{ width:.9rem; height:.9rem; fill:none; stroke:currentColor; stroke-width:1.2; }
.nav-pricing__back:hover{ color:var(--cream); }

.nav-pricing__title{
  font-family:var(--serif);
  font-weight:300;
  font-size:clamp(2rem,5vw,3rem);
  line-height:1.1;
  margin:clamp(1.25rem,3vw,2rem) 0 clamp(1.75rem,4vw,2.5rem);
}

.nav-pricing__list{
  display:grid;
  grid-template-columns:1fr;
  gap:1px;
  background:var(--rule-dark);
  border:1px solid var(--rule-dark);
  text-align:left;
}
.nav-pricing__list li{
  background:var(--black);
  padding:clamp(1.1rem,2.5vw,1.5rem);
}
.nav-pricing__name{
  font-size:.6875rem; text-transform:uppercase; letter-spacing:.2em;
  color:var(--on-black);
}
.nav-pricing__fee{
  font-family:var(--serif);
  font-size:clamp(1.5rem,3vw,1.9rem);
  line-height:1.1;
  margin:.5rem 0 .6rem;
  color:var(--cream);
}
.nav-pricing__note{
  font-size:.8125rem;
  line-height:1.7;
  color:var(--on-black-muted);
}

.nav-pricing__sub{
  font-family:var(--serif);
  font-weight:300;
  font-size:clamp(1.4rem,3vw,1.9rem);
  margin:clamp(2rem,4.5vw,3rem) 0 1rem;
  text-align:left;
}
.nav-pricing__intro{
  font-size:.875rem;
  line-height:1.8;
  color:var(--on-black-muted);
  max-width:52rem;
  text-align:left;
  margin-bottom:1rem;
}
.nav-pricing__intro + .nav-pricing__list{ margin-top:1.5rem; }

/* ============================================================
   hero
   ============================================================ */

.hero{
  min-height:100dvh;
  display:flex; flex-direction:column;
  align-items:center; justify-content:center;
  text-align:center;
  /* top pad clears the fixed header: a 100px monogram + 2 × 1.9rem padding
     is ~161px, which 9rem did not */
  padding:clamp(8rem,14vw,11rem) var(--gutter) clamp(4rem,9vw,6rem);
  gap:clamp(2.5rem,5vw,4rem);
  position:relative;
  isolation:isolate;
  /* a solid black field — the owner asked for the intro to be all black on
     2026-08-20. It used to be the fallback behind .hero__bg, the studio-suite
     photograph, which is now gone. */
  background:var(--black);
}
/* the PNG's background is exactly --cream, so it sits flush with no
   blend mode — multiply would darken cream against cream.
   logo-wordmark.png is logo.png with its dead margin cropped off (the
   original was 59% empty across and 84% down, which is why the wordmark
   used to render small); logo.png is kept untouched as the master. */
.hero__logo{
  width:min(92%, 40rem);
  /* the SVG's ink is #0A0A0A and would disappear into the photograph.
     brightness(0) flattens it to pure black whatever colour it is, and
     invert(1) turns that white — so the mark stays correct even if the file
     is re-traced in another colour later. */
  filter:brightness(0) invert(1);
}
.hero__cta{ margin-top:.5rem; }

/* ============================================================
   sections
   ============================================================ */

.section{
  padding:var(--section-y) var(--gutter);
  max-width:var(--measure);
  margin-inline:auto;
}
.section + .section{ padding-top:0; }

.section__head{ margin-bottom:clamp(1.75rem,4vw,3rem); }

/* a name under a heading — small caps in the sans, the same register as the
   footer's column heads, so it reads as a label rather than a second title */
.section__sub{
  margin-top:1rem;
  font-size:.8125rem;
  text-transform:uppercase;
  letter-spacing:.28em;
  color:var(--black);
  opacity:.7;
}

/* "What Our Clients Say About Us" is the longest heading on the site and now
   the smallest, and the only centred one: the owner had it brought down twice
   on 2026-08-17. Well under the measure at this size, so the one-line fit is
   no longer delicate. */
.reviews .section__head{ text-align:center; }
.reviews .section__head .display{ font-size:clamp(1.6rem,3.4vw,2.4rem); }

/* full-bleed white bands -------------------------------------
   The page alternates cream → white → cream and ends on the black
   footer. .section is a centred max-width box, so the band cannot be a
   background on the element itself — it is painted by a pseudo-element
   stretched to 100vw. body{overflow-x:hidden} absorbs the scrollbar
   width that 100vw overshoots by. */

.section--white{ position:relative; }
.section--white::before{
  content:"";
  position:absolute; top:0; bottom:0;
  left:50%; margin-left:-50vw; width:100vw;
  background:var(--white);
  z-index:-1;                        /* behind the content, above body */
}

/* .section + .section zeroes top padding, which would butt a band flush
   against the previous section's last line and merge two bands into one
   slab. A band needs its padding back — and so does whatever follows it.
   Both selectors are (0,2,0) and sit after that rule, so they win. */
.section.section--white,
.section--white + .section{ padding-top:var(--section-y); }

/* the booking panel stays white on the white band — the owner asked for it
   on 2026-08-17. It reads as a panel by its hairline border alone, which is
   why that border is not optional here. */
/* the review cards are the exception — they stay white on the white band
   and are separated by a shadow instead, which is what the reference does */

/* the booking panel is the last band before the footer (the photo band and
   this one swapped places on 2026-08-17), so it carries bottom padding of
   its own and the footer drops its top margin — white should run straight
   into the black rather than through a cream seam */
.booking--last{ padding-bottom:var(--section-y); }

/* the stylist section opens straight off the hero photograph, so it carries
   more room above it than the other bands do — the owner asked for the
   heading, the name and the portraits to all sit lower. #about beats the
   .section.section--white padding rule on specificity. */
/* extra room above the heading (it opens off the hero photograph), but a
   short tail below it — the Instagram mark under the portraits sat a long way
   from the Services heading otherwise */
#about{
  padding-top:clamp(5rem,11vw,9rem);
  padding-bottom:clamp(1.5rem,3vw,2.5rem);
}

/* the stylist —————————————————————————————————
   copy on one side, headshot on the other. assets/reese.jpg is not in the
   repo yet: the frame holds its shape and shows the hairline box until it
   is, which is deliberate, not a broken image to "fix". */

.stylist{
  display:grid;
  grid-template-columns:1fr;
  gap:clamp(2rem,5vw,3rem);
  /* no max-width: the photographs use the full measure, which is what makes
     them read as portraits rather than thumbnails */
}
.stylist__copy p{ font-size:clamp(1rem,1.6vw,1.125rem); line-height:1.85; }
.stylist__copy p + p{ margin-top:1.15rem; }

/* the pair reads as a spread rather than two matching tiles: the second
   frame drops half a step at 48rem and up. Both stay in one row even on a
   phone — they are portraits, so two narrow columns still work. */
/* nudged right of the copy — the whole portrait block, mark included */
.stylist__portrait{ padding-left:clamp(0rem,3vw,2.5rem); }

.stylist__frames{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:clamp(.6rem,1.5vw,1rem);
  align-items:start;
}
/* a rectangle with curved corners — the arch was tried on 2026-08-17 and
   the owner did not like it. The frame has to clip (overflow:hidden) or the
   photo's square corners show through the rounded border. */
.stylist__frame{
  aspect-ratio:3 / 4;
  border:1px solid var(--rule);
  background:var(--rule);
  overflow:hidden;
  border-radius:var(--photo-radius);
}
.stylist__frame img{
  display:block;
  width:100%; height:100%;
  object-fit:cover;
  filter:contrast(1.03);
}
/* deliberately the largest social mark on the site — it is the only thing
   under two big photographs, and at the footer's size it disappeared */
.stylist__ig{
  display:flex; align-items:center; justify-content:center;
  width:3.75rem; height:3.75rem;
  margin:1.5rem auto 0;
  border:1px solid var(--rule);
  border-radius:50%;
  color:var(--black);
  transition:background .4s var(--ease), color .4s var(--ease), border-color .4s var(--ease);
}
.stylist__ig .social-icon{ width:1.85rem; height:1.85rem; }
.stylist__ig:hover{ background:var(--black); color:var(--cream); border-color:var(--black); }

/* ============================================================
   services gallery — hover/focus swaps the photo
   ============================================================ */

.svc{
  display:grid;
  grid-template-columns:1fr;
  gap:clamp(2rem,5vw,3.5rem);
  align-items:center;
  /* it is the last thing in the section now that the price cards have moved
     into the menu — the section's own padding is the gap */
  margin-bottom:0;
}
.svc__stack{
  position:relative;
  width:100%;              /* without this the aspect-ratio sizes off the
                              word list's height and the photo comes out small */
  aspect-ratio:1;
  background:var(--rule);
  /* the photos are stacked absolutely inside, so the rounding and the clip
     both belong to the stack — putting a radius on each image would round
     four layers that all have to line up */
  border-radius:var(--photo-radius);
  overflow:hidden;
}
.svc__img{
  position:absolute; inset:0;
  width:100%; height:100%;
  object-fit:cover;
  filter:contrast(1.03);
  opacity:0;
  transition:opacity .6s var(--ease);
}
.svc__img.is-active{ opacity:1; }

/* the arrows page through one service's photographs — script.js hides them
   for any service that owns only one. They sit inside the rounded frame, so
   they need their own stacking above the images. */
.svc__nav{
  position:absolute; top:50%;
  transform:translateY(-50%);
  z-index:1;
  width:2.5rem; height:2.5rem;
  display:grid; place-items:center;
  background:rgba(248,244,238,.9);
  backdrop-filter:blur(8px);
  -webkit-backdrop-filter:blur(8px);
  border:1px solid var(--rule);
  border-radius:50%;
  cursor:pointer;
  transition:background .4s var(--ease), transform .25s var(--ease);
}
.svc__nav svg{ width:1rem; height:1rem; fill:none; stroke:var(--black); stroke-width:1.2; }
.svc__nav:hover{ background:var(--cream); }
.svc__nav--prev{ left:.75rem; }
.svc__nav--next{ right:.75rem; }

/* caption under the photograph, aligned to its bottom-left corner. The word
   list below is the control, and nothing else on the page says so. */
.svc__hint{
  margin-top:.6rem;
  text-align:left;
  font-size:.75rem;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--black);
  opacity:.55;
}

.svc__list{ text-align:center; }
/* stacked on phones: side by side, the reserved price box shoves each word
   off-centre by a different amount and the column reads ragged */
.svc__list li{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:.35rem;
}
.svc__word{
  display:inline-block;
  cursor:pointer;
  font-family:var(--serif);
  font-size:clamp(1.75rem,4.4vw,2.8rem);
  line-height:1.55;
  text-decoration:none;
  color:var(--black);
  opacity:.45;
  /* inline-block is what makes this transformable; centre origin so it grows
     on the spot instead of drifting sideways */
  transform:scale(1);
  transform-origin:center;
  transition:opacity .4s var(--ease), transform .45s var(--ease), font-style .01s;
}
.svc__word.is-active{
  opacity:1;
  font-style:italic;
  border-bottom:1px solid var(--black);
  /* the chosen service lifts slightly. Picking another eases this one back
     down as that one grows, so the swap reads as a single movement. */
  transform:scale(1.07);
}

/* the price rides the same .is-active state that swaps the photograph, so
   hover and keyboard focus reveal it alike — no second mechanism. It keeps
   its box while hidden (visibility, not display) or every row would jump as
   the fade ran. Multiple prices stack in the order they are written. */
.svc__price{
  display:block;
  /* a soft chip behind the price so the figures lift off the white band. It is
     --cream, the page's own ground, not a grey: on this white section that
     reads as a shade rather than a fourth colour. */
  background:var(--cream);
  border-radius:var(--btn-radius);
  padding:.4rem .8rem;
  /* one line per service, so the list reads as a price column rather than a
     stack of fragments — which is why the type is a notch smaller and the
     tracking tighter than the rest of the site's small caps */
  font-size:.8125rem;
  line-height:1.5;
  letter-spacing:.06em;
  text-transform:uppercase;
  text-align:center;
  opacity:0;
  visibility:hidden;
  transform:translateY(-.25rem);
  transition:opacity .4s var(--ease), transform .4s var(--ease), visibility .4s;
}
.svc__word.is-active ~ .svc__price{
  opacity:1;
  visibility:visible;
  transform:none;
}

/* ============================================================
   gallery marquee — a band that scrolls on its own, forever

   script.js appends one clone of every slide, so the track holds the set
   twice. Translating it by exactly one set's width lands the clone where
   the original started and the loop is invisible. That distance is not
   50% of the track: 2n slides carry 2n-1 gaps, so half the track is one
   gap-half short — hence the `- var(--gallery-gap)/2`. Change the gap and
   the maths follows it; hard-code a gap here and the band will judder.
   ============================================================ */

.gallery{
  /* no padding above: the owner asked for the cream strip between the
     reviews band and the photos to go, so the white runs straight into them */
  padding:0;
  --gallery-gap:clamp(.75rem,1.5vw,1.25rem);
}
/* clips the half-slides at both edges — the band should run off the page */
.gallery__viewport{ position:relative; overflow:hidden; }

.gallery__track{
  display:flex;
  width:max-content;
  gap:var(--gallery-gap);
  /* --gallery-dur is set by script.js from the slide count, so adding a
     photo keeps the speed rather than the loop length */
  animation:gallery-marquee var(--gallery-dur,36s) linear infinite;
  will-change:transform;
}
@keyframes gallery-marquee{
  from{ transform:translate3d(0,0,0); }
  /* a measured pixel distance set by script.js — exactly one set of slides.
     It replaced calc(-50% - gap/2), which assumed the track held precisely two
     sets and left blank space whenever one set was narrower than the screen. */
  to  { transform:translate3d(calc(-1 * var(--gallery-shift, 50%)),0,0); }
}
/* portrait, because the photographs are: a 3/2 landscape crop of a standing
   figure takes a band across the middle and cuts the head off */
.gallery__slide{
  flex:0 0 min(70vw, 24rem);
}
.gallery__slide img{
  display:block;
  width:100%;
  aspect-ratio:4/5;
  object-fit:cover;
  /* the sources are taller than the frame, so cover crops top and bottom.
     Bias the crop upward — a centred one takes the heads off. */
  object-position:50% 18%;
  filter:contrast(1.03);
  background:var(--rule);
}

/* ============================================================
   persistent booking tab — pinned bottom-right past the hero
   ============================================================ */

.book-fab{
  position:fixed;
  right:clamp(1rem,3vw,2rem);
  bottom:clamp(1rem,3vw,2rem);
  z-index:var(--z-fab);
  font-family:var(--sans);
  font-size:.6875rem;
  font-weight:400;
  letter-spacing:.18em;
  text-transform:uppercase;
  text-decoration:none;
  color:var(--cream);
  background:var(--black);
  border:1px solid var(--black);
  border-radius:var(--btn-radius);
  padding:15px 24px;
  /* not [hidden]: display:none cannot be transitioned, and visibility keeps
     it out of the tab order and the a11y tree while it is away */
  opacity:0;
  visibility:hidden;
  transform:translateY(14px);
  transition:opacity .45s var(--ease), transform .45s var(--ease),
             visibility .45s var(--ease),
             background .5s var(--ease), color .5s var(--ease);
}
.book-fab.is-in{ opacity:1; visibility:visible; transform:none; }
.book-fab:hover{ background:var(--cream); color:var(--black); }

/* it must not sit on top of the nav overlay. The second selector here used to
   cover the new-client modal too; that was removed on 2026-08-20. */
body.is-locked .book-fab{
  opacity:0;
  visibility:hidden;
  transform:translateY(14px);
}

/* ============================================================
   booking
   ============================================================ */

.booking{ max-width:var(--measure); }

/* no box: the owner had the border and the fill taken off on 2026-08-17, so
   this is bare centred content on the white band. The padding stays — it is
   what keeps the heading off the section's edges. */
.booking__card{
  padding:clamp(1.25rem,3vw,2.5rem) clamp(1.75rem,5vw,4rem);
  text-align:center;
}
.booking__card .display{ margin-bottom:1.5rem; }

.booking__note{
  max-width:34ch;
  margin:0 auto clamp(2rem,4vw,2.75rem);
}

.booking__actions{
  display:flex; flex-direction:column; align-items:center;
  gap:1.5rem;
}
.booking__direct{
  font-size:.78rem; text-transform:uppercase; letter-spacing:.16em;
  display:flex; gap:.7rem; align-items:center; flex-wrap:wrap; justify-content:center;
}
.booking__direct a{
  text-decoration:none;
  border-bottom:1px solid var(--rule);
  padding-bottom:.15rem;
  transition:border-color .4s var(--ease);
}
.booking__direct a:hover{ border-color:var(--black); }

/* ============================================================
   footer
   ============================================================ */

.site-footer{
  background:var(--black);
  color:var(--cream);
  margin-top:0;                      /* the gallery band supplies the gap */
}

.footer__main{ padding:clamp(3.5rem,7vw,3.75rem) 0 clamp(3rem,6vw,3.125rem); }

.footer__grid{
  display:grid;
  grid-template-columns:1fr;
  /* the row gap is what sat between the columns and the map; the owner asked
     for that closed up, so it is now half what the column gap is */
  gap:1.25rem 2.8125rem;
  padding-inline:var(--gutter);
  max-width:82rem;
  margin-inline:auto;
}

/* letterspaced wordmark — the hero PNG can't be reused here, its background
   is opaque cream and would print a cream slab on the black */
.footer__mark{
  grid-column:1 / -1;        /* its own row across the top of the footer */
  font-family:var(--serif);
  font-weight:300;
  /* kept at reduced alpha on purpose — thin serifs bloom on black, so
     full-cream Cormorant reads heavier here than it does on the page */
  font-size:1.35rem;
  letter-spacing:.34em;
  /* centred at every width, at the owner's request (mobile 2026-08-20, then
     desktop). letter-spacing leaves a .34em gap AFTER the last letter, so
     text-align alone parks the wordmark visibly left of middle; the negative
     margin pulls that trailing gap back out of the box. text-indent is 0 for
     the same reason — it used to nudge the mark right while it was left-aligned
     and would now push it past centre. */
  text-align:center;
  text-indent:0;
  margin-right:-.34em;
  color:var(--on-black);
  margin-bottom:1.5rem;
}

.footer__head{
  /* inline-block, so the rule below is the width of the words and not of the
     whole column — the owner asked for it under the text only */
  display:inline-block;
  font-size:.75rem;
  font-weight:400;
  text-transform:uppercase;
  letter-spacing:.2em;
  color:var(--cream);
  position:relative;
  padding-bottom:12px;
  margin-bottom:22px;
}
.footer__head::after{            /* as wide as the words above it */
  content:"";
  position:absolute; bottom:0; left:0;
  width:100%; height:2px;
  background:var(--cream);
}

.footer__text{
  font-size:.875rem;
  line-height:1.8;
  color:var(--on-black);
}
.footer__text + .footer__text{ margin-top:1.1rem; }
.footer__text a{ text-decoration:none; border-bottom:1px solid var(--rule-dark); color:inherit; }
.footer__text a:hover{ border-color:var(--cream); color:var(--cream); }
.footer__muted{ color:var(--on-black-muted); font-size:.8125rem; letter-spacing:.04em; }

.footer__links li + li{ margin-top:10px; }
.footer__links a{
  font-size:.875rem; line-height:1.6;
  text-decoration:none;
  color:var(--on-black);
  display:inline-block; position:relative;
  transition:color .3s var(--ease), padding-left .3s var(--ease);
}
.footer__links a::before{
  content:"";
  position:absolute; bottom:-1px; left:0;
  width:0; height:1px;
  background:var(--cream);
  transition:width .3s var(--ease);
}
.footer__links a:hover{ color:var(--cream); padding-left:5px; }
.footer__links a:hover::before{ width:100%; }

.footer__hours{
  width:100%;
  border-collapse:collapse;
  margin-bottom:0;              /* nothing follows it in the column now */
  font-size:.875rem;
}
.footer__hours tr{ border-bottom:1px solid var(--rule-dark); }
.footer__hours tr:last-child{ border-bottom:0; }
.footer__hours tr:first-child td{ padding-top:0; }
.footer__hours td{ padding:8px 0; color:var(--on-black); }
.footer__hours td:first-child{ color:var(--cream); padding-right:20px; white-space:nowrap; }
.footer__hours td:last-child{ text-align:right; white-space:nowrap; }

/* the phone and the email read across rather than stacking: mark, then the
   link, then the note. align-items:center, not baseline — a baseline would
   sit the mark's box on the text baseline and hang it below the line. */
.footer__phone{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:.5rem;
}
.footer__phone + .footer__phone{ margin-top:.4rem; }

/* the small marks beside the phone and the email. The address has none, by
   the owner's instruction. */
.footer__icon{
  flex:0 0 auto;
  width:1rem; height:1rem;
  fill:none;
  stroke:var(--on-black);
  stroke-width:1.3;
  stroke-linecap:round;
  stroke-linejoin:round;
}

/* a second heading part-way down a column — identical to the ones at the top,
   rule and all, it just needs air above it */
.footer__head--mid{ margin-top:3.5rem; }

/* two lines, under its heading in the contact column */
.footer__address{ margin-top:0; }

.footer__map{
  min-height:17.375rem;            /* 278px */
  border:1px solid var(--rule-dark);
}
.footer__map iframe{
  display:block;
  width:100%; height:100%;
  min-height:17.375rem;
  border:0;
  /* greyscaled so a full-colour map doesn't become a fourth colour */
  /* in colour, like the photographs — the owner asked for it on 2026-08-17.
     The slight dimming stays so a bright map does not glare out of the
     black footer. */
  filter:contrast(1.05) brightness(.92);
}

/* centred on its own line between the footer columns and the policy band —
   the owner moved it out of the contact column on 2026-08-17. Facebook and
   TikTok have no href until their constants are set, so this row can hold one
   live mark or three. */
.footer__social{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:18px;
  /* it opens the policy band, which supplies the side padding */
  margin-bottom:clamp(1.75rem,3.5vw,2.25rem);
}
.footer__social a{
  width:44px; height:44px;
  border-radius:50%;
  border:1px solid var(--rule-dark);
  /* pure white, not cream: the owner asked for the three marks to match each
     other exactly, so none of them carries the page's warmer off-white */
  color:var(--white);
  display:flex; align-items:center; justify-content:center;
  transition:background .4s var(--ease), color .4s var(--ease), border-color .4s var(--ease), transform .25s var(--ease);
}
.footer__social a:hover{ background:var(--cream); color:var(--black); border-color:var(--cream); transform:translateY(-2px); }

/* a mark whose account does not exist yet. It is NOT dimmed — the owner asked
   for all three to look the same — so the only difference is that it has no
   href, and so cannot be clicked through to the wrong place. */
.is-pending{ cursor:default; }
.footer__social a.is-pending:hover{
  background:none; color:var(--white); border-color:var(--rule-dark);
  transform:none;
}

.footer__social .social-icon{ width:1.4rem; height:1.4rem; }

.policy-bar{
  border-top:1px solid var(--rule-dark);
  padding:2.1875rem var(--gutter);
  text-align:center;
  /* a step up from the .8125rem it shared with the rest of the small print —
     the owner asked for it slightly larger, and it is the one line here
     people are meant to actually read */
  font-size:.9375rem;
  line-height:1.75;
  color:var(--on-black-muted);
}
.policy-bar p{ max-width:50rem; margin-inline:auto; }
.policy-bar__head{
  text-transform:uppercase;
  letter-spacing:.2em;
  font-size:.8125rem;
  color:var(--cream);
  margin-bottom:.6rem !important;
}

/* the legal strip — one step lighter than the footer body */
.footer__legal{
  background:var(--black-lift);
  /* the extra room at the foot is the booking tab's: it is fixed to the
     bottom-right, so without it the tab sits on top of these links once
     you reach the end of the page */
  padding:25px var(--gutter) calc(25px + 4.5rem);
}
.footer__legal-inner{
  max-width:82rem;
  margin-inline:auto;
  display:flex; justify-content:space-between; align-items:center;
  flex-wrap:wrap; gap:15px;
}
.footer__copyright{ font-size:.8125rem; color:var(--on-black-muted); }
.footer__legal-links{ display:flex; align-items:center; gap:20px; }
.footer__legal-links a{
  font-size:.75rem;
  letter-spacing:.08em;
  text-transform:uppercase;
  text-decoration:none;
  color:var(--on-black-muted);
  transition:color .4s var(--ease);
}
.footer__legal-links a:hover{ color:var(--cream); }
.footer__sep{ width:1px; height:12px; background:var(--rule-dark); }

/* ============================================================
   scroll reveal
   ============================================================ */

/* scoped to .js so that if the script fails to load, nothing is hidden */
.js .reveal{
  opacity:0;
  transform:translateY(1.5rem);
  transition:opacity .9s var(--ease), transform .9s var(--ease);
}
.js .reveal.is-in{ opacity:1; transform:none; }

/* ============================================================
   breakpoints
   ============================================================ */

@media (min-width:48rem){
  .booking__actions{ gap:1.75rem; }
  /* the copy is two short sentences; the photographs get the wider half.
     align-items:start, not center: centring the short copy against the tall
     portraits left a hole under the heading. */
  .stylist{ grid-template-columns:.75fr 1.25fr; align-items:start; }
  .stylist__frames > :nth-child(2){ margin-top:clamp(1.5rem,4vw,2.75rem); }
  /* the word list gets the wider half: "Depends on starting point and number
     of sessions" has to fit on one line beside its word */
  .svc{ grid-template-columns:.85fr 1.15fr; }
  .svc__list{ text-align:left; padding-left:clamp(1rem,4vw,3rem); }
  /* side by side once there is width for it, price hard right */
  .svc__list li{
    flex-direction:row;
    align-items:baseline;
    justify-content:space-between;
    gap:clamp(.75rem,2vw,1.5rem);
  }
  .svc__price{ text-align:right; transform:translateX(-.4rem); }
  .svc__word.is-active ~ .svc__price{ transform:none; }

  /* two columns first — four of them at tablet width squeezes the hours
     table and the link lists into unreadable slivers. */
  .footer__grid{ grid-template-columns:1fr 1fr; }
  .footer__map{ grid-column:1 / -1; }


  .nav-pricing__list{ grid-template-columns:1fr 1fr; }
}

@media (min-width:64rem){
  /* row 1: about | explore | services | hours
     row 2: the map, run across all four — with the newsletter and the
     booking card gone there is nothing to pair it with, and a map floating
     in two of four columns reads like something failed to load */
  .footer__grid{ grid-template-columns:.95fr .8fr 1fr 1.3fr; }
  .footer__map{ grid-column:1 / -1; }

  .nav-pricing__list{ grid-template-columns:1fr 1fr 1fr; }
}

/* ============================================================
   reduced motion
   ============================================================ */

/* ============================================================
   mobile only  (below the 48rem breakpoint)
   Deliberately a max-width block: everything else in this file is
   mobile-first, so this is the one place phone-only overrides live.
   ============================================================ */
@media (max-width:47.9375rem){

  /* the footer felt smashed together, so the columns get real air and each
     one centres. Hours & Appointments keeps its table untouched — the owner
     said that one already reads well. */
  .footer__grid{ gap:2.75rem 2.8125rem; }
  .footer__col{ text-align:center; }


  /* .footer__head is inline-block, so its 2px rule is exactly as wide as the
     words. NOTHING here may give a heading full width — that is what made the
     line run the whole column. */
  .footer__head{ margin-bottom:1.15rem; }
  .footer__head--mid{ margin-top:1.75rem; }

  /* phone + email across one row. The wrapper exists so they can flex without
     dragging the heading into the same flex line. */
  .footer__inline{
    display:flex; flex-wrap:wrap;
    justify-content:center; align-items:center;
    gap:.5rem 1.5rem;
  }
  .footer__inline .footer__phone + .footer__phone{ margin-top:0; }
  .footer__address{ margin-top:0; }
  /* the address runs on one line; the space before the <br> in the markup is
     what keeps "Suite 103" off "Boca Raton" */
  .footer__col--contact .footer__address br{ display:none; }

  /* Explore and Services read across too */
  .footer__links{
    display:flex; flex-wrap:wrap;
    justify-content:center;
    gap:.5rem 1.5rem;
  }
  .footer__links li + li{ margin-top:0; }

  /* equal air above and below the price box, so it sits midway between the
     service it belongs to and the next one rather than hugging its own word.
     The box is always reserved (visibility, not display), so this spacing is
     the same whether a price is showing or not. */
  .svc__list li{ gap:1.15rem; }
  .svc__list li + li{ margin-top:1.15rem; }
  .svc__word{ line-height:1.25; }
  .svc__price{
    max-width:min(22rem, 88vw);
    margin-inline:auto;
    padding:.45rem 1rem;
    line-height:1.6;
  }

  /* legal strip: Stylist / Services / Contact centred and ABOVE the copyright,
     lifted slightly off it */
  .footer__legal{ padding-top:16px; }
  .footer__legal-inner{
    justify-content:center; text-align:center;
    gap:6px;
  }
  .footer__copyright{
    white-space:nowrap;
    font-size:clamp(.5rem, 2.3vw, .8125rem);
  }
  .footer__legal-links{
    width:100%;
    justify-content:center;
    /* source order keeps the copyright first — it reads first for a screen
       reader and is the left-hand item on desktop — so this is a visual
       reorder for the phone only */
    order:-1;
  }

}

@media (prefers-reduced-motion:reduce){
  .svc__word, .svc__word.is-active{ transform:none; }

  html{ scroll-behavior:auto; }
  *,*::before,*::after{
    animation-duration:.01ms !important;
    transition-duration:.01ms !important;
  }
  .reveal{ opacity:1; transform:none; }


  /* the blanket .01ms rule above would turn the photo marquee into a strobe —
     stop it dead instead, and hand the band back as a scrollable strip */
  .gallery__track{ animation:none !important; width:auto; }
  .gallery__viewport{ overflow-x:auto; }
  .nav-overlay__mark,
  .nav-list li,
  .nav-overlay__social{ opacity:1; transform:none; }
}
