/* Aura & Armour — The Nail Lounge
   Studio geometry, brand colour and type. See the design language:
   one ground per section, gold is a detail never a fill, square corners,
   photographs in plain unrounded rectangles. */

:root {
  --cream: #F5EDE4;
  --tan: #E9CBA9;
  --blush: #ECD7CB;
  --gold: #A87D3A;
  --wine: #7C3A4D;
  --ink: #211A1C;
  --ink-soft: #4D4143;
  --mute: #655549; /* AA-compliant: 6.14:1 on cream, 4.61:1 on tan — do not lighten */
  --line: rgba(33, 26, 28, 0.13);

  --serif: 'Bodoni Moda', Georgia, serif;
  --body: 'Cormorant Garamond', Georgia, serif;
  --sans: 'Jost', system-ui, sans-serif;
  --script: 'Mrs Saint Delafield', cursive;

  --maxw: 1180px;
  --pad: clamp(20px, 5vw, 72px);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--body);
  font-size: 19px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 500;
  margin: 0;
  line-height: 1.02;
  letter-spacing: -0.01em;
}

img { max-width: 100%; height: auto; display: block; }

a { color: var(--wine); }

/* ---------- layout ---------- */
.section { padding: clamp(72px, 10vw, 130px) var(--pad); }
.wrap { max-width: var(--maxw); margin: 0 auto; }
.ground-cream { background: var(--cream); }
.ground-tan { background: var(--tan); }
.ground-ink { background: var(--ink); color: var(--cream); }

/* ---------- type helpers ---------- */
.kicker {
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--wine);
  font-weight: 500;
}

/* Gold hairline — a detail, never a fill. */
.rule { border: 0; border-top: 1px solid var(--gold); width: 180px; margin: 26px 0; }


/* ---------- buttons ---------- */
.btn {
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 500;
  padding: 15px 30px;
  border: 1px solid transparent;
  border-radius: 0;              /* square corners everywhere */
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  transition: background-color .2s ease, color .2s ease, border-color .2s ease;
}
/* Hidden by default (desktop shows the full label as plain text); only the
   mobile hero-buttons rule below reveals this and hides its sibling. */
