
/* ================================================================
   ALTHA ALCHEMY — Theme Stylesheet
   Single canonical file,
no duplicate rules
   ================================================================ */

/* ── Josefin Sans + Nunito Sans — self-hosted ──────────────────
   Headings are Josefin Sans, everything else is Nunito Sans. Nunito Sans
   replaced Karla because the brief was a WARMER body face: Karla is a
   grotesque and reads cool, while Nunito Sans is humanist — open apertures,
   softly rounded terminals.

   Josefin Sans replaced Khand on 2026-09-18, Liv's call: Khand is condensed
   and read hard and sporty, wrong for reiki and breathwork. Josefin Sans is
   art-deco geometric, and two of its metrics are nothing like Khand's:

     uppercase is 63% WIDER — .691em average advance against Khand's .424em
       — so every uppercase heading wraps sooner, and the fitted Expansion
       line on the homepage renders about a third shorter, because it is
       sized to the page width and a wider word needs a smaller size;
     the x-height is very low — .378em against Khand's .569em — so the
       sentence-case display lines read smaller at the same font-size.

   Both were visible in the specimens this face was chosen from.

   IT IS VARIABLE, unlike Khand, which had no axes and needed one file per
   weight. One 28 KB file covers wght 100–700, so a new heading weight costs
   nothing and interpolates. Three font files now, 91 KB, down from five.

   THE DECLARED WEIGHT RANGE MUST MATCH THE FILE. Karla was once declared
   `300 500` over a file that carried 200–800, and everything outside the
   declared range was silently clamped. Josefin Sans carries 100–700, read
   off the binary with fontTools. Its DEFAULT instance is 100, so anything
   that fails to state a weight comes out hairline.

   Do not add per-weight @font-face rules — Google serves byte-identical
   files for each. */
/* 2026-09-28: Merriweather in place of Playfair Display, Chris's call. Still
   a serif — the second since Cormorant Garamond — but a text serif built for
   reading rather than a display face: heavier, wider counters, a much taller
   x-height and a genuinely deep descender, all of which moved every derived
   token below.

   DECLARED 300 900 BECAUSE THAT IS THE AXIS — read off the fvar table, not
   assumed. This is the OPPOSITE correction from the Playfair swap: Playfair's
   axis started at 400, so 300 anywhere in the sheet silently clamped to 400.
   Merriweather's axis starts at 300 AND its default instance is 300, so this
   is now the face where forgetting to state a weight is the risk that matters
   — a rule with no explicit weight renders lighter than intended, not just
   differently. --ah-display-weight goes back to 300 for exactly this reason;
   see its note in layout.css.

   Playfair, Jost and Josefin are left below, unreferenced so they never
   download, so backing this out is a one-line change to
   --font-family-display — PLUS the derived values that move with any face
   swap: --ah-display-weight, --leading-heading, the word-spacing on the
   h1-h6 rule and on the display-line role in layout.css, .ah-statement h2's
   line-height, and .altha-logo__shift's letter-spacing. They are listed
   against the token that holds each.

   THE FILE IS BIGGER THAN ANY DISPLAY FACE SHIPPED SO FAR — 89KB latin-only
   against Playfair's 38KB, Jost's 26.5KB — even after subsetting to the same
   unicode range and dropping the numeral/fraction OpenType features nothing
   here uses (tnum, pnum, numr, dnom, frac). The site's other faces are
   geometric or humanist sans with simple outlines; Merriweather's serifs and
   ink traps carry far more delta data per glyph across the variable axis, and
   that cost does not subset away. Worth knowing before treating a future
   heavy-download complaint as a regression somewhere else. */
@font-face {
  font-family: 'Merriweather';
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url('../fonts/merriweather-latin-var.woff2') format('woff2');
}
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-display-latin-var.woff2') format('woff2');
}
@font-face {
  font-family: 'Jost';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/jost-latin-var.woff2') format('woff2');
}
@font-face {
  font-family: 'Josefin Sans';
  font-style: normal;
  font-weight: 100 700;
  font-display: swap;
  src: url('../fonts/josefin-sans-latin-var.woff2') format('woff2');
}
@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 200 1000;
  font-display: swap;
  src: url('../fonts/nunito-sans-latin-var.woff2') format('woff2');
}
@font-face {
  font-family: 'Nunito Sans';
  font-style: italic;
  font-weight: 200 1000;
  font-display: swap;
  src: url('../fonts/nunito-sans-latin-var-italic.woff2') format('woff2');
}
/* Dancing Script, 2026-09-29 — one component only: the signature under the
   About page's "My Story" biography (.ah-story__signature). Not a systemic
   role like the display or body face, so it gets a token
   (--font-family-signature) for the same reason every other typeface here
   does — nothing outside this file should hardcode a font-family string —
   but none of the display-face derivation ceremony (word-spacing, leading,
   the hero measure band) applies to it: it dresses one word, in one place,
   not running text or headings site-wide.
   VARIABLE, wght 400-700, default 400. liga is load-bearing here and must
   never be disabled — Dancing Script's cursive joins between letters are
   drawn as ligature substitutions, not fixed per-letter glyphs, so turning
   off font-variant-ligatures/liga on this class would break the connected
   script look, not just tidy it. */
