/* ================================================================
   ALTHA ALCHEMY — Layout
   ================================================================
   This is the approved mockup's stylesheet, namespaced to .ah-*, and it
   is the site's single source of page layout. It loads on every page,
   after theme.css.

   WHY IT IS SEPARATE. The homepage was first rebuilt by editing values
   into theme.css, and every fix had to beat a legacy rule that matched
   the same element: the SOMA events grid also matched the three story
   sections, the footer's compacting rules lost to declarations further
   down the file, and the copy columns drifted from the mockup's spans.
   The mockup is ~250 self-consistent lines; reconciling two systems was
   the actual problem, so the homepage now uses the mockup's rules
   verbatim and nothing in theme.css matches .ah-*.

   Colours, fonts, the page margin and the gap all come from the theme's
   tokens — this file declares no colour of its own.

   The site header and footer are shared with every page and are NOT
   here; they stay in theme.css. */

:root {
  --ah-gap: 20px;
  /* The display-line role: the ONE large line each section is allowed, set in
     the display face and sentence case. Five consumers:
     .ah-prose--lead p:first-child, .ah-pull, .ah-safe__line,
     .ah-words blockquote, .ah-begin__text h2. The mockup gave each its own
     clamp — 26/2.4/36, 28/2.8/42, 28/3/46, 26/3/44 and 30/3.2/48 — which spanned
     26px to 33px at 1024px for what is one role, and read as too big at every
     step of it. Do not give a section its own size.

     Sized against the body copy rather than as free-standing display type.
     This was a hard constraint under Josefin Sans, whose x-height is .378em
     (OS/2 sxHeight) against Nunito's .486em — 22% lower — so it shouted in the
     capitals and mumbled in the lowercase, and its lowercase fell under the
     body copy's below 20.6px at a 16px body.

     Every face since has moved the same way, and MERRIWEATHER goes furthest
     of all:

       face             x-height   threshold   at the 24px floor
       Josefin Sans      .378em     20.6px     1.17x the body's x-height
       Jost              .460em     16.9px     1.42x
       Playfair          .515em     15.1px     1.59x
       MERRIWEATHER      .555em     14.0px     1.71x

     (Threshold = the size below which the display face's lowercase falls under
     the body's. It is 16 x .486 / the face's x-height, and it moves with the
     BODY size — it was 23.1px for Josefin when the body was 18px.)

     So the constraint that set this clamp has stopped binding, and the
     opposite risk is the live one: at the 24px floor Merriweather's lowercase
     is 1.71x the body's, on a cap height that also grew this swap (.743
     against Playfair's .708). These lines READ LARGER at the same px than
     under any face before them, and Merriweather widens that gap again rather
     than closing it. If they feel big after the swap, this clamp is the dial
     — and unlike Playfair, Merriweather DOES have an instance below 400 to
     fall back to if size alone isn't enough; see --ah-display-weight.

     This was briefly raised to 30-40 on 2026-09-19, as part of making the lead
     paragraph body copy and handing the section's large line to .ah-head h2.
     That did not work and was reverted the same day at Chris's call, along with
     the lead. Leaving it at 30-40 with the lead back in the display face would
     have made the intro LARGER than the version that prompted the complaint. */
  --ah-display-line: clamp(24px, 2vw, 30px);

  /* Titles on the blocks that sit BELOW a section head — the service cards on
     the homepage, the two-up blocks on About, Events and the flexible builder.
     They are subordinate to the display line, so they have to measure smaller
     than it. At the mockup's 26px they did not: uppercase Josefin at 26px puts
     caps at 18.3px against 16.8px for the display line at its 24px floor, so a
     card title out-measured the section's own large line and an h3 came out
     bigger than the h2 under it. 22px gives 15.4px, clearing the floor by
     1.4px.
     STILL CLEARS UNDER MERRIWEATHER, and by a slightly wider margin: cap
     height moved to .743em (up from Playfair's .708em), so 22px gives 16.3px
     against 17.8px for the display line at its 24px floor — clears by 1.5px,
     against Playfair's 1.4px. Re-check it on any face swap even so — it is a
     comparison between two tokens, and a face that moved cap height without
     moving x-height would invert it silently.

     Briefly clamp(24px,2.4vw,32px) on 2026-09-19, which only held while the
     display line was at 30-40. When that was reverted this had to come back
     with it, or a 30.7px card title would have sat above a 25.6px display line
     — the exact inversion the paragraph above exists to prevent. */
  --ah-block-title: 22px;

  /* BACK TO 300, 2026-09-28: Merriweather's wght axis is 300-900, so the value
     every face before Playfair carried is available again. Playfair's axis
     started at 400 and forced this to 400 for that one trial — see the
     history this comment used to carry, kept below.

     Covers .ah-prose--lead p:first-child, .ah-pull, .ah-safe__line,
     .ah-words blockquote and .ah-begin__text h2 — the display lines, never a
     heading (those read --font-weight-heading, 400, unaffected by this
     token). Does NOT follow --font-family-display on its own; check this
     value against the new face's fvar table on every swap.

     HISTORY: while Playfair was the display face (2026-09-24 to 2026-09-28)
     this was 400, not by choice — Playfair's axis floor was 400, so 300 would
     have clamped silently and the display lines would have rendered one step
     heavier than the sheet implied, the Karla fault the @font-face note in
     theme.css is about. */
  --ah-display-weight: 300;
}
@media (max-width:739px) { :root { --ah-gap: 12px; } }

/* The mockup's global block is deliberately NOT carried over: theme.css
   already resets the box model, margins and link colours site-wide, and its
   `overflow-x:hidden` on <body> was actively harmful here — an overflow value
   on body stops the viewport propagating the scroll, so the document scrolled
   inside the body box and window scrolling barely moved (measured: a request
   to scroll 1600px moved 28). Only the two values the homepage genuinely needs
   are kept, scoped to the page itself. */
.ah-home { color:var(--color-ink); font-family:var(--font-family-body); font-size:15px; line-height:1.5; }
.ah-home img { display:block; max-width:100%; }
/* This rule (0,2,0) beats the h1-h6 element rule in theme.css, so the whole
   homepage stayed at 400 no matter what --font-weight-heading said. Kept as an
   explicit hook, now pointing at the token instead of a literal. */
.ah-home h1, .ah-home h2, .ah-home h3 { font-weight:var(--font-weight-heading); }
.ah-home a { color:inherit; text-decoration:none; }
.ah-grid{display:grid;grid-template-columns:repeat(24,minmax(0,1fr));column-gap:var(--ah-gap);padding-inline:var(--altha-gutter)}
a.ah-u:hover,.ah-links a:hover{text-decoration:underline;text-underline-offset:4px}
.ah-links{display:flex;flex-wrap:wrap;gap:14px;font-size:15px}
.ah-links span{opacity:.45}

@media (prefers-reduced-motion:reduce){.ah-bar{transition:none}}

/* Opening: photo first, headline and booking links low left */
/* The hero fills the viewport exactly: the mockup's clamp(520px,56vw,780px)
   is 717px at a 1280 window, which on most laptops is deeper than the glass,
   so the headline sat below the fold and the opener had to be scrolled to be
   seen.

   svh, NOT vh or dvh. dvh tracks the mobile URL bar live, so every scroll that
   retracted the bar would grow the hero and re-crop the photograph mid-scroll;
   vh predates the unit and is the fallback. svh is the height with the browser
   chrome shown, and it never changes while scrolling.

   The min-height keeps the headline and links from colliding in a short
   landscape window, where 100svh can be under 400px. */
.ah-feature{position:relative;height:100vh;height:100svh;min-height:520px;overflow:hidden;color:#fff}
/* Logged-in only: the admin bar is fixed over the top of the page, so a full
   viewport hero overflows by exactly its height. */
.admin-bar .ah-feature{height:calc(100svh - var(--wp-admin--admin-bar--height, 32px))}
.ah-feature img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:46% 32%}
.ah-feature::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(10,14,10,.62) 0%,rgba(10,14,10,.34) 14%,rgba(10,14,10,.08) 28%,rgba(10,14,10,0) 40%),linear-gradient(90deg,rgba(10,14,10,.55) 0%,rgba(10,14,10,.28) 36%,rgba(10,14,10,.04) 64%,rgba(10,14,10,0) 84%),radial-gradient(52% 40% at 100% 0%,rgba(10,14,10,.5) 0%,rgba(10,14,10,.22) 45%,rgba(10,14,10,0) 78%),linear-gradient(color-mix(in srgb,var(--ah-photo-tint) 6%,transparent),color-mix(in srgb,var(--ah-photo-tint) 6%,transparent))}
/* The last layer above is the honey tint at 6% (2026-09-25), an EXPERIMENT for
   Chris to judge on the homepage hero, which was first left alone because it is
   the green canopy. Remove that one gradient to undo it. */
.ah-feature .ah-grid{position:absolute;inset:0;z-index:1;align-content:end;padding-block:0 clamp(30px,3.6vw,52px)}
/* 18 of 24, widened from the mockup's 13 on 2026-09-19. The hero headline had
   a 52px ceiling against unrvld.com's 140px and sat small in the corner of a
   full-viewport photograph. 13 columns is 647px at 1280, and "TRANSFORMATION"
   — one unbreakable word — measures 614px at 64px type, so 13 columns capped
   the hero at about 64px however large the clamp said. 18 columns is 904px and
   carries the word to 94px. */
.ah-feature__title{grid-column:1 / span 18}
/* The hero reads the leading token now. It carried .92 of its own, which was
   derived for CAPITALS — cap .700em over a .170em comma, floor .87 — and in
   sentence case that is a collision, not a tight setting: the ink spans
   1.020em once a descender can sit over an ascender. See --leading-heading.

   This headline is the worst case on the site for it. "Breath. Energy." ends
   in a descender and "Transformation" opens on a cap, so the pinch is real at
   every width rather than theoretical. 1.17 clears it by .081em, 6.7px at the
   83.2px this renders at 1280 — almost the same pixel clearance Jost's 1.1
   gave, though the em figure moved for an unrelated reason (see
   --leading-heading).

   1.089 is the hard floor if the hero is ever wanted tighter than the rest of
   the headings again. Below that the g of "Energy" touches the T below it. */
.ah-feature h1{font-family:var(--font-family-display);font-size:clamp(34px,6.5vw,96px);line-height:var(--leading-heading)}
.ah-feature__title p.ah-links{margin-top:18px}

/* Section scaffolding: title, 1px rule, small link right */
.ah-section{padding-top:clamp(88px,9vw,140px)}
/* A section head is a title and a rule. The small link that used to sit on
   the right went on 2026-09-18: every one of them pointed at a page already in
   the nav, so it duplicated navigation rather than offering a route. */
/* NO RULE. Chris's call, 2026-09-24: the hairlines harden a page that is
   otherwise made of white space, and this one — a full-bleed 1px of INK under
   every heading on the site — was the loudest of them. The 13px of padding
   went with it: it existed to hold the heading off the rule, and folded into
   the margin below, which is bumped a step because the white space is now
   doing the separating on its own. */
.ah-head{grid-column:1 / -1;margin-bottom:clamp(32px,3.4vw,52px)}
/* The section's LARGE line, raised from clamp(26px,2.1vw,32px) on 2026-09-19.
   It takes over the role the lead paragraph used to hold: the lead is body
   copy now, so the head is what carries the section. 32-52 keeps the longest
   head we ship on one line at desktop — "EVERYTHING BEGINS WITH FEELING SAFE"
   is 35 characters, 1052px at the 43.5px this resolves to at 1280, inside the
   1212px full-bleed measure. The 32px floor is set by the display line's 30px
   floor: below that the two cross over on a phone.
   SENTENCE CASE from 2026-09-24 buys that fit room rather than costing it,
   and each serif swap since has spent a little of it back without threatening
   the fit: "Everything begins with feeling safe" measures 19.025em in Jost
   capitals, 14.742em in Jost sentence case, 15.679em in Playfair and 17.021em
   in MERRIWEATHER — 871px at the 51.2px this resolves to at 1280, still well
   inside the 1212px measure. The longest head on the site is the Reiki page's
   "One-to-one Reiki treatments for women" at 19.392em, 993px, which also
   fits, with 219px to spare. Both measured with uharfbuzz at wght 400, real
   shaping rather than summed advances — re-derive the same way on any future
   face swap; this margin is comfortable but not unlimited. */
.ah-head h2{font-family:var(--font-family-display);font-size:clamp(32px,4vw,52px);line-height:var(--leading-heading)}
.ah-prose{font-size:16px;line-height:1.6;display:grid;gap:18px}
/* Display type, as it always was. It was briefly made body copy on 2026-09-19
   on the theory that a 45-word intro should not sit in the display face; that
   did not work and was reverted the same day at Chris's call. --ah-display-line
   went back to its original 24-30 with it, because leaving it at 30-40 would
   have made this intro LARGER than the version that prompted the complaint. */
.ah-prose--lead p:first-child{font-family:var(--font-family-display);font-weight:var(--ah-display-weight);font-size:var(--ah-display-line);line-height:1.17;text-transform:none}
.ah-small{font-size:13.5px;color:var(--color-muted)}

/* Intro: text offset right of the rule, not centred */
.ah-intro__body{grid-column:9 / span 12}
.ah-intro__aside{grid-column:1 / span 6;font-size:13.5px;color:var(--color-muted)}
@media (max-width:1023px){.ah-intro__body{grid-column:1 / -1}.ah-intro__aside{grid-column:1 / -1;margin-bottom:20px}}

