/* ============================================================
   neoSKiN — foundation
   The one place tokens live. Loaded first on every page; page
   stylesheets (treatment.css, shop.css, the inline block in
   index.html) layer on top and only ever add page-specific work.

   Palette: PUTTY, INK, PINE, SAGE.
     Putty is the field. Ink carries every word (floor #4A443B).
     Pine anchors dark moments. Sage is the signal: it appears
     only where something acts or is alive.
   Radius: interactive = pill, panels = 18px.
   ============================================================ */

:root{
  /* ---- ground & surface ---- */
  --bg:        #EFE9DE;   /* putty, the field */
  --bg-2:      #FDFBF6;   /* bone, raised surface */
  --bg-3:      #F9F5EC;   /* putty deep, alt field */
  /* Darkened 11 Aug 2026 to match the homepage's film-hero greens —
     the lighter #2E3226/#22261E read as a different brand next to the
     new home chapters. Ratios only improve (pine is also an ink). */
  --pine:      #1F231B;   /* anchor for dark moments (home chapter green) */
  --pine-deep: #191D16;   /* deepest ground (film-hero pine) */
  --bone:      #F7F4EE;
  --bone-dim:  rgba(247,244,238,.78);
  --salmon:    #E7CBB9;
  /* the film-hero accents, from the approved direction study
     (neoskin-hero-directions/motion-video.html). Blush is display
     only, on the hero's pine ground; blush-deep is its stop for
     light grounds if the accent ever travels. Not for body text. */
  --blush:      #CF8B7D;
  --blush-deep: #9D5F55;
  --hero-pine:  #191D16;

  /* ---- ink ---- */
  --ivory:     #211D18;   /* primary text */
  --ivory-dim: #4A443B;   /* the reading floor — never go lighter */
  --ivory-mute:#4A443B;

  /* ---- sage, split by job so each stop clears AA ---- */
  /* --champagne is the brand sage. Against both putty and pine it lands
     near 3.2:1 — fine for display type, short of AA for small text on
     either ground — so labels get their own stop per ground. */
  --champagne:   #7C8370; /* the signal — display sizes only */
  --sage-label:  #4E5442; /* small labels on LIGHT grounds (6.5:1 on putty) */
  --sage-on-dark:#A3AD92; /* small labels on PINE grounds  (5.3:1 on pine) */
  --sage-fill:   #858C79; /* button fills, AA against ink */
  --champagne-soft: rgba(124,131,112,.16);

  --hairline:  rgba(33,29,24,.18);
  --hairline-2:rgba(33,29,24,.10);

  /* ---- radius ---- */
  --radius-panel: 18px;
  --radius-card:  22px;
  --radius-sm:    12px;
  --radius: var(--radius-panel);   /* legacy alias */

  /* ---- elevation: warm ink shadows, never grey ---- */
  /* ---- glass buttons ----
     The tint is the lever, not the alpha. Sitting at the pine/sage
     midpoint #4E5346 the fill had to reach .85 to keep bone above
     4.5:1 on the light grounds, and at 15% see-through it read as a
     flat colour rather than as glass. Moving the tint down the same
     line to #2D3128 — 15% of the way from pine toward sage, so still
     between the two — buys the transparency back:

       #4E5346 needs .85   (4.90 worst case)
       #2D3128 needs .70   (4.73 worst case)

     Twice as much ground shows through at the same legibility. The
     rest of the glass is the sheen and streaks on .btn::before and a
     heavier frost here. */
  --glass-tint:  45,49,40;
  --glass-fill:  rgba(45,49,40,.70);
  --glass-hover: rgba(45,49,40,.80);
  --glass-edge:  rgba(247,244,238,.30);
  --glass-edge-strong: rgba(247,244,238,.44);
  --glass-blur:  blur(22px) saturate(1.5);

  --e1: 0 1px 2px rgba(33,29,24,.06), 0 4px 10px rgba(33,29,24,.05);
  --e2: 0 2px 4px rgba(33,29,24,.07), 0 10px 26px rgba(33,29,24,.09);
  --e3: 0 4px 8px rgba(33,29,24,.08), 0 20px 44px rgba(33,29,24,.14);
  --e4: 0 8px 16px rgba(33,29,24,.10), 0 34px 70px rgba(33,29,24,.20);
  --inner-lip: inset 0 1px 0 rgba(255,255,255,.55);

  /* ---- motion: four durations, three curves, nothing else ----
     tap    the pointer gets an answer before it registers as lag
     state  a considered change of state
     enter  something arriving on screen
     amb    image and ambient drift you are not meant to notice   */
  --dur-tap:   160ms;
  --dur-state: 320ms;
  --dur-enter: 620ms;
  --dur-amb:  1100ms;
  --ease-out:  cubic-bezier(.16,1,.3,1);    /* decelerate hard: arrivals */
  --ease-soft: cubic-bezier(.32,.72,0,1);   /* gentler: state changes */
  --ease-rise: cubic-bezier(.22,1,.36,1);   /* mask-rise for type */

  /* ---- spacing: one 8px ramp ---- */
  --s1:4px; --s2:8px;  --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:48px; --s8:64px; --s9:96px; --s10:128px;

  /* ---- section rhythm: three sizes, used everywhere ---- */
  /* Section rhythm. Widened 2026-07: bands were sitting close enough
     together that the page read as a dense stack rather than a
     sequence of separate ideas. Mobile floors stay modest so the
     extra air costs scroll only where there is room for it. */
  --sec-sm: clamp(64px, 11vh, 120px);
  --sec-md: clamp(88px, 16vh, 176px);
  --sec-lg: clamp(112px,21vh, 232px);
  --head-gap: clamp(36px,5vh,58px);   /* section head → its content */

  /* ---- type scale ---- */
  --t-6: clamp(2.75rem,6.2vw,5.5rem);   /* page-defining display */
  --t-5: clamp(2.15rem,4.4vw,3.75rem);  /* section display */
  --t-4: clamp(1.75rem,3.2vw,2.65rem);  /* section heading */
  --t-3: clamp(1.45rem,2.4vw,1.95rem);  /* panel heading */
  --t-2: clamp(1.15rem,1.5vw,1.35rem);  /* subhead */
  --t-1: clamp(1.02rem,1.3vw,1.18rem);  /* lede */
  --t-0: 1rem;                          /* body */
  --t-s: .9rem;                         /* small print */
  --t-label: .75rem;                    /* mono label — 12px floor. Below this
                                           JetBrains Mono renders uneven stems and
                                           gaps at common DPRs. */

  --nav-h: 72px;
  /* z scale: 1 content lift, 10 sticky nav, 20 overlay menu */

  /* the house arrow, as a background image so a bordered ring can
     carry it without the mask eating the ring */
  --arrow-ne-ink: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none'%3E%3Cpath d='M2 12L12 2M12 2H4M12 2v8' stroke='%23211D18' stroke-width='1.4'/%3E%3C/svg%3E");
  --arrow-ne-bone:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none'%3E%3Cpath d='M2 12L12 2M12 2H4M12 2v8' stroke='%23F7F4EE' stroke-width='1.4'/%3E%3C/svg%3E");
}

