/* ==========================================================================
   MAIN  —  LAUREL & LINEN  ·  "PORCELAIN & JADE"
   --------------------------------------------------------------------------
   Layout and components. THERE ARE NO COLOUR VALUES IN THIS FILE — every
   colour comes from a custom property defined in tokens.css, so the whole
   site re-skins from six hex values rather than a search across 2,000 lines.

   Contents
     01  Reset & base
     02  Typography
     03  Layout utilities
     04  Buttons
     05  Photography  (.ph / the arch)
     06  Header, navigation, open-now pill
     07  Hero
     08  Credentials rule
     09  Split sections
     10  Service rail  (horizontal index)
     11  Ritual  (numbered process)
     12  Spread  (portfolio band)
     13  Pull quote
     14  Cards, lists, hours table
     15  CTA band
     16  Footer & mobile call bar
     17  Page headers  (inner pages)
     18  Services page  (index rail + priced ledger + tiers + FAQ)
     19  Portfolio page  (filters + staggered grid)
     20  Story page  (standards, facts, reviews)
     21  Visit page  (contact, map, booking request form)
     22  Ticker  (scrolling brand strip)
     23  Motion  (reveal, stagger, parallax, micro-interaction)
     24  Print
   ========================================================================== */


/* ═══════════════════════════════════════════════ 01 · RESET & BASE ═══════ */

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

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchored sections must clear the sticky header. */
  scroll-padding-top: calc(var(--head-h) + 1.5rem);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  background: var(--c-canvas);
  color: var(--c-ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* Paper grain over the whole page. Fixed, so it reads as the surface the
   site is printed on rather than something that scrolls with the content.
   pointer-events:none keeps it out of the way of every click. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background-image: var(--grain);
  opacity: .035;
  pointer-events: none;
  z-index: 1;
  mix-blend-mode: multiply;
}

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

a { color: inherit; text-decoration: none; }

button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }

:focus-visible {
  outline: 2px solid var(--c-jade);
  outline-offset: 3px;
  border-radius: 2px;
}

/* On pine the jade outline goes muddy — lift it. */
.site-foot :focus-visible,
.band :focus-visible,
.page-head :focus-visible,
.callbar :focus-visible { outline-color: var(--c-jade-light); }

::selection { background: var(--c-jade); color: var(--c-paper); }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip-link {
  position: absolute; top: -100%; left: var(--gutter); z-index: 200;
  background: var(--c-pine); color: var(--c-canvas);
  padding: .8rem 1.4rem; border-radius: 0 0 var(--r) var(--r);
  font-size: var(--fs-small); font-weight: 600;
}
.skip-link:focus { top: 0; }


/* ═════════════════════════════════════════════════ 02 · TYPOGRAPHY ═══════ */

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h);
  color: var(--c-ink);
  /* Fraunces is variable: force the fine, high-contrast optical cut at
     display sizes rather than letting the browser pick a text-weight one. */
  font-variation-settings: "opsz" 96;
}

h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); font-family: var(--font-body); font-weight: 600;
     letter-spacing: 0; font-variation-settings: normal; }

/* The one italic in the system. Used for a single accented word inside a
   heading and for pull quotes — never for a whole paragraph. */
.ital {
  font-style: italic;
  font-variation-settings: "opsz" 144;
  color: var(--c-jade);
}

p { text-wrap: pretty; }
p + p { margin-top: 1.05em; }
h2 + p, h3 + p, h2 + .lede { margin-top: 1.05rem; }

.lede {
  font-size: var(--fs-lede);
  line-height: 1.66;
  color: var(--c-ink-soft);
}

.fine  { font-size: var(--fs-fine); color: var(--c-ink-mute); line-height: 1.6; }
.small { font-size: var(--fs-small); color: var(--c-ink-mute); }

/* Tracked-out eyebrow label. Carries a short clay rule on its left so the
   eye finds the start of a section without needing a heavier heading. */
.label {
  display: flex;
  align-items: center;
  gap: .7rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-clay-ink);
  margin-bottom: 1.1rem;
}
.label::before {
  content: "";
  width: 26px; height: 1px;
  background: var(--c-clay);
  flex: none;
}
.label--center { justify-content: center; }
/* Doubled class on purpose. Every .label--pine in the markup is a <p> inside
   .page-head or .band, and both of those set `p { color: … }` at (0,1,1) —
   which beats a lone (0,1,0) modifier and leaves the eyebrow grey while its
   dash goes jade. (0,2,0) wins on class count and the pair stays in step. */
.label.label--pine { color: var(--c-jade-light); }
.label--pine::before { background: var(--c-jade-light); }

/* Body links inside prose. The underline retracts to the left on hover.
   :not(.btn) matters more than it looks — `.prose a` outranks `.btn`, so
   without it a primary button inside prose gets jade text on a jade fill and
   the label vanishes. .arrow-link is excluded for the same reason: it draws
   its own underline and would otherwise carry two. */
.prose a:not(.btn):not(.arrow-link),
.link {
  color: var(--c-jade);
  font-weight: 500;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 100% 1px;
  background-repeat: no-repeat;
  background-position: 0 100%;
  transition: background-size var(--dur-fast) var(--ease);
}
.prose a:not(.btn):not(.arrow-link):hover,
.link:hover { background-size: 0% 1px; }


/* ═══════════════════════════════════════════ 03 · LAYOUT UTILITIES ═══════ */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
  position: relative;
  z-index: 2;
}
.wrap--wide   { max-width: var(--wrap-wide); }
.wrap--narrow { max-width: var(--wrap-narrow); }

.section { padding-block: var(--section-y); position: relative; }
.section--tight { padding-block: calc(var(--section-y) * .55); }
.section--paper { background: var(--c-paper); }
.section--pine  { background: var(--c-pine); color: var(--c-on-pine); }
.section--pine h1, .section--pine h2, .section--pine h3 { color: var(--c-canvas); }
.section--pine .lede { color: var(--c-on-pine-mute); }

/* Hairline that spans the content column, used between major blocks. */
.rule { height: 1px; background: var(--c-line); border: 0; }
.rule--soft { background: var(--c-line-soft); }

/* Section heading block. --split puts supporting copy or a link opposite. */
.shead { margin-bottom: clamp(2.25rem, 4vw, 3.5rem); max-width: 46ch; }
.shead h2 + p { margin-top: 1rem; color: var(--c-ink-soft); }
.shead--center { margin-inline: auto; text-align: center; }
.shead--center .label { justify-content: center; }

.shead--split {
  max-width: none;
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: end;
}
.shead--split > :last-child { padding-bottom: .4rem; }

@media (max-width: 820px) {
  .shead--split { grid-template-columns: 1fr; align-items: start; }
}

/* Rotated marginal label. Pure decoration, and only shown where there is
   genuinely spare gutter — below 1400px it would collide with the text. */
.vlabel {
  display: none;
  position: absolute;
  top: var(--section-y); left: 1.5rem;
  writing-mode: vertical-rl;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  font-weight: 600;
  color: var(--c-ink-mute);
  opacity: .75;
}
@media (min-width: 1400px) { .vlabel { display: block; } }

.stack-sm > * + * { margin-top: .75rem; }
.stack    > * + * { margin-top: 1.5rem; }


/* ═══════════════════════════════════════════════════ 04 · BUTTONS ═══════ */

.btn {
  --btn-bg: transparent;
  --btn-fg: var(--c-ink);
  --btn-edge: var(--c-ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  padding: .92rem 1.7rem;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-edge);
  border-radius: var(--r-pill);
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: .015em;
  text-align: center;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.btn:hover { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }

/* A sheen that sweeps across the filled buttons on hover. It paints OVER the
   label rather than behind it — a pseudo-element cannot sit between an
   element's own background and its own text — so it is kept translucent and
   pointer-events:none. It brightens the label in passing; it never hides it,
   and it never intercepts the click. */
.btn--primary, .btn--pine { position: relative; overflow: hidden; }
.btn--primary::after, .btn--pine::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 32%, var(--c-sheen) 50%, transparent 68%);
  transform: translateX(-130%);
  transition: transform .75s var(--ease-out);
  pointer-events: none;
}
.btn--primary:hover::after,
.btn--pine:hover::after,
.btn--primary:focus-visible::after,
.btn--pine:focus-visible::after { transform: translateX(130%); }

