/* Mane Event - static rebuild of maneevent.co.nz
   Palette + type recovered from the live pawscare/Elementor site 2026-07-16.
   Text teal darkened (#00838B) and ink-on-orange used for WCAG AA contrast;
   decorative fills keep the original brand teal #00A4AD. */

:root {
  /* --teal and --teal-ink are the SAME hex on purpose, and the seam is kept.
     --teal is decorative (fills, icon silhouettes, ticks, the active nav item,
     the price tag). --teal-ink is interactive (links, .btn-teal, the field
     focus ring). They are equal today only because live-theme parity pinned
     both to #00A4AD in 2026-07-16. Teal-on-white currently fails AA for body
     text; if that is ever fixed, the fix darkens --teal-ink alone and every
     decorative fill must stay put. Merging them now would delete the place
     that fix has to land. */
  --teal: #00A4AD;        /* decorative fills, icons, chips with large text */
  --teal-ink: #00A4AD;    /* links, buttons, nav - matched to live theme #00A4AD (parity 2026-07-16) */
  --teal-deep: #00838B;   /* hover */
  --ink: #405B5B;         /* headings, dark sections */
  --ink-deep: #2C3E3E;    /* text on orange */
  --orange: #F69946;
  --orange-deep: #E8862F;
  --cream: #FCF9F5;
  --cream-2: #F8F1E7;
  --pale: #F4FCFC;
  --text: #616161;
  --muted: #8A8A8A;
  --danger: #C0392B;
  --peach: #FFCE9E;       /* footer hours on dark teal (4.9:1) */
  --white: #fff;
  --radius: 12px;
  --hairline: color-mix(in srgb, var(--ink) 12%, transparent);      /* card block separators on light */
  --hairline-dark: rgba(255, 255, 255, .25);                        /* card block separators on dark */
  --font-display: 'Paytone One', 'Nunito', sans-serif;
  --font-body: 'Nunito', sans-serif;
  --body-size: var(--fs-body);      /* identity line 32 */
  --body-leading: 1.875;            /* identity line 32 - the body line-height role */
  --page-gutter: clamp(24px, 3vw, 32px);
  --section-space: 90px;
  --section-space-compact: 64px;
  --layout-gap: clamp(40px, 5vw, 70px);
  --layout-gap-feature: 60px;
  --layout-gap-stack: 50px;
  --component-gap: 40px;
  --component-gap-compact: 34px;
  --card-gap: 30px;
  --nav-gap: 30px;
  --action-gap: 16px;
  --footer-size: var(--fs-sm);      /* identity line 87 - one footer size */
  --footer-leading: 1.6;            /* identity line 87 - one footer leading */

  /* ======================================================================
     CANONICAL TOKEN SET (DESIGN.md, adopted 2026-07-20).
     Declared here alongside the values above; the remaps that point the
     shipped rules at these names land in the batches that follow, so this
     block on its own changes nothing that renders.
     ====================================================================== */

  /* --- colour roles (DESIGN.md Section 2, "Proposed consolidation roles") --- */
  --line: #E2E0DC;          /* the one input + card border */
  --line-hover: #CDCAC4;    /* input hover border */
  --divider: #DFDCD9;       /* crisp 1px header divider (identity line 67) */
  --muted-strong: #757575;  /* darker muted: disabled feature rows, size-grid labels */
  --cream-3: #F3EADC;       /* accordion tile when it sits on a cream ground */
  --ink-overlay: #223A3D;   /* hero overlay base; alpha varies at the use site */

  /* --- radius (DESIGN.md Section 5; identity line 70) --- */
  --radius-control: 5px;     /* buttons, chips, inputs */
  --radius-feature-sm: 14px; /* large feature card */
  --radius-feature: 16px;    /* large feature card */
  --radius-pill: 999px;      /* the kicker dash */

  /* --- elevation, one ink base (DESIGN.md Section 4, The One-Base Rule) --- */
  --shadow-sm: 0 4px 14px rgba(64, 91, 91, .12);   /* low-lift cards */
  --shadow-md: 0 10px 30px rgba(64, 91, 91, .10);  /* default card elevation */
  --shadow-hover: 0 7px 18px rgba(44, 62, 62, .16);/* button lift (ink-deep is fine for a transient state) */
  --shadow-nav-dock: 0 4px 16px rgba(64, 91, 91, .10); /* identity line 68 EXACT - never folds */

  /* --- type ladder (DESIGN.md Section 3) --- */
  --fs-hero: clamp(2.09rem, 4.94vw, 3.762rem); /* home hero h1 only, signature exception; -5% again (Owner 2026-07-21) on top of the 2026-07-20 -12%, so net ~-16% from original. Ceiling 63.36 -> 60.19px */
  --fs-h1: clamp(2.1rem, 4.5vw, 3rem);     /* identity line 32 */
  --fs-h2: clamp(1.6rem, 3.4vw, 2.25rem);  /* identity line 32 */
  --fs-title: 1.5rem;      /* named card / menu titles */
  --fs-lg: 1.25rem;        /* global h3 + the loud CTA label */
  --fs-md: 1.1rem;         /* footer headings, price tag */
  --fs-control: 1.05rem;   /* button label + intro lead - NOT a title (identity line 34) */
  --fs-body: 1rem;         /* identity line 32; the ceiling for card/list/control copy */
  --fs-sm: .95rem;         /* the dominant small step - nav, labels, footer */
  --fs-xs: .875rem;        /* utility strip, micro labels */
  --fs-kicker: .82rem;     /* kept DISTINCT from --fs-xs (identity line 36) */
  --fs-2xs: .75rem;        /* micro / glyph-adjacent labels */
  /* Documented exception, not a ladder step: the legal pages' in-prose h2 sits
     below the --fs-h2 clamp floor. Named so the value has one home and the
     exception is visible rather than a raw literal. See .prose h2. */
  --fs-legal-h2: 1.35rem;

  --lh-heading: 1.25;  /* identity display leading */
  --lh-hero: 1.12;     /* hero signature exception */
  --lh-snug: 1.5;      /* card headings + labels */
  --lh-tight: 1.1;     /* tight numeric / chip */

  --ls-kicker: .1em;   /* identity line 36 */
  --ls-caps: .05em;    /* uppercase micro-labels */

  /* --- containers + the one reading measure (DESIGN.md Section 3) --- */
  --container: 1200px;
  --measure: 640px;       /* The One-Measure Rule - identity line 26 pins .intro here */
  --measure-wide: 900px;  /* centred opening band */
  --form-max: 980px;      /* the shared enquiry form */

  /* --- spacing, 4pt base under the existing role tokens above --- */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 48px;
  --space-8: 64px;
  --space-9: 96px;

  /* --- motion (DESIGN.md Section 5, "Motion") --- */
  --dur-fast: .2s;        /* the dominant micro band */
  --dur-nav: .22s;        /* identity line 68 EXACT */
  --dur-fade: .18s;       /* identity line 82 breed fade */
  --dur-accordion: .35s;
  --dur-reveal: .65s;
  --dur-crossfade: .6s;
  --dur-hero: .8s;
  --dur-drift: 72s;       /* identity line 72 EXACT */
  --ease-standard: ease;
  --ease-reveal: cubic-bezier(.22, .61, .36, 1);
  --ease-linear: linear;

  /* --- breakpoints: NAMED VALUES ONLY, applied by hand ---------------------
     Custom properties cannot drive an @media condition, so these are the
     documented set every query in this file must use, not live tokens:
       --bp-xs    390px   narrow phone
       --bp-sm    600px   (601px lower bound where a range is needed)
       --bp-md    900px   nav collapse
       --bp-lg    1024px  main two-column stack (1025px = the min-width side)
       --bp-wide  1280px  the deliberate wide threshold the price-chip hang needs
     Sanctioned outlier: the Turnstile 379px fallback is identity-fixed
     (identity line 59) and stays exact.
     --------------------------------------------------------------------- */
}

@font-face {
  font-family: 'Flaticon';
  src: url('../fonts/Flaticon.woff2') format('woff2'),
       url('../fonts/Flaticon.woff') format('woff');
  font-weight: normal;
  font-style: normal;
  font-display: block;
}

[class^="fi-"]::before, [class*=" fi-"]::before {
  font-family: 'Flaticon';
  font-style: normal;
  font-weight: normal;
  line-height: 1;
  display: inline-block;
}
.fi-checked::before  { content: "\f106"; }
.fi-call::before     { content: "\f10f"; }
.fi-check::before    { content: "\f118"; }
.fi-paw::before      { content: "\f13c"; }
.fi-dog::before      { content: "\f13d"; }
.fi-house::before    { content: "\f140"; }
.fi-grooming::before { content: "\f141"; }

/* ---------- base ---------- */

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--body-size);
  line-height: var(--body-leading);   /* live theme body: 30px on 16px (parity 2026-07-16) */
  color: var(--text);
  background: var(--white);
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; height: auto; }
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 400;
  color: var(--ink);
  line-height: var(--lh-heading);
  margin: 0 0 .5em;
}
h1 { font-size: var(--fs-h1); }
/* live theme section headlines are 36px (measured). Was maxing at 44px. */
/* Section headings get +50% breathing room to the copy beneath: .5em -> .75em
   (Owner 2026-07-21). Scoped to h2 rather than the shared h1/h2/h3/h4 rule -
   the ask was about this kind of title, the section heading that introduces a
   block of body copy, not every heading level on the site. */
h2 { font-size: var(--fs-h2); margin-bottom: .75em; }
h3 { font-size: var(--fs-lg); }
p { margin: 0 0 1em; }
a { color: var(--teal-ink); }
a:hover { color: var(--teal-deep); }
ul { padding: 0; margin: 0; list-style: none; }

.container { max-width: var(--container); margin: 0 auto; padding: 0 var(--page-gutter); }
/* keep running text inside a readable measure when columns stack.
   The One-Measure Rule: 68ch resolved to --measure 640px, so these paragraphs
   run slightly wider than before. */
.split p, .acc-panel p, .prose p { max-width: var(--measure); }
/* grid/flex children may shrink below min-content - prevents 320px blowout */
.split > *, .contact-grid > *, .cards-3 > *, .info-row > * { min-width: 0; }
.section { padding: var(--section-space) 0; }
.section-cream { background: var(--cream); }
.center { text-align: center; }
.intro { max-width: var(--measure); margin: 0 auto 1.5em; }
.page-intro { max-width: var(--measure-wide); margin: 0 auto; text-align: center; }
/* was 700px - resolved to the single --measure, so the opening column narrows */
.page-intro-copy { max-width: var(--measure); margin: 0 auto; }
/* The booking intro is 690px of text, so the 640px house measure stranded the
   final word - "Monday." sat alone on a 60px last line (Owner 2026-08-01).
   700px is the NARROWEST step that pulls it up: measured at 640/660/680/700/720,
   and it is still two lines at 680, so the /pricing-update page's own 680px
   override would not have fixed this one. Scoped to the booking opening; every
   other .intro keeps the One-Measure Rule. */
#booking .page-intro-long { max-width: 700px; }
.page-intro-copy p:last-child { margin-bottom: 0; }

/* Visually-hidden, screen-reader-visible. Promoted from the services page's
   own <style> block (where the visually-hidden h1 lives) so the same recipe is
   available site-wide for headings that exist only to keep the outline whole. */