/* ============================================================
   reset
   ============================================================ */
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:auto}
/* anchor jumps glide, but only for users who want motion. Expressed in CSS
   so it follows the OS setting live rather than a value read once at load. */
@media (prefers-reduced-motion:no-preference){html{scroll-behavior:smooth}}
body{
  background:var(--ground,var(--bg));
  color:var(--ivory);
  font-family:'Bespoke Sans',-apple-system,'Helvetica Neue',sans-serif;
  font-weight:300;
  font-size:17px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  /* `clip`, not `hidden`. overflow-x:hidden makes body a scroll container,
     but the page actually scrolls on <html> — so any position:sticky child
     has no scrollport to respond to and silently stops sticking. That broke
     the portal and the arrival stages. `clip` still prevents sideways
     scroll but creates no scroll container, so sticky keeps working. */
  overflow-x:clip;
  transition:background-color var(--dur-amb) cubic-bezier(.4,0,.2,1);
}
/* Safari < 16 has no overflow:clip — fall back to hiding on the element
   that already is the scrollport, where sticky is unaffected. */
@supports not (overflow-x: clip){
  html{overflow-x:hidden}
  body{overflow-x:visible}
}
img{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:none;cursor:pointer}
::selection{background:var(--champagne);color:var(--bone)}
:focus-visible{outline:1.5px solid var(--champagne);outline-offset:3px;border-radius:4px}

/* ============================================================
   type
   One heading scale. Every heading on the site obeys it.
   ============================================================ */
h1,h2,h3,h4{
  font-family:'Expose',sans-serif;
  font-weight:400;
  letter-spacing:-.022em;
  line-height:1.1;
  text-wrap:balance;
  font-optical-sizing:auto;
}
.display-xl,.display-lg,.display-md{
  font-family:'Expose',sans-serif;
  font-weight:500;letter-spacing:-.022em;line-height:1.06;
}
.display-xl{font-size:var(--t-6)}
.display-lg{font-size:var(--t-5)}
.display-md{font-size:var(--t-4)}

.lede{
  color:var(--ivory-dim);max-width:34em;
  font-size:var(--t-1);line-height:1.65;
}
p,.lede,.sub,.prose p{text-wrap:pretty}
.accent-i{font-style:normal;color:var(--champagne)}

/* mono is the label voice: eyebrows, prices, counts, meta */
.mono,.eyebrow{
  font-family:'JetBrains Mono',monospace;
  text-transform:uppercase;
  letter-spacing:.18em;
  /* letter-spacing is applied after the final glyph as well, which
     leaves a phantom .18em on the right and pushes every label off
     centre inside its own box. Pull it back. */
  margin-right:-.18em;
  font-size:var(--t-label);
  font-weight:500;
  font-variant-ligatures:none;
  -webkit-font-smoothing:antialiased;
}
.eyebrow{display:block;color:var(--sage-label);margin-bottom:var(--s4)}
.on-pine .eyebrow,.eyebrow.on-pine{color:var(--sage-on-dark)}

/* the section head pattern: eyebrow → heading → lede, one rhythm */
.section-head,.start-head,.treatments-head,.tech-head,.products-head{
  margin-bottom:var(--head-gap);
}
.section-head{max-width:34em}
.section-head h2{font-size:var(--t-4);margin-bottom:var(--s3)}
.section-head p{color:var(--ivory-dim)}

/* prices, counts and stats never reflow as digits change */
.t-price,.stat b,.price-row span,.deck-count,.p-price,.pk-price,
.rail-item span,.c-chip{font-variant-numeric:tabular-nums}

.container{width:min(1240px,calc(100% - 48px));margin-inline:auto}
section{position:relative}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* ============================================================
   buttons
   ============================================================ */
