/* ==========================================================================
   Myeongdong Hair Studio — Design System v8, "Editorial White"
   --------------------------------------------------------------------------
   v7 said the ground was white but kept a dark, full-viewport hero, a dark
   band mid-page, a dark masthead on every inner page and a four-column dark
   footer. Counted honestly that was more black than white. v8 is the version
   the reference actually describes.

   WHITE AND WARM WHITE ARE THE WHOLE SITE. There is exactly one dark surface
   left — the closing CTA and the single footer row beneath it. Nothing else on
   any page is dark, including the hero, which is now a LIGHT split: the
   photograph fills the band and a near-opaque warm wash over the left turns
   the studio wall into the copy field.

   TYPE IS THE ART DIRECTION.
     display   Playfair Display 400/500, SENTENCE CASE — every h1/h2/h3 that
               is a statement. High contrast, generous size, tight leading.
     labels    Instrument Sans 600, UPPERCASE, letterspaced — eyebrows, card
               titles, buttons, nav, captions. Small and quiet by design.
     body      Instrument Sans 400, 17px, line-height 1.6.
   The serif is not decoration and must not be swapped for a sans: the whole
   register of the site rests on sentence-case serif statements sitting in a
   lot of white, with sans doing all the small work around them.

   THE PALETTE IS MONOCHROME. White, warm white, one ink, warm-grey hairlines.
   There is NO accent colour and no green — the WhatsApp affordance is carried
   by its glyph alone. Colour on this site comes from the photographs and from
   nothing else, which is the only way twenty photographs of red, lavender and
   copper hair can be the loudest thing on any page.

   Geometry is square, with one deliberate exception: buttons (2026-08-18)
   carry a small radius so the site's CTAs read as approachable rather than
   austere-editorial. The audience is HDB-neighbourhood PMETs on an average
   Singapore income, not a luxury reader who wants sharp corners as a signal
   of restraint — a softened rectangle reads as considered and welcoming
   without tipping into a rounded consumer-app pill. Nothing else on the
   site takes a radius.
   ========================================================================== */

/* --------------------------------------------------------------- tokens -- */
:root {
  --white:    #FFFFFF;
  --cream:    #FAF7F3;   /* the warm ground: hero wash, rail, banded sections */
  --cream-2:  #F4F0EA;   /* the map field, one step down */
  --sand:     #EDE8E0;   /* deepest of the warm neutrals -- the homepage locator
                            panel's ground; client liked it enough (Aug 2026)
                            to ask for it again on /salons, so it's a real
                            token now instead of a value only that one rule knew */
  --line:     #E7E2DA;
  --line-2:   #D6D0C6;

  --ink:      #101010;   /* the one dark surface, and all text */
  --text:     #131313;
  --text-soft:#5A5A55;
  --text-faint:#6E6E67;

  --on-dark:      #FFFFFF;
  --on-dark-soft: #C9C7C2;
  --on-dark-faint:#97958F;
  --line-dark: rgba(255, 255, 255, 0.16);

  /* The one deliberate break from the monochrome rule above: the salon card's
     three contact rows (Aug 2026 rehaul), so address/phone/WhatsApp read as
     three distinct actions at a glance instead of three identical grey lines.
     wa-brand is the real WhatsApp green; the other two are picked to sit
     quietly next to it rather than compete. */
  --wa-brand: #25D366;
  --wa-brand-hi: #1DA851;  /* hover only, a shade darker than --wa-brand */
  --tel-accent: #3B6EA5;
  --pin-accent: #B5651D;

  --display: "Fira Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --body:    "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --kr:      "Noto Sans KR", var(--body);

  /* Two ramps. The display ramp is large and few-stepped; the label ramp is
     tiny and barely varies, because every label on the site is the same size
     give or take a point. That gap is what makes the page feel edited. */
  --t-hero: clamp(2.75rem, 1.1rem + 5.4vw, 4rem);        /* 64px */
  --t-xl:   clamp(2rem, 1.25rem + 2.5vw, 2.75rem);       /* 44px */
  --t-lg:   clamp(1.75rem, 1.2rem + 1.9vw, 2.25rem);     /* 36px */
  --t-md:   clamp(1.375rem, 1.1rem + 0.9vw, 1.75rem);    /* 28px */
  --t-sm:   1.3125rem;                                    /* 21px */
  --t-body: clamp(1rem, 0.97rem + 0.12vw, 1.0625rem);    /* 17px */
  --t-label:0.8125rem;                                    /* 13px */
  --t-nav:  0.78125rem;                                   /* 12.5px */
  --t-mini: 0.6875rem;                                    /* 11px */

  --gut:  clamp(1.25rem, 4vw, 3.5rem);
  /* Narrowed 2026-08-27, explicit client request: content ran nearly edge to
     edge on an ordinary 1500-1600px laptop window (--wide was 1500px, and
     `.wrap--wide` is what almost every section on the site uses), which read
     as a dated, no-margin layout rather than a deliberate one. --max scaled
     down by the same ratio it always sat at relative to --wide (1240/1500),
     so the few plain `.wrap` sections (the price sheet, the before/after
     grid) stay proportionally narrower than `.wrap--wide` sections rather
     than becoming wider than them, which would misalign directly stacked
     sections on the same page. Full-bleed bands (.svc-hero, .work-hero,
     .ref-photo, .cta, the homepage hero) are untouched on purpose -- they
     were never meant to have margins, this request was about content
     columns only. */
  --max:  960px;
  --wide: 1200px;

  --sp-lg: clamp(3.25rem, 5.5vw, 5rem);
  --sp-md: clamp(2.75rem, 4.5vw, 4rem);
  --sp-sm: clamp(2rem, 4vw, 3rem);

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  --arrow-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 12'%3E%3Cg fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M0 6h28'/%3E%3Cpath d='M23.5 1.5 28.5 6l-5 4.5'/%3E%3C/g%3E%3C/svg%3E");
  --wa-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M17.5 14.4c-.3-.2-1.7-.9-2-1-.3-.1-.5-.2-.7.1-.2.3-.7 1-.9 1.2-.2.2-.3.2-.6.1-.3-.2-1.2-.5-2.3-1.4-.9-.8-1.4-1.7-1.6-2-.2-.3 0-.5.1-.6l.5-.6c.1-.2.2-.3.3-.5 0-.2 0-.4 0-.5 0-.2-.7-1.6-.9-2.2-.2-.6-.5-.5-.7-.5h-.6c-.2 0-.5.1-.8.4-.3.3-1 1-1 2.5s1.1 2.9 1.2 3.1c.2.2 2.1 3.2 5.1 4.4.7.3 1.3.5 1.7.6.7.2 1.4.2 1.9.1.6-.1 1.7-.7 2-1.4.2-.7.2-1.3.2-1.4-.1-.2-.3-.2-.6-.4M12 2C6.5 2 2 6.5 2 12c0 1.8.5 3.4 1.3 4.9L2 22l5.3-1.4c1.4.8 3 1.2 4.7 1.2 5.5 0 10-4.5 10-10S17.5 2 12 2'/%3E%3C/svg%3E");
}

/* --------------------------------------------------------------- reset --- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
/* The header is position:fixed, so an in-page anchor otherwise lands with its
   own heading hidden underneath it. Clears the header plus a little air. */
:target { scroll-margin-top: clamp(5.5rem, 8vw, 7rem); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--white);
  color: var(--text);
  font-family: var(--body);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, svg, video { display: block; max-width: 100%; height: auto; }
h1, h2, h3, h4, p, figure, ul, ol, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

:where(a, button, summary, input):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: var(--gut); top: -100px; z-index: 200;
  background: var(--ink); color: #fff; padding: 0.75rem 1.15rem;
  font-size: var(--t-nav); text-transform: uppercase; letter-spacing: 0.1em;
  transition: top 0.2s;
}
.skip:focus { top: 0.75rem; }

.ico { flex: none; }

/* ---------------------------------------------------------- typography --- */
/* Display: Fira Sans (was Playfair Display, swapped v10), sentence case,
   tight. Never uppercase, never bolder than 500 — the size is the emphasis,
   not the weight. */
.h-hero, .h-xl, .h-lg, .h-md {
  font-family: var(--display);
  font-weight: 400;
  font-style: normal;
  letter-spacing: -0.008em;
  line-height: 1.14;
  text-wrap: balance;
  color: var(--text);
}
.h-hero { font-size: var(--t-hero); line-height: 1.1; }
.h-xl   { font-size: var(--t-xl); }
.h-lg   { font-size: var(--t-lg); line-height: 1.18; }
.h-md   { font-size: var(--t-md); line-height: 1.2; }
:is(.on-dark, .on-ink, .on-ink-2, .cta, .ftr) :is(.h-hero, .h-xl, .h-lg, .h-md) { color: #fff; }

/* The one emphasis device inside a display line is a half-step of weight, the
   way the reference lifts the first line of the hero. Not colour, not italic. */
.h-hero em, .ac { font-style: normal; font-weight: 500; }
.it { font-style: normal; }

/* Labels: the sans half of the system. Every card title, every caption, every
   button and every eyebrow is one of these, and they are all nearly the same
   size on purpose. */
.label, .h-xs {
  font-family: var(--body);
  font-size: var(--t-label);
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text);
}
:is(.on-dark, .on-ink, .on-ink-2, .cta, .ftr) :is(.label, .h-xs) { color: #fff; }

.eyebrow {
  display: flex; align-items: center; gap: 0.7rem;
  font-family: var(--body);
  font-size: var(--t-mini); font-weight: 600;
  letter-spacing: 0.17em; text-transform: uppercase;
  color: var(--text-soft);
}
:is(.on-dark, .on-ink, .on-ink-2, .cta) .eyebrow { color: var(--on-dark-faint); }
.eyebrow .kr { font-family: var(--kr); letter-spacing: 0.06em; font-size: 0.85rem; }
.eyebrow::after { display: none; }

.lede {
  font-size: var(--t-body);
  line-height: 1.65;
  color: var(--text-soft);
  max-width: 50ch;
}
:is(.on-dark, .on-ink, .on-ink-2, .cta) .lede { color: var(--on-dark-soft); }
.measure { max-width: 62ch; color: var(--text-soft); }
:is(.on-dark, .on-ink, .on-ink-2, .cta) .measure { color: var(--on-dark-soft); }

/* Serif at body size. Used once, in the closing CTA, where the reference sets
   the supporting line in the display face rather than the sans. */
.serif-lede {
  font-family: var(--display);
  font-size: clamp(1.0625rem, 1rem + 0.3vw, 1.25rem);
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.92);
}

/* ------------------------------------------------------------- layout ---- */
.wrap { width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--gut); }
.wrap--wide { max-width: var(--wide); }

.section { padding-block: var(--sp-lg); position: relative; }
.section--sm { padding-block: var(--sp-sm); }
.section--flush { padding-top: 0; }

.on-paper   { background: var(--white); color: var(--text); }
/* v7's second tint and v8's cream are the same surface now. */
.on-cream, .on-paper-2 { background: var(--cream); color: var(--text); }
/* The homepage locator panel's ground, promoted to a page-level surface --
   /salons' card grid picks it up Aug 2026 (client request). */
.on-sand { background: var(--sand); color: var(--text); }
.on-dark, .on-ink, .on-ink-2 { background: var(--ink); color: var(--on-dark); }
:is(.on-dark, .on-ink, .on-ink-2) a:not(.btn) { color: inherit; }

/* Section heads are centred: a small eyebrow, then one serif line. The
   reference gives them nothing else — no supporting paragraph, no rule. */
.s-head {
  max-width: 46rem; margin-inline: auto; text-align: center;
  margin-bottom: clamp(1.6rem, 2.6vw, 2.4rem);
}
.s-head > * + * { margin-top: 0.85rem; }
.s-head .eyebrow { justify-content: center; }
.s-head .lede { margin-inline: auto; }
.s-head .btn-row { justify-content: center; }
.s-head--split > div > * + * { margin-top: 0.85rem; }
.s-head--left { margin-inline: 0; text-align: left; }
.s-head--left .eyebrow { justify-content: flex-start; }
.s-head--left .lede { margin-inline: 0; }

/* ------------------------------------------------------------ buttons ---- */
/* Two buttons on the whole site: solid ink, and a hairline outline. Both
   uppercase sans, both invert on hover. That is the entire set.
   border-radius: 6px softens the corner just enough to read as considered
   rather than sharp-cornered/austere -- a nod to the audience (HDB PMETs,
   not a luxury-editorial reader) without going as far as a pill, which would
   read as a mobile-app control rather than a salon's own CTA. */
