/* ==========================================================================
   Golden Heights Salon
   Brand: espresso + cream. Display: Cormorant Garamond. UI: Jost.
   ========================================================================== */

:root {
  /* Desert, not chocolate. These were several shades darker (#2a211a /
     #1d1712 / #3a2e24) and the whole site read as dark brown. Lifting the
     ground means lifting --tan and --sand with it: at the old values the
     eyebrow accent fell to 2.75:1 on this ground, under AA. Measured on the
     rendered result, not the tokens: headings and body copy 7.20:1, eyebrows
     5.08:1. Body copy had to be made fully opaque to get there — see the note
     on text weight further down. */
  /* 2026-09-19, round two: Jamie wanted the original chocolate back, "just not
     quite as dark". These sit 30% of the way from the original (#2a211a /
     #1d1712 / #3a2e24) toward the desert set that replaced it (#5a4936 /
     #48392a / #6d5a44) — same hue as the original, a shade lifted. The
     photography lift in tools/lift-photography.py was scaled by the same 30%. */
  --espresso:        #382d22;
  --espresso-deep:   #2a2119;
  --espresso-soft:   #493b2e;
  --cream:           #f2eadc;   /* cream SURFACES — the light sections */
  --cream-deep:      #e9dfcd;
  /* Cream TEXT on the dark ground, a few shades brighter than the surface
     cream. Separate token on purpose: brightening --cream itself would turn the
     light sections near-white and change the whole feel of the page. 7.98:1 on
     #5a4936, against 7.20:1 for surface cream. Pure white only reaches 8.60:1
     and goes cold against a warm ground, so the extra 0.6 isn't worth the
     brand drift. */
  --cream-text:      #faf6ee;
  --cream-text-rgb:  250 246 238;   /* same, as channels, for translucent text and hairlines */
  --tan:             #e8d0aa;
  --sand:            #dcc3a5;
  --ink:             #1a1410;

  /* Same three darks as channel lists, so translucent overlays (hero scrim,
     stuck nav, gallery hover) can be written as rgb(var(--x) / alpha) and
     follow the palette. Before this they were hand-typed rgba() values and
     silently stayed put whenever the tokens above changed. */
  --espresso-rgb:      56 45 34;
  --espresso-deep-rgb: 42 33 25;
  --scrim-rgb:         37 30 22;

  --display: 'Cormorant Garamond', 'Times New Roman', serif;
  --sans: 'Jost', 'Helvetica Neue', Arial, sans-serif;
  --script: 'Qwigley', 'Italianno', cursive;

  /* ---- TYPE SCALE -------------------------------------------------------
     Root is 175% below, so 1rem = 28px. Sizes stay in rem rather than px so
     they still scale with whatever base font size the visitor has set in
     their own browser. The px value each one lands on is noted alongside.
     Change a number here and every element using that token follows. */
  --fs-body:    1rem;      /* 28px — paragraphs */
  --fs-service: 0.786rem;  /* 22px — the numbered services list */
  --fs-label:   0.714rem;  /* 20px — nav, buttons, eyebrows, rails, footer */
  --fs-small:   0.64rem;   /* 18px — secondary notes */
  --fs-tiny:    0.61rem;   /* 17px — copyright, scroll cue */

  --gutter: clamp(1.25rem, 5vw, 5.5rem);
  --section-y: clamp(3.5rem, 7.5vw, 6.5rem);

  /* Nav bar and monogram. The bar has to grow with the mark or the logo ends
     up crammed against the edges. Logo heights are clamped so the mark doesn't
     dominate a narrow phone screen. */
  --nav-h: 104px;
  --nav-h-stuck: 84px;
  --logo-h: clamp(52px, 4.9vw, 70px);
  --logo-h-stuck: clamp(42px, 3.8vw, 54px);
  --ease: cubic-bezier(.22, 1, .36, 1);
}

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

/* TYPE SIZE — this one number scales all body, nav, label and footer text.
   175% = 28px at the browser default. A percentage rather than px so it still
   respects whatever base size the visitor has set themselves.
     106.25% = 17px   (original)
     150%    = 24px
     175%    = 28px   (current)
     212.5%  = 34px   (literal 2x of the original)
   Display headings are pinned in px further down so they do NOT scale with
   this — otherwise the headings balloon and the hierarchy inverts. */
html { font-size: 175%; scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  margin: 0;
  background: var(--espresso);
  color: var(--cream-text);
  font-family: var(--sans);
  font-weight: 300;
  font-size: 1rem;
  line-height: 1.75;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }

:focus-visible { outline: 2px solid var(--tan); outline-offset: 3px; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--cream); color: var(--ink); padding: .75rem 1.25rem;
  font-size: var(--fs-label); letter-spacing: .16em; text-transform: uppercase;
}
.skip:focus { left: 1rem; top: 1rem; }