.btn{
  display:inline-flex;align-items:center;gap:14px;
  border-radius:999px;padding:15px 8px 15px 26px;
  /* the label must never wrap - a two-line pill with the arrow
     stranded to one side is what a broken button looks like */
  white-space:nowrap;
  background:var(--glass-fill);color:var(--bone);
  border:1px solid var(--glass-edge);
  -webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);
  box-shadow:var(--e2), inset 0 1px 0 rgba(255,255,255,.22);
  font-family:'JetBrains Mono',monospace;
  font-size:.76rem;font-weight:500;letter-spacing:.08em;text-transform:uppercase;
  transition:transform var(--dur-state) var(--ease-soft),
             box-shadow var(--dur-state) var(--ease-soft),
             background var(--dur-state) var(--ease-soft);
}
/* The 26px/8px split above exists to make room for the arrow orb on
   the right. Buttons without an orb inherited it anyway and sat 18px
   left of centre — measured 26px of space on the left against 8px on
   the right. Those get an even pill.

   The negative right margin on the label is the same correction the
   .mono rule already makes: letter-spacing is applied after the final
   glyph too, leaving a phantom .08em on the right of every label. */
/* the glass itself: a low sheen raked across the pill, condensation
   streaks at very low alpha, and a bright lip along the top edge. Kept
   off `background` on purpose — a dozen rules around the site repaint
   .btn's background, and every one of them would erase this. */
.btn{position:relative;isolation:isolate;overflow:hidden}
.btn::before{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:
    linear-gradient(135deg, rgba(255,255,255,.26) 0%, rgba(255,255,255,.06) 38%, transparent 62%),
    repeating-linear-gradient(97deg,
      rgba(255,255,255,.07) 0 1px,
      transparent 1px 7px,
      rgba(255,255,255,.04) 7px 8px,
      transparent 8px 17px);
  mix-blend-mode:screen;
}
.btn:not(:has(.btn-orb)){padding-inline:26px;justify-content:center}
.btn:not(:has(.btn-orb)) .btn-label,
.btn:not(:has(.btn-orb)){margin-right:0}
.btn:not(:has(.btn-orb)){text-indent:.08em}
.btn:hover{transform:translateY(-2px);background:var(--glass-hover);
  box-shadow:var(--e3), inset 0 1px 0 rgba(255,255,255,.22)}
.btn:active{transform:translateY(0) scale(.975);box-shadow:var(--e1)}
.btn-orb{
  width:34px;height:34px;border-radius:999px;flex:none;
  background:rgba(247,244,238,.16);
  display:flex;align-items:center;justify-content:center;
  transition:transform var(--dur-state) var(--ease-soft);
}
.btn-orb svg{width:13px;height:13px}
.btn:hover .btn-orb{transform:translate(2px,-1px) scale(1.06)}
/* .btn-ghost was an outline pill. Outlined CTAs convert worse than
   filled ones, so there are no unfilled buttons on this site any more:
   the class survives so the markup does not have to change, but it now
   renders as the same glass as every other button. Only the padding is
   still its own. */
.btn-ghost{
  background:var(--glass-fill);color:var(--bone);
  border:1px solid var(--glass-edge);
  padding:16px 28px;box-shadow:var(--e2), inset 0 1px 0 rgba(255,255,255,.22);
}
.btn-ghost:hover{background:var(--glass-hover);border-color:var(--glass-edge-strong);
  box-shadow:var(--e3), inset 0 1px 0 rgba(255,255,255,.22)}
.btn-champagne{background:var(--glass-fill);color:var(--bone)}
.btn-champagne .btn-orb{background:rgba(247,244,238,.16)}

/* label roll: the second copy waits below the mask */
/* inline-grid so the two <i> of the roll-up hover can share cell 1/1.
   Without an explicit column flow, a label made of a text node plus a
   <span> becomes TWO grid items and stacks into two rows - which is
   what put "Book / consultation" on two lines in the nav and left the
   arrow stranded. Explicitly placed children still stack. */
.btn .btn-label{display:inline-grid;grid-auto-flow:column;overflow:hidden;vertical-align:baseline}
.btn .btn-label i{grid-area:1/1;font-style:normal;will-change:transform;
  transition:transform var(--dur-state) var(--ease-rise)}
.btn .btn-label i:last-child{transform:translateY(110%)}
@media (hover:hover) and (pointer:fine){
  .btn:hover  .btn-label i:first-child,.btn:focus-visible .btn-label i:first-child{transform:translateY(-110%)}
  .btn:hover  .btn-label i:last-child, .btn:focus-visible .btn-label i:last-child {transform:translateY(0)}
}

/* underline draws in from the left, retracts to the right */
.link-line{
  position:relative;display:inline-block;
  font-family:'JetBrains Mono',monospace;
  font-size:.75rem;font-weight:500;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ivory-dim);
  transition:color var(--dur-state) var(--ease-soft);
}
.link-line::after{
  content:"";position:absolute;left:0;bottom:-3px;height:1px;width:100%;
  background:var(--champagne);transform:scaleX(0);transform-origin:right;
  transition:transform var(--dur-state) var(--ease-out);
}
.link-line:hover{color:var(--ivory)}
.link-line:hover::after{transform:scaleX(1);transform-origin:left}

/* ============================================================
   nav — shared chrome. Pages own `position` (fixed on the
   homepage, sticky on subpages) and the ground-tint behaviour.
   ============================================================ */
.nav-inner{
  display:flex;align-items:center;justify-content:space-between;
  width:min(1360px,calc(100% - 48px));margin-inline:auto;
  position:relative;z-index:1;
}
.logo{display:inline-flex;align-items:center;line-height:0;
  --lg-badge:var(--bone);   /* the neo tile */
  --lg-neo:var(--pine);     /* letters inside the tile */
  --lg-word:var(--bone);    /* SKiN */
}
.logo-svg{height:26px;width:auto;display:block}
.logo-svg .lg-a{fill:var(--lg-badge)}
.logo-svg .lg-b{fill:var(--lg-neo)}
.logo-svg .lg-c{fill:var(--lg-word)}
/* On light grounds the mark flips to ink — bone-on-putty is
   invisible. nav-ground.js sets .on-dark wherever the sampled
   ground is dark, and the bone defaults above win there. */