.btn--primary { --btn-bg: var(--c-jade); --btn-fg: var(--c-paper); --btn-edge: var(--c-jade); }
.btn--primary:hover { --btn-bg: var(--c-jade-deep); --btn-edge: var(--c-jade-deep); }

.btn--ghost:hover { --btn-bg: var(--c-ink); --btn-fg: var(--c-canvas); }

.btn--pine { --btn-fg: var(--c-canvas); --btn-edge: var(--c-line-pine); }
.btn--pine:hover { --btn-bg: var(--c-canvas); --btn-fg: var(--c-pine); --btn-edge: var(--c-canvas); }

.btn--sm { padding: .6rem 1.15rem; font-size: var(--fs-fine); }
.btn--lg { padding: 1.05rem 2.1rem; font-size: var(--fs-body); }

.btn svg { width: 1.1em; height: 1.1em; flex: none; }
.btn--arrow svg { transition: transform var(--dur-fast) var(--ease); }
.btn--arrow:hover svg { transform: translateX(4px); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: .85rem;
  margin-top: 2rem;
}
.btn-row--center { justify-content: center; }

/* A text link that behaves like a button in a row of them. */
.arrow-link {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--c-jade);
  border-bottom: 1px solid var(--c-jade-edge);
  padding-bottom: .25rem;
  transition: gap var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.arrow-link:hover { gap: .9rem; border-bottom-color: var(--c-jade); }
.arrow-link svg { width: 1em; height: 1em; }


/* ═══════════════════════════════════════════════ 05 · PHOTOGRAPHY ═══════ */

/* Every photograph on the site is wrapped in .ph. It does three jobs:
     1. holds the aspect ratio so nothing reflows while images load,
     2. applies the house grade (tokens: --photo-grade + --wash), which is
        what makes stock from six different shoots look commissioned,
     3. draws a hairline inside the edge so pale photos still have a border
        against the linen canvas.
   .ph--live adds the hover behaviour: the grade lifts and the true colour
   of the photograph comes back. Use it on anything clickable. */
.ph {
  position: relative;
  overflow: hidden;
  background: var(--c-canvas-sunk);
  border-radius: var(--r);
  isolation: isolate;
}
.ph img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: var(--photo-grade);
  transition: filter var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.ph::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--wash);
  mix-blend-mode: multiply;
  opacity: 1;
  transition: opacity var(--dur) var(--ease);
  pointer-events: none;
}
/* Inset hairline, drawn above the wash so it is never tinted. */
.ph::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px var(--c-photo-edge);
  z-index: 2;
  pointer-events: none;
}

/* No cursor change here on purpose: .ph--live is used both inside links and
   on plain figures in the portfolio, where nothing is clickable and a
   pointer cursor would promise a page that does not exist. */
.ph--live:hover img,
a:hover > .ph--live img,
a:focus-visible > .ph--live img,
button:hover > .ph--live img,
button:focus-visible > .ph--live img { filter: none; transform: scale(1.035); }
.ph--live:hover::after,
a:hover > .ph--live::after,
a:focus-visible > .ph--live::after,
button:hover > .ph--live::after,
button:focus-visible > .ph--live::after { opacity: 0; }

/* THE ARCH — the signature shape. A fingernail silhouette, used on hero
   art, service cards, portfolio tiles and the logo plate. */
.ph--arch      { border-radius: var(--arch); }
.ph--arch-soft { border-radius: var(--arch-soft); }
.ph--round     { border-radius: var(--r-pill); }

.ph--3x4  { aspect-ratio: 3 / 4; }
.ph--4x5  { aspect-ratio: 4 / 5; }
.ph--1x1  { aspect-ratio: 1 / 1; }
.ph--4x3  { aspect-ratio: 4 / 3; }
.ph--16x9 { aspect-ratio: 16 / 9; }


/* ══════════════════════════════════════════════════════ 06 · HEADER ═════ */

.site-head {
  position: sticky;
  top: 0;
  z-index: 60;
  background: transparent;
  transition: background var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              transform .45s var(--ease-out);
  border-bottom: 1px solid transparent;
}
/* main.js adds .is-stuck past 40px. Until then the header floats on the
   linen with no chrome at all, which is what makes the hero feel tall. */
.site-head.is-stuck {
  background: var(--c-paper);
  border-bottom-color: var(--c-line);
  box-shadow: var(--shadow-sm);
}
/* Scrolling down retracts the bar and gives the page its full height back;
   the first flick upwards brings it straight back. main.js never sets this
   while the mobile panel is open — the panel is a child of the header, and
   sliding the header away would take the open menu with it. */
.site-head.is-tucked { transform: translateY(-100%); }

.site-head__in {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2.25rem);
  height: var(--head-h);
}

/* --- brand lockup -------------------------------------------------------
   The D&J mark is bright yellow on transparent. On this linen canvas it
   would vanish, so it sits on a pine plate — cut to the same arch as the
   photography, which turns a contrast fix into a brand device. */
.brandmark { display: flex; align-items: center; gap: .8rem; flex: none; }

.brandmark__plate {
  width: 46px; height: 46px;
  flex: none;
  display: grid;
  place-items: center;
  background: var(--c-pine);
  border-radius: var(--arch-soft);
  padding: 7px;
  transition: transform var(--dur) var(--ease);
}
.brandmark:hover .brandmark__plate { transform: translateY(-2px) rotate(-2deg); }
.brandmark__plate img { width: 100%; height: auto; }

.brandmark__text { display: flex; flex-direction: column; line-height: 1.15; }
.brandmark__name {
  font-family: var(--font-display);
  font-size: 1.28rem;
  letter-spacing: -.015em;
  font-variation-settings: "opsz" 72;
}
.brandmark__tag {
  font-size: var(--fs-label);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--c-ink-mute);
  margin-top: .2rem;
}

/* --- navigation --------------------------------------------------------- */
.mainnav { display: flex; align-items: center; gap: clamp(.9rem, 2vw, 1.9rem); margin-left: auto; }

.navlink {
  position: relative;
  font-size: var(--fs-small);
  font-weight: 500;
  padding-block: .4rem;
  color: var(--c-ink-soft);
  transition: color var(--dur-fast) var(--ease);
}
.navlink::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-fast) var(--ease);
}
.navlink:hover { color: var(--c-ink); }
.navlink:hover::after { transform: scaleX(1); }
.navlink[aria-current="page"] { color: var(--c-jade); font-weight: 600; }
.navlink[aria-current="page"]::after { transform: scaleX(1); }

.head-phone {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: var(--fs-small);
  font-weight: 600;
}
.head-phone svg { width: 1.05em; height: 1.05em; color: var(--c-jade); }
.head-phone:hover { color: var(--c-jade); }

/* --- live open / closed pill -------------------------------------------
   Hidden until main.js has computed a state, so it can never show a stale
   guess. If JS never runs, it simply is not there. */
.status {
  display: none;
  align-items: center;
  gap: .5rem;
  font-size: var(--fs-fine);
  font-weight: 600;
  padding: .35rem .8rem .35rem .7rem;
  border: 1px solid var(--c-line);
  border-radius: var(--r-pill);
  white-space: nowrap;
}
.status.is-ready { display: inline-flex; }
.status__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--c-ink-mute); flex: none; }
/* The pill's text is --c-jade-deep rather than --c-open because the header is
   transparent until you scroll: on the linen canvas behind it, --c-open on the
   jade wash measures 4.4:1 and misses AA. The deeper jade clears it in both
   header states (5.8:1 un-stuck, 6.6:1 stuck). --c-open still drives the dot,
   which is aria-hidden decoration and only owes 3:1. */
.status.is-open   { color: var(--c-jade-deep); border-color: var(--c-jade-edge); background: var(--c-jade-wash); }
.status.is-open   .status__dot { background: var(--c-open); animation: dot-pulse 2.6s var(--ease-soft) infinite; }
.status.is-closed { color: var(--c-closed); }
.status.is-closed .status__dot { background: var(--c-closed); }