.btn-text-short { display: none; }
.btn-primary { background: var(--wine); color: #fff; }
.btn-primary:hover { background: #5F2A39; }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn-ghost:hover { background: var(--ink); color: var(--cream); }
.ground-ink .btn-ghost { border-color: var(--cream); color: var(--cream); }
.ground-ink .btn-ghost:hover { background: var(--cream); color: var(--ink); }

/* ---------- focus ---------- */
:focus-visible {
  outline: 2px solid var(--wine);
  outline-offset: 3px;
}
.ground-ink :focus-visible {
  outline-color: var(--gold);
}

/* ---------- photography ----------
   Plain rectangles. Never rounded, never framed twice. */
.photo { margin: 0; border-radius: 0; overflow: hidden; background: var(--blush); }
.photo img { display: block; width: 100%; object-fit: cover; }

/* ---------- motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  /* The generic rule above would freeze the marquee mid-frame (a near-zero
     duration still runs one iteration to completion almost instantly,
     landing on an arbitrary point in the loop). Stop it outright instead
     and let people scroll the strip by hand — a class selector's higher
     specificity wins the !important tie against the universal rule above,
     regardless of source order. */
  .gallery-track {
    animation: none !important;
    transform: none !important;
  }
  .gallery-viewport {
    overflow-x: auto;
  }
  /* Same reasoning as the marquee above: the generic near-zero-duration
     override still runs each plate's keyframes to completion once, and
     with negative animation-delay values that lands every image, caption
     and tick at an arbitrary (and different) point in its own cycle —
     a half-crossfaded stack, not a single static plate. Cut the animation
     outright and pin the first plate, first caption and first tick visible;
     everything else is one image column, not a working carousel. */
  .plate-img, .plate-caption, .plate-tick {
    animation: none !important;
    transform: none !important;
  }
  .plate-img, .plate-caption { opacity: 0; }
  .plate-tick { opacity: 0.35; }
  .plate-img:first-child,
  .plate-caption:first-child,
  .plate-tick:first-child {
    opacity: 1;
  }
}

/* Sticky nav is 107px tall on phones (the wordmark wraps to two lines) —
   without this, an anchor jump to a section heading lands it partly behind
   the nav. */
header[id], section[id] { scroll-margin-top: 115px; }

/* ---------- offer bar ---------- */
.offer-bar {
  background: var(--wine);
  color: #fff;
  text-align: center;
  padding: 9px var(--pad);
}
.offer-bar p {
  margin: 0;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* ---------- nav ---------- */
.nav {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 16px var(--pad);
  background: var(--cream);
}
.nav.scrolled { border-bottom: 1px solid var(--gold); }
.nav > * { min-width: 0; }
/* The nav CTA duplicates the hero's WhatsApp button, which sits ~200px
   further down the page — nothing is lost by hiding it below 900px, and it
   is this button's min-content width that was forcing horizontal scroll on
   narrow phones. */
.nav > .btn-primary { display: none; }
@media (min-width: 900px) {
  .nav > .btn-primary { display: inline-flex; }
}
.wordmark {
  display: flex; align-items: center; gap: 12px;
  color: var(--ink); text-decoration: none; line-height: 1;
  margin: 0;
}
.wordmark-logo {
  height: 38px; width: 38px; flex: 0 0 auto;
  border-radius: 50%; display: block;
}
.wordmark-text { margin: 0; line-height: 1; }
.wordmark .amp { font-style: italic; color: var(--gold); }
.wordmark-main {
  display: block; font-family: var(--serif); font-weight: 600; font-size: 20px;
  letter-spacing: 0.06em;
}
/* The nav lost its two-line "by Aura & Armour" sub-line (§4 revision), so
   its wordmark is a single-line "Aura & Armour" — clamp it down on the
   narrowest phones (with the logo mark, toggle button and offer-bar padding
   all sharing the same 320px) so it never wraps to two lines and forces the
   row taller than the rest of the nav controls. */
.nav .wordmark-main { font-size: clamp(16px, 4.6vw, 20px); white-space: nowrap; }
.wordmark-sub {
  display: block; font-family: var(--sans); font-size: 9px;
  letter-spacing: 0.3em; text-transform: uppercase; color: var(--mute); margin-top: 5px;
}
.ground-ink .wordmark-sub { color: rgba(245,237,228,0.6); }
.nav-links { display: flex; gap: 30px; }
.nav-links a {
  font-family: var(--sans); font-size: 12px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-soft); text-decoration: none;
}
.nav-links a:hover { color: var(--ink); }
.nav-toggle {
  display: none; font-family: var(--sans); font-size: 12px;
  letter-spacing: 0.16em; text-transform: uppercase;
  background: none; border: 1px solid var(--ink); padding: 9px 16px; cursor: pointer;
}

@media (max-width: 900px) {
  .nav-toggle { display: block; }
  .nav-links {
    display: none; position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; gap: 0; background: var(--cream);
    border-bottom: 1px solid var(--gold); padding: 8px var(--pad) 20px;
  }
  .nav-links.open { display: flex; }
  .nav-links a { padding: 12px 0; }
}

/* ---------- hero ----------
   The hero is now laid out on the same content grid as every other
   section: the header carries a `.wrap` (max-width: 1180px, centred) that
   wraps `.hero-grid`, exactly like `<section class="section">` +
   `.wrap` below it. That's what makes the copy's left edge and the
   image's right edge land on the same vertical lines as the "Our
   services" kicker and the gallery/reviews content — both systems share
   the same var(--pad) outer gutter and the same 1180px inner cap.
   Only the vertical padding is special-cased: the hero must stay flush
   against the nav's bottom edge (no gap above the image, approved
   earlier), so `.section.hero` zeroes the vertical component of
   `.section`'s padding shorthand while keeping the horizontal var(--pad)
   that produces the outer gutter `.wrap` centres inside.
   `.section.hero` (double-class selector, specificity 0,2,0) is a
   deliberate override — the header element also carries the plain
   `.section` class for its other shared styling, and `.hero`'s reset must
   win regardless of the two rules' source order. */
.section.hero { padding: 0 var(--pad); }
/* The type column is sized to its own content measure (not a fraction of
   the viewport) so the space left over reads as a deliberate gutter next
   to the image rather than empty cream. `min(520px, 46%)` caps the column
   at the copy's natural width on wide screens and lets it fold down
   proportionally on the narrower 1024/1280 steps instead of holding 520px
   rigid and starving the image column. `.hero-lead`'s own `max-width`
   (below) is what actually stops the text at ~58ch — this track just
   gives that measure a matching box instead of a 640px-wide column with
   the paragraph stranded on its left edge. */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, min(520px, 46%)) 1fr;
  column-gap: clamp(48px, 4vw, 72px);
  align-items: stretch;
}
.hero-copy {
  padding: clamp(40px, 6vw, 72px) 0 56px;
}