.sr-only {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

.skip-link {
  position: absolute; left: -9999px; top: 0;
  background: var(--ink); color: #fff; padding: 10px var(--space-4); z-index: 200;
}
.skip-link:focus { left: 0; color: #fff; }

/* ---------- buttons + chips ---------- */

.btn {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--font-display);
  font-size: var(--fs-control);
  padding: 14px 30px;
  border-radius: var(--radius-control);
  border: 0;
  cursor: pointer;
  text-decoration: none;
  transition: background .2s ease, transform .15s ease, box-shadow .2s ease;
}
.btn:hover { transform: translateY(-2px); box-shadow: var(--shadow-hover); }
.btn:active { transform: translateY(0) scale(.98); box-shadow: none; }
.btn:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
/* A .btn that carries no action (Owner 2026-07-31, the services Call button).
   It keeps the shape so the number stays prominent, but drops every affordance
   that promises a click: no lift, no press, no focus ring, no pointer. Identity
   section 4 forbids dead-end controls - this is the alternative to one, not an
   exception to it. Not focusable either: it is a <span>, so it never enters the
   tab order and a keyboard user is not stopped on something that does nothing. */
.btn.is-static { cursor: default; }
.btn.is-static:hover { transform: none; box-shadow: none; }
.btn.is-static:active { transform: none; }
.btn-teal.is-static:hover { background: var(--teal-ink); }

/* One ring site-wide (DESIGN.md Section 5, shipped 2026-07-20 after critique
   round 1): every link and button gets the 3px ink ring at 3px offset instead
   of the UA default, which measured ~2.6:1 on the ink footer ground (below
   the 3:1 WCAG 2.4.11 floor). Dark grounds override to white. Component rules
   with higher specificity (accordion teal, contact-card ink) still win. */
a:focus-visible, button:focus-visible {
  outline: 3px solid var(--ink); outline-offset: 3px;
}
.site-footer a:focus-visible { outline-color: #fff; }
.btn-teal { background: var(--teal-ink); color: #fff; }
.btn-teal:hover { background: var(--teal-deep); color: #fff; }
/* The orange FILL stays exactly #F69946 (live parity 2026-07-16, Owner
   direction). What changes is the LABEL: white on orange measured 2.20:1,
   which fails AA at any size. Identity line 40 already names --ink-deep
   #2C3E3E as "text on orange", and that pairing measures 5.13:1 and passes.
   So this is the identity's own colour being applied, not a new one.
   This is the round's one taste call - revert this commit alone if the
   darker label reads wrong on the hero. */
.btn-orange { background: var(--orange); color: var(--ink-deep); font-size: var(--fs-lg); }
.btn-orange:hover { background: var(--orange-deep); color: var(--ink-deep); }
.btn .fi-paw::before { font-size: 1.1em; }

/* Kicker - the house section tag (identity v1.1, Owner 2026-07-17). Replaces the
   old boxed .chip pills: an orange dash + uppercase display-face label, UNBOXED.
   Ink text on light grounds; .kicker-light (white) on photo/dark grounds. Sits
   above every main section heading - left in left blocks, centered inside .center
   (inline-flex, so text-align centers it as a unit). */
.kicker {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--font-display);
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--ink);
  margin: 0 0 14px;
}
.kicker::before {
  content: "";
  width: 28px; height: 3px; border-radius: var(--radius-pill);
  background: var(--orange);
  flex: none;
}
/* The two hero text-shadows below stay literal rgba(20,40,42,x) = #14282A.
   The token annex lists them under --ink-overlay, but --ink-overlay is #223A3D:
   folding them would shift the shadow colour, not just name it. Kept exact;
   promoting a second shadow base is an Owner call, not a mechanical remap. */
.kicker-light { color: #fff; text-shadow: 0 2px 18px rgba(20, 40, 42, .45); }

/* ---------- preloader ---------- */
/* Origin pawscare theme preloader (mechanics from the archived
   pluggable_preloader.min.css): staggered paw-print fade, 2750ms loop, container
   rotated -30deg on near-white. Paws are the origin's teal #63aeb1. Fades out and
   is removed on window load (see main.js). PENDING OWNER CONFIRM on the preview. */
#preloader-wrapper {
  position: fixed; inset: 0; width: 100%; height: 100%;
  background-color: #fefefe; z-index: 10000;
  opacity: 1; transition: opacity .4s ease;
}
#preloader-wrapper.is-done { opacity: 0; pointer-events: none; }
.preloader-container {
  position: relative; width: fit-content;
  left: 50%; top: 50%; transform: translate(-50%, -50%) rotate(-30deg);
}
.preloader-container > div {
  position: relative; display: inline-block;
  color: #63aeb1; opacity: 0;
  animation: preloader-paw 2750ms ease-in-out infinite;
}
.preloader-container > div > svg { transform: rotate(105deg); display: block; width: 50px; height: 50px; fill: currentColor; }
.preloader-container > div:nth-child(odd) > svg { transform: rotate(90deg) translateX(-150%) rotate(-15deg); }
.preloader-container > div:nth-child(1) { animation-delay: 0s; }
.preloader-container > div:nth-child(2) { animation-delay: .25s; }
.preloader-container > div:nth-child(3) { animation-delay: .5s; }
.preloader-container > div:nth-child(4) { animation-delay: .75s; }
.preloader-container > div:nth-child(5) { animation-delay: 1s; }
@keyframes preloader-paw { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 0; } }

/* ---------- top bar + header ---------- */

/* Live theme (measured on origin 2026-07-16): the utility strip and the nav are
   both cream #FCF9F5, but the strip is set apart by (a) a 1px divider line
   #DFDCD9 at its base and (b) a soft downward box-shadow that bleeds ~38px into
   the nav (nav top reads ~#F4F1ED fading to full cream). Round-1 unified the two
   bands to flat cream and dropped both cues, which is why the strip lost its
   separation. The orange top border is 5px on both (measured identical); the
   perceived "thicker" border is this restored separation, not a wider border.
   Strip height on the origin is 37px (5px border + 32px cream). */
.topbar {
  background: var(--white);   /* white strip, Owner 2026-07-17 (deviation from live-parity cream) */
  /* --orange, not the old #E99D57: identity line 55 says the strip and the
     card top rules move together, and every card rule is --orange #F69946. */
  border-top: 3px solid var(--orange);  /* 5px->3px Owner 2026-07-17 (deliberate deviation from live parity; moves with the card rules) */
  border-bottom: 1px solid var(--divider);
  /* box-shadow removed, Owner 2026-07-17: the soft downward bleed read as a
     gradient on the nav band. The 1px divider + the white-to-cream step now
     carry the separation on their own. */
  position: relative; z-index: 101;
  font-size: var(--fs-xs);
}
.topbar .container {
  display: flex; justify-content: flex-end; align-items: center; gap: 28px;
  padding-top: 1px; padding-bottom: 1px;
}
.topbar span[class^="fi-"] { color: var(--teal-ink); margin-right: 7px; font-size: .9em; }
.topbar .topbar-ico { color: var(--teal-ink); margin-right: 7px; flex: none; }
.topbar span { display: inline-flex; align-items: center; }
.topbar a { color: var(--text); text-decoration: none; display: inline-block; padding: 1px 0; }
.topbar a:hover { color: var(--teal-ink); }

/* Sticky, shrinking nav (Owner 2026-07-17): the topbar (address/phone) is NOT
   sticky - it scrolls away above the nav; the nav then sticks to the top and
   shrinks ~20% (logo 90->72, padding 16->13; total ~122->98). The menu font is
   UNCHANGED. A micro transition animates the size change. */
.site-header {
  background: var(--cream);   /* live theme: utility bar + nav are one cream band, not white (parity 2026-07-16) */
  position: sticky; top: 0; z-index: 100;
  transition: box-shadow .22s ease;
}
.site-header .container {
  display: flex; align-items: center; justify-content: space-between;
  padding-top: 16px; padding-bottom: 16px;
  transition: padding-top .22s ease, padding-bottom .22s ease;
}
.logo img { height: 90px; width: auto; display: block; transition: height .22s ease; }
.site-header.nav-shrink { box-shadow: var(--shadow-nav-dock); }   /* identity line 68 exact - does not fold into the card set */
/* 13px is identity-fixed (identity line 68: container padding 16 -> 13, header
   122 -> 98). It does NOT snap to the 4pt scale. */
.site-header.nav-shrink .container { padding-top: 13px; padding-bottom: 13px; }
.site-header.nav-shrink .logo img { height: 72px; }
@media (prefers-reduced-motion: reduce) {
  .site-header, .site-header .container, .logo img { transition: none; }
}

.main-nav ul { display: flex; gap: var(--nav-gap); }
.main-nav ul a {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--ink);
  text-decoration: none;
  padding: 6px 0;
}
.main-nav ul a:hover { color: var(--teal-deep); }
.main-nav ul a.active { color: var(--teal); }   /* live theme: active nav is bright teal #00A4AD */

.nav-toggle {
  display: none;
  /* 8px 9px, not 8px: the 26px bars + 18px horizontal padding make the hit
     area exactly 44px wide. Height is already 55px. (WCAG 2.5.8) */
  background: none; border: 0; cursor: pointer; padding: 8px 9px;
}
.nav-toggle span {
  display: block; width: 26px; height: 3px; border-radius: var(--radius-pill);   /* rounded line ends; pill is the on-scale radius for this */
  background: var(--ink); margin: 5px 0; transition: transform .2s ease, opacity .2s ease;
}
.nav-open .nav-toggle span:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.nav-open .nav-toggle span:nth-child(2) { opacity: 0; }
.nav-open .nav-toggle span:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

/* ---------- hero (home) ---------- */

/* Full-bleed static hero (Owner 2026-07-20): edge-to-edge photo band, the
   live-site composition, WITHOUT the carousel machinery - one slide renders
   from hero.json slides.0 and there is nothing to rotate, so no arrows, no
   track, no JS. Measurements carried over from the retired .hero-carousel
   (700px band, 640px copy column, left-dark gradient overlay). The carousel
   engine in main.js still self-guards on `.hero-carousel` and stays inert. */
.hero-wide {
  position: relative;
  overflow: hidden;
  background: var(--ink);
}
.hero-wide .hero-slide {
  position: relative; min-height: 700px;
  display: flex; align-items: center;
  background-size: cover; background-repeat: no-repeat; background-position: center right;
}
.hero-overlay {
  position: absolute; inset: 0;
  background: linear-gradient(90deg, rgba(34, 58, 61, .62) 0%, rgba(34, 58, 61, .25) 45%, rgba(34, 58, 61, 0) 70%);
}
.hero-wide .hero-slide .container { position: relative; z-index: 2; width: 100%; }
/* 640 -> 700px (Owner 2026-07-21): the three-line hero "We know their coat, /
   their habits and their / favourite hello." needs 687px on its longest line at
   the capped 63.36px hero size, so at 640px line 2 wrapped and orphaned "their"
   onto a fourth line. --fs-hero is an Owner-settled value (net -12%, 2026-07-20)
   and is deliberately NOT touched; the container gives way instead. Only binds
   above ~1135px viewport, where --fs-hero has hit its clamp ceiling. */
.hero-wide .hero-copy { max-width: 700px; padding: 90px 0 150px; transform: translateY(clamp(20px, 2.5vw, 35px)); }  /* sit ~5% lower on desktop (Owner 2026-07-20); reset to none on mobile below */
.hero-wide h1 {
  color: #fff;
  font-size: var(--fs-hero);   /* hero signature exception, unchanged */
  line-height: var(--lh-hero);
  margin: 0 0 18px;
  text-shadow: 0 2px 28px rgba(20, 40, 42, .45);
}
/* --lh-snug, not the inherited --body-leading (Owner 2026-07-21): 1.875 on a
   20px two-line lede rendered 37.5px apart and read as drifting. 30px holds the
   two lines together as one statement under the hero line. */