@font-face {
  font-family: 'Dancing Script';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/dancing-script-latin-var.woff2') format('woff2');
}
:root {
  /* ── Palette ──────────────────────────────────────────────────────
     One palette for the whole site, and the single source of colour
     truth. Two rules for anything added below:

       1. NOTHING outside this block declares a raw colour. If you need
          a token at partial opacity, use color-mix() against it — never
          unroll it to rgba(). Grep the file for '#' to check.
       2. theme.json mirrors these values by hand, because JSON cannot
          read CSS variables. Change a value here, change it there too,
          or the block editor will offer a different palette to the page
          it is editing.

     Taken from the palette image the client supplied: a warm paper
     ground with sage green, clay, blush and a grey, over a very dark
     olive used as the type colour.

     Those are SHAPE colours — large flat fills in the source image, and
     most are far too light to be read as text. Where a token is type,
     the hue was darkened to a contrast target rather than used raw:
     the sage arch is #648E7B, which is only 3.0:1 on paper, so
     --color-accent is a darkened version of it. The image's own values
     survive where the area is large: --color-background,
     --color-surface-deep and --color-accent-on-dark are exact.

     Unlike the violet scheme this replaces, the palette now agrees with
     the photography — the client photos are green canopy, and the sage
     sits in the same hue family. The hero rig below is still held back
     to a warm olive-charcoal rather than pushed green: matching the
     foliage exactly would flatten the photograph against its own frame.

     Every value here is WCAG AA as used, verified rather than eyeballed.
     Two traps worth knowing:
       · --color-accent FAILS as text on --color-ink. Use
         --color-accent-on-dark for accent text on any dark band.
       · --color-accent also FAILS as small text on --color-blush (4.33:1),
         which is now the tightest light ground — it is darker than
         --color-surface, which the accent was tuned against. Use
         --color-accent-dark (6.09:1) or --color-link (4.70:1) for accent
         text on blush.
       · The four --card-accent-* values double as the "View session"
         link colour, so they are darkened to pass AA as small text.
         All four clear 4.5:1 on paper, white and surface — surface is
         the tightest of the three, so it is the one they are tuned to. */

  /* Grounds. The page is white and the photographs carry the colour, as the
     Kinfolk reference does. Only ONE tinted ground exists (--color-surface),
     and it is allowed once per page. --color-blush is deliberately the same
     value so the old blush consumers fold into that one tint rather than
     introducing a second. */
  /* A warm off-white rather than paper white: pure white is tiring over a page
     this long, and this sits in the same hue family as the tinted block and
     the logo gold. Ink measures 16.91:1 on it and --color-muted 6.14:1, so
     nothing moves closer to the AA line than it was.
     --color-white stays pure white: it is the colour of type on the dark
     bands, not a ground. */
  --color-background:  #F7F5F0;
  --color-white:       #FFFFFF;
  /* Deepened a step with the ground. Against white the tinted block stood at
     1.31:1; on the new ground #E4E1DA would have fallen to 1.20 and the band
     would barely register. #E0DCD3 restores it to 1.26, and muted type on it
     still clears AA at 4.89:1 (ink 13.46:1). */
  --color-surface:     #E0DCD3;   /* warm grey — the single tinted block */
  --color-blush:       #E0DCD3;   /* = --color-surface; kept so old refs resolve */

  /* Ink. One near-black for every heading and every line of running text;
     --color-muted is the only softer voice, for captions and meta.
     18.42:1 on white, 14.11:1 on the tint. */
  --color-ink:         #141414;
  --color-ink-deep:    #000000;
  --color-body:        #141414;
  --color-muted:       #5C5C5C;   /* 6.69:1 on white, 5.12:1 on the tint */
  --color-border:      #D0D0D0;   /* hairline, decorative only */

  /* Accent. There is no coloured accent any more: links and buttons are ink,
     and a link is marked by its underline rather than by hue. The tokens are
     kept because ~60 rules name them. */
  --color-accent:      #141414;
  --color-accent-dark: #5C5C5C;
  --color-accent-on-dark:  #FFFFFF;

  --color-link:        #141414;
  --color-link-hover:  #5C5C5C;

  /* On dark */
  /* THE DARK BAND AND THE FOOTER. Chris's call, 2026-09-24: the builder
     offered "Forest" and resolved it to --color-ink, so the label promised a
     green and delivered #141414. This is Liv's own brand green -- #4A5A3C,
     still on the admin screens, which is where that name came from -- taken
     dark enough to carry white type. Hue 96, so it sits on the same warm side
     as the blush and the gold rather than against them.

     NOT --color-ink, which stays #141414 and stays a TYPE colour: it is named
     51 times across this file and layout.css, almost all of it text. */
  --color-forest:      #2C3526;

  --color-on-dark:     #FFFFFF;
  /* #A8A8A8 until 2026-09-24, which was 7.75:1 on the old near-black ground.
     On the olive it is 5.37:1 flat and 3.94:1 at the footer texture's peak --
     under the 4.5 floor, so it went up a step. #B4B4B4 is 6.16:1 flat and
     4.52:1 at that peak. See the note on --color-gold-light below for why the
     peak is what this is measured against. */
  --color-on-dark-muted: #B4B4B4;  /* 6.16:1 on forest, 4.52:1 under the texture */

  /* Buttons */
  --color-btn:         #141414;
  --color-btn-hover:   #000000;
  --color-btn-text:    #FFFFFF;

  /* Secondary accent — folded into the ink/tint pair. */
  --color-alt:         #141414;
  --color-alt-dark:    #000000;
  --color-alt-light:   #E4E1DA;

  /* Status. The only hues left on the site, and only for form feedback. */
  --color-success:     #3E6A4E;   /* 5.57:1 on white */
  --color-success-bg:  #EDF2EE;
  --color-error:       #A2433A;   /* 5.53:1 on white */
  --color-error-bg:    #F8EDEA;

  --color-shadow:      #141414;

  --color-selection:   #E0DCD3;
  --color-surface-deep:  #D0D0D0;

  /* Liv's logo gold, read out of the circle-logo SVG she uses in her
     marketing. It is 1.97:1 on white, so it is NOT a text colour and never a
     hairline on a light ground: it dresses the logo mark, and it is the FILL
     of the booking button (Chris's call, 2026-09-18) where the ink on top of
     it measures 8.51:1. --hero-logo-mark carries the same value. */
  --color-gold:        #C2B067;
  /* The booking button's edge. A gold fill is only 1.97:1 against the page
     ground, so the button's OUTLINE fails the 3:1 that WCAG 1.4.11 asks of a
     control's boundary even though its label is fine — the shape goes faint
     for a low-vision reader while the words stay sharp. This is the same hue
     carried to 74% gold / 26% ink: 3.29:1 on the ground, and it doubles as
     the hover fill, where the ink label still measures 5.14:1. */
  --color-gold-deep:   #958751;
  /* THE FOOTER'S COLUMN LABELS, and nothing else. The footer blends a screen
     texture whose peak pixel value is 25, so the lightest ground any footer
     text sits on is not --color-forest but #41493B. Liv's gold is 5.90:1 on
     the flat colour and 4.33:1 there -- just under the 4.5 an --text-xs label
     needs. This is her hue and saturation with the lightness raised two
     points, which clears it at 4.52:1 and is not a difference the eye can
     find. --color-gold itself is read out of her logo and is not to be moved.

     Measured at the PEAK rather than the flat colour because that is this
     project's own convention for the textured blocks -- see the texture note
     in layout.css, which quotes worst-case figures for the same reason. */
  --color-gold-light:  #C5B46E;

  /* Service-card accents — all four are ink now; the cards carry no colour. */
  --card-accent-1:     #141414;
  --card-accent-2:     #141414;
  --card-accent-3:     #141414;
  --card-accent-4:     #141414;

  /* ── Hero ────────────────────────────────────────────────────────
     The hero is a stack of four painted layers over a photograph, so it
     needs its own sub-family. Channel triples (space-separated) because
     every one of these is used at several opacities — consume them as
     rgb(var(--hero-x-rgb) / 0.4).

     The type colours are separate from the page palette on purpose:
     they are read against the photograph, not against a page ground.
     They stay warm cream: cream on a neutral rig still reads, and it
     keeps the hero from looking like a different site to the body.

     The rig is a warm olive-charcoal, deliberately NOT the palette's
     sage. The photograph is green canopy; tinting its frame the same
     green flattens the two together. A warm neutral lets the foliage
     read as foliage while the sage owns everything below the fold. */

  /* Hero type, over the photo */
  --hero-ink:          #FFFFFF;   /* hero H1, nav over the photo */
  --hero-ink-warm:     var(--color-btn-text);
  --hero-ink-soft:     #FFFFFF;
  --hero-ink-hint:     #FFFFFF;
  --hero-ink-body:     #FFFFFF;
  --hero-rule-rgb:     255 255 255;  /* whisper rule */
  --hero-scrim-rgb:    10 14 10;   /* leaf-sprig drop shadow */
  --hero-logo-mark:    #F4ECC9;
  --hero-logo-1:       #FFFFFF;
  --hero-logo-2:       #FFFFFF;

  /* Hero light rig — each painted layer as a complete gradient.
     Whole gradients rather than per-stop channel tokens because the
     multi-stop ramps are the design: each stop darkens AND shifts hue
     slightly, and that curve was tuned by eye against the photograph.

     These values are now authoritative. They used to be fallbacks under
     a PHP generator (altha_hero_css) that rebuilt every ramp from an
     admin-set tint and emitted them as an inline :root block; that whole
     system was removed, so what you edit here is what the browser paints.

     Do not raise the stop OPACITIES. The vignette contains the outer
     edge and the scrim sits under type; between them they used to cover
     most of the photograph, which is the thing they exist to frame. */
  --hero-ground:       radial-gradient(64% 72% at 70% 44%, #fbfbfa 0%, #f0efec 12%, #cfcdc8 30%, #86847e 54%, #26262a 100%);
  --hero-vignette:     radial-gradient(120% 112% at 74% 44%, rgba(16,18,16,0) 55%, rgba(16,18,16,0.06) 70%, rgba(16,18,16,0.14) 82%, rgba(16,18,16,0.22) 92%, rgba(16,18,16,0.3) 100%);
  --hero-scrim:
    radial-gradient(66% 74% at 4% 66%, rgba(10,14,10,0.62) 0%, rgba(10,14,10,0.5) 24%, rgba(10,14,10,0.32) 44%, rgba(10,14,10,0.16) 62%, rgba(10,14,10,0.05) 78%, rgba(10,14,10,0) 90%),
    radial-gradient(52% 40% at 100% 0%, rgba(10,14,10,0.5) 0%, rgba(10,14,10,0.22) 45%, rgba(10,14,10,0) 78%),
    linear-gradient(180deg, rgba(10,14,10,0.34) 0%, rgba(10,14,10,0.2) 7%, rgba(10,14,10,0.09) 13%, rgba(10,14,10,0.03) 18%, rgba(10,14,10,0) 24%),
    linear-gradient(0deg, rgba(10,14,10,0.4) 0%, rgba(10,14,10,0.27) 10%, rgba(10,14,10,0.15) 20%, rgba(10,14,10,0.07) 28%, rgba(10,14,10,0) 34%);

  /* Bokeh mote palette, read by hero-canopy.js. Comma-separated RGB
     triples — NOT semicolons, which would end the declaration. The JS
     falls back to its own copy if this is absent or unparseable. */
  --hero-bokeh-small:  255 253 246, 252 248 238, 246 240 228;
  /* THE ONE LINE THAT SWAPS THE DISPLAY FACE. Backing out to Playfair is
     changing this back — but the derived values below do NOT come back with
     it: --ah-display-weight, --leading-heading, the word-spacing on the
     h1-h6 rule and on the display-line role in layout.css, .ah-statement h2's
     line-height, and .altha-logo__shift's letter-spacing.
     Fallback stays a SERIF stack — falling back to a system sans would change
     the page far more than falling back to Georgia. */
  --font-family-display: 'Merriweather', Georgia, 'Times New Roman', serif;
  --font-family-body:    'Nunito Sans', 'Segoe UI', system-ui, sans-serif;
  /* One component only — see the @font-face note above. Cursive script
     fallback, not a serif or sans one: a missing web font here should still
     read as "a signature," not fall back to body text. */
  --font-family-signature: 'Dancing Script', 'Brush Script MT', cursive;

  /* Global inheritance is limited to typography and link colour — see
     the note above. */
  --font-family: var(--font-family-body);
  --font-family-heading: var(--font-family-display);
  --font-weight-light:  300;
  --font-weight-normal: 400;
  --font-weight-medium: 500;
  /* Headings only, and deliberately separate from --font-weight-light, which
     also dresses the testimonial and pull quotes — those are body text in
     Nunito Sans and should not follow the headings up.
     Josefin Sans is VARIABLE, so unlike Khand this is no longer restricted to
     the weights a file exists for — anything in 100–700 interpolates. Stay
     inside that range: the @font-face declares it, and outside it the browser
     clamps silently. */
  /* 400 -> 600 -> 500 -> 400 over 2026-09-19. BACK WHERE IT STARTED, and the
     round trip was my error, so the reasoning is recorded rather than the
     number alone.

     The raise was justified with "light strokes go optically thinner as type
     grows". That is true of hairlines at small sizes and it is the WRONG way
     round here: at a given weight, larger type carries more visual mass, which
     is why display settings conventionally run LIGHTER than text, not heavier.
     The section head went 26.9px -> 51.2px in the same change, so raising the
     weight alongside it was a double increase, and Chris called it too heavy
     twice.

     400 is Liv's documented heading weight.

     THE STEP DOWN IS BACK under Merriweather — its wght axis is 300-900, wider
     than Playfair's 400-900 floor, so 300 is available again if 400 ever reads
     heavy. Not used here: this token stays 400, Liv's figure. --ah-display-weight
     is the one that actually moved back to 300, because it dresses smaller,
     lighter-context text (the lead paragraph, pulled lines) rather than
     headings — see its note in layout.css. */
  --font-weight-heading: 400;

  /* Uppercase tracking, and the word space that goes with it. Both re-derived
     for JOST on 2026-09-19 from the OUTLINES, not the bounding boxes — the
     earlier pass summed side bearings, which is only the true gap for two
     flat-sided letters and is badly wrong for anything diagonal or open.

     Method: instance the variable font at wght 400, flatten every glyph to
     polygons, apply the GPOS kern, then walk 400 scanlines across the cap band
     and take the real horizontal clearance at each. 90 uppercase pairs, taken
     from the headings this site actually ships.

                              median MIN   median MEAN   word space
        Nunito Sans (normal)     .1296        .2656        .257  = 0.97x MEAN
        Josefin Sans             .1625        .2882        .316  = 1.10x
        JOST                     .1200        .2396        .300  = 1.25x

     So the LETTERFIT was never the loose part: Jost is already tighter than
     Nunito Sans, which is this site's own body face and the fairest "normal"
     to hold it against. What is loose is the WORD SPACE — Jost keeps a
     generous one over an unusually tight fit, and at 1.25x its own letter gap
     against Nunito's 0.97x it is 29% wide relative to the face it sits in.
     Headings are two to four words, so those gaps are most of what the eye
     reads, which is why the page kept reading letterspaced after the tracking
     came off. -0.07em lands the word gap at .2300em, 0.96x the letter gap.

     Tracking then takes -0.025em on top, and the old note's stop was measured
     on the wrong thing. By bounding box AT/RA/RY/YT all read .0150em; by
     outline AT is .1250em (an apex under a crossbar — the boxes touch, the ink
     is nowhere near), and RY and YT occur in no heading we ship. RA is the only
     genuinely tight pair, and its pinch is the R's leg tip against the A's
     foot: at -0.025em it overlaps by .0100em over 1.2% of the cap band, at the
     baseline, which is how tight display caps are drawn anyway. THE HARD STOP
     IS AROUND -0.03em, where that reaches 2% of the band and starts to show.
     Everything else clears .0200em or more.

     Ignore the ?J pairs in any measurement of this face. Jost's J has a left
     side bearing of -0.111em — the tail swings left under the preceding letter
     by design — so pair maths reports QJ/AJ/KJ/VJ/YJ as overlapping when they
     are drawn that way on purpose. None of them occurs in a heading we ship.

     BOTH TOKENS ARE NOW UNREFERENCED, and that is the whole shape of the
     2026-09-24 change: headings are sentence case, and sentence case takes
     neither figure, because both are derived from CAPITALS. -0.025em of
     tracking closes lowercase up rather than opening it, and -0.07em of word
     space is measured against a letter gap that only capitals have. Every
     heading role now takes the recipe .ah-begin__text h2 used to carry alone:
     letter-spacing:normal and a -0.05em word space, the display line's figure,
     derived for lowercase (see the note on that role in layout.css).

     They stay DEFINED, the way Josefin Sans stays in the @font-face block
     unreferenced — putting the capitals back is then a one-line change rather
     than a day of re-measurement. Do not delete them, and do not "tidy" either
     one back onto a heading.

     The wide-tracked roles are separate, deliberate, and KEEP their capitals
     (.14em/.21em/.38em): the kickers, the wordmark, the footer's column
     headings, the nav dropdowns and the primary button. Those are labels, not
     headings, and the distinction is the reason the site still has any
     capitals at all. .altha-footer__label is the only one that is an h-tag, so
     it restores word-spacing:normal where it is set, because letters opened to
     .14em need the word gap opened with them.
     Re-derive both only if the display face changes AND the capitals return. */
  --tracking-caps: -0.025em;
  --word-spacing-caps: -0.07em;
  /* THE FLOOR IS 1.089 FOR MERRIWEATHER, up from .972 for Playfair and 1.020
     for Jost (and from .87 for Jost in capitals, which is a different test
     entirely — see the history at the bottom of this comment). This is the
     first face-swap where the floor went UP rather than down.

     Headings are sentence case, so the test is a descender sitting over the
     next line's ascender. Merriweather 400 measures its tallest ascender at
     .819em (b/d/h/k/l) and its deepest descender at -.270em (g; y stops at
     -.251), so the ink spans 1.089em — Merriweather is a text serif with a
     real descender loop on the g, where Playfair's is comparatively shallow.
     We ship 1.17, which clears by .081em — close to the .078em clearance
     every face before it shipped, chosen the same way rather than by eye.

     WATCH BOTH DIRECTIONS ON THIS FACE. Merriweather also renders optically
     LARGER at a given font-size — x-height .555em against Playfair's .515em,
     8% more, on a cap height that moved too (.743 against .708, 5% more) — so
     even with MORE leading than before, headings read bigger overall. If they
     read cramped, 1.089 is the hard stop; if they read loose, the dial is
     --ah-display-line / the section-head clamps, not this token, because the
     floor itself cannot come down.

     History, so the method is not re-derived from scratch each time: in
     CAPITALS the test was a line-ending comma over the next line's caps, and
     the floor was cap + comma depth — .870 for Jost, .844 for Josefin. That
     test does not apply to any heading on this site today. Merriweather's axis
     runs 300-900, so unlike Playfair there IS a lighter instance to check if
     the figure ever needs to move with weight again — untested here because
     --font-weight-heading stays 400. An accented capital still beats every
     ascender — Merriweather's E-acute reaches .988em, wanting 1.28 — and no
     heading we ship carries one.
     Re-derive from the binary with fontTools if the display face moves. */
  --leading-heading: 1.17;

  --text-xs:   0.78125rem;  /* 12.5px — captions and meta */
  --text-sm:   0.84375rem;  /* 13.5px */
  --text-base: 0.9375rem;   /* 15px — nav, links, UI */
  --text-lg:   1.125rem;    /* 18px — running text */
  --text-xl:   1.25rem;     /* 20px — h4 */
  /* 22px, was 1.625rem/26px. At 26px it was EXACTLY the floor of --text-3xl
     below, so bare h2 and bare h3 rendered identically at any viewport under
     ~1238px — no hierarchy at all in WordPress-authored content. That had been
     invisible while the h1-h6 rule was commented out; restoring it in 4c2c5d3
     made it show. 22px also matches --ah-block-title in layout.css, so an h3
     typed into the editor now measures the same as a designed block title.
     .altha-modal__service-name is itself an <h3>, so it moves with this and
     does not diverge; the modal price row reads it too. */
  --text-2xl:  1.375rem;
  --text-3xl:  clamp(1.625rem, 2.1vw, 2rem);   /* section titles, 26-32px */
  --text-4xl:  clamp(1.75rem, 2.6vw, 2.5rem);  /* 28-40px */
  --text-5xl:  clamp(2.125rem, 3.4vw, 3.25rem);  /* hero headline, 34-52px */

  --space-section-xl: 8rem;
  --space-section-lg: 5rem;
  --space-section:    clamp(3rem, 8vw, 8rem);

  --max-width-wide:    1280px;
  --max-width-content: 896px;

  /* ONE radius language for the whole site, from 2026-09-25 (Chris: "roll out
     the rounded corners... all buttons, cards, images"). Chrome (buttons, modal,
     dropdowns, mobile menu) reads this scale; page content in layout.css uses
     16px, the same value as --radius-xl, hardcoded there for the reasons its
     own notes give. Buttons are pills. Real circles (dots, icon badges) use 50%.
     Deliberately still square: full-bleed sections (hero, statement band,
     tinted/forest bands, footer), which touch the viewport edge, and the ruled
     form fields, whose underline is their affordance. */
  --radius-sm:    6px;
  --radius-md:    12px;
  --radius-lg:    12px;
  --radius-xl:    16px;
  --radius-2xl:   20px;
  --radius-3xl:   24px;
  --radius-pill:  999px;

  --transition-base: all 0.3s ease;
  --transition-slow: all 0.5s ease;

  /* Page margin for the full-bleed blocks (the reference runs 34/26/16). */
  --altha-gutter: 34px;

  /* ── The bar's scale ──────────────────────────────────────────────
     FLUID ON WINDOW HEIGHT, and it has to be height and not width. The header
     is fixed over a hero that is 100svh, so what decides whether it reads as a
     thin bar or a slab is its share of the GLASS, not of the page measure. At
     the 112px it used to be pinned at, that share is 8.3% of a 1350px-tall
     window and 15.7% of the ~712px a 1920x1200 laptop actually gives you at
     Windows' default 150% scaling — the same bar carrying twice the weight on
     the smaller screen, which is what read as the header being out of
     proportion. Width is the NAV's problem and is solved separately at the gap
     steps near :663; scaling on width here would put the seven links back over
     the row.

     One driver, the logo mark, because it is the tallest thing in the row and
     so is what the height is actually made of. Everything else is a ratio of
     it, which keeps the lockup's proportions at every size and lets the two
     height tokens below be COMPUTED instead of hand-synced. They were
     hand-synced, and they had already drifted: 108px was derived from the 60px
     custom-logo path, but with no custom logo set the default lockup draws a
     64px mark, so the bar was really 112px and every anchor landed 4px shallow.

     4.74svh is 64px at 1350, so a tall window is unchanged — it is only the
     short one that comes down. The 50px floor is a measured stop, not a round
     number: the shipped compact state already draws this mark at 46px, so 50 is
     inside proven territory, and it holds the bar at 87.5px (12.3% at 712)
     rather than letting a pure ratio take it to 59px, where the wordmark under
     the mark falls to 6.8px.

     svh, NOT vh or dvh, for the reason the hero gives at layout.css:105 — dvh
     tracks the mobile URL bar live, so every scroll that retracted the bar
     would resize the header mid-scroll. The vh line is the fallback for
     anything that does not know svh. */
  --altha-logo-mark:          clamp(50px, 4.74vh,  64px);
  --altha-logo-mark:          clamp(50px, 4.74svh, 64px);
  --altha-logo-mark-compact:  clamp(40px, 3.41vh,  46px);
  --altha-logo-mark-compact:  clamp(40px, 3.41svh, 46px);
  /* The hamburger is the one item in the bar that must NOT scale — 48px is the
     tap-target floor — and below 1024px it is the tallest thing in the
     compacted row. It is shown below 1024px only, so above that it contributes
     nothing; the override sits with the toggle's own rule. */
  --altha-toggle-size: 0px;

  /* Rendered height of the fixed header, full and compacted: bar padding x2
     plus whatever the tallest item in the row is. Everything that has to clear
     the header reads these and nothing else. Both are calc'd off the same
     tokens the bar itself draws from, so the two can no longer disagree — and
     the ratios (0.375 and 0.26087) are the 24/64 and 12/46 the bar shipped
     with. --altha-logo-img is 0.9375 of the mark and so never leads. */
  /* The header is a floating pill from 2026-09-25 (see .altha-header__inner).
     The coefficients are 2 x the bar padding ratio in each state: 0.21875 full
     (14px on a 64px mark) and 0.2 compact — the pill is slimmer than the old
     full-width bar, which was 0.375 / 0.26087. Change a padding ratio below
     and its coefficient here together. The pill's own gap from the top of the
     window is --altha-pill-top and is added by whatever needs to clear it. */
  --altha-pill-top:         14px;
  --altha-header-h:         calc(var(--altha-logo-mark) * 0.4375 + max(var(--altha-logo-mark), var(--altha-toggle-size)));
  --altha-header-h-compact: calc(var(--altha-logo-mark-compact) * 0.4 + max(var(--altha-logo-mark-compact), var(--altha-toggle-size)));

  --shadow-sm: none;
  --shadow-md: none;
  --shadow-lg: none;
  --shadow-xl: none;
  --shadow-2xl: none;
}

/* Content area. From 2026-09-25 the page is a centred column of at most 1440px
   with a floor of 5vw (64px at 1280), not a full-bleed grid on a 34px gutter:
   on a laptop or a large monitor the copy ran to the edge of the glass. Every
   consumer of this token — the 24-column grid, the header, the footer, the card
   rail, the photo opener — moves with it, which is how the bar stays aligned
   with the page. Full-bleed photographs are unaffected; they are not on it. */
@media (min-width:1024px) { :root { --altha-gutter:max(5vw, calc((100vw - 1440px) / 2)); } }
@media (max-width:1023px) { :root { --altha-gutter:26px; } }
@media (max-width:739px)  { :root { --altha-gutter:24px; } }

/* ── Reset ──────────────────────────────────────────────────────── */
*,*::before,*::after { box-sizing:border-box; margin:0; padding:0; }
html { scroll-behavior:smooth; -webkit-text-size-adjust:100%; }
/* Anchor targets must clear the fixed header — without this, /services-and-costs/#slug
   parks the heading underneath the bar. */
[id] { scroll-margin-top: calc(var(--altha-header-h-compact) + var(--altha-pill-top) + 1.5rem); }
body {
  font-family: var(--font-family);
  font-size: var(--text-base);
  /* Weight 500 and -0.04em are the reference's body treatment. The leading is
     NOT: it runs 1.2 there, which works for one or two marketing sentences and
     not for 60-100 word paragraphs like the Reiki card or the story sections.
     1.75 stays on purpose. */
  font-weight: 400;
  /* Was 500 / -0.04em / 1.75. The reference sets running text at a normal
     weight with a HAIR of positive tracking; the tight negative tracking was a
     large part of why the page read as a template. */
  letter-spacing: 0.01em;
  line-height: 1.6;
  color: var(--color-ink);
  background-color: var(--color-background);
  -webkit-font-smoothing: antialiased;
}
/* Clears the fixed header — the ONLY thing that does. Templates must not add
   a top gap of their own; that is how the previous four stacked mechanisms
   drifted to between 110px and 218px depending on the template. Any page that
   opens with a photograph is exempt: its hero runs under a transparent bar and
   is sized to the whole viewport, so reserving space above it would push the
   opener past the fold. */
body:not(.has-photo-hero) { padding-top: calc(var(--altha-header-h) + var(--altha-pill-top)); }
img,video { max-width:100%; height:auto; display:block; }
a { color:var(--color-link); text-decoration:none; transition:var(--transition-base); }
a:hover { color:var(--color-link-hover); }
::selection { background:var(--color-selection); color:var(--color-ink); }
ul,ol { list-style:none; }
button { cursor:pointer; font-family:var(--font-family); border:none; background:none; }
input,textarea,select { font-family:var(--font-family); }
/* ── Typography ─────────────────────────────────────────────────── */
/* SENTENCE CASE from 2026-09-24, Chris's call. Three declarations change
   together and none of them is optional on its own, because all three of the
   old values were derived from capitals:

     text-transform  uppercase -> none
     letter-spacing  --tracking-caps (-0.025em) -> normal
     word-spacing    --word-spacing-caps (-0.07em) -> -0.05em -> +0.035em with
                     the Playfair swap -> +0.07em with Merriweather — see below

   Tracking measured for capitals is WRONG for lowercase in the same direction
   twice over: Jost's lowercase fits tighter than its capitals to begin with,
   and -0.025em was already near this face's hard stop.

   THE WORD SPACE CHANGED SIGN ON THE PLAYFAIR SWAP, 2026-09-24, and this is
   the single most carry-over-prone value on the site: it LOOKS like a house
   preference for tight word spacing and it is nothing of the kind. It is one
   target — a word gap of .55 of the x-height, which is where display text
   wants it — solved for whatever face is in --font-family-display:

     face                space   x-height   natural   correction
     Jost                .300em    .460em     0.652     -0.047  (shipped -0.05)
     Playfair Display    .249em    .515em     0.483     +0.034  (shipped +0.035)
     MERRIWEATHER        .237em    .555em     0.427     +0.068  (ships +0.07)
     Nunito Sans (body)  .257em    .486em     0.528     +0.011  (not applied)

   Jost has a generous space over a small x-height, so it needed closing.
   Playfair has a NARROW space over an unusually large one, so it needed
   opening a little. Merriweather needs MORE opening than Playfair did, and for
   a different reason: its natural space isn't unusually tight (.237em is
   close to Jost's .300em), it's that its x-height is the largest of any face
   this site has shipped — .555em, 8% over Playfair's .515em and nearly half
   again Jost's .460em — so the 0.55x target itself is bigger and the gap
   between it and the font's own space is wider. Carrying Playfair's +0.035
   over would have landed the gap at .272em, 0.49 of the x-height — under the
   0.55 target and visibly tighter than the running text sitting next to it.
   Same derivation on the display-line role in layout.css; both move together,
   always. --leading-heading moved with these too — see its token.

   letter-spacing and word-spacing INHERIT, and every heading class on the site
   (.altha-feature__heading, .altha-carousel__title, .altha-offer-card__title,
   .altha-page-hero__heading and the rest) sits on a real h1-h6 tag, so setting
   them here once reaches all of them. Do NOT repeat them per component — and
   note that a component rule re-declaring its own line-height beats this one,
   which is what the .ah-* roles in layout.css do. They read the token. */
h1,h2,h3,h4,h5,h6 { font-family:var(--font-family-heading); font-weight:var(--font-weight-heading); line-height:var(--leading-heading); letter-spacing:normal; word-spacing:0.07em; text-transform:none; color:var(--color-ink); }
h1 { font-size:var(--text-5xl); }
h2 { font-size:var(--text-3xl); }
h3 { font-size:var(--text-2xl); }
h4 { font-size:var(--text-xl); font-weight:var(--font-weight-normal); }
p  { color:var(--color-body); line-height:1.6; }

/* ── Layout ─────────────────────────────────────────────────────── */
.altha-container {
  max-width: var(--max-width-wide);
  margin-inline: auto;
  padding-inline: 1.5rem;
}
@media (min-width:1024px) { .altha-container { padding-inline:3rem; } }

/* ── Label ──────────────────────────────────────────────────────── */
.altha-label {
  display: block;
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: 0.9rem;
}

/* ── Buttons ────────────────────────────────────────────────────── */
.altha-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 1rem 2rem;
  /* 12px, the title labels' radius (2026-09-25): buttons were pills, Chris
     preferred the radius on the cards and labels. The header capsule and the
     nav pills stay pills — those are a different object. */
  border-radius: var(--radius-lg);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  transition: var(--transition-base);
  white-space: nowrap;
  cursor: pointer;
  border: none;
  font-family: var(--font-family);
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: none;
}
.altha-btn--primary        { background-color:var(--color-accent);   color:var(--color-white)  !important; }
.altha-btn--primary:hover  { background-color:var(--color-accent-dark); color:var(--color-white) !important; }
/* Sold out / disabled. Was a grey-on-grey pair hardcoded in page-events.php
   and theme.js, so it sat outside the palette and looked dead beside it. */
