/* ═══════════════════════════════════════════════════════════════════════════
   Pampered by Sam — styles.css
   One file. No preprocessor, no build step. Every value is a custom property,
   so a Wix or Base44 developer can retheme the whole page from the :root block.
   ═══════════════════════════════════════════════════════════════════════════ */

:root{
  /* ── Ground and ink ───────────────────────────────────────────────────── */
  --v-25:   #FAF8FC;   /* the page ground — 85% of all page area */
  --v-50:   #F4F1F8;   /* recessed panels, form fills */
  --v-100:  #EDE8F3;
  --v-150:  #E3DCEC;   /* hairlines */
  --v-200:  #D6CDE3;   /* ⚠ decorative fill only — never a text colour */
  --v-600:  #5F4B7D;   /* input borders, icons */
  --v-700:  #4A3862;   /* primary CTA fill */
  --v-800:  #382A4A;   /* links, small headings */
  --v-900:  #241C30;   /* the dark anchor */
  --ink:    #1C1622;
  --ink-70: #4B4155;
  --white:  #FFFFFF;
  --error:  #8A1C1C;

  --bone-50:  #FBF9F5;
  --bone-100: #F5F1E9;

  --honey-300: #E0A947;  /* accents on DARK grounds only */
  --honey-800: #6E4310;  /* the light-ground amber */

  --wa:   #075E54;       /* WhatsApp. Never #25D366 — 1.98:1 on white */
  --wa-2: #054D45;

  /* The lilac she asked for, as the footer ground. It is a decorative fill
     carrying dark text — never used AS text. */
  --lilac:      #D6CDE3;
  --lilac-deep: #C4B7D6;

  /* ── Type ─────────────────────────────────────────────────────────────── */
  --font-display: 'Newsreader', Georgia, serif;
  --font-body:    'Schibsted Grotesk', system-ui, -apple-system, Arial, sans-serif;
  --font-mono:    'DM Mono', ui-monospace, monospace;

  --t-statement: clamp(3.25rem, 1.9rem + 5.6vw, 6.5rem);
  --t-h2:        clamp(2.375rem, 1.586rem + 3.238vw, 4.5rem);
  --t-h3:        clamp(1.5625rem, 1.354rem + 0.857vw, 2.125rem);
  --t-quote:     clamp(1.875rem, 1.271rem + 2.476vw, 3.5rem);
  --t-lede:      clamp(1.375rem, 1.236rem + 0.571vw, 1.75rem);
  --t-price:     clamp(1.75rem, 1.471rem + 1.143vw, 2.5rem);
  --t-body:      clamp(1.1875rem, 1.164rem + 0.095vw, 1.25rem);
  --t-small:     clamp(1.0625rem, 1.039rem + 0.095vw, 1.125rem);
  --t-input:     1.0625rem;   /* 17px fixed — below this iOS zooms and never returns */
  --t-label:     0.9375rem;   /* 15px — the floor, everywhere */

  /* ── Space ────────────────────────────────────────────────────────────── */
  --s-1:4px; --s-2:8px; --s-3:12px; --s-4:16px; --s-5:24px;
  --s-6:40px; --s-7:64px; --s-8:96px; --s-9:144px; --s-10:208px;
  --pad-tight:  clamp(40px, 6vw, 64px);
  --pad-normal: clamp(72px, 10vw, 128px);
  --pad-open:   clamp(112px, 16vw, 208px);

  --page-pad: clamp(20px, 5vw, 64px);
  --container: 1320px;
  --rail: 120px;
  --measure: 36rem;
  --measure-lede: 26em;

  /* ── Motion ───────────────────────────────────────────────────────────── */
  --ease-out:  cubic-bezier(0.16, 1, 0.30, 1);
  --ease-soft: cubic-bezier(0.33, 0, 0.15, 1);
  --ease-io:   cubic-bezier(0.65, 0, 0.35, 1);
  --d-micro:180ms; --d-type:620ms; --d-image:1100ms; --d-sticky:320ms;

  --rule-hair:   1px solid rgba(28,22,34,.11);   /* was color-mix — see note at .chip */
  --rule-solid:  1px solid var(--v-150);
  --rule-anchor: 2px solid var(--ink);
  --shadow-sticky: 0 -1px 0 var(--v-150), 0 -18px 40px -24px rgba(28,22,34,.28);

  --tap-min:56px; --tap-gap:24px;
  --grade: saturate(0.92) contrast(1.02);
}

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

/* ── Review-only notes ────────────────────────────────────────────────────
   Notes written for Craig and Sam, not for her customers: which prices are
   ours rather than hers, which copy still needs her approval, what is blocked
   on what. On 6 August seven of these were rendering to the public page,
   including "The 30, 90 and two-hour prices are Adtelligent's recommendation".

   HIDDEN BY DEFAULT. Add ?review to the URL to see them — that is the link to
   open in the client meeting. They can never leak to a visitor, because
   showing them takes a deliberate query string.

   .no-js fallback: if app.js never runs, they stay hidden, which is the safe
   direction. */
.review-only{ display:none !important; }
/* 15 Sep: an aside holding only review-only placeholders (the portrait slot) left a
   ~400px blank band on a phone. Collapse it for visitors; review mode still shows it. */
html:not(.review) body:not(.review) .grid-rail > .aside:not(:has(> :not(.review-only))){ display:none; }
.review .review-only{ display:block !important; }
.review .review-only{ outline:2px dashed var(--honey-800); outline-offset:6px; }

html{ -webkit-text-size-adjust:100%; text-size-adjust:100%;
      scroll-behavior:smooth; font-optical-sizing:auto; }
:target, [id]{ scroll-margin-top:calc(56px + var(--s-5)); }
@media (min-width:1080px){ [id]{ scroll-margin-top:calc(72px + var(--s-5)); } }
/* Tabbing to anything just below the fold used to scroll it flush to y=0 —
   i.e. underneath the sticky header, which is 57px tall and z-index 60.
   scroll-margin-top only applied to elements with an id. WCAG 2.4.11. */
:where(a,button,input,textarea,select,summary,[tabindex]){
  scroll-margin-top:calc(56px + var(--s-5)); }
@media (min-width:1080px){
  :where(a,button,input,textarea,select,summary,[tabindex]){
    scroll-margin-top:calc(72px + var(--s-5)); }
}
@media (max-width:1079.98px){
  /* `summary` was missing, so the 12 FAQ and privacy questions could sit
     behind the 79px sticky bar. */
  :where(a,button,input,textarea,select,summary,[tabindex]){
    scroll-margin-bottom:calc(79px + var(--tap-gap)); }
}

body{
  font-family:var(--font-body); font-size:var(--t-body); line-height:1.65;
  letter-spacing:0.005em;                 /* POSITIVE, never negative on body */
  color:var(--ink); background:var(--v-25);
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
}

p{ max-width:min(var(--measure), 100%); text-wrap:pretty; }
p + p{ margin-top:1.05em; }
h1,h2,h3{ font-family:var(--font-display); font-weight:400; line-height:1.06; }
h2,h3{ text-wrap:balance; }
/* `balance` evens out line lengths, which is right for a two- or three-word
   heading and wrong for a lede: it will happily leave one word alone on the
   last line. `pretty` is the value that avoids orphans. On a phone the §01
   lede was ending with "too." by itself. */
.lede{ text-wrap:pretty; }
ul,ol{ list-style:none; }
img{ max-width:100%; }
code{ font-family:var(--font-mono); font-size:.94em; }
.price,.rate-dur,table{ font-variant-numeric:tabular-nums lining-nums; }
::selection{ background:var(--honey-300); color:var(--ink); }

a{ color:var(--v-800); text-decoration:underline; text-decoration-thickness:1px;
   text-underline-offset:.22em;
   text-decoration-color:rgba(56,42,74,.38);
   transition:text-decoration-color var(--d-micro) var(--ease-io); }
@media (hover:hover) and (pointer:fine){ a:hover{ text-decoration-color:currentColor; } }

:focus{ outline:none; }
:focus-visible{ outline:3px solid var(--v-700); outline-offset:3px; border-radius:2px; }
.on-dark :focus-visible{ outline-color:var(--honey-300); }

.skip{ position:absolute; left:-9999px; top:0; z-index:200;
  display:inline-flex; align-items:center; min-height:var(--tap-min);
  background:var(--ink); color:var(--v-25); padding:var(--s-3) var(--s-5);
  font-size:var(--t-small); text-decoration:none; }
.skip:focus{ left:0; }
.vh{ position:absolute; width:1px; height:1px; overflow:hidden;
     clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; }

/* grain — 5.5%, below the sticky bar, off under prefers-contrast */
body::after{ content:""; position:fixed; inset:0; z-index:5; pointer-events:none;
  opacity:.055; mix-blend-mode:multiply;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.62' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='260' height='260' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:260px 260px; }
@media (prefers-contrast:more){ body::after{ display:none; } }

/* ── Layout ─────────────────────────────────────────────────────────────── */
.wrap{ max-width:var(--container); margin-inline:auto; padding-inline:var(--page-pad); }
.pad-tight{ padding-block:var(--pad-tight); }
.pad-normal{ padding-block:var(--pad-normal); }
.pad-open{ padding-block:var(--pad-open); }

@media (min-width:1080px){
  .grid-rail{ display:grid;
    grid-template-columns:var(--rail) minmax(0,1fr) minmax(0,1fr); column-gap:var(--s-7); }
  .grid-rail > .rail{ grid-column:1; }
  .grid-rail > .body{ grid-column:2; }
  .grid-rail > .aside{ grid-column:3; }
  .grid-rail > .span{ grid-column:2 / -1; }
  .bleed-right{ margin-right:calc(-1 * var(--page-pad) - 80px); }
  .quote-rail{ grid-column:1 / 3; }
}
.s06{ overflow-x:clip; }   /* only the section that bleeds — never the root */

/* ── Header ─────────────────────────────────────────────────────────────── */
.hdr{ position:sticky; top:0; z-index:60; background:var(--v-25);
      border-bottom:var(--rule-hair); }
@supports ((backdrop-filter:blur(12px)) or (-webkit-backdrop-filter:blur(12px))){
  .hdr{ background:rgba(250,248,252,.88);
        backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px); }
}
.hdr-in{ display:flex; align-items:center; justify-content:space-between;
         gap:var(--s-5); min-height:56px; flex-wrap:wrap; }
.logo{ text-decoration:none; color:var(--ink); line-height:1.1;
       display:flex; flex-direction:column; justify-content:center; min-height:var(--tap-min); }
.logo b{ display:block; font-family:var(--font-display); font-weight:400;
         font-size:1.25rem; }
.logo span{ display:block; font-family:var(--font-mono); font-size:var(--t-label);
            letter-spacing:.09em; text-transform:uppercase; color:var(--ink-70); margin-top:2px; }