.hero-lede { font-size: var(--fs-lg); line-height: var(--lh-snug); margin: 0 0 32px; }
.hero-wide .hero-lede { color: rgba(255, 255, 255, .94); text-shadow: 0 1px 18px rgba(20, 40, 42, .5); }
.hero-wide .btn { font-size: calc(var(--fs-lg) * 0.85); padding: 14px 27px; }
@media (max-width: 1024px) {
  .hero-wide .hero-slide { min-height: 540px; background-position: 72% center; }
  .hero-overlay { background: linear-gradient(90deg, rgba(34, 58, 61, .72) 0%, rgba(34, 58, 61, .45) 55%, rgba(34, 58, 61, .18) 100%); }
  .hero-wide .hero-copy { padding: 70px 0 110px; transform: none; }
}
/* Phones only: drop the hero's forced <br>s and let the line flow (Owner
   2026-07-21). The three-line break is authored for desktop; on a ~327px
   measure at the 35.2px --fs-hero floor the forced breaks stack on top of
   natural wrapping and orphan "coat," and "their" onto lines of their own
   (measured 5 lines: 251/88/289/87/271). Flowing gives 4 clean lines. The
   breaks still fit above ~430px, so the cut is at 480 rather than the 1024
   hero breakpoint - no desktop or tablet loses the authored shape.
   The lede is the same story and was missed on the first pass (Owner spotted
   it): at 20px both its authored lines overflow the 327px measure - 392px and
   363px - so the forced break left "ways," stranded. 4 lines -> 3. Both need
   the space BEFORE the <br> in content/hero.json, or hiding the break welds
   the words either side of it together. */
@media (max-width: 480px) {
  .hero-wide h1 br,
  .hero-wide .hero-lede br { display: none; }
}

/* Five floating doodle layers matching the origin's Slider Revolution hero
   (measured layer positions 2026-07-16): a yarn/spiral top-centre, the streak
   "swoosh" top-right, a bone on the left edge, a bone bottom-centre, and a paw
   bottom-right. The swoosh is the origin's one looping layer; all five drift
   gently here (see the float animation below). */
.hero-wide .doodle { position: absolute; opacity: .9; pointer-events: none; z-index: 3; }
.doodle-swoosh { top: 7%; right: 8%; width: 96px; }
.doodle-yarn   { top: 10%; left: 47%; width: 46px; }
.doodle-bone-l { top: 38%; left: 1%; width: 50px; }
.doodle-bone-c { bottom: 16%; left: 36%; width: 58px; }
.doodle-paw    { bottom: 20%; right: 9%; width: 50px; }

/* ---------- feature cards ---------- */

/* Live theme (measured 2026-07-16): the 3 service cards are pulled UP over the
   hero by margin-top:-50px so their top ~third overlaps the hero photo, and the
   hero shows THROUGH the gaps between the card tops. Below that overlap the page
   is cream, flowing continuously into the about section. Round-1 filled the whole
   band with flat cream, which painted over the hero in the overlap zone (cards
   sat entirely on cream, no hero visible between them). The fix: a vertical
   background that is transparent for the top 50px (the overlap strip - hero shows
   through) and cream from 50px down (no white/cream bar below). */
.feature-cards {
  margin-top: -50px;
  position: relative; z-index: 3;
  padding-bottom: 90px;
  background: linear-gradient(to bottom, transparent 0, transparent 50px, var(--cream) 50px);
}
.cards-3 {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--card-gap);
}
.card {
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  border-top: 3px solid var(--orange);   /* house card convention (identity v1.2, 3px) */
  padding: 30px 28px;
}
.icon-card { display: flex; gap: 16px; align-items: flex-start; }   /* 16 not 20: reclaims text width so card 3 body holds 3 lines with the larger icon (Owner 2026-07-20) */
.icon-card .icon { font-size: 46px; color: var(--teal); line-height: 1; }
/* Custom brand card icons (Owner-supplied SVGs, 2026-07-20): masked so they
   paint in --teal and follow the token rather than the SVG's baked fill. */
.card-icon {
  flex: none; width: 59px; height: 59px; background-color: var(--teal);
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.icon-paws { -webkit-mask-image: url(../img/icons/paws.svg); mask-image: url(../img/icons/paws.svg); }
.icon-ball { -webkit-mask-image: url(../img/icons/ball.svg); mask-image: url(../img/icons/ball.svg); }
.icon-dog  { -webkit-mask-image: url(../img/icons/dog-head.svg); mask-image: url(../img/icons/dog-head.svg); }
.icon-card h3 { margin-bottom: .35em; line-height: var(--lh-snug); }  /* live theme card title lh 30px on 20px */
/* Card bodies keep the body SIZE, WEIGHT and COLOUR (they are the body
   paragraph in every other respect) but take --lh-snug rather than
   --body-leading: 24px not 30px. Leading wants to tighten as the measure
   narrows, and the card column is ~228px against the about band's ~476px, so
   identical leading read as airy in the card and normal in the prose. Owner
   2026-07-21, after measuring the two and finding the type already identical.
   text-wrap: balance evens the line lengths so no card bulges in the middle. */
.icon-card p { margin: 0; font-size: var(--fs-body); line-height: var(--lh-snug); text-wrap: balance; }

/* ---------- about / split ---------- */

.split {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--layout-gap); align-items: center;
}
.split-start { align-items: start; }
/* About: give the pug column ~600px on desktop so the image renders at the
   origin's measured size (~600 wide); guarded by min-width so the text column
   is never squeezed. Below this it uses the even 1fr/1fr split, then stacks. */
/* The pug PNG has ~100px of transparent space on its left (the solid dog sits
   16.7% in) - so at the 600px figure column, shift the figure left by that
   100px to flush the VISIBLE dog to the column's left edge (Owner 2026-07-17).
   overflow-x:clip on the section stops the paw watermark (now bleeding further
   left) from ever adding a horizontal scrollbar. */
#about { overflow-x: clip; }
@media (min-width: 1100px) {
  #about .split { grid-template-columns: 600px 1fr; gap: var(--layout-gap-feature); }
  .about-figure { margin-left: -100px; }
}
/* Copy column lifted 50px against the photo (Owner 2026-07-21).
   Offset positioning, NOT transform: this element carries [data-reveal], which
   animates transform and resets it to none on reveal, so a transform here would
   be wiped the moment the section scrolled into view.
   Gated at 1025px to match where .split actually becomes two-column - the
   neighbouring #about block is 1100px, and using that would have left the
   1025-1099px band two-up but unlifted. Below 1025 the columns stack and the
   lift would just pull the copy into the photo. */
@media (min-width: 1025px) {
  #about .about-copy { position: relative; top: -50px; }
}
.about-figure { text-align: left; }
/* The sticker PNGs are full-canvas overlay layers (670x606) from the original
   theme, painted for that theme's dog crop. This pug is a different aspect
   (600x691, portrait), so overlaying the canvases inset:0/width:100% lands the
   glyphs off the dog. Each glyph is nudged onto THIS pug with the CSS
   `translate` property (percentages relative to the sticker box, so placement
   scales with the image at every width) to match the LIVE theme: the crown
   floats just above the top of the head (slightly right of centre) and the bone
   floats mid-left, outside the pug.
   Pug SIZE: the origin renders the about image at ~600px wide on desktop
   (measured); max-width:460 rendered it too small ("dog still smaller"). Bumped
   to 600 (constrained by the column at narrow widths, so mobile is unaffected).
   MOTION (corrected 2026-07-16): the crown and bone stickers DO animate on the
   origin - a gentle continuous float. Proven by frame-diffing the live about
   section (only the crown + bone regions change frame-to-frame; the pug and the
   background ellipses are static). The round-2 "stickers are static" verdict came
   from computed-CSS probing, which cannot see JS/SR-driven inline-transform
   animation and was wrong. `transform` is animated here; `translate` (used for
   placement) is a separate property, so the float composes over the placement. */
.figure-stack {
  position: relative;
  display: inline-block;
  width: 100%;
  max-width: 600px;
}
.figure-stack .main-img { position: relative; z-index: 1; width: 100%; }
.figure-stack .sticker { position: absolute; inset: 0; width: 100%; height: auto; pointer-events: none; }
.sticker-paw { z-index: 0; transform: translate(-14%, -6%) scale(1.15); }
.sticker-crown { z-index: 2; translate: -9% -12%; animation: sticker-float 3.6s ease-in-out infinite; }
.sticker-bone { z-index: 2; translate: -8% 26%; animation: sticker-float 4.4s ease-in-out infinite .7s; }

@keyframes sticker-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-9px) rotate(2.5deg); }
}

.checklist {
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px 28px; margin-top: 28px;
}
.checklist li { display: flex; align-items: flex-start; gap: 12px; }
/* Glyph sizes (here and on .features .tick) stay literal: they size an icon
   inside a fixed box, not text on the ladder. Folding them to --fs-2xs would
   resize the mark for no typographic reason. */
.checklist .fi-check::before {
  font-size: .8rem; color: #fff;
  background: var(--teal);
  width: 26px; height: 26px; border-radius: var(--radius-control);
  display: inline-flex; align-items: center; justify-content: center;
}
.checklist span.label {
  min-width: 0;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: var(--lh-snug);
  color: var(--ink);
}
.checklist .fi-check { flex: none; margin-top: 1px; }
.checklist-1col { grid-template-columns: 1fr; margin-top: 0; }

/* ---------- pricing ---------- */

/* The 520px paw watermark that sat here is REMOVED (Owner 2026-07-21): on the
   white ground it collided with the cards' top-left corners rather than sitting
   behind them. The footer keeps its two paws - those are on the dark --ink
   ground, where the shape reads as texture instead of clutter. */
#pricing { position: relative; }
/* ---------- pricing section (rebuilt 2026-07-21) ----------
   The package cards are now the SAME component as the three under the hero -
   .cards-3 / .card / .icon-card, shared verbatim. The only difference is what
   stands in the icon's 59px slot: the price chip. Everything the old merged
   .price-card and .size-grid did is gone; the breed lookup replaced it. */

/* One source of truth for the chip's geometry. The included block below derives
   its indent from these three values, so the tick rows always start exactly where
   the heading and description start - change the chip and the alignment follows. */
.pricing-cards {
  margin-top: 44px;
  --chip-w: 80px;
  --chip-gap: 16px;
  --chip-pull: -28px;          /* flush with the card's left edge */
}
/* The chip OVERFLOWS the card's left edge (Owner 2026-07-21), it does not sit
   flush inside it. Gated to wide viewports for the same reason the pre-rebuild
   design gated its -54px hang: below this there is no gutter to spare and the
   first card's chip would park against the viewport edge. */
@media (min-width: 1280px) {
  .pricing-cards { --chip-pull: -46px; }   /* 18px proud - eased 6px inward, Owner 2026-07-21 */
}
/* The chip is square and sits close to the card's left edge, not inset by the
   card's full 28px padding - it reads as a stamped price tag rather than a
   panel floating inside the card. Owner 2026-07-21. */
/* Centred against the text block, not flex-start: the chip sits vertically
   centred in the zone between the orange top rule and the grey divider
   (Owner 2026-07-22). Scoped - the hero feature cards keep flex-start. */