/* ---------- text weight on the dark ground ----------------------------------
   Every cream on dark used to be translucent, with alphas picked against the old
   near-black (#2a211a). At 84% over near-black, body copy still measured 9.7:1,
   so the alpha was free contrast-wise and just softened the look.

   The desert palette moved the ground to #5a4936 and those alphas did not move
   with it. The same 84% now measures 5.65:1, and four smaller items fell under
   AA outright: the scroll cue (4.10), the gallery note (3.89), the Vagaro note
   (4.48) and the footer bar (4.33). That is the "washed out" — the ground
   showing through the glyphs, not an overlay sitting on top of them. The text
   has always been the top layer (`.hero-inner { z-index: 2 }`, and the scrims
   are `z-index: auto` beneath it).

   Reaching 7:1 on this ground needs alpha 0.99, so body copy and primary
   navigation are now fully opaque. Secondary items keep a little softness but
   not enough to drop below ~5:1.
   --------------------------------------------------------------------------- */

/* ---------- shared type ---------- */

.eyebrow {
  font-size: var(--fs-label);
  letter-spacing: .24em;
  text-transform: uppercase;
  font-weight: 400;
  color: var(--sand);
  margin: 0 0 1.75rem;
}
.on-cream .eyebrow { color: #6f5a42; }

.display {
  font-family: var(--display);
  font-weight: 300;
  line-height: .96;
  letter-spacing: .015em;
  margin: 0;
  text-transform: uppercase;
}

h2.display { font-size: clamp(38px, 5.2vw, 70px); }
/* gallery and booking are supporting acts, not headline sections — giving every
   h2 the same size flattens the page into a stack of equal shouts */
.gallery h2.display,
.booking h2.display { font-size: clamp(30px, 3.4vw, 46px); }

p { margin: 0 0 1.5rem; max-width: 46ch; font-size: var(--fs-body); line-height: 1.6; color: var(--cream-text); }
p, blockquote { text-wrap: pretty; }  /* avoids one-word last lines; ignored where unsupported */
.on-cream p { color: rgba(32,25,19,.82); }

/* underlined arrow link */
.arrow-link {
  display: inline-flex; align-items: center; gap: .85rem;
  /* inside .panel (a flex column) this would otherwise stretch to full width
     and drag the underline across the whole column */
  align-self: flex-start;
  min-height: 44px;
  font-size: var(--fs-label); letter-spacing: .16em; text-transform: uppercase;
  padding-bottom: .5rem;
  border-bottom: 1px solid currentColor;
  transition: gap .4s var(--ease), color .3s;
}
.arrow-link .arw { transition: transform .4s var(--ease); display: inline-block; }
.arrow-link:hover { gap: 1.4rem; color: var(--tan); }
.arrow-link:hover .arw { transform: translateX(4px); }
.on-cream .arrow-link:hover { color: #7a6141; }

/* Visually hidden, still announced. Used to tell screen-reader users that a link
   opens in a new tab — without it the window changes and the Back button appears
   to stop working, with nothing having said why. */
.vh {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* Buttons on a cream ground. The base .btn is cream-on-transparent and .btn--solid
   is cream-filled — both vanish on a light background. Inverting them here keeps
   one set of button classes across the site instead of a second vocabulary.
   Contrast: espresso on cream is 12.6:1, cream on espresso 12.6:1. */
.on-cream .btn {
  border-color: rgba(42,33,26,.45);
  color: var(--ink);
}
.on-cream .btn:hover {
  background: var(--espresso); color: var(--cream); border-color: var(--espresso);
}
.on-cream .btn--solid {
  background: var(--espresso); color: var(--cream); border-color: var(--espresso);
}
.on-cream .btn--solid:hover {
  background: transparent; color: var(--ink); border-color: rgba(42,33,26,.45);
}

/* vertical keyword rail */
.rail {
  list-style: none; margin: 0; padding: 0 0 0 1.4rem;
  border-left: 1px solid rgb(var(--cream-text-rgb) / .3);
  font-size: var(--fs-label); letter-spacing: .18em; text-transform: uppercase;
  line-height: 2.2; color: rgb(var(--cream-text-rgb) / .9);
}
.rail--right { border-left: none; border-right: 1px solid rgba(32,25,19,.22); padding: 0 1.4rem 0 0; text-align: right; }
.on-cream .rail { border-color: rgba(32,25,19,.28); color: rgba(32,25,19,.75); }

/* ---------- nav ---------- */

.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  height: var(--nav-h);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 0 var(--gutter);
  transition: background .45s var(--ease), height .45s var(--ease), box-shadow .45s;
}
.nav.is-stuck {
  background: rgb(var(--espresso-deep-rgb) / .93);
  backdrop-filter: blur(14px);
  height: var(--nav-h-stuck);
  box-shadow: 0 1px 0 rgb(var(--cream-text-rgb) / .1);
}

.nav-group { display: flex; align-items: center; gap: clamp(1.4rem, 3.4vw, 3rem); }
.nav-group--right { grid-column: 3; justify-content: flex-end; }

.nav a.nav-link {
  font-size: var(--fs-label); letter-spacing: .16em; text-transform: uppercase;
  color: var(--cream-text);
  position: relative;
  display: inline-flex; align-items: center; min-height: 44px;
}
.nav a.nav-link::after {
  content: ''; position: absolute; left: 0; bottom: 12px; height: 1px; width: 0;
  background: var(--tan); transition: width .45s var(--ease);
}
.nav a.nav-link:hover::after { width: 100%; }

.nav-mark {
  /* Explicit column, not auto-placement. The left nav group is display:none on
     phones; an auto-placed monogram would then take column 1 and drag the
     hamburger into column 2, centring the toggle and pushing the mark left.
     Pinning columns makes the bar independent of whether the spacer exists. */
  grid-column: 2;
  justify-self: center;
  /* the <img> inside is display:block, which collapses an inline <a> to zero
     width; inline-flex gives the link a real box (and a tappable one) */
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px;
}
.nav-mark img { height: var(--logo-h); width: auto; transition: height .45s var(--ease); }
.nav.is-stuck .nav-mark img { height: var(--logo-h-stuck); }

.btn {
  display: inline-block;
  font-size: var(--fs-label); letter-spacing: .16em; text-transform: uppercase;
  padding: .9rem 1.6rem;
  border: 1px solid rgb(var(--cream-text-rgb) / .55);
  color: var(--cream-text);
  transition: background .4s var(--ease), color .4s var(--ease), border-color .4s;
  white-space: nowrap;
}
.btn:hover { background: var(--cream); color: var(--ink); border-color: var(--cream); }
.btn--solid { background: var(--cream); color: var(--ink); border-color: var(--cream); }
.btn--solid:hover { background: transparent; color: var(--cream-text); border-color: rgb(var(--cream-text-rgb) / .55); }

.nav-toggle {
  display: none; background: none; border: 0; cursor: pointer; padding: .6rem;
  width: 44px; height: 44px;
}
.nav-toggle span {
  display: block; height: 1px; background: var(--cream); margin: 6px 0;
  transition: transform .4s var(--ease), opacity .3s;
}
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.drawer {
  position: fixed; inset: 0; z-index: 99;
  background: var(--espresso-deep);
  display: flex; flex-direction: column; align-items: center;
  /* `safe center` centres while there is room and falls back to flex-start when
     there is not. Plain `center` overflows in BOTH directions once the content
     is taller than the box, so on a short screen the first link rode up under
     the nav and out of reach — scrolling cannot recover content that overflows
     the start edge. The unprefixed `center` before it is the fallback for
     engines without `safe`. */
  justify-content: center;
  justify-content: safe center;
  gap: clamp(1rem, 3.2vh, 1.6rem);
  /* Pad the top by the nav's height. The drawer centres its links in the
     viewport, and the nav sits above it at z-index 100 — so a sixth link
     ("Home") grew the stack enough that the first item rode up underneath the
     monogram. Padding keeps the centring inside the space the bar doesn't
     occupy. overflow-y keeps every link reachable on a short screen, where six
     items plus padding can exceed the viewport height. */
  padding: var(--nav-h) var(--gutter) 2rem;
  overflow-y: auto;
  opacity: 0; visibility: hidden; transition: opacity .45s var(--ease), visibility .45s;
}
.drawer.is-open { opacity: 1; visibility: visible; }
.drawer a {
  font-family: var(--display); font-size: clamp(30px, 7vw, 40px); text-transform: uppercase;
  letter-spacing: .08em; font-weight: 300;
}

/* ---------- hero ---------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex; flex-direction: column; justify-content: center;
  padding: calc(var(--nav-h) + 3rem) var(--gutter) 5rem;
  overflow: hidden;
}
.hero::before {
  content: ''; position: absolute; inset: 0;
  background: url('../img/hero.jpg?v=82a6821f') center 42% / cover no-repeat;
  /* WebP where it decodes (~35% smaller), the JPEG everywhere else. build.sh
     stamps each url() separately, so both stay cache-busted. */
  background-image: image-set(url('../img/hero.webp?v=946ca8a3') type('image/webp'), url('../img/hero.jpg?v=82a6821f') type('image/jpeg'));
  transform: scale(1.06);
  animation: heroDrift 26s ease-in-out infinite alternate;
}
.hero::after {
  content: ''; position: absolute; inset: 0;
  background:
    linear-gradient(to bottom, rgb(var(--scrim-rgb) / .55) 0%, rgb(var(--scrim-rgb) / .2) 38%, rgb(var(--scrim-rgb) / .72) 100%),
    radial-gradient(120% 80% at 30% 45%, rgb(var(--scrim-rgb) / 0) 0%, rgb(var(--scrim-rgb) / .55) 100%);
}
@keyframes heroDrift { from { transform: scale(1.06) translate3d(0,0,0); } to { transform: scale(1.14) translate3d(-1.2%, -1.6%, 0); } }

.hero-inner {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 3rem; align-items: center;
  width: 100%;
}

.hero-wordmark { width: min(52vw, 620px); }
.hero-wordmark img { width: 100%; filter: drop-shadow(0 18px 44px rgba(0,0,0,.45)); }

.hero-tagline {
  display: flex; align-items: center; gap: 1.5rem;
  margin-top: 2.4rem;
  font-size: var(--fs-label); letter-spacing: .18em; text-transform: uppercase;
  color: var(--cream-text);
  white-space: nowrap;
}
.hero-tagline::before { content: ''; width: clamp(48px, 9vw, 130px); height: 1px; background: rgb(var(--cream-text-rgb) / .5); flex: none; }

.scroll-cue {
  position: absolute; left: 50%; bottom: 2rem; transform: translateX(-50%);
  z-index: 2; font-size: var(--fs-tiny); letter-spacing: .2em; text-transform: uppercase;
  color: rgb(var(--cream-text-rgb) / .82); display: flex; flex-direction: column; align-items: center; gap: .7rem;
}
.scroll-cue i { display: block; width: 1px; height: 46px; background: linear-gradient(rgb(var(--cream-text-rgb) / .6), transparent); }

/* ---------- section scaffolding ---------- */

.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
}