/* Liv's story: narrow measure, photo bleeding off the right edge */
.ah-story{margin-top:clamp(88px,9vw,140px)}
.ah-story .ah-grid{align-items:start}
.ah-story__text{grid-column:1 / span 11}
/* Centred beside the photograph, as the events block does (2026-09-25). Only
   matters when the copy is SHORTER than the photo; when it is deeper the row
   is as tall as the copy and this does nothing. Desktop only: stacked, the
   text is display:contents in a flex column and has no box to align. */
@media (min-width:861px){.ah-story__text{align-self:center}}
.ah-story__photo{grid-column:14 / -1}
/* NOW 652/706, the events block's own ratio (2026-09-25). Chris found the 2:3
   "too tall", tried 4:3, and preferred the events module's proportions as
   "more balanced", so every left-right block shares that one. What follows is
   the history of the 2:3 it replaced, kept because it explains why the number
   was 2:3 and why it is not any more.

   2:3, measured off the approved mockup, NOT the 4/3 its stylesheet reads.
   Measured from the two full-page screenshots at the same scale (the page
   gutter is 52px in both, so they are directly comparable): the mockup's
   figure is 918x1400 device pixels against staging's 918x686 — identical
   width, double the depth. Whatever the mockup's own rule says, that is what
   it renders and what was approved.

   Change the ratio here and nowhere else; the image is cropped by the box. */
.ah-story__photo img{width:100%;aspect-ratio:1/1;object-fit:cover;object-position:60% 40%;border-radius:16px}
/* THE PHOTOGRAPH SITS INSIDE THE CONTENT AREA (2026-09-25), 9 of 24 columns
   and no longer 12 bleeding off the edge — Chris's call: bleeding photographs
   read too big once the page came in from the edges. Inside the gutter every
   corner is on screen, so the radius is uniform 16px, and the grid keeps its
   padding on both sides at every width.

   STICKY: the copy on these blocks can run far deeper than a 9-column photo,
   so the photo pins under the header and the copy scrolls past it, released
   when the row ends. Sticky needs the photo to be a grid item that does not
   stretch (align-self:start) and a row as tall as the copy, which is what the
   grid gives it. Desktop only, and each block's own stacking breakpoint —
   860px for this one — because stacked, the grid is a flex column for the
   session cards and a sticky figure there would pin across the whole section.
   `top` must not be set outside the media query: a sticky that loses its
   position but keeps its offset is the bug .altha-story__image had for months. */
@media (min-width:861px){.ah-story__photo{position:sticky;top:7rem;align-self:start}}
.ah-story__photo figcaption{margin-top:10px;font-size:13px;color:var(--color-muted)}
.ah-pull{margin-top:28px;font-family:var(--font-family-display);font-weight:var(--ah-display-weight);font-size:var(--ah-display-line);line-height:1.17;max-width:18ch}
@media (max-width:860px){.ah-story__text,.ah-story__photo{grid-column:1 / -1}.ah-story__photo{margin-top:36px}.ah-story__photo img{border-radius:16px}}

/* Safety: two columns of running text under one display line */
.ah-safe__line{grid-column:1 / span 15;font-family:var(--font-family-display);font-weight:var(--ah-display-weight);font-size:var(--ah-display-line);line-height:1.17;margin-bottom:34px}
.ah-safe__cols{grid-column:1 / -1;columns:2;column-gap:clamp(30px,4vw,64px);font-size:16px;line-height:1.6}
.ah-safe__cols p{margin:0 0 18px;break-inside:avoid}
@media (max-width:739px){.ah-safe__cols{columns:1}.ah-safe__line{grid-column:1 / -1}}

/* The one oversized line */
.ah-statement{position:relative;height:clamp(380px,40vw,600px);overflow:hidden;color:#fff;display:flex;align-items:flex-end;margin-top:clamp(88px,9vw,140px)}
.ah-statement img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% 62%}
.ah-statement::after{content:'';position:absolute;inset:0;background:linear-gradient(rgba(12,16,12,.34),rgba(12,16,12,.34)),linear-gradient(color-mix(in srgb,var(--ah-photo-tint) 6%,transparent),color-mix(in srgb,var(--ah-photo-tint) 6%,transparent))}
/* The ONE role where sentence case changed geometry rather than just letter
   shapes, and it needs both halves of the change or the line is CLIPPED.

   This is a single nowrap line, so leading cannot collide with anything — but
   .ah-statement is overflow:hidden with align-items:flex-end, so the h2's
   bottom edge is the section's bottom edge and any ink below it is cut off.
   The leading here is therefore a CLIPPING control, not a rhythm one, and it
   is the one place on the site where the two point in opposite directions.

   The sum, for whatever face is in --font-family-display: with a vertical box
   of (asc - desc) and a line box of L, the line box bottom sits at
   desc + (box - L)/2 from the baseline, and L must push that below the
   deepest descender.

     face          asc/desc      box      deepest desc   floor L   ships
     Jost          1.070/-.375   1.445em    -.230em       1.155     1.18
     Playfair      1.082/-.251   1.333em    -.188em       1.207     1.24
     MERRIWEATHER  0.984/-.273   1.257em    -.270em       1.251     1.28

   NOTE WHAT'S DIFFERENT ABOUT MERRIWEATHER: its box is the SMALLEST of the
   three (1.257em, against Playfair's 1.333 and Jost's 1.445) and yet it ships
   the tallest floor. Jost and Playfair both design their typo descent deeper
   than any letter actually reaches — Playfair's box allows .063em of slack
   below the g/y before hitting glyph ink, Jost's .145em — so leading can sit
   inside the box and still clear. Merriweather's typo descent (-.273em) and
   its deepest real descender (-.270em, the g) are within three thousandths of
   each other: there is almost no slack, so the floor lands almost exactly at
   the box itself rather than under it. Reading either the descender alone or
   the box alone gives the wrong answer here, which is why the whole sum is
   written out — this face is the reason.

   padding-bottom stays at nought — the line box carries that space itself, and
   restoring the old clamp on top of the leading would be the same space twice.

   (This role needed the sum at all only because headings went sentence case on
   2026-09-24. In capitals nothing descended past a comma and the fault could
   not appear — under Jost it was live and costing a descender 3.4px at 1280
   and 11.6px at 1920.) */
.ah-statement h2{position:relative;z-index:1;width:100%;padding:0 var(--altha-gutter);font-family:var(--font-family-display);font-weight:400;white-space:nowrap;line-height:1.28;font-size:11vw}
.ah-after-statement{padding-top:clamp(40px,4vw,64px)}
.ah-after-statement .ah-prose{grid-column:9 / span 12}
@media (max-width:1023px){.ah-after-statement .ah-prose{grid-column:1 / -1}}

/* Sessions: bare 3:4 images running off the right edge */
.ah-rail{display:grid;grid-auto-flow:column;grid-auto-columns:calc((100vw - 2 * var(--altha-gutter) - 3 * var(--ah-gap)) / 3.35);gap:var(--ah-gap);overflow-x:auto;scroll-snap-type:x mandatory;padding-inline:var(--altha-gutter);scroll-padding-inline:var(--altha-gutter);scrollbar-width:none}
.ah-rail::-webkit-scrollbar{display:none}
@media (max-width:739px){.ah-rail{grid-auto-columns:76vw}}
/* FULL WIDTH ON THE CONTENT AREA (2026-09-25, Chris): from 740px the rail stops
   being a sideways scroller showing 3.35 cards and becomes a grid that shares
   the whole content width evenly — three cards are three columns, four are
   four, and a fifth wraps rather than shrinking them below 270px. Any number
   of cards works, which is the point: the count is the admin's. Under 740 the
   swipeable rail above is kept, because a phone has no room for columns. The
   scroll and snap machinery is switched off so nothing is left half-scrollable. */
@media (min-width:740px){.ah-rail{grid-auto-flow:row;grid-auto-columns:auto;grid-template-columns:repeat(auto-fit,minmax(min(100%,270px),1fr));overflow:visible;scroll-snap-type:none}}
.ah-card{scroll-snap-align:start;display:block}
.ah-card .ah-img{aspect-ratio:4/5;background:var(--color-surface);overflow:hidden;border-radius:16px;position:relative}
/* border-radius:inherit as well as the parent's own overflow:hidden clip —
   belt and braces. Relying on the clip alone left a hairline of .ah-img's own
   --color-surface background visible at the curve on staging: a real,
   commonly-hit rendering seam where a rounded overflow:hidden container clips
   a perfectly square child, and anti-aliasing at the arc doesn't quite meet
   the container's edge. Rounding the img to match closes the gap outright
   rather than depending on the browser's clip being pixel-exact. */
.ah-card .ah-img img{width:100%;height:100%;object-fit:cover;border-radius:inherit}
/* The scoop: a quarter-circle notch cut into the photo's bottom-right corner,
   with the card's own arrow re-housed in a gold circle nested in the hollow —
   the Engage-reference device Chris asked for, 2026-09-25. Two flat shapes,
   no mask: .ah-card__scoop is a 64px square with ONLY its inward-facing corner
   rounded (border-radius:50% 0 0 0), so the shape itself is a square with one
   big quarter-circle bitten out of it. Anchored to the image's true corner
   (right/bottom:-1px, covering the 1px seam), it paints that corner white —
   opaque at the photo's actual edge, curving away as it moves toward the
   image's centre. .ah-img's own overflow:hidden clips the 1px it overhangs.
   The gold circle then sits on top, inside the notch, carrying the arrow that
   used to sit at the foot of the card.
   TWO CORRECTIONS caught after this first shipped. First: the page ground is
   NOT white — body's background-color is --color-background #F7F5F0
   (theme.css), and there is no override on main or .ah-home, so a white notch
   left a faint seam against the real ground. Second: "the rail never sits on
   a tinted or forest band" was checked against seeded CONTENT, not against
   what the theme allows — page-flexible.php's 'rail' case renders inside
   whatever `$altha_band` the builder gives its section, and
   .ah-band--dark a.ah-card already recolours the card's link text for exactly
   that case a few rules below. Nothing seeded uses it today, but the code
   path is real, so the notch now follows the section's own background on all
   three grounds rather than assuming one. */
.ah-card__scoop{position:absolute;right:-1px;bottom:-1px;width:64px;height:64px;background:var(--color-background);border-radius:50% 0 0 0}
.ah-band--tint .ah-card__scoop{background:var(--color-surface)}
.ah-band--dark .ah-card__scoop{background:var(--color-forest)}
.ah-card__badge{position:absolute;right:12px;bottom:12px;width:40px;height:40px;display:flex;align-items:center;justify-content:center;background:var(--color-gold);color:var(--color-ink);border-radius:50%;font-size:16px;line-height:1}
.ah-card__badge svg{width:18px;height:18px;display:block}
/* The same notch and badge on a Services photograph, scaled up to the picture:
   88px scoop and 56px badge against the card's 64 and 40, on a photo about
   1.6x the card's width. The frame is what they are positioned against (the
   figure itself is sticky, or static when stacked, and cannot be relied on to
   be the containing block), and it clips them to the photo's own 16px corners.
   The badge here is a <button>, so it needs its reset, a pointer and states. */
.ah-photo-frame{position:relative;overflow:hidden;border-radius:16px}
.ah-photo-frame .ah-card__scoop{width:88px;height:88px}
.ah-photo-frame .ah-card__badge{right:16px;bottom:16px;width:56px;height:56px}
.ah-photo-frame .ah-card__badge svg{width:24px;height:24px}
/* A WARM TINT ON THE SERVICE PICTURES (2026-09-25), so the admin's varied
   uploads read as one set: the homepage/rail card images and the Services
   photographs, and nothing with a person in it. A flat overlay in a honey tone
   at 16%, in CSS, so any upload gets it and it is one variable to tune or one
   line to remove. Chris ruled out green ("I dislike the green"); this is not
   a palette colour, it is a photographic grade. It is ::before with no z-index
   on purpose: positioned children paint in tree order, so the overlay sits
   over the picture and UNDER the title panel, the scoop and the badge, which
   come later. A card with no image (the forest fallback) is not tinted. */