.pricing-cards .icon-card { align-items: center; }
.price-chip {
  background: var(--ink);
  color: #fff;
  font-family: var(--font-display);
  border-radius: var(--radius-control);   /* identity line 70: chips are 5px */
  line-height: var(--lh-tight);
  flex: none;
  /* 80x66 and FLUSH with the card's left edge, not tucked inside it. The card
     pads 28px, so -28px cancels that exactly. The old -12px top lift (the
     "chip-hang") is retired: the chip now centres vertically between the card's
     orange top rule and the grey divider (Owner 2026-07-22), via align-items on
     .pricing-cards .icon-card below. */
  width: var(--chip-w); height: 66px;
  margin-left: var(--chip-pull);
  /* Row-centring alone leaves the chip 4px low of the true orange-rule-to-divider
     midpoint (card pads 30 on top vs 22+1 above the rule). Flex centres the margin
     box, so -8 on top lifts the visual box the missing 4px. Measured 2026-07-22. */
  margin-top: -8px;
  padding: 0 10px;
  /* Left-aligned, not centred (Owner 2026-07-21): "from" and the number share one
     left edge, so the chip reads as a stamped label rather than a centred tile. */
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  text-align: left;
  box-sizing: border-box;
}
/* Body face at normal weight, NOT the chip's inherited --font-display: Paytone
   One is a single heavy weight and read as bold, which the Owner did not want on
   this label. */
.price-chip .from {
  display: block; font-family: var(--font-body); font-weight: 400;
  font-size: var(--fs-2xs); text-transform: lowercase; letter-spacing: var(--ls-caps);
}
/* 1.5rem: the chip is 80px wide with 10px padding, so the number has 60px to work
   in and must still clear the widest real price - $200, the Full Groom XL. Sized
   against that worst case, not against the $35 in the reference. */
.price-chip .amount { font-size: 1.5rem; display: block; font-variant-numeric: tabular-nums; }
/* The dog's name lands inside the chip once the lookup answers, so the chip
   carries the whole result rather than needing a second line elsewhere. */
/* Collapsed entirely when empty, so at rest the chip centres exactly the two
   lines that are there. Left in flow it contributed its 2px top margin and threw
   the group 2px high. */
.price-chip .price-yours:empty { display: none; }
.price-chip .price-yours {
  display: block; font-family: var(--font-body); font-size: 10px; font-weight: 700;
  letter-spacing: 0; text-transform: none; color: var(--pale);
  line-height: 1.25; margin-top: 2px;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pricing-cards .card-rule { height: 1px; background: var(--divider); margin: 22px 0 16px; }
/* The included block starts where the HEADING and DESCRIPTION start, not at the
   card's padding edge (Owner 2026-07-21). That column begins after the chip and
   its gap, so the indent is derived rather than hand-typed: pull + width + gap.
   The grey rule deliberately stays full width, spanning under both columns. */
.pricing-cards .incl-label,
.pricing-cards .features {
  margin-left: calc(var(--chip-pull) + var(--chip-w) + var(--chip-gap));
}
.pricing-cards .incl-label {
  font-family: var(--font-display); font-size: var(--fs-2xs);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--muted-strong); margin-top: 0; margin-bottom: 6px;
}
.features { margin: 0; padding: 0; list-style: none; }
.features li {
  display: flex; align-items: center; gap: 14px;
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-sm); color: var(--ink);
  padding: 8px 0;
}
/* No disc behind the glyph (Owner 2026-07-21): the tick itself is teal and the
   dash is grey, drawn straight onto the card. The 24px box is KEPT even though
   nothing fills it - it is what holds the row's left edge in line with the
   heading above, so shrinking it would pull every label out of alignment. The
   glyph grows from .78rem, which was sized to sit inside the old disc. */
.features .tick::before {
  font-family: 'Flaticon'; content: "\f118";
  font-size: 1.1rem; color: var(--teal);
  width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  flex: none;
}
.features li.off { color: var(--muted-strong); }
/* "Not included" keeps its second channel beyond colour (critique round 1): the
   glyph is an en dash rather than a tick, and the markup carries an sr-only
   prefix, so the meaning survives for anyone who cannot see the difference. */
.features li.off .tick::before {
  content: "\2013"; font-family: var(--font-body); font-weight: 700;
  font-size: 1.25rem; color: var(--muted-strong);
}

/* ---------- the breed lookup (Owner 2026-07-31) ----------
   ONE field, the /pricing-update tool's combobox. The two-step .pl flow that
   stood here - crumbs, panes, size chips, back links - is gone, along with its
   markup and its script, so its styles go too rather than linger as dead rules.

   The native <select> is styled to match the input the script swaps in, so the
   no-JS fallback is not a visibly poorer control than the enhanced one. */
.fd-field { position: relative; max-width: 440px; margin: 0 auto; text-align: left; }
.fd-label {
  display: block; font-family: var(--font-body); font-weight: 700; color: var(--ink);
  font-size: .9rem; letter-spacing: .01em; margin-bottom: 8px;
}
.fd-select {
  width: 100%; box-sizing: border-box;
  font-family: var(--font-body); font-size: var(--fs-body); color: var(--ink-deep);
  padding: 14px 46px 14px 16px;
  border: 2px solid var(--line); border-radius: var(--radius-control);
  background-color: var(--pale);
  appearance: none; -webkit-appearance: none; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9'%3E%3Cpath fill='%232C3E3E' d='M7 9 0 2l2-2 5 5 5-5 2 2z'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 16px center;
  transition: border-color .18s ease;
}
.fd-select:hover { border-color: var(--line-hover); }
.fd-select:focus-visible { outline: 3px solid var(--teal); outline-offset: 2px; border-color: var(--teal-deep); }
.fd-select::placeholder { color: var(--muted-strong); }
/* The script's input carries no chevron: there is nothing to drop down until a
   character is typed, so the affordance would be a lie at rest. */
.fd-combo-input { background-image: none; padding-right: 16px; cursor: text; }
.fd-combo-list {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 30;
  margin: 6px 0 0; padding: 6px; list-style: none;
  background: var(--white);
  border: 2px solid var(--line); border-radius: var(--radius-control);
  box-shadow: var(--shadow-md);
  max-height: 320px; overflow-y: auto; text-align: left;
}
.fd-combo-list [role="option"] {
  padding: 9px 12px; border-radius: var(--radius-control); cursor: pointer;
  color: var(--ink-deep); font-size: var(--fs-sm); line-height: 1.4;
}
.fd-combo-list [role="option"]:hover,
.fd-combo-list [role="option"].is-active { background: var(--pale); color: var(--teal-deep); }
.fd-combo-empty { padding: 10px 12px; color: var(--muted-strong); }

.fd-selected {
  font-family: var(--font-display); color: var(--ink-deep);
  font-size: 1.05rem; margin: 20px 0 0;
}
/* Moved out of the lookup to below the cards (Owner 2026-07-21): it qualifies
   every price in the section, so it belongs to the section, not to the search box. */
.pricing-note {
  max-width: 760px; margin: 34px auto 0;
  font-size: var(--fs-xs); line-height: 1.65; color: var(--muted-strong);
  text-align: center;
}

@media (max-width: 1024px) {   /* --bp-lg: the cards stack with the rest of the site */
  .pricing-cards { max-width: 560px; margin-left: auto; margin-right: auto; }
}

/* ---------- "Find your dog" card (Owner 2026-07-31) ----------
   The pricing section is now ONE card with the three packages inside it,
   adapting the /pricing-update tool shell to the two public pages. House card
   convention applies at both levels: 3px orange top rule, a FULL-WIDTH hairline
   under the title block (the head carries the padding, not the card), and INSET
   hairlines inside the package cards.

   The rules above (.pricing-cards, .price-chip) no longer match any markup -
   the partial that used them was rewritten. They are left in place rather than
   deleted so the previous composition can be restored from one file; retire
   them deliberately, not as a drive-by cleanup. */
.fd {
  max-width: 900px; margin: 44px auto 0;
  background: var(--white);
  border: 1px solid var(--line);
  border-top: 3px solid var(--orange);
  border-radius: 16px;
  box-shadow: var(--shadow-md);
  padding: 0 36px 34px;
  text-align: center;
}
/* Negative side margins pull the head out to the card's own edges so its
   hairline runs edge to edge, while the head keeps the horizontal padding.
   Same device as .pu-tool-head on the transition page. */
/* No bottom hairline (Owner 2026-08-01). With the field label gone, the heading
   and the search box read as one instruction plus its control - a rule between
   them separated a thing from its own answer. The negative side margins stay:
   they are what let the head sit on the card's own edges. */
.fd-head {
  margin: 0 -36px 22px; padding: 30px 36px 0;
}
/* 600px, not the 540px this inherited: the heading renders 541px wide, so the
   old cap was ONE pixel short and broke "service." onto its own line (measured
   2026-08-01). 600 leaves headroom for the Owner to reword it in the CMS
   without it silently wrapping again; the card's inner width is 828px. */
.fd-heading { max-width: 600px; margin: 0 auto; }

/* Three across at desktop, stacking with the rest of the site at --bp-lg.
   Stretched + column so the price rows, hairlines and tick lists sit LEVEL
   across the row even when a description wraps to a different depth
   (identity section 4: the cards' internal blocks align across the row). */
.fd-cards {
  display: grid; grid-template-columns: 1fr; gap: 16px;
  margin-top: 28px; text-align: left; align-items: stretch;
}
.fd-card {
  display: flex; flex-direction: column;
  background: var(--cream);
  border: 1px solid var(--line);
  border-top: 3px solid var(--orange);
  border-radius: 14px;
  padding: 22px 20px;
}
.fd-card h4 { font-size: 1.12rem; color: var(--ink-deep); margin: 0 0 6px; line-height: var(--lh-snug); }
/* Absorbs the slack so everything below it starts at a common height. */
/* min-height, NOT flex-grow. Growing the description equalises what follows it
   only while every card has the SAME content below - which stopped being true
   once a card could drop its tick list for the not-offered line: that card has
   far more free space, its description would absorb all of it, and its price
   row would sit well below the other two.
   Reserving two lines here instead makes the whole top block (title, blurb,
   price) deterministic, so the price rows align regardless of what is beneath.
   A description that ever runs to THREE lines needs this raised to 4.5em. */
.fd-desc {
  min-height: 3em; margin: 0 0 14px;
  font-size: var(--fs-sm); line-height: var(--lh-snug); color: var(--text);
}
/* The labels sit ABOVE the figures (Owner 2026-08-01) in a row that mirrors the
   figures row, so each label lands over its own price. On the Full Groom card
   of a breed priced both ways that reads "Clipper cut from" / "Hand stripping
   from"; everywhere else it is just "from" on the left. */
.fd-price { margin: 0; }
/* Rendered on all three cards, with a reserved line height. Only the Full Groom
   card of a breed priced both ways puts anything in it - but if the row existed
   on that card alone, its figures and every hairline below them would sit a
   line lower than the other two cards. */
.fd-labels {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  min-height: 1.15em; margin-bottom: 2px;
}
.fd-toplabel {
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-2xs);
  letter-spacing: var(--ls-caps); color: var(--ink);
}
/* Each figure carries its own inline "from", so the labels above are free to
   name the technique rather than repeat the qualifier. */
.fd-money { display: inline-flex; align-items: baseline; gap: 6px; }
/* An author `display` beats the UA rule behind the `hidden` attribute, so the
   alternative price rendered even while hidden - showing "from --" at rest and,
   worse, a stale $180 beside an unrelated breed's price. The attribute selector
   outranks the class, so no !important is needed. */