/* A slow halo on the dot while the salon is actually open. It is the only
   looping animation in the header, and it stops entirely under
   prefers-reduced-motion. */
@keyframes dot-pulse {
  0%   { box-shadow: 0 0 0 0   var(--c-pulse-in); }
  60%  { box-shadow: 0 0 0 7px var(--c-pulse-out); }
  100% { box-shadow: 0 0 0 0   var(--c-pulse-out); }
}

/* --- mobile toggle ------------------------------------------------------ */
.navtoggle {
  display: none;
  width: 44px; height: 44px;
  margin-left: auto;
  position: relative;
  flex: none;
}
.navtoggle span {
  position: absolute;
  left: 11px;
  width: 22px; height: 1.5px;
  background: var(--c-ink);
  transition: transform var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease);
}
.navtoggle span:nth-child(1) { top: 16px; }
.navtoggle span:nth-child(2) { top: 21.5px; }
.navtoggle span:nth-child(3) { top: 27px; }
.navtoggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
.navtoggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.navtoggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5.5px) rotate(-45deg); }

/* The header sheds items as it narrows, in order of how much each earns its
   space: the tagline first, then the phone (the call bar takes over), then
   the whole nav collapses into the panel. */
@media (max-width: 1180px) { .brandmark__tag { display: none; } }
@media (max-width: 1040px) { .head-phone { display: none; } }

@media (max-width: 960px) {
  .navtoggle { display: block; }

  .mainnav {
    position: fixed;
    inset: var(--head-h) 0 auto 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin: 0;
    padding: 1rem var(--gutter) 2rem;
    background: var(--c-paper);
    border-bottom: 1px solid var(--c-line);
    box-shadow: var(--shadow);
    max-height: calc(100dvh - var(--head-h));
    overflow-y: auto;
    transform: translateY(-14px);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--dur-fast) var(--ease),
                transform var(--dur-fast) var(--ease),
                visibility var(--dur-fast);
  }
  .mainnav.is-open { opacity: 1; transform: none; visibility: visible; }

  /* The panel's contents come in one after another rather than as a block.
     The delays are on the OPEN state only, so closing is immediate — a menu
     that takes half a second to get out of the way feels broken. */
  .mainnav > * {
    opacity: 0;
    transform: translateY(9px);
    transition: opacity .34s var(--ease-out), transform .34s var(--ease-out);
  }
  .mainnav.is-open > * { opacity: 1; transform: none; }
  .mainnav.is-open > :nth-child(1) { transition-delay: .06s; }
  .mainnav.is-open > :nth-child(2) { transition-delay: .10s; }
  .mainnav.is-open > :nth-child(3) { transition-delay: .14s; }
  .mainnav.is-open > :nth-child(4) { transition-delay: .18s; }
  .mainnav.is-open > :nth-child(5) { transition-delay: .22s; }
  .mainnav.is-open > :nth-child(6) { transition-delay: .26s; }
  .mainnav.is-open > :nth-child(7) { transition-delay: .30s; }
  .mainnav.is-open > :nth-child(8) { transition-delay: .34s; }

  .navlink {
    font-size: 1.15rem;
    font-family: var(--font-display);
    padding: .95rem 0;
    border-bottom: 1px solid var(--c-line-soft);
  }
  .navlink::after { display: none; }
  .mainnav .btn { margin-top: 1.35rem; }
  .mainnav .status { align-self: flex-start; margin-top: 1.1rem; }
}


/* ════════════════════════════════════════════════════════ 07 · HERO ═════ */

/* A split editorial hero: type on the left, an arch of photography on the
   right. Deliberately not a full-bleed photo with text on top — that is the
   default every salon site reaches for, and both earlier designs use it. */
.hero { padding-block: clamp(2.5rem, 6vw, 5rem) clamp(3.5rem, 7vw, 6rem); }

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.02fr) minmax(0, .98fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}

.hero__title {
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  font-variation-settings: "opsz" 144;
  margin-bottom: 1.5rem;
}
.hero__title .ital { display: block; }

/* Each line of the headline is a window with the type sliding up inside it.
   The padding/margin pair widens the window past the descenders — Fraunces
   italic at display size hangs well below the baseline, and a flush
   overflow:hidden shears the tail off the "q" and the "y". */
.hero__title .line {
  display: block;
  overflow: hidden;
  padding-bottom: .14em;
  margin-bottom: -.14em;
}
.hero__title .line > span { display: block; }

.hero__lede { max-width: 44ch; }

/* Small facts under the buttons. Each carries its own clay dot rather than
   a separator between items — with a separator, a line that wraps starts
   with a stray divider hanging off its left edge. */
.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: .55rem 2rem;
  list-style: none;
  padding: 0;
  margin-top: 2.5rem;
  font-size: var(--fs-fine);
  color: var(--c-ink-mute);
}
.hero__meta li { display: flex; align-items: center; gap: .6rem; }
.hero__meta li::before {
  content: "";
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--c-clay);
  flex: none;
}
.hero__meta b { color: var(--c-ink); font-weight: 600; }

/* --- hero media --------------------------------------------------------- */
.hero__media { position: relative; }
.hero__media .ph { box-shadow: var(--shadow); }

/* The small square photo tucked into the lower-left of the arch. Hidden on
   narrow screens, where it would overlap the arch instead of framing it. */
.hero__detail {
  position: absolute;
  left: -2.5rem; bottom: 2.5rem;
  width: 40%;
  max-width: 190px;
  border: 6px solid var(--c-canvas);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  display: none;
}
@media (min-width: 1100px) { .hero__detail { display: block; } }

/* Floating "open today" card. main.js fills it in; it ships with the plain
   hours as text so it is still correct with JS off. */
.hero__card {
  position: absolute;
  right: -1rem; bottom: -1.75rem;
  background: var(--c-paper);
  border: 1px solid var(--c-line);
  border-radius: var(--r);
  box-shadow: var(--shadow);
  padding: 1rem 1.25rem;
  display: flex;
  align-items: center;
  gap: .85rem;
  max-width: 15rem;
}
.hero__card svg { width: 20px; height: 20px; color: var(--c-jade); flex: none; }
.hero__card .k {
  display: block;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-mute);
}
.hero__card .v { display: block; font-weight: 600; font-size: var(--fs-small); }

@media (max-width: 900px) {
  .hero__grid { grid-template-columns: 1fr; gap: 2.75rem; }
  .hero__media { max-width: 30rem; margin-inline: auto; width: 100%; }
  .hero__card { right: 0; bottom: -1.25rem; }
}
@media (max-width: 420px) {
  .hero__card { position: static; margin-top: 1.25rem; max-width: none; }
}


/* ═════════════════════════════════════════ 08 · CREDENTIALS RULE ════════ */

/* Four claims on one hairline. No icons on purpose — icons here are decoration
   that costs vertical space, and the type alone is calmer. */
.creds {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  list-style: none;
  padding: 0;
  border-block: 1px solid var(--c-line);
}
.creds li {
  padding: 1.4rem 1.5rem;
  font-size: var(--fs-fine);
  letter-spacing: .04em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--c-ink-soft);
  text-align: center;
  border-left: 1px solid var(--c-line);
}
.creds li:first-child { border-left: 0; }
.creds b { display: block; font-weight: 700; color: var(--c-jade); }

@media (max-width: 820px) {
  .creds { grid-template-columns: repeat(2, 1fr); }
  .creds li:nth-child(3) { border-left: 0; }
  .creds li:nth-child(n+3) { border-top: 1px solid var(--c-line); }
}


/* ═══════════════════════════════════════════════ 09 · SPLIT SECTION ═════ */

.split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}
.split--media-right > .split__media { order: 2; }
/* Top-aligned, not centred: this variant pairs running copy with a tall form,
   and centring leaves the copy floating in the middle of the column. */
.split--wide-text { grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); align-items: start; }

.split__media { position: relative; }
.split__media .ph { box-shadow: var(--shadow); }