.altha-btn--soldout        { background-color:var(--color-surface) !important; color:var(--color-body) !important; cursor:not-allowed; opacity:.85; }
.altha-btn--outline        { background-color:color-mix(in srgb, var(--color-ink) 8%, transparent); color:var(--color-ink) !important; border:0; }
.altha-btn--outline:hover  { background-color:color-mix(in srgb, var(--color-ink) 14%, transparent); }
.altha-btn svg { width:1rem; height:1rem; flex-shrink:0; }
/* Hero buttons are smaller: px-6 py-3 */
.altha-hero__actions .altha-btn { padding:0.75rem 1.5rem; }

/* ── Header ─────────────────────────────────────────────────────── */
.altha-header {
  position: fixed;
  top:0; left:0; right:0;
  z-index: 100;
  /* THE WRAPPER IS TRANSPARENT AND CLICK-THROUGH. The paint — white, rounded,
     shadowed — is on .altha-header__inner, the floating pill; this element is
     only the fixed positioning box, and it spans the window, so without
     pointer-events:none it would swallow every click in the strip beside the
     pill. The pill and the mobile panel turn them back on. */
  background: transparent;
  pointer-events: none;

  /* The bar's own dimensions, every one a ratio of --altha-logo-mark so the
     lockup holds its shape at whatever size the window height asks for. The
     ratios ARE the values the bar shipped with, over a 64px mark: 24/64,
     60/64, 25/64, 11/64.

     The two text sizes carry a floor the mark does not. They are the one part
     of the lockup with an absolute legibility limit — ALCHEMY is set at 0.38em
     tracking in the muted grey — and 19px/10px is the smallest this wordmark
     has ever shipped at, because it is what the compact state below uses. So
     the sub pins at 10px once the mark passes under 58px and the shift at 19px
     under 48.6px (i.e. never, at a 50px floor). The stack is then 19+10+6=35px,
     still under the 50px mark, so the mark stays what sets the height. */
  --altha-bar-pad:    calc(var(--altha-logo-mark) * 0.21875);
  --altha-logo-img:   calc(var(--altha-logo-mark) * 0.9375);
  --altha-logo-shift: max(19px, calc(var(--altha-logo-mark) * 0.390625));
  --altha-logo-sub:   max(10px, calc(var(--altha-logo-mark) * 0.171875));
}

/* Compacted once scrolled, reusing the .is-scrolled class the header
   already carried — the bar is fixed, so shrinking it reflows nothing.
   Same construction as the full state, its own ratios: 12/46, 44/46, 19/46,
   10/46. --altha-header-h-compact no longer has to be kept in step by hand; it
   is calc'd from --altha-logo-mark-compact off the same two ratios.

   The compact floor is 40px against the full state's 50, not the same number:
   this state has less to give. Both text floors bind immediately here (19px is
   exactly 46 x 0.41304), so as the mark comes down it is the mark and the
   padding that move and the wordmark holds — which is why 40 is the stop. The
   text stack is 35px and the mark must stay above it to keep setting the
   height. */