:root{--ah-photo-tint:#C98F5A;--ah-photo-tint-strength:.16}
/* THE STORY PHOTOGRAPHS get a lighter grade (2026-09-25): sepia(.12), a warm
   shift roughly as faint as a 6% honey overlay. A FILTER, not an overlay,
   because the figure also holds a caption and is sometimes static, so there is
   no clean box to lay a tint over without covering the words; the filter acts
   on the picture alone. The frame's own images are excluded (the Services
   photographs already carry the 16% overlay). The events photograph and the
   homepage hero are left alone, as anything with a person or the green canopy
   is at risk of going sallow or olive. */
.ah-story__photo > img,
.ah-events__photo img{filter:sepia(.12)}
.ah-card .ah-img--title:has(img)::before,
.ah-photo-frame::before{content:'';position:absolute;inset:0;background:var(--ah-photo-tint);opacity:var(--ah-photo-tint-strength);pointer-events:none}
.ah-photo-book{border:0;padding:0;cursor:pointer;transition:background-color .2s ease}
.ah-photo-book:hover{background:var(--color-gold-deep)}
.ah-photo-book:focus-visible{outline:2px solid var(--color-ink);outline-offset:3px}
@media (max-width:860px){
  .ah-photo-frame .ah-card__scoop{width:64px;height:64px}
  .ah-photo-frame .ah-card__badge{right:12px;bottom:12px;width:40px;height:40px}
  .ah-photo-frame .ah-card__badge svg{width:18px;height:18px}
}
.ah-card .ah-cat{font-size:12.5px;color:var(--color-muted);margin-top:10px}
.ah-card h3{font-family:var(--font-family-display);font-size:var(--ah-block-title);line-height:var(--leading-heading);margin-top:12px}
/* A card whose picture carries its own title (the homepage service cards and
   the builder's card rail, 2026-09-25). The image is shown in full colour and
   untouched; the category and name sit on a small cream rounded panel inset in
   the bottom-left corner, so legibility comes from the panel and not from the
   photograph — any upload works, which matters because the admin picks them.
   The panel keeps clear of the badge in the scoop's hollow on the right. It
   reads the same tokens as everything else here: --color-surface, 12px radius,
   ink type. A card with no image is a green panel so the cream panel has
   something to sit on. h3 takes a colour from theme.css, which beats one
   inherited, so ink is stated. (Two earlier versions were wrong: a photograph
   under a dark scrim with white type, then a texture screened over cream like
   the quote strip, which washes out because the strip's ground is the site's
   one tint.) */
.ah-card .ah-img--title:not(:has(img)){background:var(--color-forest)}
/* TOP-LEFT (2026-09-25, Chris): away from the calendar badge in the bottom
   corner, so the title label and the booking control are not neighbours. With
   the badge no longer beside it the panel can use the full width. */
.ah-card__panel{position:absolute;z-index:2;left:12px;top:12px;max-width:calc(100% - 24px);padding:14px 18px 16px;background:var(--color-surface);border-radius:12px}
.ah-card__panel .ah-cat{margin:0 0 4px}
.ah-card .ah-card__title{margin:0;color:var(--color-ink);font-size:var(--ah-block-title);line-height:var(--leading-heading)}
/* The description is the first thing under the picture now (the category and
   title moved onto it on 2026-09-25), so it had 8px of air above it — sized
   for text that followed a heading, not a photograph — and read squashed
   against the image edge. 20px, and running-text size and leading (16px/1.6)
   like every other paragraph on the site; it was 15px/1.5. */
.ah-card p.ah-deck{font-size:16px;line-height:1.6;margin-top:20px;max-width:36ch}
.ah-card .ah-go{display:inline-block;margin-top:12px;font-size:13px;font-weight:700}

/* Events: photo left, detail list right */
.ah-events{margin-top:clamp(88px,9vw,140px)}
.ah-events .ah-grid{align-items:center}
.ah-events__photo{grid-column:1 / span 11}
@media (min-width:861px){.ah-events__photo{position:sticky;top:7rem;align-self:start}}
/* Measured off the approved mockup, not read from its stylesheet — which says
   16/10 and renders something else, exactly as the story photograph did.
   At the same scale in both full-page screenshots (page gutter 52px):

     mockup   photo 998 x 1080 device px  (652 x 706 CSS)  ratio 0.92
     staging  photo 998 x  578 device px  (652 x 378 CSS)  ratio 1.73

   Identical width again, nearly double the depth. The ratio is written as the
   measurement so it can be checked. */
.ah-events__photo img{width:100%;aspect-ratio:1/1;object-fit:cover;border-radius:16px}
.ah-events__body{grid-column:14 / -1;display:grid;gap:18px}
.ah-events__body .ah-go--primary{justify-self:start}
.ah-events__body h2{font-family:var(--font-family-display);font-size:clamp(28px,2.6vw,40px);line-height:var(--leading-heading)}
.ah-events__body .ah-kicker{font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--color-muted)}
/* No rules here either. These rows are the one place the hairline was doing
   real work — it carried the eye from a label on the left to its value on the
   right — so the row spacing comes down to compensate: a tighter set of rows
   reads as a group without needing a line drawn under each one. */
.ah-facts{list-style:none;margin:0;padding:0}
.ah-facts li{display:flex;justify-content:space-between;gap:16px;padding:5px 0;font-size:13.5px}
.ah-facts span{color:var(--color-muted)}
@media (max-width:860px){.ah-events__photo,.ah-events__body{grid-column:1 / -1}.ah-events__body{margin-top:28px}.ah-events__photo img{border-radius:16px}}

/* The one tinted block */
.ah-words{margin-top:clamp(88px,9vw,140px);background:var(--color-surface);padding-block:clamp(64px,7vw,110px)}
.ah-words__inner{grid-column:5 / span 16;text-align:center}
.ah-words .ah-kicker{font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--color-muted)}
.ah-words blockquote{margin:22px 0 0;font-family:var(--font-family-display);font-weight:var(--ah-display-weight);font-size:var(--ah-display-line);line-height:1.17}
.ah-words figcaption{margin-top:20px;font-size:13.5px;color:var(--color-muted)}
@media (max-width:1023px){.ah-words__inner{grid-column:1 / -1}}

/* Closing */
.ah-begin__text{grid-column:1 / span 11}
.ah-begin__text h2{font-family:var(--font-family-display);font-weight:var(--ah-display-weight);font-size:var(--ah-display-line);line-height:1.17;margin-bottom:20px}
.ah-begin__side{grid-column:14 / span 9;align-self:end;display:grid;gap:10px}
.ah-begin__side a{font-size:18px;font-weight:700;border-bottom:1px solid var(--color-ink);justify-self:start;padding-bottom:3px}
@media (max-width:860px){.ah-begin__text,.ah-begin__side{grid-column:1 / -1}.ah-begin__side{margin-top:24px}}

@media (max-width:739px){
  .ah-nav ul{display:none}
  .ah-nav .ah-book{margin-top:0;padding-top:0;border-top:0}
  .ah-bar nav a{display:none}.ah-bar nav .ah-menu{display:block}
}

/* ── Wiring the mockup to WordPress's own markup ──────────────────
   Three small additions the mockup did not need because its content was
   hand-written: the booking control is a <button> (it opens the booking
   dialog rather than following a link), the prose blocks come out of the
   editor as <p> runs, and the hero keeps .altha-hero so theme.js can find
   it for the scrolled-header toggle. */
.ah-go {
  display:inline-block; margin-top:12px; padding:0;
  font-family:inherit; font-size:13px; font-weight:700; color:var(--color-ink);
  background:none; border:0; cursor:pointer; text-align:left;
  text-decoration:underline; text-underline-offset:4px;
}
.ah-go:hover { color:var(--color-muted); }
.ah-u:hover { text-decoration:underline; text-underline-offset:4px; }

/* altha_prose() emits paragraphs; the mockup's .ah-prose is a grid, so its
   gap already spaces them. Clear the editor's own margins. */
.ah-prose p { margin:0; }

/* ── Line length ──────────────────────────────────────────────────
   Measure on this site comes from grid column spans on a 24-column FULL-BLEED
   grid with no max-width anywhere, so it grows in direct proportion to the
   viewport. Measured against Nunito Sans's real advance widths, .ah-prose ran
   74 characters at 1280, 84 at 1440 and 114 at 1920 — the comfortable band is
   45-75, tolerance 85. So the copy became unreadable on a large monitor while
   looking fine on the laptop it was built on.

   1ch is the "0" advance, 0.600em in Nunito Sans, against an average text
   character of 0.446em — so 1ch buys 1.35 characters and 50ch is 67 of them.
   That matches the caps this file already carries on the page-hero copy (46ch
   = 62 chars) and the events copy (42ch = 57); these are the blocks that were
   missed, not a new idea.

   CAP THE PARAGRAPH, NEVER THE .ah-prose CONTAINER. ch resolves against the
   element's OWN font-size, and .ah-prose--lead p:first-child renders at the
   display size. A 50ch cap on the container resolves at 18px — 540px — and
   would strangle the lead line to about 15 characters. On the paragraphs it
   resolves per element, which is why the lead takes its own cap below. */
.ah-prose p,
.ah-story__text p,
.ah-events__body p { max-width: 50ch; }
/* The story and events copy alone runs to 56ch (about 76 characters), the top
   of the comfortable range, from 2026-09-25. At 50ch the copy stopped 170px
   short of its column on a 1920 screen and, with the gap, left about 370px of
   nothing beside the photograph. Same specificity as the rule above and later,
   so it wins for these two and every other .ah-prose stays at 50ch. */
.ah-story__text p,
.ah-events__body p { max-width: 56ch; }
/* No separate cap on the lead. It is display type again, and 50ch resolves
   against its OWN font-size, so at 25.6px that is 768px — wider than its grid
   column, i.e. no constraint, which is how it behaved originally. */

/* columns:2, so this one IS capped on the container: 100ch at 18px is 1080px,
   giving two 508px columns of 63 characters each, down from 111 at 1920. */
.ah-safe__cols { max-width: 100ch; }
.ah-safe__cols p:last-child { margin-bottom:0; }

/* The hero carries .altha-hero for theme.js. That class also sets a height
   and a painted ground in theme.css; this sheet loads after it, so .ah-feature
   wins on height, and the ground is invisible behind a full-bleed photo. */
.ah-feature.altha-hero { background:none; max-height:none; }
.ah-feature picture { display:contents; }

/* The statement band's picture, when the section has one. */
.ah-statement img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:50% 62%; }

/* Type on the two photographic bands.
   In the mockup the hero headline and the EXPANSION line simply INHERIT the
   white set on their section, because nothing there gave headings a colour.
   theme.css does: `h1,h2,h3,h4,h5,h6 { color: var(--color-ink) }`, an element
   selector that beats an inherited value no matter how specific the ancestor
   is. So both lines came out near-black on the photograph. They have to be
   told explicitly. */
.ah-feature h1,
.ah-feature .ah-links a,
.ah-statement h2 { color: var(--color-white); }

/* ── The header over the hero ───────────────────────────
   Nothing here any more. This used to restate the mockup's header values for
   the homepage's un-scrolled state — the nav as a right-aligned BLOCK rather
   than a row, 14px at 1.62 leading, and a booking link under a hairline —
   because the site header is shared with every page and was otherwise running
   theme.css's bar styling.

   Liv asked for the standard header back on 2026-09-18: the same bar every
   other page uses, still transparent over the full-bleed photograph. So the
   homepage no longer wants its own nav geometry, and the paint-only rules that
   remain all live in theme.css beside the rest of the header. */

/* Hidden everywhere. It is not a menu item, and the only thing that ever
   showed it was the homepage's vertical nav list, which went with the standard
   header on 2026-09-18. The booking call to action is the gold button in the
   page itself (.ah-go--primary), one per view — a second one in the bar would
   be the same ask twice. The markup stays in header.php against the day a bar
   CTA is wanted. */
.altha-nav__book { display:none; }

/* theme.css paints every <p> with the body colour, which beats the white this
   section inherits — so the hero's booking links and the divider between them
   came out near-black on the photograph. */
.ah-feature .ah-links { color: var(--color-white); }

/* The mockup's headline is two lines ("Breath, Energy," / "Transformation"),
   which is what makes it narrower and seat higher in the frame. It gets that
   from a manual line break in hand-written HTML; here the words come from one
   CMS field, so the measure is capped to wrap in the same place. Putting a
   line break into the Hero Heading field in wp-admin does the same thing and
   takes precedence.

   EM, NOT PER CENT — 62% did not deliver that two-line break at the widths
   this site is actually read at, and the note claiming it "still forces the
   three-line break" had it backwards: three lines was the fault, not the
   intent. A percentage cap is a share of the 18-column title block, which
   grows as 0.75vw - 56, while the headline grows as 6.5vw. The two do not
   track, and the type wins: "BREATH, ENERGY," measures 8.195em, which against
   62% of the column needs a 1767px viewport before it fits. So the hero was
   two lines on a large monitor and THREE on anything smaller — at 1280 it
   broke 318 / 339 / 716px, two stubs over a long line, which is what read as
   the headline needing tightening.

   In em the cap scales with the type instead of against it, so the break is
   the same at every width. 9.5em is 790px at 1280 and 912px at 96px type,
   inside the column both times.

   THE SAFE BAND HAS MOVED ON EVERY FACE SWAP SO FAR and 9.5em has survived
   all three by luck rather than by design, so derive it again before changing
   the cap — do not treat "it was fine before" as evidence.

     setting                  first line   whole line   safe band
     Jost, capitals             8.195em      16.78em    8.3 - 11.8em
     Jost, sentence case        6.505em      13.03em    6.6 - 12.9em
     Playfair, sentence case    6.956em      14.32em    7.0 - 14.3em
     MERRIWEATHER, sentence     7.270em      15.20em    7.3 - 15.1em

   The band's floor is the first line's width ("Breath. Energy.") and its
   ceiling is the whole headline's, above which the second line pulls up and
   the hero becomes one line — measured with uharfbuzz (real shaping and
   kerning, not summed glyph advances) at wght 400. Merriweather is wider
   again than Playfair — a text serif with generous sidebearings, not built to
   be set tight — so the band moved up for the second swap running. 9.5em
   still sits inside it, comfortably this time rather than by luck: it is
   2.2em clear of the floor and 5.6em clear of the ceiling, the widest margin
   any face here has left it.
   Re-derive if the Hero Heading field is rewritten — or just put the break in
   the field, which overrides all of this. */
.ah-feature h1 { max-width: 9.5em; }

/* ── Hero on a phone ──────────────────────────────────────────────
   The mockup keeps .ah-feature__title at 13 of 24 columns at EVERY width, so
   on a 390px screen the headline and the two booking links share about 190px
   and "Book a treatment | Join a group event" breaks over two lines. The
   mockup has the same fault; it was only ever looked at wide.

   Below 900px the title takes the full measure, the headline drops its
   two-line cap (it wraps on its own at that width), and the links are held on
   one line. */
@media (max-width:899px) {
  .ah-feature__title { grid-column:1 / -1; }
  .ah-feature h1 { max-width:none; }
  .ah-feature .ah-links { flex-wrap:nowrap; white-space:nowrap; gap:10px; }
}

/* ── Where theme.css's element rules still reach the homepage ─────
   theme.css styles bare elements site-wide, and an element rule beats a value
   the page merely INHERITS from a section. The mockup relied on inheritance in
   several places, so those have to be restated here. Found by auditing every
   element-only rule in theme.css against the mockup's own values, after the
   same trap had caused three separate faults.

   1. body carries letter-spacing:0.01em. The mockup's does not, so every line
      on the page was fractionally wider than the approved design. */