.nav:not(.on-dark) .logo{
  --lg-badge:var(--ivory);
  --lg-neo:var(--bg);
  --lg-word:var(--ivory);
}
.nav-links{display:flex;gap:34px;align-items:center}
.nav-links a{
  font-family:'JetBrains Mono',monospace;
  font-size:var(--t-label);letter-spacing:.1em;text-transform:uppercase;
  color:var(--ivory-dim);
  transition:color var(--dur-state);
}
.nav-links a:hover{color:var(--ivory)}

/* the nav bar paints nothing itself; ::before carries tint + blur
   and dissolves downward, so there is no hard seam over imagery */
.nav::before{
  content:"";position:absolute;inset:0 0 -16px 0;z-index:0;pointer-events:none;
  background:linear-gradient(to bottom,
    var(--nav-tint, rgba(239,233,222,.80)) 0%,
    var(--nav-tint, rgba(239,233,222,.80)) 52%,
    transparent 100%);
  -webkit-backdrop-filter:blur(18px) saturate(145%);
          backdrop-filter:blur(18px) saturate(145%);
  -webkit-mask-image:linear-gradient(to bottom,#000 0%,#000 58%,transparent 100%);
          mask-image:linear-gradient(to bottom,#000 0%,#000 58%,transparent 100%);
}
.nav.nav-solid::before{
  background:var(--nav-solid,#22261E);inset:0;
  -webkit-mask-image:none;mask-image:none;
  -webkit-backdrop-filter:none;backdrop-filter:none;
}
.nav.on-dark .nav-links a{color:var(--bone-dim)}
.nav.on-dark .nav-links a:hover{color:var(--bone)}
.nav.on-dark .logo{--lg-badge:var(--bone);--lg-neo:var(--pine);--lg-word:var(--bone)}
.nav.on-dark .menu-toggle span{background:var(--bone)}

@media (hover:hover) and (pointer:fine){
  .nav-links a,.foot-grid ul a{position:relative}
  .nav-links a::after,.foot-grid ul a::after{
    content:"";position:absolute;left:0;right:0;bottom:-3px;height:1px;
    background:currentColor;transform:scaleX(0);transform-origin:left;
    transition:transform var(--dur-state) var(--ease-out);
  }
  .nav-links a:hover::after,.foot-grid ul a:hover::after{transform:scaleX(1)}
}

/* ============================================================
   footer — one footer, every page
   ============================================================ */
/* The footer sits on its own surface rather than on the putty field.
   It was the last block on every page where copy ran straight over
   the ground, and the design system always specified an anchored
   footer - it just never got applied. */
footer{
  background:var(--bg-2);
  border-top:1px solid var(--hairline-2);
  padding:var(--s8) 0 var(--s7);
}
.foot-sign{overflow:hidden;margin-bottom:var(--s7);pointer-events:none}
/* The real logo, not Expose set to spell the name. Masked rather than
   dropped in as an <img> at low opacity: the mask takes the fill from
   the same rgba(33,29,24,.07) ink the typed version used, so the ghost
   is identical on any ground and needs no second colour-matched asset. */
/* Capped. width:100% against an 8000x2000 asset makes the ghost a
   quarter as tall as the container is wide — 310px at 1240, which
   pushed the footer to 1092px, or 1.27 screens. A watermark should
   sit under the footer, not be the footer. */
.foot-sign .foot-mark{
  display:block;width:100%;aspect-ratio:8000/2000;
  max-height:clamp(64px,9vw,132px);
  background:rgba(33,29,24,.07);
  -webkit-mask:url("img/logo-black.png") no-repeat left center;
          mask:url("img/logo-black.png") no-repeat left center;
  -webkit-mask-size:contain;mask-size:contain;
  transform:translateY(4%);
}
.foot-sign span{
  display:block;font-family:'Expose','Bespoke Sans',sans-serif;font-weight:500;
  font-size:clamp(4.5rem,15.5vw,15rem);line-height:.82;letter-spacing:.01em;
  color:rgba(33,29,24,.07);user-select:none;transform:translateY(10%);white-space:nowrap;
}
.foot-grid{display:grid;grid-template-columns:1.4fr 1fr 1fr 1fr;gap:var(--s7);margin-bottom:var(--s8)}
.foot-grid h2,.foot-grid h3{
  font-family:'JetBrains Mono',monospace;font-weight:400;
  font-size:.75rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ivory-mute);margin-bottom:var(--s4);
}
.foot-grid ul{list-style:none;display:flex;flex-direction:column;gap:2px}
.foot-grid a,.foot-grid p{font-size:.93rem;color:var(--ivory-dim)}
.foot-grid a:hover{color:var(--ivory)}
.foot-open{
  font-family:'JetBrains Mono',monospace;font-size:.75rem;font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;color:var(--sage-label);
  margin-bottom:var(--s3);
}
.logo-foot{--lg-badge:var(--ivory);--lg-neo:var(--bone);--lg-word:var(--ivory)}
.logo-foot .logo-svg{height:32px}
.foot-base{
  display:flex;justify-content:space-between;gap:var(--s5);flex-wrap:wrap;
  border-top:1px solid var(--hairline-2);padding-top:var(--s5);
  font-size:.82rem;color:var(--ivory);
}
/* minmax(0,1fr), not 1fr — the same trap already documented for .band-split
   in chrome.css. A bare 1fr is minmax(auto,1fr) and keeps a min-content
   floor, so the address line in the first cell ("Advanced skin and laser
   clinic. 175 Wells Street, South Melbourne.") held column one open at
   256px, crushed column two to 88px and ran the row to 360px inside a 327px
   container. That 33px overflow put 36 of 41 pages into a sideways scroll at
   375px; the only pages measuring clean were the handful that do not carry
   this footer layout. */
@media (max-width:760px){
  .foot-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--s6) var(--s5)}
}
/* Single column here cost 696px of footer on a 375px phone — more
   height than any content band. Two columns hold every item without
   wrapping and halve it. */