.logo .logo-city{ display:none; }
.nav{ display:none; gap:var(--s-6); }
/* The nav only shows at >=1080px, which includes a large iPad in landscape —
   a touch device. 28px was fine for a mouse and not for a thumb. */
.nav a{ color:var(--ink); text-decoration:none; font-size:1.0625rem;
        display:inline-flex; align-items:center; min-height:var(--tap-min); }
@media (hover:hover) and (pointer:fine){
  .nav a:hover{ color:var(--v-800); text-decoration:underline; }
}
.hdr .btn{ min-height:var(--tap-min); padding:0 var(--s-4); }
@media (min-width:768px){ .logo .logo-city{ display:inline; } }
@media (min-width:1080px){ .hdr-in{ min-height:72px; } .nav{ display:flex; }
  .logo b{ font-size:1.375rem; } .hdr .btn{ padding:0 var(--s-5); } }

/* ── HERO — the photograph, with the words ON it ─────────────────────────
   A SOLID panel, not a gradient scrim. A scrim over a hero is the reliable
   cheapness tell in this category, and text over an unknown photograph
   cannot be contrast-verified. The panel is 94% opaque over --v-900, so
   the worst case is computable: 97% over a white photograph still gives
   15.0:1 for white and 7.1:1 for the amber eyebrow. */
/* ── Hero ────────────────────────────────────────────────────────────────
   The words sit ON the photograph — there is no panel behind them. What
   keeps them readable is the scrim below, and its stops are measured, not
   chosen: `_build/hero_contrast.py` reads the real pixels under every line
   at five viewport sizes and exits non-zero below 7:1.

   RUN IT AFTER ANY CHANGE to the image, the crop, the type sizes, or the
   gradient. Softening the scrim to "let more photo through" is exactly the
   edit that breaks it, and it breaks worst on a phone, where the window
   light lands behind the headline. */
/* --v-900 behind the picture is not decoration. The words are white and they
   now sit on the image rather than on a panel, so if the photograph 404s or
   is still loading, this is what they are legible against. */
/* overflow:hidden is not decoration. The photograph breathes (scale 1.02 over
   18s) and without a clip the page was 2–4px wider than the phone for twelve
   seconds of every eighteen — a sideways wobble on every scroll. Found by the
   slow-network audit pass, 14 Sep 2026; reveal-check.py now samples it. */
.hero{ position:relative; display:grid; isolation:isolate;
       background:var(--v-900); overflow:hidden; }
.hero > picture{ grid-area:1 / 1; display:block; }
.hero img{ display:block; width:100%; height:82svh; min-height:540px;
           object-fit:cover; object-position:60% 55%; filter:var(--grade); }
/* vh first so browsers without svh (Chrome <108, Safari <15.4) still get a
   sized hero instead of falling back to height:auto. */
.hero img{ height:82vh; height:82svh; }
@media (min-width:1080px){ .hero img{ height:88vh; height:88svh; } }
.js .hero img{ animation:breath 18s var(--ease-io) infinite; }
@keyframes breath{ 0%,100%{ transform:scale(1); } 50%{ transform:scale(1.02); } }

/* The scrim. Top quarter left completely clear so the room still reads as a
   room; the ramp is steep through the middle because that is where the bright
   wall is. rgb(36 28 48) is --v-900 — written out because a gradient stop
   cannot take a custom property with a separate alpha in every browser we
   support. If --v-900 changes, change these too. */
/* THE SCRIM IS ANCHORED TO THE TEXT, NOT TO A PERCENTAGE OF THE IMAGE.
   This is the third time it has been re-solved, and the reason is always the
   same: a gradient pinned to percentages of the hero assumes the text block is
   a fixed height. It is not. Removing one non-breaking space made the headline
   wrap to three lines, the block grew, and the eyebrow rose into the part of
   the frame the gradient deliberately leaves clear — at 1.9:1.

   So the fade now lives ON the panel: 12rem of transparent-to-solid directly
   above the first line of type, whatever height that type turns out to be, at
   whatever viewport. The text can never outgrow its own background again. */
.hero-panel{
  grid-area:1 / 1; z-index:2; align-self:end; width:100%;
  padding:9rem var(--page-pad) var(--s-7);
  color:var(--white);
  /* 30 Sep: eased, not linear. The old 0 → .34 → .88 ramp ended in a hard
     line across the room. The hold at .88 still starts exactly where the type
     does (padding-top), which is what hero_contrast.py measures. */
  background:linear-gradient(to bottom,
    rgb(36 28 48 / 0)   0,
    rgb(36 28 48 / .06) 1.5rem,
    rgb(36 28 48 / .2)  3rem,
    rgb(36 28 48 / .42) 4.5rem,
    rgb(36 28 48 / .64) 6rem,
    rgb(36 28 48 / .8)  7.5rem,
    rgb(36 28 48 / .88) 9rem,
    rgb(36 28 48 / .88) 100%);
}
/* The panel is full-bleed so the gradient spans the frame; only the words are
   measured. */
.hero-panel > *{ max-width:47rem; }


/* Amber cannot carry text on a photograph — honey-300 needs a ground of
   essentially solid --v-900 to reach 7:1. So the accent becomes a rule and
   the words become bone. Decorative amber, legible type. */
/* Scoped to .hero-panel deliberately: `.label` sets color:var(--ink-70) and is
   declared LATER in this file, so at equal specificity it would win and paint
   the eyebrow dark ink on a dark scrim. */
/* NOT flex: as a flex container the name and the rest of the line became
   separate items and wrapped into two ragged columns on a phone. Inline-block
   rule, normal text flow. */
.hero-panel .hero-eyebrow{ color:var(--bone-100); margin-bottom:var(--s-4); }
.hero-panel .hero-eyebrow::before{ content:''; display:inline-block;
  vertical-align:middle; width:2.25rem; height:2px; margin-right:var(--s-3);
  background:var(--honey-300); }

.hero-h1{ font-size:var(--t-statement); font-weight:200; line-height:.98;
          max-width:min(15ch, 100%); text-wrap:balance; color:var(--white); }
.hero-h1 em{ font-style:italic; }
.hero-lede{ font-size:var(--t-lede); line-height:1.4; margin-top:var(--s-5);
            max-width:min(var(--measure-lede), 100%); color:var(--bone-100); }

@media (min-width:1080px){
  .hero img{ height:88svh; }
  .hero-panel{ padding-bottom:var(--s-8);
    --hero-inset:max(var(--page-pad), calc((100vw - var(--container)) / 2 + var(--page-pad)));
    padding-inline:var(--hero-inset) var(--s-8);
    /* 30 Sep: the room shows through to the right of the words. The fade starts
       1rem past the widest the type can be (inset + 47rem), so every letter
       still sits on the full .88 that hero_contrast.py assumes. */
    -webkit-mask-image:linear-gradient(to right, #000 calc(var(--hero-inset) + 48rem),
      rgb(0 0 0 / .38) calc(var(--hero-inset) + 70rem));
            mask-image:linear-gradient(to right, #000 calc(var(--hero-inset) + 48rem),
      rgb(0 0 0 / .38) calc(var(--hero-inset) + 70rem)); }
}

/* ── Session lengths ────────────────────────────────────────────────────── */
.lengths{ display:grid; grid-template-columns:repeat(2,1fr); gap:var(--s-3);
          margin-bottom:var(--s-6); }
.lengths a{ display:flex; flex-direction:column; align-items:center;
  justify-content:center; gap:2px; min-height:var(--tap-min);
  padding:var(--s-4) var(--s-3); text-decoration:none; color:var(--ink);
  border:1px solid var(--v-600); }
.lengths b{ font-family:var(--font-display); font-size:1.75rem; font-weight:400; line-height:1; }
.lengths span{ font-family:var(--font-mono); font-size:var(--t-label); color:var(--ink-70); }
@media (hover:hover) and (pointer:fine){
  .lengths a:hover{ background:var(--v-50); }
}
@media (min-width:600px){ .lengths{ grid-template-columns:repeat(4,1fr); } }

/* ── Buttons ────────────────────────────────────────────────────────────── */
.btn{ display:inline-flex; align-items:center; justify-content:center;
  min-height:var(--tap-min); padding:0 var(--s-5);
  font-family:var(--font-body); font-weight:600; font-size:1.0625rem;
  letter-spacing:.012em; text-decoration:none; border:1px solid transparent;
  border-radius:0; cursor:pointer; white-space:nowrap;
  transition:background-color var(--d-micro) var(--ease-io),
             color var(--d-micro) var(--ease-io); }
.btn-primary{ background:var(--v-700); color:var(--white); }
.btn-primary:active{ background:var(--v-900); }
.btn-outline{ background:transparent; color:var(--ink); border-color:var(--ink); }
.btn-wa{ background:var(--wa); color:var(--white); }
.btn-wa:active{ background:var(--wa-2); }
@media (hover:hover) and (pointer:fine){
  .btn-primary:hover{ background:var(--v-800); }
  .btn-outline:hover{ background:var(--ink); color:var(--v-25); }
  .btn-wa:hover{ background:var(--wa-2); }
}
.btn:focus-visible{ outline:none;
  box-shadow:0 0 0 3px var(--v-25), 0 0 0 6px var(--v-700); border-radius:2px; }
.on-dark .btn:focus-visible{ box-shadow:0 0 0 3px var(--v-900), 0 0 0 6px var(--honey-300); }
.btn[data-loading]{ pointer-events:none; opacity:.92; }
.btn-i{ width:19px; height:19px; margin-right:var(--s-3); flex:none; }
.cta-row{ display:flex; flex-direction:column; gap:var(--tap-gap);
          align-items:stretch; margin-top:var(--s-6); }
.cta-row .btn{ width:100%; }
@media (min-width:768px){
  .cta-row{ flex-direction:row; align-items:center; flex-wrap:wrap; }
  .cta-row .btn{ width:auto; }
}

/* ── Labels, rules, section furniture ───────────────────────────────────── */
.label{ font-family:var(--font-mono); font-size:var(--t-label); font-weight:500;
  letter-spacing:.09em; text-transform:uppercase; line-height:1.35;
  color:var(--ink-70); text-wrap:balance; }
.tick{ display:block; width:24px; border-top:2px solid var(--honey-800);
       margin-bottom:var(--s-3); }
.h2-rule{ border:0; border-top:var(--rule-hair); width:88px; margin-bottom:var(--s-5); }
.h2-rule.rule-anchor{ border-top:var(--rule-anchor); }
h2{ font-size:var(--t-h2); font-weight:300; }
.lede{ font-size:var(--t-lede); line-height:1.45; margin-top:var(--s-5);
       max-width:min(var(--measure-lede), 100%); }
.boundary{ font-family:var(--font-display); font-size:var(--t-h3);
  line-height:1.34; max-width:min(22em, 100%); }