.ah-home { letter-spacing: normal; }

/* 2. NOTHING LEFT TO RESTATE — three rules were DELETED here on 2026-09-24,
      not left as no-ops, because a no-op that names --tracking-caps is exactly
      how that token would find its way back onto a heading.

      h1-h6 used to arrive uppercased and tracked --tracking-caps site-wide.
      .ah-begin__text h2 was the one heading exempted from it, with
      text-transform:none and letter-spacing:normal — "Not sure where to
      begin?" was shouting on staging — and .ah-card h3 and .ah-events__body h2
      were opted back IN to the tracking, having been left untracked by the
      mockup.

      Sentence case is the site-wide default now, so the first of those is what
      every heading already gets and the other two would re-apply a figure
      derived from capitals to lowercase. See the h1-h6 rule in theme.css. */

/* 3. Word spacing on the display lines. ONE TARGET, RE-SOLVED PER FACE: a word
      gap of about .55 of the x-height, which is where display text wants it.
      The value has now been every sign, which is the point — it is not a house
      preference for tight words, and carrying the previous number across a
      face swap has been wrong every single time:

        face                space    x-height   natural   ships
        Josefin Sans        .316em    .378em     0.84     -0.10em
        Jost                .300em    .460em     0.65     -0.05em
        Playfair Display    .249em    .515em     0.48     +0.035em
        MERRIWEATHER        .237em    .555em     0.43     +0.07em

      Playfair was the first face here whose space needed OPENING, and
      Merriweather needs opening further still — not because its own space is
      unusually tight (.237em is close to Jost's .300em) but because its
      x-height is the largest yet, so the 0.55x target it is measured against
      is bigger too. Carrying Playfair's +0.035em over would have landed
      .272em, 0.49 of the x-height — under target and visibly tighter than the
      running text beside it. The same figure is on the h1-h6 rule in
      theme.css and the two move together, always.

      Leading is 1.17 across the role, up from Playfair's 1.05 — this is the
      one place the increase in --leading-heading (see theme.css) had to be
      copied by hand rather than inherited, because every selector in this
      role sets its own line-height literal instead of reading the token (most
      of them are not headings, so they cannot read --leading-heading, which
      only h1-h6 gets). Merriweather's ink span is 1.089em, so 1.17 clears it
      by .081em — close to the .078em every face before it was tuned to.
      Under Jost the same role cleared by only .030em, three times tighter
      than intended, and this comment used to carry a live warning about it;
      retired rather than deleted, because the risk returns the moment a
      short-clearance face does.
      Applies to every display line, not just the intro; the ratio does not
      change with size, since both word-spacing and line-height are in em.

      .ah-begin__text h2 was in this list and came OUT on 2026-09-24: headings
      are sentence case site-wide now and h1-h6 carries the same figure, so
      naming it here was a duplicate of the default rather than an exception
      to it. The four that remain are a p and three blockquote-ish roles, none
      of which the heading rule reaches. */
.ah-prose--lead p:first-child,
.ah-pull,
.ah-safe__line,
.ah-words blockquote { word-spacing: 0.07em; }

/* ── The alchemist section on a phone ─────────────────────────────
   (The photograph used to bleed off the right edge, so this block once had to
   restore .ah-story .ah-grid's right padding when it stacked. It no longer
   bleeds and the grid never loses that padding.) */
@media (max-width:860px) {
  /* Stacked, the picture runs the full width of the page, so the desktop 2:3
     would be nearly 600px tall on a phone. Held at 4:3 there. */
  .ah-story__photo img { aspect-ratio: 4/3; }
}

/* The pulled line is a <p>, so theme.css paints it the body colour and sets
   its leading; both have to be restated. */
.ah-pull, .ah-safe__line { color: var(--color-ink); }
.ah-pull, .ah-safe__line { line-height: 1.17; }

/* ── Values the mockup carried as inline styles ──────────────────
   These were written on the elements in the mockup's HTML, not in its
   stylesheet, so extracting the CSS lost every one of them. The rail's top
   margin is the gap between the services copy and the cards, which is why
   they ran together. <figure> also carries a browser default margin that the
   mockup cancelled inline. */
.ah-rail { margin-top: clamp(36px, 4vw, 60px); }
/* .ah-head already spans the full grid; the mockup restated it inline inside
   .ah-story because that grid is declared on the section rather than the row.
   Stated here instead — see the note on inline styles in CLAUDE.md. */
.ah-story .ah-head { grid-column: 1 / -1; }
.ah-story__photo,
.ah-events__photo,
.ah-words__inner { margin: 0; }
/* The <figure> reset above is a shorthand and sits at the end of the file, so
   it also wiped the mockup's own stacked spacing at line 90 — the 36px that
   separates the copy from the photograph once they stack. Restated after it. */
@media (max-width:860px) {
  .ah-story__photo { margin-top: 36px; }
}

/* ── One link treatment for the whole page ────────────────────────
   Every text link is underlined in Liv's logo gold at the same weight as the
   nav marker over the hero: 1px, four pixels clear of the baseline. The mockup
   underlined only on hover and only in the current colour; this is Chris's
   call and it gives the page one consistent signal for "this is a link".

   The card is a link as a whole now, so it must NOT take the underline — only
   the "Book ..." line inside it does, which is a <span> and therefore needs
   the rule by class rather than by element. */
.ah-home .ah-links a,
.ah-home .ah-prose a,
.ah-home .ah-begin__side a,
.ah-go {
  text-decoration: underline;
  text-decoration-color: var(--color-gold);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}
/* The whole-card link: no underline on the card itself, and the type inside
   stays ink. */
a.ah-card, a.ah-card:hover { text-decoration: none; color: var(--color-ink); }
a.ah-card .ah-go { color: var(--color-ink); }

/* Hover dims the type and leaves the gold rule in place, rather than swapping
   in a second underline. */
.ah-home .ah-prose a:hover,
.ah-home .ah-begin__side a:hover,
a.ah-card:hover .ah-go { color: var(--color-muted); }
.ah-feature .ah-links a:hover { color: var(--color-on-dark); }

/* The closing link carried a solid ink rule of its own; it joins the rest. */
.ah-begin__side a { border-bottom: 0; padding-bottom: 0; }

/* A card is a big target, so it gets a visible focus ring like any control. */
a.ah-card:focus-visible { outline: 2px solid var(--color-ink); outline-offset: 4px; }

/* The figure caption under the story photograph: the mockup's leading value
   (it inherits 1.5 from the page otherwise). The mockup's own 46ch cap is
   gone (2026-09-29, Chris: full photo width, not an early wrap) — it was
   sized against the mockup's narrower photo and had long since fallen well
   short of the story photo's current 11-column width, wrapping a caption
   that had plenty of room left. The figure itself is already the photo's
   width, so a plain block caption fills it with no cap needed. */
.ah-story__photo figcaption { line-height: 1.45; }

/* ── Components the inner pages need ──────────────────────────────
   The homepage does not use these shapes, but Reiki, SOMA Breath, About,
   Services, Events and Contact all do. Same scale as everything above:
   18px reading text, 13.5px meta, 26-32px section titles, hairlines rather
   than boxes.
   "NOTHING HERE INTRODUCES A CARD, A RADIUS OR A SHADOW" was true until
   2026-09-25: .ah-pair > div is now a filled, rounded card (see its own note
   below), off a reference Chris asked to match. It remains true of
   everything else here — .ah-list, .ah-facts, .ah-faq, .ah-form, .ah-split —
   which are still ruled or bare, not boxed. */

/* A page opener for a page with no photograph yet: the title and one line on
   the page ground, under the ordinary white header bar. */
.ah-pagehead { padding-top: clamp(40px,5vw,72px); }
.ah-pagehead__inner { grid-column: 1 / -1; }
@media (min-width:900px) { .ah-pagehead__inner { grid-column: 1 / span 14; } }
.ah-pagehead h1 {
  font-family: var(--font-family-display);
  font-size: clamp(34px,3.4vw,52px);
  line-height: var(--leading-heading);
}
.ah-pagehead__text {
  margin-top: 18px; font-size: 16px; line-height: 1.6; max-width: 46ch;
  color: var(--color-body);
  /* altha_prose() can now emit more than one <p> (Hero Subtext became a real
     'html' field) — grid + gap spaces them, same device as .ah-prose, at a
     smaller gap suited to a short subtext. */
  display: grid; gap: 10px;
}
.ah-pagehead__text p { margin: 0; }

/* ── The inner-page opener ───────────────────────────────
   The inner pages used to open with the homepage's own hero — the very same
   .ah-feature.altha-hero, so Reiki and About arrived at a full viewport of
   photograph with the headline over its lower left and the nav floating on the
   picture, exactly as the front page does. Nothing told a visitor they had
   left the homepage.

   This keeps type on the photograph, which is what the page wanted, but makes
   it a different OBJECT: roughly half the height, and held inside the page
   margin so the white page frames it on three sides. It reads as a picture on
   a page rather than as the page itself. The header above it is the ordinary
   white bar — has-photo-hero is front-page only now, see functions.php.

   Chosen on 2026-09-18 over a full-bleed band under the title, and over a
   split with the photograph bleeding off the right edge. Chris's call. */

.ah-opener { padding-top: clamp(24px,3vw,40px); }

.ah-opener__cover {
  position: relative; margin-inline: var(--altha-gutter);
  height: clamp(380px,42vw,560px); overflow: hidden; border-radius: 16px;
  display: flex; align-items: flex-end;
}
/* Fill rules live on <picture>, not the <img>, so picture can carry its own
   height/radius/scrim independent of .ah-opener__cover on mobile, where cover
   becomes a plain auto-height wrapper around three stacked children (photo,
   label panel, content) rather than the single clipped photo box it is here.
   Desktop is unaffected: picture absolutely fills cover exactly as img did. */
.ah-opener__cover picture {
  position: absolute; inset: 0; display: block;
}
.ah-opener__cover picture img {
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 40%;
}
/* Aimed at the type. The homepage's scrim above darkens the TOP and the LEFT
   while its headline sits bottom-left, so the copy there is carried by a
   gradient that has already fallen to about 0.28 by the time it reaches the
   words. This one ramps from the bottom, where the words actually are. */
.ah-opener__cover picture::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  /* The second layer is the honey tint at 6% (2026-09-25), the same colour as
     --ah-photo-tint; a custom property cannot carry an alpha into a gradient
     stop without color-mix, so it is written out. It sits under the scrim. */
  background:
    linear-gradient(0deg,
      rgba(10,14,10,.62) 0%, rgba(10,14,10,.42) 18%, rgba(10,14,10,.2) 34%,
      rgba(10,14,10,.06) 48%, rgba(10,14,10,0) 62%),
    linear-gradient(color-mix(in srgb, var(--ah-photo-tint) 6%, transparent),
                    color-mix(in srgb, var(--ah-photo-tint) 6%, transparent));
}
.ah-opener__inner {
  position: relative; z-index: 1;
  /* No max-width on the block any more (it was 46ch, 441px): the buttons in the
     links row need room to sit side by side, and a 46ch box forced the gold
     button and the text beside it to stack on desktop (Reiki, 2026-09-25). The
     heading keeps its measure below instead, and the description its own 42ch. */
  padding: clamp(24px,3vw,40px);
}
/* Desktop (and everything above 767px): dissolve the two mobile-only wrapper
   divs so kicker/h1/text/links render as one flat, bottom-anchored stack —
   byte-identical to the markup that shipped before .ah-opener__panel and
   .ah-opener__content existed. Nothing below this rule had to change. See the
   mobile block at the bottom of this section for what replaces it under
   767px. */
.ah-opener__panel,
.ah-opener__content { display: contents; }

.ah-opener h1 {
  max-width: 365px;   /* the block's old 46ch less its padding, so the title wraps as it did */
  font-family: var(--font-family-display);
  font-size: clamp(34px,3.4vw,52px);
  line-height: var(--leading-heading);
}
.ah-opener__text {
  margin-top: 14px; font-size: 16px; line-height: 1.5; max-width: 42ch;
  /* Same reason as .ah-pagehead__text: altha_prose() can emit more than one
     <p> now that Hero Subtext is a real 'html' field. */
  display: grid; gap: 10px;
}
.ah-opener__text p { margin: 0; }
.ah-opener__inner .ah-links { margin-top: 18px; }
/* theme.css gives h1-h6 an element-level colour and does the same for <p>. An
   element rule beats a colour merely INHERITED from the section, however
   specific the ancestor — the same trap the .ah-feature restatements above
   document. Both have to be said again here.
   .ah-opener__text p is the same trap AGAIN, one level deeper: since Hero
   Subtext started rendering through altha_prose_line() (wpautop), the text
   lives in a real <p> INSIDE .ah-opener__text rather than .ah-opener__text
   being the <p> itself, so theme.css's bare `p` rule now matches that inner
   <p> directly and wins over the white the outer div only passes down by
   inheritance — exactly what the comment above warns about. */
.ah-opener h1,
.ah-opener__text,
.ah-opener__text p,
.ah-opener .ah-links,
.ah-opener .ah-links a,
.ah-opener .ah-kicker { color: var(--color-white); }