.masthead { display: block; }
.masthead-name {
  display: block;
  font-family: var(--serif);
  font-size: clamp(44px, 6.5vw, 92px);
  line-height: 0.95;
  letter-spacing: -0.01em;
}
.masthead-by {
  display: block;
  font-family: var(--body);
  font-style: italic;
  font-size: clamp(19px, 2vw, 26px);
  color: var(--ink-soft);
  font-weight: 400;
}
/* The gold hairline reads as part of the masthead device, not a separate
   rule element — a ::before on the by-line keeps the markup to exactly the
   two spans the masthead needs while still sitting directly under the name. */
.masthead-by::before {
  content: '';
  display: block;
  width: 120px;
  height: 1px;
  background: var(--gold);
  margin: 16px 0 14px;
}

/* 58ch keeps the readable ~60-70-characters-per-line band in Cormorant
   Garamond at the hero's 19px body size, and — not coincidentally — is
   what the 520px type column above was measured to hold. Widening past
   this to fill the column would trade readability for looking busy;
   narrower is what caused the void this fix closes. */
.hero-lead { max-width: 58ch; color: var(--ink-soft); margin-top: 28px; }
.hero-actions { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 32px; }

/* ---------- hero plate (rotating image) ---------- */
.hero-plate { position: relative; display: flex; flex-direction: column; min-height: 420px; }
.plate-stack { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; background: var(--blush); }
/* plate-cycle's @keyframes, the per-item animation-delay on each
   :nth-child, and --plate-duration are all generated by
   tools/generate-markup.mjs from the actual count of assets/hero/
   photographs, into the <!--BUILD:HERO_ANIMATION--> <style> block in
   <head> — see assets/README.md. That block is what makes the crossfade
   work at any hero count instead of a hardcoded three. */
.plate-img { position: absolute; inset: 0; margin: 0; opacity: 0; animation: plate-cycle var(--plate-duration, 18.6s) ease-in-out infinite; }
.plate-img img { width: 100%; height: 100%; object-fit: cover; display: block; }

.hero-plate:hover .plate-img,
.hero-plate:focus-within .plate-img,
.hero-plate:hover .plate-caption,
.hero-plate:focus-within .plate-caption,
.hero-plate:hover .plate-tick,
.hero-plate:focus-within .plate-tick {
  animation-play-state: paused;
}

/* The caption strip sits on the tan ground below the photograph, never over
   it, so the Jost micro-caps stay AA-safe without a scrim. */