.panel { padding: clamp(3rem, 7vw, 6.5rem) clamp(1.5rem, 5vw, 5rem); display: flex; flex-direction: column; justify-content: center; }
.panel--cream { background: var(--cream); color: var(--ink); }
.panel--dark { background: var(--espresso-deep); }

.figure { position: relative; min-height: 420px; overflow: hidden; }
.figure img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.figure .rail-overlay {
  position: absolute; left: clamp(1rem, 3vw, 2.6rem); top: 50%; transform: translateY(-50%); z-index: 2;
}

/* ---------- about ---------- */

#about .panel--cream h2 { margin-bottom: 1.9rem; }

/* ---------- services ---------- */

.services {
  position: relative;
  padding: var(--section-y) var(--gutter);
  background: var(--espresso-deep);
  overflow: hidden;
}
.services::before {
  content: ''; position: absolute; inset: 0;
  background: url('../img/services.jpg?v=948ddf6a') center / cover no-repeat;
  background-image: image-set(url('../img/services.webp?v=f72bffa5') type('image/webp'), url('../img/services.jpg?v=948ddf6a') type('image/jpeg'));
  opacity: .62;
}
.services::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(105deg, rgb(var(--espresso-deep-rgb) / .9) 0%, rgb(var(--espresso-deep-rgb) / .62) 55%, rgb(var(--espresso-deep-rgb) / .85) 100%);
}
.services > * { position: relative; z-index: 2; }