/* ── Mobile: H1 stays a label over the photo, the rest drops below ──────
   Chris, 2026-09-29: the full text block overlaid on a short mobile photo
   was cramped and low-contrast. Fix: the kicker+h1 become a cream label
   (the same device the service cards use for category+title) pinned to the
   photo's bottom edge; the description and links move into a normal block
   below it, on the same cream ground, so the two read as one shape rather
   than two unrelated pieces.

   767px matches the mobile art-directed crop already in altha_page_opener()
   (the <source media="(max-width:767px)"> swap) — kept in step so both
   mobile-only treatments change at the same width.

   THE PHOTO'S VISIBLE HEIGHT MUST NOT DEPEND ON HOW MUCH TEXT IS IN THE
   LABEL. Three earlier versions of this got the pin wrong, and this one
   found a real problem the other two didn't — worth recording so a fourth
   attempt doesn't repeat any of them:

   1. position:absolute against .ah-opener__cover — wrong, because cover's
      mobile height is auto (photo + panel + content all stacked), so
      "bottom" on the absolutely-positioned label resolved against the
      bottom of the CONTENT block once the description ran long enough, not
      against the photo at all.
   2. A fixed -40px margin-top pulling the label up over the previous
      sibling (the photo) — read at the time as "no overlap at all," so it
      was replaced before establishing whether the OFFSET was actually the
      problem, rather than something else. It wasn't the offset.
   3. .ah-opener__cover as display:grid, photo and label sharing one row,
      the label pinned to the row's bottom edge with align-self:end. This
      fixed the pin, but introduced a worse fault: a grid row sized to the
      photo's height, with the label sharing it, means a TALLER label
      (Reiki's heading is the longest on the site and wraps to two lines
      where shorter ones fit on one) simply COVERS MORE OF THE PHOTO from
      the bottom — the photo's underlying box is identical everywhere, but
      how much of it stays visible isn't, because it scales inversely with
      the label's own content length. Chris caught this by swapping Reiki's
      photo for one used elsewhere and finding it "still less deep" — proof
      it was never about the photo.

   The fix: back to a fixed-height photo and a margin-based pull-up, like
   attempt #2, but with the overlap amount understood correctly this time —
   it is a CONSTANT, not a guess at the label's total height. The label
   overlaps the photo by exactly this many pixels NO MATTER how tall its
   own content makes it: a short one-line label mostly hangs below the
   photo with a small bite taken out of the bottom of it; a tall two-line
   one hangs further below, but bites the SAME amount off the photo, never
   more. This is what a plain negative margin gives you for free — it moves
   the label up by a fixed amount, full stop, regardless of the label's own
   height — which is exactly the property grid's shared-row/align-self
   approach did not have. */
@media (max-width: 767px) {

  /* .ah-opener__inner swaps places with the pair below: on mobile IT
     dissolves instead of them, so .ah-opener__panel and .ah-opener__content
     stop being flattened away (the shared rule above makes them
     display:contents unconditionally, for desktop) and become real boxes —
     otherwise their background/padding/margin never render at all, since an
     element with display:contents has no box of its own to paint. This is
     what shipped broken the first time: .ah-opener__inner was overridden
     here but .ah-opener__panel/.ah-opener__content were never reset back
     off display:contents, so everything stayed flattened and the panel had
     no box to show. */
  .ah-opener__inner { display: contents; }
  .ah-opener__panel,
  .ah-opener__content { display: block; }

  .ah-opener__cover {
    display: block; height: auto;
    overflow: visible; border-radius: 0;   /* radius moves to the photo itself, below */
  }
  .ah-opener__cover picture {
    position: relative; inset: auto; display: block;
    height: clamp(280px, 62vw, 380px);
    overflow: hidden; border-radius: 16px;
  }
  .ah-opener__cover picture::after {
    /* Scaled back from the desktop scrim: the type no longer needs contrast
       from it, since it sits on opaque cream now. Kept only for the photo's
       own mood — easy to restore to full strength or drop, judge on staging. */
    background:
      linear-gradient(0deg, rgba(10,14,10,.3) 0%, rgba(10,14,10,.12) 32%, rgba(10,14,10,0) 55%),
      linear-gradient(color-mix(in srgb, var(--ah-photo-tint) 6%, transparent),
                      color-mix(in srgb, var(--ah-photo-tint) 6%, transparent));
  }

  /* Full width, square top corners (it starts flush against the photo — see
     .ah-opener__content below for why the radius belongs at the BOTTOM of
     the coloured section, not here). margin-top is the CONSTANT overlap:
     however tall the label's own content makes it, exactly 44px of it
     bites into the photo — never more, regardless of a one-line vs a
     two-line heading.

     44px, NOT A ROUNDER NUMBER: it has to stay BELOW the shortest label this
     component can render, or .ah-opener__content — which starts exactly
     where the label ends, they are plain adjacent flow siblings with
     nothing between them — starts ABOVE the photo's bottom edge instead of
     at it, intruding into the photo by the difference. That shipped once,
     on every page whose label is shorter than the overlap constant (no
     kicker, a one-line heading — the minimum is roughly 60px: one line at
     this size, ~30px, plus the label's own 30px of padding), and read as a
     stray line between the heading and the description. Reiki's kicker +
     two-line heading was never under this, which is why it was the one page
     that looked right. Keep this constant comfortably under that ~60px
     floor if the label's padding or font-size ever change. */
  /* The quote band's COLOUR, flat (2026-10-15): no picture on the opener, and
     the SAME value on both pieces so they read as one shape with no join.
     --ah-texture-flat is the band's base colour, flat. */
  .ah-opener__panel {
    position: relative; z-index: 2;
    margin-top: -44px;
    background: var(--ah-texture-flat);
    border-radius: 0;
    padding: 14px 18px 16px;
  }
  .ah-opener__content {
    background: var(--ah-texture-flat);
    border-radius: 0 0 16px 16px; /* the surface tier, matching the photograph above it */
    padding: 6px 18px clamp(22px,6vw,30px); /* panel already carries 16px of its own bottom padding */
  }

  .ah-opener .ah-opener__panel h1 {
    max-width: none;
    font-size: clamp(24px, 7vw, 30px);      /* new value, no prior mobile baseline — check on staging */
  }
  .ah-opener .ah-opener__panel .ah-kicker {
    font-size: 12px; letter-spacing: .16em; text-transform: uppercase;
    margin: 0 0 4px;
  }
  .ah-opener__content .ah-opener__text { margin-top: 0; max-width: none; }
  .ah-opener__content .ah-links { margin-top: 14px; }

  /* Off the photo now, onto cream — undo the desktop blanket-white rule
     above. Selectors here are deliberately MORE SPECIFIC than their desktop
     counterparts (.ah-opener .ah-opener__panel h1 beats .ah-opener h1, etc.)
     so this wins on specificity, not on merely being declared later in the
     file — see "Specificity ties" elsewhere in this project's notes for why
     that distinction has mattered before. */
  .ah-opener .ah-opener__panel h1 { color: var(--color-ink); }
  .ah-opener .ah-opener__panel .ah-kicker { color: var(--color-muted); }
  .ah-opener .ah-opener__content .ah-opener__text,
  .ah-opener .ah-opener__content .ah-links,
  .ah-opener .ah-opener__content .ah-links a { color: var(--color-ink); }
  /* The desktop white rule now also names .ah-opener__text's inner <p> (see
     that rule's comment — the same "element rule beats inherited colour"
     trap, one level deeper), and that rule carries no breakpoint, so without
     this it would win here too, on the cream ground where text is ink.
     Higher specificity than the desktop rule's .ah-opener__text p, so it
     wins outright rather than relying on this block coming later in the
     file. */
  .ah-opener .ah-opener__content .ah-opener__text p { color: var(--color-ink); }
}

/* The story block flipped: photograph off the LEFT edge, copy on the right.
   Alternating sides is how the services page reads as a sequence rather than
   a list of identical rows. */
.ah-story--flip .ah-grid {
  /* Dense packing, because the copy comes FIRST in the markup and sits in the
     right-hand columns here. Under the default sparse flow the grid cursor has
     already passed column 15 by the time it reaches the photograph, so a
     picture asking for column 1 could only go on the next row — measured on
     staging, the flipped service had its text at y=1514 and its photograph at
     y=2089, a row below. Dense lets it back-fill the space beside the copy,
     and still works if the section has a full-width head above it. */
  grid-auto-flow: dense;
}
@media (min-width:900px) {
  .ah-story--flip .ah-story__photo { grid-column: 1 / span 11; }
  .ah-story--flip .ah-story__text  { grid-column: 14 / -1; }
}
/* The flip stacks at 900px, not the 860 the unflipped block and .ah-events use,
   so between 861 and 899 the unflipped photo is sticky and the flipped one is
   stacked. The flip's sticky therefore starts at 900. */
@media (min-width:900px) {
  .ah-story--flip .ah-story__photo { position: sticky; top: 7rem; align-self: start; }
}
@media (max-width:899px) {
  /* Stacked: the sticky rule above is scoped to 861px and up, and the flip
     stacks lower, so the flipped photo must be put back to normal flow. */
  .ah-story--flip .ah-story__photo { position: static; }
}

/* The spaces-left note, inline after an event date, and the sold-out line
   that replaces the booking link. Both are meta type, not badges — the old
   pill sat on the photograph in the accent colour that no longer exists. */
.ah-spots { color: var(--color-muted); }
.ah-soldout { margin-top: 18px; font-size: 13px; font-weight: 700; color: var(--color-muted); }

/* A session title inside a story block. Smaller than a page heading, larger
   than a card name — the same step the events block uses. */
.ah-story__title {
  font-family: var(--font-family-display);
  font-size: clamp(28px,2.6vw,40px);
  line-height: var(--leading-heading);
  margin: 6px 0 18px;
}
/* Inside a story column the pieces stack with air between them; the prose grid
   handles its own paragraphs. */
.ah-story__text > .ah-facts,
.ah-story__text > .ah-list { margin-top: 22px; }
.ah-story__text > .ah-go { margin-top: 18px; }
/* The signature under About's "My Story" biography (2026-09-29). Script
   faces read thin and fragile at body-copy sizes — this is sized closer to
   a section head than to running text on purpose. letter-spacing:normal is
   explicit, not a default assumption: tracking a script face pulls its
   cursive joins apart, which would be a real fault here, not a cosmetic
   one — the letters are drawn to connect at zero tracking. */
.ah-story__signature {
  margin-top: 32px;
  font-family: var(--font-family-signature);
  font-size: clamp(32px, 4vw, 40px);
  font-weight: 400;
  line-height: 1;
  letter-spacing: normal;
  color: var(--color-ink);
}
/* A service's Length and Price. Space-between put the label at one edge of the
   text column and its value at the other, and on a wide screen that is 600px of
   nothing between two words. A fixed label column keeps the pair together and
   the values in a line. */
.ah-story__text .ah-facts li { display: grid; grid-template-columns: 6rem 1fr; justify-content: start; gap: 16px; padding: 6px 0; font-size: 16px; font-weight: 700; }
.ah-story__text .ah-facts span { font-weight: 400; }

/* A benefits list: a gold bullet per line, no rules and no tick icons.
   The bullet started inside .ah-pair on 2026-09-24 and was promoted to every
   .ah-list the same day, when the hairlines came off the whole site. One list
   treatment now, whether it is in a card or running the width of the page:
   Reiki, Services, Events, About, the builder's Ruled List and the benefit
   cards all read the same.

   The dot is the one round shape in this stylesheet. A bullet is a dot; the
   note about nothing being rounded is about panels and controls. */
.ah-list { list-style: none; margin: 0; padding: 0; }
.ah-list li {
  position: relative; padding: 0 0 0 20px;
  font-size: 15px; line-height: 1.5; color: var(--color-body);
}
.ah-list li + li { margin-top: 9px; }
.ah-list li::before {
  content: '';
  position: absolute; left: 0;
  /* Half the 1.5 line-height, less half the dot: centred on the first line,
     and in em so it holds if the list type is ever resized. */
  top: calc(0.75em - 3px);
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--color-gold-deep);
}
.ah-band--dark .ah-list li::before { background: var(--color-gold); }
/* Bullets on a card are indented (2026-09-25, Chris: indented bullets "look more
   natural and scan easier"). The bullet sits at the li's own left edge with its
   text 20px in, so indenting the LIST moves both together and wrapped lines
   still hang under the text. 16px. Scoped to the cards and to the story blocks'
   copy (the Services and Events session lists); a full-width list on the page
   keeps its bullet on the column edge. */
.ah-pair .ah-list,
.ah-story__text .ah-list,
.ah-events__body .ah-list { padding-left: 16px; }

/* Two equal columns on the grid, for pairs of short blocks.

   CREAM FILL, NO BORDER, RADIUS 16px — Chris's call, 2026-09-25, off a
   reference screenshot (Engage's homepage) he said reads warmer than this
   site's cards. This REVERSES the previous day's gold-edge decision, which is
   the SECOND departure in two days from "nothing here is a card" — and this
   one is the bigger one, because a border can still be argued as a rule
   rather than a panel; a filled, rounded block cannot. The old defence — "a
   section is a band, not a card… nothing else in this sheet is rounded,
   including the button" — is retired rather than left standing beside a rule
   that now contradicts it. See .ah-icon below and .ah-card__scoop above for
   the rest of this pass; all three move together.

   This is also the first card on the cream TINT rather than the page ground —
   a card living inside a cream section was already possible before this
   change (the tint is a `bg` any section can take) but nothing did it. Ink
   text on cream stays 14.11:1, comfortably clear.

   The grid stretches its items, so every card in a row shares the tallest
   one's height and the edges line up rather than running ragged. */