@media (max-width:430px){
  .foot-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--s5) var(--s4)}
}

/* ============================================================
   entrance grammar
   One definition of "arriving". Blocks fade and rise; feature
   panels additionally resolve out of a shallow blur, like a
   focus pull. Headings rise word by word out of a mask.
   ============================================================ */
.rv{opacity:1}
@media (prefers-reduced-motion:no-preference){
  .rv{opacity:0;transform:translate3d(0,18px,0)}
  .rv.in{
    opacity:1;transform:none;
    transition:opacity var(--dur-enter) var(--ease-out),
               transform var(--dur-enter) var(--ease-out);
  }
  /* reserved for large panels — a blur on every revealing element
     is expensive and reads as mush rather than depth */
  .rv-feature{filter:blur(4px)}
  .rv-feature.in{
    filter:none;
    transition:opacity var(--dur-enter) var(--ease-out),
               transform var(--dur-enter) var(--ease-out),
               filter var(--dur-enter) var(--ease-out);
  }
}

/* word mask-rise. The injected <i> is a transform-only mask element,
   NOT emphasis: reset the UA italic or every split heading renders
   as a synthesised oblique. */
.wl .w{display:inline-block;overflow:hidden;vertical-align:baseline;
  /* generous clip box, netted out by margins: PP Editorial Italic
     overshoots the em box (the f in particular) and the old tight
     mask sliced it. The rise offset below compensates. */
  padding:.18em .1em .24em .04em;margin:-.18em -.1em -.24em -.04em}
.wl .w>i{display:inline-block;font-style:normal}
/* the splitter's structural <i> must not flatten a real <em> accent —
   inside one, the italic (PP Editorial New Italic) carries through */
.wl em .w>i{font-style:italic}
.wl.rise .w>i{transform:translateY(140%);transition:transform var(--dur-enter) var(--ease-rise);will-change:transform}
/* drop the layer once it has landed — a permanent will-change costs
   memory and leaves faint compositing seams over photography */
.wl.rise.risen .w>i{transform:none;will-change:auto}

/* Page introduction — the ground lifts away as the masthead rises
   beneath it. Deliberately short: a veil that holds is just a page
   that feels slow. It starts clearing immediately and is gone in
   half a second, by which time the heading is already rising.
   The homepage opts out — it has the logo portal instead. */
.page-intro::after{
  content:"";position:fixed;inset:0;z-index:60;pointer-events:none;
  background:var(--bg);
  animation:introLift 520ms var(--ease-out) forwards;
}
@keyframes introLift{
  from{opacity:1}
  to  {opacity:0;visibility:hidden}
}

/* ============================================================
   motion polish
   Hover feedback was inheriting entrance timings, which reads as
   lag. Response snaps in at --dur-tap; the base durations still
   govern the settle-out, giving deliberate asymmetry. Fine
   pointers only, so touch never inherits a hover state.
   ============================================================ */
@media (hover:hover) and (pointer:fine){
  .btn:hover,.btn:hover .btn-orb,.btn:focus-visible,.btn:focus-visible .btn-orb,
  .btn:hover .btn-label i,.btn:focus-visible .btn-label i,
  .link-line:hover::after,.nav-links a:hover::after,
  .deck-btn:hover,.rail-item:hover,.filter:hover,
  .nav-panel a:hover{transition-duration:var(--dur-tap)}
}

/* ============================================================
   target size — WCAG 2.5.8, without touching the type scale
   ============================================================ */
.nav-links>a,.nav-item>a,
a[aria-label="neoSKiN home"]{display:inline-flex;align-items:center;min-height:44px}
.foot-grid a,.foot-base a,.crumb a,.link-line,.c-go,.rel-link,
a[href^="tel:"]{display:inline-block;padding-block:12px;margin-block:-12px}

/* ============================================================
   reduced motion — movement out, state changes kept.
   Colour and opacity still communicate; nothing travels.
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important;scroll-behavior:auto!important;
  }
  body{transition:background-color .4s linear}
  .wl.rise .w>i{transform:none;transition:none}
  .page-intro::after{display:none}
}

/* ============================================================
   GROUND — paper, not a flat fill

   The site read flat because every section was the same solid
   putty at the same width. One colour repeated eight times has no
   depth, whatever the headings do.

   Two layers, both site-wide, both free:
   1. fine grain, so the field reads as paper rather than a fill
   2. soft light, so the ground is lit rather than uniform

   Fixed attachment keeps the grain from scrolling with content,
   which is what stops it reading as a texture image and starts it
   reading as the stock the page is printed on.
   ============================================================ */
body{
  position:relative;
  /* keep --ground: nav-ground.js tints the field per section and a flat
     var(--bg) here would kill that */
  background-color:var(--ground,var(--bg));
}
/* The three field washes used to live on body with background-attachment:
   fixed. That anchors them to the viewport, which is the look we want — but
   a fixed attachment cannot be composited, so the browser repainted the whole
   viewport on every scroll frame. Over a large flat dark area (the pine cross
   band) that showed as the green dragging and tearing behind the scroll.

   Same picture, different mechanism: one fixed layer, promoted to its own
   compositor layer, so scrolling moves pixels instead of repainting them.
   z-index 0 keeps it under all content (body > * is z-index 1). */