/* Small inset photo overlapping the main one. */
.split__inset {
  position: absolute;
  right: -1.75rem; bottom: -1.75rem;
  width: 42%;
  max-width: 200px;
  border: 6px solid var(--c-canvas);
  border-radius: var(--r);
  box-shadow: var(--shadow);
}
.section--paper .split__inset { border-color: var(--c-paper); }

@media (max-width: 880px) {
  .split { grid-template-columns: 1fr; }
  .split--media-right > .split__media { order: 0; }
  .split__inset { display: none; }
}

/* Tick list — used for standards and inclusions.
   The marker is absolutely positioned rather than a grid or flex track. A
   two-column grid works only while each <li> holds exactly one child: add a
   <strong> and the trailing text becomes a second anonymous item, which
   wraps into the 22px marker column and sets the copy one word per line. */
.ticks { list-style: none; padding: 0; margin-top: 1.75rem; display: grid; gap: .85rem; }
.ticks li {
  position: relative;
  padding-left: 2.1rem;
  font-size: var(--fs-small);
  color: var(--c-ink-soft);
}
.ticks li::before {
  content: "";
  position: absolute;
  left: 0; top: .2em;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--c-jade-wash);
  border: 1px solid var(--c-jade-edge);
  background-image: linear-gradient(var(--c-jade), var(--c-jade));
  background-size: 7px 1.5px;
  background-position: center;
  background-repeat: no-repeat;
}
.ticks strong { color: var(--c-ink); font-weight: 600; }
.section--pine .ticks li { color: var(--c-on-pine-mute); }


/* ══════════════════════════════════════════════ 10 · SERVICE RAIL ═══════ */

/* A horizontally scrolling index of services. Deliberately not a grid of
   cards (design 01) and not an accordion (design 02): it reads like the
   contents page of a lookbook, and it lets six services occupy one screen
   without shrinking the photography to thumbnails. */
.rail-wrap { position: relative; }

.rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: clamp(230px, 25vw, 300px);
  gap: clamp(1rem, 2vw, 1.75rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-left: var(--gutter);
  padding: .5rem var(--gutter) 2rem;
  margin-inline: calc(var(--gutter) * -1);
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}
.rail::-webkit-scrollbar { display: none; }

/* Full-bleed: the rail runs off the right edge of the viewport so it is
   visibly a strip that continues, not a row that happens to be cut. */
@media (min-width: 1240px) {
  .rail {
    margin-right: calc((100vw - var(--wrap)) / -2 - var(--gutter));
    padding-right: calc((100vw - var(--wrap)) / 2);
  }
}

/* Column flex, so the "from $28" line sits on the same baseline across every
   card no matter how long the description runs. */
.rail__item {
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  transition: transform var(--dur) var(--ease);
}
.rail__item:hover { transform: translateY(-7px); }
.rail__item .ph { aspect-ratio: 3 / 4; flex: none; }

.rail__head {
  display: flex;
  align-items: baseline;
  gap: .75rem;
  margin-top: 1.1rem;
}
.rail__num {
  font-family: var(--font-display);
  font-size: var(--fs-small);
  color: var(--c-clay-ink);
  font-variation-settings: "opsz" 24;
  flex: none;
}
.rail__title {
  font-family: var(--font-display);
  font-size: 1.3rem;
  letter-spacing: -.015em;
  line-height: 1.2;
}
.rail__item:hover .rail__title { color: var(--c-jade); }
.rail__text { display: block; font-size: var(--fs-small); color: var(--c-ink-mute); margin-top: .5rem; }
.rail__from {
  display: block;
  margin-top: auto;
  padding-top: .85rem;
  font-size: var(--fs-fine);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--c-jade);
}

/* Rail controls: two buttons and a progress bar that reflects scroll position.
   The bar is decorative — the rail is scrollable, swipeable and keyboard
   reachable without it. */
.rail-ctl { display: flex; align-items: center; gap: 1rem; margin-top: .5rem; }
.rail-ctl__btn {
  width: 44px; height: 44px;
  border: 1px solid var(--c-line);
  border-radius: 50%;
  display: grid;
  place-items: center;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease);
}
.rail-ctl__btn:hover:not(:disabled) { background: var(--c-ink); color: var(--c-canvas); border-color: var(--c-ink); }
.rail-ctl__btn:disabled { opacity: .3; cursor: default; }
.rail-ctl__btn svg { width: 18px; height: 18px; }

.rail-prog { flex: 1; height: 2px; background: var(--c-line); position: relative; max-width: 22rem; }
.rail-prog i {
  position: absolute;
  inset-block: 0; left: 0;
  width: 25%;
  background: var(--c-jade);
  transition: width .15s linear, transform .15s linear;
}

@media (max-width: 700px) { .rail-ctl__btn { display: none; } }


/* ═══════════════════════════════════════════════════════ 11 · RITUAL ════ */

/* The numbered process. Left column holds the heading and stays put while
   the steps scroll past it on desktop. */
.ritual {
  display: grid;
  grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: start;
}
@media (min-width: 1000px) {
  .ritual__aside { position: sticky; top: calc(var(--head-h) + 3rem); }
}
.ritual__aside .ph { margin-top: 2rem; }

.ritual__steps { list-style: none; padding: 0; margin: 0; counter-reset: step; }

.ritual__step {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2.5rem);
  padding-block: clamp(1.75rem, 3vw, 2.5rem);
  border-top: 1px solid var(--c-line);
}
.ritual__step:last-child { border-bottom: 1px solid var(--c-line); }

.ritual__step::before {
  counter-increment: step;
  content: "0" counter(step);
  font-family: var(--font-display);
  font-size: var(--fs-num);
  line-height: 1;
  font-variation-settings: "opsz" 144;
  color: var(--c-clay);
  opacity: .55;
}
.ritual__step h3 { margin-bottom: .55rem; }
.ritual__step p { color: var(--c-ink-soft); font-size: var(--fs-small); }

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


/* ══════════════════════════════════════════════════════ 12 · SPREAD ═════ */

/* Recent work, laid out as a magazine spread: five tiles with alternating
   vertical offsets, captions set in the margin beneath each one. */
.spread {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: clamp(.9rem, 2vw, 1.75rem);
  align-items: start;
}
.spread__item { display: block; transition: transform var(--dur) var(--ease); }
.spread__item:hover { transform: translateY(-7px); }
.spread__item:nth-child(even) { margin-top: clamp(1.5rem, 4vw, 3.5rem); }
.spread__item .ph { aspect-ratio: 4 / 5; }
.spread__cap {
  display: block;
  margin-top: .85rem;
  font-size: var(--fs-fine);
  color: var(--c-ink-mute);
}
.spread__item:hover .spread__cap { color: var(--c-jade); }

@media (max-width: 900px) {
  .spread { grid-template-columns: repeat(3, 1fr); }
  .spread__item:nth-child(n+4) { display: none; }
}
@media (max-width: 560px) {
  .spread { grid-template-columns: repeat(2, 1fr); }
  .spread__item:nth-child(n+3) { display: none; }
}


/* ══════════════════════════════════════════════════ 13 · PULL QUOTE ═════ */

.quote {
  max-width: 34ch;
  margin-inline: auto;
  text-align: center;
  position: relative;
}
.quote__mark {
  font-family: var(--font-display);
  font-size: 5rem;
  line-height: .6;
  color: var(--c-clay);
  opacity: .35;
  display: block;
  margin-bottom: 1rem;
}
.quote blockquote {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1.4rem, 2.8vw, 2.15rem);
  line-height: 1.36;
  letter-spacing: -.015em;
  font-variation-settings: "opsz" 144;
}
.quote figcaption {
  margin-top: 1.75rem;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  font-weight: 600;
  color: var(--c-ink-mute);
}


/* ═══════════════════════════════════════ 14 · CARDS, LISTS, HOURS ═══════ */