.altha-header.is-scrolled {
  --altha-logo-mark:  var(--altha-logo-mark-compact);
  --altha-bar-pad:    calc(var(--altha-logo-mark) * 0.2);
  --altha-logo-img:   calc(var(--altha-logo-mark) * 0.95652);
  --altha-logo-shift: max(19px, calc(var(--altha-logo-mark) * 0.41304));
  --altha-logo-sub:   max(10px, calc(var(--altha-logo-mark) * 0.21739));
}
/* Full-bleed on --altha-gutter, the same measure as every page. It used to be
   capped at --max-width-wide with its own 1.5rem/3rem gutter, which is the
   .altha-container system — and NO template uses .altha-container any more;
   all twelve build on .ah-grid, which is full-bleed on --altha-gutter. So the
   bar disagreed with the page twice over: 48px against 34px from 1024px up,
   and then, past 1376px, the 1280px cap froze the logo while the content below
   it kept travelling outward. The footer already sits on --altha-gutter. */
/* THE PILL (2026-09-25, Chris's call). The bar is a white rounded capsule
   floating clear of the top of the window, its outer edges on the content
   area — margin, not padding, is what puts them there, so the pill lines up
   with the page below it and the logo sits inside its own padding. White with
   the one soft shadow; no divider line. Gold is not the pill's colour: the
   logo mark is Liv's gold and would vanish on a gold ground, so gold is kept
   for the active page inside it. */
.altha-header__inner {
  pointer-events: auto;
  max-width: none;
  margin: var(--altha-pill-top) var(--altha-gutter) 0;
  padding-inline: clamp(20px, 2vw, 32px);
  padding-block: var(--altha-bar-pad);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--color-white) 94%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 10px 30px color-mix(in srgb, var(--color-shadow) 12%, transparent);
  transition: padding .28s ease, margin .28s ease, background-color .28s ease, box-shadow .28s ease, border-radius .28s ease;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
}

/* Logo — circle mark + two-line wordmark lockup.
   Defaults are the ON-LIGHT colours, used by every inner page and by the
   home header once scrolled. The cream variants are applied only while the
   header floats over the hero. */
.altha-logo { flex-shrink:0; display:flex; align-items:center; color:var(--color-ink); }
.altha-logo img, .altha-logo .altha-logo__img { height:var(--altha-logo-img); width:auto; transition:height .28s ease; }
.altha-logo__lockup { display:inline-flex; align-items:center; gap:13px; }
/* Mark inherits .altha-logo's colour so it follows the header's light/dark state.

   The negative margin is an OPTICAL ALIGNMENT, not a nudge. The artwork does
   not fill its own viewBox: measured off the path data in altha_logo(), it
   spans x 6.82–55.05 of the 58-unit box, so it carries 6.82 units of empty
   space on the left (and only 2.95 on the right — it is not centred either).
   Left flush to a gutter, the box lands on the gutter and the DRAWN mark lands
   7.5px inside it at the header's resting size, which is what read as "the
   logo doesn't line up". 6.82/58 = 0.1176, so the compensation is a ratio and
   tracks every size the mark takes — 64px resting, 46px scrolled, 52px in the
   footer and below 480px. Re-derive it if the mark artwork is ever replaced. */
.altha-logo__mark {
  display:block; flex:none;
  width:var(--altha-logo-mark); height:var(--altha-logo-mark);
  margin-left: calc(var(--altha-logo-mark) * -0.1176);
  transition:width .28s ease, height .28s ease, margin-left .28s ease;
}
.altha-logo__text { display:flex; flex-direction:column; line-height:1; }
.altha-logo__shift {
  font-family: var(--font-family-display);
  font-weight: 400;
  font-size: 20px;
  /* 0.24em for MERRIWEATHER, scaled up from the 0.22em Playfair carried by the
     same rule that set it: tracking is in em but letters are not, so a wider
     face shows a visibly tighter gap at the same value. Merriweather's caps
     average .688em against Playfair's .6423em — 7.1% wider — and
     0.22 x .688/.6423 = 0.2357, rounded to 0.24. The chain before that: 0.21
     for Jost (.6095em), 0.24 for Josefin (.691em), 0.16 for Khand (.424em) —
     each step scaled from the one immediately before it, not re-derived from
     the original baseline, so treat this as a chain to continue, not a ratio
     to recompute from Khand.

     NOTE this is the WORDMARK and stays a serif again: .altha-logo__shift
     reads --font-family-display, so "ALTHA" follows the headings' typeface on
     every swap. Not a side effect to fix — it is not a heading and nobody
     asked for it to change, but it is the one part of any display-face trial
     worth looking at first, because it is the one place the face appears at a
     size and a role nothing else on the site uses it for. */
  letter-spacing: 0.24em;
  color: var(--color-ink);
  text-transform: uppercase;
  /* Compensates the trailing letter-space so both lines stay optically
     flush-left. Must match the letter-spacing above. */
  padding-left: 0.24em;
}
.altha-logo .altha-logo__shift { font-size:var(--altha-logo-shift); transition:font-size .28s ease; }
.altha-logo .altha-logo__sub   { font-size:var(--altha-logo-sub);   transition:font-size .28s ease; }
.altha-logo__sub {
  font-family: var(--font-family-body);
  font-weight: 500;
  font-size: 10px;
  letter-spacing: 0.38em;
  text-transform: uppercase;
  color: var(--color-muted);
  margin-top: 6px;
  padding-left: 0.38em;   /* same trick as above — must match the tracking */
}
/* The 480px block that used to shrink the logo to 52px and pin the bar at
   100px is gone, and deliberately not replaced: every phone lands on the 50px
   floor on its own now. The tallest handset in portrait gives about 830svh and
   an iPad about 1024, and 4.74svh of either is under 50 — so the floor, not a
   width query, is what sizes the mark there. Re-adding a width override would
   also put --altha-header-h back out of step, since it is calc'd now. */

/* Desktop nav — hidden below lg */
.altha-nav { display:none; }
/* 1024px, not 900. At 900 the nav box is 536px and the seven links need 476px
   at the OLD 15px — it already overflowed between 900 and 975px before the size
   went up. The burger covers everything below; --altha-header-h-compact already
   switches at 1023px, so the two now agree. */
@media (min-width:1024px) { .altha-nav { display:flex; align-items:center; } }
/* Each top-level link carries 14px a side of its own now (its pill), so the gap
   between links is the old 40/28/20px less 28: 12px above 1300, none below. */
.altha-nav__list { display:flex; align-items:center; gap:0.75rem; list-style:none; }
.altha-header__inner .altha-nav .altha-nav__list > li > a { padding: 0.45rem 0.875rem; border-radius: var(--radius-lg); }
.altha-nav__list li { list-style:none; }

/* Nav links — force styles on WP-generated anchors too */
.altha-nav__link,
.altha-nav__list .menu-item > a {
  font-family: var(--font-family-body);
  /* FLUID, and it has to be. Measured off the Nunito Sans binary at weight 500
     with the 0.02em below, the seven links total 31.7px of width per 1px of
     font-size. The row also carries a 191.6px logo lockup, two 2rem flex gaps
     and 56px of social icons, so the nav's own box is (viewport - 2*gutter -
     311.6). At 18px the links alone need 571px, which does not fit until about
     1070px of viewport — at 1024 the ceiling is 16.5px and at 900 it is 12.6px,
     below which no gap value can save it.
     So this ramps 16px -> 18px across 1024-1152 and holds 18px above, giving
     the largest size that fits at every width the desktop nav is shown at.
     Re-derive it if a menu item is renamed: 1.5625vw is 16px at exactly 1024. */
  font-size: clamp(1rem, 1.5625vw, 1.125rem) !important;
  /* One step up from the 400 these used to inherit from body. Nunito Sans is
     variable 200–1000, so 500 is a real instance, not a synthesised weight. */
  font-weight: var(--font-weight-medium) !important;
  letter-spacing: 0.02em !important;
  text-transform: none !important;
  color: var(--color-ink) !important;
  white-space: nowrap;
  transition: all 0.5s ease !important;
  position: relative;
  padding-bottom: 2px;
  text-decoration: none !important;
  display: inline-block;
}
.altha-nav__link:hover,
.altha-nav__list .menu-item > a:hover { color:var(--color-link-hover) !important; }

/* Seven links at 0.02em tracking need the gap tightened before the burger takes
   over at 1024px. These are load-bearing now, not cosmetic: the fluid size
   above was solved against 40px gaps over 1300, 28px to 1150 and 20px below
   that, and 20px is what makes 16px fit at 1024 with 17px to spare. Loosening
   either step puts the nav back over the row. */
@media (max-width:1300px) { .altha-nav__list { gap:0; } }

/* Active page marker — ONE treatment for the whole site.
   The text itself underlined in the logo gold, 1px thick, four pixels clear of
   the baseline: identical to the list over the hero and to every text link on
   the page. It used to be a drawn 1px bar floated at bottom:-2px in the accent
   colour, which sat tighter to the type and in a colour the palette no longer
   has, so the hero list and the bar disagreed. */
.altha-nav__link::after,
.altha-nav__list .menu-item > a::after { content: none !important; }
/* THE ACTIVE PAGE IS A FOREST PILL WITH A GOLD LABEL. It was a gold pill,
   replacing the gold underline; Chris then asked for the colours REVERSED,
   so that gold fill stays the mark of an action (the buttons) and navigation
   state is the inverse of it — ink at first, then changed to --color-forest
   (Chris: green instead of black), which is dark enough that gold still
   reads as type on it (the footer headings were the precedent there too).
   Radius 12px, matching the buttons. The top-level links carry the padding
   that gives the fill its shape (see above); the mobile panel's links have
   their own. */
.altha-nav__link[aria-current="page"],
.altha-nav__list .current-menu-item > a,
.altha-nav__list .current_page_item > a {
  color: var(--color-gold) !important;
  background-color: var(--color-forest);
  text-decoration: none !important;
}

/* ── Desktop dropdown sub-menu ───────────────────────────────────── */
.altha-nav .menu-item-has-children { position: relative; }

/* Invisible bridge fills the gap so hover stays alive moving into sub-menu */
.altha-nav .menu-item-has-children::after {
  content: '';
  position: absolute;
  left: -8px; right: -8px;
  top: 100%;
  height: 14px;
}
.altha-nav .sub-menu {
  position: absolute;
  top: calc(100% + 14px);
  left: 50%;
  transform: translateX(-50%) translateY(-6px);
  min-width: 200px;
  list-style: none;
  padding: 0.5rem;
  margin: 0;
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--color-shadow) 10%, transparent);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.2s ease, visibility 0.2s ease, transform 0.2s ease;
  z-index: 200;
}
.altha-nav .menu-item-has-children:hover > .sub-menu,
.altha-nav .menu-item-has-children.is-open > .sub-menu {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}
.altha-nav .sub-menu .menu-item > a {
  display: block !important;
  padding: 0.6rem 0.875rem !important;
  border-radius: var(--radius-lg) !important;
  font-size: var(--text-xs) !important;
  letter-spacing: 0.15em !important;
  text-transform: uppercase !important;
  color: var(--color-body) !important;
  white-space: nowrap;
  text-decoration: none !important;
  transition: background 0.15s ease, color 0.15s ease !important;
}
.altha-nav .sub-menu .menu-item > a::after { display: none !important; }
.altha-nav .sub-menu .menu-item > a:hover {
  background: var(--color-background) !important;
  color: var(--color-ink) !important;
}
.altha-nav .sub-menu .current-menu-item > a {
  background: var(--color-surface) !important;
  color: var(--color-ink) !important;
  font-weight: 500 !important;
}

/* Chevron toggle button */
.altha-nav-toggle {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  color: inherit;
  display: inline-flex;
  align-items: center;
  line-height: 1;
  transition: transform 0.2s ease, color 0.2s ease, background 0.2s ease;
  vertical-align: middle;
}
/* Real padding, not just a margin, so this reads as its own hit area next to
   the text link rather than a decorative mark — and the hover/open tint (the
   same soft ink fill `.altha-btn--outline` uses, not a hard border — this
   site has no hairlines) makes the second click target obvious before you've
   even found it. */
.altha-nav .altha-nav-toggle { margin-left: 2px; padding: 6px; border-radius: var(--radius-lg); color: var(--color-body); }
.altha-nav .altha-nav-toggle svg { width: 14px; height: 14px; }
.altha-nav .menu-item-has-children:hover > .altha-nav-toggle,
.altha-nav .menu-item-has-children.is-open > .altha-nav-toggle {
  transform: rotate(180deg);
  color: var(--color-accent);
  background: color-mix(in srgb, var(--color-ink) 8%, transparent);
}
.altha-nav .menu-item-has-children.is-open > .altha-nav-toggle { background: color-mix(in srgb, var(--color-ink) 14%, transparent); }

/* No social icons in the header (2026-09-25): Chris does not want visitors
   sent off to social pages at that point in the journey. They are in the
   footer. */

/* Hamburger */
.altha-header__toggle {
  display:flex; align-items:center; justify-content:center;
  width:48px; height:48px;
  color:var(--color-ink);
  transition:var(--transition-base);
  background:none; border:none; cursor:pointer; padding:0;
}
.altha-header__toggle:hover { color:var(--color-accent); }
.altha-header__toggle svg  { width:2rem; height:2rem; }
@media (min-width:1024px) { .altha-header__toggle { display:none; } }
/* Compacted height is floored by the 48px toggle wherever it is shown. Giving
   --altha-toggle-size its real value here is all that takes now: the max() in
   --altha-header-h-compact picks the toggle over the mark from 48px down, and
   reproduces the 72px this used to state by hand (12 x 2 + 48). */