.note-tick{ border-left:2px solid var(--honey-800); padding-left:var(--s-4);
            margin-top:var(--s-6); }
.note-tick p{ font-size:var(--t-small); color:var(--ink-70); }
.draft-note{ font-family:var(--font-mono); font-size:var(--t-label);
             color:var(--honey-800); margin-top:var(--s-3); }
.hand-off{ margin-top:var(--s-5); font-family:var(--font-display);
           font-size:var(--t-h3); line-height:1.3; color:var(--ink); }
.cred li{ font-family:var(--font-mono); font-size:var(--t-label);
          letter-spacing:.06em; color:var(--ink-70); line-height:1.6; }
.cred{ margin-top:var(--s-4); }
blockquote.pull p{ font-family:var(--font-display); font-size:var(--t-quote);
  font-weight:300; font-style:italic; line-height:1.16;
  max-width:min(19ch,100%); text-indent:-.42em; }
blockquote.pull cite{ display:block; margin-top:var(--s-5); font-style:normal;
                      color:var(--ink-70); }
.panel{ background:var(--v-50); }

/* ── Steps ──────────────────────────────────────────────────────────────── */
.steps{ margin-top:var(--s-5); }
.step{ display:grid; gap:var(--s-4); padding:var(--s-5) 0;
       border-top:var(--rule-hair); }
.step:first-child{ border-top:0; }
.step-body p{ max-width:min(34em,100%); }
.step-fig{ margin-top:var(--s-3); }
@media (min-width:1080px){
  .step{ grid-template-columns:var(--rail) minmax(0,1fr) minmax(0,1fr);
         gap:var(--s-7); align-items:start; }
  .step-body{ grid-column:2; }
  .step-fig{ grid-column:3; max-width:260px; margin-top:0; }
}

/* ── Images ─────────────────────────────────────────────────────────────── */
.ph{ position:relative; }
.ph > picture, .ph > .sq{ position:relative; display:block; overflow:hidden; }
.ph img{ display:block; width:100%; height:auto; filter:var(--grade); }
.ph > picture::after, .ph > .sq::after{ content:""; position:absolute; inset:0;
  pointer-events:none; background:var(--v-900); mix-blend-mode:soft-light; opacity:.10; }
.ph figcaption{ margin-top:var(--s-3); }
.reveal-img > picture, .reveal-img > .sq{
  transition:clip-path var(--d-image) var(--ease-soft); }
.js .reveal-img > picture, .js .reveal-img > .sq{ clip-path:inset(100% 0 0 0); }
.js .reveal-img.in > picture, .js .reveal-img.in > .sq{ clip-path:inset(0 0 0 0); }
.reveal-img img{ transition:transform calc(var(--d-image) + 300ms) var(--ease-soft); }
.js .reveal-img img{ transform:scale(1.06); }
.js .reveal-img.in img{ transform:scale(1); }
.sq{ aspect-ratio:1/1; } .sq img{ height:100%; object-fit:cover; }
.hives{ margin-top:var(--s-7); max-width:520px; }

.portrait-todo,.shot-todo,.logo-todo{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:var(--s-2); text-align:center; padding:var(--s-5);
  background:var(--v-50); border:2px dashed var(--v-600); }
.portrait-todo{ aspect-ratio:4/5; padding:var(--s-6) var(--s-5); }
.portrait-todo .label,.shot-todo .label,.logo-todo .label{ color:var(--ink); }
.portrait-todo p,.shot-todo p,.logo-todo p{ font-size:var(--t-label);
  color:var(--ink-70); max-width:min(24em,100%); }

/* ── Rate card ──────────────────────────────────────────────────────────── */
.cat{ font-family:var(--font-mono); font-size:var(--t-label); font-weight:500;
  letter-spacing:.09em; text-transform:uppercase; color:var(--ink-70);
  margin:var(--s-8) 0 var(--s-4); }
.cat .tick{ margin-bottom:var(--s-3); }
.cat-note{ font-size:var(--t-small); color:var(--ink-70);
           margin:calc(-1 * var(--s-2)) 0 var(--s-4); }
.rate{ display:grid; grid-template-columns:minmax(0,1fr); gap:var(--s-2);
       align-items:baseline; padding-block:var(--s-5); border-top:var(--rule-hair); }
.rate:last-child{ border-bottom:var(--rule-hair); }
.rate-name h3{ font-size:var(--t-h3); margin-bottom:var(--s-1); }
.rate-desc{ font-size:var(--t-small); color:var(--ink-70); line-height:1.55;
            max-width:min(34em,100%); }
.rate-fig{ text-align:left; }
.price{ font-family:var(--font-display); font-size:var(--t-price); font-weight:400;
  line-height:1; letter-spacing:0; white-space:nowrap;
  font-variant-numeric:tabular-nums lining-nums; }
.price .cur{ font-family:var(--font-mono); font-size:var(--t-label);
  letter-spacing:.09em; vertical-align:.42em; margin-right:.42em; color:var(--ink-70); }
/* Only ever the small note under a price ("depending on the area") — app.js
   renders nothing else with this class. Block, not inline: as an inline box
   `text-wrap:balance` is ignored, and the note was leaving one word alone on
   the second line at 414px. On its own line it also reads better than trailing
   the price. */
.rate-dur{ display:block; margin-top:var(--s-2); color:var(--ink-70); }
.tiers{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
        gap:var(--s-4) var(--s-5); margin-top:var(--s-4); }
.tier{ display:flex; flex-direction:column; gap:2px; }
.tier .price{ font-size:var(--t-h3); }
.tier .label{ color:var(--ink-70); }
.tier.is-standard .label{ color:var(--honey-800); }
.tier.is-standard .price{ color:var(--ink); }
.tier:not(.is-standard) .price{ color:var(--ink-70); }
.rate:not(.rate-matrix){ grid-template-columns:minmax(0,1fr) auto; column-gap:var(--s-4); }
.rate:not(.rate-matrix) .rate-fig{ text-align:right; }
.rate-group[data-group="waxing"] .rate-name h3{ font-size:1.25rem; margin-bottom:0; }
@media (min-width:600px){
  .rate-fig{ text-align:right; }
  .rate-dur{ margin:var(--s-2) 0 0; }
}
@media (min-width:1080px){ .tiers{ grid-template-columns:repeat(4,minmax(0,1fr)); gap:var(--s-5); } }

/* ── Places, zones ──────────────────────────────────────────────────────── */
.places{ margin-top:var(--s-6); }
.place{ padding-block:var(--s-4); border-top:var(--rule-hair); }
.place:last-child{ border-bottom:var(--rule-hair); }
.place h3{ font-size:var(--t-h3); margin-bottom:var(--s-2); }
.place p{ font-size:var(--t-body); color:var(--ink-70); line-height:1.55;
          max-width:min(34em,100%); }
.place-meta{ font-family:var(--font-mono); font-size:var(--t-label);
  color:var(--ink-70); margin-top:var(--s-2); letter-spacing:.04em;
  overflow-wrap:break-word; }
.zones{ background:var(--v-50); padding:var(--s-5) var(--s-6); margin-top:var(--s-6); }
.zones .label{ color:var(--honey-800); }
.zones p{ font-size:1.09rem; line-height:1.6; max-width:none; margin-top:var(--s-3); }
.zones-note{ color:var(--ink-70); }
@media (min-width:1080px){ .s06 .zones{ grid-column:2; } }

/* ── Gift, referral ─────────────────────────────────────────────────────── */
.tiers-grid{ display:grid; gap:var(--s-4); margin-top:var(--s-6); }
.gift-tier{ border:1px solid var(--v-600); padding:var(--s-5); }
.gift-tier .price{ display:block; margin:var(--s-2) 0; }
.gift-tier p{ font-size:var(--t-small); color:var(--ink-70); }
@media (min-width:768px){ .tiers-grid{ grid-template-columns:repeat(3,1fr); } }

.refer-row{ display:flex; flex-wrap:wrap; gap:var(--s-3); margin-top:var(--s-5);
            align-items:stretch; }
.refer-row input{ flex:1 1 18rem; min-height:var(--tap-min); padding:0 var(--s-4);
  font-family:var(--font-mono); font-size:var(--t-input); color:var(--ink);
  background:var(--v-50); border:1px solid var(--v-600); }

/* ── FAQ ────────────────────────────────────────────────────────────────── */
.faq{ margin-top:var(--s-5); }
.faq details{ border-top:var(--rule-hair); }
.faq details:last-child{ border-bottom:var(--rule-hair); }
.faq summary{ display:flex; align-items:center; gap:var(--s-3);
  min-height:var(--tap-min); padding:var(--s-4) 0; cursor:pointer;
  font-family:var(--font-display); font-size:var(--t-h3); line-height:1.25;
  list-style:none; }
.faq summary::-webkit-details-marker{ display:none; }
.faq summary::before{ content:"+"; font-family:var(--font-mono); font-size:1.25rem;
  color:var(--honey-800); flex:none; width:1.2em; }
.faq details[open] summary::before{ content:"–"; }
.faq details > p{ padding-bottom:var(--s-5); max-width:min(38em,100%); }

/* ── Booking flow ───────────────────────────────────────────────────────── */
.booker{ margin-top:var(--s-6); }
.bk-step{ padding-block:var(--s-5); border-top:1px solid rgba(255,255,255,.22); }
.bk-step[hidden]{ display:none; }
.bk-step h3{ font-family:var(--font-body); font-size:var(--t-small);
  font-weight:600; letter-spacing:.01em; display:flex; align-items:center;
  gap:var(--s-3); color:var(--white); margin-bottom:var(--s-4); }
.bk-steps{ counter-reset:bk; }
.bk-step:not([hidden]){ counter-increment:bk; }
.bk-n::before{ content:counter(bk); }
.bk-n{ font-family:var(--font-mono); font-size:var(--t-label);
  color:var(--honey-300); border:1px solid var(--honey-300);
  width:1.9em; height:1.9em; display:inline-flex; align-items:center;
  justify-content:center; flex:none; }
.bk-choices{ display:flex; flex-wrap:wrap; gap:var(--s-3); }
.bk-choices + .bk-choices{ margin-top:var(--s-3); }
.chip{ display:flex; flex-direction:column; align-items:flex-start; gap:2px;
  min-height:var(--tap-min); padding:var(--s-2) var(--s-4);
  background:transparent; color:var(--white);
  /* ⚠️ Plain rgba, deliberately. color-mix() with a var() inside is invalid
     at computed-value time on Chrome <=110 / iOS <=16.1 — the declaration is
     dropped to `initial`, NOT to a previous cascade value — so this border
     computed to 0px and the chips became invisible. They are transparent with
     no fill, so this border is the entire affordance. Do not "modernise" it. */
  border:1px solid rgba(255,255,255,.45);
  font-family:var(--font-body); font-size:var(--t-input); cursor:pointer;
  transition:background-color var(--d-micro) var(--ease-io); }