.plate-info {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  background: var(--tan);
  padding: 16px clamp(18px, 3vw, 28px);
}
.plate-captions { position: relative; height: 1.4em; flex: 1 1 auto; min-width: 0; }
.plate-caption {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  margin: 0;
  opacity: 0;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink);
  font-weight: 500;
  animation: plate-caption-cycle var(--plate-duration, 18.6s) ease-in-out infinite;
}

.plate-ticks { display: flex; gap: 8px; flex: 0 0 auto; }
.plate-tick { width: 18px; height: 1px; background: var(--gold); opacity: 0.35; animation: plate-tick-cycle var(--plate-duration, 18.6s) ease-in-out infinite; }

@media (max-width: 900px) {
  /* `.section.hero`'s horizontal var(--pad) padding already applies at
     every width, so the stacked mobile layout keeps both the text and the
     image inside the same gutters instead of either bleeding. */
  .hero-grid {
    grid-template-columns: 1fr;
    padding: clamp(28px, 8vw, 48px) 0 40px;
  }
  /* Required mobile reading order is kicker -> masthead -> rule -> by-line
     -> PLATE -> description -> buttons, i.e. the photograph moves up from
     the end of the stack to directly under the heading. `.hero-copy` never
     changes in the DOM (so focus order for its buttons stays exactly where
     it was); `display: contents` here only unwraps its own box at this
     breakpoint, promoting kicker/masthead/lead/actions to be direct grid
     items of `.hero-grid` alongside `.hero-plate` so each can carry an
     independent `order`. The plate has zero focusable descendants (photos,
     a caption strip, aria-hidden ticks), so moving it purely with `order`
     cannot change tab order — verified by tabbing the built page. This
     entire block is scoped inside the existing max-width:900px query, so
     none of it reaches the desktop two-column grid above. */
  .hero-copy { display: contents; }
  .hero-copy > .kicker { order: 1; }
  .hero-copy > .masthead { order: 2; }
  .hero-plate { order: 3; min-height: 0; margin: 26px 0 clamp(32px, 7vw, 48px); }
  .hero-copy > .hero-lead { order: 4; }
  .hero-copy > .hero-actions { order: 5; }
  .plate-stack { aspect-ratio: 4 / 5; flex: none; }

  /* ---- mobile type scale: bring hero copy to normal phone reading
     sizes instead of the desktop-derived clamp() values. ---- */
  body { font-size: 16.5px; }
  .kicker { font-size: 11px; }
  .masthead-name { font-size: clamp(34px, 9.5vw, 40px); }
  .masthead-by { font-size: clamp(15px, 4vw, 18px); }

  /* ---- hero buttons: one row, shrunk to fit, scoped to the hero only
     (`.hero-actions` is reused verbatim in the services section and the
     footer, which keep their current two-button wrapping layout). ---- */
  .hero-copy > .hero-actions {
    flex-wrap: nowrap;
    gap: 8px;
  }
  .hero-copy > .hero-actions .btn {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    font-size: 10.5px;
    letter-spacing: 0.05em;
    padding: 12px 8px;
    gap: 0;
    white-space: nowrap;
  }
  /* Full accessible name always lives in aria-label on the <a> itself, so
     which span is visible never changes what a screen reader announces. */
  .hero-copy > .hero-actions .btn-text-short { display: inline; }
  .hero-copy > .hero-actions .btn-text-full { display: none; }
}

/* ---------- services ---------- */
.section-h { font-size: clamp(34px, 5vw, 62px); }
.section-lead { color: var(--ink-soft); font-size: 17px; }

.tabs { display: flex; flex-wrap: wrap; gap: 8px; margin: 30px 0 34px; }
.tab {
  font-family: var(--sans); font-size: 12px; letter-spacing: 0.14em;
  text-transform: uppercase; padding: 10px 18px; cursor: pointer;
  background: transparent; color: var(--ink-soft);
  border: 1px solid var(--line); border-radius: 0;
}
.tab[aria-selected="true"] { background: var(--ink); color: var(--tan); border-color: var(--ink); }