.services-grid {
  display: grid; grid-template-columns: 1.05fr .95fr; gap: clamp(2.5rem, 6vw, 6rem); align-items: center;
}

/* A plain list of what the salon does. The rules between items are real
   structure (they separate entries); there is no hover state because nothing
   here is clickable — anything that looks interactive should be. */
.svc-list { list-style: none; margin: 0; padding: 0; }
.svc-list li {
  border-bottom: 1px solid rgb(var(--cream-text-rgb) / .22);
  padding: 1.1rem .2rem;
  font-size: var(--fs-service); letter-spacing: .18em; text-transform: uppercase;
  color: var(--cream-text);
}
.svc-list li:first-child { border-top: 1px solid rgb(var(--cream-text-rgb) / .22); }

/* ---------- quote ---------- */

.quote-text {
  font-family: var(--display);
  font-weight: 300;
  font-size: clamp(28px, 3.4vw, 50px);
  line-height: 1.24;
  letter-spacing: .05em;
  text-transform: uppercase;
  margin: 0;
  color: var(--cream-text);
  max-width: 14ch;
}

/* Centre the quote in its half of the band.

   It was `align-items: flex-start`, which pinned the block to the panel's left
   edge. Combined with `max-width: 14ch` — narrow on purpose, so the quote breaks
   into short lines — that left a wide empty gap to the right and made the panel
   look mis-set rather than deliberately ragged.

   align-items centres the BLOCK on the flex cross axis; text-align centres the
   lines inside it. Both are needed: the first alone leaves ragged-right lines
   sitting in a centred box, which still reads as off. */