.fd-money[hidden] { display: none; }
.fd-money-alt { text-align: right; white-space: nowrap; }
.fd-from {
  font-family: var(--font-body); font-weight: 400; font-size: var(--fs-2xs);
  text-transform: lowercase; letter-spacing: var(--ls-caps); color: var(--muted-strong);
}
/* Clipped price left, hand-stripped right. Baseline-aligned so the two figures
   sit on one line despite their different sizes. The min-height holds the row at
   the tall figure's height even when the card is showing the shorter
   "Not for this coat" text instead, so the hairlines below stay level. */
/* 2rem, not the figure's own 1.62rem: a money group carrying an inline "from"
   and an asterisk builds a taller line box than a bare figure (32px vs 25.9px
   measured), so the Full Groom card's hairline sat 6.1px below the other two.
   Pinning the row to the tallest case on ALL cards holds them level. */
.fd-amounts {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  min-height: 2rem;
}
.fd-amt {
  font-family: var(--font-display); font-size: 1.6rem; line-height: var(--lh-tight);
  color: var(--ink-deep); font-variant-numeric: tabular-nums;
}
/* The "$" is a pseudo-element, not markup, so the count-up can rewrite the
   span's whole textContent without eating it - and so the not-offered state
   can drop it, since "$--" is not a thing. */
.fd-amt::before { content: "$"; font-size: .82em; margin-right: 1px; }
/* No number yet, or a service this breed is not offered. The "$" and the "from"
   label both go: "$--" and "from --" read as broken rather than as waiting.
   --text, not --muted-strong: on cream the latter is 4.4:1 and fails AA. */
.fd-amt.is-na { color: var(--text); }
.fd-amt.is-na::before { content: none; }
.fd-price.is-na .fd-from { display: none; }
/* The hairline under the price (Owner 2026-07-31). INSET - it sits inside the
   card's padding and never touches the edges, per the row-separator tier of the
   card convention. */
.fd-rule { height: 1px; background: var(--hairline); margin: 16px 0 14px; }
/* --muted-strong (#757575) is the site's AA-safe grey ON WHITE, where it makes
   4.6:1. These cards sit on --cream, which drops it to 4.4:1 - under AA by a
   hair (detector, 2026-07-31). --text (#616161) clears it at 6.1:1 on the same
   ground and stays clearly lighter than the --ink tick rows, so the "included"
   and "not included" channels still read apart. Scoped to the card, so the grey
   is unchanged everywhere it still sits on white. */
.fd-incl {
  font-family: var(--font-display); font-size: var(--fs-2xs);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--text); margin: 0 0 4px;
}
/* Fainter than the included rows (Owner 2026-07-31) - via WEIGHT, not colour.
   The base row is 700; dropping the excluded ones to 400 is a large perceptual
   step that costs no contrast. Colour has almost nothing left to give: --text
   on cream is 6.1:1, and the next grey down (--muted-strong) is 4.4:1, which
   already failed AA on this card once today. Weight is the free lever. */
.fd-card .features li.off { color: var(--text); font-weight: 400; }
/* "Not included" (Owner 2026-07-31): an X in the tick's own style, replacing
   the en dash - the dash read as a placeholder rather than as an exclusion.

   The Owner also asked whether the row should be FAINTER. It should not, and
   the X is why: --text on cream is already 6.1:1 and any lighter fails AA (the
   4.4:1 finding this same card produced an hour ago). Differentiation is the
   actual goal, so it comes from the glyph, which can carry brand orange freely
   - identity section 3 permits orange as a small accent, the glyph is
   aria-hidden, and the row keeps its sr-only "Not included:" prefix, so no
   meaning depends on seeing the colour.

   --orange-deep, not --orange: against cream it holds roughly the optical
   weight the teal tick does. Plain --orange sits at about 2.0:1 and would read
   as a faded tick rather than a deliberate mark. */
.fd-card .features li.off .tick::before {
  content: "\00D7"; font-family: var(--font-body); font-weight: 700;
  font-size: 1.5rem; color: var(--orange-deep);
}
/* The glyph box is 24px, but the SPAN around it is INLINE, so its line box
   still grew with the glyph - an inline-flex ::before aligns on its baseline,
   and the 1.5rem X sits lower than the 1.1rem tick. That made each X row 1px
   taller than each tick row; the cards carry different numbers of each, so
   their flex-grown descriptions compensated by different amounts and the price
   rows drifted 4px apart (measured 2026-07-31).

   line-height alone did not fix it - the baseline is the mechanism, not the
   leading. Making the span a flex container removes the inline line box
   altogether, so row height no longer depends on which glyph is in the row. */
.fd-card .features .tick {
  display: flex; align-items: center; justify-content: center;
  flex: none; width: 24px; height: 24px;
}
.fd-card .features .tick::before { line-height: 1; }
.fd-note {
  max-width: 620px; margin: 26px auto 0;
  font-size: var(--fs-xs); line-height: 1.65; color: var(--muted-strong);
}

/* --- a service this coat is not offered (Owner 2026-07-31) ---
   128 of the 248 breeds in the new price set have no Maintenance Groom: the
   service holds a CLIPPED shape, and smooth, double and hand-stripped coats are
   not clipped, so there is nothing to hold. Leaving that card at "--" with its
   tick list intact was the confusing part - the ticks kept asserting what was
   included in something the dog cannot have.

   The card keeps its shell and its 3px orange rule: this is a STATE, not a
   different card, and the rule is an identity MUST that applies to every card
   site-wide. The recede comes from the content instead - no price where the
   siblings carry a large number, a muted title, and two short lines where they
   carry six tick rows. That is a bigger difference in visual mass than any
   amount of dimming, and it costs no contrast. */
.fd-card.is-unavailable h4 { color: var(--text); }
.fd-card.is-unavailable .fd-incl,
.fd-card.is-unavailable .features { display: none; }
/* The height guard moved to .fd-amounts above: with "from" promoted to its own
   line the row that must not collapse is the figures row, not the whole block. */
/* visibility, NOT display: "from" is its own line now, so removing it from flow
   pulled the figures row and every hairline below it up by 23.5px on the
   not-offered card only (measured 2026-08-01). Hiding it in place keeps the
   space it occupies on the other two cards. */
/* The whole money group goes, not its parts: "from" and the figure are one unit
   again, and .fd-amounts holds the row height so the hairlines stay level. */
.fd-card.is-unavailable .fd-money { display: none; }
.fd-unavail {
  font-family: var(--font-body); font-weight: 700;
  font-size: var(--fs-sm); line-height: var(--lh-snug); color: var(--text);
}
.fd-instead {
  margin: 0; font-size: var(--fs-sm); line-height: var(--lh-snug); color: var(--text);
}

/* --- the hand-stripped alternative (Owner 2026-08-01) ---
   11 breeds are priced two ways: clipped, or hand stripped. They used to be two
   separate entries in the lookup, which meant finding the second one required
   already knowing the word. One entry now, with the alternative shown here.

   Deliberately the LAST thing in the card, under the tick list: the price row,
   hairline, label and tick rows above it are all held level across the three
   cards, and a second price higher up would push this card out of line. Cards
   stretch to equal height anyway, so this fills space the card already had. */
/* Both figures use .fd-amt outright - same size, face and colour. They are
   equal choices, distinguished by the technique label above each. */
.fd-star { color: var(--orange-deep); margin-left: 1px; }
/* Last thing in the card, under the tick list, with the inset separator the
   card convention uses for row divisions. */
.fd-strip-note {
  margin: 16px 0 0; padding-top: 14px;
  border-top: 1px solid var(--hairline);
  font-size: var(--fs-xs); line-height: 1.55; color: var(--text);
}

/* The micro-fade across the not-offered boundary. .18s matches SWAP_MS in
   main.js and the breed switcher's own dip (identity section 5) - if either
   moves, move both or the swap lands before or after the card is dark.

   Only the four elements that actually change are faded, not the card: the
   title and blurb stay put, so the card reads as answering rather than as
   reloading. Nothing here animates a layout property - the grid stretches all
   three cards to one height, so at desktop the box does not move at all. */
.fd-price, .fd-incl, .features, .fd-instead { transition: opacity .18s ease; }
.fd-card h4 { transition: color .18s ease; }
.fd-card.is-swapping .fd-price,
.fd-card.is-swapping .fd-incl,
.fd-card.is-swapping .features,
.fd-card.is-swapping .fd-instead { opacity: 0; }
/* Identity section 5: reduced motion kills the transition, so the class toggle
   becomes an instant swap - same end state, no movement. main.js takes the same
   branch and skips the timeout entirely, so the two cannot disagree. */
@media (prefers-reduced-motion: reduce) {
  .fd-price, .fd-incl, .features, .fd-instead,
  .fd-card h4 { transition: none; }
}

@media (min-width: 1025px) {
  .fd-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
  .fd-card { padding: 20px 16px; }
  .fd-card h4 { font-size: 1rem; }
  .fd-amt { font-size: 1.45rem; }
}
@media (max-width: 720px) {
  .fd { padding: 0 20px 26px; }
  .fd-head { margin: 0 -20px 22px; padding: 26px 20px 16px; }
}

/* ---------- extras ---------- */

/* Corgi bottom-aligned with the extras card (Owner 2026-07-17): the section's
   split stretches, the text column flexes, and the figure drops to the bottom
   so the pup's underside sits level with the card's bottom edge. */
#extras .split { align-items: stretch; }
#extras .split > div:first-child { display: flex; flex-direction: column; }
.extras-figure { margin-top: auto; position: relative; }
/* Sleeping Z Z Z above the pup's head - same sticker style and float motion as
   the hero bone doodle (Owner mockup 2026-07-17): orange fill, dark outline,
   white halo, gentle doodle-float. Sizes rise away from the head. */
.zzz {
  position: absolute;
  left: 68%;
  bottom: 63%;
  z-index: 2;
  pointer-events: none;
  animation: doodle-float 6s ease-in-out infinite;
  overflow: visible;
}
/* stroked hand-drawn Z's, sticker layering: white halo / dark #5C4D48 (sampled
   from the crown+bone sticker PNGs) / orange core */