/* Every category panel is in the document and visible by default, so the
   menu reads with JavaScript off. tabs.js sets .hidden on the four it isn't
   showing — spelled out here rather than left to the UA sheet, because a
   `display` declaration on .service-panel would otherwise out-specify the
   browser's own [hidden] rule and the panel would never actually hide. */
.service-panel[hidden] { display: none; }

/* The per-category heading only earns its place when the tabs are inert.
   Once tabs.js takes over, the selected tab is the heading. */
.service-list.is-enhanced .service-panel-h { display: none; }
.service-panel-h {
  font-family: var(--sans); font-size: 12px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-soft);
  margin: 34px 0 4px; font-weight: 400;
}
.service-panel:first-child .service-panel-h { margin-top: 0; }

.service-row {
  display: flex; align-items: baseline;
  padding: 18px 0; border-bottom: 1px solid var(--line);
}
.service-name { font-family: var(--body); font-size: clamp(19px, 2.2vw, 25px); }

/* How long the service takes, pushed to the right of the row. Quiet — it is
   reference information, not the thing you are reading the row for. Three
   services have no recorded duration and simply have no span here, so the
   row must still look right with the name alone. */
.service-duration {
  margin-left: auto; padding-left: 18px;
  font-family: var(--sans); font-size: 12px; letter-spacing: 0.1em;
  color: var(--mute); white-space: nowrap;
}

@media (max-width: 900px) {
  /* Same mobile reading scale as the hero: heading down to a normal phone
     size, service names and the "ask us for pricing" line to 16-17px. */
  .section-h { font-size: clamp(30px, 8vw, 36px); }
  .service-name { font-size: 16.5px; }
  .service-duration { font-size: 11px; padding-left: 12px; letter-spacing: 0.06em; }

  /* Category tabs: one scrollable row instead of wrapping to several. Arrow
     key navigation (tabs.js) is unchanged — it still moves aria-selected
     and focus between the same buttons; this only changes how they're laid
     out and adds a scroll-into-view nudge on focus so a keyboard user never
     loses the focused tab off-screen. */
  .tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 2px; /* keeps the focus outline from being clipped */
  }
  .tabs::-webkit-scrollbar { display: none; }
  .tab {
    flex: 0 0 auto;
    scroll-snap-align: start;
  }
}

/* ---------- gallery (auto-scrolling marquee) ----------
   Two copies of the same 8 photographs sit back to back in one flex track;
   translating the track by exactly -50% loops it seamlessly. The viewport
   clips overflow so the strip never widens the page. */
.gallery-viewport { overflow: hidden; }
.gallery-track {
  display: flex; gap: 14px; width: max-content;
  animation: gallery-scroll 48s linear infinite;
}
.gallery-viewport:hover .gallery-track,
.gallery-viewport:focus-within .gallery-track {
  animation-play-state: paused;
}
.gallery-track .photo { flex: 0 0 auto; width: 320px; }
.gallery-track img { aspect-ratio: 4 / 5; }

@keyframes gallery-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

@media (max-width: 900px) {
  /* Each photograph should read as a picture, not a thumbnail — roughly
     70-80% of the viewport width. `100vw` isn't available inside the flex
     item without accounting for `.wrap`'s var(--pad) gutter on each side,
     so this is expressed as calc(100vw - 2 * gutter) * a fraction, which
     lands in the 70-80% band across 320-900px without the item ever
     exceeding the viewport (avoiding page-level horizontal overflow). */
  .gallery-track .photo { width: min(78vw, calc(100vw - 2 * var(--pad) - 20px)); }
}

/* ---------- reviews, social, footer ---------- */
.review-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
.review-card { margin: 0; padding: 26px; background: var(--cream); border: 1px solid var(--line); }
/* Wine, not gold — gold at 14px on cream is 3.20:1, under the 4.5:1 body-text
   floor, and the spec reserves gold for large display text and hairlines. */