#quote .panel--dark { align-items: center; text-align: center; }
/* margin-bottom: 0 matters. .quote-attrib is a <p>, so it picks up the base
   rule's `margin: 0 0 1.5rem`. Flex centring counts that trailing margin as part
   of the group, so the visible text sat exactly half of it — 21px — above the
   panel's true centre. Invisible as a cause, measurable as an effect. */
.quote-attrib { margin: 2rem 0 0; font-size: var(--fs-label); letter-spacing: .2em; text-transform: uppercase; color: var(--sand); }

/* ---------- artists ---------- */

.artists {
  background: var(--cream); color: var(--ink);
  padding: var(--section-y) var(--gutter);
}
.artists-grid {
  display: grid; grid-template-columns: 1fr 1.05fr auto;
  gap: clamp(2rem, 5vw, 4.5rem); align-items: center;
}
/* Square, and square on purpose: artists.jpg is a 1:1 portrait of Jamie and the
   box has to match it, or `object-fit: cover` crops his arms off at the sides.
   min-height must be zeroed — the 420px floor on .figure would otherwise win on
   narrow columns and re-introduce the crop. */
.artists .figure { aspect-ratio: 1 / 1; min-height: 0; }

/* ---------- instagram gallery ---------- */

.gallery {
  background: var(--espresso);
  padding: var(--section-y) var(--gutter);
}
.gallery-head {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: 1.5rem; margin-bottom: clamp(2rem, 4vw, 3rem);
}
.gallery-head .eyebrow { margin-bottom: .9rem; }
.gallery-handle {
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: var(--fs-label); letter-spacing: .16em; text-transform: uppercase;
  padding-bottom: .5rem; border-bottom: 1px solid rgb(var(--cream-text-rgb) / .4);
  transition: color .3s, border-color .3s;
}
.gallery-handle:hover { color: var(--tan); border-color: var(--tan); }
/* @goldenheightssalon is a single unbreakable token; at 320px the tracking
   pushes it past the gutter. Tighten rather than wrap — a broken handle mid-word
   reads as a typo. */
@media (max-width: 380px) {
  .gallery-handle { font-size: var(--fs-small); letter-spacing: .08em; }
  /* .btn is white-space: nowrap, so its min-content is the whole label. At
     1.6rem side padding "Book with Jamie" measures 299px, which is wider than
     a 320px screen minus gutters — and because it sits in a grid column, it
     widened the column and pushed the artist photo off-screen with it. Trim
     the padding and tracking rather than letting the button wrap; a
     two-line button reads as broken. Vertical padding is untouched, so the
     44px touch target survives. */
  .btn { padding: .9rem 1rem; letter-spacing: .12em; }
}

/* ---------- the gallery is a horizontal scroller, not a grid ----------
   Jacob: "we can't have just four static photos". Every photo in the feed is
   shown; the visitor swipes or uses the arrows. Native overflow scrolling with
   scroll-snap does the work — touch, trackpad, wheel and keyboard all come for
   free, and the JS only drives the arrow buttons and the progress bar.

   The track bleeds off the right edge of the page (negative margin equal to
   the gutter, padding to put it back) so the last visible tile is always cut
   mid-photo. That partial tile is the strongest "there's more" cue there is;
   arrows alone get missed. */
.gallery-scroller {
  margin-right: calc(-1 * var(--gutter));
}
.gallery-grid {
  --tile: clamp(220px, 26vw, 380px);
  display: flex; gap: clamp(.5rem, 1vw, .85rem);
  list-style: none; margin: 0; padding: 0 var(--gutter) .25rem 0;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity; scroll-padding-inline: 0;   /* proximity, not mandatory: the last tile's start lies past the max scroll offset, and mandatory can refuse to rest there in some engines */
  overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch;
  scrollbar-width: none;   /* the progress bar below is the scrollbar */
}
.gallery-grid::-webkit-scrollbar { display: none; }
.gallery-grid li {
  position: relative; flex: 0 0 var(--tile); aspect-ratio: 1 / 1; overflow: hidden;
  background: var(--espresso-soft); scroll-snap-align: start;
}
.gallery-grid img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transition: transform 1.1s var(--ease), opacity .5s var(--ease);
}
.gallery-grid a { display: block; position: absolute; inset: 0; }
.gallery-grid a::after {
  content: ''; position: absolute; inset: 0;
  background: rgb(var(--espresso-deep-rgb) / .42);
  opacity: 0; transition: opacity .5s var(--ease);
}
.gallery-grid a:hover img { transform: scale(1.07); }
.gallery-grid a:hover::after { opacity: 1; }
.gallery-grid .ig-mark {
  position: absolute; z-index: 2; left: 50%; top: 50%; transform: translate(-50%,-50%) scale(.85);
  width: 22px; height: 22px; fill: var(--cream);
  opacity: 0; transition: opacity .45s var(--ease), transform .45s var(--ease);
}
.gallery-grid a:hover .ig-mark { opacity: 1; transform: translate(-50%,-50%) scale(1); }
@media (prefers-reduced-motion: no-preference) { .gallery-grid { scroll-behavior: smooth; } }