.btn {
  --btn-bg: var(--ink);
  --btn-fg: #fff;
  --btn-bd: var(--ink);
  display: inline-flex; align-items: center; justify-content: center; gap: 0.6rem;
  min-height: 48px;
  padding: 0.85rem 1.7rem;
  background: var(--btn-bg); color: var(--btn-fg);
  border: 1px solid var(--btn-bd);
  border-radius: 6px;
  font-family: var(--body);
  font-size: var(--t-nav); font-weight: 500;
  letter-spacing: 0.11em; text-transform: uppercase;
  text-decoration: none; white-space: nowrap;
  transition: background 0.25s var(--ease), color 0.25s var(--ease),
              border-color 0.25s var(--ease);
}
.btn:hover { --btn-bg: transparent; --btn-fg: var(--ink); --btn-bd: var(--ink); }

.btn--ghost { --btn-bg: transparent; --btn-fg: var(--ink); --btn-bd: var(--ink); }
.btn--ghost:hover { --btn-bg: var(--ink); --btn-fg: #fff; --btn-bd: var(--ink); }

:is(.on-dark, .on-ink, .on-ink-2, .cta) .btn { --btn-bg: #fff; --btn-fg: var(--ink); --btn-bd: #fff; }
:is(.on-dark, .on-ink, .on-ink-2, .cta) .btn:hover { --btn-bg: transparent; --btn-fg: #fff; --btn-bd: #fff; }
:is(.on-dark, .on-ink, .on-ink-2, .cta) .btn--ghost { --btn-bg: transparent; --btn-fg: #fff; --btn-bd: rgba(255,255,255,0.6); }
:is(.on-dark, .on-ink, .on-ink-2, .cta) .btn--ghost:hover { --btn-bg: #fff; --btn-fg: var(--ink); --btn-bd: #fff; }

/* In a grid of twelve repeated salon cards a wall of solid ink reads as a
   pattern rather than as a list, so there the affordance is a hairline. */
.btn--quiet { --btn-bg: transparent; --btn-fg: var(--ink); --btn-bd: var(--line-2); }
.btn--quiet:hover { --btn-bg: var(--ink); --btn-fg: #fff; --btn-bd: var(--ink); }

/* Sep 2026, explicit client request: every WhatsApp button on the site
   should read as WhatsApp at a glance -- real WhatsApp brand green fill,
   not the site's monochrome default, plus the glyph. Supersedes the
   "no green, glyph carries the affordance alone" rule above for this one
   button type; every other button on the site is untouched. */
.btn--wa {
  --btn-bg: var(--wa-brand); --btn-fg: #fff; --btn-bd: var(--wa-brand);
}
.btn--wa:hover {
  --btn-bg: var(--wa-brand-hi); --btn-fg: #fff; --btn-bd: var(--wa-brand-hi);
}
.btn--wa::before {
  content: ""; width: 16px; height: 16px; flex: none; background: currentColor;
  -webkit-mask: var(--wa-icon) center/contain no-repeat;
          mask: var(--wa-icon) center/contain no-repeat;
}

.btn--sm { min-height: 42px; padding: 0.6rem 1.1rem; font-size: 0.72rem; gap: 0.45rem; }
.btn--lg { min-height: 54px; padding: 1rem 2rem; }

.btn-row { display: flex; flex-wrap: wrap; gap: 0.7rem; }
.btn-row--center { justify-content: center; margin-top: clamp(1.4rem, 2.4vw, 2.1rem); }

/* The text link: uppercase label, rule under the WORDS only, arrow outside it
   that steps right on hover. */
.link {
  display: inline-flex; align-items: center; gap: 0.85rem; min-height: 40px;
  font-size: var(--t-nav); font-weight: 600;
  letter-spacing: 0.11em; text-transform: uppercase;
  text-decoration: none;
}
.link > span { padding-bottom: 0.35rem; border-bottom: 1px solid currentColor; }
.link::after {
  content: ""; width: 30px; height: 12px; flex: none; background: currentColor;
  -webkit-mask: var(--arrow-icon) center/contain no-repeat;
          mask: var(--arrow-icon) center/contain no-repeat;
  transition: transform 0.25s var(--ease);
}
.link:hover::after { transform: translateX(5px); }

/* ------------------------------------------------------------- lockup ---- */
/* Typeset, not an image: crisp at any size, recolours for the dark footer,
   and stays selectable. 명동 over the name over the descriptor. */
.lockup {
  display: inline-flex; flex-direction: column; gap: 0.3rem;
  text-decoration: none; color: inherit; min-height: 44px; justify-content: center;
}
.lockup__kr {
  font-family: var(--kr); font-weight: 500;
  font-size: 1.28rem; line-height: 1; letter-spacing: 0.03em;
}
.lockup__stack { display: flex; flex-direction: column; gap: 0.22rem; }
.lockup__name {
  font-size: 0.9rem; font-weight: 500; line-height: 1;
  letter-spacing: 0.19em; text-transform: uppercase;
}
.lockup__sub {
  font-size: 0.5rem; font-weight: 600; line-height: 1;
  letter-spacing: 0.235em; text-transform: uppercase;
  color: var(--text-faint);
}
:is(.on-dark, .cta, .ftr) .lockup__sub { color: var(--on-dark-faint); }

.lockup--inline { flex-direction: row; align-items: center; gap: 0.85rem; }
.lockup--inline .lockup__kr { font-size: 1.35rem; }
.lockup--inline .lockup__stack {
  padding-left: 0.85rem; border-left: 1px solid var(--line-dark);
}

/* ------------------------------------------------------------- header ---- */
/* Light on every page, never inverts. Solid from the very first frame --
   2026-08-27 fix, explicit client request: it used to float fully
   transparent until scrolled (only `.is-stuck` carried a background),
   which put dark nav text directly over whatever photo sits at the top of
   a hero -- illegible wherever that photo's own tone was light. `.is-stuck`
   now only adds the scroll-compacted padding and the hairline shadow; the
   background/blur that used to be scroll-triggered is the header's
   permanent state. */
.hdr {
  position: fixed; inset: 0 0 auto; z-index: 100;
  padding-block: 1.15rem;
  color: var(--ink);
  transition: padding 0.3s var(--ease), box-shadow 0.3s var(--ease);
}
/* Sep 2026, found during a full mobile pass: `backdrop-filter` (like
   `transform`/`filter`/`perspective`) establishes a containing block for
   any `position:fixed` DESCENDANT -- and `#nav`/`.hdr__nav` (the
   full-screen mobile menu overlay) lives inside `.hdr`. With
   backdrop-filter on `.hdr` itself, `.hdr__nav`'s own `position:fixed;
   inset:0` was resolving against `.hdr`'s ~80px box instead of the
   viewport, so the open mobile menu only ever covered the header's own
   sliver -- solid cream background and all, just clipped to nothing
   visible -- while the hero and rest of the page showed straight through
   underneath it. Moved the blur/tint onto this `::before` instead of the
   `.hdr` element itself, so `.hdr` no longer carries a filter property
   and stops being a containing block for its fixed-position children;
   visually identical, `#nav` now covers the full viewport when open. */
.hdr::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: rgba(250, 247, 243, 0.94);
  -webkit-backdrop-filter: saturate(150%) blur(12px);
          backdrop-filter: saturate(150%) blur(12px);
  transition: background 0.3s var(--ease);
}
.hdr.is-stuck {
  padding-block: 0.75rem;
  box-shadow: 0 1px 0 var(--line);
}

.hdr__inner { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 1.5rem; }

.hdr__nav ul { display: flex; gap: clamp(1rem, 2.6vw, 2.6rem); justify-content: center; }
.hdr__nav a {
  display: flex; align-items: center; min-height: 42px; padding: 0.5rem 0;
  font-size: var(--t-nav); font-weight: 600;
  letter-spacing: 0.11em; text-transform: uppercase;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s, opacity 0.2s;
}
.hdr__nav a:hover, .hdr__nav a[aria-current] { border-bottom-color: currentColor; }

.hdr__cta { display: flex; align-items: center; gap: 0.55rem; }

/* Must outrank .hdr__nav.is-open's z-index:150 (below) -- otherwise once
   the mobile menu is open, the overlay paints over this button entirely
   (hiding the X it morphs into) and swallows its clicks, leaving no way
   to close the menu except navigating away via a nav link. Found in a
   Sep 2026 mobile pass. */
.hdr__burger { display: none; width: 44px; height: 44px; align-items: center; justify-content: center; position: relative; z-index: 200; }
.hdr__burger span, .hdr__burger span::before, .hdr__burger span::after {
  content: ""; display: block; width: 22px; height: 1.5px;
  background: currentColor; transition: transform 0.28s var(--ease), opacity 0.2s;
}
.hdr__burger span::before { transform: translateY(-7px); }
.hdr__burger span::after  { transform: translateY(5.5px); }
.hdr__burger[aria-expanded="true"] span { background: transparent; }
.hdr__burger[aria-expanded="true"] span::before { transform: rotate(45deg); }
.hdr__burger[aria-expanded="true"] span::after  { transform: rotate(-45deg) translateY(0); }

@media (max-width: 1060px) {
  .hdr__inner { grid-template-columns: auto 1fr; }
  .hdr__burger { display: flex; justify-self: end; }
  .hdr__cta { display: none; }
  .hdr__nav {
    position: fixed; inset: 0; z-index: -1;
    background: var(--cream); color: var(--ink);
    display: grid; align-content: center; justify-items: center;
    gap: 0.4rem; padding: 6rem var(--gut) 3rem;
    opacity: 0; visibility: hidden;
    transition: opacity 0.3s var(--ease), visibility 0.3s;
  }
  /* Sep 2026: found during a full mobile pass -- the closed panel needs
     z-index:-1 so it can't be tabbed/clicked into while hidden underneath
     the header (z-index:100), but that same -1 was still in effect when
     OPEN too, so the "solid cream, centred links" panel painted behind
     the header and hero instead of over them -- opaque background and
     all, just invisible under everything else, with only the nav text
     itself somehow still visible through the stacking mess. z-index:150
     here clears the header safely. */
  .hdr__nav.is-open { opacity: 1; visibility: visible; z-index: 150; }
  .hdr__nav ul { flex-direction: column; align-items: center; gap: 0.2rem; }
  .hdr__nav a {
    font-family: var(--display); font-weight: 400; font-size: 1.9rem;
    letter-spacing: -0.005em; text-transform: none; padding: 0.5rem 0;
  }
  .hdr__nav .m-cta { margin-top: 2rem; }
}
@media (min-width: 1061px) { .hdr__nav .m-cta { display: none; } }

/* --------------------------------------------------------------- hero ---- */
/* A LIGHT split. The photograph fills the band and a near-opaque warm wash
   over the left two-fifths turns the plain studio wall into the copy field.
   That is the whole trick: no card, no scrim over the face, and the type sits
   on cream rather than on a photograph. */
.hero {
  position: relative;
  display: grid; align-items: center;
  min-height: 680px;
  /* Aug 2026 follow-up: the band's SHAPE, not the photo, was the problem.
     `min(92svh, 760px)` made a band that's proportionally wider/shorter than
     the 16:9 plate at nearly every real desktop window (measured live:
     1512x764 window -> a 1497x703 band, aspect 2.13, against the photo's own
     1.78 -- `cover` had to crop ~16% off the top and bottom just to fill
     that too-short shape). aspect-ratio:16/9 makes the band's shape MATCH
     the shipped photo exactly, so `cover` has nothing left to crop, at any
     width between the min-height floor (this hero's actual text needs 635px
     at 1512px wide, measured live) and the max-height ceiling. Between
     those two, and only there, `cover` still crops -- sideways, not
     top/bottom -- which is deliberately the rarer failure mode.

     Sep 2026 correction: the ceiling was 950px, sized around a 3440px
     ultrawide guard, but that math never accounted for perfectly ordinary
     widths in between -- at 1728px (a real MacBook Pro logical width) the
     band needs 972px for zero crop, and at 1920px (a common external
     monitor) it needs 1080px; both exceeded 950 and were being cropped
     top/bottom by `cover`, cutting hair off the top of the subject on any
     such window -- reported directly ("top of hair is cut off"), not
     caught before shipping because verification that round only checked a
     1512px window. Raised to 1300px, which covers zero-crop up to a
     2311px-wide band; a genuinely ultrawide monitor (2560px+) still gets a
     capped, cropped band on purpose, same reasoning as before, just at a
     width that's actually rare rather than one plenty of laptops hit. */
  aspect-ratio: 16 / 9;
  max-height: 1300px;
  padding-block: clamp(7rem, 11vw, 8.5rem) clamp(2.5rem, 5vw, 4rem);
  background: var(--cream);
  overflow: hidden; isolation: isolate;
}
.hero__bg { position: absolute; inset: 0; z-index: -2; }
/* Figma revamp, Aug 2026: the shipping plate (home-cover.jpg) is exported at
   its own native 16:9 -- no pre-crop -- so it is the browser's `cover`, ALONE
   against the real viewport, that frames it. And unlike the outgoing AI plate,
   this one already carries its own warm wash over the left ~40% baked into
   the photograph (it's a real ghosted studio background, not a CSS effect),
   so there is no ::after scrim here any more -- layering one on top just
   double-washes it, which is what the first cut of this got wrong. */
.hero__bg img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 46%; }

.hero__inner { position: relative; z-index: 1; }
.hero__panel > * + * { margin-top: 1.35rem; }
/* 13ch was tuned for the outgoing 3-line headline ("Korean Hair," / "Done in
   Your" / "Neighbourhood"), each line short enough to sit well under it. The
   Sep 2026 copy is only 2 lines (one manual <br>), and its second line,
   "Designed for you", is 16 characters -- wider than 13ch, so it was
   wrapping a second time inside itself and shipping as an unwanted 3rd
   line. 17ch raised the cap, but that alone didn't fix it: the real
   constraint at every desktop width was the grid column below, not this
   max-width -- see that rule's comment. Left at 17ch since it's still a
   harmless outer guard. */
.hero__panel .h-hero { max-width: 17ch; }
.hero__panel .lede { max-width: 42ch; }
.hero__panel .btn-row { margin-top: 2rem; }

@media (min-width: 940px) {
  /* Sep 2026: measured live (canvas text metrics + getBoundingClientRect),
     "Designed for you" at the hero's old max size (4.4rem/70.4px) needs
     ~540px, but the old 0.95fr/1.05fr split with up to 4rem of padding-right
     only ever gave the panel 452-497px across 940-1920px viewports -- it
     was mathematically impossible for that line to fit on one line at any
     desktop width, ch cap notwithstanding. Dropped the hero ceiling to
     4rem/64px, tilted the split to 1.1fr/0.9fr, and trimmed padding-right
     to clamp(1.25rem, 3vw, 2.25rem); re-measured the same way and it now
     clears 940-1920px with 70-100px to spare. */
  .hero__inner { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); }
  .hero__panel { grid-column: 1; padding-right: clamp(1.25rem, 3vw, 2.25rem); }
}
@media (max-width: 939px) {
  .hero { min-height: min(94svh, 720px); aspect-ratio: auto; max-height: none; align-items: end; }
  .hero__bg img { object-position: 62% 30%; }
  /* Mobile stacks the copy at the BOTTOM of the photo, not beside a washed-
     out left third, so the plate's own baked-in left wash does nothing here
     -- this scrim is still needed and is self-contained (declares its own
     `content`) since the shared desktop ::after above was removed. */
  .hero__bg::after {
    content: "";
    position: absolute; inset: 0;
    background:
      linear-gradient(to top,
        var(--cream) 0%, rgba(250, 247, 243, 0.985) 42%,
        rgba(250, 247, 243, 0.88) 58%, rgba(250, 247, 243, 0.35) 78%,
        rgba(250, 247, 243, 0) 100%),
      linear-gradient(to bottom, rgba(250, 247, 243, 0.85) 0%, rgba(250, 247, 243, 0) 14%);
  }
}

/* --------------------------------------------------------------- rail ---- */
/* Four facts under the hero, hairline-separated, on the same warm ground so
   the hero and the rail read as one band. Note: `.rail` and `.rail__list`
   (the two wrapper levels) were removed as dead CSS 2026-08-27 -- neither
   is ever generated (render_trust() in build.py only produces the inner
   `.rail__item` list items below, and no template supplies the wrappers).
   render_trust() itself is still computed every build (wired to a "TRUST"
   token no current page uses, same dormant-but-intact pattern as
   render_faq()/render_managers()), so `.rail__item`/`.rail__t`/`.rail__d`
   stay -- reactivating it needs a template with its own wrapper markup
   either way, so keeping unused wrapper CSS bought nothing. */

.rail__item {
  display: flex; align-items: center; gap: 0.95rem;
  padding: 1.35rem 0 1.35rem clamp(1rem, 2.5vw, 2rem);
  position: relative;
}
.rail__item:first-child { padding-left: 0; }
.rail__item + .rail__item::before {
  content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 1px; height: 2.5rem; background: var(--line-2);
}
@media (min-width: 860px) { .rail__item:nth-child(1) { padding-left: 0; } }
@media (max-width: 859px) { .rail__item:nth-child(3) { padding-left: 0; } .rail__item:nth-child(3)::before { display: none; } }

.rail__item .ico { color: var(--text); }
.rail__item > span:last-child { display: flex; flex-direction: column; gap: 0.2rem; }
.rail__t { font-size: var(--t-label); font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; }
.rail__d { font-size: 0.875rem; color: var(--text-soft); }

/* ----------------------------------------------------------- services ---- */
/* Four plates. The name and one line of framing copy sit over the photograph
   with a rule beneath — the only place on the site where type is allowed on
   an image, because these are mood plates rather than evidence. */
.svc-grid { display: grid; gap: clamp(0.5rem, 0.9vw, 0.75rem); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 1080px) { .svc-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.svc-tile {
  position: relative; display: block; overflow: hidden;
  aspect-ratio: 3 / 4;
  text-decoration: none; color: #fff;
  background: var(--ink);
}
.svc-tile img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; transition: transform 0.7s var(--ease);
}
.svc-tile::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(8,8,8,0.74) 0%, rgba(8,8,8,0.34) 30%, rgba(8,8,8,0) 62%);
  transition: background 0.4s var(--ease);
}
.svc-tile:hover img { transform: scale(1.045); }
.svc-tile:hover::after { background: linear-gradient(to top, rgba(8,8,8,0.82) 0%, rgba(8,8,8,0.44) 34%, rgba(8,8,8,0.06) 70%); }

.svc-tile__body {
  position: absolute; inset: auto 0 0 0; z-index: 1;
  display: flex; flex-direction: column; gap: 0.4rem;
  padding: clamp(1.15rem, 2vw, 1.7rem);
}
.svc-tile__name {
  font-size: clamp(1.05rem, 0.85rem + 0.6vw, 1.375rem);
  font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; line-height: 1.1;
}
.svc-tile__sub { font-size: 0.8125rem; color: rgba(255, 255, 255, 0.9); }
.svc-tile__body::after {
  content: ""; width: 54px; height: 1px; background: rgba(255, 255, 255, 0.85);
  margin-top: 0.55rem; transition: width 0.3s var(--ease);
}
.svc-tile:hover .svc-tile__body::after { width: 84px; }

/* The long-form service blocks, still used on the Services page. */
.svc {
  display: grid; gap: clamp(1.6rem, 4vw, 3.5rem); align-items: center;
  padding-block: clamp(2.25rem, 5vw, 3.5rem);
  border-top: 1px solid var(--line);
}
.svc:first-child { border-top: 0; padding-top: 0; }
@media (min-width: 860px) {
  .svc { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr); }
  .svc--flip { grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr); }
  .svc--flip .svc__media { order: 2; }
}
.svc__media img { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; }
.svc__body > * + * { margin-top: 1rem; }