.chip-s{ font-family:var(--font-mono); font-size:var(--t-label);
         color:var(--honey-300); }
.chip.is-on{ background:var(--white); color:var(--v-900); border-color:var(--white); }
.chip.is-on .chip-s{ color:var(--v-800); }
.chip.is-off{ opacity:.5; cursor:not-allowed; }
@media (hover:hover) and (pointer:fine){
  .chip:not(.is-on):not(.is-off):hover{ background:rgba(255,255,255,.12); }
}
.bk-note{ font-size:var(--t-small); color:var(--honey-300); margin-top:var(--s-3); }
/* Her first-visit rule. Styled as a redirect, not an error — it offers the
   room rather than refusing the booking. Sits on .s13.on-dark (--v-900). */
.bk-gate{ margin-top:var(--s-4); padding:var(--s-5);
  background:rgba(255,255,255,.10);
  border-left:2px solid var(--honey-300); }
.bk-gate-h{ font-family:var(--font-display); font-size:var(--t-h3);
  font-weight:400; color:var(--bone-100); }
.bk-gate-b{ font-size:var(--t-small); color:var(--white); margin-top:var(--s-2); }
/* .btn is white-space:nowrap, which makes a long room name overflow its own
   fill on a narrow screen. Let this one wrap — belt and braces alongside
   CONFIG.places[].short. */
.bk-gate-a{ margin-top:var(--s-4); width:100%;
  white-space:normal; text-align:center; padding-block:var(--s-3); }
@media (min-width:36rem){ .bk-gate-a{ width:auto; } }
.bk-summary{ margin:var(--s-6) 0; padding:var(--s-5);
  background:rgba(255,255,255,.10);   /* was 8% — the price line failed AAA at 6.12:1 */
  border-left:2px solid var(--honey-300); }
.bk-summary .label{ color:var(--honey-300); }
.bk-line{ font-family:var(--font-display); font-size:var(--t-h3);
  color:var(--white); margin-top:var(--s-2); line-height:1.25; max-width:none; }
.bk-total{ font-family:var(--font-mono); font-size:var(--t-label);
  color:var(--honey-300); margin-top:var(--s-3); max-width:none; }

/* ── Forms ──────────────────────────────────────────────────────────────── */
.field{ margin-bottom:var(--s-5); }
label{ display:block; font-size:var(--t-input); font-weight:600;
       margin-bottom:var(--s-2); color:var(--ink); }
input,textarea,select{ width:100%; min-height:var(--tap-min); padding:14px 16px;
  font-family:var(--font-body); font-size:var(--t-input); line-height:1.5;
  color:var(--ink); background:var(--white); border:1px solid var(--v-600);
  border-radius:0; }
textarea{ min-height:110px; resize:vertical; }
input:focus-visible,textarea:focus-visible{ outline:3px solid var(--v-700);
  outline-offset:1px; border-color:var(--v-800); }
.hint{ font-size:var(--t-small); color:var(--ink-70); margin-top:var(--s-2); }
.err{ display:flex; gap:var(--s-2); align-items:flex-start;
  font-size:var(--t-small); color:var(--error); margin-top:var(--s-2); font-weight:500; }
input[aria-invalid="true"],textarea[aria-invalid="true"]{
  border-color:var(--error); border-width:2px; }
.hp{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }

.on-dark{ background:var(--v-900); color:var(--white); }
.on-dark h2{ color:var(--bone-100); }
.on-dark p,.on-dark .lede{ color:var(--white); }
.on-dark label{ color:var(--white); }
.on-dark .hint{ color:var(--bone-100); }
.on-dark .note-tick{ border-left-color:var(--honey-300); }
.on-dark .note-tick p{ color:var(--bone-100); }
.on-dark .err{ color:var(--honey-300); font-weight:600; }
.aside-note{ margin-top:var(--s-6); font-size:var(--t-small); color:var(--bone-100); }
.aside-note a{ display:inline-flex; align-items:center; min-height:var(--tap-min); }
.on-dark a{ color:var(--bone-100); text-decoration-color:rgb(245 241 233 / .55); }
#form-summary:empty{ display:none; }
#form-summary{ margin:var(--s-5) 0; }
#form-summary .err-summary{ color:var(--honey-300); }
#form-summary a{ color:var(--honey-300); font-size:var(--t-small); }
#form-summary ul{ margin-top:var(--s-2); }
.ok{ color:var(--white); } .ok-note{ margin-top:var(--s-2);
  font-size:var(--t-small); color:var(--honey-300); }
.confirm-echo{ margin-top:var(--s-5);
  border-top:1px solid rgba(255,255,255,.22); }
.confirm-echo dt{ font-family:var(--font-mono); font-size:var(--t-label);
  letter-spacing:.09em; text-transform:uppercase; color:var(--honey-300);
  margin-top:var(--s-4); }
.confirm-echo dd{ font-size:var(--t-small); color:var(--white);
  margin-top:var(--s-1); overflow-wrap:break-word; }

/* ── Empty states ───────────────────────────────────────────────────────── */
.empty-state{ font-size:var(--t-small); color:var(--ink-70);
  border-top:var(--rule-hair); border-bottom:var(--rule-hair);
  padding-block:var(--s-6); margin-top:var(--s-5); }

/* ── Products ───────────────────────────────────────────────────────────── */
.scroll-row{ display:flex; gap:var(--s-5); overflow-x:auto;
  scroll-snap-type:x proximity; margin-left:-20px; padding:0 0 var(--s-3) 20px;
  scrollbar-width:none; margin-top:var(--s-6); }
.scroll-row{ scroll-padding-inline:20px; padding-right:20px; }
.scroll-row > *{ scroll-snap-align:start; flex:0 0 78%; }
.scroll-row::-webkit-scrollbar{ display:none; }
.prod h3{ font-size:var(--t-small); font-weight:600;
  font-family:var(--font-body); margin-top:var(--s-4); }
.prod .price{ font-size:var(--t-h3); display:block; margin-top:var(--s-1);
              white-space:normal; }
.prod .label{ display:block; margin-top:var(--s-2); }
@media (min-width:1080px){
  .scroll-row{ display:grid; grid-template-columns:repeat(3,1fr);
    overflow:visible; margin-left:0; padding-left:0; }
  .scroll-row > *{ flex:none; }
}

/* ── Sticky bar ─────────────────────────────────────────────────────────── */
.sticky{ position:fixed; left:0; right:0; bottom:0; z-index:70;
  display:flex; flex-wrap:wrap; gap:var(--tap-gap); background:var(--v-25);
  border-top:var(--rule-solid); padding:11px 14px;
  padding-bottom:calc(11px + env(safe-area-inset-bottom, 0px));
  box-shadow:var(--shadow-sticky); transform:translateY(115%); visibility:hidden;
  transition:transform var(--d-sticky) cubic-bezier(.4,0,.2,1),
             visibility 0s linear var(--d-sticky); }
.sticky.show{ transform:translateY(0); visibility:visible; transition-delay:0s; }
.sticky-a{ flex:1 1 0; min-width:0; display:flex; align-items:center;
  justify-content:center; min-height:var(--tap-min); font-size:var(--t-input);
  font-weight:600; text-decoration:none;
  /* NEVER break these labels. `overflow-wrap:anywhere` was splitting the word
     across two lines as "WhatsA / pp" once the bar went from two buttons to
     three — 99px each at 375px. A brand name broken in half reads as a bug. */
  overflow-wrap:normal; word-break:keep-all; white-space:nowrap; }
.sticky-call{ background:var(--v-25); color:var(--ink); border:1px solid var(--ink); }
.sticky-book{ background:var(--v-700); color:var(--white); }
@media (min-width:1080px){ .sticky{ display:none; } }
.page-end{ height:96px; }
@media (min-width:1080px){ .page-end{ height:0; } }

/* ── FOOTER — the lilac she asked for ────────────────────────────────────
   --lilac is a decorative fill carrying dark text. It is never used AS a
   text colour. --ink on --lilac is 11.54:1; --v-800 for headings is 8.57:1.
   Amber is NOT used here — --honey-800 on lilac is only 5.54:1, and amber on
   a violet ground is a muddy pairing regardless. */
.ftr{ background:var(--lilac); color:var(--ink); }
.ftr a{ color:var(--v-800); }
.ftr-logo{ display:grid; gap:var(--s-5); margin-bottom:var(--s-7); }
.logo-todo{ background:rgba(255,255,255,.55);
  border-color:var(--v-700); max-width:260px; aspect-ratio:16/9; }
.ftr-strap{ font-size:var(--t-small); max-width:min(32em,100%); }
.ftr-cols{ display:grid; gap:var(--s-6); }
.ftr h3{ font-family:var(--font-mono); font-size:var(--t-label); font-weight:500;
  letter-spacing:.09em; text-transform:uppercase; color:var(--v-800);
  margin-bottom:var(--s-3); }
.ftr li{ font-size:var(--t-small); }
.ftr li + li{ margin-top:var(--s-2); }
/* Footer links are real tap targets, not 21px lines of text. */
.ftr li a, .ftr-note a{ display:inline-flex; align-items:center;
                        min-height:var(--tap-min); }
.ftr-rule{ border:0; border-top:1px solid var(--lilac-deep); margin-block:var(--s-6); }
.ftr-note{ font-size:var(--t-small); color:var(--ink); margin-top:var(--s-3);
           overflow-wrap:break-word; }
/* The email list (27 Oct 2026). --error on --lilac is only 6.05:1, so the footer's error line uses
   a deeper red, 7.3:1. */