/* progress bar + arrows. Hidden by gallery.js when every tile already fits. */
.gallery-controls {
  display: flex; align-items: center; gap: 1rem; margin-top: clamp(1.2rem, 2.5vw, 1.8rem);
}
.gallery-progress {
  flex: 1; height: 1px; position: relative; background: rgb(var(--cream-text-rgb) / .18);
}
.gallery-progress i {
  position: absolute; top: -1px; height: 3px; left: 0; width: 25%;
  background: var(--tan); transition: left .15s linear, width .15s linear;
}
.gallery-btn {
  width: 48px; height: 48px; border-radius: 50%; flex: 0 0 auto;
  border: 1px solid rgb(var(--cream-text-rgb) / .4); background: transparent;
  color: var(--cream-text); font: inherit; font-size: 1.1rem; line-height: 1; cursor: pointer;
  display: grid; place-items: center;
  transition: background .35s var(--ease), color .35s, border-color .35s, opacity .3s;
}
.gallery-btn:hover { background: var(--cream); color: var(--ink); border-color: var(--cream); }
.gallery-btn[aria-disabled="true"] { opacity: .35; cursor: default; }
.gallery-btn[aria-disabled="true"]:hover { background: transparent; color: var(--cream-text); border-color: rgb(var(--cream-text-rgb) / .4); }
.gallery-btn:focus-visible { outline: 2px solid var(--tan); outline-offset: 3px; }

.gallery-note {
  margin: 1.6rem 0 0; font-size: var(--fs-small); letter-spacing: .14em; text-transform: uppercase;
  color: rgb(var(--cream-text-rgb) / .85); max-width: none;
}


/* ---------- booking strip ---------- */

.booking {
  background: var(--espresso);
  padding: var(--section-y) var(--gutter);
  text-align: center;
  border-top: 1px solid rgb(var(--cream-text-rgb) / .12);
}
.booking h2 { margin-bottom: 1.4rem; }
.booking p { margin-left: auto; margin-right: auto; }
.vagaro-slot {
  margin: 2.8rem auto 0;
  max-width: 1000px;
  min-height: 220px;
  border: 1px dashed rgb(var(--cream-text-rgb) / .28);
  display: flex; align-items: center; justify-content: center;
  padding: 2.5rem 1.5rem;
}
.vagaro-slot .note {
  font-size: .8rem; letter-spacing: .18em; text-transform: uppercase;
  color: rgb(var(--cream-text-rgb) / .85); max-width: 42ch; line-height: 2.2; margin: 0;
}

/* ---------- footer ---------- */

.footer { background: var(--espresso-deep); padding: clamp(3rem, 6vw, 4.5rem) var(--gutter) 0; }
/* Three tracks, not four, so the signature lands on the page's centre line.

   It used to be `auto 1fr auto 1fr` with the monogram, nav, signature and
   address as four separate children. Those tracks are not symmetric — the
   monogram's `auto` track was 81px while the trailing `1fr` was 359px — so the
   signature's track sat 77px right of centre at 1440. Grouping the monogram and
   nav into one `.footer-brand` cell makes the flanks a matched `1fr` pair, and
   a centred `auto` track between two equal tracks is centred in the grid box,
   which is itself symmetric about the page.

   The dividers hang off the FLANKS rather than coming from a `> * + *` rule.
   A border and padding on the signature itself would grow its track and push
   the artwork off-centre inside it — the padding has to live on its neighbours. */
.footer-grid {
  display: grid; grid-template-columns: 1fr auto 1fr; gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
  padding-bottom: 3rem;
}
.footer-brand {
  display: flex; align-items: center;
  gap: clamp(1.5rem, 4vw, 3.5rem);
}
.footer-brand .footer-nav,
.footer-contact {
  border-left: 1px solid rgb(var(--cream-text-rgb) / .16);
  padding-left: clamp(1.5rem, 4vw, 3.5rem);
}
.footer-brand { border-right: 1px solid rgb(var(--cream-text-rgb) / .16); padding-right: clamp(1.5rem, 4vw, 3.5rem); }
.footer-mark img { height: clamp(84px, 7vw, 116px); width: auto; opacity: .92; }