.ticks { display: grid; gap: 0.45rem; }
.ticks li { display: grid; grid-template-columns: 1.3rem 1fr; align-items: baseline;
            font-size: 0.95rem; color: var(--text-soft); }
.ticks li::before { content: "—"; color: var(--text-faint); }
.svc__note { font-size: 0.8rem; color: var(--text-faint); }

/* ------------------------------------------------------------ results ---- */
/* One strong before/after large, the other two stacked beside it. The circular
   handle on the seam is what says "before / after" without putting a word on
   top of the evidence. */
.results { display: grid; gap: clamp(2rem, 4vw, 3.5rem); align-items: center; }
@media (min-width: 960px) {
  .results { grid-template-columns: minmax(0, 0.68fr) minmax(0, 2.6fr); }
}
.results__body > * + * { margin-top: 1.25rem; }
.results__body p { color: var(--text-soft); font-size: 0.95rem; line-height: 1.7; }
.results__body .link { margin-top: 1.75rem; }

.results__media { display: grid; gap: clamp(0.5rem, 0.9vw, 0.75rem); }
@media (min-width: 640px) {
  .results__media { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); align-items: start; }
}
.results__lead { display: block; }
.results__lead figcaption {
  padding-top: 0.7rem;
  font-size: var(--t-mini); font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-faint);
}
.results__grid { display: grid; gap: clamp(0.5rem, 0.9vw, 0.75rem); align-content: start; }

.pair { position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: clamp(0.35rem, 0.6vw, 0.5rem); }
.pair img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.pair__handle {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 38px; height: 38px; border-radius: 50%;
  background: #fff; color: var(--ink);
  display: grid; place-items: center;
  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.16);
}
.pair__handle .ico--h { width: 15px; height: 15px; }
/* On the small pairs the handle is just a dot: at that size an icon inside it
   is illegible and reads as dirt on the photograph. */
.results__grid .pair__handle { width: 16px; height: 16px; }
.results__grid .pair__handle .ico--h { display: none; }

/* --------------------------------------------------- reasons / why ------- */
/* Four numbered claims in four columns, divided by hairlines and nothing else.
   No card, no box, no background — the whitespace does the separating. */