.ftr-news{ margin-top:var(--s-7); max-width:34rem; }
.ftr-news-p{ font-size:var(--t-small); margin-bottom:var(--s-4); }
.news-form label{ font-size:var(--t-small); }
.news-row{ display:flex; gap:var(--s-2); }
.news-row input{ flex:1 1 auto; min-width:0; }
.news-row .btn{ flex:0 0 auto; }
.news-msg{ font-size:var(--t-small); margin-top:var(--s-2); }
.news-msg:empty{ margin-top:0; }   /* a shift after a tap is not layout shift (CLS ignores recent input) */
.news-msg.is-bad{ color:#741717; font-weight:500; }
.ftr-news-small{ font-size:var(--t-small); }
.ftr-news-small a{ display:inline-flex; align-items:center; min-height:var(--tap-min); }
@media (min-width:768px){ .ftr-logo{ grid-template-columns:auto 1fr; align-items:center; } }
@media (min-width:1080px){ .ftr-cols{ grid-template-columns:repeat(3,1fr); gap:var(--s-7); } }

/* ── Reveal ─────────────────────────────────────────────────────────────── */
.rv{ transition:opacity var(--d-type) var(--ease-out),
                transform var(--d-type) var(--ease-out); }
.js .rv{ opacity:0; transform:translateY(10px); }
.js .rv.in{ opacity:1; transform:none; }

/* ── Tablet ─────────────────────────────────────────────────────────────── */
@media (min-width:768px) and (max-width:1079.98px){
  .wrap{ padding-inline:clamp(40px, 8vw, 96px); }
  .scroll-row{ display:grid; grid-template-columns:repeat(2,1fr);
    overflow:visible; margin-left:0; padding-left:0; }
}

/* ── Mobile overrides — declared LAST so they win ───────────────────────── */
@media (max-width:1079.98px){
  .grid-rail > .aside{ margin-top:var(--s-6); }
  .s06 .zones{ margin-top:var(--s-6); }
}

/* A phone on its side is 390px tall. `--pad-open` is 135px top AND bottom, so
   the audit's landscape contact sheet showed whole screens holding one tick
   mark. Halve the section padding when the viewport is that short. */
@media (max-height:500px) and (orientation:landscape){
  :root{ --pad-open:64px; --pad-normal:56px; --pad-tight:40px; }
  .grid-rail > .aside{ margin-top:var(--s-5); }
  .hero img{ min-height:390px; }
}

/* ── Reduced motion — never *{transition:none!important} ────────────────── */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation-duration:.01ms !important;
    animation-iteration-count:1 !important; transition-duration:.01ms !important;
    scroll-behavior:auto !important; }
  a,button,input,textarea,select,.btn{
    transition:background-color 150ms linear, color 150ms linear,
               outline-color 150ms linear !important; }
  .reveal-img,.reveal-img > picture,.reveal-img > .sq{ clip-path:none !important; opacity:1 !important; }
  .reveal-img img{ transform:none !important; }
  .rv{ opacity:1 !important; transform:none !important; }
  .hero img{ animation:none !important; }
}

/* ── Added 6 Aug ─────────────────────────────────────────────────────────── */

/* The away notice. She may need this changed at short notice with nobody
   around to help, so it is deliberately the plainest thing on the page. */
.notice{ background:var(--v-900); color:var(--bone-100);
  padding:var(--s-3) var(--page-pad); font-size:var(--t-small);
  text-align:center; line-height:1.45; }
.notice[hidden]{ display:none; }

/* Name and credential in the first screen — the anti-persona test. Sits under
   the hero lede, so hero_contrast.py covers it inside the lede band. */
.hero-panel .hero-eyebrow strong{ font-weight:500; color:var(--white);
  letter-spacing:.04em; }

/* "You came from a friend's link" */
.ref-arrived{ background:var(--v-50); border-left:2px solid var(--honey-800);
  padding:var(--s-4) var(--s-5); margin-bottom:var(--s-6);
  font-size:var(--t-small); }
.ref-arrived[hidden]{ display:none; }

/* No-JS bar. Everything below it is rendered by app.js, so this is the only
   way to reach her when the script does not run. */
.noscript-bar{ background:var(--v-900); color:var(--white);
  padding:var(--s-4) var(--page-pad); font-size:var(--t-small); text-align:center; }
.noscript-bar a{ color:var(--honey-300); }

/* WhatsApp in the sticky bar — the default channel in this market, and the
   one route the bar was missing. Three items now, so they share the width. */
.sticky-wa{ background:var(--wa); color:var(--white); }
.sticky-wa .btn-i{ width:20px; height:20px; margin-right:6px; flex:none; }
/* Three targets, 24px apart (the mis-tap rule), inside 375px leaves ~99px each.
   "WhatsApp" needs ~78px of type; the icon does not fit alongside it. The word
   is the recognisable thing in this market, so the icon goes and the word stays. */
@media (max-width:419px){
  .sticky-wa .btn-i{ display:none; }
  .sticky-call .btn-i{ display:none; }
}

/* Required / optional markers. The fields carried `required` but nothing
   visible said so, and the textarea gave no clue it was optional. WCAG 3.3.2. */
.req,.opt{ font-family:var(--font-mono); font-size:var(--t-label);
  letter-spacing:.04em; text-transform:none; }
.on-dark .req,.on-dark .opt{ color:var(--bone-100); }
.privacy-inline{ margin-top:var(--s-4); }

/* The privacy link sits inside a sentence, which WCAG 2.5.8 exempts from the
   target-size rule — but this site's own floor is 56px everywhere and the
   mobile harness rightly flagged it at 21px. Give it a real target without
   breaking the sentence. */
.privacy-inline{ display:flex; flex-wrap:wrap; align-items:center; gap:0 .35em; }
.privacy-inline a{ display:inline-flex; align-items:center;
  min-height:var(--tap-min); padding-block:var(--s-2); }

/* Short mono notes under a price ("depending on the area") were leaving a
   single word alone on the second line at 414px. `balance` is right here —
   these are two-to-four word fragments, not prose. */
.rate-dur{ text-wrap:balance; }

/* ── Booking step 1: grouped treatment chips ────────────────────────────────
   The 24 treatments rendered as one flat list ran to 1,740px on a 390px
   phone — over two screens of buttons before step 2 came into view. They are
   grouped now, one group open at a time. */
.chip-group{
  display:flex; align-items:center; justify-content:flex-start; gap:.5rem;
  width:100%; margin:.5rem 0 .35rem; padding:.75rem .9rem;
  background:var(--v-50); border:1px solid var(--v-150); border-radius:0;
  font:inherit; font-size:1rem; font-weight:600; color:var(--v-800);
  text-align:left; cursor:pointer; min-height:48px;
}
.chip-group small{
  /* 0.85rem was 13.6px and dropped the page's smallest text below its
     15px floor. The audience is 45-85; the count badge is not worth that. */
  font-weight:500; font-size:.95rem; color:var(--ink-70);
  background:var(--bone-50); border:1px solid var(--v-150);
  border-radius:0; padding:.1rem .5rem;
}
.chip-group::after{
  content:''; width:.55rem; height:.55rem; margin-left:auto; flex:0 0 auto;
  border-right:2px solid var(--v-600); border-bottom:2px solid var(--v-600);
  transform:rotate(45deg) translateY(-2px); transition:transform .18s ease;
}
.chip-group.is-open::after{ transform:rotate(-135deg) translateY(-2px); }
.chip-group.is-open{ background:var(--v-100); border-color:var(--v-600); }
.chip-group:focus-visible{ outline:3px solid var(--v-700); outline-offset:2px; }
.chip.is-collapsed{ display:none; }
@media (prefers-reduced-motion:reduce){ .chip-group::after{ transition:none; } }

/* ── Booking step 5: before you arrive (added 14 Sep, her ask of 11 Sep) ──
   A consultation, not a medical form. It asks WHETHER there is something to
   discuss, never what — the detail stays verbal, as the privacy section
   promises. The release clause is a real checkbox with a real tap target. */
.bk-step-form{ margin-top:var(--s-6); }
.bk-step-form .field{ margin-top:var(--s-5); }
.consult-note[hidden]{ display:none; }
.consent{ display:flex; gap:var(--s-4); align-items:flex-start;
  margin-top:var(--s-5); padding:var(--s-4) 0; min-height:var(--tap-min);
  cursor:pointer; }
.consent input{ width:28px; height:28px; min-height:0; flex:none;
  margin-top:3px; padding:0; accent-color:var(--honey-300); cursor:pointer; }
.consent span{ font-size:var(--t-input); line-height:1.5; color:var(--white); }
.consent input:focus-visible{ outline:3px solid var(--honey-300); outline-offset:3px; }
.on-dark .consent .err{ margin-left:calc(28px + var(--s-4)); }

/* ── A quiet Jamaican mark ──────────────────────────────────────────────────
   Sized to the cap-height of the text beside it and given a hairline, so it
   reads as a mark rather than a sticker. Deliberately restrained: the brand
   is discretion, so this sits next to the place name, not across the hero. */
.jm-flag{
  display:inline-block; width:1.15em; height:auto; vertical-align:-0.12em;
  margin-left:.35em; border-radius:2px;
  box-shadow:0 0 0 1px rgb(36 28 48 / .22);
}
.on-dark .jm-flag, .ftr .jm-flag{ box-shadow:0 0 0 1px rgb(251 249 245 / .35); }

/* 30 Sep 2026 · the chosen logo (option 1, Signature) replaces the typed wordmark.
   Header: the one-line cut, 38px tall on phones (about 200px wide, leaves room for Book),
   46px from 1080px. The typed tagline is hidden: the hero kicker directly below says the same.
   Footer: the stacked cut. */
.logo .logo-img{ display:block; height:38px; width:auto; max-width:none; align-self:flex-start; }
.logo .logo-tag{ display:none; }
@media (min-width:1080px){ .logo .logo-img{ height:46px; } }
.ftr-logo-img{ display:block; width:170px; height:auto; }


/* ── 30 Sep 2026 · real photographs, her signature, the gift card, calm motion ──────────
   Checked by _build/motion-check.py (M1–M7) and _build/brand-check.py. Every start state
   lives under .js, so without JavaScript everything is simply there; reduced motion is
   handled at the end of this block. */

/* Photographs of Sam keep her colour exactly as photographed: no page grade, no plum wash */
.ph.real img{ filter:none; }
.ph.real > picture::after{ display:none; }
.ph.portrait img{ aspect-ratio:4/5; object-fit:cover; }

/* About on phones: her face first, then her words (desktop keeps the portrait beside the text) */
@media (max-width:1079.98px){
  .s04 .grid-rail{ display:grid; grid-template-columns:minmax(0,1fr); row-gap:var(--s-6); }   /* block flow ignores order */
  .s04 .grid-rail > .aside{ order:1; margin:0; }
  .s04 .grid-rail > .body{ order:2; }
  .s04 .ph.portrait{ max-width:420px; }
}

/* About: her signature signs the page off, like a letter */
.signoff{ margin-top:var(--s-5); }
.signoff-sig{ display:block; width:150px; height:auto; }
@media (min-width:1080px){ .signoff-sig{ width:170px; } }
/* …and writes itself as it comes into view: a left-to-right wipe, like a pen */
.js .signoff.rv .signoff-sig{ clip-path:inset(0 100% 0 0);
  transition:clip-path 1200ms cubic-bezier(.45,.05,.35,1) 250ms; }
.js .signoff.rv.in .signoff-sig{ clip-path:inset(0 0 0 0); }

/* Gift: the lede beside a small certificate */
.gift-intro{ display:grid; gap:var(--s-6); align-items:center; }
@media (min-width:900px){ .gift-intro{ grid-template-columns:minmax(0,1fr) 290px; gap:var(--s-7); } }
.gift-card{ margin:0; justify-self:center; width:100%; max-width:290px; aspect-ratio:1.6;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px;
  padding:22px; background:var(--bone-50); border:1px solid var(--lilac-deep);
  outline:1px solid var(--lilac-deep); outline-offset:-9px;
  box-shadow:0 18px 40px -18px rgba(56,42,74,.45); transform:rotate(-2deg); }