.card {
  background: var(--c-paper);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  padding: clamp(1.5rem, 3vw, 2.4rem);
}
.section--paper .card { background: var(--c-canvas); border-color: var(--c-line); }
.section--pine .card {
  background: var(--c-pine-2);
  border-color: var(--c-line-pine);
  color: var(--c-on-pine-mute);
}
/* A card heading is styled by its role, not its level. Which level it should
   be depends on what precedes it on the page — on visit.html these cards are
   the first thing after the <h1> and must be h2s, on index.html they sit under
   a section h2 and must be h3s. Styling both identically lets the markup use
   whichever keeps the outline correct. */
.card h2, .card h3 {
  margin-bottom: 1.35rem;
  font-family: var(--font-body);
  font-size: var(--fs-h3);
  font-weight: 600;
  letter-spacing: 0;
  font-variation-settings: normal;
}

.duo-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1rem, 2.5vw, 2rem);
}
@media (max-width: 820px) { .duo-grid { grid-template-columns: 1fr; } }

.trio-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2.5vw, 2rem);
}
@media (max-width: 900px) { .trio-grid { grid-template-columns: 1fr; } }

/* --- hours table -------------------------------------------------------- */
.hours { width: 100%; border-collapse: collapse; font-size: var(--fs-small); }
.hours th, .hours td { padding: .72rem 0; text-align: left; font-weight: 400; }
.hours tr + tr th, .hours tr + tr td { border-top: 1px solid var(--c-line-soft); }
.hours th { color: var(--c-ink-soft); font-weight: 500; }
.hours td { text-align: right; font-variant-numeric: tabular-nums; color: var(--c-ink); }
.hours .closed { color: var(--c-ink-mute); }
.section--pine .hours th { color: var(--c-on-pine-mute); }
.section--pine .hours td { color: var(--c-canvas); }
.section--pine .hours tr + tr th,
.section--pine .hours tr + tr td { border-top-color: var(--c-line-pine); }

/* main.js tags today's row. */
.hours tr.is-today th, .hours tr.is-today td { color: var(--c-jade); font-weight: 600; }
.hours tr.is-today th::after {
  content: "Today";
  display: inline-block;
  margin-left: .6rem;
  font-size: var(--fs-label);
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: .12rem .5rem;
  border-radius: var(--r-pill);
  background: var(--c-jade-wash);
  color: var(--c-jade);
}
.section--pine .hours tr.is-today th,
.section--pine .hours tr.is-today td { color: var(--c-jade-light); }
.section--pine .hours tr.is-today th::after { color: var(--c-jade-light); background: var(--c-jade-lt-wash); }

/* --- contact list ------------------------------------------------------- */
.contact-list { list-style: none; padding: 0; display: grid; gap: 1.5rem; }
.contact-list li { display: grid; grid-template-columns: 38px 1fr; gap: 1rem; align-items: start; }
.contact-list .ico {
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--c-jade-wash);
  color: var(--c-jade);
}
.contact-list .ico svg { width: 17px; height: 17px; }
.contact-list .k {
  display: block;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-mute);
  margin-bottom: .25rem;
}
.contact-list .v { display: block; font-weight: 500; line-height: 1.5; }
.contact-list a.v:hover { color: var(--c-jade); }
.section--pine .contact-list .ico { background: var(--c-jade-lt-wash); color: var(--c-jade-light); }
.section--pine .contact-list .v { color: var(--c-canvas); }

/* --- figures / stats ---------------------------------------------------- */
.facts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
  list-style: none;
  padding: 0;
}
.facts li { border-top: 1px solid var(--c-line); padding-top: 1.15rem; }
.facts .n {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(2rem, 3.6vw, 2.9rem);
  line-height: 1;
  font-variation-settings: "opsz" 144;
}
.facts .k {
  display: block;
  margin-top: .5rem;
  font-size: var(--fs-fine);
  color: var(--c-ink-mute);
}
@media (max-width: 760px) { .facts { grid-template-columns: repeat(2, 1fr); } }


/* ═════════════════════════════════════════════════════ 15 · CTA BAND ════ */

.band {
  position: relative;
  overflow: hidden;
  background: var(--c-pine);
  color: var(--c-on-pine);
  border-radius: var(--r-lg);
  padding: clamp(3rem, 7vw, 6rem) clamp(1.5rem, 5vw, 5rem);
  text-align: center;
  isolation: isolate;
}
.band__media { position: absolute; inset: 0; z-index: -1; }
.band__media img { width: 100%; height: 100%; object-fit: cover; opacity: .3; filter: grayscale(.3); }
.band__media::after { content: ""; position: absolute; inset: 0; background: var(--scrim-band); }
.band h2 { color: var(--c-canvas); }
.band p { color: var(--c-on-pine-mute); max-width: 48ch; margin-inline: auto; margin-top: 1rem; }
.band .btn-row { justify-content: center; }
.band__rule { width: 46px; height: 1px; background: var(--c-jade-light); margin: 0 auto 1.5rem; }


/* ══════════════════════════════════════ 16 · FOOTER & CALL BAR ══════════ */

.site-foot {
  background: var(--c-pine);
  color: var(--c-on-pine-mute);
  padding-block: clamp(3rem, 6vw, 5rem) 2rem;
  font-size: var(--fs-small);
}
.site-foot .brandmark__plate { background: var(--c-pine-2); }
.site-foot .brandmark__name { color: var(--c-canvas); }
.site-foot .brandmark__tag { color: var(--c-on-pine-dim); display: block; }

.foot-grid {
  display: grid;
  grid-template-columns: 1.6fr repeat(3, 1fr);
  gap: clamp(1.75rem, 4vw, 3.5rem);
}
.foot-grid h3 {
  font-family: var(--font-body);
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  font-weight: 700;
  color: var(--c-canvas);
  margin-bottom: 1.15rem;
}
.foot-blurb { margin-top: 1.35rem; max-width: 34ch; color: var(--c-on-pine-dim); font-size: var(--fs-small); }

/* .fine and .small default to a warm grey tuned for the linen canvas. On
   pine that grey is close to unreadable, so every dark context re-points
   them at the on-pine scale. */
.site-foot .fine, .site-foot .small,
.section--pine .fine, .section--pine .small,
.band .fine, .band .small,
.page-head .fine, .page-head .small { color: var(--c-on-pine-dim); }

.foot-links { display: grid; gap: .7rem; }
.foot-links a { color: var(--c-on-pine-mute); width: fit-content; }
.foot-links a:hover { color: var(--c-jade-light); }

.social { display: flex; gap: .65rem; margin-top: 1.5rem; }
.social a {
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border: 1px solid var(--c-line-pine);
  border-radius: 50%;
  color: var(--c-on-pine-mute);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.social a:hover {
  background: var(--c-jade);
  border-color: var(--c-jade);
  color: var(--c-paper);
  transform: translateY(-3px) rotate(-6deg);
}
.social svg { width: 16px; height: 16px; }

.foot-base {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--c-line-pine);
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 2rem;
  justify-content: space-between;
  font-size: var(--fs-fine);
  color: var(--c-on-pine-dim);
}
.foot-base a { color: var(--c-on-pine-mute); text-decoration: underline; text-underline-offset: 3px; }
.foot-base a:hover { color: var(--c-jade-light); }

@media (max-width: 900px) { .foot-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .foot-grid { grid-template-columns: 1fr; } }

/* --- sticky mobile call bar --------------------------------------------
   Phone taps are most of the conversions on a salon site, so the two
   primary actions never scroll away on a phone. */
.callbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 70;
  display: none;
  background: var(--c-pine);
  border-top: 1px solid var(--c-line-pine);
  padding-bottom: env(safe-area-inset-bottom);
}
.callbar a {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: 1rem .5rem;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--c-canvas);
}
.callbar a.is-primary { background: var(--c-jade); color: var(--c-paper); }
.callbar svg { width: 17px; height: 17px; }

@media (max-width: 760px) {
  .callbar { display: flex; }
  /* Keep the bar from covering the last line of the footer. */
  body { padding-bottom: 60px; }
}


/* ═══════════════════════════════════════════════ 17 · PAGE HEADERS ══════ */