.review-stars { margin: 0 0 12px; color: var(--wine); letter-spacing: 0.16em; font-size: 14px; }
.review-card blockquote { margin: 0; font-size: 18px; line-height: 1.6; }
.review-card figcaption { margin-top: 16px; font-family: var(--sans); font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase; }
.review-author { color: var(--ink); }
.review-date { color: var(--mute); display: block; margin-top: 5px; }
.review-more { display: inline-block; margin-top: 12px; font-family: var(--sans); font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; }

/* Instagram embeds set their own width (min ~326px) and height — the grid
   constrains the *column*, not the embed. Each .ig-embed is the container
   Instagram's script leaves in place (it replaces the blockquote in situ),
   so overflow-x:auto there — not on .post-grid or the page — is what keeps
   a narrower-than-326px viewport from widening the whole page. */
.post-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.ig-embed { min-width: 0; max-width: 100%; overflow-x: auto; }

#footer { padding: clamp(56px, 8vw, 96px) var(--pad) 34px; }
.footer-grid { display: grid; grid-template-columns: 1.5fr repeat(4, 1fr); gap: 36px; padding-bottom: 40px; border-bottom: 1px solid rgba(245,237,228,0.18); }
.footer-h { font-family: var(--sans); font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--gold); margin: 0 0 12px; }
/* Footer body copy matches the nav menu links (Jost, --sans) rather than the
   Cormorant Garamond used everywhere else — the owner found the serif hard
   to read against the ink ground. Sized down from the 17px serif and given a
   touch of tracking, since a sans face at serif-body size reads oversized
   and a sans face at zero tracking reads cramped. */
.footer-p { font-family: var(--sans); color: rgba(245,237,228,0.72); font-size: 15px; letter-spacing: 0.01em; line-height: 1.7; margin: 0 0 18px; }
#footer a { color: rgba(245,237,228,0.72); }
#footer a:hover { color: var(--cream); }
.footer-bottom { display: flex; justify-content: space-between; align-items: center; gap: 14px; padding-top: 22px; flex-wrap: wrap; font-family: var(--sans); font-size: 11px; color: rgba(245,237,228,0.5); }
.footer-landmark { color: rgba(245,237,228,0.5); }
.script-signoff { font-family: var(--script); font-size: 30px; color: var(--gold); }