html::before{
  content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
  background-image:
    radial-gradient(120% 80% at 8% 0%,    rgba(255,255,255,.72), transparent 58%),
    radial-gradient(100% 70% at 100% 22%, rgba(124,131,112,.10), transparent 60%),
    radial-gradient(90% 60% at 50% 100%,  rgba(33,29,24,.05),    transparent 62%);
  transform:translateZ(0);
}
body::before{
  content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
  opacity:.5;mix-blend-mode:multiply;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.4'/%3E%3C/svg%3E");
}
body > *{position:relative;z-index:1}

/* ============================================================
   BUTTONS ON DARK GROUNDS

   This block used to flip the primary button to a solid bone fill,
   because --sage-fill was only 3.76:1 against pine and the button
   died into the ground. The glass fill does not have that problem:
   bone on it measures 8.05:1 over pine against 4.90 over putty, so
   one treatment now carries every ground and the inversion is gone.

   What dark grounds still need is an edge. The blur has nothing
   bright to pick up down here, so the button is separated by lifting
   the rim rather than by repainting the fill.
   ============================================================ */
.on-pine .btn,
.band-sec--pine .btn,
.offer .btn,
.hero .btn,
.quiz-bar .btn,
.quiz-overlay .btn,
footer .btn{
  border-color:var(--glass-edge-strong);
  box-shadow:var(--e2), inset 0 1px 0 rgba(255,255,255,.28);
}
.on-pine .btn:hover,
.band-sec--pine .btn:hover,
.offer .btn:hover,
.hero .btn:hover,
.quiz-bar .btn:hover,
.quiz-overlay .btn:hover,
footer .btn:hover{
  background:var(--glass-hover);
  box-shadow:var(--e3), 0 0 0 1px rgba(247,244,238,.25), inset 0 1px 0 rgba(255,255,255,.28);
}
/* The orb used to carry pine ink, because the button under it was a
   solid bone fill. The fill is glass now and the label is bone, so a
   pine arrow would be the one dark mark on a light-on-dark button.
   It rides the label instead. */
.on-pine .btn .btn-orb,
.band-sec--pine .btn .btn-orb,
.offer .btn .btn-orb,
.hero .btn .btn-orb,
.quiz-bar .btn .btn-orb,
.quiz-overlay .btn .btn-orb,
footer .btn .btn-orb{
  background:rgba(247,244,238,.16);
  border-color:var(--glass-edge-strong);
  color:currentColor;
}
/* on dark grounds the ghost used to drop to a bare outline. Filled there
   too — same rule as everywhere else, just a brighter rim. */
.on-pine .btn-ghost,
.band-sec--pine .btn-ghost,
.hero .btn-ghost,
.offer .btn-ghost{
  background:var(--glass-fill);
  color:var(--bone);
  border-color:var(--glass-edge-strong);
}
.on-pine .btn-ghost:hover,
.band-sec--pine .btn-ghost:hover,
.hero .btn-ghost:hover,
.offer .btn-ghost:hover{
  background:var(--glass-hover);
  border-color:rgba(247,244,238,.6);
  box-shadow:var(--e3), inset 0 1px 0 rgba(255,255,255,.28);
}

/* ============================================================
   CHROME FIXES

   scroll-margin-top: the nav is fixed at 72px, so #concerns,
   #treatments and #assessment were landing underneath it. Every
   anchor target now clears the bar plus a little air.
   ============================================================ */
[id]{scroll-margin-top:calc(var(--nav-h) + 24px)}

/* Removes the 300ms double-tap delay and the grey flash on tap. */
a,button,summary,.filter,[role="button"]{
  touch-action:manipulation;
  -webkit-tap-highlight-color:rgba(124,131,112,.18);
}

/* Notched phones: the fixed bar and any full-bleed section need to
   respect the safe area or content sits under the notch and the
   home indicator. */
.nav{padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right)}
.hero{padding-bottom:max(clamp(56px,9vh,104px), env(safe-area-inset-bottom))}
footer{padding-bottom:max(var(--s7), env(safe-area-inset-bottom))}

/* Skip link: first focusable thing on the page, invisible until
   focused. */
.skip{
  position:absolute;left:-9999px;top:0;z-index:80;
  background:var(--bg-2);color:var(--ivory);
  padding:14px 20px;border:1px solid var(--hairline);
  border-radius:0 0 var(--radius-panel) 0;
  font-family:'JetBrains Mono',monospace;font-size:var(--t-label);
  letter-spacing:.14em;text-transform:uppercase;
}
.skip:focus{left:0}

/* number columns line up */
.price-row span,.rg-price,.p-price,.menu-card-price,.c-chip{
  font-variant-numeric:tabular-nums;
}

/* ============================================================
   AUDIT FIXES — 2026-07-30
   ============================================================ */

/* FOOTER SIZE.
   Measured 1032px at 375 (1.27 screens) and 644px at 1440 on every
   page. Three things were paying for that: a fixed 64px top pad, a
   64px gap under the link grid, and the ghost wordmark. Each is now
   proportional, so the footer costs about a third of a screen
   instead of more than a whole one on a phone. */
footer{padding:clamp(36px,5vh,64px) 0 clamp(28px,4vh,48px)}
.foot-sign{margin-bottom:clamp(20px,3vh,40px)}
.foot-grid{margin-bottom:clamp(28px,4vh,56px)}
.foot-sign .foot-mark{max-height:clamp(48px,6vw,96px)}

/* BUTTON LABEL CENTRING.
   .btn uses padding 15px 8px 15px 26px to leave room for the arrow
   orb on the right. Buttons with no orb inherited that and sat up to
   18px left of centre. Worse, .hero-actions .btn sets
   justify-content:space-between for the orb layout — on an orb-less
   button at 375 that threw the label 122px off. Both handled here. */