.page-head {
  position: relative;
  background: var(--c-pine);
  color: var(--c-on-pine);
  padding-block: clamp(3.5rem, 8vw, 6.5rem);
  overflow: hidden;
  isolation: isolate;
}
/* Overscanned top and bottom, not inset:0. This layer is parallaxed, and a
   backdrop that exactly fills its container slides a bare strip of pine into
   view the moment it moves. 48px covers the largest data-parallax on the
   page with room to spare; .page-head clips the excess. */
.page-head__media { position: absolute; inset: -48px 0; z-index: -1; }
.page-head__media img { width: 100%; height: 100%; object-fit: cover; opacity: .34; }
.page-head__media::after { content: ""; position: absolute; inset: 0; background: var(--scrim-head); }
.page-head h1 { color: var(--c-canvas); max-width: 18ch; }
.page-head p { color: var(--c-on-pine-mute); max-width: 52ch; margin-top: 1.15rem; }

/* Breadcrumb. Small, quiet, and genuinely useful on a 5-page site. */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  font-size: var(--fs-fine);
  color: var(--c-on-pine-dim);
  margin-bottom: 1.5rem;
}
.crumbs a:hover { color: var(--c-jade-light); }
.crumbs span[aria-hidden] { opacity: .5; }


/* ══════════════════════════════════════════════ 18 · SERVICES PAGE ══════ */

.svc-layout {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
/* A grid item's automatic minimum size is its content's min-content width. The
   category index becomes a horizontal chip scroller on narrow screens, and
   without this it reports the full width of all eight chips — pushing the page
   to 871px on a 390px phone and giving the whole site a sideways scrollbar. */
.svc-layout > * { min-width: 0; }
@media (max-width: 940px) { .svc-layout { grid-template-columns: 1fr; } }

/* Sticky category index. main.js highlights the section in view. On narrow
   screens it becomes a horizontal scroller pinned under the header. */
.svc-index { position: sticky; top: calc(var(--head-h) + 2rem); }
.svc-index h2 {
  font-family: var(--font-body);
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  font-weight: 700;
  color: var(--c-ink-mute);
  margin-bottom: 1.15rem;
}
.svc-index a {
  display: flex;
  align-items: baseline;
  gap: .7rem;
  padding: .7rem 0;
  border-top: 1px solid var(--c-line);
  font-size: var(--fs-small);
  color: var(--c-ink-soft);
  transition: color var(--dur-fast) var(--ease), padding-left var(--dur-fast) var(--ease);
}
.svc-index a:last-child { border-bottom: 1px solid var(--c-line); }
.svc-index a::before {
  content: attr(data-n);
  font-family: var(--font-display);
  font-size: var(--fs-fine);
  color: var(--c-ink-mute);
}
.svc-index a:hover { color: var(--c-ink); padding-left: .3rem; }
.svc-index a.is-active { color: var(--c-jade); font-weight: 600; }
.svc-index a.is-active::before { color: var(--c-clay-ink); }

@media (max-width: 940px) {
  /* display:block, not a single-column grid. A sticky element can only travel
     inside its containing block, and as a grid item that block is its own
     one-row grid area — exactly its own height, so the range is zero and the
     bar never actually sticks. As a normal block child of the full-height
     .svc-layout it has the whole price list to travel down. */
  .svc-layout { display: block; }
  .svc-layout > * + * { margin-top: 2rem; }

  .svc-index {
    top: var(--head-h);
    z-index: 40;
    background: var(--c-canvas);
    margin-inline: calc(var(--gutter) * -1);
    padding: .75rem var(--gutter);
    border-bottom: 1px solid var(--c-line);
  }
  .svc-index h2 { display: none; }
  .svc-index nav { display: flex; gap: .5rem; overflow-x: auto; scrollbar-width: none; }
  .svc-index nav::-webkit-scrollbar { display: none; }
  .svc-index a {
    border: 1px solid var(--c-line);
    border-radius: var(--r-pill);
    padding: .45rem .95rem;
    white-space: nowrap;
    font-size: var(--fs-fine);
  }
  .svc-index a:last-child { border-bottom: 1px solid var(--c-line); }
  .svc-index a::before { display: none; }
  .svc-index a.is-active { background: var(--c-jade); color: var(--c-paper); border-color: var(--c-jade); }
}

.menu-group + .menu-group { margin-top: clamp(3rem, 6vw, 5rem); }
.menu-group__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 1.1rem;
  border-bottom: 2px solid var(--c-ink);
  margin-bottom: .5rem;
}
.menu-group__head h2 { font-size: clamp(1.6rem, 2.6vw, 2.15rem); }
.menu-group__note { font-size: var(--fs-fine); color: var(--c-ink-mute); max-width: 34ch; }

/* The priced ledger — a restaurant menu, not a table. Dot leaders come from a
   flexible middle element rather than a repeating background, so they stay put
   at any zoom level and never collide with a long service name.

   Named grid areas, not flex-wrap. Flex looks like the obvious fit here and is
   a trap: line breaking measures each item's flex-basis CLAMPED BY max-width,
   so a description set to `flex: 0 0 100%; max-width: 62ch` reports 62ch, fits
   beside the price, and never wraps to its own line. Grid places it on row two
   by name and the measure is then free to be whatever reads best. */
.menu-item {
  display: grid;
  grid-template-columns: auto auto minmax(1.5rem, 1fr) auto auto;
  grid-template-areas:
    "name tag dots dur price"
    "desc desc desc desc desc";
  align-items: baseline;
  column-gap: .6rem;
  padding-block: 1rem;
  border-bottom: 1px solid var(--c-line);
}
.menu-item__name  { grid-area: name; font-weight: 600; font-size: 1.02rem; }
.menu-item__dots  {
  grid-area: dots;
  align-self: center;
  border-bottom: 1px dotted var(--c-line);
}
.menu-item__dur   {
  grid-area: dur;
  font-size: var(--fs-fine);
  color: var(--c-ink-mute);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.menu-item__price {
  grid-area: price;
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-variation-settings: "opsz" 24;
  font-variant-numeric: tabular-nums;
  color: var(--c-jade);
  white-space: nowrap;
  min-width: 3.5rem;
  text-align: right;
}
.menu-item__desc {
  grid-area: desc;
  font-size: var(--fs-small);
  color: var(--c-ink-mute);
  margin-top: .35rem;
  max-width: 68ch;
}
.menu-item__tag {
  grid-area: tag;
  font-size: var(--fs-label);
  letter-spacing: .1em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--c-clay-ink);
  background: var(--c-clay-wash);
  border-radius: var(--r-pill);
  padding: .15rem .55rem;
  white-space: nowrap;
}

/* On a phone the five-column row cannot hold a service name, a duration and a
   price without either wrapping mid-word or scrolling sideways. Stack it: the
   price stays on the name's line where it is being compared, and the leaders
   go, because a dot leader spanning 40px communicates nothing. */
@media (max-width: 560px) {
  .menu-item {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "name price"
      "tag  dur"
      "desc desc";
    row-gap: .15rem;
  }
  .menu-item__dots { display: none; }
  .menu-item__tag { justify-self: start; }
  .menu-item__dur { justify-self: end; }
}

/* Add-ons read as a chip grid rather than more ledger rows, because they are
   bought alongside something else, not chosen from. */
.addons { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: .85rem; }
.addon {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .75rem;
  padding: .85rem 1.1rem;
  background: var(--c-paper);
  border: 1px solid var(--c-line);
  border-radius: var(--r);
  font-size: var(--fs-small);
}
.addon b { color: var(--c-jade); font-family: var(--font-display); font-variation-settings: "opsz" 24; }

/* Package tiers. */
.tier {
  display: flex;
  flex-direction: column;
  background: var(--c-paper);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  padding: clamp(1.5rem, 3vw, 2.25rem);
}
.tier--feature { border-color: var(--c-jade); box-shadow: var(--shadow); position: relative; }
.tier--feature::before {
  content: attr(data-flag);
  position: absolute;
  top: -.75rem; left: 50%;
  transform: translateX(-50%);
  background: var(--c-jade);
  color: var(--c-paper);
  font-size: var(--fs-label);
  letter-spacing: .12em;
  text-transform: uppercase;
  font-weight: 700;
  padding: .25rem .8rem;
  border-radius: var(--r-pill);
  white-space: nowrap;
}
.tier__price {
  font-family: var(--font-display);
  font-size: 2.4rem;
  font-variation-settings: "opsz" 144;
  line-height: 1;
  margin: .75rem 0 .25rem;
}
.tier__meta { font-size: var(--fs-fine); color: var(--c-ink-mute); }
.tier .ticks { margin-top: 1.5rem; margin-bottom: 1.75rem; }
.tier .btn { margin-top: auto; }