.reasons { display: grid; gap: 2.5rem 0; }
@media (min-width: 700px)  { .reasons { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1040px) { .reasons { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; } }

.reason { padding-inline: clamp(1.25rem, 2.6vw, 2.4rem); }
.reason:first-child { padding-left: 0; }
.reason:last-child { padding-right: 0; }
.reason + .reason { border-left: 1px solid var(--line-2); }
@media (max-width: 699px) { .reason { padding-inline: 0; } .reason + .reason { border-left: 0; } }
@media (min-width: 700px) and (max-width: 1039px) {
  .reason:nth-child(odd) { padding-left: 0; }
  .reason:nth-child(even) { padding-right: 0; }
  .reason:nth-child(odd) + .reason { border-left: 1px solid var(--line-2); }
  .reason:nth-child(3) { border-left: 0; }
}

.reason__top { display: flex; align-items: baseline; gap: 1.1rem; }
.reason__n {
  font-family: var(--display); font-weight: 400;
  font-size: clamp(1.85rem, 1.4rem + 1vw, 2.3rem); line-height: 1;
  color: var(--text);
}
.reason__kr {
  font-family: var(--kr); font-weight: 400;
  font-size: clamp(1.4rem, 1.1rem + 0.8vw, 1.75rem); line-height: 1;
  color: var(--text);
}
.reason .label { margin-top: 1.2rem; }
.reason__body { margin-top: 0.85rem; }
.reason__body p { font-size: 0.9375rem; line-height: 1.7; color: var(--text-soft); }
.reason__body p + p { margin-top: 0.85rem; }
.reason__body strong {
  display: block; font-weight: 600; letter-spacing: 0.03em;
  color: var(--text); margin-top: 0.55rem;
}
:is(.on-dark, .on-ink, .on-ink-2) .reason__body p { color: var(--on-dark-soft); }
:is(.on-dark, .on-ink, .on-ink-2) :is(.reason__n, .reason__kr, .reason__body strong) { color: #fff; }
:is(.on-dark, .on-ink, .on-ink-2) .reason + .reason { border-left-color: var(--line-dark); }

/* -------------------------------------------------------- outlets-mini --- */
/* Homepage locator, simplified (2026-08-25) -- replaces a four-cut ~420-line
   ".locshow" "premium brand moment" section (map plate, region cards, gold
   stat medallion; full history in PROJECT-MEMORY.md if it's ever wanted
   back) with a plain overview: all twelve salons, name + address only, the
   whole card a link straight through to `salons.html`. Reuses the site's
   existing `.s-head` section-head pattern and the plain-card visual
   language (border, no shadow, no radius) that the rest of the site already
   uses -- this section isn't trying to be a destination, just a preview
   that funnels to the real directory. */
.outlets-mini {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.9rem, 1.6vw, 1.4rem);
  margin-top: clamp(2rem, 4vw, 3rem);
}
@media (min-width: 720px)  { .outlets-mini { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .outlets-mini { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.outlets-mini__card {
  display: flex; flex-direction: column; gap: 0.5rem;
  padding: clamp(1.15rem, 1.5vw, 1.5rem);
  background: var(--cream);
  border: 1px solid var(--line);
  text-decoration: none;
  transition: border-color 0.25s var(--ease), background-color 0.25s var(--ease);
}
.outlets-mini__card:hover { border-color: var(--text); background: #fff; }
.outlets-mini__name {
  font-family: var(--display); font-weight: 400; font-size: 1.1rem;
  line-height: 1.25; color: var(--text);
}
.outlets-mini__addr { font-size: 0.85rem; line-height: 1.5; color: var(--text-soft); }
/* Bottom-right arrow, pushed down by the flex column's `margin-top: auto` --
   the one visual cue (besides the border/hover) that this whole card, not
   just an inner link, is clickable. Plain `currentColor`, matching every
   other quiet-arrow affordance sitewide -- no accent colour on card text,
   per the earlier client correction against the section this replaces
   (accent stays on icons only, never on text). */
.outlets-mini__arrow {
  margin-top: auto; padding-top: 0.5rem; align-self: flex-end;
  display: grid; place-items: center; color: var(--text-faint);
}
.outlets-mini__arrow .ico { width: 15px; height: 15px; transition: translate 0.25s var(--ease); }
.outlets-mini__card:hover .outlets-mini__arrow { color: var(--text); }
.outlets-mini__card:hover .outlets-mini__arrow .ico { translate: 3px 0; }

/* --------------------------------------------------------------- work ---- */
.tiles {
  display: grid; gap: clamp(0.5rem, 1vw, 0.85rem);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 700px)  { .tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .tiles { grid-template-columns: repeat(5, minmax(0, 1fr)); } }

/* Every tile is forced to one 4:5 box: the sources arrive at a dozen shapes,
   and a ragged grid is most of what made the old gallery look like a camera
   roll rather than a portfolio. */
.tile { overflow: hidden; }
.tile img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; transition: transform 0.6s var(--ease); }
.tile > :first-child { overflow: hidden; display: block; }
.tile:hover img { transform: scale(1.04); }

/* Captions sit UNDER the photograph, never on it. Standing rule: on a results
   shot you have to be able to see the hair. */
.tile figcaption { display: flex; flex-direction: column; gap: 0.2rem; padding-top: 0.7rem; }
.tile__t { font-size: var(--t-label); font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; }
.tile__s { font-size: var(--t-mini); letter-spacing: 0.13em; text-transform: uppercase; color: var(--text-faint); }
:is(.on-dark, .on-ink, .on-ink-2) .tile__s { color: var(--on-dark-faint); }

/* ----------------------------------------------------- before / after ---- */
.bas { display: grid; gap: clamp(1.75rem, 3.5vw, 2.75rem); }
@media (min-width: 820px) { .bas { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.ba > * + * { margin-top: 0.8rem; }
.ba__pair { display: grid; grid-template-columns: 1fr 1fr; gap: 0.4rem; }
.ba__pair img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.ba__pair figcaption {
  padding-top: 0.5rem; font-size: var(--t-mini); font-weight: 600; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--text-faint);
}
.ba p { font-size: 0.93rem; color: var(--text-soft); }
:is(.on-dark, .on-ink, .on-ink-2) .ba p { color: var(--on-dark-soft); }

/* ---------------------------------------------------------- reviews ---- */
/* "What Our Customers Say", added 2026-09-01 -- three real Google reviews,
   an initial-avatar + star row per card (Figma reference: Apgujeong
   Website Revamp file, "#myeongdong" frame, node 4263:239). No per-card
   link (Google's review UI has no stable per-review permalink); the whole
   section closes on one real link out to the outlet's Google Maps page,
   same rhythm as every other homepage section's single closing link. */
.reviews-grid {
  display: grid; gap: clamp(1.25rem, 2vw, 1.75rem);
  margin-top: clamp(2rem, 4vw, 3rem);
}
@media (min-width: 820px) { .reviews-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.review-card {
  background: var(--white);
  border: 1px solid var(--line);
  padding: clamp(1.5rem, 2.2vw, 1.85rem);
}
.review-card__head { display: flex; align-items: center; gap: 0.85rem; }
.review-card__avatar {
  flex: none; width: 42px; height: 42px; display: grid; place-items: center;
  border-radius: 50%; background: var(--cream-2);
  font-family: var(--display); font-size: 1.1rem; color: var(--text);
}
.review-card__name { font-size: 0.95rem; font-weight: 600; color: var(--text); }
.review-card__stars { margin-top: 0.15rem; display: flex; gap: 0.1rem; color: var(--pin-accent); }
.review-card__stars .ico { width: 15px; height: 15px; }
.review-card__text { margin-top: 1rem; font-size: 0.92rem; line-height: 1.6; color: var(--text-soft); }

/* ------------------------------------------------------------ process ---- */
.steps { display: grid; gap: 1px; background: var(--line); border-block: 1px solid var(--line); }
@media (min-width: 700px)  { .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1060px) { .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.step { background: var(--white); padding: clamp(1.5rem, 3vw, 2.1rem); }
.step > * + * { margin-top: 0.7rem; }
.step__n { display: block; font-family: var(--display); font-size: 1.9rem; line-height: 1;
           color: var(--text-faint); }
.step p { font-size: 0.93rem; color: var(--text-soft); }
.on-cream .step, .on-paper-2 .step { background: var(--cream); }
:is(.on-dark, .on-ink, .on-ink-2) .steps { background: var(--line-dark); border-color: var(--line-dark); }
:is(.on-dark, .on-ink, .on-ink-2) .step { background: var(--ink); }
:is(.on-dark, .on-ink, .on-ink-2) .step p { color: var(--on-dark-soft); }
:is(.on-dark, .on-ink, .on-ink-2) .step__n { color: var(--on-dark-faint); }

/* --------------------------------------------------------------- split --- */
.split { display: grid; gap: clamp(1.75rem, 4.5vw, 3.5rem); align-items: center; }
@media (min-width: 900px) {
  .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}
.split__body > * + * { margin-top: 1.1rem; }
.media-caption { padding-top: 0.6rem; font-size: var(--t-mini); font-weight: 600;
                 letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-faint); }
:is(.on-dark, .on-ink, .on-ink-2) .media-caption { color: var(--on-dark-faint); }

/* -------------------------------------------------------- salon price ---- */
.price-meta { color: var(--text-soft); font-size: 0.95rem; }
.price-meta + .price-meta { margin-top: 0.2rem; }
.price-meta a { color: inherit; text-decoration: none; }
.price-meta a:hover { color: var(--text); text-decoration: underline; }
/* No frame: the sheet is already white on the section's own white ground
   (the cream masthead above already marks where the page content starts),
   so a border here would only draw a box around nothing. */
.price-sheet { max-width: 40rem; margin-inline: auto; }
.price-sheet img { display: block; width: 100%; height: auto; }
.price-notes { max-width: 40rem; margin: 1.25rem auto 0; text-align: center; }
.price-notes p { font-size: 0.8rem; line-height: 1.6; color: var(--text-faint); }
.price-notes p + p { margin-top: 0.75rem; }

/* ------------------------------------------------------------ managers --- */
.mgrs { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); }
@media (min-width: 620px) { .mgrs { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 980px) { .mgrs { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.mgr { background: var(--white); padding: clamp(1.35rem, 2.6vw, 1.85rem); }
.mgr > * + * { margin-top: 0.5rem; }
.mgr__initial {
  width: 48px; height: 48px; display: grid; place-items: center; margin-bottom: 0.9rem;
  border: 1px solid var(--line-2);
  font-family: var(--display); font-size: 1.35rem; color: var(--text);
}
.mgr__salons { font-size: var(--t-mini); font-weight: 600; letter-spacing: 0.13em;
               text-transform: uppercase; color: var(--text-faint); }
.mgr__where  { font-size: 0.88rem; color: var(--text-soft); }
.mgr .btn { margin-top: 1rem; }
.on-cream .mgr, .on-paper-2 .mgr { background: var(--cream); }

/* ---------------------------------------------------------- outlet nav --- */
/* Jump-to index, 2026-08-27 -- added the same pass as the per-outlet map
   embeds below. Twelve rows, each now with a real Google Map beside it, is
   a much longer scroll than the old two-column text directory was; this
   sits right under the masthead (same --cream ground, so it reads as part
   of the same header zone) and lists every outlet name where it can be
   seen without scrolling. Each link is a plain #slug anchor;
   `:target { scroll-margin-top }` (top of this file) already accounts for
   the fixed header, so no extra offset rule is needed here or on
   `.outlet-row` below.

   SECOND PASS, same week: client -- "align the outlet summary better,
   split into its own regions, label them." The first cut was one flat
   wrapped row of twelve chips, alphabetical-ish by outlets.json's own
   order -- nothing grouped them, so "at a glance" still meant reading all
   twelve one by one. Four labelled columns instead (West / North /
   North-East / Central+East -- render_outlet_nav() in build.py).

   THIRD PASS, same week: client -- "can it look more obvious? now it's
   just a word and a link." The second pass's columns were unboxed text
   sitting in a bare grid track (`.ftr__nav`'s label-then-list pattern with
   no card around it) -- four labels and four lists, but nothing telling
   the eye "these are four separate, designed things" at a glance. Now
   each column is a real card: white against the section's own cream
   ground (same lift `.gcard`/`.outlets-mini__card` already use elsewhere
   on this site, so this isn't a new visual language). This pass also
   topped each card with a revived region-landmark illustration
   (REGION_ICON in build.py) -- reverted again the same day, client:
   "dont use these assets anymore... the previous one you did with the
   white boxes is good, use that." The card treatment stays; the icon
   files were archived, see build.py's render_outlet_nav() docstring. */
.outlet-nav { background: var(--cream); border-bottom: 1px solid var(--line); }
.outlet-nav__inner {
  display: grid; gap: 1.25rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  padding-block: clamp(1.75rem, 3vw, 2.5rem);
}
@media (min-width: 620px) { .outlet-nav__inner { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.outlet-nav__col {
  background: var(--white);
  border: 1px solid var(--line);
  padding: clamp(1.25rem, 1.8vw, 1.6rem) clamp(1.1rem, 1.6vw, 1.4rem);
  display: flex; flex-direction: column;
}
.outlet-nav__label {
  font-size: var(--t-mini); font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--text);
  padding-bottom: 0.75rem; border-bottom: 1px solid var(--line-2);
}
.outlet-nav__col ul { margin-top: 0.5rem; display: grid; gap: 0; }
.outlet-nav__col a {
  display: flex; align-items: center; min-height: 38px;
  font-size: 0.875rem; color: var(--text-soft); text-decoration: none;
  transition: color 0.2s var(--ease);
}
.outlet-nav__col a:hover { color: var(--text); text-decoration: underline; }

/* -------------------------------------------------------------- salons --- */
/* Redesigned 2026-08-27 to add a real embedded Google Map beside every
   outlet's contact details (`o.map` in outlets.json -- an embed URL that
   has sat in the data file, unrendered anywhere, since the file's own early
   days). The old layout was a dense two-column text-only directory, capped
   at 54rem so two narrow columns didn't stretch into dead space -- that
   whole premise breaks once each entry needs room for a map, so this is a
   single full-width column of rows instead, contact details beside the
   map on desktop, map below on mobile (plain DOM order, no CSS reorder
   needed). Still "no card, no box" for the info half -- a hairline above
   each row is the only divider, same directory language as before; the map
   itself is naturally a rectangle, so the row doesn't need a second border
   to read as separated from its neighbours. */
.outlets-list { display: flex; flex-direction: column; }
.outlet-row {
  display: grid; align-items: start;
  gap: clamp(1.5rem, 3vw, 2.5rem);
  padding-block: clamp(2rem, 3.5vw, 2.75rem);
  border-top: 1px solid var(--line);
}
.outlets-list > .outlet-row:last-child { border-bottom: 1px solid var(--line); }
@media (min-width: 780px) {
  .outlet-row { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); }
}
.outlet-row__info { display: flex; flex-direction: column; gap: 0.9rem; }
/* The one serif line in an otherwise all-sans row -- same move as every
   other heading on the site, so a directory of twelve names still reads as
   this brand's page and not a spreadsheet of any hair salon's addresses. */
.salon__name { font-family: var(--display); font-weight: 400; font-size: 1.4rem;
               line-height: 1.2; color: var(--text); }

/* Address / tel / WhatsApp: one fact per row, never sharing a line, each led
   by its own coloured icon so the three read as distinct actions at a glance
   -- not three identical grey lines the eye has to parse word by word. */
.salon__row { display: flex; align-items: flex-start; gap: 0.6rem; }
.salon__row .ico { flex: none; width: 18px; height: 18px; margin-top: 0.05rem; }
.salon__row--addr .ico { color: var(--pin-accent); }
.salon__row--tel .ico  { color: var(--tel-accent); }
.salon__row--wa .ico   { color: var(--wa-brand); }
.salon__addr { font-size: 0.88rem; line-height: 1.5; color: var(--text-soft); }
.salon__tel, .salon__wa { font-size: 0.88rem; line-height: 1.4; }
.salon__tel a, .salon__wa a { color: var(--text); text-decoration: none; }
.salon__tel a:hover, .salon__wa a:hover { text-decoration: underline; }

/* Price list is the one action that leaves the page, so it gets the site's
   quiet arrow-link (underline + step-out arrow) instead of a second button --
   WhatsApp already has its own row above and needs no box to repeat it. */
.salon__actions { margin-top: 0.3rem; }
.salon__actions .link { min-height: 0; }

/* The map itself -- a fixed aspect-ratio box (not stretched to match the
   info column's height, which varies row to row with address length) so
   every embed is a consistent, predictable shape regardless of how much
   text sits beside it. */
.outlet-row__map { aspect-ratio: 4 / 3; background: var(--line); }
.outlet-row__map iframe { width: 100%; height: 100%; border: 0; display: block; }

/* ------------------------------------------------------- locator map ----- */
/* Homepage "Find Your Nearest Salon", 2026-09-01 -- a plain full-width map
   sitting above the region cards below, matching the Figma reference's own
   map plate in position and proportion (node 4262:187, image 102, ~2.1:1). A
   generic Singapore view rather than a fabricated pins-everywhere graphic:
   the twelve individual pins already live on /salons, one real embed per
   outlet -- this slot's job is just "here is roughly where we are." */
.locator-map {
  margin-top: clamp(2rem, 4vw, 3rem);
  aspect-ratio: 21 / 10;
  background: var(--line);
}
.locator-map iframe { width: 100%; height: 100%; border: 0; display: block; }
@media (max-width: 700px) { .locator-map { aspect-ratio: 4 / 3; } }

/* -------------------------------------------------------- region cards --- */
/* Five cards, one per region -- see render_region_cards() in build.py. Each
   is just a label and a plain list of mall names, no click-through (the
   comp shows plain text, not links); `salons.html` stays the only page with
   anything actionable. DOM order (North/Central/North-East/West/East)
   reproduces the comp's own grid layout: North+Central share the top row,
   North-East+West the second, East sits alone on the third. */
.region-cards {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.9rem, 1.6vw, 1.4rem);
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
}
@media (max-width: 600px) { .region-cards { grid-template-columns: 1fr; } }
.region-card {
  background: var(--cream);
  border: 1px solid var(--line);
  padding: clamp(1.25rem, 1.8vw, 1.6rem) clamp(1.4rem, 2vw, 1.75rem);
}
.region-card__label {
  font-family: var(--body); font-weight: 700; font-size: 1.1rem;
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--text);
}
.region-card__list {
  margin-top: 0.75rem; display: grid; gap: 0.35rem;
  list-style: disc; padding-left: 1.1rem;
}
.region-card__list li { font-size: 0.9rem; color: var(--text-soft); }

/* ------------------------------------------------------------ /salons ---- */
/* Rebuilt 2026-09-01 to match the Figma reference (Apgujeong Website
   Revamp file, "#location" frame, node 4268:391) -- a pill jump-nav and
   region-grouped two-column salon cards, replacing the earlier four-column
   labelled-card nav (`.outlet-nav*` above, now dormant) and single-column
   `.outlet-row` list (also above, now dormant). */
.region-pills { background: var(--cream); border-bottom: 1px solid var(--line); }
.region-pills__inner {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem;
  padding-block: clamp(1.5rem, 2.6vw, 2rem);
}
.pill {
  display: inline-flex; align-items: center; min-height: 42px;
  padding: 0.55rem 1.25rem;
  border: 1px solid var(--line-2);
  border-radius: 6px;
  font-size: var(--t-nav); font-weight: 600;
  letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--text); text-decoration: none;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease),
              border-color 0.2s var(--ease);
}
.pill:hover { background: var(--ink); color: #fff; border-color: var(--ink); }
/* .region-pills/.pill above are dormant since 2026-09-02, superseded by
   .salon-overview-section below -- see render_region_pills()'s docstring
   in build.py. Left in place, unused, same "one word to revert" pattern as
   every other superseded section on this site. */

/* Overview, replacing the region-pills jump-nav above -- see
   render_salon_overview() in build.py. Takes its shape from the homepage's
   own locator (map, then region groups, see .locator-map/.region-cards
   below) rather than restating a plain nav strip: the same all-outlets
   map used there, paired beside a clickable region list here -- the map
   answers "where", the list answers "click to jump to that card below".
   Same --cream ground as the masthead so the two read as one header zone
   before the hairline into the region list proper. White cards on cream,
   the same "cream section, white card" pattern `.gcard`/`.mgr` already
   use elsewhere -- not a new visual language. */
.salon-overview-section {
  background: var(--cream); border-bottom: 1px solid var(--line);
  padding-block: clamp(1.75rem, 3vw, 2.5rem);
}
.salon-overview { display: grid; gap: clamp(1.25rem, 2.5vw, 2rem); }
@media (min-width: 980px) {
  .salon-overview { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); align-items: stretch; }
}
/* No min-height here: paired with aspect-ratio, a min-height forces a
   minimum WIDTH too (260px tall at 16:11 needs >=378px wide) -- wider
   than the content column at 360-390px viewports, so it overflowed the
   page horizontally. aspect-ratio alone already keeps the map a sensible
   height as it shrinks with its container; nothing forces it short. */
.salon-overview__map { aspect-ratio: 16 / 11; background: var(--line); }
@media (min-width: 980px) { .salon-overview__map { aspect-ratio: auto; } }
.salon-overview__map iframe { width: 100%; height: 100%; border: 0; display: block; }

.salon-overview__cards {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(0.9rem, 1.6vw, 1.4rem);
  align-content: start;
}
@media (max-width: 560px) { .salon-overview__cards { grid-template-columns: 1fr; } }
.salon-overview__card {
  background: #fff; border: 1px solid var(--line);
  padding: clamp(1.1rem, 1.6vw, 1.4rem) clamp(1.2rem, 1.8vw, 1.5rem);
}
/* Same label-over-bulleted-list shape as the homepage's plain-text
   .region-card (no border-bottom under the label, no per-row divider
   lines) -- but every item here is a real link, so it needs its own
   "this is clickable" cue that doesn't depend on a mouse hover (most of
   this site's traffic is mobile, which never hovers): a soft permanent
   underline that darkens on hover/focus, rather than a bordered row or an
   icon that only appears on interaction. */
.salon-overview__label {
  font-family: var(--body); font-weight: 700; font-size: 1rem;
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--text);
}
.salon-overview__list {
  margin-top: 0.65rem; display: grid; gap: 0.4rem;
  list-style: disc; padding-left: 1.1rem;
}
.salon-overview__list a {
  font-size: 0.88rem; line-height: 1.4; color: var(--text-soft);
  text-decoration: underline; text-decoration-color: var(--line-2);
  text-underline-offset: 3px;
  transition: color 0.2s var(--ease), text-decoration-color 0.2s var(--ease);
}
.salon-overview__list a:hover,
.salon-overview__list a:focus-visible {
  color: var(--text); text-decoration-color: currentColor;
}

.region-block + .region-block { margin-top: clamp(3rem, 5vw, 4.5rem); }
.region-block__label {
  font-family: var(--body); font-weight: 700; font-size: clamp(1.35rem, 1.1rem + 1vw, 1.75rem);
  letter-spacing: 0.03em; text-transform: uppercase; color: var(--text);
  padding-bottom: 0.9rem; border-bottom: 1px solid var(--line);
}
.salon-cards {
  display: grid; grid-template-columns: 1fr;
  gap: clamp(1.25rem, 2vw, 1.75rem);
  margin-top: clamp(1.5rem, 2.6vw, 2rem);
}
@media (min-width: 760px) { .salon-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Sep 2026: cards in the same row are grid-stretched to equal OUTER height
   already, but that alone doesn't make them look aligned -- the two are not
   the same thing. A card's content (address length, one phone line vs two)
   varies, and plain block layout just leaves the leftover stretched height
   as dead space after the map, so the map itself ends up sitting at a
   different height in every card, and the shorter card reads as if it has
   less in it (client: "boxes not aligned", "not good design"). Flexing the
   card column-wise and letting the body (not the map) absorb the leftover
   space keeps the map's own height and its position from the bottom fixed
   and identical across every card in a row, regardless of body content. */
.salon-card {
  background: var(--cream); border: 1px solid var(--line);
  display: flex; flex-direction: column;
}
.salon-card__body { flex: 1 1 auto; }
.salon-card__map { aspect-ratio: 21 / 10; background: var(--line); flex: none; }
.salon-card__map iframe { width: 100%; height: 100%; border: 0; display: block; }
.salon-card__body {
  padding: clamp(1.5rem, 2.4vw, 2rem);
  text-align: center;
}
.salon-card__name {
  font-family: var(--body); font-weight: 700; font-size: 1.15rem;
  letter-spacing: 0.03em; text-transform: uppercase; color: var(--text);
}
.salon-card__info {
  margin-top: 1.1rem; padding-top: 1.1rem;
  border-top: 1px solid var(--line-2);
  display: grid; gap: 0.9rem;
}
.salon-card__field-label { font-weight: 700; font-size: 0.85rem; color: var(--text); }
.salon-card__field p:not(.salon-card__field-label),
.salon-card__field .tel-block,
.salon-card__field .hours-block {
  margin-top: 0.15rem; font-size: 0.88rem; line-height: 1.5; color: var(--text-soft);
}
.salon-card__field a { color: var(--text-soft); text-decoration: none; }
.salon-card__field a:hover { color: var(--text); text-decoration: underline; }
/* Sep 2026: "Address"/"Contact"/"Operating Hours" labels sat at a different
   height on every card, because the value above each label is a different
   number of lines card to card -- some addresses carry a landmark line and
   some don't, and one outlet (punggol) has only a mobile number where every
   other card has two. Reserving a fixed 2-line height on the Address value
   and the Contact tel-block (regardless of whether 1 or 2 lines actually
   render) means every card's "Contact" and "Operating Hours" label starts
   at the exact same y, whatever that specific card actually has to show.

   Second pass, same day: fixing only Address/Contact wasn't enough -- the
   CTA divider and the Price List/WhatsApp buttons come AFTER Operating
   Hours, and that field still ranged from 1 line ("Daily") to 3
   (marina-bay's Mon-Fri/Sat/Closed), so the buttons still landed at a
   different height on every card even once the two fields above them were
   fixed. Same fix, sized to the tallest real case (3 lines) instead of 2,
   applied to every card -- not just marina-bay's -- so the CTA row, and
   the map below it, sit at the exact same position on every single card on
   the page, not just within one grid row. This is also why `.salon-card`
   no longer NEEDS its grid-row stretch to look aligned -- every card's
   natural content height is now identical outright -- but the flex fix
   from two entries up is left in place as a floor, in case a future outlet
   ever needs a 4th hours group.

   The numbers: each line is 1.32rem (0.88rem font-size * 1.5 line-height).
   `.tel-block`/`.hours-block` are CSS grids with a 0.15rem `row-gap`
   BETWEEN rows, which a naive `lines * 1.32rem` undercounts -- verified
   live and caught exactly this: 2.64rem left marina-bay's real 3-row
   content (3 * 1.32rem + 2 gaps = 4.26rem) taller than every other card's
   reserved height, and left punggol's real 1-row content clamped 0.15rem
   short of every other card's real 2-row content. `.salon-card__field
   --address` doesn't need the gap term -- its 2 lines are a plain `<br>`
   inside one `<p>`, not a grid, so there's no row-gap to add. */
.salon-card__field--address p:not(.salon-card__field-label) { min-height: 2.64rem; }
.salon-card__field--contact .tel-block { min-height: 2.79rem; }  /* 2 * 1.32rem + 1 gap */
.salon-card__field--hours .hours-block { min-height: 4.26rem; }  /* 3 * 1.32rem + 2 gaps */
/* Salon/Mobile tag ahead of each tel: link -- see render_phone_lines() in
   build.py. Replaces the old "6xxx xxxx · 9xxx xxxx" single joined string,
   which read as one confusing number and only ever dialled the first half
   no matter which part was tapped.

   `.tel-block` is a 2-column grid, not two independently-centred lines --
   the first cut put "Salon"/"Mobile" as plain text before each link, so
   the numbers themselves sat at different x-positions because "Mobile" is
   a character wider than "Salon" (client: "salon numbers are smaller than
   mobile", i.e. the numbers didn't line up). The tag column is
   right-aligned and the number column left-aligned, so both numbers share
   one edge regardless of label width; `inline-grid` keeps the whole block
   one unit so the surrounding `text-align: center` still centres it. */
.tel-block {
  display: inline-grid; grid-template-columns: auto auto;
  column-gap: 0.45em; row-gap: 0.15rem;
  justify-items: start; text-align: left;
}
.tel-tag {
  justify-self: end;
  font-weight: 700; font-size: 0.72rem; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-faint);
}
/* Days-left, time-right schedule -- see render_hours_lines() in build.py.
   Same `inline-grid` pattern as `.tel-block` just above, for the same
   reason: independent centred lines put the times at different x-positions
   depending on how wide "Mon–Fri" vs "Sat/Sun/PH" happened to be. Client:
   read easier with the day on the left, time on the right.

   Second pass, same day: the time RANGE itself ("10:00am–8:30pm") is now
   three more columns -- start / dash / end -- not one string, so every
   start time, dash and end time lines up down the card too, the same way
   the day column already did. `.hours-status` (marina-bay's "Closed") spans
   all three time columns instead of leaving empty start/dash/end cells. */
.hours-block {
  display: inline-grid; grid-template-columns: auto auto auto auto;
  column-gap: 0.35em; row-gap: 0.15rem;
  justify-items: start; text-align: left;
}
.hours-day { justify-self: end; font-weight: 600; color: var(--text); }
.hours-start { justify-self: end; color: var(--text-soft); }
.hours-dash { justify-self: center; color: var(--text-soft); }
.hours-end { justify-self: start; color: var(--text-soft); }
.hours-status { grid-column: 2 / span 3; justify-self: start; color: var(--text-soft); }
.salon-card__actions {
  margin-top: 1.25rem; padding-top: 1.1rem;
  border-top: 1px solid var(--line-2);
  display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem;
}
.salon-card__actions .btn { min-height: 42px; padding: 0.6rem 0.5rem; }

/* ----------------------------------------------------------------- faq --- */
.faqs { border-top: 1px solid var(--line); max-width: 56rem; margin-inline: auto; }
.faq { border-bottom: 1px solid var(--line); }
.faq summary {
  display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
  padding: 1.35rem 0; cursor: pointer; list-style: none;
  font-family: var(--display); font-weight: 400; font-size: var(--t-md);
  line-height: 1.25; transition: opacity 0.2s;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { opacity: 0.62; }
.faq summary::after {
  content: ""; flex: none; width: 12px; height: 12px;
  border-right: 1px solid currentColor; border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translate(-3px, -3px);
  transition: transform 0.28s var(--ease);
}
.faq[open] summary::after { transform: rotate(-135deg) translate(-4px, -4px); }
.faq__a { padding-bottom: 1.5rem; }
.faq__a p { max-width: 66ch; color: var(--text-soft); }

/* ------------------------------------------------------------ cta band --- */
/* The one dark surface on the site. */
.cta {
  position: relative; overflow: hidden;
  display: flex; align-items: flex-end;
  background: var(--ink); color: #fff;
  padding-block: clamp(3.5rem, 7vw, 5.5rem);
}
/* Layout pass, Aug 2026: the band's height now comes from matching the
   shopfront photo's own aspect-ratio (see below), which made it tall enough
   that a single heading + one line + a button, vertically CENTRED, left a
   huge dead gap of pure ink above and below the text -- floating rather than
   closing the page. Anchoring to the bottom instead lets the photo's own
   signage own the top of the frame and the message read as a grounded
   close. Content itself also gained an eyebrow (parity with every other
   section) and a fuller lede -- see src/partials/cta.html. */
/* Figma revamp, Aug 2026: full-bleed real shopfront photo (was a contained
   46%-width grayscale strip of a colour-result plate). The dark-to-lit wash
   that makes the left side readable is baked into the photograph itself --
   it is the sign lit at night, not a CSS gradient -- so there is no ::after
   scrim to layer on top any more; adding one would just double the wash. */
.cta__media { position: absolute; inset: 0; z-index: 0; }
.cta__media img { width: 100%; height: 100%; object-fit: cover; object-position: 38% 22%; }
@media (max-width: 899px) { .cta__media { display: none; } }
@media (min-width: 900px) {
  /* Same hero lesson, applied here: the band's SHAPE decides how much of the
     photo shows, not padding-block alone -- this section used to be exactly
     content-height (padding + a "Contact" heading, one line and a button,
     measured live at 289px total), which cropped ~52% off the 2.51:1
     shopfront plate to fill it. aspect-ratio:1920/766 matches the band's
     shape to the photo's, floored so the (small) text content never clips
     and ceilinged so an ultrawide window doesn't turn this into an
     oversized banner. */
  .cta { aspect-ratio: 1920 / 766; min-height: 320px; max-height: 780px; }
}

.cta__inner { position: relative; z-index: 1; display: grid; gap: clamp(1.75rem, 4vw, 3.5rem); }
@media (min-width: 900px) {
  /* Second attempt, Aug 2026 -- the first one (a 3-column grid, "Contact" in
     its own column beside the lede+button) was wrong at the root, not just
     misaligned: the Figma reference stacks "Contact" DIRECTLY ABOVE the
     lede and button, left-aligned in a single column, never beside them.
     Top-aligning the old side-by-side columns still left them side by
     side, which is why that fix didn't read as fixed. This is a plain
     single-column stack (same as the mobile-width rule below 900px
     already was, unchanged) with a max-width standing in for what the old
     empty third grid column did -- keep the text clear of the photograph
     on the right without needing a column dedicated to it. */
  /* `.cta__inner` also carries the shared `.wrap` utility class, which sets
     `margin-inline: auto` to CENTRE a full-width block -- exactly wrong
     once this is capped at 26rem inside a `display:flex` `.cta`, where auto
     margins on a flex item eat the row's free space and centre it instead
     of leaving it pinned left against the photo. Overridden back to 0. */
  .cta__inner { max-width: 26rem; margin-inline: 0; }
}
.cta .btn-row { margin-top: 1.75rem; }

/* -------------------------------------------------------------- footer --- */
/* One row. The navigation lives in the header on every page. */
.ftr { background: var(--ink); color: #fff; padding-block: 1.5rem; }
.ftr__legal { font-size: 0.78rem; color: var(--on-dark-faint); }
.ftr__social { display: flex; align-items: center; gap: 0.9rem; }
.ftr__social > span { font-size: var(--t-mini); font-weight: 600; letter-spacing: 0.16em;
                      text-transform: uppercase; color: var(--on-dark-faint); }
.ftr__social ul { display: flex; gap: 0.15rem; }
.ftr__social a {
  display: grid; place-items: center; width: 42px; height: 42px;
  color: var(--on-dark-soft); transition: color 0.2s;
}
.ftr__social a:hover { color: #fff; }

/* --------------------------------------------------------- page header --- */
/* Inner pages open on a light cream band, not a dark one. */
.masthead {
  background: var(--cream); color: var(--text);
  padding-block: clamp(8.5rem, 14vw, 11rem) clamp(3rem, 6vw, 4.5rem);
  text-align: center;
  border-bottom: 1px solid var(--line);
}
.masthead__inner { max-width: 46rem; margin-inline: auto; }
.masthead__inner > * + * { margin-top: 1.15rem; }
.masthead .eyebrow { justify-content: center; }
.masthead .lede { margin-inline: auto; }
.masthead .btn-row { justify-content: center; margin-top: 1.85rem; }

/* ------------------------------------------------------------ work hero -- */
/* Rebuilt 2026-08-27 to the Figma MYD_Work comp -- replaces the three-plate
   mosaic (v11) with a single full-bleed photo, the same "photo fills the
   entire band, text sits in its own baked-in wash" pattern as `.svc-hero`
   and `.cta`. The comp's hero and this page's supplied photo
   (`work-banner`) are the exact same 1920:766 shopfront-chairs plate ratio
   those two already use, which is a strong signal this was cropped
   specifically for this treatment -- not a coincidence to fight with a
   different layout. Box SHAPE matches the photo's own ratio via
   `aspect-ratio` on the section itself (floored for text safety, ceilinged
   for ultrawide safety), so `cover` has nothing left to crop. */
.work-hero {
  position: relative;
  background: var(--cream);
  display: flex; align-items: center;
  padding-block: clamp(3rem, 6vw, 5rem);
  overflow: hidden;
}
@media (min-width: 900px) {
  .work-hero { aspect-ratio: 1920 / 766; min-height: 460px; max-height: 780px; }
}
.work-hero__media { position: absolute; inset: 0; }
.work-hero__media img { width: 100%; height: 100%; object-fit: cover; }
.work-hero__inner { position: relative; z-index: 2; }
.work-hero__panel { max-width: 34rem; }
.work-hero__panel > * + * { margin-top: 1.3rem; }
.work-hero__panel .h-hero { max-width: 14ch; }
.work-hero__panel .lede { max-width: 40ch; }
.work-hero__panel .btn-row { margin-top: 2.1rem; }

@media (max-width: 899px) {
  .work-hero { min-height: 0; padding-block: 0; display: block; }
  .work-hero__media { position: relative; inset: auto;
                       width: 100%; aspect-ratio: 1920 / 766; }
  .work-hero__inner { padding-block: 1.5rem clamp(2.5rem, 7vw, 3.5rem); }
}

/* ---------------------------------------------------------- ref photo ---- */
/* "A Reference Photo" section, same pass -- the comp runs this the same
   full-bleed way as the hero above it (confirmed off the Figma metadata: the
   photo's own clipping mask spans the full 1440px frame width, edge to
   edge, not contained inside the page's normal wrap padding like every
   other `.split` section on the site). The supplied photo (`work-reference`)
   is the same 1920:766 ratio again, so this reuses the identical box-shape
   pattern rather than inventing a third one. Deliberately its own class,
   not a shared abstraction with `.work-hero` -- same reasoning as `.svc-hero`
   and `.cta` staying separate blocks despite the same underlying pattern:
   each has its own padding/floor/ceiling tuned to its own copy. */
.ref-photo {
  position: relative;
  display: flex; align-items: center;
  padding-block: clamp(3rem, 6vw, 5rem);
  overflow: hidden;
}
@media (min-width: 900px) {
  .ref-photo { aspect-ratio: 1920 / 766; min-height: 420px; max-height: 700px; }
}
.ref-photo__media { position: absolute; inset: 0; }
.ref-photo__media img { width: 100%; height: 100%; object-fit: cover; }
.ref-photo__inner { position: relative; z-index: 2; }
.ref-photo__panel { max-width: 30rem; }
.ref-photo__panel > * + * { margin-top: 1.1rem; }
.ref-photo__panel .btn-row { margin-top: 1.9rem; }

@media (max-width: 899px) {
  .ref-photo { min-height: 0; padding-block: 0; display: block; }
  .ref-photo__media { position: relative; inset: auto;
                       width: 100%; aspect-ratio: 1920 / 766; }
  .ref-photo__inner { padding-block: 2.5rem clamp(2.5rem, 7vw, 3.5rem); }
}

/* ------------------------------------------------------------- reveal ---- */
/* Gated behind .js, set inline in <head>: a blocked main.js must never leave
   the page rendering blank. */
.js .r { opacity: 0; transform: translateY(16px); transition: opacity 0.7s var(--ease), transform 0.7s var(--ease); }
.js .r.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .js .r { opacity: 1; transform: none; transition: none; }
  .tile:hover img, .svc-tile:hover img { transform: none; }
}

/* ==========================================================================
   SERVICES PAGE — built to the client comp, Aug 2026
   --------------------------------------------------------------------------
   Everything below this line is new for /services, plus three changes that are
   necessarily SITE-WIDE because the comp draws them in the shared chrome:

     1. THE BRAND MARK IS AN IMAGE AGAIN. The comp draws the real wordmark, the
        one on the shopfront. See render_logo() in build.py. The typeset
        `.lockup` rules above are left intact so reverting is one line.
     2. THE FOOTER IS A FOUR-COLUMN SITEMAP AGAIN. v8 reduced it to a
        signature; the comp puts the sitemap back.
     3. THERE IS AN ACCENT COLOUR. v8's header note says there is none and no
        green. The comp's primary buttons are a deep forest green and nothing
        else on the page is coloured, so the accent is exactly one thing: the
        action. Photographs are still the only other colour on the page.

   The comp also sets the three service-group names in the SERIF, IN CAPS,
   which the v8 note forbids. It is allowed here and only here: it is applied
   to group names, never to a statement. "Everything We Do" and "The Myeongdong
   Approach" stay sentence case, which is what that rule was protecting.
   ========================================================================== */

:root {
  /* One action colour. 10.5:1 against white, so button text passes AA at any
     size, and it is dark enough to sit beside the ink without competing. */
  --accent:     #14452B;
  --accent-ink: #FFFFFF;
  --accent-hi:  #1C5C3A;   /* hover only */
  --cream-3:    #F1EDE6;   /* one step below --cream: the boxed strips */
}

/* ------------------------------------------------------- shared chrome --- */
.brandmark { display: inline-flex; align-items: center; padding-block: 7px; }
.brandmark img { height: clamp(26px, 2.6vw, 38px); width: auto; }
.ftr .brandmark img { height: 36px; }

.btn--go {
  --btn-bg: var(--accent); --btn-fg: var(--accent-ink); --btn-bd: var(--accent);
}
.btn--go:hover { --btn-bg: var(--accent-hi); --btn-fg: #fff; --btn-bd: var(--accent-hi); }
/* `:is(.on-dark, ...) .btn` further up is two classes and would otherwise win,
   turning the primary action white inside the closing band -- where it needs to
   stay green, because the white button beside it is the SECONDARY action. */
:is(.on-dark, .on-ink, .on-ink-2, .cta) .btn--go {
  --btn-bg: var(--accent); --btn-fg: var(--accent-ink); --btn-bd: var(--accent);
}
:is(.on-dark, .on-ink, .on-ink-2, .cta) .btn--go:hover {
  --btn-bg: var(--accent-hi); --btn-fg: #fff; --btn-bd: var(--accent-hi);
}
/* The WhatsApp glyph is a mask, not an <img>, so it inherits the button ink
   instead of staying black on the green. */
.btn--go.btn--wa::before {
  background: currentColor;
  -webkit-mask: var(--wa-icon) center / contain no-repeat;
          mask: var(--wa-icon) center / contain no-repeat;
}
/* --------------------------------------------------------- small parts --- */
/* The display face, in caps, letterspaced. Group names only — see the note above. */
.serif-caps {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.375rem;
  line-height: 1.2;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  color: var(--text);
}
.serif-caps--lg { font-size: clamp(1.5rem, 1.1rem + 1.1vw, 1.95rem); }

.micro-label {
  font-size: var(--t-mini); font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--text-soft);
}
.rule { display: block; width: 34px; height: 1px; background: var(--line-2); }
.on-dark .rule { background: var(--line-dark); }

.s-head__sub { color: var(--text-soft); font-size: 0.95rem; max-width: 62ch; margin-inline: auto; }
.s-head .eyebrow.is-plain { justify-content: center; }
/* Outranks the line above, which would otherwise re-centre the eyebrow of a
   left-aligned head (three classes beats .s-head--left .eyebrow's two). */
.s-head--left .eyebrow.is-plain { justify-content: flex-start; }

.foot-note {
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
  text-align: center;
  font-size: 0.8125rem; line-height: 1.7; color: var(--text-faint);
}
.on-cream-2 { background: var(--cream-3); color: var(--text); }
.section--flush-x { padding-inline: 0; }

/* --------------------------------------------------------- services hero - */
/* Full-bleed, same pattern as .cta below: the plate fills the ENTIRE band,
   not a right-hand box. Earlier passes treated this as a "light split" --
   photo confined to the right ~52%, copy on plain cream ground -- which was
   right for an AI-generated plate with no wash of its own, but this is a
   real shopfront photo that already carries its own light-to-dark wash
   baked into the left third. Confining it to a small floating box just
   throws that wash away and leaves the reference's full-width band
   unreproduced. Box SHAPE matches the photo's own 1920:766 ratio (via
   aspect-ratio on the section itself, floored/ceilinged for text safety and
   ultrawide safety) so `cover` has nothing left to crop -- same lesson as
   .cta, which is the same photo ratio for the same reason. */
.svc-hero {
  position: relative;
  background: var(--cream);
  display: flex; align-items: center;
  padding-block: clamp(3rem, 6vw, 5rem);
  overflow: hidden;
}
@media (min-width: 900px) {
  .svc-hero { aspect-ratio: 1920 / 766; min-height: 460px; max-height: 780px; }
}
.svc-hero__media { position: absolute; inset: 0; }
/* No wash overlay here — this plate already carries its own light wash over
   the left third, baked in. A CSS gradient on top of that would double it. */
.svc-hero__media img { width: 100%; height: 100%; object-fit: cover; }
.svc-hero__inner { position: relative; z-index: 2; }
.svc-hero__panel { max-width: 34rem; }
.svc-hero__panel > * + * { margin-top: 1.3rem; }
.svc-hero__panel .h-hero { max-width: 11ch; }
.svc-hero__panel .lede { max-width: 42ch; }
.svc-hero__panel .btn-row { margin-top: 2.1rem; }

@media (max-width: 899px) {
  .svc-hero { min-height: 0; padding-block: 0; display: block; }
  .svc-hero__media { position: relative; inset: auto;
                      width: 100%; aspect-ratio: 1920 / 766; }
  .svc-hero__inner { padding-block: 1.5rem clamp(2.5rem, 7vw, 3.5rem); }
}

/* ------------------------------------------------------- group cards ----- */
.gcards {
  display: grid; gap: clamp(1rem, 1.6vw, 1.5rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  margin-top: clamp(2.25rem, 4vw, 3.25rem);
}
.gcard {
  display: flex; flex-direction: column;
  background: var(--cream);
  border: 1px solid var(--line);
}
.gcard__media { display: block; overflow: hidden; }
.gcard__media img {
  width: 100%; aspect-ratio: 5 / 4; object-fit: cover;
  transition: transform 0.6s var(--ease);
}
.gcard:hover .gcard__media img { transform: scale(1.035); }
.gcard__body { flex: 1; padding: clamp(1.4rem, 2.2vw, 1.85rem); }
.gcard__body > * + * { margin-top: 0.85rem; }
.gcard__body .ticks { margin-top: 1.15rem; }
.gcard__body .ticks li { font-size: 0.9rem; color: var(--text-soft); }
.eyebrow .num { font-family: var(--body); letter-spacing: 0.1em; }

.gcard__foot {
  display: flex; align-items: center; justify-content: center; gap: 0.75rem;
  min-height: 52px; padding: 0.9rem 1rem;
  border-top: 1px solid var(--line);
  font-size: var(--t-nav); font-weight: 600;
  letter-spacing: 0.15em; text-transform: uppercase;
  text-decoration: none;
  transition: background 0.25s var(--ease), color 0.25s var(--ease);
}
.gcard__foot .ico { transition: transform 0.25s var(--ease); }
.gcard__foot:hover { background: var(--ink); color: #fff; }
.gcard__foot:hover .ico { transform: translateX(4px); }

/* --------------------------------------------------- detailed breakdown -- */
.d-head { display: grid; gap: 1rem clamp(2rem, 5vw, 4rem); align-items: end; }
@media (min-width: 900px) { .d-head { grid-template-columns: 1fr 1fr; } }
.d-head .eyebrow { margin-bottom: 0.7rem; }
.d-head .lede { max-width: 44ch; }

.drows { margin-top: clamp(2rem, 4vw, 3rem); }
.drow { display: grid; align-items: stretch; background: var(--white); }
.drow:nth-child(even) { background: var(--cream); }

.drow__media { position: relative; min-height: 320px; }
.drow__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

.drow__body {
  padding: clamp(2rem, 4.5vw, 3.75rem) clamp(1.25rem, 4vw, 3.5rem);
  align-self: center; max-width: 42rem;
}
.drow__body > * + * { margin-top: 1rem; }
.drow__body .rule { margin-top: 1.1rem; }
.drow__body .lede { margin-top: 1.25rem; max-width: 46ch; }
.drow__body .micro-label { margin-top: 1.75rem; }
.drow__body .ticks { margin-top: 0.8rem; }
.drow__body .ticks li { font-size: 0.9rem; color: var(--text-soft); }

.drow__foot {
  margin-top: 1.85rem;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 1rem;
}
.drow__note {
  display: flex; align-items: center; gap: 0.5rem;
  /* --text-faint is 4.81:1 on --cream and only 4.40:1 on --cream-3, which is
     the ground under the third row. --text-soft clears AA on all three. */
  font-size: 0.8125rem; color: var(--text-soft);
}
.drow__note .ico { color: var(--text-soft); }

@media (min-width: 900px) {
  .drow { grid-template-columns: 1fr 1fr; }
  .drow--flip .drow__media { order: 2; }
}
@media (max-width: 899px) {
  .drow__media { min-height: 0; aspect-ratio: 3 / 2; }
  .drow__media img { position: static; }
}

/* -------------------------------------------------------------- strip ---- */
.strip {
  display: grid; gap: 1.75rem clamp(1.5rem, 3vw, 2.75rem);
  margin-top: clamp(2.5rem, 4.5vw, 3.5rem);
}
@media (min-width: 760px) { .strip { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; } }
.strip li { display: flex; align-items: flex-start; gap: 1rem; padding-inline: clamp(0px, 2.4vw, 2.25rem); }
.strip li:first-child { padding-left: 0; }
.strip li:last-child { padding-right: 0; }
@media (min-width: 760px) { .strip li + li { border-left: 1px solid var(--line-2); } }
.strip .ico { color: var(--text); margin-top: 2px; }
.strip li > span:last-child { display: flex; flex-direction: column; gap: 0.4rem; }
.strip li > span:last-child > span:last-child {
  font-size: 0.875rem; line-height: 1.65; color: var(--text-soft);
}
.strip--boxed {
  background: var(--cream); border: 1px solid var(--line);
  padding: clamp(1.5rem, 3vw, 2.25rem);
}
@media (min-width: 760px) { .strip--boxed { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* --------------------------------------------------- four-column blocks -- */
.cols4 {
  display: grid; gap: 2.5rem 0;
  margin-top: clamp(2.5rem, 4.5vw, 3.5rem);
}
@media (min-width: 700px)  { .cols4 { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2.75rem 0; } }
@media (min-width: 1040px) { .cols4 { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; } }
.col4 { padding-inline: clamp(1.25rem, 2.6vw, 2.4rem); }
.col4:first-child { padding-left: 0; }
.col4:last-child { padding-right: 0; }
@media (min-width: 700px) { .col4 + .col4 { border-left: 1px solid var(--line-2); } }
@media (min-width: 700px) and (max-width: 1039px) {
  .col4:nth-child(odd) { padding-left: 0; border-left: 0; }
  .col4:nth-child(even) { padding-right: 0; }
}
@media (max-width: 699px) { .col4 { padding-inline: 0; } }
.col4 .ico { color: var(--text); }
.col4__kr {
  margin-top: 1.4rem;
  font-family: var(--kr); font-size: 0.8125rem; letter-spacing: 0.04em;
  color: var(--text-faint);
}
.col4 .h-md { margin-top: 0.3rem; }
.col4 > p:not(.col4__kr) {
  margin-top: 0.9rem; font-size: 0.9rem; line-height: 1.7; color: var(--text-soft);
}
.col4 .ticks { margin-top: 1.3rem; }
.col4 .ticks li { font-size: 0.85rem; color: var(--text-faint); }

/* ------------------------------------------------------------- journey --- */
.jsteps {
  display: grid; gap: 2.5rem clamp(1.25rem, 2.4vw, 2.25rem);
  margin-top: clamp(2.5rem, 4.5vw, 3.5rem);
  counter-reset: none;
}
@media (min-width: 620px)  { .jsteps { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .jsteps { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
.jstep { position: relative; display: flex; flex-direction: column; }
.jstep__n {
  font-family: var(--display); font-size: 1.05rem; letter-spacing: 0.06em;
  color: var(--text-faint);
}
.jstep .h-md { margin-top: 0.6rem; font-size: 1.375rem; }
.jstep > p { margin-top: 0.9rem; font-size: 0.85rem; line-height: 1.65; color: var(--text-soft); }
.jstep__img { display: block; margin-top: auto; padding-top: 1.85rem; }
.jstep__img img {
  width: clamp(112px, 9vw, 148px); aspect-ratio: 1; object-fit: cover;
  border-radius: 50%;   /* the one round shape on the site, and the comp's */
}
/* The chevron between steps. Sits on the title line, hidden on the last step
   and whenever the row wraps. */
@media (min-width: 1000px) {
  .jstep:not(:last-child)::after {
    content: ""; position: absolute; top: 2.6rem; right: clamp(-1.1rem, -1.2vw, -0.6rem);
    width: 22px; height: 12px;
    background: currentColor; opacity: 0.35;
    -webkit-mask: var(--arrow-icon) center / contain no-repeat;
            mask: var(--arrow-icon) center / contain no-repeat;
  }
}

/* ----------------------------------------------------------- standards --- */
.stds { display: grid; gap: 2.5rem 0; margin-top: clamp(2.5rem, 4.5vw, 3.5rem); }
@media (min-width: 900px) { .stds { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; } }
.std { display: flex; align-items: flex-start; gap: 1.1rem; padding-inline: clamp(1.25rem, 2.6vw, 2.4rem); }
.std:first-child { padding-left: 0; }
.std:last-child { padding-right: 0; }
@media (min-width: 900px) { .std + .std { border-left: 1px solid var(--line-2); } }
@media (max-width: 899px) { .std { padding-inline: 0; } }
.std .ico { color: var(--text); margin-top: 4px; }
.std .h-md { font-size: 1.3125rem; }
.std > div > p { margin-top: 0.8rem; font-size: 0.9rem; line-height: 1.7; color: var(--text-soft); }
.std .ticks { margin-top: 1.2rem; }
.std .ticks li { font-size: 0.85rem; color: var(--text-faint); }

/* --------------------------------------------------------------- looks --- */
.looks {
  display: grid; gap: clamp(0.75rem, 1.4vw, 1.25rem);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: clamp(2.5rem, 4.5vw, 3.5rem);
}
@media (min-width: 900px) { .looks { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.look { display: block; background: var(--white); border: 1px solid var(--line); text-decoration: none; }
.look > img { width: 100%; aspect-ratio: 7 / 8; object-fit: cover; transition: transform 0.6s var(--ease); }
.look { overflow: hidden; }
.look:hover > img { transform: scale(1.035); }
.look__foot {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  min-height: 48px; padding: 0.85rem clamp(0.85rem, 1.6vw, 1.15rem);
  border-top: 1px solid var(--line);
  font-size: var(--t-mini); font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
}
.look__foot .ico { transition: transform 0.25s var(--ease); }
.look:hover .look__foot .ico { transform: translateX(4px); }

/* ----------------------------------------------------- transformations --- */
.tcards { display: grid; gap: clamp(1.75rem, 3vw, 2.5rem); margin-top: clamp(2.5rem, 4.5vw, 3.5rem); }
@media (min-width: 820px) { .tcards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.tcard__pair {
  position: relative;
  display: grid; grid-template-columns: 1fr 1fr; gap: 2px;
  background: var(--white);
}
.tcard__pair img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
.tcard__handle {
  position: absolute; left: 50%; top: 50%; translate: -50% -50%;
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, 0.9); color: var(--ink);
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
}
/* BEFORE / AFTER sit on the photograph, which the rest of the site does not
   do. Each label carries its own small scrim rather than a wash over the whole
   frame, so the hair — which is the evidence — is never under anything. */
.tcard__lbl {
  position: absolute; bottom: 0.6rem; z-index: 2;
  padding: 0.28rem 0.55rem;
  background: rgba(12, 12, 12, 0.62);
  color: #fff;
  font-size: 0.625rem; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
}
.tcard__lbl--b { left: 0.6rem; }
.tcard__lbl--a { right: 0.6rem; }
.tcard .h-md { margin-top: 1.25rem; font-size: 1.3125rem; }
.tcard > p { margin-top: 0.6rem; font-size: 0.9rem; line-height: 1.7; color: var(--text-soft); }

/* ----------------------------------------------------------- faq (2-up) -- */
.faqs--two {
  border-top: 0; max-width: none;
  display: grid; gap: 0.75rem clamp(1rem, 2vw, 1.75rem);
  margin-top: clamp(2.25rem, 4vw, 3rem);
  align-items: start;
}
@media (min-width: 820px) { .faqs--two { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.faqs--two .faq { border: 1px solid var(--line); background: var(--white); }
.faqs--two .faq summary { padding: 1.15rem clamp(1rem, 2vw, 1.35rem); }
.faqs--two .faq__a { padding: 0 clamp(1rem, 2vw, 1.35rem) 1.35rem; }
.faqs--two .faq summary::after {
  width: 13px; height: 13px; border: 0; transform: none; margin-left: 1rem; flex: none;
  background:
    linear-gradient(var(--text), var(--text)) center / 100% 1.3px no-repeat,
    linear-gradient(var(--text), var(--text)) center / 1.3px 100% no-repeat;
  transition: transform 0.25s var(--ease), background-size 0.25s var(--ease);
}
.faqs--two .faq[open] summary::after {
  transform: none;
  background:
    linear-gradient(var(--text), var(--text)) center / 100% 1.3px no-repeat,
    linear-gradient(var(--text), var(--text)) center / 0 100% no-repeat;
}
.faqs__foot {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 0.75rem clamp(1.5rem, 3vw, 2.5rem);
  margin-top: clamp(2rem, 3.5vw, 2.75rem);
  font-size: 0.9rem; color: var(--text-soft);
}

/* ------------------------------------------------------------- promise --- */
.pros { display: grid; gap: 2.25rem 0; margin-top: clamp(2.25rem, 4vw, 3.25rem); }
@media (min-width: 640px)  { .pros { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .pros { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; } }
.pro {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 0.85rem; padding-inline: clamp(1rem, 2.4vw, 2.25rem);
}
.pro:first-child { padding-left: 0; }
.pro:last-child { padding-right: 0; }
@media (min-width: 640px) { .pro + .pro { border-left: 1px solid var(--line-2); } }
@media (min-width: 640px) and (max-width: 999px) {
  .pro:nth-child(odd) { padding-left: 0; border-left: 0; }
  .pro:nth-child(even) { padding-right: 0; }
}
.pro .ico { color: var(--text); }
.pro > span:last-child { font-size: 0.875rem; line-height: 1.65; color: var(--text-soft); max-width: 26ch; }

/* ------------------------------------------------------ arrange a visit -- */
.asteps { display: grid; gap: 1.75rem clamp(1.25rem, 2.4vw, 2.25rem); margin-top: 1.75rem; }
@media (min-width: 620px)  { .asteps { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1020px) { .asteps { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.astep { position: relative; display: flex; align-items: flex-start; gap: 0.9rem; padding-right: clamp(0.5rem, 2vw, 1.75rem); }
.astep .ico { color: var(--text); margin-top: 2px; }
.astep > span:last-child { display: flex; flex-direction: column; gap: 0.35rem; }
.astep > span:last-child > span:last-child {
  font-size: 0.85rem; line-height: 1.6; color: var(--text-soft);
}
@media (min-width: 1020px) {
  .astep:not(:last-child)::after {
    content: ""; position: absolute; top: 0.35rem; right: 0;
    width: 18px; height: 10px;
    background: currentColor; opacity: 0.3;
    -webkit-mask: var(--arrow-icon) center / contain no-repeat;
            mask: var(--arrow-icon) center / contain no-repeat;
  }
}

/* -------------------------------------------------------- footer (4-up) -- */
.ftr { padding-block: 0; }
.ftr__top {
  display: grid; gap: 2.5rem clamp(2rem, 5vw, 4.5rem);
  padding-block: clamp(2.75rem, 5vw, 4rem);
}
@media (min-width: 900px) { .ftr__top { grid-template-columns: minmax(0, 1fr) minmax(0, 1.9fr); } }
.ftr__brand { display: flex; flex-direction: column; align-items: flex-start; }
.ftr__brand > * + * { margin-top: 1.2rem; }
.ftr__line { font-size: 0.875rem; line-height: 1.7; color: var(--on-dark-soft); max-width: 30ch; }
.ftr__social { display: flex; gap: 0.6rem; margin-top: 1.5rem; }
.ftr__social a {
  display: grid; place-items: center; width: 40px; height: 40px;
  border: 1px solid var(--line-dark); border-radius: 50%;
  color: var(--on-dark-soft);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.ftr__social a:hover { color: #fff; border-color: #fff; }

.ftr__nav { display: grid; gap: 2rem clamp(1.5rem, 3vw, 2.5rem); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 620px) { .ftr__nav { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.ftr__ct {
  font-size: var(--t-mini); font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase; color: #fff;
  padding-bottom: 0.75rem; border-bottom: 1px solid var(--line-dark);
}
.ftr__col ul { margin-top: 0.6rem; display: grid; gap: 0; }
/* The comp spaces these tighter than 40px. The gap is moved into the link's
   own padding instead, so the visual rhythm is the comp's but every row is a
   real 40px target. */
.ftr__col a {
  display: flex; align-items: center; min-height: 40px;
  font-size: 0.875rem; color: var(--on-dark-soft); text-decoration: none;
  transition: color 0.2s var(--ease);
}
.ftr__col a:hover { color: #fff; }

.ftr__bar {
  display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem;
  justify-content: space-between; align-items: center;
  padding-block: 1.25rem;
  border-top: 1px solid var(--line-dark);
}

/* ==========================================================================
   SERVICES PAGE — second pass, measured against the comp
   --------------------------------------------------------------------------
   The first pass matched the comp's structure but not its proportions. Every
   number below was taken off the comp and converted to a fraction of page
   width, so it holds at any viewport rather than only at the width it was
   measured at.
   ========================================================================== */

.svc-page {
  /* MEASURE. This page used to override --wide down to 1400px to match the
     comp's ~84% margin measure against the site's old 1500px default.
     Removed 2026-08-27: the sitewide default dropped to 1200px that same
     pass (client request, narrower content everywhere), which is already
     narrower than the 1400px this page asked for -- keeping the override
     would have made /services WIDER than every other page, the opposite of
     what it originally wanted. Inherits the site default now. */

  /* DISPLAY RAMP. The comp's headline-to-body ratio is 5.0 at the hero and 2.4
     at section heads; the site ramp is 4.1 and 2.1. The serif is what carries
     this page, so the ramp is ~20% larger here and nowhere else. Body, labels
     and every uppercase size are untouched -- widening the gap between the two
     is the entire point. */
  --t-hero: clamp(2.9rem, 0.9rem + 6.7vw, 5.25rem);    /* 84px */
  --t-xl:   clamp(2.1rem, 1.15rem + 3.2vw, 3.25rem);   /* 52px */
  --t-lg:   clamp(1.85rem, 1.1rem + 2.5vw, 2.7rem);    /* 43px */
  --t-md:   clamp(1.4rem, 1.15rem + 0.95vw, 1.85rem);  /* 30px */

  --chevron-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M9.5 5.5 16 12l-6.5 6.5'/%3E%3C/svg%3E");
}

/* The comp's group cards are 1.4:1 -- see prep_images.py for why the first
   pass shrank them to 1.25:1 and why it no longer needs to. Hero sizing is
   now handled entirely by `.svc-hero`'s own aspect-ratio, above -- this used
   to also set a competing min-height here, the exact "two declarations for
   the same box" bug this project has hit before. */
.gcard__media img { aspect-ratio: 7 / 5; }

/* Section heads: the comp sets the supporting line noticeably smaller than the
   heading, and centres it on a tighter measure. */
.svc-page .s-head__sub { font-size: 0.9375rem; max-width: 56ch; }

/* ---- Before / after -----------------------------------------------------
   The comp's pair is TALL: each half is roughly a 0.45 sliver, the pair about
   0.9 overall. The first pass ran two 4:5 halves side by side, which made a
   1.6 landscape block -- the opposite shape.

   These halves are 2:3 rather than the comp's 0.45. The three pairs are split
   out of the client's own diptychs and two of those sources cannot be cropped
   narrower than ~0.75 without cutting the head in half, which is the exact
   failure v8 fixed. 2:3 is as close to the comp as the real photographs go. */
.tcard__pair img { aspect-ratio: 2 / 3; }
/* The comp's handle is a hairline ring with a double chevron, not a filled
   disc with a single arrow. */
.tcard__handle {
  width: 30px; height: 30px;
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.92);
  color: #fff;
  box-shadow: none;
}

/* ---- Journey ------------------------------------------------------------ */
.jstep__img img { width: clamp(120px, 11.5vw, 176px); }
/* The comp separates the steps with a small chevron, not the site's long
   arrow. Same for "how to arrange a visit". */
@media (min-width: 1000px) {
  .jstep:not(:last-child)::after {
    width: 15px; height: 15px; top: 2.4rem; opacity: 0.42;
    -webkit-mask: var(--chevron-icon) center / contain no-repeat;
            mask: var(--chevron-icon) center / contain no-repeat;
  }
}
@media (min-width: 1020px) {
  .astep:not(:last-child)::after {
    width: 13px; height: 13px; top: 0.15rem; opacity: 0.4;
    -webkit-mask: var(--chevron-icon) center / contain no-repeat;
            mask: var(--chevron-icon) center / contain no-repeat;
  }
}

/* ---- FAQ ---------------------------------------------------------------- */
/* The comp's questions are set at body size, not at the display size the first
   pass used -- these are rows in a list, not headings. */
.faqs--two .faq summary {
  font-family: var(--display);
  font-size: 1.0625rem;
  line-height: 1.35;
}

/* The transparent header sits over the top of the hero plate. On the comp that
   is always plain light wall; on a real photograph it can be dark hair, and
   "PRICING" was landing on it. Same shallow top-down wash the homepage hero
   already uses -- it is invisible against the wall and only does work where the
   subject reaches the top of the frame. */
.svc-hero__media::after {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 150px; z-index: 1;
  background: linear-gradient(to bottom, var(--cream) 0%, rgba(250, 247, 243, 0.72) 42%, rgba(250, 247, 243, 0) 100%);
}

/* --text-faint is tuned to clear AA on white (5.13) and on --cream (4.81). On
   --cream-3, the grey ground the comp gives "our service standards", it drops
   to 4.40. One darker faint, scoped to that ground only: 5.19 there. */
.on-cream-2 { --text-faint: #63635C; }

/* The trust rail used to sit between the hero and this block and gave the
   junction its structure. With the rail gone the hero's cream and this
   section's cream are the same value, so a full --sp-lg of padding read as a
   hole rather than as breathing room. Tightened, and the header block's two
   columns are pinned to a shared baseline so the paragraph does not float
   against the heading. */
.svc-page .section--tight { padding-top: clamp(1.9rem, 3.2vw, 2.85rem); }
.svc-page .section--tight .d-head { align-items: start; }
.svc-page .section--tight .d-head > .lede { margin-top: 0.35rem; }


/* This measurement was for the outgoing stock colour-result frame (3:2)
   and no longer applies now that row two is a real client photo (Aug
   2026) -- see the /services block in prep_images.py's main RECIPES list.
   (The hero's own equivalent object-position override was removed
   outright, not updated: the hero box is now `aspect-ratio`-matched to its
   plate -- see `.svc-hero__media` above -- so nothing is cropped and
   object-position has no effect left to tune.)
   Row two: the new Colour.jpg is natively 1.17:1, already close to the
   ~1.25 box it sits in, so `cover` now crops only lightly -- centred is
   correct again, and the old 15% left-anchor (tuned for a 3:2 source that
   needed far more cropping) would cut into the stylist's hand instead.
   No override needed; removed rather than left wrong. */