.btn:not(:has(.btn-orb)){
  padding-inline:26px;
  justify-content:center;
}
/* letter-spacing is applied after the final glyph too, leaving a
   phantom .08em on the right of every uppercase label */
.btn:not(:has(.btn-orb)) > .btn-label,
.btn:not(:has(.btn-orb)){text-indent:.08em}

/* NAV LINK CONTRAST — reverted, and left alone deliberately.
   An earlier attempt set .nav:not(.on-dark) .nav-links a to
   --sage-label. That assumed the nav always sits on a light ground.
   It does not: shop.html and every treatment page put it over the
   pine hero, where a dark label measured 1.1:1 — far worse than the
   2.17:1 it was meant to fix on the homepage.

   The correct fix is per-ground, which needs the .on-dark class to
   actually be set. nav-ground.js does that but is not loaded on any
   page, and it samples the DOM every frame, so it should not simply
   be switched on. Left as found rather than made worse. */


/* ============================================================
   ICON SYSTEM
   Sprite lives in assets/icons.svg. One symbol, referenced by
   <use>, inherits currentColor from whatever it sits inside — so
   the same glyph serves putty and pine without a second asset.

     <svg class="ic" aria-hidden="true">
       <use href="assets/icons.svg#i-bbl"/></svg>

   From /concerns or /treatments the href needs "../assets/…".
   If the icon IS the control, the name goes on the parent:
     <a href="tel:…" aria-label="Call the clinic"><svg class="ic"
        aria-hidden="true"><use href="…#i-phone"/></svg></a>
   ============================================================ */
.ic{
  width:var(--ic, 20px);
  height:var(--ic, 20px);
  flex:none;
  display:inline-block;
  vertical-align:-.18em;
  color:inherit;
  /* the stroke stays 1.5 device-independent at every size, so a
     40px feature icon is not four times heavier than a 20px inline
     one — which is what happens if you just scale the symbol. */
  overflow:visible;
}
.ic *{vector-effect:non-scaling-stroke}

/* four sizes, and only four */
.ic--sm{--ic:16px}     /* inside small print and mono labels */
.ic--md{--ic:20px}     /* default: buttons, list rows, nav */
.ic--lg{--ic:28px}     /* card headers */
.ic--xl{--ic:40px}     /* feature / section markers */

/* Optical alignment. An icon set on the 24 grid sits a touch high
   next to lowercase text at 17px; this is the correction, not a
   nudge someone forgot to remove. */
.btn .ic{margin-inline-end:.55em}

/* HOVER: the line redraws itself. Cheap — one property, compositor
   friendly — and it reads as craft rather than decoration because
   it only fires on deliberate hover, never on scroll.
   Opt in per component with .ic--draw; it is NOT global, because
   a page of simultaneously redrawing icons looks like a glitch. */
@media (prefers-reduced-motion:no-preference) and (hover:hover){
  .ic--draw *{
    stroke-dasharray:var(--len, 120);
    stroke-dashoffset:0;
    transition:stroke-dashoffset var(--dur-enter) var(--ease-out);
  }
  a:hover > .ic--draw *,
  button:hover > .ic--draw *,
  .card:hover .ic--draw *{
    stroke-dashoffset:var(--len, 120);
    animation:ic-draw var(--dur-enter) var(--ease-out) forwards;
  }
  @keyframes ic-draw{
    from{stroke-dashoffset:var(--len, 120)}
    to  {stroke-dashoffset:0}
  }
}

/* The icon must never be the only carrier of meaning (WCAG 1.4.1).
   Where a row is icon + label, the label stays visible at every
   breakpoint; where space forces the label out, it becomes an
   aria-label on the control, not a title attribute — title is not
   reliably announced and never reachable by touch. */


/* ============================================================
   PRODUCT RAIL — grab affordance
   Set by rail-auto.js once a rail is actually an endless
   carousel, so a shelf that does not overflow never advertises
   a drag it cannot perform.
   ============================================================ */
.rg-grid.is-grabbable{cursor:grab}
.rg-grid.is-grabbable:active{cursor:grabbing}
.rg-grid.is-dragging{
  cursor:grabbing;
  user-select:none;
  -webkit-user-select:none;
}
/* while the pointer is down the cards are the handle — not link
   targets, not image drag sources */
.rg-grid.is-dragging *{
  pointer-events:none;
  -webkit-user-drag:none;
}
/* the drag only reads as direct manipulation if nothing lags
   behind the pointer; kill the hover lift mid-drag */
.rg-grid.is-dragging .rg-item{transform:none !important}

/* footer contact rows: the email/phone links were 16px tall — below
   any usable touch target. Padding buys 44px without moving the grid. */
footer .ic-row a{display:inline-block;padding-block:12px;margin-block:-12px}
footer .foot-grid li a{
  display:inline-flex;
  align-items:center;
  min-width:44px;
  min-height:44px;
}
footer .foot-base a{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:44px;
  min-height:44px;
}
.crumb a,.ix-crumb a{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:44px;
  min-height:44px;
}

/* ============================================================
   FACELIFT 20260812 — script italic accents (PP Editorial New)
   on the closing word of headline copy, site-wide. Colour flips
   with the ground: blush-deep on light, blush on pine/ink.
   ============================================================ */
h1 em,h2 em,h3 em{
  font-family:'PP Editorial New','Iowan Old Style',Baskerville,'Times New Roman',serif;
  font-style:italic;font-weight:400;letter-spacing:-.015em;
  color:var(--blush);
}
.t-hero h1 em,.ix-hero h1 em,.tm-hero h1 em,.book-card h2 em{color:var(--blush)}