/* FAQ — built on <details> so it works with JavaScript switched off. */
.faq { border-top: 1px solid var(--c-line); }
.faq details { border-bottom: 1px solid var(--c-line); }
.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.35rem 0;
  cursor: pointer;
  font-weight: 600;
  list-style: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "";
  width: 13px; height: 13px;
  flex: none;
  background:
    linear-gradient(var(--c-jade), var(--c-jade)) center / 13px 1.5px no-repeat,
    linear-gradient(var(--c-jade), var(--c-jade)) center / 1.5px 13px no-repeat;
  transition: transform var(--dur-fast) var(--ease);
}
.faq details[open] summary::after { transform: rotate(45deg); }
.faq summary:hover { color: var(--c-jade); }
.faq__body { padding-bottom: 1.5rem; color: var(--c-ink-soft); font-size: var(--fs-small); max-width: 68ch; }


/* ═════════════════════════════════════════════ 19 · PORTFOLIO PAGE ══════ */

.filters { display: flex; flex-wrap: wrap; gap: .6rem; }
.filter {
  padding: .5rem 1.1rem;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-pill);
  font-size: var(--fs-fine);
  font-weight: 600;
  color: var(--c-ink-soft);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.filter:hover { border-color: var(--c-ink); color: var(--c-ink); }
.filter[aria-pressed="true"] { background: var(--c-jade); border-color: var(--c-jade); color: var(--c-paper); }

.work-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.15rem, 2.5vw, 2.25rem);
  align-items: start;
}
/* Staggered columns: the middle column drops, so the grid reads as a spread
   instead of a contact sheet. Switched off below tablet where the offset
   just wastes a screenful of height. */
@media (min-width: 900px) {
  .work-grid > :nth-child(3n+2) { margin-top: clamp(1.5rem, 4vw, 3.5rem); }
}
@media (max-width: 900px) { .work-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .work-grid { grid-template-columns: 1fr; } }

.work { display: block; }
.work .ph { aspect-ratio: 4 / 5; }
.work__cap {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-top: .9rem;
}
.work__name { font-family: var(--font-display); font-size: 1.12rem; letter-spacing: -.01em; }
.work__tag {
  font-size: var(--fs-label);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--c-ink-mute);
  white-space: nowrap;
}
/* main.js sets .is-hidden on filtered-out tiles. */
.work.is-hidden { display: none; }

.filter-empty { display: none; padding: 3rem 0; text-align: center; color: var(--c-ink-mute); }
.filter-empty.is-shown { display: block; }


/* ═════════════════════════════════════════════════ 20 · STORY PAGE ══════ */

.standards { list-style: none; padding: 0; display: grid; gap: 0; }
.standards li {
  display: grid;
  grid-template-columns: minmax(0, 18ch) minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 2.5rem);
  padding-block: 1.35rem;
  border-top: 1px solid var(--c-line);
  align-items: start;
}
.standards li:last-child { border-bottom: 1px solid var(--c-line); }
.standards dt, .standards .k {
  font-weight: 600;
  font-size: var(--fs-small);
  letter-spacing: .02em;
}
.standards .v { color: var(--c-ink-soft); font-size: var(--fs-small); }
@media (max-width: 640px) { .standards li { grid-template-columns: 1fr; gap: .4rem; } }

/* Reviews. Three static cards — no carousel, no autoplay, nothing to get
   stuck. They stagger in one after another as the row comes into view. */
.reviews { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(1rem, 2.5vw, 2rem); }
@media (max-width: 900px) { .reviews { grid-template-columns: 1fr; } }

.review {
  background: var(--c-paper);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  padding: clamp(1.5rem, 3vw, 2.1rem);
  display: flex;
  flex-direction: column;
}
.section--paper .review { background: var(--c-canvas); }
.review__stars { display: flex; gap: .18rem; color: var(--c-clay); margin-bottom: 1.1rem; }
.review__stars svg { width: 15px; height: 15px; }
.review blockquote {
  font-family: var(--font-display);
  font-size: 1.1rem;
  line-height: 1.5;
  font-variation-settings: "opsz" 36;
}
.review figcaption {
  margin-top: auto;
  padding-top: 1.35rem;
  font-size: var(--fs-fine);
  color: var(--c-ink-mute);
}


/* ═════════════════════════════════════════════════ 21 · VISIT PAGE ══════ */

.map {
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--c-canvas-sunk);
  aspect-ratio: 16 / 10;
}
.map iframe { width: 100%; height: 100%; border: 0; display: block; filter: saturate(.75) contrast(1.02); }
@media (max-width: 700px) { .map { aspect-ratio: 4 / 3; } }

/* --- appointment request form ------------------------------------------
   No backend. On submit main.js composes a mailto: and also prints a plain
   summary the visitor can copy — because mailto: silently fails on plenty
   of phones, and a form that appears to send but does not is worse than no
   form at all. */
.form { display: grid; gap: 1.15rem; }
.form__row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.15rem; }
@media (max-width: 620px) { .form__row { grid-template-columns: 1fr; } }

.field { display: grid; gap: .45rem; }
.field > label {
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  font-weight: 700;
  color: var(--c-ink-mute);
}
.field .req { color: var(--c-clay-ink); }

/* --c-line-strong, not --c-line: the form sits on a .card painted the same
   porcelain as the fields, so this border is the ONLY thing telling you a
   control is there. The decorative hairline manages 1.4:1 against that card
   — WCAG 1.4.11 asks for 3:1 on the boundary of an interactive element. */
.field input,
.field select,
.field textarea {
  width: 100%;
  padding: .8rem 1rem;
  background: var(--c-paper);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r);
  font-size: var(--fs-small);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.field textarea { resize: vertical; min-height: 7rem; }
.field select { appearance: none; padding-right: 2.5rem; background-image:
  linear-gradient(45deg, transparent 50%, currentColor 50%),
  linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 19px) 55%, calc(100% - 13px) 55%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--c-jade);
  box-shadow: 0 0 0 3px var(--c-jade-wash);
}
.field input:user-invalid,
.field textarea:user-invalid { border-color: var(--c-closed); }

.field__hint { font-size: var(--fs-fine); color: var(--c-ink-mute); }

.form-note {
  font-size: var(--fs-fine);
  color: var(--c-ink-mute);
  background: var(--c-jade-wash);
  border: 1px solid var(--c-jade-edge);
  border-radius: var(--r);
  padding: .9rem 1.1rem;
}

/* The composed summary, revealed after submit. */
.summary { display: none; }
.summary.is-shown { display: block; }
.summary pre {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: var(--fs-fine);
  line-height: 1.7;
  white-space: pre-wrap;
  word-break: break-word;
  background: var(--c-canvas-sunk);
  border: 1px solid var(--c-line);
  border-radius: var(--r);
  padding: 1.1rem 1.25rem;
  margin: 1rem 0;
  color: var(--c-ink);
}


/* ══════════════════════════════════════════════════════ 22 · TICKER ═════ */

/* A full-bleed pine strip that scrolls its own contents. The list is written
   TWICE in the markup and the track travels exactly -50%, so the second copy
   lands where the first started and the loop has no seam. Everything in it is
   decoration — the second copy is aria-hidden, and nothing in the strip is a
   link or a fact you cannot find elsewhere on the page. */
.ticker {
  background: var(--c-pine);
  color: var(--c-on-pine-mute);
  border-block: 1px solid var(--c-line-pine);
  overflow: hidden;
  padding-block: 1.05rem;
  position: relative;
  z-index: 2;
}
.ticker__track {
  display: flex;
  width: max-content;
  animation: ticker-run 44s linear infinite;
}
/* Hovering holds it still so a name that caught your eye stops running away. */
.ticker:hover .ticker__track { animation-play-state: paused; }