.ah-pair { grid-column: 1 / -1; display: grid; grid-template-columns: 1fr; gap: clamp(28px,3.4vw,48px); }
.ah-pair > div {
  padding: clamp(20px,2.2vw,30px);
  background: var(--color-surface);
  border-radius: 16px;
}
/* On the ink band a cream fill would sit oddly close to the forest behind it
   — barely any separation — so the card there stays a shade of forest itself,
   lifted just enough to read as its own surface: 90% forest, 10% white.
   NOT A COINCIDENCE that this lands on #41493C — it is the same texture-peak
   ground the footer's own colours were derived against (see --color-on-dark
   and --color-gold-light in theme.css), so the numbers below are consistent
   with figures already checked elsewhere rather than a fresh derivation:
   white text 9.36:1, gold icon fill 4.32:1, --color-on-dark-muted 4.52:1 —
   all clear. --color-gold-deep does NOT: 2.61:1 here, so it stays off this
   ground (.ah-icon already switches to plain gold on a dark band, below).
   Not a token: this is the one place a colour is mixed rather than named,
   because a named token for one lightened-forest card is not proportionate. */
.ah-band--dark .ah-pair > div { background: color-mix(in srgb, var(--color-forest) 90%, white 10%); }
/* The builder lets ANY section take the cream tint, including one carrying
   .ah-pair — nothing seeded does today, but the schema does not stop an
   editor doing it tomorrow, and a cream card on a cream section is invisible
   as a card: same value, no boundary at all. White is the only colour left
   that differs from both the tint and the ink band, so it is what the card
   falls back to there. */
.ah-band--tint .ah-pair > div { background: var(--color-white); }
@media (min-width:900px) { .ah-pair { grid-template-columns: 1fr 1fr; gap: clamp(30px,4vw,64px); } }
/* Three across, for a set of three short blocks. */
@media (min-width:900px) { .ah-pair--3 { grid-template-columns: repeat(3, 1fr); gap: clamp(30px,3vw,48px); } }
.ah-pair h3 {
  font-family: var(--font-family-display); font-size: var(--ah-block-title);
  line-height: var(--leading-heading); margin-bottom: 10px;
}

/* The bulleted list that used to live here is the base .ah-list treatment
   now — see the note there. A card's list and a full-width list are the same
   object and no longer need telling apart. */

/* Forms (rewritten 2026-09-25, off the ruled hairline fields). Chris found the
   Contact page outside the revised design language, so the fields now speak it:
   filled, 12px-rounded, no border — the same fill-and-radius vocabulary as the
   cards. A field is the page ground nudged toward the tint, which is enough to
   find it (the pair cards work the same way); on focus it goes white with a 2px
   ink ring, which is the state cue and the real accessibility affordance. The
   label stays above in meta type. The old rule kept an underline "because
   without it there is no visible input"; the fill is what replaces it. */
.ah-form { display: grid; gap: 22px; }
.ah-form label { display: block; font-size: 13.5px; color: var(--color-muted); margin-bottom: 6px; }
.ah-form input:not([type="checkbox"]),
.ah-form select,
.ah-form textarea {
  width: 100%; padding: 14px 16px; font-size: 16px; line-height: 1.4;
  color: var(--color-ink);
  background: color-mix(in srgb, var(--color-surface) 45%, var(--color-background));
  border: 0; border-radius: 12px;
  transition: background-color .2s ease, box-shadow .2s ease;
}
.ah-form textarea { min-height: 9em; resize: vertical; }
.ah-form input:not([type="checkbox"]):focus,
.ah-form select:focus,
.ah-form textarea:focus { outline: 0; background: var(--color-white); box-shadow: 0 0 0 2px var(--color-ink); }
.ah-form__note { font-size: 13.5px; color: var(--color-muted); }

/* ── Contact ──────────────────────────────────────────────────────
   The form and the details sit side by side on the grid; both stack under
   900px. The details are ONE cream card (2026-09-25) — surface fill, 16px
   radius, the badge icon per row, label over value — the site's card language,
   where they were a bare facts list. The hours share the clock's row. */
.ah-split__main { grid-column: 1 / -1; }
.ah-split__side { grid-column: 1 / -1; margin-top: clamp(40px,5vw,64px); }
@media (min-width:900px) {
  .ah-split__main { grid-column: 1 / span 12; }
  .ah-split__side { grid-column: 15 / -1; margin-top: 0; }
}

/* A form's result message: a soft rounded panel now (was ruled top and bottom).
   The text keeps the success / error colour on a 10% tint of it. */
.ah-note { padding: 14px 18px; border-radius: 12px; font-size: 15px; margin-bottom: 22px; }
.ah-note--ok    { color: var(--color-success); background: color-mix(in srgb, var(--color-success) 10%, var(--color-background)); }
.ah-note--error { color: var(--color-error);   background: color-mix(in srgb, var(--color-error) 10%, var(--color-background)); }

/* The details card. */
.ah-contact { background: var(--color-surface); border-radius: 16px; padding: clamp(24px,2.6vw,36px); display: grid; gap: 24px; }
.ah-contact__row { display: flex; align-items: flex-start; gap: 16px; }
.ah-icon--sm { flex: none; width: 40px; height: 40px; margin-bottom: 0; }
.ah-icon--sm svg { width: 20px; height: 20px; }
.ah-contact__label { display: block; font-size: 12.5px; color: var(--color-muted); }
.ah-contact__value { display: block; font-size: 16px; line-height: 1.4; color: var(--color-ink); margin-top: 2px; }
.ah-contact__hours { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; flex: 1; }
.ah-contact__hours li { display: grid; grid-template-columns: minmax(7rem, auto) 1fr; gap: 16px; font-size: 16px; line-height: 1.4; color: var(--color-ink); }
.ah-contact__hours li span:first-child { color: var(--color-muted); }

/* Consent checkboxes sit beside their wording, which stays meta-sized. */
.ah-consent { display: grid; gap: 14px; }
.ah-consent label { display: flex; align-items: flex-start; gap: 12px; cursor: pointer; font-size: 13.5px; line-height: 1.5; color: var(--color-body); margin: 0; }
/* A styled box in the same fill and a 6px radius (it is a small control),
   ink with a gold tick when checked. The tick is an inline SVG data URI. */
.ah-consent input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; flex: none;
  width: 22px; height: 22px; margin-top: 1px; border: 0; border-radius: 6px;
  background-color: color-mix(in srgb, var(--color-surface) 45%, var(--color-background));
  background-repeat: no-repeat; background-position: center; background-size: 14px;
  cursor: pointer; transition: background-color .2s ease;
}
.ah-consent input[type="checkbox"]:checked {
  background-color: var(--color-ink);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23C2B067' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12 5 5 9-10'/%3E%3C/svg%3E");
}
.ah-consent input[type="checkbox"]:focus-visible { outline: 2px solid var(--color-ink); outline-offset: 2px; }
.ah-consent a { text-decoration: underline; text-decoration-color: var(--color-gold); text-decoration-thickness: 1px; text-underline-offset: 4px; }
.ah-required { color: var(--color-error); }

/* Questions and answers: the question in the display face, separated by air
   rather than by a rule. */
.ah-faq { display: grid; gap: clamp(22px,2.4vw,32px); }
.ah-faq > div { padding: 0; }
.ah-faq h3 {
  font-family: var(--font-family-display); font-size: 22px;
  line-height: var(--leading-heading); margin-bottom: 8px;
}
/* 50ch, not the 70ch this carried: 70ch is 94 characters, well past the 85
   tolerance. ch scales with font-size, so 50ch is 67 characters here at 15px
   exactly as it is at 18px elsewhere. */
.ah-faq p { font-size: 15px; line-height: 1.6; color: var(--color-body); max-width: 50ch; }

/* ── Band colours, for the flexible page builder ──────────────────
   The builder lets whoever edits a page pick white, cream or forest per
   section, so that choice has to survive into this layout. It maps onto the
   palette rather than onto three new colours: the page ground, the one tint,
   and the ink band the footer uses.

   A section is a band, not a card — the colour runs the full width and the
   grid inside it is the same grid as everywhere else. */
/* THE GAP ABOVE A BAND IS A MARGIN, NOT THE NEXT SECTION'S PADDING.
   .ah-section carries padding-top and no padding-bottom, so the rhythm
   between two white sections is made entirely by the LOWER one's padding.
   The moment that lower section is a band, that padding moves inside the
   colour — so the coloured rectangle butted straight up against the last line
   of the section above it, while the white section BELOW the band still set
   its own full 88-140px. Nought above, 140 below, which is what reads as the
   band crowding the copy over it.

   The margin puts the white space back outside the colour. This is what
   .ah-words has always done — margin-top for the gap, padding-block for the
   air inside — and these two now match it rather than contradicting it. */
.ah-band--tint,
.ah-band--dark { margin-top: clamp(88px,9vw,140px); }
.ah-band--tint { background: var(--color-surface); padding-block: clamp(56px,6vw,96px); }
/* --color-forest, not --color-ink. The builder's "Forest" resolved to
   #141414, which is black, which is what it looked like. The class keeps its
   --dark name: it is still the dark band, and the stored value the builder
   writes is already 'forest'. */
.ah-band--dark { background: var(--color-forest); padding-block: clamp(56px,6vw,96px); }
.ah-band--tint.ah-section,
.ah-band--dark.ah-section { padding-top: clamp(56px,6vw,96px); }
/* EVERY piece of type a banded section can contain, not just the ones the
   homepage happened to use. The benefits grid came out black on black because
   this listed p, h2 and h3 — and a card's lines are <li>, which .ah-list
   paints --color-body. The four after it are the same fault waiting in the
   Copy and Photograph, Two Columns and Card Rail sections: a pulled line, a
   display line and a whole card are all ink by default, and the builder lets
   any of them be set to forest.

   Bare element selectors on purpose. A band should darken whatever is put
   inside it, and naming components one at a time is how this list came to be
   missing four of them. */
.ah-band--dark,
.ah-band--dark .ah-head h2,
.ah-band--dark h2, .ah-band--dark h3,
.ah-band--dark p,
.ah-band--dark li,
.ah-band--dark .ah-pull,
.ah-band--dark .ah-safe__line,
.ah-band--dark a.ah-card,
.ah-band--dark a.ah-card:hover { color: var(--color-on-dark); }
/* The head, the FAQ rows, the lists and the detail rows all carried a
   border-colour for the dark band. None of them has a border any more, so the
   whole group went with the rules themselves rather than being left to point
   at nothing. The bullet takes its own colour on a band — see .ah-list. */
/* The quiet type: 7.75:1 on the ink, so it stays quiet without disappearing.
   .ah-intro__aside, .ah-kicker and figcaption were all --color-muted, which is
   2.76:1 there — legible only if you already knew what it said. */
.ah-band--dark .ah-cat,
.ah-band--dark .ah-small,
.ah-band--dark .ah-facts span,
.ah-band--dark .ah-intro__aside,
.ah-band--dark .ah-kicker,
.ah-band--dark figcaption { color: var(--color-on-dark-muted); }
.ah-band--dark .ah-go,
.ah-band--dark a { color: var(--color-on-dark); }
.ah-band--dark .ah-go:hover,
.ah-band--dark a:hover { color: var(--color-gold); }

/* A banded .ah-story needs NO special case any more, and this rule has to go
   rather than stay as a no-op: it would cancel the margin the band block above
   now sets, and put a banded story back to hugging whatever is over it.

   It read: a story block already carries margin-top of its own, so a banded
   one "got both and sat in roughly twice the space it should". Margin plus
   padding is the correct arrangement — .ah-words has always been built that
   way — so the diagnosis was the wrong way round. What was actually
   inconsistent was that a banded .ah-section got no margin at all. Both come
   from one declaration now, and .ah-story's own margin-top is the same clamp,
   so nothing is doubled. */

/* The closing link's own 1px underline is cleared further up, so there is no
   border here to recolour for the dark band. The rule that did went with the
   rest of the hairlines on 2026-09-24. Its gold text-decoration is what marks
   it as a link now, on either ground. */

/* An icon a page builder chose, above its heading — now a FILLED CIRCLE
   BADGE rather than a bare glyph, 2026-09-25, the same Engage reference and
   the same day as .ah-pair's cream fill above. The two changes are linked:
   this used to be a bare gold glyph specifically BECAUSE .ah-pair > div was
   white — deep gold at 3.58:1 was chosen to clear 3:1 on that ground, and the
   note here already flagged that the SAME gold only reaches 2.74:1 on cream,
   "recorded here rather than worked around" because nothing put a card on the
   tint at the time. Something now does, so it has to be worked out rather
   than merely recorded: a plain gold circle on the new cream card measures
   1.66:1, well under the bar — no stroke width fixes that, the ground itself
   is too close to the gold.

   FOREST FILL, GOLD GLYPH. The badge is a solid --color-forest circle instead
   of a coloured glyph on the card's own ground: forest-on-cream is 9.78:1,
   miles clear, and it sidesteps the gold-on-cream problem entirely by not
   asking gold to carry the contrast — gold now only has to read against
   forest, at 5.90:1, which it does easily. On the ink band the badge inverts,
   the way the old bare-glyph version did: gold fill, ink glyph (8.51:1) — see
   the .ah-card__badge note above the card rail for the same fill on the same
   ground.

   Still decorative: every icon sits above a heading that says the same thing,
   so nothing is lost if the badge is not perceived. 3:1 is aimed at anyway,
   because it is also the line between "coloured" and "washed out" — and a
   52px filled circle clears it by more than a 1.5px stroke ever could. */
.ah-icon { display: flex; align-items: center; justify-content: center; width: 52px; height: 52px; margin-bottom: 14px; background: var(--color-forest); border-radius: 50%; color: var(--color-gold); }
.ah-band--dark .ah-icon { background: var(--color-gold); color: var(--color-ink); }
.ah-icon svg { width: 24px; height: 24px; }