.gift-card img{ width:112px; height:auto; }
.gift-card-label{ font-family:var(--font-mono); font-size:.72rem; letter-spacing:.18em;
  text-transform:uppercase; color:var(--v-700); }
.gift-card-line{ width:68%; border-bottom:1px solid var(--lilac-deep); padding-bottom:2px;
  font-family:var(--font-display); font-style:italic; font-size:.95rem; color:var(--ink-70); }
.gift-card-foot{ font-family:var(--font-mono); font-size:.62rem; letter-spacing:.14em;
  text-transform:uppercase; color:var(--ink-70); }
.js .gift-card.rv{ transform:translateY(12px) rotate(-5deg); }
.js .gift-card.rv.in{ transform:rotate(-2deg); }

/* Headings: the short rule above each one draws across as the section arrives; so do the rail ticks */
.js .rv .h2-rule{ transform:scaleX(0); transform-origin:left center;
  transition:transform 700ms var(--ease-out) 120ms; }
.js .rv.in .h2-rule{ transform:none; }
.js .rail.rv .tick{ transform:scaleX(0); transform-origin:left center;
  transition:transform 500ms var(--ease-out) 60ms; }
.js .rail.rv.in .tick{ transform:none; }

/* Duration boxes and gift tiers fade in, in a short stagger capped at four. Fade only: their section already rises */
.js .rv .lengths > li, .js .rv .tiers-grid > .gift-tier{ opacity:0;
  transition:opacity 480ms var(--ease-out); }
.js .rv.in .lengths > li, .js .rv.in .tiers-grid > .gift-tier{ opacity:1; }
.js .rv.in .lengths > li:nth-child(2), .js .rv.in .tiers-grid > .gift-tier:nth-child(2){ transition-delay:70ms; }
.js .rv.in .lengths > li:nth-child(3), .js .rv.in .tiers-grid > .gift-tier:nth-child(3){ transition-delay:140ms; }
.js .rv.in .lengths > li:nth-child(4){ transition-delay:210ms; }

/* Booking: each step eases in when it appears */
@keyframes stepIn{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:none; } }
.js .bk-step:not([hidden]){ animation:stepIn 300ms var(--ease-out) both; }

/* Booking thank-you: her mark beside the thanks */
.ok-mark{ display:block; width:44px; height:44px; margin-bottom:var(--s-4); }
@keyframes markIn{ from{ opacity:0; transform:scale(.85); } to{ opacity:1; transform:none; } }
.js .ok-mark{ animation:markIn 520ms var(--ease-out) both; }

/* Desktop hover: a small lift on what can be clicked (the certificate is not a link, so it stays still) */
@media (hover:hover) and (pointer:fine){
  .lengths a{ transition:transform 220ms var(--ease-out), box-shadow 220ms var(--ease-out),
                         background-color 180ms linear; }
  .lengths a:hover{ transform:translateY(-2px); box-shadow:0 10px 24px -14px rgba(56,42,74,.45); }
}

@media (prefers-reduced-motion:reduce){
  .signoff-sig{ clip-path:none !important; }
  .h2-rule, .tick{ transform:none !important; }
  .gift-card{ transform:rotate(-2deg) !important; }
  .lengths > li, .tiers-grid > .gift-tier{ opacity:1 !important; }
  .bk-step, .ok-mark{ animation:none !important; }
  .lengths a:hover{ transform:none !important; }
}

/* 30 Sep 2026 · fixes from the independent design review */
/* Gift note: it borrowed .bk-note, honey meant for the dark booking panel, about 1.9:1 on lilac */
.gift-note{ font-size:var(--t-small); color:var(--ink-70); margin-top:var(--s-5); max-width:min(34em,100%); }
.gift-note + .draft-note + .btn, .gift-note + .btn, .note-tick + .btn{ margin-top:var(--s-5); }
/* Side labels on two lines sat on the parent's line box, 51px for two 20px lines */
.rail .label{ display:block; }
/* A heading straight over a paragraph sat 0px above it in About, Refer and Shop */
.span > h2 + p, .body > h2 + p{ margin-top:var(--s-5); }
/* Phone: the side label and its tick already sit above the heading, so the
   heading's own short rule made three stacked marks. It stays on desktop,
   where the label is in a column of its own. */
@media (max-width:1079.98px){ .rail + .span > .h2-rule, .rail + .body > .h2-rule{ display:none; }
  .grid-rail > .rail{ margin-bottom:var(--s-4); } }
/* What happens: three pictures, one shape (they were 4:5, 3:4 and 3:2) */
.step-fig img{ aspect-ratio:4/5; width:100%; height:auto; object-fit:cover; }

/* 30 Sep 2026 · Sam's welcome, straight after the opening room. On a phone she comes
   first, then the lengths; on desktop she stands beside them. */
@media (max-width:1079.98px){
  .s01 .grid-rail{ display:grid; grid-template-columns:minmax(0,1fr); row-gap:var(--s-6); }
  .s01 .grid-rail > .rail{ order:0; margin-bottom:0; }
  .s01 .grid-rail > .aside{ order:1; margin:0; }
  .s01 .grid-rail > .body{ order:2; }
  .s01 .ph.portrait{ max-width:420px; }
}
@media (min-width:1080px){ .s01 .grid-rail{ align-items:start; } .s01 .lengths{ grid-template-columns:repeat(2,1fr); } }
.s06 .ph.portrait{ max-width:520px; }

/* 30 Sep 2026 · AI-posed pictures of Sam (from her own photographs, judged blind for likeness and skin
   tone). Like her real photographs they skip the page grade and plum wash, so her skin stays as judged. */
.ph.as-shot img{ filter:none; }
.ph.as-shot > picture::after{ display:none; }
.place-fig{ margin-top:var(--s-4); }
.place-fig img{ aspect-ratio:16/9; width:100%; height:auto; object-fit:cover; }

/* Where: home visits band, two pictures side by side from 700px, under the rooms */
.visits{ margin:var(--s-7) 0 0; }
.visits-grid{ display:grid; gap:var(--s-4); grid-template-columns:minmax(0,1fr); }
.visits-grid img{ aspect-ratio:16/9; width:100%; height:auto; object-fit:cover; }
.visits figcaption{ margin-top:var(--s-3); }
@media (min-width:700px){ .visits-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (min-width:1080px){ .s06 .visits{ grid-column:2 / -1; } }

/* ── 30 Sep 2026 · the 404 page on the shared header and footer (multi-page step 4) ──────────
   These rules were an inline <style> in 404.html. The page now carries the site header, footer
   and sticky bar, because on its own it was shorter than a large phone and left a 330–480px
   empty band under its content (audit.py, once it ran on every page). */
.oops{ max-width:38rem; margin-inline:auto; padding:var(--pad-open) var(--page-pad); }
.oops h1{ font-size:var(--t-h2); font-weight:300; }
.oops p{ margin-top:var(--s-5); }
.oops .cta-row{ margin-top:var(--s-7); }


/* ── 30 Sep 2026 · desktop rhythm (01_SPEC/2026-09-30_multipage_rebuild/04_DESKTOP_SPACING.md) ──
   Craig: "too much white space on the desktop pages". Measured at 1440: 208px section padding,
   empty bands of 272–365px where sections meet, small pictures beside short text. On phones,
   pad-normal keeps its 72px minimum, and the three open sections go from 112px to 96px. */
:root{ --pad-normal: clamp(72px, 5.5vw, 80px); --pad-open: clamp(96px, 6.5vw, 96px); }
@media (min-width:1080px){
  /* What happens: the picture grows and the words sit beside its middle */
  .step{ align-items:center; padding:var(--s-6) 0; }
  .step-fig{ max-width:440px; }
  .step-fig img{ aspect-ratio:4/3; }
  /* The pull quote: normal padding, across both content columns */
  .s07{ padding-block:var(--pad-normal); }
  .quote-rail{ grid-column:2 / -1; }
  blockquote.pull p{ max-width:min(26ch,100%); }
  /* A long column of text beside one picture: the picture travels with the text */
  .s04 .grid-rail > .aside, .s06 .grid-rail > .aside{ position:sticky; top:96px; align-self:start; }
  /* Price list: the short single-price groups in two columns, hairlines kept */
  .rate-group[data-group="focused"], .rate-group[data-group="facial"], .rate-group[data-group="waxing"]{
    display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); column-gap:var(--s-7); }
  .rate-group[data-group="focused"] > .rate:nth-last-child(-n+2),
  .rate-group[data-group="facial"] > .rate:nth-last-child(-n+2),
  .rate-group[data-group="waxing"] > .rate:nth-last-child(-n+2){ border-bottom:var(--rule-hair); }
  /* Massage prices: the name and description beside the four lengths, not above them */
  .rate.rate-matrix{ grid-template-columns:minmax(0,1fr) minmax(0,1.5fr); column-gap:var(--s-7); align-items:center; }
  .rate.rate-matrix .tiers{ margin-top:0; }
  /* Shop: the hives picture as a wide band, not a small block */
  .hives{ max-width:none; }
  .hives img{ aspect-ratio:21/9; width:100%; height:auto; object-fit:cover; }
}

/* ── 30 Sep 2026 · the current page in the navigation (multi-page step 5) ─────────────────────
   build_pages.py marks the link to the page you are on with aria-current="page". */
.nav a[aria-current="page"]{ color:var(--v-800); text-decoration:underline; text-decoration-thickness:2px;
  text-underline-offset:.32em; }
.ftr a[aria-current="page"]{ text-decoration-thickness:2px; text-decoration-color:currentColor; }

/* ── 30 Sep 2026 · About (multi-page step 5, static; 01_SPEC/2026-09-30_multipage_rebuild/03_ABOUT_MOTION_SPEC.md)
   Her real portrait and her letter share the first screen. Her face is never hidden: the portrait
   carries no reveal, and neither do the opening's words (only the heading rule draws). The motion
   layer (K1–K4) is a later, separate deploy. */
.ab-open-grid{ display:grid; row-gap:var(--s-6); }
.ab-portrait{ margin:0; max-width:420px; }
.ab-letter .ab-eyebrow{ margin-bottom:var(--s-4); }
.ab-letter .ab-eyebrow strong{ font-weight:500; color:var(--ink); }
.ab-letter h1{ font-size:var(--t-h2); font-weight:300; line-height:1.06; }
.ab-cred{ display:flex; flex-wrap:wrap; gap:0 var(--s-5); margin:var(--s-3) 0 var(--s-6); }
@media (min-width:1080px){
  .ab-open-grid{ grid-template-columns:minmax(0,5fr) minmax(0,7fr); column-gap:var(--s-8); align-items:start; }
  .ab-portrait{ max-width:none; }
  .ab-letter{ padding-top:var(--s-6); }
}