.footer-nav { list-style: none; margin: 0; padding: 0; }
.footer-nav a {
  display: inline-block; padding: .6rem 0;
  font-size: var(--fs-label); letter-spacing: .16em; text-transform: uppercase;
  line-height: 1.4; color: var(--cream-text); transition: color .3s;
}
.footer-nav a:hover { color: var(--tan); }

/* The footer signature.

   This is the REAL artwork from the reference design, supplied by Jacob —
   img/signature.png, a white-on-transparent export trimmed to its ink.

   It is applied as a MASK rather than an <img> so the lettering takes
   --cream-text and keeps following the palette. Paint it as an image and the
   white is baked in: fine today, wrong the next time the ground moves.

   The two-line lockup underneath is the fallback: browsers without mask-image
   get real text instead of an empty box, which is why the spans stay in the
   markup and are only hidden inside @supports. In practice that branch is
   unreachable — the page already relies on aspect-ratio and clamp(), both newer
   than -webkit-mask — so the Qwigley WEBFONT was dropped rather than shipped to
   everyone for a case that cannot happen. The family is still named in --script,
   so it renders if it is installed, and degrades to system cursive if not.

   The first export was clipped at the bottom — the "S" descender loop was cut
   mid-stroke at the image edge. This one is complete (4px of clear space below
   the lowest ink). If the artwork is ever replaced again, re-run the trim and
   update the aspect-ratio below to whatever the new export measures; the ratio
   is the one number here that is not derived at runtime. */
.footer-script {
  font-family: var(--script);
  font-size: clamp(40px, 4.8vw, 60px);
  line-height: .92;
  color: var(--cream-text);
  display: flex; flex-direction: column; align-items: center;
}
.footer-script span { display: block; transform-origin: 50% 60%; }
.footer-script span:first-child  { transform: rotate(-6deg); }
.footer-script span:last-child   { transform: rotate(-1.5deg) translateX(-.1em); }

@supports (mask-image: url('')) or (-webkit-mask-image: url('')) {
  .footer-script {
    width: clamp(190px, 21vw, 280px);
    aspect-ratio: 1.6931;            /* the export's own ratio — 640 x 378 */
    background-color: var(--cream-text);
    -webkit-mask: url('../img/signature.png?v=7644e3a0') center / contain no-repeat;
            mask: url('../img/signature.png?v=7644e3a0') center / contain no-repeat;
  }
  .footer-script span { display: none; }
}

.footer-contact { font-size: var(--fs-label); letter-spacing: .14em; text-transform: uppercase; line-height: 1.9; color: rgb(var(--cream-text-rgb) / .92); }
.footer-addr { display: block; color: inherit; transition: color .3s; }
.footer-addr span { display: block; }
.footer-addr:hover, .footer-tel:hover { color: var(--tan); }
.footer-tel { display: inline-block; min-height: 44px; color: inherit; transition: color .3s; }
.footer-contact .rule { height: 1px; background: rgb(var(--cream-text-rgb) / .22); margin: 1.1rem 0 1.3rem; }
.socials { display: flex; gap: .8rem; margin-top: .4rem; }
.socials a {
  width: 44px; height: 44px; border-radius: 50%;
  border: 1px solid rgb(var(--cream-text-rgb) / .4);
  display: grid; place-items: center;
  transition: background .35s var(--ease), color .35s, border-color .35s;
}
.socials a:hover { background: var(--cream); color: var(--ink); border-color: var(--cream); }
.socials svg { width: 17px; height: 17px; fill: currentColor; }

.footer-bar {
  border-top: 1px solid rgb(var(--cream-text-rgb) / .14);
  padding: 1.5rem 0 1.8rem;
  display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  font-size: var(--fs-tiny); letter-spacing: .14em; text-transform: uppercase;
  color: rgb(var(--cream-text-rgb) / .85);
}

/* ---------- reveal ---------- */

.reveal { opacity: 0; transform: translateY(26px); transition: opacity 1s var(--ease), transform 1s var(--ease); }
.reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .reveal { opacity: 1; transform: none; } }

/* ---------- responsive ---------- */