/* ── Stacked, the photograph belongs to its title ─────────────────
   A service or an event is a card: title, picture, then the detail. Stacked
   at 860px the picture was landing after the booking button, four blocks
   below the name it belongs to, which read as an image that had come loose
   rather than one that belonged to the session above it.

   The figure is a SIBLING of .ah-story__text, so nothing inside the text
   column can be reordered around it by itself. `display:contents` on the text
   column lifts its children into the row, and the row becomes a flex column
   so `order` can put the picture between the title and the copy. Source order
   is untouched, so the reading order for a screen reader is still name → copy
   → detail, and the desktop grid above 860px is not involved at all.

   Scoped with :has(.ah-story__title) on purpose: that title is what makes a
   story block a session card. The homepage's story sections carry a section
   head and prose instead, and they keep the picture at the end, which is
   right for a narrative. */
@media (max-width:860px) {
  .ah-story:has(.ah-story__title) .ah-grid {
    display: flex;
    flex-direction: column;
    /* .ah-story .ah-grid sets align-items:start for the desktop columns. On
       the cross axis of a flex column that is horizontal, so every block
       would shrink to its own content width — the photograph's width:100%
       would collapse and .ah-list's rules would stop short of the margin. */
    align-items: stretch;
  }
  .ah-story:has(.ah-story__title) .ah-story__text { display: contents; }
  .ah-story:has(.ah-story__title) .ah-head { order: 0; }
  .ah-story:has(.ah-story__title) .ah-story__text > * { order: 3; }
  .ah-story:has(.ah-story__title) .ah-story__text > .ah-cat,
  .ah-story:has(.ah-story__title) .ah-story__text > .ah-story__title { order: 1; }
  /* The 36px that separated the copy from a trailing photograph is wrong now
     it sits under the title: the title's own 18px bottom margin is the gap
     above it, and the space it needs is below, before the copy resumes. */
  .ah-story:has(.ah-story__title) .ah-story__photo {
    order: 2;
    margin-top: 0;
    margin-bottom: 26px;
  }
}

/* ── A story block's heading is part of the centred copy ──────────
   The section head is a full-width first row and the copy a second row beside
   the photograph. Centring the copy in that second row (above) left the head
   parked at the top of the section with the copy floating mid-photograph a
   screen below it — a large gap between a heading and what it heads.

   Fix without touching markup: the photograph spans BOTH rows, the head is
   bottom-aligned in the first and the copy top-aligned in the second. The two
   auto rows share whatever height the photograph adds, so the pair sits
   together, centred against the picture, with the head's own bottom margin as
   the only gap. Scoped with :has(.ah-head) because a block with no head (the
   services sessions, which carry their title inside the copy) has one row and
   needs none of this. The flip's copy is on the right, so its head goes there.
   Desktop only, each at its own stacking breakpoint. */
@media (min-width:861px) {
  .ah-story:not(.ah-story--flip):has(.ah-head) .ah-head { grid-column: 1 / span 11; grid-row: 1; align-self: end; }
  .ah-story:not(.ah-story--flip):has(.ah-head) .ah-story__text { grid-row: 2; align-self: start; }
  .ah-story:not(.ah-story--flip):has(.ah-head) .ah-story__photo { grid-row: 1 / span 2; }
}
@media (min-width:900px) {
  .ah-story--flip:has(.ah-head) .ah-head { grid-column: 14 / -1; grid-row: 1; align-self: end; }
  .ah-story--flip:has(.ah-head) .ah-story__text { grid-row: 2; align-self: start; }
  .ah-story--flip:has(.ah-head) .ah-story__photo { grid-row: 1 / span 2; }
}

/* ── The primary action is a button, not a link ───────────────────
   Every other CTA on the page is a word with a gold rule under it, which is
   right for "read more about this" and wrong for the action a page exists to
   get: at 13px underlined, "Book this session" carried no more weight than
   "Ask about this session" sitting in its place, and "Send message" carried
   no more weight than the privacy-policy link directly above it.

   Gold fill, ink label. The measurements, because CLAUDE.md is otherwise
   explicit that gold is not a UI colour and this is the exception:

     ink #141414 on gold #C2B067   8.51:1   the label, comfortably AA/AAA
     gold #C2B067 on ground #F7F5F0  1.97:1  the button's EDGE. It carried a
                                             --color-gold-deep border for that
                                             until 2026-09-25, when Chris had
                                             every button border removed. The
                                             fill alone is under the 3:1 WCAG
                                             asks of a control boundary; the
                                             8.51:1 label is what identifies it

   45px tall with the padding below, so it also clears the 44px touch target
   the 13px text link never did. */
.ah-go--primary {
  display: inline-block;
  margin-top: 18px;
  padding: 13px 26px;
  font-family: inherit;
  font-size: 15px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  color: var(--color-ink);
  background: var(--color-gold);
  border: 0;
  border-radius: 12px;       /* same as .altha-btn and the card title labels */
  cursor: pointer;
  /* The services labels name their session, and those names come out of the
     Availability Manager, so their length is Liv's to set — "Book Reiki Reset
     (In Person)" is 28 characters uppercase. The button wraps to two centred
     lines rather than running past its column. */
  max-width: 100%;
}
/* .ah-go is in a grouped underline rule further up; a button does not take it. */
.ah-go--primary,
a.ah-card .ah-go--primary { text-decoration: none; color: var(--color-ink); }
/* Hover deepens the fill rather than dimming the label the way a text link
   does — the label stays ink and still measures 5.14:1 on it. */
.ah-go--primary:hover,
a.ah-card:hover .ah-go--primary { background: var(--color-gold-deep); color: var(--color-ink); }
.ah-go--primary:focus-visible { outline: 2px solid var(--color-ink); outline-offset: 3px; }
/* On a dark band .ah-go is painted --color-on-dark; white on gold is 2.17:1,
   so the button keeps its own label colour there. */
.ah-band--dark .ah-go--primary { color: var(--color-ink); }
/* The poller adds .altha-btn--soldout when an event fills up; that rule is
   !important in theme.css and greys the button out, which is what we want. */
/* .ah-form is a grid, so a blockified button fills the column — a full-width
   gold bar, and the one button on the site at a different width. Sized to its
   label like the rest, with a little more air than the 22px row gap gives the
   fields, because it is the end of the form rather than another row of it. */
.ah-form .ah-go--primary { justify-self: start; margin-top: 6px; }

/* ── Carousel: every card's button on the same line ───────────────
   The rail is a grid, so the cards already stretch to the tallest of them —
   but the button sat directly under its own deck, so a two-line deck and a
   four-line one put their buttons 40px apart and the rail read as ragged.
   The card becomes a flex column and the button takes the slack above it. */
.ah-card { display: flex; flex-direction: column; }
.ah-card .ah-go--primary {
  margin-top: auto;        /* absorbs the difference between cards */
  align-self: flex-start;  /* the card stretches; the button keeps its width */
}
/* On the tallest card the auto margin resolves to zero, so the block above
   the button carries the clearance instead of relying on it. */
.ah-card > *:has(+ .ah-go--primary) { margin-bottom: 18px; }

/* ── Texture on the two coloured blocks ───────────────────────────
   Liv's dawn meadow, high-passed to fine detail, on the quote band and the
   footer only. The FOOTER RULE IS SITE-WIDE — the footer is shared, and a
   textured one on the homepage against a flat one everywhere else read as a
   bug. The band rule is site-wide from 2026-09-25 — the quote band appears on
   several pages and was flat on all but the homepage. NOT on the page ground — it was tried there and did not work:
   the sections are white space and a texture across them reads as dirt
   rather than as tooth. The coloured blocks are where it has something to
   sit on.

   `screen` blending, and it is the right tool rather than a clever one.
   Screen only ever LIGHTENS: out = base + tex*(1 - base). Three consequences
   that between them remove every problem the earlier attempts had.

   It is LINEAR, so amplitude is one number per block and behaves as you
   expect. (Soft-light is not: it collapses as the backdrop approaches white,
   giving 0.9 levels out for +/-10 in on #F7F5F0, which is why a texture on
   the ground was invisible at any strength.)

   It SCALES ITSELF to the base. The same texture value lightens the near
   black footer 7.5x more than the mid-tone band, because (1 - base) is that
   much larger. So the two assets differ only in peak pixel value — 82 for
   the band, 25 for the footer — and each lands at the swing it wants.

   And on the band it CANNOT COST CONTRAST. --color-muted on --color-surface
   is 4.89:1 with only 0.39 above AA, and every earlier attempt was throttled
   by it. Lightening raises that figure, so the worst case on this band is
   the untextured 4.89 and the texture is free. Darkening was never viable
   there: 4% of it already lands on 4.52.

     block    base      worst text at the texture's peak        floor
     band     #E0DCD3   muted 4.89 (= flat; screen cannot cost)   4.5
     footer   #2C3526   gold-light 4.52, on-dark-muted 4.52,      4.5
                        white 9.37, gold 4.33 (border, 3.0)

   THE FOOTER ROW WAS RE-DERIVED on 2026-09-24, when the ground went from
   #141414 to --color-forest. Screen lightens by tex*(1 - base), so a lighter
   base swings LESS -- but it also starts closer to the type, and that second
   effect is the larger one: every figure here fell. --color-on-dark-muted had
   to go #A8A8A8 -> #B4B4B4 and the footer labels to --color-gold-light to stay
   over 4.5. The asset itself is unchanged and did not need rebuilding.

   `cover` + `no-repeat`, so each block shows ONE copy of the image. These
   are bounded blocks, not an endless page, so nothing tiles and there is no
   horizontal repeat to see. That also retires the whole class of problem
   that tiling caused — a tile whose row means vary stacks into stripes, and
   that is what banded the page twice.

   The assets are opaque greyscale, not RGBA. WebP stores alpha losslessly
   and noisy alpha does not compress: the same textures as RGBA came to
   538 KB, against 30 KB like this.

   FILENAMES CARRY A CONTENT HASH AND MUST. ALTHA_THEME_VERSION
   (functions.php:14-18) is max() of the mtimes of theme.css and layout.css
   only, so a fixed image name is never busted — this shipped three times as
   meadow-dawn.webp and the page kept rendering the FIRST version's bytes
   under the THIRD version's rules. Re-hash whenever the bytes change.

   To change strength, rebuild with a different peak pixel value. Measure in
   sRGB, never in linear light: a "grain" figure computed on linearised
   values reads five to eight times high near white, which is how a build
   that measured fine turned out to be invisible.               ── */
/* A LIFT ON THE LIGHT GROUNDS (2026-09-25, Chris: "a bit more of the background
   image coming through"). The band and footer texture was built for `screen` on
   the DARK green ground: its brightest pixel is 88/255 and its mean 4.5, so
   screened onto cream it moved the colour by about 4% at the very brightest
   flowers and was, for practical purposes, invisible. Both blocks are light now,
   so they use texture-light.*.webp instead: the same meadow, brightened
   (gamma .55) and remapped so its DARKEST point is 90% white and its brightest
   is pure white, blended with MULTIPLY — where the image is bright the ground is
   untouched, where it is dark the ground goes up to 10% deeper, so the grasses
   and flowers read as tone. Its mean is .913, so the base colour is the tint
   lifted toward white by the same amount, which lands the average back on
   --color-surface (a touch less warm) and leaves the meadow as the difference.
   Strength is the 0.10 the file was generated with; regenerate to change it. */
:root {
  /* THE WARM TINT, TRIALLED HERE TOO (2026-09-25, Chris: "I would like to know
     how that feels"): the same honey as the photographs, mixed into the base
     colour at --ah-texture-tint. Set that to 0% to take it out. */
  --ah-texture-tint: 8%;
  --ah-texture-base: color-mix(in srgb, var(--ah-photo-tint) var(--ah-texture-tint), color-mix(in srgb, var(--color-surface) 50%, white));
  /* The band's colour with no picture: the BASE itself, which is what the band,
     the quote strip and the footer show wherever the texture is light. (First built as the base
     darkened by the texture's mean, .913, and read as the old grey-cream.) */
  --ah-texture-flat: var(--ah-texture-base);
}
body .ah-words {
  background-color: var(--ah-texture-base);
  background-image: url(../images/texture/texture-light.15ddcb60.webp);
  background-size: cover;
  background-repeat: no-repeat;
  background-blend-mode: multiply;
}
.altha-footer {
  /* The footer wears the quote strip's texture and tint (2026-09-25), so the
     two read as one family. texture-footer.*.webp and texture-band.*.webp are no
     longer referenced; they stay in the theme, like the unused fonts. */
  background-color: var(--ah-texture-base);
  background-image: url(../images/texture/texture-light.15ddcb60.webp);
  background-size: cover;
  background-repeat: no-repeat;
  background-blend-mode: multiply;
}

/* ── Two links in a closing note stack, rather than running side by side ──
   .ah-begin__side is already `display:grid; gap:10px`, so its direct children
   stack — the CTA link above the note. But the note itself is a single <p>
   hard-coded in the templates (page-about.php:123 and six siblings), so any
   links an editor puts inside it are inline siblings and sit on one line
   however the field is formatted. That is why two links typed one above the
   other came out beside each other.

   Scoped with :has(a + a) so it only touches notes that actually carry two or
   more links. Notes that are a line of prose, or prose with one link, are
   untouched — several pages rely on that and a blanket rule would break them.

   line-height:0 on the container is doing real work: editors separate links
   with non-breaking spaces, which are NOT collapsible whitespace, so once the
   anchors are blockified each separator forms its own line box. Zeroing the
   container's leading gives those boxes no height, and the anchors restore
   their own. Without it there is a stray blank line between the links.  ── */
.ah-begin__side .ah-small:has(a + a) { line-height: 0; }
.ah-begin__side .ah-small:has(a + a) a { display: block; line-height: 1.4; }
.ah-begin__side .ah-small:has(a + a) a + a { margin-top: 10px; }