.zzz-w { stroke: #fff; stroke-width: 15; }
.zzz-d { stroke: #5C4D48; stroke-width: 10.5; }
.zzz-o { stroke: var(--orange); stroke-width: 6.5; }

/* ============================================================================
   HIDDEN DECORATIONS (Owner 2026-07-17: hide, may restore later).
   The markup and SVG assets are untouched - to bring any group back, delete
   its line below (or this whole block to restore all of them).
   ============================================================================ */
.hero-band .doodle { display: none; }   /* white-outline hero doodles: swoosh, ball, paw, 2 bones */
.sticker-crown, .sticker-bone { display: none; }   /* orange floating stickers on the about pug */
.zzz { display: none; }                     /* sleeping Z Z Z on the extras corgi */
/* Real 600x600 photo cropped to the band's 2:1; the dog's face sits in the
   upper third of the square, so the crop window rides high (2026-07-20). */
.extras-figure img { display: block; width: 100%; aspect-ratio: 2 / 1; object-fit: cover; object-position: center 22%; border-radius: var(--radius-feature); }
/* Owner-directed 2026-07-16: render the corgi ~1.15x bigger. On the 2-col layout
   there is whitespace to the right of the cutout and a wide gap before the price
   list, so it grows up-and-out from its bottom without colliding or exposing crop
   edges (transform, not layout size, so it never reflows the price column). Below
   1024px the section stacks and the figure is already full-width, so the scale is
   dropped there to avoid horizontal overflow. */
@media (min-width: 1025px) {   /* --bp-lg, min-width side */
  .extras-figure { overflow: visible; }
  .extras-figure img { transform: scale(1.15); transform-origin: bottom center; }
}
.extras-list {
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  border-top: 3px solid var(--orange);   /* house card convention (identity v1.2, 3px) */
  padding: var(--space-4) var(--space-6);
}
.extra-row {
  display: flex; align-items: center; gap: var(--space-5);
  padding: var(--space-3) 0;
}
.extra-row + .extra-row { border-top: 1px solid var(--hairline); }
/* Name leads, price recedes (critique round 1): six of seven extras cost the
   same $20, so the service name is the information and the price is the
   footnote. The tag moves to the row end; teal stays, per its DESIGN.md role. */
.price-tag {
  background: var(--teal);
  color: #fff;
  font-family: var(--font-display);
  font-size: var(--fs-md);
  border-radius: var(--radius-control);
  padding: 10px 0;
  width: 86px; text-align: center;
  flex: none;
  margin-left: auto;
}
.extra-row .name { font-family: var(--font-display); font-size: var(--fs-title); color: var(--ink); }  /* live theme extras: 24px (measured); was 19.2px */

/* ---------- Instagram drift band ---------- */

/* Natural-colour CMS photos are repeated three times and translated by exactly
   one pass for a seamless loop. The gallery is presentational; the only
   Instagram destination is the explicit CTA above it. */
.ig-strip { position: relative; overflow: hidden; background: var(--white); }
.ig-track {
  display: flex;
  height: 100%;
  width: max-content;
  will-change: transform;
  animation: ig-drift 72s linear infinite;
}
.ig-track img {
  height: 100%;
  width: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
  flex: none;
}
/* -33.3333% = one of the three photo passes, so the reset is invisible. Tied to
   the pass count in home.html: -100%/passes. Two passes (-50%) left only one
   pass width covering the strip at the end of the cycle, so windows wider than
   3120px showed white at the right-hand end. */
@keyframes ig-drift {
  from { transform: translateX(0); }
  to { transform: translateX(-33.3333%); }
}
.ig-band { background: var(--white); text-align: center; }
.ig-head { padding: var(--section-space) var(--page-gutter) var(--card-gap); }
.ig-head h2 { margin: 0; }
.ig-head .intro { margin: 10px auto 0; }
.ig-cta { margin-top: var(--space-4); }
.ig-band .ig-strip { height: 260px; }

/* ---------- inner page basics ---------- */

/* DOCUMENTED EXCEPTION. The legal pages' in-prose h2 sits below the --fs-h2
   clamp floor (1.35rem vs 1.6rem). It is not lifted to the clamp: at clause
   density that would read as a stack of section titles rather than sub-heads.
   Tokenised as its own named step so the value has one home and the exception
   is visible instead of hiding as a raw literal. */
.prose h2 { font-size: var(--fs-legal-h2); margin-top: 2em; }
.prose { max-width: var(--measure); }   /* was 700px - the one reading measure */
.prose .lead { font-size: var(--fs-control); }
.error-page-title { max-width: 16ch; }
.error-page-copy { max-width: var(--measure); }
.page-intro .error-page-title,
.page-intro .error-page-copy { margin-left: auto; margin-right: auto; }
.error-page-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--action-gap);
  justify-content: center;
  margin: 0;
}

/* ---------- accordion ---------- */

.accordion { display: grid; gap: 14px; }
.faq-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--component-gap);
  align-items: start;
}
.acc-item { background: var(--cream-2); border-radius: var(--radius); overflow: hidden; }
.section-cream .acc-item { background: var(--cream-3); }
.acc-head {
  width: 100%;
  display: flex; align-items: center; gap: 16px;
  background: none; border: 0; cursor: pointer;
  text-align: left;
  font-family: var(--font-body); font-weight: 700; font-size: var(--fs-body); line-height: var(--lh-snug); color: var(--ink);
  padding: var(--space-4) var(--space-5);
  transition: background-color .18s ease;
}
.acc-head:hover { background: color-mix(in srgb, var(--teal) 7%, transparent); }
.acc-head:focus-visible { outline: 3px solid var(--teal); outline-offset: -3px; }
.acc-head .acc-icon {
  flex: none;
  width: 26px; height: 26px; border-radius: var(--radius-control);
  background: var(--white);
  color: var(--teal-ink);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-lg); line-height: 1;
  transition: transform .2s ease;
}
.acc-item[data-open] .acc-icon { transform: rotate(45deg); }
.acc-panel { font-size: var(--fs-sm); padding: 0 var(--space-5) 20px 64px; }
.acc-panel ul { list-style: disc; padding-left: 20px; margin: 0 0 1em; }
.acc-panel li { margin-bottom: 6px; }

/* ---------- booking page ---------- */

.badges { display: flex; gap: var(--space-4); margin-top: var(--space-4); }
.badges img { height: 48px; width: auto; }

/* Booking content follows the shared inner-page pattern: the opening message
   lives above, while the dog image and booking checklist form the next 1:1 band. */
.booking-figure {
  margin: 0;
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}
.booking-figure img { display: block; border-radius: var(--radius-feature); width: 100%; max-width: 560px; }
.booking-app-copy { margin-top: 28px; }

/* ---------- contact page ---------- */

.contact-grid {
  display: grid; grid-template-columns: 380px 1fr; gap: var(--component-gap);
  align-items: stretch;   /* info card matches the map height (Owner 2026-07-17) */
}
/* ONE info card, not three (identity v1.2, Owner 2026-07-17): address / email /
   phone are rows inside a single card, separated by INSET hairlines - the card
   carries the horizontal padding so row borders sit inside the card edges,
   matching the extras list and the original grooming-menu reference (row lines
   inset; only TITLE separators run full-width). */
.info-card {
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  border-top: 3px solid var(--orange);   /* house card convention (identity v1.2, 3px) */
  padding: 0 24px;
  /* stretches to the map's height; auto margins on the rows share the extra
     space out evenly so the three rows stay balanced at any height */
  display: flex; flex-direction: column;
}
/* Rows grow from their NATURAL content height and share the card's spare space
   equally (flex-basis auto, Owner 2026-07-17) - so the 3-line address row gets
   a taller band than the 1-line email/phone rows and every row ends up with the
   SAME breathing room above and below its content. Grid centers the content in
   the band (align-content) while keeping the icon level with the row title
   (align-items:start, the earlier Owner rule). Card height is set by the map. */
.info-row {
  flex: 1 1 auto;
  display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 16px;
  align-content: center; align-items: start;
  padding: 14px 0;
}
.info-row + .info-row { border-top: 1px solid var(--hairline); }
/* Icons are FILLED teal silhouettes, no circle chip (Owner 2026-07-17), sized
   and nudged so each sits level with its row title. fi-call stays the font
   glyph; the doghouse + envelope are inline SVGs on currentColor. */
.info-row .icon {
  flex: none;
  width: 24px; height: 24px;
  color: var(--teal-ink);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 22px;
  margin-top: 1px;
}
.info-row .icon svg { width: 100%; height: 100%; fill: currentColor; }
/* Folded DOWN from 1.05rem: identity line 34 says a 1.05rem control label is
   not a title, so the row heading resolves to the body step. */
.info-row h3 { font-size: var(--fs-body); margin-bottom: 2px; }
.info-row p { margin: 0; font-size: var(--fs-sm); overflow-wrap: anywhere; }
.info-row a,
.info-row a:hover { color: inherit; }
.info-row a { text-underline-offset: 2px; }
.info-row a:hover { text-decoration-thickness: 2px; }
.info-row a:focus-visible {
  outline: 3px solid var(--ink);   /* reconciled to the site ring width (DESIGN.md Section 5) */
  outline-offset: 3px;
  border-radius: var(--radius-control);
}
.info-row > div { min-width: 0; }
.map-embed {
  width: 100%; height: 100%; min-height: 420px;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  border: 0; border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  background: var(--cream-2);
}
/* Apply the approved wash to the tiles only. Controls, attribution and the
   branded location marker retain their intended colours and contrast. */
.map-embed .leaflet-tile-pane {
  filter: grayscale(.72) sepia(.16) hue-rotate(128deg) saturate(.88) contrast(.96);
}
.map-fallback {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  padding: 24px;
  text-align: center;
  font-weight: 700;
  background: var(--cream-2);
}
.map-embed.map-ready .map-fallback { display: none; }
.brand-map-marker {
  width: 42px !important;
  height: 50px !important;
  border: 0;
  background: transparent;
}
.brand-map-marker::before {
  content: '';
  position: absolute;
  left: 6px; top: 3px;
  width: 30px; height: 30px;
  border: 3px solid var(--white);
  border-radius: 50% 50% 50% 0;
  background: var(--orange);
  box-shadow: 0 5px 14px rgba(44, 62, 62, .28);
  transform: rotate(-45deg);
}
.brand-map-marker::after {
  content: '';
  position: absolute;
  left: 16px; top: 13px;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--white);
}
.map-embed .leaflet-control-attribution {
  margin: 0 7px 7px 0;
  padding: 3px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, .86);
  color: var(--ink);
  font: 600 10px/1.35 var(--font-body);
}
.map-embed .leaflet-control-attribution a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.map-embed .leaflet-control-zoom a {
  color: var(--ink);
  background: rgba(255, 255, 255, .94);
}
.map-embed .leaflet-control-zoom a:hover,
.map-embed .leaflet-control-zoom a:focus-visible {
  color: var(--ink-deep);
  background: var(--cream);
}

.contact-form {
  background: var(--white);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  border-top: 3px solid var(--orange);   /* house card convention (identity v1.2, 3px) */
  padding: 40px;
  max-width: var(--form-max);
  margin: 40px auto 0;
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5);
}
.contact-form > * { min-width: 0; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field-full { grid-column: 1 / -1; }
.field label { font-weight: 700; font-size: var(--fs-sm); color: var(--ink); }
.field label .req { color: var(--danger); }
.field input, .field textarea {
  font-family: var(--font-body); font-size: var(--fs-body); color: var(--ink);
  /* One input radius. The pricing-update select is still 10px in that page's
     own <style> block, which is outside this round's edit surface. */
  border: 1px solid var(--line); border-radius: var(--radius-control);
  padding: 12px 14px;
  background: #fff;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.field input:focus, .field textarea:focus {
  outline: 3px solid var(--ink); outline-offset: 3px; border-color: var(--teal-ink);   /* reconciled to the site ring (DESIGN.md Section 5) */
}
.field input:user-invalid, .field textarea:user-invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--danger) 14%, transparent);
}
.field input:user-invalid:focus, .field textarea:user-invalid:focus {
  outline-color: var(--danger);
}
.form-actions { grid-column: 1 / -1; text-align: center; }
.form-status { grid-column: 1 / -1; margin: 0; font-weight: 700; text-align: center; }
.form-status.ok { color: var(--teal-deep); }
.form-status.err { color: var(--danger); }
.hp-field { position: absolute; left: -9999px; height: 0; overflow: hidden; }
/* Turnstile's normal widget is fixed at 300x65. Keep its readable horizontal
   format but reduce the visual footprint inside the shared enquiry form. */