@media (max-width:1023px) { :root { --altha-toggle-size: 48px; } }

/* ── Mobile menu ─────────────────────────────────────────────────── */
/*
  React mobile menu: full-width below header, NO left/right margin,
  NO rounded corners, white/95 backdrop.
  bg-white/95 backdrop-blur-md rounded-2xl p-6 mb-4
  BUT looking at the React code again: the mobile nav is inside the
  header container with rounded-2xl. The difference from before is
  React uses rounded-2xl on the nav element, not full-bleed.
  Image 1 shows the REACT version: full width within the header inner,
  with rounded corners. The issue was the previous CSS had left:1.5rem
  right:1.5rem which adds to the container's own padding, making it too
  narrow. Fix: position relative to .altha-header not .altha-header__inner.
*/
.altha-mobile-menu {
  display: none;
  position: absolute;
  pointer-events: auto;
  top: calc(100% + 8px);   /* a card hanging under the pill, not a full-width sheet */
  left: var(--altha-gutter);
  right: var(--altha-gutter);
  border-radius: var(--radius-xl);
  /* The warm off-white ground, not paper white: the panel covers the top of
     the page, so a pure-white sheet over #F7F5F0 reads as a colour change. */
  background-color: var(--color-background);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--color-shadow) 12%, transparent);
  /* (Was the same gutter as the bar above it; the panel is inset by margin now.) This was 3rem while the bar was 1.5rem,
     so the panel already sat 24px inside the logo before the bar moved to
     --altha-gutter. The 1024px override that used to follow was dead in any
     case — the burger is gone by 900px. NOTE the links keep their own 1rem of
     padding for the hover pill, so link TEXT still starts 1rem inside the
     gutter; the pill is what aligns. */
  padding: 1rem;
}
.altha-mobile-menu.is-open { display:block; }

.altha-mobile-menu .altha-nav__list {
  flex-direction: column;
  align-items: stretch;
  gap: 0.25rem;
}

/* Mobile nav links */
.altha-mobile-menu .altha-nav__link,
.altha-mobile-menu .menu-item > a {
  display: block !important;
  padding: 0.75rem 1rem !important;
  border-radius: var(--radius-lg) !important;
  color: var(--color-body) !important;
}
.altha-mobile-menu .altha-nav__link::after,
.altha-mobile-menu .menu-item > a::after { display:none; }
.altha-mobile-menu .altha-nav__link:hover,
.altha-mobile-menu .menu-item > a:hover { background-color:color-mix(in srgb, var(--color-surface) 50%, var(--color-background)) !important; color:var(--color-body) !important; }
.altha-mobile-menu .current-menu-item > a,
/* 600, not 500: the nav base rule is 500 now, so 500 here would no longer be a
   step at all and the current page would lose half its marker. */
.altha-mobile-menu .current_page_item > a { background-color:var(--color-forest) !important; color:var(--color-gold) /*!important; border-left:3px solid var(--color-accent)*/ !important; padding-left:calc(1rem - 3px) !important; font-weight:600 !important; }

/* ── Mobile sub-menu ─────────────────────────────────────────────── */
/* Grid layout: link col 1, chevron col 2, sub-menu spans full width on row 2 */
.altha-mobile-menu .menu-item-has-children {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  align-items: center;
}
.altha-mobile-menu .menu-item-has-children > a {
  grid-column: 1;
  grid-row: 1;
}
.altha-mobile-menu .menu-item-has-children > .altha-nav-toggle {
  grid-column: 2;
  grid-row: 1;
  align-self: center;
  padding: 0.4rem 0.6rem;
  margin-right: 0.25rem;
  color: var(--color-ink);
  background: var(--color-background);
  border: 1px solid var(--color-border) !important;
  border-radius: var(--radius-lg);
  opacity: 1;
}
.altha-mobile-menu .menu-item-has-children > .altha-nav-toggle svg {
  width: 20px;
  height: 20px;
}
.altha-mobile-menu .menu-item-has-children.is-open > .altha-nav-toggle {
  transform: rotate(180deg);
  background: var(--color-surface);
  color: var(--color-ink);
  border-color: var(--color-ink) !important;
}
/* The link's forest pill spans the WHOLE row (both grid columns) and the
   chevron sits on top of it in the right-hand corner, same grid cell — one
   green band with a gold badge inside it, not two bands side by side.
   The gold border/icon were invisible for a real reason, not a contrast
   one: .altha-nav__link (theme.css ~937) sets position:relative on every
   nav link, site-wide, for its underline effect. A positioned element
   always paints above a static sibling regardless of DOM order, so the
   link's own box was covering the button completely no matter what the
   button was styled with. position:relative + z-index:1 here puts the
   button in the same paint tier, where DOM order (it comes after the link)
   finally applies and it paints on top as intended. */
.altha-mobile-menu .menu-item-has-children.current-menu-item > a,
.altha-mobile-menu .menu-item-has-children.current_page_item > a {
  grid-column: 1 / -1;
}
.altha-mobile-menu .menu-item-has-children.current-menu-item > .altha-nav-toggle,
.altha-mobile-menu .menu-item-has-children.current_page_item > .altha-nav-toggle,
.altha-mobile-menu .menu-item-has-children.current-menu-item.is-open > .altha-nav-toggle,
.altha-mobile-menu .menu-item-has-children.current_page_item.is-open > .altha-nav-toggle {
  grid-column: 2;
  position: relative;
  z-index: 1;
  background: transparent;
  color: var(--color-gold);
  border: 1.5px solid var(--color-gold) !important;
}
/* 0.5rem of top padding so the sub-item doesn't sit flush against the pill
   above it, and a deeper indent (1.5rem, up from 0.75rem) so it reads as
   nested under Events rather than another line at the same level. */
.altha-mobile-menu .sub-menu {
  display: none;
  list-style: none;
  padding: 0.5rem 0 0.25rem 1.5rem;
  margin: 0;
  grid-column: 1 / -1;
  grid-row: 2;
}
.altha-mobile-menu .menu-item-has-children.is-open > .sub-menu { display: block; }
.altha-mobile-menu .sub-menu .menu-item > a {
  display: block !important;
  padding: 0.6rem 1rem !important;
  border-radius: var(--radius-lg) !important;
  color: var(--color-body) !important;
  font-size: var(--text-xs) !important;
  letter-spacing: 0.15em !important;
  text-transform: uppercase !important;
  opacity: 0.8;
}
.altha-mobile-menu .sub-menu .menu-item > a:hover {
  background: color-mix(in srgb, var(--color-surface) 50%, var(--color-background)) !important;
  opacity: 1;
}
.altha-mobile-menu .sub-menu .current-menu-item > a {
  background: var(--color-surface) !important;
  color: var(--color-ink) !important;
  font-weight: 500 !important;
  opacity: 1;
}

/* ── Hero — canopy (photographic) ─────────────────────────────────
   Layer order, bottom to top: ground radial → photo (+vignette) →
   bokeh canvas → scrim → content.

   This stack used to be nine layers deep. The sky bleed, sun flare and
   sun core were painted UNDER an opaque photo and had never been visible;
   the white "lift" haze and the grain texture were washing the colour out
   of the photograph. All five are gone, and the vignette and scrim were
   pulled back to the minimum that keeps type legible — the point is to
   see the photo, not the treatment.                             ── */
.altha-hero {
  position: relative;
  width: 100%;
  /* svh, NOT dvh. dvh tracks the mobile URL bar live, so every scroll that
     retracted the bar grew the hero, and the photo below (inset:0, cover)
     re-cropped against the new box — the image appeared to resize mid-scroll.
     svh is the small viewport: the height with the browser chrome fully
     SHOWN, and it never changes while scrolling. The trade is that the hero
     stops a bar's-worth short of the glass once the chrome retracts; a stable
     frame is worth more than the last 60px. vh first for anything predating
     the unit. */
  /* Sized to its content, not to the glass: a 100svh opener pushed the first
     section entirely off the first screen, and the headline sits low anyway. */
  height: clamp(560px, 64vw, 820px);
  max-height: 100svh;
  overflow: hidden;
  isolation: isolate;
  background: var(--hero-ground);

  /* ── Hero photo framing — the only three values worth touching ──
     --hero-zoom     how much wider than the hero box the photo is drawn
     --hero-focal-x  horizontal crop point
     --hero-focal-y  vertical crop point

     Read this before changing them. The photo is 1650x1100 (3:2). On any
     viewport WIDER than 3:2 — i.e. most desktops — object-fit:cover scales
     the image to fill the width and crops only vertically, so --hero-focal-x
     has no effect there. It bites on narrow/portrait viewports, where the
     image becomes height-constrained instead.

     --hero-zoom is 100% at every width: the photo is drawn exactly as wide as
     the hero and nothing is enlarged or panned. Raising it draws the photo
     wider than the box; since the image is anchored left:0 that overflow spills
     off the right, which pushes the subject rightward — that was the old 140%
     desktop value, used to clear the headline. It costs resolution (the source
     is only 1650px wide) and on a portrait phone it drags the visible window
     off the subject entirely, so it is off on both. Panning at all requires
     overflow; at 100% there is none, by design. */
  --hero-zoom: 100%;
  --hero-focal-x: 50%;
  --hero-focal-y: 30%;
}
/* The 62vw box exists to keep the headline clear of the subject on a wide
   crop. On a phone there is no room to give away, so it takes the full
   measure. */
@media (max-width:739px) { .altha-hero__inner { max-width:none; } }
/* Desktop runs a third smaller than the sizing above — both terms are two
   thirds of theirs (7.4vw, 112px). Scoped to the desktop breakpoint so tablet
   and phone keep the original curve; the 44px floor never bites up here,
   since 4.93vw is already 50px at 1024. A smaller headline only improves the
   separation from Liv that the note above is about. */
@media (min-width:1024px) {
  .altha-hero__heading { font-size: clamp(34px,3.4vw,52px); }
}

/* Support copy sits in its own row below the CTA rather than beside it —
   sharing the actions row leaves each column around 250px once the button
   has taken its share of the 760px inner box. The gap tracks the site
   container gutter (1.5rem, 3rem from 1024px — see .altha-container) so the
   hero keeps the same rhythm as the sections below it. */
.altha-hero__support-grid {
  margin-top: clamp(18px,2vw,26px);
  display:grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
}
/* The headline box is a share of the viewport in the design; below the
   desktop breakpoint that leaves it far too narrow for a 46px+ H1. */
@media (max-width:1023px) {
  .altha-hero__inner   { max-width:none; }
  .altha-hero__support { max-width:none; }
  .altha-hero__actions { gap:20px; }

  /* The hero grows with its content here instead of being pinned to the
     viewport. The content layer returns to flow so it can push the section
     taller; the photo, scrim and bokeh stay absolute and stretch to whatever
     height that produces. Without this a second column of subtext is simply
     clipped by the hero's overflow:hidden. max() keeps the base rule's 600px
     floor, which a short landscape phone would otherwise drop below. svh for
     the same reason as the base rule — this is the breakpoint the jump was
     actually reported at. */
  .altha-hero          { height:auto; min-height: max(100vh, 600px); min-height: max(100svh, 600px); }
  .altha-hero__content { position:relative; inset:auto; min-height: max(100vh, 600px); min-height: max(100svh, 600px); }
}
/* The 767px --hero-zoom override that used to sit here is gone: the base rule is
   now 100% at every width, so the phone override said nothing the base rule did
   not already say. 767px still appears in page-home.php, on the <source media>
   for the separate mobile hero upload — that is a different mechanism and stays. */
@media (max-width:600px) {
  .altha-hero__cta     { padding:14px 28px; }
  .altha-hero__support { font-size:13px; }
  /* Two columns at 375px would be ~160px each — roughly four words a line. */
  .altha-hero__support-grid { grid-template-columns:1fr; }
  .altha-hero__whisper { gap:11px; margin-bottom:16px; }
  .altha-hero__scroll  { --altha-scroll-drop: 16px; font-size:14px; }
  .altha-hero__whisper-rule { width:26px; }
}

@media (prefers-reduced-motion: reduce) {
  /* The header still compacts, it just does it instantly. */
  .altha-header__inner,
  .altha-logo img, .altha-logo .altha-logo__img, .altha-logo__mark,
  .altha-logo .altha-logo__shift, .altha-logo .altha-logo__sub { transition:none; }
}

/* ── Homepage header — the standard bar, transparent over the hero ──
   It is the SAME header as every other page: same geometry, same horizontal
   nav, same container. The only thing that differs is the paint — no ground,
   no shadow, and white ink instead of dark, because it is sitting on a
   photograph. Once .is-scrolled arrives it becomes the ordinary white bar.

   It used to be a different object: a right-aligned VERTICAL list with the
   wordmark opposite it and a booking link under a hairline, carried over from
   the mockup and pinned to the page margin rather than the container. Liv
   asked for the standard header back on 2026-09-18, transparent over the
   full-bleed image as before. Every layout override went with that; only the
   colour rules below remain.

   Keep these scoped to :not(.is-scrolled). Unscoped, the transparency also
   matched the scrolled bar and was only undone by .altha-header.is-scrolled
   further down at EQUAL specificity — a source-order tie of exactly the kind
   that has bitten this stylesheet before. */
/* Gold in BOTH scroll states. The bar is a photograph before the scroll and a
   dark brown after it, so the logo never meets a light ground on the homepage
   and never needs to flip to ink. */
/* Over the photograph only — see the :not(.is-scrolled) rules below. These
   used to be unscoped, which left the wordmark white on the solid bar. */