.ticker__item {
  display: inline-flex;
  align-items: center;
  gap: 1.6rem;
  padding-inline: 1.6rem;
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1.9vw, 1.5rem);
  font-variation-settings: "opsz" 72;
  letter-spacing: -.01em;
  white-space: nowrap;
}
/* The separator is drawn by the item, not placed between items — a marquee
   has no "last" item once it loops, so a :not(:last-child) rule would leave a
   visible gap at the seam. */
.ticker__item::after {
  content: "";
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--c-clay);
  flex: none;
}
.ticker__item em { font-style: italic; color: var(--c-jade-light); }

@keyframes ticker-run {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}


/* ═══════════════════════════════════════════════════════ 23 · MOTION ════ */

/* --- 23.1 reveal on scroll ----------------------------------------------
   Elements are only hidden once JavaScript has confirmed it is alive
   (html.js-ready, set inline in <head>). If main.js then fails to load, a
   3-second timer adds .reveal-fallback and everything appears. Content can
   never end up permanently invisible because of a script error.

   --d is the delay. Set it inline for a one-off, or put [data-stagger] on a
   container and main.js will write an increasing --d onto each .reveal child
   it holds. */
.js-ready .reveal {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition: opacity var(--dur-reveal) var(--ease-out) var(--d),
              transform var(--dur-reveal) var(--ease-out) var(--d);
  will-change: opacity, transform;
}

/* Direction variants. The default is a rise; these are for content that
   reads better arriving from the side it sits on. */
.js-ready .reveal[data-anim="left"]  { transform: translate3d(-34px, 0, 0); }
.js-ready .reveal[data-anim="right"] { transform: translate3d(34px, 0, 0); }
.js-ready .reveal[data-anim="scale"] { transform: scale(.955); }
/* "none" opts out of the transform and the fade but still receives .is-in, so
   an element can drive a child animation without moving itself. The hero
   headline uses it: the <h1> stays put and its lines slide inside it. */
.js-ready .reveal[data-anim="none"]  { opacity: 1; transform: none; }

.js-ready .reveal.is-in,
.js-ready.reveal-fallback .reveal {
  opacity: 1;
  transform: none;
}
/* Once it has arrived, stop promising the compositor a repaint. */
.js-ready .reveal.is-in { will-change: auto; }

/* --- 23.2 photographs ----------------------------------------------------
   A photograph inside a revealing block settles out of a slight push-in.
   This is an ANIMATION rather than a transition on purpose: when it finishes
   the rule stops applying, the image returns to its own base transform, and
   .ph--live's hover zoom works normally. A transition here would out-specify
   that hover and kill it permanently. */
@keyframes ph-settle {
  from { transform: scale(1.075); }
  to   { transform: scale(1); }
}
.js-ready .reveal.is-in .ph > img {
  animation: ph-settle 1.15s var(--ease-out);
}

/* The CTA band's backdrop gets a longer, slower version of the same move. */
@keyframes band-settle {
  from { transform: scale(1.14); }
  to   { transform: scale(1); }
}
.js-ready .band.reveal.is-in .band__media img {
  animation: band-settle 2.6s var(--ease-out);
}

/* --- 23.3 hero headline --------------------------------------------------
   Each line sits in an overflow-hidden window (see § 07) and starts below
   it. They arrive a beat apart, after the eyebrow above them. */
.js-ready .hero__title .line > span {
  transform: translate3d(0, 105%, 0);
  transition: transform 1s var(--ease-out) var(--d);
}
.js-ready .hero__title.is-in .line > span,
.js-ready.reveal-fallback .hero__title .line > span { transform: none; }
.js-ready .hero__title .line:nth-child(2) > span { transition-delay: calc(var(--d) + .09s); }

/* --- 23.4 numbered steps -------------------------------------------------
   The big clay numeral swings up a little later than the text beside it, so
   the eye lands on the words first. */
@keyframes step-num {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
  to   { opacity: .55; transform: none; }
}
.js-ready .ritual__step.reveal.is-in::before {
  animation: step-num .9s var(--ease-out) calc(var(--d) + .12s) backwards;
}

/* --- 23.5 scroll progress ------------------------------------------------
   Built by main.js, not the markup: it reports how far down the document you
   are and carries no information the page does not already show. */
.scroll-prog {
  position: fixed;
  inset: 0 0 auto 0;
  height: 2px;
  z-index: 90;
  background: linear-gradient(90deg, var(--c-jade), var(--c-clay));
  transform: scaleX(0);
  transform-origin: 0 50%;
  pointer-events: none;
}

/* --- 23.6 mobile call bar ------------------------------------------------
   It rides up once, on load. A CSS animation rather than a JS class, so the
   two phone-tap conversions are never hidden behind a script that failed —
   with animations unsupported or blocked it is simply already in place. */
@keyframes bar-rise {
  from { transform: translateY(100%); }
  to   { transform: none; }
}
@media (max-width: 760px) {
  .callbar { animation: bar-rise .5s var(--ease-out) .35s backwards; }
}

/* --- 23.7 parallax -------------------------------------------------------
   main.js writes --shift onto anything carrying [data-parallax]. The element
   must own no other transform, or this will overwrite it. */
[data-parallax] { transform: translate3d(0, var(--shift, 0px), 0); will-change: transform; }


/* --- 23.8 reduced motion -------------------------------------------------
   Everything above is decoration. Under prefers-reduced-motion the page must
   look identical at rest, so each effect is returned to its END state rather
   than merely sped up. main.js also skips the parallax loop and snaps the
   counters straight to their final value. */
@media (prefers-reduced-motion: reduce) {
  .js-ready .reveal { opacity: 1 !important; transform: none !important; transition: none; }
  .js-ready .hero__title .line > span { transform: none !important; transition: none; }

  .ph img, .btn, .brandmark__plate { transition: none !important; }
  .ph--live:hover img { transform: none; }
  .rail { scroll-behavior: auto; }

  .site-head.is-tucked { transform: none; }
  .ticker__track,
  .status.is-open .status__dot,
  .js-ready .reveal.is-in .ph > img,
  .js-ready .band.reveal.is-in .band__media img,
  .js-ready .ritual__step.reveal.is-in::before,
  .callbar { animation: none; }
  .js-ready .ritual__step.reveal.is-in::before { opacity: .55; }

  .btn--primary::after, .btn--pine::after { display: none; }
  .rail__item:hover, .spread__item:hover, .social a:hover { transform: none; }
  [data-parallax] { transform: none; }
  .scroll-prog { display: none; }
}


/* ═════════════════════════════════════════════════════════ 24 · PRINT ═══ */

/* Salons really do print their price list. This makes services.html come out
   as a clean two-page menu instead of a screenshot of a website. */
@media print {
  @page { margin: 14mm; }

  body { background: #fff; color: #000; font-size: 10.5pt; padding: 0; }
  body::before { display: none; }

  .site-head, .site-foot, .callbar, .band, .page-head__media,
  .svc-index, .btn, .btn-row, .filters, .rail-ctl, .skip-link,
  .form, .map, .social, .ticker, .scroll-prog { display: none !important; }

  .page-head { background: none; color: #000; padding-block: 0 1rem; }
  .page-head h1, .page-head p { color: #000; }

  .section { padding-block: .5rem; }
  .svc-layout { grid-template-columns: 1fr; }
  .menu-group { break-inside: avoid; }
  .menu-item { break-inside: avoid; padding-block: .3rem; }
  .menu-item__price { color: #000; }
  .menu-group__head { border-bottom: 1.5pt solid #000; }

  .ph, figure, img { display: none; }

  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 8pt; color: #555; }
  a[href^="#"]::after, a[href^="tel"]::after, a[href^="mailto"]::after { content: ""; }

  /* Nothing may print mid-animation: every motion hook is forced to its
     finished state, including the hero lines and anything parallaxed. */
  .reveal { opacity: 1 !important; transform: none !important; animation: none !important; }
  .hero__title .line > span { transform: none !important; }
  [data-parallax] { transform: none !important; }
}