.turnstile-field {
  width: 240px;
  height: 52px;
  overflow: hidden;
}
.turnstile-field .cf-turnstile {
  width: 300px;
  min-height: 65px;
  transform: scale(.8);
  transform-origin: 0 0;
}
@media (max-width: 379px) {   /* identity line 59 EXACT - not --bp-xs */
  .turnstile-field { width: 225px; height: 49px; }
  .turnstile-field .cf-turnstile { transform: scale(.75); }
}

/* ---------- footer ---------- */

/* Footer watermark (Owner 2026-07-17): the little orange paw PNG is replaced by
   the SAME big paw shape the pricing section uses, refilled slightly darker than
   the footer ink so it reads as a quiet watermark. Two paws: one anchored LEFT
   and cropped by the footer edge (the "bleeding out" read), one smaller top-right,
   rotated, also cropped. Subtle by design - keep the fill within a few percent
   of --ink (#3D5757 on #405B5B - ~5% darker than the ground; 10% read too dark, Owner 2026-07-17). */
.site-footer {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cg fill='%233D5757'%3E%3Cellipse cx='58' cy='56' rx='21' ry='30' transform='rotate(-18 58 56)'/%3E%3Cellipse cx='100' cy='42' rx='21' ry='30'/%3E%3Cellipse cx='142' cy='56' rx='21' ry='30' transform='rotate(18 142 56)'/%3E%3Cpath d='M100 88c-36 0-58 27-58 58 0 26 26 42 58 42s58-16 58-42c0-31-22-58-58-58z'/%3E%3C/g%3E%3C/svg%3E") left -270px bottom -250px / 830px auto no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cg fill='%233D5757' transform='rotate(24 100 100)'%3E%3Cellipse cx='58' cy='56' rx='21' ry='30' transform='rotate(-18 58 56)'/%3E%3Cellipse cx='100' cy='42' rx='21' ry='30'/%3E%3Cellipse cx='142' cy='56' rx='21' ry='30' transform='rotate(18 142 56)'/%3E%3Cpath d='M100 88c-36 0-58 27-58 58 0 26 26 42 58 42s58-16 58-42c0-31-22-58-58-58z'/%3E%3C/g%3E%3C/svg%3E") right -150px top -110px / 540px auto no-repeat,
    var(--ink);
  /* Mirrors the .topbar rule that opens the page (identity line 55: the strip
     and the card top rules move together, all --orange #F69946 at 3px). The
     page now opens and closes on the same orange line. Owner 2026-07-21. */
  border-top: 3px solid var(--orange);
  color: rgba(255, 255, 255, .85);
  font-family: var(--font-body);
  font-size: var(--footer-size);
  line-height: var(--footer-leading);
}
.footer-grid {
  display: grid;
  /* brand column slimmed (logo + social only, Owner 2026-07-17); the contact
     rows column gets the room so the email never wraps mid-address. */
  grid-template-columns: 1.1fr 1fr 0.9fr;   /* 3 columns: brand+contact | reviews | hours (Owner 2026-07-17) */
  gap: var(--component-gap);
  padding: 70px 0 50px;
}
/* Footer section headings. The TAG is h2 so the document outline never skips
   a level; .footer-heading carries the visual size the identity fixes as the
   one footer element allowed to be larger than --footer-size (identity lines
   83-87). Rendered px, face, colour and margin are unchanged from the h4. */