/* ── Video ────────────────────────────────────────────
   SELF-HOSTED, and it has to be. The site sends `frame-src 'none'` from
   functions.php and again from htaccess-snippet.txt, so a YouTube or Vimeo
   iframe is blocked outright; a file from our own origin falls under
   `default-src 'self'`. There is no media-src directive, which is the same
   thing said twice.

   NO ASPECT-RATIO AND NO object-fit. Every other picture on this site is
   cropped by its box — .ah-story__photo img forces 2/3, .ah-card .ah-img 3/4 —
   because a landscape can lose its edges without losing its subject. These
   videos are Liv talking to camera, shot on a phone, and nobody knows whether
   a given one is portrait or landscape. So altha_video_tag() writes the real
   width and height off the attachment metadata and the browser derives the
   ratio from them: the right box is reserved before the file loads, nothing
   shifts, and nothing is cropped. --unsized is the fallback for a file that is
   not in the Media Library and therefore has no metadata to read.

   The ink ground matters while the poster loads and behind any letterboxing
   the browser's own control bar leaves. White there reads as a broken image. */
/* HALF HEIGHT, and it is done as half WIDTH. Chris's call, 2026-09-24: the
   clips are phone footage and do not stand up to the size their column gives
   them. There is no height to set — altha_video_tag() writes the file's real
   width and height and the browser derives the ratio from them, so the element
   is sized entirely by its width. Halving that halves the height, at every
   viewport, and keeps the video at its own shape whether it was shot portrait
   or landscape.

   Do NOT do this by setting a height, and do not reach for aspect-ratio: both
   would crop or letterbox somebody mid-sentence, which is the thing the note
   above exists to prevent.

   The cap is released where each layout stacks, in the two media queries that
   already exist for that purpose — half the width of a phone is not a video,
   it is a thumbnail. */
.ah-video { width:100%; height:auto; max-width:50%; background:var(--color-ink); border-radius:16px; }
.ah-video--unsized { aspect-ratio:16/9; }

/* The story photographs no longer bleed (2026-09-25), so the video no longer
   needs the gutter handed back and its padding rules are gone. The slot is 9
   columns now, so the video takes three quarters of it rather than half. */
.ah-story__video .ah-video { max-width: 75%; }
@media (max-width:860px) {
  /* The figure is the full page width here, so the half-width cap would leave
     the video at about a phone's half — too small to watch. */
  .ah-story__video .ah-video { max-width: 100%; }
}

/* A video on its own, with the copy above it. Held to the same 16 of 24 the
   quote band uses rather than the full bleed: a 2:24 talking head across
   1900px of monitor is a wall, not a feature. */
.ah-video__full { grid-column:5 / span 16; margin:clamp(28px,3vw,44px) 0 0; }
.ah-video__full figcaption { margin-top:10px; font-size:13px; line-height:1.45; max-width:46ch; color:var(--color-muted); }
@media (max-width:1023px) {
  .ah-video__full { grid-column:1 / -1; }
  .ah-video__full .ah-video { max-width: 100%; }
}

/* ── A lead paragraph above a set of blocks ──────────────────
   .ah-pair is grid-column:1/-1 and so lands on the row below the intro by
   itself; the margin is only the air between them.

   LEFT, at column 1. It was taking .ah-intro__body's 9 / span 12, which is
   the offset the homepage intro uses to sit a paragraph against the RIGHT of
   its own section head — right for a single opening statement under a rule
   that runs the full width, wrong here, where the blocks underneath start at
   column 1 and the copy introduces them. Set flush with what it introduces.

   12 of 24 rather than the full bleed: .ah-prose p caps at 50ch, which is
   narrower than this at every width, so the span is really just a ceiling. */
.ah-pair__intro { grid-column: 1 / span 12; margin-bottom: clamp(28px,3vw,44px); }
/* .ah-intro__body's own stacking rule sits hundreds of lines above this one,
   so it cannot win on order — restated here or the intro would stay at 12
   columns on a phone. */
@media (max-width:1023px) { .ah-pair__intro { grid-column: 1 / -1; } }

/* ── Several short client quotes ─────────────────────────
   NOT the tinted band — that is .ah-words, the page gets one of it, and this
   runs on the page ground beside it. The shape is .ah-pair--3, so the columns
   and the stacking are already handled; all that is added is the type and a
   hairline over each, which is how every other list on this site is divided.

   The quote sits in the display face at the body size rather than at
   --ah-display-line. Six of these at 25.6px would be six large lines competing
   with the section's own head, and the display-line role is explicitly ONE per
   section (see the token's note at the top of this file). */
.ah-quotes figure { margin:0; }
.ah-quotes blockquote {
  margin:0;
  font-family:var(--font-family-display);
  font-weight:var(--ah-display-weight);
  font-size:18px; line-height:1.35;
  word-spacing:-0.05em;
  color:var(--color-ink);
}
.ah-quotes figcaption { margin-top:12px; font-size:13px; line-height:1.45; color:var(--color-muted); }
/* The link out to Google reviews, under the quotes. A grid child, so it has to
   ask for the full row. It holds the gold primary button now (2026-10-15), so it
   needs no link styling of its own. */
.ah-quotes__more { grid-column: 1 / -1; margin-top: clamp(28px,3.4vw,48px); font-size: 16px; }
.ah-band--dark .ah-quotes figure { border-top-color: color-mix(in srgb, var(--color-on-dark) 28%, transparent); }
.ah-band--dark .ah-quotes blockquote { color: var(--color-on-dark); }
.ah-band--dark .ah-quotes figcaption { color: var(--color-on-dark-muted); }

/* On desktop the header no longer follows the scroll (2026-09-25), so a pinned
   photograph needs only a small breather from the top of the window, not the
   7rem that cleared the floating pill. Below 1024 the header is still fixed and
   keeps the larger offset. */
@media (min-width:1024px) {
  .ah-story__photo, .ah-events__photo, .ah-story--flip .ah-story__photo { top: 2.5rem; }
}

/* ── Solid shape meeting solid shape gets a little more air ───────
   Every section gap is the same clamp(88px,9vw,140px) in the code, but where a
   section that ENDS on a solid shape (a photograph, a video, a card, a gold
   button, the edge of a tinted or green band) is followed by one that STARTS on
   one, both edges are visible right up to the boundary, and the gap reads
   tighter than the identical gap beside text — text has leading built into its
   top, so its ink starts 10 to 20px inside its box. Measured on 2026-09-25 at
   1280: homepage rail -> events and events -> quote band; Services story ->
   flipped story; Reiki quote band -> story and section -> Expansion. 28px is
   Chris's figure.

   Sections that START on text (.ah-section, which spaces itself with padding)
   are deliberately not in the second list. Change the extra in one place. */
:root { --ah-hard-edge-extra: 28px; }
:is(.ah-story, .ah-events, .ah-words, .ah-band--tint, .ah-band--dark, .ah-statement,
    .ah-section:has(.ah-rail, .ah-pair, .ah-video__full))
+ :is(.ah-story, .ah-events, .ah-words, .ah-band--tint, .ah-band--dark, .ah-statement) {
  margin-top: calc(clamp(88px,9vw,140px) + var(--ah-hard-edge-extra));
}

/* ── The primary hero link is the gold button ─────────────────────
   altha_hero_links() marks the first link .ah-go--primary (2026-09-25). The
   hero rules paint every link in it white and underline it on hover, and they
   are as specific as a button's own colours, so the button's are restated here
   one class stronger, and last. Ink on gold, hover to the deeper gold, never
   an underline. The row centres its items so a text link beside the button sits
   on the button's midline, drops the button's own top margin (the row already
   has one), and is allowed to wrap: the base hero rule forbids it, which was
   right for two short text links and would push a button and a link off a
   phone. */
.ah-home .ah-links a.ah-go--primary,
.ah-feature .ah-links a.ah-go--primary,
.ah-opener .ah-links a.ah-go--primary { color: var(--color-ink); margin-top: 0; text-decoration: none; }
.ah-home .ah-links a.ah-go--primary:hover,
.ah-feature .ah-links a.ah-go--primary:hover,
.ah-opener .ah-links a.ah-go--primary:hover { color: var(--color-ink); background: var(--color-gold-deep); text-decoration: none; }
.ah-links:has(.ah-go--primary),
.ah-feature .ah-links:has(.ah-go--primary) { align-items: center; flex-wrap: wrap; white-space: normal; gap: 14px 22px; }

/* Full-width lists in an .ah-intro__body (the About page's training list, the
   builder's Ruled List) sit in the right-hand columns on desktop, so their
   bullet is already well in from the page edge. Below 1024px that body goes
   full width and the bullet lands on the margin, so it is indented there like
   every other list (2026-09-25). */
@media (max-width:1023px) {
  .ah-intro__body .ah-list { padding-left: 16px; }
}

/* ── The homepage quote band fades through its quotes (2026-10-15) ──
   The slides share ONE grid cell, so the band is as tall as the tallest quote
   and nothing below it moves as they change. Without JS only .is-active (the
   first) is visible; theme.js moves the class on a timer. The transition is
   opacity only, and a slide that is not showing is visibility:hidden after the
   fade so it cannot be tabbed to or read out. */
.ah-words__stack { display: grid; }
.ah-words__slide {
  grid-area: 1 / 1;
  opacity: 0; visibility: hidden;
  transition: opacity 1s ease, visibility 0s linear 1s;
}
.ah-words__slide.is-active {
  opacity: 1; visibility: visible;
  transition: opacity 1s ease, visibility 0s;
}
@media (prefers-reduced-motion: reduce) {
  .ah-words__slide, .ah-words__slide.is-active { transition: none; }
}

/* The quote band's button: centred by the band's text-align, with more air
   above it than a button in running copy, since it follows a large quote. */
.ah-words .ah-words__cta { margin-top: clamp(28px,3vw,40px); }

/* ── Testimonials, one column on the texture (2026-10-15) ──────────
   The Client Quotes section's "One column" layout. Each quote is its own block
   wearing the homepage quote band's ground (same base colour, same meadow
   texture, same multiply blend, so the two read as one family), in the display
   face at the display-line size, centred. Three columns of 18px type read as
   bland and ran short lines; here the quote has the room. The measure is capped
   in ch on the quote itself (1ch is ~1.45 characters in this face), so a wide
   screen gets a wide block, not a wide line. Radius is the 16px surface tier.
   Uploaded band texture: functions.php lists this selector beside .ah-words. */
.ah-quotes--stack {
  grid-column: 1 / -1;
  display: grid; gap: clamp(20px,2.4vw,32px);
}
.ah-quotes--stack figure {
  margin: 0; text-align: center;
  padding: clamp(40px,5.5vw,84px) clamp(24px,5vw,72px);
  border-radius: 16px;
  background-color: var(--ah-texture-base);
  background-image: url(../images/texture/texture-light.15ddcb60.webp);
  background-size: cover; background-repeat: no-repeat;
  background-blend-mode: multiply;
}
.ah-quotes--stack blockquote {
  margin: 0 auto; max-width: 40ch;
  font-size: var(--ah-display-line); line-height: 1.17; word-spacing: 0.07em;
}
.ah-quotes--stack figcaption { margin-top: 20px; font-size: 13.5px; }
/* On a forest section the quote blocks are still light, so the dark-band type
   colours have to be put back to ink. */
.ah-band--dark .ah-quotes--stack blockquote { color: var(--color-ink); }
.ah-band--dark .ah-quotes--stack figcaption { color: var(--color-muted); }

/* Revised the same day: ONE textured block holding every quote, not a block per
   quote (Chris: "a single background image for all of them"). The texture and
   radius move from the figure to the container; the figures are bare, and white
   space does the separating. Written as a second pass rather than edited above
   so the specificity of what it replaces is plain to see. */
.ah-quotes--stack {
  padding: clamp(40px,5.5vw,84px) clamp(24px,5vw,72px);
  gap: clamp(48px,6vw,96px);
  border-radius: 16px;
  background-color: var(--ah-texture-base);
  background-image: url(../images/texture/texture-light.15ddcb60.webp);
  background-size: cover; background-repeat: no-repeat;
  background-blend-mode: multiply;
}
.ah-quotes--stack figure {
  padding: 0; border-radius: 0;
  background: none;
}

/* A background chosen for this one section (Client Quotes > Background Image),
   separate from the Site Settings texture the homepage band uses. The template
   passes only the address, as --ah-quotes-img. It is an ordinary picture, not the
   pale multiply-ready asset, so it gets the same treatment as an uploaded band
   texture: a veil of the block's own base colour at 94% over it (86% when first built; Chris wanted less of the picture), normal blend,
   so about 6% shows through at any tonality and the type stays readable. For
   more of the picture, lower the 94%. Beats functions.php's band override on
   specificity (two classes against one). */
.ah-quotes--stack.ah-quotes--image {
  background-image:
    linear-gradient(color-mix(in srgb, var(--ah-texture-base) 94%, transparent), color-mix(in srgb, var(--ah-texture-base) 94%, transparent)),
    var(--ah-quotes-img);
  background-blend-mode: normal;
}

/* The Google reviews button under the quotes (2026-10-15): a primary button, as
   the homepage band has. In the one-column layout it sits INSIDE the textured
   block, centred beneath the last quote, the block's own gap giving the air. */
.ah-quotes--stack .ah-quotes__cta { justify-self: center; margin-top: 0; }

/* ── About: Liv's portrait is deeper than the other story photographs ──
   Chris asked for it deeper on desktop and mobile. 4:5 beside the copy (the
   shared photographs are square) and square when stacked (they are 4:3).
   Scoped to .ah-story--about so Services, Events and Reiki keep the shared
   ratio. Change the ratios here and nowhere else. */
.ah-story--about .ah-story__photo img { aspect-ratio: 4/5; }
@media (max-width:860px) {
  .ah-story--about .ah-story__photo img { aspect-ratio: 1/1; }
}