/* How I work: three steps, each its words and then her hands. A 2px rule runs down the left over a
   hairline track (the motion pass makes it fill as you read; here it is simply drawn). */
.how-grid{ position:relative; display:grid; row-gap:var(--s-7); padding-left:var(--s-5); margin-top:var(--s-6); }
.how-grid::before, .how-rule{ content:""; position:absolute; left:0; top:0; bottom:0; width:2px; }
.how-grid::before{ background:var(--v-150); }
.how-rule{ background:var(--v-700); transform-origin:top; }
.how-text h3{ font-size:var(--t-h3); }
.how-text p{ margin-top:var(--s-3); }
.how-quote p{ font-family:var(--font-display); font-size:var(--t-lede); font-style:italic; line-height:1.32; }
.how-fig{ margin:var(--s-4) 0 0; max-width:420px; }
.how-fig img{ aspect-ratio:4/5; width:100%; height:auto; object-fit:cover; }
.ab-both{ margin-top:var(--s-7); }
@media (min-width:1080px){
  .how-grid{ margin-left:calc(var(--rail) + var(--s-7)); padding-left:var(--s-6); row-gap:var(--s-6); }
  .how-step{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,400px); column-gap:var(--s-7); align-items:center; }
  .how-fig{ margin-top:0; }
  .ab-both{ margin-left:calc(var(--rail) + var(--s-7)); }
}

/* What I work with: the conditions in one sentence, then the six techniques without their prices */
.ab-tech{ display:grid; gap:var(--s-4) var(--s-7); margin-top:var(--s-6); }
.ab-tech h3{ font-size:var(--t-h3); }
.ab-tech h3 a{ color:var(--ink); display:inline-flex; align-items:center; min-height:var(--tap-min); }
.ab-tech p{ font-size:var(--t-small); color:var(--ink-70); }
.ab-note{ margin-top:var(--s-6); }
@media (min-width:768px){ .ab-tech{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (min-width:1080px){ .ab-tech{ grid-template-columns:repeat(3,minmax(0,1fr)); } }

/* The room, and her bee farm on a warmer ground */
.more-link{ margin-top:var(--s-5); }
.more-link a{ display:inline-flex; align-items:center; min-height:var(--tap-min); font-size:var(--t-lede); }
.ab-bees{ background:var(--bone-100); }
.ab-hives{ margin:0; }
.ab-hives img{ aspect-ratio:3/2; width:100%; height:auto; object-fit:cover; }
@media (max-width:1079.98px){ .ab-hives{ max-width:520px; } }
@media (min-width:1080px){ .ab-room .grid-rail, .ab-bees .grid-rail{ align-items:center; } }

/* ── 1 Oct 2026 · header, menu sheet, sticky bar, booking bar, Book (multi-page step 6; 01 §3.1–3.2) ──
   Phones and tablets: the logo and a labelled Menu button; Call · WhatsApp · Book sit in the sticky bar
   from the first screen. From 1080px: the logo, four pages, the phone number as text, and Book. */
.hdr-tel, .hdr-book{ display:none; }
.hdr-menu{ display:inline-flex; align-items:center; justify-content:center; min-height:var(--tap-min);
  min-width:var(--tap-min); padding:0 var(--s-4); border:1px solid var(--ink); color:var(--ink);
  font-weight:600; font-size:1.0625rem; text-decoration:none; }
@media (min-width:1080px){
  .hdr-in{ flex-wrap:nowrap; justify-content:flex-start; gap:var(--s-5); }
  .hdr .nav{ margin-left:auto; gap:var(--s-5); }
  .hdr-tel{ display:inline-flex; align-items:center; min-height:var(--tap-min); padding-inline:var(--s-2);
    color:var(--ink); font-family:var(--font-mono); font-size:1rem; letter-spacing:.04em;
    text-decoration:none; white-space:nowrap; }
  .hdr .hdr-book{ display:inline-flex; }
  .hdr-menu{ display:none; }
}
@media (min-width:1280px){ .hdr .nav{ gap:var(--s-6); } }

/* The sheet. Above everything; the page under it does not scroll while it is open. */
.sheet{ position:fixed; inset:0; z-index:90; background:var(--v-25); overflow-y:auto; }
.sheet[hidden]{ display:none; }
html.sheet-open{ overflow:hidden; }
.sheet-in{ max-width:36rem; margin:0 auto; padding:var(--s-3) var(--page-pad) var(--s-7);
  display:flex; flex-direction:column; }
.sheet-close{ align-self:flex-end; min-height:var(--tap-min); min-width:var(--tap-min); padding:0 var(--s-4);
  background:transparent; border:1px solid var(--ink); color:var(--ink); font:inherit; font-weight:600; cursor:pointer; }
.sheet-nav{ display:flex; flex-direction:column; margin-top:var(--s-5); border-top:var(--rule-hair); }
.sheet-nav a{ display:flex; align-items:center; min-height:var(--tap-min); padding-block:var(--s-2);
  font-size:1.25rem; color:var(--ink); text-decoration:none; border-bottom:var(--rule-hair); }
.sheet-nav a[aria-current="page"]{ color:var(--v-800); text-decoration:underline; text-underline-offset:.3em; }
.sheet-do{ display:flex; flex-direction:column; gap:var(--tap-gap); margin-top:var(--s-6); }
.sheet-do .btn{ width:100%; }
@media (min-width:1080px){ .sheet{ display:none !important; } }

/* The sticky bar now shows from the first screen (it used to wait for 85% of a scroll). */
.sticky{ transform:none; visibility:visible; }

/* The booking bar on Book: the running summary and Next, in the sticky bar's place. */
.bookbar{ position:fixed; left:0; right:0; bottom:0; z-index:70; display:flex; align-items:center;
  gap:var(--s-4); background:var(--v-25); border-top:var(--rule-solid); padding:11px 14px;
  padding-bottom:calc(11px + env(safe-area-inset-bottom, 0px)); box-shadow:var(--shadow-sticky); }
.bookbar-line{ flex:1 1 auto; min-width:0; font-size:var(--t-label); line-height:1.35; color:var(--ink);
  max-width:none; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
.bookbar-next{ flex:none; min-width:6.5rem; }
@media (min-width:1080px){ .bookbar{ display:none; } }

/* Book: the steps, and the summary card beside them on desktop so the right side is never empty
   (04_DESKTOP_SPACING.md, notes for the new pages). */
.book-page h1{ font-size:var(--t-h2); font-weight:300; color:var(--bone-100); }
.bk-card-call{ margin-top:var(--s-3); font-size:var(--t-small); }
.bk-card-call a{ display:inline-flex; align-items:center; min-height:var(--tap-min); }
.bk-chosen{ display:flex; flex-wrap:wrap; align-items:center; gap:0 var(--s-2); margin-top:var(--s-2);
  font-family:var(--font-display); font-size:var(--t-h3); color:var(--white); max-width:none; }
.bk-change{ min-height:var(--tap-min); padding:0 var(--s-4); background:transparent; color:var(--bone-100);
  border:1px solid rgba(255,255,255,.55); font:inherit; font-family:var(--font-body); font-size:var(--t-small);
  font-weight:600; cursor:pointer; }
.bk-step.is-folded .bk-choices{ display:none; }
.book-privacy{ margin-top:var(--s-7); padding-top:var(--s-5); border-top:1px solid rgba(255,255,255,.22); }
.book-privacy .label{ color:var(--honey-300); }
.book-privacy p{ margin-top:var(--s-3); font-size:var(--t-small); }
.book-privacy a{ display:inline-flex; align-items:center; min-height:var(--tap-min); }
@media (min-width:1080px){
  .book-page .booker{ display:grid; grid-template-columns:minmax(0,1fr) 380px; column-gap:var(--s-7); align-items:start; }
  .book-page .bk-steps, .book-page #book-form{ grid-column:1; }
  /* the summary is a full-height side panel; its contents stay in view as the form scrolls */
  .book-page .bk-summary{ grid-column:2; grid-row:1 / span 2; align-self:stretch; margin:0; }
  .book-page .bk-summary-in{ position:sticky; top:calc(72px + var(--s-5)); }
  .book-page .lede, .book-page .book-privacy p{ max-width:min(40em, 100%); }
}

/* ── 1 Oct 2026 · desktop rhythm, continued (multi-page step 6) ─────────────────────────────────
   whitespace-check.py W2 (at most 40% empty rows at 1440) failed on Home once the booker left it, and on
   Book. The bar stays where 04_DESKTOP_SPACING.md set it; the pages lose white space instead:
   the footer becomes one compact row (it was 55–65% empty rows on every page), the price list's rows
   and group gaps tighten, and the question rows lose padding but keep their 56px targets. */
@media (min-width:1080px){
  .ftr .wrap{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,3fr); column-gap:var(--s-7); align-items:start; }
  .ftr-logo{ grid-column:1; grid-template-columns:1fr; gap:var(--s-4); margin-bottom:0; }
  .ftr-cols{ grid-column:2; grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1.6fr); gap:var(--s-6); }
  .ftr-pages ul{ display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); column-gap:var(--s-5); }
  .ftr-pages li + li{ margin-top:0; }
  .ftr-rule, .ftr-note{ grid-column:1 / -1; }
  .ftr-rule{ margin-block:var(--s-5); }
  /* 27 Oct 2026: the email list sits under the three columns, words left and form right, one band */
  .ftr-news{ grid-column:2; max-width:none; margin-top:var(--s-3);
    display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.15fr); column-gap:var(--s-6); align-items:start; }
  /* the privacy link sits under the form, so the two columns end level (whitespace W2 on Privacy) */
  .ftr-news > h3{ grid-column:1; grid-row:1; }
  .ftr-news-p{ grid-column:1; grid-row:2 / span 2; margin-bottom:0; }
  .news-form{ grid-column:2; grid-row:1 / span 2; }
  .ftr-news-small{ grid-column:2; grid-row:3; }
  .rate{ padding-block:var(--s-4); }
  .cat{ margin-top:var(--s-7); }
  .faq summary{ padding-block:var(--s-2); }
}
@media (min-width:1280px){
  /* eleven waxing prices read as one block in three columns, not a long two-column list. Facials stay in
     two: "Microdermabrasion" is one long word and ran into its price at three. */
  .rate-group[data-group="waxing"]{ grid-template-columns:repeat(3, minmax(0,1fr)); }
  .rate-group[data-group="waxing"] > .rate:nth-last-child(-n+3){ border-bottom:var(--rule-hair); }
}
/* A long one-word treatment ("Microdermabrasion") ran into its price in the two-column groups at 1080px
   (from the 4b block). It now breaks with a hyphen where it has to, and only then. */