.site-footer .footer-heading { color: #fff; font-size: var(--fs-md); margin-bottom: 1em; }
/* Phone number promoted to match the "Explore" / "Visit" column headings
   (Owner 2026-07-21). Those headings are NOT bold - they are Paytone One at
   weight 400, which is why they read heavy - so a true match means the display
   face, not a font-weight bump. IDENTITY DEVIATION: lines 83-87 fix
   .footer-heading as the ONE footer element larger than --footer-size; this is
   now a second, chosen deliberately to make the phone the loudest thing in the
   footer. Colour and no-underline are inherited from .f-row a. */
.f-phone a { font-family: var(--font-display); font-size: var(--fs-md); }
/* Footer contact rows (Owner 2026-07-17): same icon + title + value pattern as
   the contact page's info card, but text WHITE on the dark footer - icons stay
   the same teal. Replaces the old Help links column. */
.f-row {
  /* 24px icon column, matching .info-row - the same pattern was running two
     different column widths for no reason. */
  display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: var(--space-3);
  align-items: start;
  margin-bottom: var(--space-4);
}
.f-row .icon {
  /* -10% (Owner 2026-07-21): 24->21.6px box for the two inline SVGs, 20->18px
     for the .fi-call webfont glyph - both paths need it or only two of the
     three shrink. The GRID COLUMN stays 24px so the text edge does not move
     and the .info-row parity above is preserved. */
  width: 21.6px; height: 21.6px;
  /* --orange, not --teal (Owner 2026-07-21): teal #00A4AD measured 2.41:1 on
     the --ink footer, under the 3:1 non-text bar, and read as washed out.
     --orange is 3.34:1 and matches the is-today hours bullet. */
  color: var(--orange);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 18px;
  margin-top: 2px;
}
.f-row .icon svg { width: 100%; height: 100%; fill: currentColor; }
.f-row p { margin: 0; color: #fff; overflow-wrap: anywhere; }
.f-row a { color: #fff; text-decoration: none; }
.f-row a:hover { text-decoration: underline; }
/* Footer contact block: phone / address / email spaced EQUALLY by one grid gap
   (Owner 2026-07-17) - the three gaps are identical, not per-row margins. */
/* 18px row-gap is identity-fixed (identity line 86, Owner on record that the
   three contact rows are evenly spaced) - it does NOT snap to the 4pt scale. */
.footer-brand .f-contact { display: grid; row-gap: 18px; margin: 4px 0 8px; }
.footer-brand .f-row { margin: 0; }
.f-address { text-decoration: none; color: inherit; }
.f-address:hover p { text-decoration: underline; }
.footer-social { display: flex; gap: var(--space-3); margin-top: var(--space-4); }
.footer-social a {
  width: 44px; height: 44px; border-radius: 50%;   /* was 38px - WCAG 2.5.8 minimum target */
  border: 1px solid rgba(255, 255, 255, .35);
  color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
}
.footer-social a:hover { background: var(--teal); border-color: var(--teal); color: #fff; }
.footer-social svg { width: 16px; height: 16px; fill: currentColor; }
/* Every footer text element inherits the single size and leading from
   .site-footer. Only the section headings override that inherited type role. */
.hours li { display: flex; justify-content: space-between; margin-bottom: 8px; }
.hours .time { color: #fff; font-weight: 800; }   /* white, not orange (Owner 2026-07-17) */
.hours .closed { color: #fff; font-weight: 800; }
.footer-bottom {
  border-top: 1px solid rgba(255, 255, 255, .18);
  padding: var(--space-5) 0;
  /* Legal strip (T&Cs, privacy, copyright) drops off --footer-size to --fs-xs -
     the token whose stated role is exactly this ("utility strip, micro
     labels"), and the same step .topbar uses for the utility strip at the top
     of the page. Owner 2026-07-21. */
  font-size: var(--fs-xs);
  display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap;
}
.footer-bottom a { color: rgba(255, 255, 255, .85); text-decoration: none; display: inline-block; padding: 3px 0; }
.footer-bottom a:hover { color: #fff; text-decoration: underline; }

/* footer reviews column (Owner-picked variant B). Data is CMS-tokenised from
   content/reviews.json - manual until the dynamic Google feed is wired, at which
   point only the data source changes, not this markup/CSS. Stars are site orange
   on the dark teal footer; quote accent bars are brand teal. */
.gr-badge { max-width: 13em; }   /* about the width of the "What our clients say" heading (Owner 2026-07-17) */
.gr-line { display: flex; align-items: center; gap: 8px; margin: 6px 0 4px; }
.gr-stars { display: inline-flex; gap: 3px; }
.gr-rating { margin: 0 0 2px; }
.gr-rating strong { font-family: var(--font-body); font-weight: 800; }
.gr-star { fill: var(--orange); }
.gr-count { opacity: .82; margin: 2px 0 12px; }   /* size from the footer-wide p rule */
/* The rating is verifiable (critique round 1): the count links to the live
   Google listing, so 5.0-from-44 is a claim the visitor can check. */
.gr-count a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.gr-count a:hover { color: var(--peach); }
/* One review at a time, chosen at random and crossfaded by main.js (Owner
   2026-07-17). The rotator is a FIXED-height stage (with JS) so a longer or
   shorter review never moves the footer; the excerpt is capped at 3 lines by
   line-clamp so a long Google review cannot break the column. Without JS the
   quotes simply stack, visible and static. */
.gr-quote {
  opacity: .9; margin: 0 0 12px;
  border-left: 2px solid var(--teal); padding-left: 12px;
}
.gr-qtext {
  display: -webkit-box;
  font-style: italic;
  font-weight: 600;
  -webkit-line-clamp: 5;   /* excerpt cap 5 lines (Owner 2026-07-17) */
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.gr-quote cite { display: block; font-style: normal; opacity: .7; margin-top: 3px; }
/* fixed stage; JS sizes it to the tallest actual review so it never jumps on
   rotate and wastes no space (falls back to this min-height before JS runs) */
html.js .gr-rotator { position: relative; min-height: 90px; }
html.js .gr-quote {
  position: absolute; inset: 0 0 auto 0; margin: 0;
  opacity: 0; transition: opacity .6s ease;
  pointer-events: none;
}
html.js .gr-quote.is-on { opacity: .9; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) {
  html.js .gr-quote { transition: none; }
}

/* ---------- responsive ---------- */

@media (max-width: 1024px) {   /* --bp-lg: main two-column stack */
  .cards-3 { grid-template-columns: 1fr; max-width: 560px; margin-left: auto; margin-right: auto; }
  .split { grid-template-columns: 1fr; gap: var(--layout-gap-stack); }
  .faq-columns { grid-template-columns: 1fr; gap: 14px; }
  .contact-grid { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr 1fr; }
}

/* Tablet footer composition (BLAT5T5). At two columns the three semantic groups
   would leave Working Hours alone in a half-empty second row. Hours is the
   shortest, widest-tolerant group, so it becomes an intentional full-width band
   under the two tall columns, its four days laid out across rather than stacked.
   Scoped to the exact range where the footer is two columns (601-1024px);
   below 601px the whole footer is a single column and the days stack again.
   Two-up rather than four-across so the longest label, "Tuesday to Friday
   9am - 6pm", never wraps at the narrow end of the range. */
@media (min-width: 601px) and (max-width: 1024px) {   /* --bp-sm .. --bp-lg */
  .footer-hours { grid-column: 1 / -1; }
  .footer-hours .hours {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    /* Same token as the parent .footer-grid gap, not a matching literal: the
       point of the band is that its two day columns track the two columns
       above it, so the alignment must survive a change to --component-gap. */
    column-gap: var(--component-gap);
    row-gap: 8px;
  }
  .footer-hours .hours li { margin-bottom: 0; }
  /* Insurance, dormant at the current four lines: if the identity's four-line
     hours rule is ever revised to an odd count, the last row spans both tracks
     instead of leaving the half-empty cell this whole band exists to remove. */
  .footer-hours .hours li:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

@media (max-width: 900px) {   /* --bp-md: nav collapse */
  .nav-toggle { display: block; }
  .main-nav {
    display: block;
    position: absolute; left: 0; right: 0; top: 100%;
    background: #fff;
    box-shadow: 0 14px 24px rgba(64, 91, 91, .15);
    opacity: 0; visibility: hidden; transform: translateY(-8px);
    pointer-events: none;
    transition: opacity .2s ease, transform .2s ease, visibility 0s linear .2s;
  }
  .nav-open .main-nav {
    opacity: 1; visibility: visible; transform: none;
    pointer-events: auto; transition-delay: 0s;
  }
  .main-nav ul { flex-direction: column; gap: 0; padding: 10px var(--page-gutter) 10px; }
  .main-nav ul a { display: block; padding: 12px 0; font-size: var(--fs-body); }
  .section { padding: var(--section-space-compact) 0; }
  .doodle-yarn, .doodle-bone-c { display: none; }
  .ig-head { padding: var(--section-space-compact) 20px 24px; }
  .ig-band .ig-strip { height: 200px; }
}

@media (max-width: 390px) {   /* --bp-xs */
  .ig-band .ig-strip { height: 170px; }
}

@media (max-width: 600px) {   /* --bp-sm */
  .topbar .container { justify-content: center; gap: 16px; flex-wrap: wrap; }
  .logo img { height: 62px; }
  /* shrink from the mobile base (62px), not the desktop 72px override - else the
     "shrunk" nav would grow on phones (62 -> 72). ~20% smaller here too. */
  .site-header.nav-shrink .logo img { height: 50px; }
  .checklist { grid-template-columns: 1fr; }
  .contact-form { padding: var(--space-5) 20px; grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: 1fr; gap: var(--component-gap-compact); }
  .footer-bottom { justify-content: center; text-align: center; }
  .extras-list { padding: 12px 20px; }
  .badges img { height: 42px; }
  /* Keep the shared centred page-opening hierarchy, but return long mobile
     paragraphs to the readable running-copy axis. Short intros remain centred. */
  .page-intro-long, .page-intro-copy { text-align: left; }
  /* The Instagram drift KEEPS RUNNING on phones (Owner 2026-08-01), reversing
     the earlier static fallback. The reason that fallback existed - no reliable
     pause affordance on touch, WCAG 2.2.2 - still stands, but the About-column
     review rotator already carries exactly that trade-off as a recorded
     deliberate choice, so stopping only this one was inconsistent rather than
     safer. The prefers-reduced-motion branch below is untouched and still
     stops it for anyone who has asked the OS for less movement, which is the
     branch that actually serves motion sensitivity. */
}

/* ---------- motion ---------- */

/* scroll reveals - hidden only when JS is running (html.js) */
html.js [data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .65s cubic-bezier(.22, .61, .36, 1), transform .65s cubic-bezier(.22, .61, .36, 1);
  transition-delay: var(--reveal-delay, 0s);
}
html.js [data-reveal].in { opacity: 1; transform: none; }
html.js [data-reveal].btn.reveal-done {
  transition: background .2s ease, transform .15s ease, box-shadow .2s ease;
  transition-delay: 0s;
}
html.js [data-reveal].btn.reveal-done:hover { transform: translateY(-2px); }
html.js [data-reveal].btn.reveal-done:active { transform: translateY(0) scale(.98); }

/* hero doodles: gentle float */
@keyframes doodle-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-10px) rotate(3deg); }
}
.hero-band .doodle { animation: doodle-float 6s ease-in-out infinite; }
.doodle-bone-c, .doodle-paw { animation-duration: 7.5s; animation-delay: 1.2s; }
.doodle-yarn { animation-duration: 8s; animation-delay: .5s; }

/* accordion open/close */
html.js .acc-panel {
  display: block;
  max-height: 0;
  overflow: hidden;
  padding: 0 var(--space-5) 0 64px;
  transition: max-height .35s ease, padding-bottom .35s ease;
}
html.js .acc-panel[hidden] { display: none; }
html.js .acc-item[data-open] .acc-panel { padding-bottom: 20px; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .btn, .acc-head, .main-nav, .nav-toggle span, .field input, .field textarea { transition: none; }
  .btn:hover, .btn:active { transform: none; }
  html.js [data-reveal] { opacity: 1; transform: none; transition: none; }
  html.js [data-reveal].btn.reveal-done { transition: none; }
  html.js [data-reveal].btn.reveal-done:hover,
  html.js [data-reveal].btn.reveal-done:active { transform: none; }
  .hero-band .doodle { animation: none; }
  .zzz { animation: none; }
  .figure-stack .sticker { animation: none; }
  #preloader-wrapper { display: none !important; }
  html.js .acc-panel { transition: none; }
  .acc-head .acc-icon { transition: none; }
  .ig-track { animation: none; transform: none; will-change: auto; }
}

/* Reviews band (2026-07-20, side-chat decisions): warm off-white ground (no
   blue/teal section fills - teal stays an accent), photo-framed cards from
   the six client-cleared review photos, orange stars (jewel-accent use only -
   the recorded brand exception; never large text on white). Clean by design:
   generous whitespace, short quotes, static - no carousel, nothing to pause. */
/* Rotating review seated inside the About column (Owner 2026-07-21). Everything
   here matches the surrounding body copy on size, leading and weight - nothing is
   bold and nothing is larger. The change of voice is carried by the italic, the
   orange marks and the attribution, deliberately NOT by emphasis, so the block
   reads as the same passage continuing rather than a widget dropped in. */
/* The block reserves its full height even when a quote is short. Nothing sits
   below it inside the column any more, but the About section's height feeds the
   whole page: without this, every rotation between a two-line and a three-line
   quote would shift everything beneath the section by 30px. The quotation itself
   hugs its content so the rule still ends at the name - the slack sits below the
   rule, as empty space.
   7.5em = 4 lines (up to 3 for the quote, 1 for the name); 32px = the stars row
   and its margin. */
.ab-review {
  /* 24 -> 36px, +50% (Owner 2026-07-21). Written as a multiple of --space-5
     rather than a literal 36px so the relationship to the scale stays visible -
     there is no 36px step on the 4pt ladder. */
  margin-top: calc(var(--space-5) * 1.5);
  min-height: calc(7.5em + 32px);
}
/* The rule brackets the QUOTATION only - stars, quote, name - and stops at the
   name (Owner 2026-07-21). The closing line below is the salon's own voice
   resuming, so it sits outside the rule and back on the paragraph measure. */
.ab-quoted {
  margin: 0;
  border-left: 2px solid var(--orange);
  padding-left: 22px;
}
/* No reservation here - the quotation hugs its content so the rule can end at
   the name. The height is held by .ab-review above instead. */
.ab-stars { display: flex; gap: 5px; margin: 0 0 14px; line-height: 1; }   /* 15 -> 17.25px, +15% (Owner 2026-07-21); 14px clear of the quote */
.ab-stars svg { width: 17.25px; height: 17.25px; fill: var(--orange); display: block; }
.ab-quote {
  margin: 0;
  color: var(--ink-deep);
  font-size: var(--fs-body);
  line-height: var(--body-leading);
}
.ab-quote [data-ab-text] { font-style: italic; }
.ab-mark {
  font-family: Georgia, "Times New Roman", serif;
  color: var(--orange);
  font-size: 1.5em; line-height: 0;
  vertical-align: -.28em; display: inline-block;
}
/* Attribution on its own line, flush with the quote's left edge (Owner 2026-07-21). */
.ab-who {
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--body-leading);
  margin: 0;
}
/* Body face, NOT --font-display: Paytone One is a single heavy weight and read as
   bold even at 400, which the Owner did not want on the closing line. */
/* .44s, not .4s (Owner 2026-07-21, 10% slower). Kept in lockstep with FADE in
   main.js, which waits this long before swapping the text - change one and the
   other has to move with it. */
.ab-quote, .ab-who { transition: opacity .44s ease; }
.ab-review.is-swapping .ab-quote,
.ab-review.is-swapping .ab-who { opacity: 0; }
/* Quote holds a 60% measure on desktop so it reads as a pulled aside rather than
   another full-width paragraph; full width once the column itself is narrow. */
@media (min-width: 901px) {
  /* .ab-review prefix is load-bearing: this block sits inside .split, and
     ".split p { max-width: 68ch }" at (0,1,1) beats a bare ".ab-quote" at
     (0,1,0). Two classes (0,2,0) takes it back. */
  .ab-review .ab-quote, .ab-review .ab-who { max-width: 60%; }
}
@media (prefers-reduced-motion: reduce) { .ab-quote, .ab-who { transition: none; } }

.reviews-band { background: var(--cream-2); }
.reviews-band .intro a { color: var(--teal-ink); }
.rb-stars { display: flex; gap: 6px; justify-content: center; margin: 6px 0 14px; color: var(--orange); }
.rb-stars svg { width: 26px; height: 26px; fill: currentColor; }
/* Cards, take 2 (Owner 2026-07-20): the boxed white cards read too heavy.
   No box at all now - a round-framed dog portrait, the quote set as display
   type rather than body copy in a <p>, and the first name beneath. The frame
   is the branding's own move: a cream ring on the warm ground, the same
   circle language as the paw stickers. */
.tst-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 48px var(--card-gap); margin-top: 48px; }
.tst-grid > * { min-width: 0; }
.tst-card { margin: 0; display: flex; flex-direction: column; align-items: center; text-align: center; }
.tst-frame {
  display: block; width: 132px; height: 132px; border-radius: 50%;
  padding: 6px; background: var(--white);
  box-shadow: 0 4px 18px color-mix(in srgb, var(--ink) 10%, transparent);
}
.tst-photo { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }
.tst-quote {
  margin: 22px 0 0; max-width: 30ch;
  font-family: var(--font-body); font-weight: 600;
  font-size: 1.12rem; line-height: 1.55; color: var(--ink);
}
.tst-quote::before { content: "\201C"; }
.tst-quote::after { content: "\201D"; }
.tst-name {
  margin-top: 12px; color: var(--muted);
  font-size: .82rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
}
@media (max-width: 1024px) { .tst-grid { grid-template-columns: repeat(2, 1fr); gap: 40px 24px; } }
@media (max-width: 600px) { .tst-grid { grid-template-columns: 1fr; } }

/* Footer rebuild (Owner 2026-07-20, revised): reviews and the Book tile are
   both OUT - proof lives in the homepage testimonial band, booking in the nav
   button and the hero/pricing CTAs. The footer orients instead: Explore /
   Visit, then the Daily Wag on its own blocky strip. The wag signature and
   the joke tile are the two personality moves; both static under reduced
   motion and without JS. */
.f-links { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.f-links a { display: inline-block; padding: 8px 0; color: rgba(255, 255, 255, .9); }
.f-links a:hover { color: #fff; }
/* KNOWN AA EXCEPTION (Owner 2026-07-21, eyes open): --orange is 3.34:1 on the
   --ink footer, under the 4.5:1 AA bar for text this size. Chosen deliberately
   so this line matches the is-today hours bullet exactly; --peach was 5.09:1.
   Carry to the white-on-teal contrast merge gate as an accepted exception. */
.f-open { color: var(--orange); font-weight: 800; margin: 12px 0 0; }
.hours li.is-today span:first-child { color: #fff; font-weight: 800; }
.hours li.is-today span:first-child::before {
  content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--orange); margin-right: 8px; vertical-align: 2px;
}
.footer-bottom { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 24px; }
/* Tap-size lift (side-chat fix): social targets to 44px. */
.footer-social a { width: 44px; height: 44px; }

/* Secondary action next to a primary CTA - the "or send us a note" route the
   Owner asked to surface alongside the phone number (2026-07-20). */
.btn-ghost {
  background: transparent; color: var(--teal-ink);
  box-shadow: inset 0 0 0 2px currentColor;
}
.btn-ghost:hover { background: var(--pale); color: var(--teal-deep); }
.studio-actions { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