/* ── Homepage header — a list over the photograph ─────────────────
   The homepage nav is NOT a bar: it sits on the hero as a right-aligned
   list, the way the reference sets an index against a full-bleed image,
   with the wordmark opposite it. Everything here is scoped to .home and to
   the un-scrolled state; once .is-scrolled arrives (theme.js adds it past
   the hero) the header becomes the ordinary white bar every inner page
   already uses, because a nav that scrolls away with the photo leaves
   nothing to navigate with.

   Scoped to .altha-nav (the desktop bar) on purpose: unscoped, these also
   matched the mobile menu's list, painting its links white on the white
   dropdown panel — invisible whenever the menu was opened from the homepage. */
.has-photo-hero .altha-header:not(.is-scrolled) {
  position: absolute;
  background: transparent;
  background-color: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
  border-bottom: 0;
}
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .altha-nav__link,
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .altha-nav__list > .menu-item > a,
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .altha-nav-toggle {
  color: var(--hero-ink) !important;
}
/* > between .altha-nav__list and .menu-item on purpose, here and below: an
   unscoped descendant selector also reaches INTO the sub-menu, which Events
   never had before — repainting "Groups and Workplace Wellbeing" hero-white
   on the sub-menu panel's own white background made it invisible the moment
   Events got a child page. The sub-menu keeps its own ink colour regardless
   of the header's scroll state; only the top-level link follows the hero. */
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .altha-nav__link:hover,
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .altha-nav__list > .menu-item > a:hover {
  color: var(--color-white) !important;
  text-decoration: underline !important;
  text-underline-offset: 4px;
}
/* The toggle has no text to underline, so it gets the hover/open tint instead
   — without this it stays --hero-ink even on hover, same bug as the base
   colour rule above: everything beside it repaints, this didn't. */
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .menu-item-has-children:hover > .altha-nav-toggle,
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .menu-item-has-children.is-open > .altha-nav-toggle {
  color: var(--color-white) !important;
  background: color-mix(in srgb, var(--color-white) 16%, transparent);
}
/* The underline marks the current page; gold, so it reads as the logo's
   colour rather than as an accent the rest of the site does not have. */
.has-photo-hero .altha-header:not(.is-scrolled) .altha-header__toggle { color:var(--hero-ink); }
/* Scoped to .altha-nav like every rule above it: unscoped, this also matched
   the mobile panel's active link, painting it white on the light ground
   whenever the menu was opened from a photo-hero page before scrolling. */
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .altha-nav__list > .current-menu-item > a,
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .altha-nav__list > .current_page_item > a,
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .current-menu-item > .altha-nav-toggle,
.has-photo-hero .altha-header:not(.is-scrolled) .altha-nav .current_page_item > .altha-nav-toggle { color:var(--color-gold) !important; }
/* THE HOMEPAGE HAS NO PILL AT THE TOP (2026-09-25): it would sit on the hero
   photograph, so the bar stays the transparent strip it always was and gives
   the pill's margin and rounding back — the logo returns to the content edge
   and the vertical space the margin took moves into padding so nothing shifts.
   Scrolling adds .is-scrolled, this rule stops matching, and the transitions on
   .altha-header__inner draw the pill in. An open mobile menu also counts as
   "not at the top": the pill is solid so the panel has something to hang
   from. The active link keeps its gold fill; white on gold would fail. */
.has-photo-hero .altha-header:not(.is-scrolled):not(:has(.altha-mobile-menu.is-open)) .altha-header__inner {
  margin-inline: 0;
  margin-top: 0;
  padding-inline: var(--altha-gutter);
  padding-block: calc(var(--altha-bar-pad) + var(--altha-pill-top));
  border-radius: 0;
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
}
.has-photo-hero .altha-header:not(.is-scrolled) .altha-logo,
.has-photo-hero .altha-header:not(.is-scrolled) .altha-logo__shift { color:var(--hero-logo-1); }
.has-photo-hero .altha-header:not(.is-scrolled) .altha-logo__sub { color:var(--hero-logo-2); }
/* The mark keeps the real logo gold in both states — it is the logo, not a
   UI colour, which is the one place gold is allowed on a light ground. */
.altha-logo .altha-logo__mark { color:var(--color-gold); }

/* NOT STICKY ON DESKTOP (2026-09-25, Chris: "ditch the sticky header on
   desktop"). From 1024px the header is out of flow and scrolls away with the
   page: the pill at the top of an inner page, the transparent strip over the
   homepage hero. theme.js also stops adding .is-scrolled there, otherwise the
   bar would compact and turn into the pill while still at the top of the page.
   Phones and tablets below 1024 keep the fixed floating pill. */
@media (min-width:1024px) {
  .altha-header { position: absolute; }
  /* AND THE SAME STRIP ON EVERY PAGE (2026-09-25, Chris): on desktop the
     internal pages use the homepage header — no pill, no ground, no shadow, the
     logo back on the content edge — with the ink type they already have. Same
     declarations as the homepage rule further down, without its
     .has-photo-hero scope; the type stays dark because nothing here repaints
     it. The margin-to-padding swap keeps the strip exactly as tall as the pill
     was, so body's top padding still clears it. */
  .altha-header .altha-header__inner {
    margin-inline: 0;
    margin-top: 0;
    padding-inline: var(--altha-gutter);
    padding-block: calc(var(--altha-bar-pad) + var(--altha-pill-top));
    border-radius: 0;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: none;
  }
}

/* MOBILE AND TABLET: A STICKY FULL-WIDTH BAR, NOT A FLOATING PILL (2026-09-26,
   Chris). Below 1024px the header is the fixed bar it was before the pill
   experiment: edge to edge, square corners, the page ground behind it, no gap
   above it. --altha-pill-top goes to 0 so everything that added it (the body's
   top padding, the anchor scroll margin) clears the bar and no more. The menu
   panel follows: full width, square, flush under the bar. The homepage's
   transparent-at-the-top state is untouched — its rule is more specific — so it
   still goes bare over the hero and takes this bar on scroll. No divider line;
   a faint shadow separates the bar from content passing beneath it. */
@media (max-width:1023px) {
  :root { --altha-pill-top: 0px; }
  .altha-header .altha-header__inner {
    margin: 0;
    padding-inline: var(--altha-gutter);
    border-radius: 0;
    background: var(--color-background);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: 0 2px 14px color-mix(in srgb, var(--color-shadow) 8%, transparent);
  }
  .altha-mobile-menu {
    top: 100%;
    left: 0;
    right: 0;
    border-radius: 0;
    padding: 1rem var(--altha-gutter);
  }
}

/* Past the hero: the floating pill, exactly as on every inner page — that is
   .altha-header__inner's own rule and needs nothing here. The bottom divider
   that used to be on this rule is gone with the full-width bar. */

/* Mobile menu open — the pill must be solid whatever the scroll state (see the
   :not(:has(.is-open)) in the hero rule above). */
.has-photo-hero .altha-header:has(.altha-mobile-menu.is-open) .altha-logo,
.has-photo-hero .altha-header:has(.altha-mobile-menu.is-open) .altha-logo__shift { color:var(--color-ink); }
.has-photo-hero .altha-header:has(.altha-mobile-menu.is-open) .altha-logo__sub { color:var(--color-muted); }
.has-photo-hero .altha-header:has(.altha-mobile-menu.is-open) .altha-header__toggle { color:var(--color-ink) !important; }
/* Every link in the open panel is dark, current page included, whatever the
   header is doing above it. */
.altha-header .altha-mobile-menu .altha-nav__list .menu-item > a { color:var(--color-ink) !important; }
/* ...except the current page, which is the forest pill with a gold label. */
.altha-header .altha-mobile-menu .altha-nav__list .current-menu-item > a,
.altha-header .altha-mobile-menu .altha-nav__list .current_page_item > a,
.altha-header .altha-mobile-menu .altha-nav__list a[aria-current="page"] { color:var(--color-gold) !important; }
/* ── Rich text from the admin's visual editor ──────────────────── */
/* ── The three journey sections get three layouts, not one ─────── They are
   the same template loop, so the variant class the loop already emits
   (--alchemist / --safe / --expansion) is what separates them. One repeated
   two-column block for all three is exactly what made the page read as a
   template: same shape, same heading size, three times. All three run
   full-bleed on the page margin rather than inside the centred container, so
   they share the grid the header and hero sit on. */
.altha-story .altha-container {
  max-width: none;
  padding-inline: var(--altha-gutter);
}
/* 1. The alchemist within — a narrow measure of copy with the photograph
   running off the right-hand edge of the page. */
@media (min-width:900px) {
  .altha-story--alchemist .altha-container { padding-right:0; }
  .altha-story--alchemist .altha-feature__content { grid-column:1 / span 10; }
  .altha-story--alchemist .altha-feature__image   { grid-column:13 / -1; order:2; }
  .altha-story--alchemist .altha-feature__image--portrait { aspect-ratio:4/3; }
}
/* 2. Everything begins with feeling safe — a wide band of photograph, then a
   long, even run of copy set in two newspaper columns rather than one tall
   ribbon. */
@media (min-width:900px) {
  .altha-story--safe .altha-feature__content,
  .altha-story--safe .altha-story__single > * { grid-column:1 / -1; }
  .altha-story--safe .altha-story__body {
    columns: 2;
    column-gap: clamp(30px, 4vw, 64px);
  }
  .altha-story--safe .altha-story__body > * { break-inside:avoid; margin-top:0; margin-bottom:1em; }
}

/* 3. Expansion — a band of photograph, then the page's one oversized line.
   The heading is fitted to the full page width by theme.js; the vw size
   below is what it falls back to if the script never runs. */
@media (min-width:900px) {
  .altha-story--expansion .altha-feature__grid { position:relative; }
  .altha-story--expansion .altha-feature__image { margin-bottom:clamp(20px,2vw,32px); }
  /* Legibility under the line, and only there: the band's top stays clear. */
  .altha-story--expansion .altha-feature__image::after {
    content:''; position:absolute; inset:0;
    background:linear-gradient(0deg, rgba(10,14,10,.55) 0%, rgba(10,14,10,.28) 22%, rgba(10,14,10,.06) 44%, rgba(10,14,10,0) 62%);
  }
  /* Anchored to the foot of the band: the band's height is its aspect ratio
     against the full viewport width, capped by the same max-height. */
  .altha-story--expansion .altha-feature__heading {
    position:absolute; left:0; width:100%; z-index:2;
    top:min(calc(100vw / (21 / 9)), 52vh, 460px);
    transform:translateY(-100%);
    color:var(--color-white);
    margin-bottom:0;
  }
}
@media (min-width:900px) {
  .altha-story--expansion .altha-label { grid-column:1 / -1; }
}
.altha-experience .altha-container { max-width:none; padding-inline:var(--altha-gutter); }

.altha-carousel { position:relative; }
/* A plain swipeable row until theme.js adds .is-enhanced — then the scrollbar
   goes, because the arrows and dots take over. */
.altha-carousel__track {
  display:flex; gap:20px;
  overflow-x:auto;
  scroll-snap-type:x mandatory;
  scroll-behavior:smooth;
  overscroll-behavior-x:contain;
}
.altha-carousel.is-enhanced .altha-carousel__track { scrollbar-width:none; }
.altha-carousel.is-enhanced .altha-carousel__track::-webkit-scrollbar { display:none; }
/* One card and a peek on a phone,
two up on tablet,
on the same
   two breakpoints.

   THERE ARE ONLY EVER THREE SLIDES: meta-boxes.php loops 1..3 and page-home.php
   reads the same fixed three. A peek carousel is a pattern for lists too long to
   show at once,
so on desktop it was solving a problem this section does not
   have — and it cost real legibility. At 2.35 cards the third was guillotined by
   a hard vertical line on the container's right gutter,
the same gutter every
   heading on the page aligns to,
which reads as a rendering fault rather than an
   invitation to scroll. It also left the track with only two resting states for
   three dots. Do not reinstate a desktop peek.

   The peek survives below 768px,
where it belongs: on a touch device a clipped
   card is the swipe affordance.

   Nothing here hides the arrows and dots — theme.js does,
on its own,
via
   `controls.hidden = full.every( Boolean )`. The IntersectionObserver behind it
   re-fires on resize,
so the three-up row needs no extra rules for either.

   A flat blush ground,
no photograph. The cards used to be a photo under an
   80% ink overlay; the overlay was doing all the legibility work and the
   photo showed through as a bruise,
so both are gone and the type sits on
   the palette's peach. Everything below is therefore a LIGHT-ground colour —
   do not reintroduce --color-on-dark here. */
.altha-carousel__slide {
  position:relative;
  flex-direction:column;
  flex:0 0 78%;
  scroll-snap-align:start;
  display:flex;
  background-color:transparent;
  border-top:1px solid var(--color-ink);
}
@media (min-width:768px) {
  /* Two cards and the one gap between them fill the track exactly, so the third
     sits wholly offscreen rather than as a sliver. That also lands slide 2's
     snap point precisely at the end of the scroll range — both work out to
     0.5W + 12 — so one press of the arrow reaches it and nothing clamps. */
  .altha-carousel__slide { flex-basis:calc((100% - 1.5rem) / 2); }
}
@media (min-width:900px) {
  /* `flex:1 1 0` rather than a calc() basis: the three then share the row
     exactly, where a calc() can round a fraction of a pixel long and leave the
     track scrollable by 1px — enough to keep the arrows on screen forever.
     min-width:0 because flex items default to min-width:auto, and one long
     unbroken word would otherwise push a card past its third.
     This must stay AFTER the 768px block: that rule sets flex-basis alone and
     leaves grow/shrink at 0, so only the shorthand resets them. */
  .altha-carousel__slide { flex:1 1 0; min-width:0; }
}
/* A card with a picture does not need the hairline as well. */
.altha-carousel__slide:has(.altha-carousel__image) { border-top:0; }
/* ── Feature sections — py-32 ──────────────────────────────────── */
.altha-feature { padding-block:clamp(88px,9vw,140px) 0; position:relative; }
/* On the homepage this block runs to the page edge: the photograph bleeds
   off the left and the copy sits in the right-hand columns. Scoped to .home
   so the inner pages keep the ordinary contained two-up. */