.rate-name h3{ -webkit-hyphens:auto; hyphens:auto; overflow-wrap:break-word; }
@media (min-width:1080px){
  /* …better still, when a name cannot sit beside its price, the price takes its own line under it */
  .rate:not(.rate-matrix){ display:flex; flex-wrap:wrap; align-items:baseline; column-gap:var(--s-4); row-gap:var(--s-2); }
  .rate:not(.rate-matrix) .rate-name{ flex:1 1 auto; min-width:0; }
  .rate:not(.rate-matrix) .rate-fig{ flex:none; margin-left:auto; }
}

/* ── 1 Oct 2026 · Treatments and prices (multi-page step 7; 01 §2.4, §4.3) ─────────────────────
   The rate card as on the one page; each price is a link into Book with that treatment (and length)
   chosen. Four jump links: a row on phones, the left rail on desktop, staying in view. */
.tr-head h1{ font-size:var(--t-h2); font-weight:300; }
.tr-jump{ display:flex; flex-wrap:wrap; gap:var(--s-3); margin:var(--s-6) 0 0; }
.tr-jump a{ display:inline-flex; align-items:center; min-height:var(--tap-min); padding:0 var(--s-4);
  border:1px solid var(--v-600); color:var(--ink); text-decoration:none; font-size:var(--t-small); }
@media (hover:hover) and (pointer:fine){ .tr-jump a:hover{ background:var(--v-50); } }
.tr-list > .cat:first-child{ margin-top:var(--s-7); }
.tier-book{ display:flex; flex-direction:column; gap:2px; min-height:var(--tap-min); justify-content:center;
  color:inherit; text-decoration:none; }
.tier-book .price{ text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:.18em;
  text-decoration-color:rgba(56,42,74,.3); }
.price-book{ display:inline-flex; align-items:baseline; min-height:var(--tap-min); padding-top:var(--s-2);
  color:inherit; text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:.18em;
  text-decoration-color:rgba(56,42,74,.3); }
@media (hover:hover) and (pointer:fine){
  .tier-book:hover .price, .price-book:hover{ text-decoration-color:currentColor; }
}
@media (min-width:1080px){
  .tr-grid{ grid-template-rows:auto 1fr; }
  .tr-grid > .rail{ grid-column:1; grid-row:1; }
  .tr-grid > .tr-head{ grid-column:2 / -1; grid-row:1; }
  .tr-jump{ grid-column:1; grid-row:2; align-self:start; position:sticky; top:calc(72px + var(--s-5));
    flex-direction:column; margin-top:var(--s-7); gap:0; }
  .tr-jump a{ border:0; padding:0; min-height:var(--tap-min); font-family:var(--font-mono); font-size:var(--t-label);
    letter-spacing:.06em; text-transform:uppercase; color:var(--ink-70); }
  .tr-jump a:hover{ background:none; color:var(--v-800); text-decoration:underline; }
  .tr-grid > .tr-list{ grid-column:2 / -1; grid-row:2; }
}
/* From 1280px the rate card is two columns, like a printed menu: massage on the left, the fixed-price
   groups on the right. It halves the page and the two columns fill each other's gaps (whitespace W2). */
.tr-group > .cat{ margin-top:var(--s-7); }
.tr-list > .tr-group:first-child > .cat{ margin-top:var(--s-6); }
@media (min-width:1280px){
  .tr-list{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); column-gap:var(--s-7); align-items:start; }
  .tr-list > .tr-massage{ grid-column:1; grid-row:1 / span 3; }
  .tr-list > .tr-group:not(.tr-massage){ grid-column:2; }
  .tr-list > .tr-group:nth-child(2) > .cat{ margin-top:var(--s-6); }
  .tr-list > .note-tick{ grid-column:1 / -1; }
  .tr-list .rate.rate-matrix{ grid-template-columns:minmax(0,1fr); row-gap:var(--s-3); }
  .tr-list .tiers{ grid-template-columns:repeat(2, minmax(0,1fr)); gap:var(--s-2) var(--s-5); }
  .tr-list .rate-group[data-group]{ display:block; }
  .tr-list .rate-group > .rate{ border-bottom:0; }
  .tr-list .rate-group > .rate:last-child{ border-bottom:var(--rule-hair); }
}
@media (min-width:1280px){
  /* each massage is a card and each fixed-price group a panel (01 §2.4 "six cards"), on white */
  .tr-list .rate.rate-matrix{ border:1px solid var(--v-150); background:var(--white); padding:var(--s-5);
    margin-bottom:var(--s-4); }
  .tr-list .rate-group[data-group="massage"] > .rate:last-child{ border-bottom:1px solid var(--v-150); margin-bottom:0; }
  .tr-list .rate-group[data-group="focused"], .tr-list .rate-group[data-group="facial"],
  .tr-list .rate-group[data-group="waxing"]{ border:1px solid var(--v-150); background:var(--white); padding:0 var(--s-5); }
  .tr-list .rate-group:not([data-group="massage"]) > .rate:first-child{ border-top:0; }
  .tr-list .rate-group:not([data-group="massage"]) > .rate:last-child{ border-bottom:0; }
}

/* ── 1 Oct 2026 · Contact (multi-page step 7; 01 §2.6, §4.5, §5) ──────────────────────────────
   Phones: the heading, the line, Call · WhatsApp · Book, hours, then the rooms and home visits.
   Desktop: how to reach her on the left, staying in view; the places on the right. */
.ct-grid{ display:grid; row-gap:var(--s-7); }
.ct-reach h1{ font-size:var(--t-h2); font-weight:300; }
.ct-reach .lede a{ white-space:nowrap; }
.ct-hours{ margin-top:var(--s-6); }
/* 12 Nov 2026: the message form on Contact */
.ct-msg-in{ max-width:36rem; }
.ct-msg-in h2{ font-size:var(--t-h3, 1.75rem); font-weight:300; }
.ct-msg-in > p{ margin-top:var(--s-3); }
.ct-msg form{ margin-top:var(--s-5); }
.ct-msg .news-msg{ margin-top:var(--s-4); font-size:var(--t-body); }
.ct-msg .news-msg:empty{ margin-top:0; }
.ct-hours p{ margin-top:var(--s-2); }
.ct-page h2.label, .ct-visits h2.label{ display:block; margin-bottom:var(--s-3); }
.ct-places .places{ margin-top:var(--s-3); }
.ct-visits .visits-grid{ margin-top:var(--s-3); }
@media (min-width:1080px){
  .ct-grid{ grid-template-columns:minmax(0,5fr) minmax(0,7fr); column-gap:var(--s-8); align-items:start; }
  .ct-reach{ position:sticky; top:calc(72px + var(--s-6)); }
}

/* ── 1 Oct 2026 · Gifts (multi-page step 7; 01 §2.7, §4.6) ───────────────────────────────────
   §08 and §10 of the one page: the certificate, the smallest card, three suggested gifts, how it
   works, buying from abroad, then her honey, candles and gift sets. */
.gf-page h1{ font-size:var(--t-h2); font-weight:300; }
.gf-page .gift-intro .btn{ margin-top:var(--s-5); }
.gf-steps{ display:grid; gap:var(--s-5); margin-top:var(--s-6); }
.gf-steps li{ display:grid; grid-template-columns:2.25rem minmax(0,1fr); align-items:baseline;
  border-top:var(--rule-hair); padding-top:var(--s-4); }
.gf-steps .label{ color:var(--honey-800); }
.gf-abroad{ margin-top:var(--s-6); }
@media (min-width:1080px){ .gf-steps{ grid-template-columns:repeat(3, minmax(0,1fr)); column-gap:var(--s-7); } }
.gf-how{ margin-top:var(--s-7); }

/* ── 1 Oct 2026 · Privacy (multi-page step 7; 01 §2.8, §4.7) ──────────────────────────────────
   The four parts open, in one readable column, with her real photograph beside them on desktop. */
.pv-grid{ display:grid; row-gap:var(--s-7); }
.pv-text h1{ font-size:var(--t-h2); font-weight:300; }
.pv-part{ margin-top:var(--s-6); }
.pv-part h2{ font-size:var(--t-h3); }
.pv-part p{ margin-top:var(--s-3); }
.pv-act{ margin-top:var(--s-6); font-size:var(--t-small); color:var(--ink-70); }
.pv-photo{ margin:0; max-width:420px; }
@media (min-width:1080px){
  .pv-grid{ grid-template-columns:minmax(0,7fr) minmax(0,5fr); column-gap:var(--s-8); align-items:start; }
  .pv-photo{ max-width:none; position:sticky; top:calc(72px + var(--s-6)); }
}
@media (min-width:1080px){
  /* the footer's closing notes share one row, and the footer loses some padding: it was still half
     empty rows at 1440, which short pages (Privacy) could not carry under whitespace W2 */
  .ftr.pad-tight{ padding-block:var(--s-6); }
  /* 27 Oct 2026: the email list takes row 2, so the rule and the notes move down to 3 and 4 */
  .ftr .ftr-news{ grid-row:2; }
  .ftr .ftr-rule{ grid-row:3; }
  .ftr .ftr-note{ grid-row:4; margin-top:0; }
  .ftr .ftr-note:last-child{ grid-column:1; }
  .ftr .ftr-note:not(:last-child){ grid-column:2 / -1; }
}

/* 1 Oct 2026 · Home slimmed to teasers (multi-page step 7e): the six massages as links into Treatments */
.treat-links{ display:flex; flex-wrap:wrap; gap:var(--s-3); margin-top:var(--s-5); }
.treat-links a{ display:inline-flex; align-items:center; min-height:var(--tap-min); padding:0 var(--s-4);
  border:1px solid var(--v-150); color:var(--ink); text-decoration:none; font-family:var(--font-display);
  font-size:1.125rem; }
.treat-links a:hover{ background:var(--v-50); }

/* 1 Oct 2026 · the opening photograph is Sam welcoming you in, with her signature logo above the
   headline. On portrait screens the tall picture keeps her face in the top third, clear of the words. */
/* `.hero img` sizes the photograph (full width, 82svh, the breath); the logo opts out of all of it */
.hero .hero-panel .hero-logo{ display:block; width:200px; height:auto; min-height:0; margin:0 0 var(--s-5);
  object-fit:contain; filter:none; animation:none; transform:none; }
@media (min-width:1080px){ .hero .hero-panel .hero-logo{ width:260px; } }
@media (max-aspect-ratio: 1/1){ .hero img{ object-position:50% 12%; } }

/* 1 Oct 2026 · phone rhythm (Craig: "work on the white spaces on both mobile and desktop"). Measured at
   390: empty bands of 144–196px where sections meet, about a quarter of a phone screen. The minimums come
   down; the desktop maximums from 04_DESKTOP_SPACING.md are unchanged. whitespace-check.py W4 holds it. */
:root{ --pad-tight: clamp(32px, 6vw, 64px); --pad-normal: clamp(48px, 5.5vw, 80px); --pad-open: clamp(48px, 6.5vw, 96px); }