@media (max-width: 1024px) {
  /* The nav collapses to the drawer at 1024, not 860.

     860 was fine with two links on the left. Adding a third ("Home") pushed the
     two groups past the bar's width: between 861 and 1024 the right-hand group
     and the Book Now button ran off the right edge, and the monogram slid up to
     61px off centre. The links set the floor, so the breakpoint has to follow
     the content rather than stay at a round number someone once picked.

     Everything hidden here is in the drawer, Home included. */
  .nav { grid-template-columns: 1fr auto 1fr; }
  .nav-group { display: none; }
  .nav-group--right { display: flex; }
  /* These need `.nav` and the element selector to outweigh the base rule.
     `.nav a.nav-link` is 0-2-1; `.nav-group--right .nav-link` is only 0-2-0,
     so the base `display: inline-flex` won and the desktop links stayed on
     screen at phone widths, running off the right edge. A media query adds no
     specificity of its own — it is the selector that has to win. The base rule
     grew a `display` when the links were given 44px touch targets, which is
     what silently broke this. */
  .nav .nav-group--right a.nav-link { display: none; }
  .nav-toggle { display: block; }
  /* The Book Now button is the site's one conversion. It stays beside the
     hamburger on tablets (iPad landscape is 1024) and only folds into the
     drawer on phones — see the 600px block. */
  .nav .nav-group--right { gap: clamp(.8rem, 2vw, 1.6rem); }
  /* Was `display: flex; visibility: hidden` — invisible but still occupying
     ~180px of min-content, which pushed the hamburger 28px off the right edge
     at 320px. `1fr auto 1fr` centres the monogram on its own with the side
     columns empty, so the spacer was never needed. display:none also takes
     these links out of the accessibility tree on mobile, where the drawer is
     the real navigation. */
  .nav-group--left { display: none; }

  .artists-grid { grid-template-columns: 1fr 1fr; }
  .artists-grid .rail--right { display: none; }
  /* Three children now, so the old nth-child(3)/(4) rules addressed the wrong
     boxes. Brand on its own row, then signature and address side by side. */
  .footer-grid { grid-template-columns: auto 1fr; row-gap: 2.5rem; }
  .footer-brand { grid-column: 1 / -1; border-right: 0; padding-right: 0; }
  .footer-script { justify-self: start; }
}

@media (max-width: 860px) {

  .hero { min-height: 92svh; }
  .hero::before {
    background-image: url('../img/hero-sm.jpg?v=f02f5ec3');
    background-image: image-set(url('../img/hero-sm.webp?v=ad93ebf9') type('image/webp'), url('../img/hero-sm.jpg?v=f02f5ec3') type('image/jpeg'));
    background-position: center;
  }
  /* The rail's last line and the scroll cue shared the same 40px at 390x844 —
     the cue's hairline ran straight through "STANDARD". Four rail items above
     the fold already say "there's more"; the cue is decoration here. */
  .scroll-cue { display: none; }
  /* 175% root gives 20px paragraphs on a 390px screen and an 8,900px home page.
     150% lands paragraphs at ~17px and labels at ~17px; display headings are
     pinned in px so they don't move. Still a percentage, so the visitor's own
     browser text size is respected. */
  html { font-size: 150%; }
  .hero-inner { grid-template-columns: 1fr; gap: 2.2rem; }
  .hero-wordmark { width: min(86vw, 460px); }

  /* The tagline carried white-space: nowrap, which held "Hair that fits you.
     Beauty that lasts." on one 460px line inside a 350px column. body has
     overflow-x: hidden, so nothing scrolled and no scrollbar appeared — the
     sentence just ended mid-word ("BEAUTY T") and looked deliberate. Let it
     wrap; the rule stays beside it. */
  .hero-tagline { white-space: normal; }
  .hero-inner .rail { border-left: 1px solid rgb(var(--cream-text-rgb) / .3); }

  .split { grid-template-columns: 1fr; }
  #about .figure { order: -1; min-height: 62vh; }
  #quote .figure { min-height: 48vh; }
  .quote-text { max-width: 100%; }

  .services-grid { grid-template-columns: 1fr; gap: 2.6rem; }
  .artists-grid { grid-template-columns: 1fr; }
  .artists .figure { aspect-ratio: 1 / 1; min-height: 0; order: -1; }

  .footer-grid { grid-template-columns: 1fr; text-align: left; }
  .footer-grid > * + *,
  .footer-brand .footer-nav {
    border-left: 0; padding-left: 0;
    border-top: 1px solid rgb(var(--cream-text-rgb) / .14); padding-top: 2rem;
  }
  .footer-brand { display: flex; flex-direction: column; align-items: flex-start; gap: 1.6rem; }
  .footer-script { align-items: flex-start; justify-self: start; }
}

@media (max-width: 600px) {
  .nav .nav-group--right a.btn { display: none; }
  .gallery-grid { --tile: min(72vw, 300px); }
}

@media (max-width: 480px) {
  .hero-tagline { letter-spacing: .12em; font-size: var(--fs-small); gap: .9rem; }
  .hero-tagline::before { width: 30px; }
  h2.display { font-size: clamp(32px, 9vw, 44px); }
}