@media (min-width:900px) {
  .home .altha-feature > .altha-container { max-width:none; padding-left:0; padding-right:var(--altha-gutter); }
  .home .altha-feature > .altha-container > .altha-feature__grid { grid-template-columns:13fr 11fr; gap:clamp(30px,4vw,64px); align-items:center; }
  .home .altha-feature > .altha-container > .altha-feature__grid .altha-feature__image--portrait { aspect-ratio:16/10; }
}
/* The optical top-alignment trim that used to sit here is gone. It pulled
   .altha-feature__heading and .altha-label up by the distance between the top
   of the line box and the cap, so a heading beside a photograph lined up with
   the picture's hard top edge. Two reasons it went: .altha-feature__content is
   no longer rendered by any template — the .ah-* rebuild replaced it — and
   every number in it was derived from Khand's 1.529em content box, which
   Josefin Sans (exactly 1.000em) does not share. Restoring it means
   re-deriving from the binaries with fontTools, not scaling the old values;
   the "(line-height - 1) / 2" shortcut is wrong because it assumes a 1em box.
   text-box-trim is the modern one-liner, once it is safe to rely on. */
/* --color-accent-dark, not --color-accent. The plain accent does technically
   clear AA on this gold (4.50:1) but only just, and at the eyebrow's 13px
   that is no margin at all; the darker sage gives 6.35:1. */
.altha-testimonial .altha-label { color: var(--color-accent-dark); }

/* ── Offers ─────────────────────────────────────────────────────── */
.altha-offers { padding-block:var(--space-section-lg); background-color:var(--color-background); }
/* ── CTA — not sure where to begin? ───────────────────────────── */
/* Every call to action on the homepage is a text link with a rule under it,
   matching the hero. Done here rather than by dropping .altha-btn--primary
   in the template, so the class keeps meaning "this is the primary action"
   and the inner pages keep their filled buttons. */
.altha-cta .altha-btn,
.home .altha-feature .altha-btn {
  background:transparent !important; color:var(--color-ink) !important;
  padding:0 0 3px; border-bottom:1px solid var(--color-ink);
  font-size:var(--text-lg); font-weight:600; letter-spacing:0.01em; text-transform:none;
  white-space:normal; line-height:1.3;
}
.altha-cta .altha-btn:hover,
.home .altha-feature .altha-btn:hover { color:var(--color-muted) !important; border-color:var(--color-muted); }
.altha-cta .altha-btn svg,
.home .altha-feature .altha-btn svg { display:none; }

/* ── Breath-pulse rhythm — the testimonial glow, on the hero animation's 7.5s cycle ── */
@keyframes altha-breathe {
  0%, 100% { transform:translate(-50%,-50%) scale(1);    opacity:1; }
  50%      { transform:translate(-50%,-50%) scale(1.08); opacity:0.85; }
}

/* ── Leaf transition at the hero seam ───────────────────────────────
   A photographed sprig straddling the boundary, so the section edge cuts
   through one object rather than separating two sets of leaves.

   It has to live here rather than in the hero: .altha-hero is
   overflow:hidden with isolation:isolate, so anything inside it is clipped
   flat at the seam. This sits in the band below and reaches upward.

   overflow-x:clip with overflow-y:visible is deliberate — the sprigs are
   rotated and would otherwise widen the page (that combination previously
   produced ~11px of horizontal scroll). `hidden` is not a substitute,
   because hidden on one axis forces the other to auto. */
.altha-leaf-divider {
  position: relative;
  width: 100%;
  height: clamp(56px, 7vw, 104px);
  margin-bottom: -1px;
  background: var(--leaf-bg, var(--color-white));
  line-height: 0;
  overflow-x: clip;
  overflow-y: visible;
  z-index: 3;
}
.altha-leaf-divider__sprig {
  position: absolute;
  display: block;
  width: var(--w);
  pointer-events: none;
  will-change: transform;
  /* Same sway as the hero canopy, so the whole seam breathes together */
  animation: althaLeafSway var(--leaf-dur, 14s) ease-in-out var(--leaf-delay, 0s) infinite;
}
.altha-leaf-divider__sprig img { display: block; width: 100%; height: auto; }
/* Mirrored variant. Transitions alternate down the page so the sprigs
   stagger rather than stacking in a single column on the right. The scaleX
   goes on the <img>, never the wrapper — the wrapper's transform belongs to
   the sway animation and would overwrite it. */
.altha-leaf-divider--left .altha-leaf-divider__sprig--near {
  right: auto;
  left: clamp(6px, 3vw, 72px);
  --r: 5deg;
}
.altha-leaf-divider--left .altha-leaf-divider__sprig--far {
  right: auto;
  left: clamp(140px, 21vw, 380px);
  --r: -6deg;
}
.altha-leaf-divider--left .altha-leaf-divider__sprig--near img { transform: scaleX(-1); }
.altha-leaf-divider--left .altha-leaf-divider__sprig--far  img { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .altha-leaf-divider__sprig { animation-duration: calc(var(--leaf-dur) * 2); }
}
/* ── Footer ──────────────────────────────────────────────────── */
/* A dark band closing the page, so every colour below is an ON-DARK colour —
   --color-body and --color-ink are the same near-black and would be invisible
   here.

   ONE set of content, laid out two ways. Brand, Quick Links, Connect and
   Accredited are four siblings of .altha-footer__grid: four COLUMNS under
   their gold labels from 900px up, and a stack of BANDS below it. The legal
   line is the same at both. Everything aligns to --altha-gutter at every
   width, and nothing is hidden at any width — the labels, the tagline and the
   seven links are all present in both layouts.

   Hairlines are --altha-footer-rule on a --altha-footer-band rhythm: one above
   the legal line always, plus one above Accredited while it is a band. The
   footer used to carry a single hairline in total, with 1.5rem between the nav
   row and the badges and 5.5rem before the smallest line on the page — the
   rhythm was inverted and the alignment flipped from left to centre halfway
   down. */
.altha-footer {
  /* No longer drawn. Kept as a declaration so the two places that consumed it
     read as deliberately ruleless rather than as having lost a value: the
     hairlines came off the whole site on 2026-09-24 and the footer's dividers
     went with them. Set it back to a 1px border to restore both at once. */
  --altha-footer-rule: 0;
  --altha-footer-band: clamp(2rem, 4vw, 2.75rem);
  /* The circular mark in the footer lockup. It is declared HERE, not on
     .altha-footer__logo, because the accreditation badges size themselves off
     it — nothing in that row may reach Liv's own mark, and making the ceiling
     a real relationship is what stops the two drifting apart again. */
  --altha-footer-mark: 52px;
  margin-top: clamp(96px,10vw,160px);
  /* LIGHT FROM 2026-09-25 (Chris: "I don't care for the large band of green"):
     the footer is the client-quote strip's own ground — the tint with the same
     texture screened over it (layout.css) — and everything in it reads as on any
     other light surface: ink type, muted secondary, no gold as text. */
  background-color: var(--color-surface);
  color: var(--color-muted);
  padding-block: clamp(2.5rem,5vw,3.5rem) clamp(1.5rem,3vw,2rem);
  font-size: var(--text-sm);
}
.altha-footer .altha-container { max-width:none; padding-inline:var(--altha-gutter); }

/* The three band edges. One rhythm token either side of each rule, which puts
   the two hairlines ~200px apart — far enough that they read as band edges
   rather than as the boxed-in strip this comment used to warn about. */
.altha-footer__grid   { padding-bottom: var(--altha-footer-band); }
.altha-footer__bottom { padding-top:    var(--altha-footer-band); border-top: var(--altha-footer-rule); }

/* One label treatment for all three band headings — Quick Links, Connect and
   Accredited. This is the sanctioned use of gold as type, and it takes
   --color-gold-light rather than --color-gold: on the olive ground, under the
   footer's texture, Liv's own gold lands at 4.33:1 and an --text-xs label
   needs 4.5. See the token. */
/* word-spacing:normal, not the caps token: this is the wide-tracked label role,
   and --word-spacing-caps is derived against a letter gap of .2396em. At .14em
   of added tracking the letters here sit far wider than that, so closing the
   word gap on "Quick Links" would pull the two words together. */
.altha-footer__label {
  font-size: var(--text-xs); letter-spacing:0.14em; word-spacing:normal; text-transform:uppercase;
  color: var(--color-ink); font-weight:600; margin-bottom:1rem;
}

/* ── Band 1: brand and navigation ────────────────────────────────
   A thin strip, not a four-column block: the reference ends on a single quiet
   row, and a tall footer after a page built out of white space reads as a
   second page. Logo and nav sit on ONE line from 900px up.

   The base declarations come FIRST here and the media query LAST. They used to
   be the other way round, and at equal specificity the base won: the 900px
   "auto 1fr" grid, the row-flexed brand and the 22ch tagline were all dead
   rules, and the desktop footer was running on a stray "1fr 2fr". */
.altha-footer__brand { display:flex; flex-direction:column; gap:1.5rem; }
.altha-footer__brand p { color:var(--color-muted); line-height:1.6; margin:0; max-width:32ch; }
.altha-footer__grid  { display:grid; grid-template-columns:1fr; gap:2rem; }
/* Stacked, the Accredited column is still a band, so it keeps the hairline and
   the full rhythm either side of it — this is the mobile footer Chris signed
   off. The negative-looking margin just tops the grid's 2rem row-gap up to a
   full --altha-footer-band, so the rule sits the same distance from the copy
   above it as the legal rule does. Both come off at 900px, where it is a
   column and not a band. */
.altha-footer__col--accred {
  border-top: var(--altha-footer-rule);
  margin-top: calc(var(--altha-footer-band) - 2rem);
  padding-top: var(--altha-footer-band);
}
.altha-footer__col ul { list-style:none; margin:0; padding:0; }
/* Quick Links runs 4+3 down two columns rather than as a 7-item stack beside
   two social icons, which left the Connect column about 90% void. */
.altha-footer__col--nav ul { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:0.75rem 1.5rem; }
/* 15px rather than --text-sm: these are navigation, not fine print, and the
   token's other 16 consumers are a mixed bag that should not all move with it. */
.altha-footer__col a { color:var(--color-ink); font-size:15px; transition:var(--transition-base); }
.altha-footer__col a:hover { color:var(--color-muted); }
.altha-footer__social { display:flex; gap:1rem; }
.altha-footer__social a { color:var(--color-ink); transition:var(--transition-base); display:flex; align-items:center; }
.altha-footer__social a:hover { color:var(--color-muted); }
.altha-footer__social svg { width:1.5rem; height:1.5rem; }

/* The footer carries .altha-logo too, so it draws the same lockup as the
   header — but the --altha-logo-* tokens live on .altha-header, which shrinks
   them on scroll. The footer needs its own static set or the lockup falls back
   to the SVG's intrinsic size and an inherited font-size. Static on purpose:
   the header's tokens are fluid on window height now and the footer is not
   over a 100svh photograph, so it has nothing to be a share OF. These are the
   sizes the old 480px header override used, with the image height left at the
   48px the footer has always used. */
.altha-footer__logo {
  --altha-logo-img:   48px;
  --altha-logo-mark:  var(--altha-footer-mark);
  --altha-logo-shift: 21px;
  --altha-logo-sub:   10.5px;
  align-self: flex-start;        /* the brand column is a flex column — keep the hit area to the logo */
  color: var(--color-gold);      /* the mark is currentColor */
}
/* Beats the global a:hover, which would otherwise pull the mark green while
   the wordmark beside it stayed ink. */
.altha-footer__logo.altha-logo:hover { color:var(--color-gold); }
.altha-footer__brand .altha-logo__shift { color:var(--color-ink); }
.altha-footer__brand .altha-logo__sub   { color:var(--color-muted); }

/* FOUR COLUMNS from 900px up — brand, Quick Links, Connect, Accredited — each
   under its gold label, then the hairline and the legal line.

   This replaces a single quiet row that hid both labels, flattened the seven
   links into a wrapping strip and dropped the tagline. That came from the
   approved mockup, which had far less in its footer than this site does; with
   seven links, two icons and three badges it stopped working (Chris, 2026-09-19).
   Nothing is hidden at any width now.

   FOUR EQUAL COLUMNS. An earlier pass weighted them 0.95/2/0.45/1.2 so that a
   two-up Quick Links could clear "Services and costs"; the uneven measures
   read as a mistake rather than as a system (Chris, 2026-09-19). Equal columns
   work here only because Quick Links runs as ONE column on desktop — seven
   single lines whose longest, "Services and costs", measures 145px at 15px
   Nunito Sans against the 188px each column gets at 900px, the tightest width.
   The two-up split stays on mobile, where it is what keeps the stack short.

   Headroom at 900px, per column: brand needs 145px for the lockup, Quick Links
   145px, Connect 64px, Accredited 171px for three badges at the tighter gap
   below. All four fit 188px, so nothing wraps at the breakpoint. */