@media (max-width: 900px) {
  /* Reviews: a horizontally snapping row of cards instead of a stacked
     single column. Cards keep every existing style — only the container
     that holds them changes to a scroller. */
  .review-grid {
    display: flex;
    flex-wrap: nowrap;
    gap: 18px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin: 0 calc(-1 * var(--pad));
    padding: 0 var(--pad) 4px;
  }
  .review-grid::-webkit-scrollbar { display: none; }
  .review-card {
    flex: 0 0 85%;
    scroll-snap-align: center;
  }

  /* Instagram: same horizontal-scroller treatment. The embeds set their
     own minimum width (~326px) and height via Instagram's own script, so
     the *wrapper* (.ig-embed) is what gets a fixed scroll-snapped column —
     the embed itself is left alone. */
  .post-grid {
    display: flex;
    flex-wrap: nowrap;
    gap: 14px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin: 0 calc(-1 * var(--pad));
    padding: 0 var(--pad) 4px;
  }
  .post-grid::-webkit-scrollbar { display: none; }
  .ig-embed {
    flex: 0 0 85%;
    scroll-snap-align: center;
    overflow-x: auto;
  }

  .footer-grid { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 480px) {
  .footer-grid { grid-template-columns: 1fr; }
}

/* ---------- footer: tighter on mobile ---------- */
@media (max-width: 900px) {
  #footer { padding: clamp(36px, 8vw, 56px) var(--pad) 20px; }
  .footer-grid { gap: 28px 20px; padding-bottom: 24px; }
  .footer-h { margin: 0 0 8px; }
  .footer-p { margin: 0 0 12px; }
  .footer-bottom { padding-top: 14px; }
}

/* ---------- article pages (service pages) ----------
   The home page is a sequence of full-bleed sections; these are single
   columns of prose. Same type scale and the same gold hairline, set to a
   measure you can actually read a paragraph in. */
.article { max-width: 760px; }
.article-h1 { font-size: clamp(34px, 5vw, 58px); }

/* Sits under the H1 and names the studio, so the heading itself stays the
   service and the location. A styled line, not a heading: the H2s on these
   pages are load-bearing structure (a crawler and an AI answer both read
   them as the question each block answers) and a decorative subtitle
   promoted to H2 would sit in that outline claiming to be a section. */
/* Selector carries .article deliberately: `.article p` further down this
   file is 0,2,0 and would otherwise win the margin outright, leaving the
   subtitle jammed against the H1 with paragraph spacing below it. Written
   as a bare `.article-sub` this rule computed to margin-top: 0 and the
   browser said so while the whole suite stayed green. */
.article .article-sub {
  font-family: var(--sans); font-size: 12px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--mute);
  /* The H1 runs at line-height 1.02, so its last line sits tight to its own
     baseline; a small margin here reads as crowding rather than spacing. */
  margin: 22px 0 0;
}
.article h2 {
  font-size: clamp(25px, 3.2vw, 34px);
  margin: 54px 0 14px;
}
.article h3 {
  font-family: var(--body); font-weight: 600;
  font-size: clamp(19px, 2.2vw, 22px);
  line-height: 1.35;
  margin: 30px 0 6px;
}
.article p { margin: 0 0 18px; }
.article .lead { font-size: clamp(20px, 2.4vw, 24px); color: var(--ink-soft); }
.article ul { margin: 0 0 18px; padding-left: 20px; }
.article li { margin-bottom: 7px; }
.article a { text-underline-offset: 3px; }

/* Breadcrumb. Small, quiet, and a real <nav> so the BreadcrumbList schema
   describes something the page actually shows. */
.crumbs {
  font-family: var(--sans); font-size: 11px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--mute); margin: 0 0 22px;
}
.crumbs a { color: var(--mute); }
.crumbs span { margin: 0 8px; }

/* The "what we offer" list on a service page — the same rows as the home
   page menu, without the tab machinery around them. */
.article .service-row:first-of-type { border-top: 1px solid var(--line); }

/* Closing block: address, hours and the booking buttons. */
.visit-card { margin-top: 22px; }
.visit-card p { margin: 0 0 10px; }
.visit-card .hero-actions { margin-top: 24px; }

@media (max-width: 900px) {
  .article-h1 { font-size: clamp(30px, 8vw, 36px); }
  .article h2 { font-size: clamp(22px, 6vw, 26px); margin: 40px 0 12px; }
  .article .lead { font-size: 17px; }
  .article p, .article li { font-size: 16.5px; }
}

/* The generated "How long does it take?" list on a service page. Built from
   services.json by tools/build.mjs, so the figures here and the ones in the
   home page menu cannot disagree. */
/* Selector carries .article: `.article ul` is (0,1,1) and out-ranks a bare
   .duration-list (0,1,0), so the padding-left: 0 here was being ignored and
   the table sat indented from the prose beside it. Third time this file has
   had that argument; check the computed style, not the declaration. */
.article .duration-list, .duration-list { list-style: none; padding-left: 0; margin: 0 0 18px; }
.duration-list li {
  display: flex; justify-content: space-between; align-items: baseline; gap: 18px;
  padding: 11px 0; border-bottom: 1px solid var(--line);
}
.duration-list li span:last-child {
  font-family: var(--sans); font-size: 12px; letter-spacing: 0.06em;
  color: var(--mute); white-space: nowrap; text-align: right;
}