/* ============================================================
   FOOTER — CUT DOWN. 21 Aug 2026, site-wide.

   Measured before this block: 545px at 1440 (61% of the viewport)
   and 926px at 375 — a footer TALLER THAN THE PHONE SCREEN. It was
   all air: --s8/--s7 padding, --s7 grid gap, --s8 under the grid,
   and a 96px watermark.

   Also fixed here: .foot-open was specified at .75rem but rendering
   at 14.88px, because `.foot-grid p` (0,1,1) outranks `.foot-open`
   (0,1,0) and it is a <p> inside the grid. That is why the opening
   status was the loudest thing in the footer — bigger than the
   address it sits above, with .14em tracking stretched over 14.88px.
   ============================================================ */
footer{padding:clamp(34px,4.6vh,56px) 0 clamp(26px,3.6vh,42px)}
.foot-sign{margin-bottom:clamp(14px,2vh,26px)}
.foot-sign .foot-mark{max-height:clamp(28px,3.4vw,56px)}
.foot-grid{
  gap:clamp(22px,2.8vw,40px);
  margin-bottom:clamp(24px,3.2vh,40px);
}
.foot-grid h2,.foot-grid h3{margin-bottom:var(--s3)}
.foot-grid ul{gap:1px}
.foot-grid a,.foot-grid p{font-size:.88rem;line-height:1.62}
.foot-base{padding-top:var(--s4)}

/* the status line, back to the label it was written as */
.foot-grid .foot-open{
  font-size:.75rem;letter-spacing:.1em;
  margin-bottom:var(--s3);
}

@media (max-width:430px){
  .foot-grid a,.foot-grid p{font-size:.84rem}
  .foot-sign{margin-bottom:12px}
}

/* second pass: 826px at 375 was still 92% of the screen. The mass is
   the two long lists — 8 clinic links and 8 hours rows — so the cut
   has to come out of their leading, not out of more padding. */
@media (max-width:760px){
  .foot-grid{gap:20px var(--s4);margin-bottom:20px}
  .foot-grid h2,.foot-grid h3{margin-bottom:var(--s2)}
  .foot-grid ul{gap:0}
  .foot-grid a,.foot-grid p{line-height:1.48}
  .foot-grid .foot-open{margin-bottom:var(--s2)}
  .foot-base{padding-top:var(--s3);font-size:.78rem}
}

/* the address column's email is 30 characters against a 156px column at
   375, and it was spilling 78px straight across the Hours column —
   measured, not eyeballed. Nothing clipped it because the overflow was
   text, so the page still measured 375/375 and looked clean. */
.foot-grid .ic-row{min-width:0}
.foot-grid .ic-row a{min-width:0;overflow-wrap:anywhere}

/* ============================================================
   FOOTER WATERMARK — the "neo" slab. 21 Aug 2026.

   The mark is masked from logo-black.png, and a CSS mask takes the
   image's ALPHA. In that artwork the badge is a filled shape with
   the letters painted opaque WHITE on top of it — measured: 1303
   near-white opaque pixels sitting inside the badge. Alpha does not
   see white, only "present", so the letters filled in and the mark
   rendered as a grey rectangle followed by SKiN.

   logo-ghost.png is the same artwork with brightness mapped onto
   alpha, so the letters become holes: 7708 opaque pixels removed,
   11.8% of the mark. Antialiased edges fade on a ramp rather than
   being stencilled.
   ============================================================ */
.foot-sign .foot-mark{
  -webkit-mask-image:url("img/logo-ghost.png");
          mask-image:url("img/logo-ghost.png");
}

/* At 28px tall the reversed-out letters are roughly 14px of 7% ink and
   stop resolving — the mark goes back to being a slab. On a phone the
   nav already carries the logo, and the watermark is pure height on
   the one viewport where the footer was over a full screen. */
@media (max-width:520px){
  .foot-sign{display:none}
}
/* The mail icon was dropping to its own line once the address wrapped.
   flex-wrap did nothing here — measured: .ic-row computes to
   display:list-item, not flex. It is an <li> holding two inline-blocks,
   and an inline-block cannot split across lines, so the whole anchor
   moved down. Make the row a real flex line and let the text wrap
   inside its own box instead. */
.foot-grid .ic-row{display:flex;align-items:flex-start;gap:8px}
.foot-grid .ic-row .ic{flex:none;margin:.22em 0 0}
.foot-grid .ic-row a{min-width:0;overflow-wrap:anywhere}

/* Registered and trademark marks ride small and raised. At full size
   they sit on the baseline as loud as a letter — measured 93 of them
   across the site, 69 in visible copy. */
/* Expose carries no ® or ™ glyph. Without an explicit family the mark
   inherited the display face, fell back per-character, and drew a
   full-size parenthesised "(R)" beside the word. */
/* Bespoke Sans's own ® is drawn as a parenthesised R — measured 50px
   wide at 60px against system-ui's 36px circled mark. Naming the
   system face first is the only way to get the circle. */
sup.tm,sup.rmark{font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  font-size:.5em;line-height:0;vertical-align:super;letter-spacing:0;font-weight:400;font-style:normal}

/* ============================================================
   TRADEMARK MARKS — 25 Aug 2026.
   Expose carries no ® or ™ glyph, so on a display heading the
   browser fell back and drew a full-size "(R)" beside the word.
   The mark now renders in the body face at half size, raised,
   with its tracking cleared. Wrapped in the markup by a sweep
   over visible text only — attributes, JSON-LD and <title> were
   deliberately left alone, since markup cannot live in those.
   ============================================================ */
.rmark{
  font-family:'Bespoke Sans',-apple-system,'Helvetica Neue',sans-serif;
  font-size:.5em;line-height:0;vertical-align:super;
  letter-spacing:0;font-style:normal;font-weight:400;
}