@media (min-width:900px) {
  .altha-footer__grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: clamp(1.5rem, 3.5vw, 3.5rem);
    align-items: start;
  }
  /* Seven single lines, not 4+3. */
  .altha-footer__col--nav ul { grid-template-columns:1fr; gap:0.75rem; }
  /* Drops each label onto the logo's line. The brand column leads with a 52px
     lockup and the others with a 12.5px label, so at align-items:start the
     labels sat hard against the top of the row while the wordmark beside them
     sat halfway down it, and the row had no shared line at all. Centring the
     label against the mark puts all four on one. Derived, not nudged: both
     terms are the tokens that set those two heights, so it survives either
     changing. */
  .altha-footer__col { padding-top: calc((var(--altha-footer-mark) - var(--text-xs)) / 2); }
  /* A column here, not a band: the rule and its rhythm belong to the stack. */
  .altha-footer__col--accred { border-top:none; margin-top:0; }
  /* Tighter than the 2rem/3rem the badges take when they have a full band to
     themselves — three of them measure ~131px and the column is ~199px at the
     900px end, so they only stay on one row at this gap. */
  .altha-footer__col--accred .altha-footer__accreditations { gap:1rem 1.25rem; }
}

/* ── The accreditation badges ────────────────────────────────────
   A column of the grid from 900px up and a band of the stack below it; these
   rules are the marks themselves and hold at both.

   The UK Reiki Federation mark and the two SOMA Breath marks, carried over
   from the old site. They ship with the theme in assets/images/accreditations/
   rather than coming from the media library: they are fixed credentials, not
   content, so a fresh install should never come up without them.

   Normalised on HEIGHT, never width. The three have quite different aspect
   ratios — 1.09:1, 1.29:1 and a circle — and a shared width would shrink the
   disc to nothing beside the two wordmarks. The disc then gets its own, taller
   height on top of that, because a circle reads optically smaller than a
   rectangle drawn in the same box; the old site made the same allowance.

   THEY ARE SIZED OFF THE BRAND MARK AND STAY UNDER IT. These are other
   people's credentials; they must never out-measure Liv's own logo. Both
   heights derive from --altha-footer-mark (52px), so the disc always lands at
   0.90 of the mark and the flat wordmarks at 0.68 — move the logo and the row
   follows, still subordinate.

   0.68 is low for a bounding box, and deliberately so: all three badges are
   STACKED lockups (flower + UKRF + strapline + MEMBER; funnel + SOMA + BREATH;
   and the disc packs four tiers), where the brand mark is one compact glyph.
   Matched on height they still out-shout it, so they are set a clear step
   under. Their fine straplines are unreadable at any footer size — they have
   been since the old site — so they work as credential SHAPES, and nothing
   legible is lost by the reduction.

   A stray "gap:1.25rem !important" and "max-height:52px" used to sit 50 lines
   ABOVE this block and beat all of it. The clamp was right about the badges
   being too big; being an unscoped override far from the block it fought is
   what made it invisible, and it flattened the disc's allowance with it. Size
   this row through the two tokens below, never with a max-height. */
.altha-footer__accreditations {
  --altha-accred-h:    calc(var(--altha-footer-mark) * 0.68);   /* ~35px — the flat wordmarks */
  --altha-accred-disc: calc(var(--altha-accred-h)    * 1.32);   /* ~47px — 0.90 of the mark */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 2rem 3rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* THE BADGES ARE AS SUPPLIED NOW (2026-09-25). The footer is light, so the two
   flat marks are shown in their own colours — black SOMA Breath, aubergine UK
   Reiki Federation — and the reversal below is gone. What follows is the
   history of why it was there:

   No panel behind the badges — they sat straight on the ink. That needed the
   two flat marks reversed out, because as supplied they were invisible on it:
   the SOMA Breath wordmark is solid black (1.83:1) and the UK Reiki Federation
   mark aubergine (1.43:1). Both are single-colour lockups, so white IS their
   reversed form — this is the mono treatment those orgs publish themselves, not
   a recolouring of a full-colour mark.
   The Advanced Instructor badge is excluded: it is a teal gradient disc with
   white type already inside it, and inverting would destroy it rather than
   reverse it. It reads as an object on the ink, just quietly.
   Replace all of this with properly supplied reversed assets when Liv can get
   them, and drop the filter. */
/* ALL THREE DARK (2026-09-25, Chris: the coloured logos "look off with the rest
   of the site"). The two flat marks are single-colour lockups, so they go to
   solid black. The Advanced Instructor disc is a teal gradient with white type
   inside it, so a flat black would erase its lettering: it is desaturated and
   darkened instead, which keeps the white type a clear step lighter than the
   disc (about 4:1) while the disc itself reads as a deep grey. If Liv can get
   mono versions of the badges, use those and drop these filters. */
.altha-footer__accreditation:not(.altha-footer__accreditation--round) img { filter: brightness(0); }
.altha-footer__accreditation--round img { filter: grayscale(1) brightness(0.6); }
.altha-footer__accreditation img { height:var(--altha-accred-h); width:auto; }
.altha-footer__accreditation--round img { height:var(--altha-accred-disc); }
.altha-footer__accreditation a:focus-visible { outline:2px solid var(--color-ink); outline-offset:4px; border-radius:2px; }
/* Gap only. The height override that used to sit here (56px, down from 68px)
   existed because the badges were too wide to fit three across; at the sizes
   above they total ~130px and fit a 320px viewport with room to spare. The
   logo mark does not shrink on mobile either, so neither should these. */
@media (max-width:480px) {
  .altha-footer__accreditations { gap:1.5rem 2rem; }
}

/* ── Band 3: legal ───────────────────────────────────────────────
   Left-aligned like every band above it; the two links move out to the right
   edge once there is room for them on the same line. */
.altha-footer__bottom { display:flex; flex-direction:column; gap:0.5rem; text-align:left; }
.altha-footer__bottom p { color:var(--color-muted); font-size:var(--text-sm); margin:0; }
.altha-footer__legal-links { display:flex; flex-wrap:wrap; align-items:baseline; gap:0.5rem; }
/* Replaces an inline style="color:inherit" that used to sit on this link. */
.altha-footer__legal-link { color:inherit; }
@media (min-width:900px) {
  .altha-footer__bottom { flex-direction:row; justify-content:space-between; align-items:baseline; gap:1.5rem; }
}
/* ── Inner page hero ────────────────────────────────────────────── */
/* ── Booking modal ──────────────────────────────────────────────── */
#bookingModal { display:none; position:fixed; inset:0; background:color-mix(in srgb, var(--color-shadow) 50%, transparent); z-index:99999; align-items:center; justify-content:center; padding:1.5rem; backdrop-filter:blur(8px); }
#bookingModal.is-open { display:flex; }
.altha-modal { background:var(--color-white); border-radius:var(--radius-2xl); max-width:500px; width:100%; max-height:90vh; overflow-y:auto; box-shadow:var(--shadow-xl); }
.altha-modal__header { padding:2rem; border-bottom:1px solid var(--color-border); display:flex; justify-content:space-between; align-items:flex-start; }
.altha-modal__close { background:none; border:none; font-size:2rem; color:var(--color-body); cursor:pointer; padding:0; width:32px; height:32px; display:flex; align-items:center; justify-content:center; line-height:1; flex-shrink:0; transition:var(--transition-base); }
.altha-modal__close:hover { color:var(--color-ink); }
.altha-modal__service-name { font-size:var(--text-2xl); font-weight:var(--font-weight-heading); color:var(--color-ink); margin-bottom:0.5rem; }
.altha-modal__price { font-size:var(--text-xl); color:var(--color-accent); font-weight:var(--font-weight-medium); }
.altha-modal__body { padding:2rem; }
.altha-modal__intro { color:var(--color-body); margin-bottom:2rem; line-height:1.75; }
.altha-modal__field { margin-bottom:1.5rem; }
.altha-modal__field label { display:block; margin-bottom:0.5rem; font-size:var(--text-sm); color:var(--color-ink); font-weight:var(--font-weight-medium); }
.altha-modal__field input,.altha-modal__field select { width:100%; padding:0.875rem; border:1px solid var(--color-border); border-radius:var(--radius-md); font-size:var(--text-base); font-family:var(--font-family); color:var(--color-ink); box-sizing:border-box; outline:none; transition:var(--transition-base); }
.altha-modal__field input:focus,.altha-modal__field select:focus { border-color:var(--color-accent); box-shadow:0 0 0 3px color-mix(in srgb, var(--color-accent) 18%, transparent); }
.altha-modal__price-summary { display:none; background-color:var(--color-background); border-radius:var(--radius-md); padding:1.5rem; margin-bottom:1.5rem; }
.altha-modal__price-summary.is-visible { display:block; }
.altha-modal__price-row { display:flex; justify-content:space-between; align-items:center; }
.altha-modal__price-row span:first-child { color:var(--color-ink); font-weight:var(--font-weight-medium); }
.altha-modal__price-row span:last-child { font-size:var(--text-2xl); font-weight:var(--font-weight-medium); color:var(--color-accent); }
.altha-modal__price-note { font-size:var(--text-sm); color:var(--color-body); margin-top:1rem; }
.altha-modal__actions { display:flex; gap:0.5rem; }
.altha-modal__actions .altha-btn { flex:1; justify-content:center; padding:0.75rem 1rem; font-size:0.8rem; }
.altha-modal__consent { margin-bottom:1rem; }
.altha-modal__consent label { display:flex; align-items:flex-start; gap:0.75rem; cursor:pointer; }
.altha-modal__consent input[type="checkbox"] { margin-top:3px; width:16px; height:16px; accent-color:var(--color-accent); flex-shrink:0; }
.altha-modal__consent span { font-size:var(--text-sm); color:var(--color-ink); line-height:1.5; }
.altha-modal__consent--optional span { color:var(--color-body); }
/* The last consent row before the price summary, so it carries the gap to it.
   This and .altha-modal__optional below were inline styles on the markup until
   2026-09-24, which put them beyond the reach of a breakpoint and invisible to
   the stylesheet audit. */
.altha-modal__consent--optional { margin-bottom:1.5rem; }
/* The "(optional)" note beside a modal field label, which is otherwise bold
   and ink. */
.altha-modal__optional { font-weight:400; color:var(--color-body); }
.altha-modal__loading,.altha-modal__no-slots { display:none; font-size:var(--text-sm); margin-top:0.5rem; }
.altha-modal__loading { color:var(--color-accent); }
.altha-modal__no-slots { color:var(--color-error); background-color:var(--color-error-bg); padding:0.75rem 1rem; border-radius:var(--radius-sm); }
.spots-notice { color:var(--color-accent); font-weight:var(--font-weight-medium); margin-top:0.5rem; font-size:var(--text-sm); }

/* ── Animations ─────────────────────────────────────────────────── */
@keyframes fadeUp { from { opacity:0; transform:translateY(2rem); } to { opacity:1; transform:translateY(0); } }
.altha-fade-up { opacity:0; animation:fadeUp 0.6s ease forwards; }

/* ── Utilities ──────────────────────────────────────────────────── */
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border-width:0; }
.text-center { text-align:center; }
.text-gold   { color:var(--color-accent); }
.text-forest { color:var(--color-ink); }
.text-body   { color:var(--color-body); }

/* ── Mobile responsive ──────────────────────────────────────────── */
@media (max-width:767px) {
  .altha-feature__image--left,.altha-feature__content--right { order:unset; }
}

/* ── Print ──────────────────────────────────────────────────────── */
@media print { .altha-header,.altha-footer,#bookingModal { display:none !important; } }

/* ── Cookie Banner ──────────────────────────────────────────────── */
.altha-cookie-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 9999;
  background-color: var(--color-blush);
  border-top: 1px solid color-mix(in srgb, var(--color-ink) 14%, transparent);
  box-shadow: 0 -4px 24px color-mix(in srgb, var(--color-shadow) 18%, transparent);
  padding-block: 1rem;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.35s ease, visibility 0.35s ease;
  pointer-events: none;
}
.altha-cookie-banner.is-visible {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}
.altha-cookie-banner__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}
.altha-cookie-banner__text {
  color: var(--color-body);
  font-size: var(--text-sm);
  line-height: 1.6;
  margin: 0;
  flex: 1 1 auto;
  min-width: 200px;
}
.altha-cookie-banner__policy-link {
  color: var(--color-link);
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
  transition: color 0.2s ease;
}
.altha-cookie-banner__policy-link:hover { color: var(--color-link-hover); }
.altha-cookie-banner__actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 0;
}
.altha-cookie-banner__btn.altha-btn {
  padding: 0.5rem 1.25rem;
  font-size: var(--text-sm);
  white-space: nowrap;
}
.altha-cookie-banner__policy-link:focus-visible,
.altha-cookie-banner__btn:focus-visible {
  outline: 2px solid var(--color-ink);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Manage link in footer bottom bar */
.altha-cookie-manage-link {
  background: none;
  border: none;
  padding: 0;
  color: inherit;
  font-size: inherit;
  font-family: inherit;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.3s ease;
  line-height: inherit;
}
/* Inherits its colour from the footer bottom bar, so it only needs a hover. */
.altha-cookie-manage-link:hover { color: var(--color-ink); }
/* Ink now: the footer's legal band is light, so the ring is dark. (It was
   --color-on-dark while the footer was green, because ink on ink drew an
   invisible ring on the one control down there.) */
.altha-cookie-manage-link:focus-visible {
  outline: 2px solid var(--color-ink);
  outline-offset: 2px;
  border-radius: 2px;
}

@media (max-width:639px) {
  .altha-cookie-banner__inner {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.875rem;
  }
  .altha-cookie-banner__actions {
    width: 100%;
    justify-content: space-between;
  }
}

@media print { .altha-cookie-banner { display:none !important; } }
