/* Shared wireframe stylesheet for the /mockup routes.
   Lives in public/ because two pages now use it: the homepage wireframe
   and the per-doctor profile pages. */

/* --- TYPE, ADOPTED FROM KELLIE'S GPT REFERENCE SITE -------------------------
   Sab, 2026-09-13: "i want body text to use the reference Kellie gave but we
   keep the accent font, actually lets just use the heading text from the
   Openai one." So display and text both come from the reference; the accent
   face does not.

   What the reference actually is, measured live rather than assumed: it loads
   NO webfonts. Zero @font-face rules, zero font links, nothing in
   document.fonts. It is Arial with a single Georgia italic in the hero. The
   reason its headings read as designed is not the face — it is the settings:

     display    79.4px / weight 400 / -0.045em / 1.10 leading
     section    51.2px / weight 400 / -0.045em / 1.10 leading
     eyebrow    12.5px / weight 600 / +0.16em / uppercase
     body       18.1px / weight 400 /  normal  / 1.60 leading

   Big, light and tight, against a small hard-tracked cap line. That is the
   whole trick, and it is what transferred here. The family token is the cheap
   half of this: swapping Arial for a licensed grotesque later is a one-line
   change and every metric below still holds.

   Bitter and Work Sans are gone from this layout. Caveat stays, because the
   accent face was kept by name, and THE ACCENT RULE below is unchanged.

   THE METRICS ARE TOKENS TOO, and that is a fix rather than tidiness. The first
   pass moved this stylesheet and left SiteHeader/SiteFooter hardcoding Bitter,
   so the page changed and the chrome did not — Sab, within the hour: "headings
   in the footer are still the old font." Shared chrome now reads every one of
   these, with the old values as its var() fallback, so B-E keep Bitter at 700
   without a single line of per-layout branching. */
:root {
  --font-display: Arial, Helvetica, sans-serif;
  --font-text: Arial, Helvetica, sans-serif;
  --font-accent: "Caveat", cursive;

  --display-weight: 400;
  --display-leading: 1.1;
  --display-tracking: -0.045em;
  --label-weight: 600;

  /* --- COLOUR ------------------------------------------------------------
     Sab, 2026-09-13: "we will also begin to incoroporiate colros."

     The brief was "warmer, like Macquarie Park." Both reference sites were
     measured from computed styles rather than eyeballed, and they turned out
     to share a VALUE structure: dark ink, one saturated accent, one pale
     ground. What separates them is hue. Kellie's GPT reference grounds on a
     blue-shifted tint (#EAF3F7) under a steel-blue accent (#175B79);
     Macquarie Park grounds on cream (#FBF8F5) under teal (#278378).

     So "warmer" is a neutrals swap plus one hue rotation, not a redesign.
     That is the entire colour decision, and it is why this arrived as a token
     block rather than a restyle.

     MEASURED, from macquarieparkmedical.com.au: --paper-band, --accent.

     AND THE MEASUREMENT WAS HALF-READ THE FIRST TIME, which is the correction
     below. #fbf8f5 is genuinely Macquarie Park's cream — but on that site it is
     a BAND, not the paper. Driven live 2026-09-14, computed styles: their
     `body` is rgb(255,255,255) and the cream covers ~904k px² against ~14.0M
     px² of white. We had it as the document ground, so the same hex that is an
     accent neutral over there was carrying ~93% of the page over here (our
     body measured rgb(251,248,245), 16.7M px² of cream and no white band at
     all). Sab, 2026-09-14: "so the document background is cream here i dont
     know if i like that". He was right, and it is not a taste call — it is a
     divergence from the reference the client named, and the reference settles
     it. The hue was never the problem; the AREA was.

     SO THE ROLES SPLIT IN TWO AND KEEP THEIR OWN NAMES. --paper is the document
     ground and is now #fff; --paper-band is the cream, spent deliberately on
     #services and #visiting — the two card bands, where a white card on cream
     is the lift the cards were always relying on. Hero is photographic,
     #doctors and #location are white, so the page reads photo / cream / white
     / cream / white, which is Macquarie Park's own rhythm rather than a flat
     wash of its accent neutral.

     --paper and --paper-raised are both #fff now and that is NOT a redundant
     pair to collapse. They answer different questions — "what is the page made
     of" and "what is a card made of" — and the whole reason this correction was
     cheap is that every consumer already asked the right one of those two. Fuse
     them and the next card-lift change edits the body background by accident.

     DERIVED, and therefore the ones to argue with: --ink is the accent taken
     down to a near-black so the darkest value in the page still carries the
     teal cast rather than going neutral or navy; --accent-ink and --accent-deep
     are the same hue carried down for type and for filled states; the two rules
     are warm greys pulled toward the ground rather than grey-neutral, because a
     cool hairline on a cream field is the thing that reads as "unfinished
     template".

     THE ACCENT IS THREE STEPS IN ONE HUE, NOT ONE VALUE, and the split is
     forced by measurement rather than taste. Sab, 2026-09-14: "can we make the
     color lighter please the teal one? more like AMC but perhaps a mix between
     it and the logo". Taken literally: AMC's teal is #62afa2 and the GMC mark
     samples #7abeda (light BLUE — the logo is not teal at all, which is why the
     mix moves the hue as well as the value). Half way between them is #6eb6be,
     hue 186, and that was --accent until 2026-09-15.

     THE CLIENT CLOSED THE DISTANCE. Mae Atillo (amcgrp.co), 2026-09-15, on the
     teal iteration: use the current Glenmore Park logo as the working logo and
     "feel free to complement the current logo colours" — Sab: "lets move
     towards the logos blues please". The ramp rotated hue 186 -> 205, the
     sampled family of the logo mark itself (#7abeda ring, #84b7e9 globe,
     #709cc6 Australia), with saturation and lightness held — so every role
     and every step keeps its shape and only the hue moved. Blue is darker
     in weighted luminance than teal, so every contrast ratio in this block
     improved in the move. The warm tokens (--paper-band, --rule) deliberately
     stay warm: the warm ground is what keeps Mae's 2026-09-11 "warmer colour
     direction" note true while the accents answer the logo.

     IT CANNOT CARRY TEXT. #6e9dbe is 2.9:1 on white, which still fails even the 3:1
     large-text floor, so every `color:` site that used to take --accent would
     have gone unreadable had the token simply been reassigned. AMC gets away
     with it because they never set type in it: measured live on www.amcgrp.co,
     that teal covers 535,897 px2 of BACKGROUND and exactly 143 CHARACTERS of
     text. It is a fill colour on their site and it is a fill colour here.

     SO THE ROLES SPLIT BY MEASUREMENT, not by shade preference:
       --accent       #6e9dbe  2.9:1 on white, 5.71:1 on --ink.
                      Fills, marks, rings, hairlines, type on dark. Never type
                      on white.
       --accent-ink   #376586  6.23:1 on white. Accent TYPE, and any control
                      whose STATE is carried by colour (1.4.11 wants 3:1).
                      This is the LIGHTEST value at hue 205 that still clears
                      AA; the rotation held S and L, so the step kept the
                      shape it had at hue 186 and the ratio only went up
                      (4.68 -> 6.23). Accent type therefore could not get
                      meaningfully lighter than the #377e86 it replaces — the
                      ask lands on the fills, which is where it was visible
                      anyway.
       --accent-deep  #2d5876  7.58:1. The filled-button ground under white
                      type. Was #2d6f76 at 5.76, so this step darkened in the
                      move — blue luminance, not a design change.

     THE ICONS ARE GENERATED FROM THIS BLOCK. scripts/build-icons.mjs reads the
     accent tone out of this stylesheet and bakes it into the SVGs under
     public/icons/gmc, because a two-tone SVG in an <img> cannot be recoloured
     from CSS. Changing --accent here without re-tinting those leaves twenty-one
     marks in the OLD teal with no build error to catch it — and the script's own
     header explains why a plain re-run is not enough to do it. (The glob for
     those files is spelled out in words rather than typed, because a star and a
     slash inside a comment opens a nested comment and this file's oracle fails
     the build on exactly that. It just did.)

     EVERY COLOUR LITERAL IN THIS FILE LIVES IN THIS BLOCK. Below it there are
     only var() references with no fallbacks, which is deliberate: it keeps
     the palette to one auditable place, and it lets the oracle pin this
     layout to exactly these values while B-E stay greyscale. A stray hex
     anywhere else in the file now fails the build. */
  --paper: #ffffff;
  --paper-band: #fbf8f5;
  --paper-raised: #ffffff;
  --ink: #121f31;
  --ink-inverse: #ffffff;
  --rule: #d9cfc4;
  --rule-strong: #121f31;
  --rule-dashed: #b9ab9c;
  /* THE CHROME'S RULE TOKENS ARE NAMED PER CONSUMER, 2026-09-15. The shared
     chrome never reads --rule directly any more: the four greyscale sheets
     define --rule as the FULL SHORTHAND `1px solid #000`
     (mockup-parkway.css:12 and siblings), so a longhand consumer like
     `border: 1px solid var(--rule, ...)` receives "1px solid 1px solid #000",
     invalidates, and silently draws no border at all. Consuming a named token
     with a literal #000 fallback means Nepean keeps its warm hairline
     (--header-rule here, --footer-rule above) and the greyscale layouts
     regain the black one they were signed off with. */
  --header-rule: var(--rule);
  --accent: #6e9dbe;
  --accent-ink: #376586;
  --accent-deep: #2d5876;

  /* CARD GROUNDS. Sab, 2026-09-14, in order, on the same band: "can we also
     introduce the theme colors here as well?", then — looking at the first
     answer — "lets ensure the background is the ideal white color as well
     pelase right now it looks abit drab so to speak can we have warmer tones as
     well".

     THE FIRST ANSWER WAS THE WRONG COLOUR AND THE SECOND NOTE SAYS WHY. Asked
     for "theme colours" I reached for the accent and dissolved it into the
     card ground and the icon disc. The accent is TEAL, which is the cool end of
     this palette, and a page whose ground is cream cannot take a cool tint on
     its cards without the whole band going grey-green — which is precisely what
     "drab" names. Colour was added and warmth was removed, and warmth is what
     the reference site actually trades on.

     SO THE TINTS CAME OFF --rule RATHER THAN --accent — and then came off the
     page entirely, which is where this block ends up and why it now declares no
     tokens at all. The intermediate answer was a biscuit disc (--rule at 60%
     into cream) behind each mark and a lighter version of the same sand as the
     card hover. Sab, looking at one card: "the secondary color its like a really
     drab cream? cant we jsut make it an outline then on hover invert the colors
     of the selected card?"

     HE IS RIGHT AND THE DIAGNOSIS IS THE SAME ONE, A THIRD TIME. A low-chroma
     warm neutral spread over AREA is drab wherever you put it — as the document
     ground, as a section band, and now as a 5rem disc repeated twenty-one times
     down a page. Desaturated warm neutrals are a good HAIRLINE and a bad FILL,
     because at low saturation the only thing a fill contributes is a value step,
     and a small value step off white is indistinguishable from a dirty screen.
     Three corrections, one root cause, and the root cause was never the hue.

     SO THE MARK GETS A RING, NOT A DISC. An outline is --rule doing the thing
     --rule is already good at — one warm line — and it costs no area at all.
     The circle survives, the drabness does not.

     AND THE HOVER IS AN INVERSION, WHICH IS WHY THERE IS NO HOVER TOKEN. The
     old hover was a third warm neutral mixed to sit between the card and the
     disc, and it existed only because a tinted object on a tinted ground needs
     its tints a step apart in every state. Invert to --ink and the whole problem
     is deleted rather than tuned: the step is maximal, it needs no new value,
     and it reuses two tokens the page already has. A hover state that has to be
     mixed to a hundredth is a hover state that is too quiet to be worth having.

     AND THE CARD ITSELF WAS PURE WHITE, resting — until round four, later the
     same day: the ring field made the ground textured, and Sab asked for
     "abit of background speeration by making the service card colors slighly
     different". The rest state is now one quiet --rule step off white (see
     .service). Nothing else here reopens — no disc, no hover mix, nothing
     repeated at area — but the verdict was drawn on a plain white band, and
     the card no longer sits on one. */

  /* NOT A COLOUR — A MASK, and it lives here because the oracle requires every
     hex in this sheet to sit in one auditable block and was right to stop me
     writing these stops inline. #000 in a mask names full OPACITY, not black;
     a mask reads only the alpha channel, so the two ends are "paint this" and
     "paint nothing". Naming it once also means the two cards that fade their
     corners cannot drift apart, which is the same argument as every other
     token above.

     TWO STOPS DO TWO DIFFERENT JOBS, and Sab's note — "less prominent we want
     it abit subtle" — asked for both. The POSITIONS lengthen the ramp, so the
     line dissolves over most of each edge instead of holding full strength
     across three-quarters of it and dropping off at the ends. The ALPHA holds
     the whole border back, corners and middle alike, so it never reads as a
     hard box even where the mask paints most strongly.

     THREE PASSES, and the direction is monotone: 14/86 at full alpha, then
     30/70 at 0.7, now 42/58 at 0.55. Sab looked at the third card and said "can
     we make the corners less obvious?", which is the same note a third time, so
     this stops guessing at increments and goes most of the way — 42/58 leaves
     only the middle sixth of the diagonal at full mask strength and ramps
     across everything either side of it, which is as far as this can travel
     before the border is not a border.

     THE TWO NUMBERS ARE THE WHOLE KNOB. If this has gone too far, 36/64 at 0.62
     is the halfway point back; nothing else needs touching, because every
     border that fades on this page — six visiting cards and fifteen service
     cards — reads this one token. That is why the fade was tokenised rather
     than written per card.

     (The roster above shrank twice on 2026-09-16. Sab: "can we make button
     borders just full" took .btn off it — the mask line came off .btn::before
     outright — and "update the header book now cta to use the filled border
     please instead of the softer corners" took the shared chrome off it: the
     --chrome-border-mask handoff below is withdrawn, so .hdr-book renders its
     hairline full-strength on all four corners, matching .btn. What remains
     faded is exactly what Sab has ratified as faded: the cards.) */
  --corner-fade: linear-gradient(
    135deg,
    transparent 0%,
    rgba(0, 0, 0, 0.55) 42%,
    rgba(0, 0, 0, 0.55) 58%,
    transparent 100%
  );

  /* WITHDRAWN (2026-09-16, Sab: "filled border ... instead of the softer
     corners" on the header CTA). This used to hand --corner-fade to the
     shared chrome: SiteHeader's .hdr-book::before asks for
     var(--chrome-border-mask, none) because the component sits behind all
     five layouts and cannot be styled from a layout sheet — an oracle gate
     forbids it, and an Astro-scoped rule and a layout rule both land at
     (0,2,0) where source order, not intent, decides. Declaring nothing is
     the layout's answer channel: the fallback `none` is a no-op, and the
     ghost's hairline now runs full-strength to every corner, matching .btn.
     The channel stays open — declaring the token again would re-fade the
     chrome — but Nepean opts out. */

  /* THE FOOTER'S OWN FIELD. Sab, 2026-09-14: "for the footer lets also now make
     it looklike MPMC's footer" — and MPMC's is a dark ground with light type.

     Every value here is an EXISTING token re-pointed, not a new colour, which is
     why none of this is a hex: the ground is the ink, the type is the paper, and
     the button is the accent. That is not a shortcut around the one-block rule
     above — it is the reason the footer stays in palette when the palette moves.

     NEPEAN ONLY, and that is worth being exact about because I had it wrong for
     one commit. 8c916da's message says these declarations sit in "the base sheet
     every layout loads". They do not. This file is Nepean's sheet and nothing
     else's — Terrace, Parkway, Mulgoa and Lakeside each link only their own, and
     the four references to this filename in their sources are all inside
     comments. Verified against the build rather than the source: dist/layout-b/
     index.html links mockup-layout-b.css and no other local sheet.

     So the other four keep the light footer, and the footer is now constant in
     CONTENT across the five but not in colour. That is the same divergence
     Nepean's sign-off pairing and its .lit span already carry, for the same
     reason — A.1 is the candidate the client chose and the one being designed —
     and the oracle's footer gate compares content, which still matches. If the
     dark ground is ever wanted on all five, the tokens move to a shared sheet;
     they do not get copied four times.

     The rules are the paper at low alpha rather than --rule. --rule is a warm
     grey chosen to sit ON paper; on a dark ground it reads as a dirty line. A
     hairline on a dark field is the ground's own light coming through, which is
     what an alpha does and what a fixed grey cannot.

     color-mix, NOT rgba(251, 248, 245, 0.18). Those three numbers are the
     cream's digits typed out a second time, and the oracle caught them on the
     first run — correctly, because that is precisely the drift the one-block
     rule exists to prevent: change the token and the footer's hairlines
     silently keep the old hue. The mix names no colour at all and follows it.

     AND THE TOKEN IT FOLLOWS IS --paper-band, NOT --paper, since the split.
     The footer is a dark field and its type wants to stay CREAM on navy — that
     is the warm half of the palette doing the one job it is unambiguously good
     at. Pointing these at --paper after the ground went white would have turned
     the footer type pure #fff and cooled the only band that was already right,
     which is the exact failure mode this comment was written about: a rename
     upstream, a silent hue change downstream. */
  --footer-field: var(--ink);
  --footer-ink: var(--paper-band);
  --footer-rule: color-mix(in srgb, var(--paper-band) 18%, transparent);
  /* THE FOOTER CTA IS THE .btn-lg PRIMITIVE, DELIVERED THROUGH THE TOKEN SEAM.
     Sab, 2026-09-14: "for book an appointment can we use our proper butotn
     primitive not a pill as well please". It was a 999px pill on the grounds
     that MPMC's booking button is one; that was a reference borrowed past the
     point it helped, since this page squares off every other control it has.

     WHY THE TOKENS AND NOT `class="btn btn-lg"` ON THE ELEMENT. SiteFooter is
     ONE component rendering on all six pages, and B-E define .btn/.btn-lg of
     their own while setting none of these tokens. Putting the classes in the
     markup would silently repaint four archived greyscale layouts that the
     client has already finished choosing between. These four tokens exist
     precisely so a layout can dress the shared footer without reaching into it,
     so the primitive arrives here by value: --accent-deep field and edge, white
     ink, square corners, lifting to --accent-ink on hover — which is exactly
     what .btn-lg and .btn-lg:hover declare, thirty lines apart in this file.
     If those two ever disagree, THIS is the copy that is wrong. */
  --footer-cta-field: var(--accent-deep);
  --footer-cta-edge: var(--accent-deep);
  --footer-cta-field-hover: var(--accent-ink);
  --footer-cta-ink: var(--ink-inverse);
  --footer-cta-radius: 0;
}

* {
  box-sizing: border-box;
}

/* SCROLL LOCK WITHOUT CLS (2026-09-16, Sab): locking scroll for the
   relocation modal used to remove the scrollbar and reflow the page a
   gutter width. scrollbar-gutter: stable was tried first and FALSIFIED
   live on two shapes (on html, and with the lock moved to the root
   scroller): this Chrome does not honour the reservation once the root
   overflow goes hidden. The fix is therefore in the modal script —
   measure the scrollbar before locking, pad the root by exactly that
   while locked. Overlay-scrollbar platforms measure 0 and pad nothing. */

body {
  margin: 0;
  position: relative;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-weight: 400;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* THE RING FIELD. Sab, 2026-09-14: "lets take the circle motif here, im
   thinking we do svg circles but just the outline for the backgorund? to give
   it abit of texture almost document like to to speak we can have them animate
   using gsap?" The icon rings are the one rounded shape this design owns, so
   the ground carries them too: hairline circle outlines distributed down the
   full length of the document — a watermark, not a decoration. Cards and
   bands paint over it; it shows in the margins, the way laid paper shows in

   FIXED, THEN PAPER: the field launched as a fixed viewport layer the page
   scrolled over — "the paper is not part of the text" — then carried Sab's
   "slighty parallaex rather then just fully being fixed backgroudn" as a 5%
   drift. Both retired the same afternoon, because pinned to the screen the
   same few arc fragments sat in the same corners at every scroll depth and
   the texture read as ending: "the circle background terminates pretty
   quicktly it needs to stretch the full legnth of its visibility or the full
   page i think". So the paper became part of the document — one absolute
   layer spanning the body's full height, scrolling with the content, circles
   laid out down the whole page, the way the original brief imagined it
   ("texture almost document like"). The parallax ask survives as a
   counter-drift: the layer rides the hero's scroll handler and lifts 5%
   against the scroll (see the script and --ring-parallax).

   THE STACKING DEPENDENCY, WRITTEN DOWN: z-index -1 sits behind every section
   because body's background PROPAGATES to the canvas — html sets no background
   of its own — and the canvas paints beneath everything, this field included.
   If an html background is ever added, body stops propagating, paints its own
   paper, and this field silently disappears behind it. That dependency is why
   this comment exists.

   Colour discipline: no literals — the same color-mix-off-tokens move as the
   hero overlay, with the plain token declared first for engines without
   color-mix (they render solid --rule hairlines instead of nothing). The
   accent ring is dashed in the stylesheet, so WITHOUT the script it is
   simply a quiet dashed circle — never half-drawn. */
.bg-rings {
  position: absolute;
  /* OVERSCAN AT THE TOP ONLY, and for a new reason: the counter-drift
     (--ring-parallax, written by the hero's scroll handler, negated here)
     lifts the layer UP against the scroll, so the top edge is the only one
     that can rise into view. It stands 5% of the document proud and the
     drift maxes at 5% of the scrollable distance, which is always less than
     5% of the height. The bottom needs none — the document's last surface is
     the footer's own opaque field, and an overscanned bottom would add dead
     scroll below it. Percentage geometry in the markup (no viewBox): cx is
     of the width, cy of the page's own height, r of the normalised diagonal,
     so the composition lays itself out down whatever length the page turns
     out to be.

     ONE LAYER FOR THE WHOLE DOCUMENT, Sab 2026-09-15: "make it such that the
     circle background is bascially an z element that occupies the while
     document / page". Until tonight the dark trust band painted its own
     paper-tone rings and the seam between the two systems read as offset
     fragments of circles that refused to line up. Now the band's ink sits at
     z-index -2, BELOW this layer, and the strokes went white under
     mix-blend-mode: difference — |255 - white| is a dark hairline on the
     white canvas, |255 - ink| is a light one on the dark band. One set of
     circles, continuous through every ground, no second ring layer anywhere.
     The accent ring shares the white: a teal stroke under difference turns
     red over white, so the field is greyscale by arithmetic and the accent
     lives where it can survive — buttons, rules, the hero's own rings. */
  top: -5%;
  left: 0;
  width: 100%;
  height: 105%;
  z-index: -1;
  pointer-events: none;
  mix-blend-mode: difference;
  transform: translate3d(0, calc(-1 * var(--ring-parallax, 0px)), 0);
}

/* THE LAYER'S CONTAINING BLOCK, WRITTEN DOWN: an absolute layer with no
   positioned ancestor resolves against the viewport-sized initial containing
   block — it would span one screen at the top of the document and terminate
   exactly as Sab described. body is positioned for this page only
   (:has-scoped), so the field measures its height against the document's
   real length. */
body:has(.bg-rings) {
  position: relative;
}

.bg-rings circle {
  fill: none;
  stroke: var(--paper);
  stroke: color-mix(in srgb, var(--paper) 82%, transparent);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.bg-rings .bg-ring--accent {
  stroke: var(--paper);
  stroke: color-mix(in srgb, var(--paper) 38%, transparent);
  stroke-dasharray: 3 6;
}

/* THE BANDS STEP ASIDE — but only on pages that carry the field. The Section
   component paints every band white in its own Astro-scoped style, shared by
   all five layouts, which buries a behind-everything watermark completely
   (verified live: the field drew, animated and drifted, and not one arc was
   visible — main and .mk-section were painting over it). Bands made
   transparent over the propagated canvas render IDENTICALLY — both are the
   same white — except the field now shows through them, which is the whole
   point of a watermark: texture under the document's panels, not beside them.
   body:has(.bg-rings) scopes this to the homepage, which is the only page
   rendering the field; the four sibling layouts and the Nepean inner pages
   keep their opaque bands untouched. .mk-section.mk-section doubled plus the
   :has so the specificity beats Astro's scoped selector on arithmetic rather
   than on bundle order. */
body:has(.bg-rings) .mk-section.mk-section {
  background-color: transparent;
}

/* CIRCLES NEVER BELOW TEXT (2026-09-16, Sab: "can we ensure that the circles
   are never below text? this just looks out of place" — on Find Us the
   field's dark hairline crossed the checklist and ran over the Getting Here
   link). The field is one document-wide layer at negative z showing through
   the transparent bands; what must not carry it is any READING SURFACE. So
   the content container goes opaque paper — in-flow backgrounds paint above
   the negative-z layer, no positioning needed — and the watermark retreats
   to the true whitespace: section gaps, side margins, the hero photographs,
   and the dark band. .mk-inner.mk-inner doubled for the same arithmetic the
   bands-step-aside rule needs against Astro's scoped selectors.

   EXEMPTED, where rings-on-content IS the design: #hero and the inner
   photographic heroes (type sits on the picture, strokes flip light through
   the difference blend), and the trust band, whose ink at -2 sits below the
   field so its strokes read light on dark. */
body:has(.bg-rings) .mk-inner.mk-inner {
  background: var(--paper);
}

body:has(.bg-rings) #hero .mk-inner,
body:has(.bg-rings) .mk-section.trust .mk-inner,
/* .innerpage is a class ON body, not a descendant of it — the class must sit
   on the same element as :has() or the chain can never match (falsified live:
   the descendant form left the photographic hero painted paper). */
body.innerpage:has(.bg-rings) > .mk-section:first-of-type .mk-inner {
  background: none;
}

/* Weight 400, not 700. The reference sets its 79px hero at regular weight and
   lets size carry the emphasis, which is why it reads as composed rather than
   shouted. Bold at this scale was doing the shouting for us. */
h1,
h2 {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  line-height: var(--display-leading);
  letter-spacing: var(--display-tracking);
  margin: 0;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2.5rem, 5.5vw, 4.5rem);
}

/* +15% on Sab's direction (2026-09-13). All THREE terms of the clamp are
   multiplied, not just the cap: scale the maximum alone and the heading is
   unchanged on a phone and at every width below the clamp's upper breakpoint,
   which is most of the widths a patient actually uses. Multiplying the whole
   curve moves the heading 15% at every width and keeps its shape.
   1.875 x 1.15 = 2.15625 · 3.2 x 1.15 = 3.68 · 2.75 x 1.15 = 3.1625 */
h2 {
  font-size: clamp(2.15625rem, 3.68vw, 3.1625rem);
}

/* THE LIT PHRASE — "everyday life." inside the services heading.
   Sab, 2026-09-14, naming website-evygen-com-au's hero word as the reference.

   THE SHINE IS GONE, by direction, and this is the second time a decorative
   pass has come off this page. It was built as evygen has it — a light band
   travelling through the phrase — and cut the same day: "can we remove the
   shine actually". What is left is the part that carries meaning. A phrase in
   the accent colour with a rule drawing in beneath it says "this is the point
   of the sentence"; a band of light moving across it says only that something
   is animating. The first is emphasis, the second is motion, and only one of
   them is worth a patient's attention.
   Gone with it: background-clip: text, its @supports guard, the sheen
   keyframes, and the --accent-sheen token. The cut went all the way back to
   the palette rather than leaving an unused colour behind.

   IT RESTS LIT. Everything below describes the FINISHED state: accent text,
   underline at full width. The animation is opt-IN — JS adds .is-armed to wind
   it back and .is-lit to play it forward. Written the other way round, a
   blocked CDN or a script error would leave the underline permanently at zero
   width, and the phrase Sab asked to emphasise would be the one thing on the
   page that looked broken. */
.lit {
  position: relative;
  color: var(--accent-ink);
}

/* Its own element rather than text-decoration, because a decoration cannot be
   scaled from one end — it would have to be revealed by animating a width or a
   clip on the text itself, and both of those move the phrase's own box. This
   paints in its own layer and scales along X, which touches nothing else. */
.lit::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.04em;
  height: 0.05em;
  background: var(--accent);
  transform: scaleX(1);
  transform-origin: left center;
}

/* Wound back by JS, one step before the class that plays it forward. */
.lit.is-armed::after {
  transform: scaleX(0);
}

.lit.is-lit::after {
  animation: lit-underline 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes lit-underline {
  from {
    transform: scaleX(0);
  }
  to {
    transform: scaleX(1);
  }
}

/* The script already refuses to arm under reduced motion, so in practice this
   never has anything to switch off. It is here for the case the script arms
   the phrase and the preference changes underneath it mid-session — the
   underline snaps to full rather than being stranded at zero width. */
@media (prefers-reduced-motion: reduce) {
  .lit,
  .lit::after {
    animation: none;
  }

  .lit.is-armed::after {
    transform: scaleX(1);
  }
}

p {
  margin: 0;
}

.lede {
  font-size: clamp(1rem, 1.15vw, 1.1875rem);
  max-width: 46ch;
}

/* --- THE ACCENT RULE -------------------------------------------------------
   The accent face is the practice's own voice, and it gets exactly two jobs on
   a page. Sab, 2026-09-10: "accents should be sparse and use the accent font
   logically." It was doing neither — five Caveat lines down this page, three of
   them section eyebrows, which is the accent face used as texture. A second
   typeface that appears everywhere is not an accent, it is just a second body
   font, and it stops meaning anything.

   The two jobs, in the order you meet them going down the page:

     1. THE GREETING — "Welcome to", above the h1 in the hero. Once, at the top.
     2. THE SIGN-OFF — "Ready when you are", in the footer.

   Greeting and sign-off: it opens the page and it closes it, and in between the
   practice speaks in its institutional voice. Never two in one screen.

   "AND NEVER ABOVE AN H2" was the third clause here, with "a section heading
   takes .kicker instead", and it was struck on 2026-09-14 by direction. Section
   headings now open in the accent face too — but on .accent-greeting, a
   separate class subtracted from this budget as a directed exception, so the
   count below still measures the two jobs above and not the new ones. See
   .accent-greeting for the design argument and the oracle for the arithmetic.

   THERE WAS A THIRD JOB and losing it is the most useful thing this rule has
   learned. .allied-caveat — "these are independent practices ... not covered by
   the clinic's bulk billing" — was set in the accent face on the reasoning that
   it is the one place the page steps out of its own voice, so it should sound
   like the practice talking. In the PDF Sab read it as "notes ... which is metta
   commentary?", and that reaction is the finding: a handwritten line beside a
   list does not read as a confidence, it reads as an ANNOTATION about the
   design. The sentence is the one thing on the page a patient could be out of
   pocket for skipping, so it is now body copy. The lesson generalises — the
   accent face can carry a greeting, but it cannot carry information.

   Two layouts hold DIRECTED exceptions Sab set by name (Terrace's service group
   headings, Parkway's kickers). The oracle subtracts those rather than raising
   the ceiling, so the budget still measures the two jobs the rule is about.

   The oracle reads which classes this stylesheet sets the accent face on, counts
   them in the built HTML, and fails outside two. */
.accent {
  font-family: var(--font-accent);
  font-weight: 600;
  font-size: 1.5rem;
  line-height: 1;
}

/* Job 1, the greeting. Larger than the other two because it is competing with a
   photograph and a 5rem h1 rather than sitting in quiet margin.

   It is also the one exception to the no-eyebrow hero rule, reinstated by
   direction (Sab, 2026-09-10) after being removed on 2026-09-09: "i wanted the
   'welcome to' in the accent font". The original objection stands and is not
   contradicted — an eyebrow that repeats what the h1 already says is decoration.
   This one is not that. "Welcome to Glenmore Park Mediclinic" is a single
   sentence broken across two faces, so the greeting is doing the work the
   practice's own <title> does and the h1 alone cannot: it makes the first line
   of the page an address to a person rather than a label on a building. */
/* THE RATIFIED HERO GREETING SCALE. Restored 2026-09-10 after being rebuilt from
   scratch instead of restored: the greeting was briefly clamp(1.75rem, 3vw,
   2.5rem) with no optical correction, which is about a third smaller than this
   at 1280px and sat visibly off the h1's left edge. These three values are the
   ratified ones and the ratio they set — greeting at 0.8x the h1's vw term —
   is what the other four layouts derive their own greeting from.

   Tight to the h1 because they are one sentence across two faces, not two
   stacked elements; .stack's 1.25rem gap is far too much between them.

   THE OPTICAL CORRECTION IS NEGATIVE, AND IT USED TO BE POSITIVE.
   This line carried `margin-left: 0.15em` with the note "Caveat's W starts
   further left than the G below it". That is false, and measuring it says so:
   at 1440 the Caveat W's ink starts 8.49px inside its box (0.134em) and the
   Arial G's starts 3.83px inside its (0.053em). The W begins further RIGHT, so
   the old nudge did not cancel the drift — it roughly doubled it, pushing the
   greeting 14.16px past the name it is a sentence with. Sab caught it by eye
   on 2026-09-13.

   -0.074em is the measured difference, not a taste value: 0.134em(accent) -
   0.053em(h1) x (72/63.36). Exact at 1440. It cannot be exact everywhere,
   because the two clamps bottom out at different widths and the size ratio
   drifts from 0.88 to 0.70 — one em value cannot track two independent clamps.
   Swept at 390/480/640/768/900/1024/1280/1440: worst residual 0.44px. That is
   under the width of the hairline rules elsewhere on this page, so it stays one
   declaration rather than four breakpoint overrides.

   It is a property of THESE TWO FACES. If Arial is ever swapped for the
   licensed grotesque, remeasure — do not carry this number across. */
#hero .accent {
  font-size: clamp(1.75rem, 4.4vw, 4.25rem);
  margin-bottom: -0.5rem;
  margin-left: -0.074em;
}

/* THE SECTION GREETING — the hero's "Welcome to" move, made available to a
   section heading. Sab, 2026-09-14: "can we make the meet our team look more
   like welcome to, so the font ratio should be similar here".

   THIS REVERSES A POSITION I ARGUED, and it is worth saying so in the file
   rather than quietly complying: the three section eyebrows were demoted OUT of
   the accent face on 2026-09-10 for turning a second typeface into a second
   body font. Sab had already ratified the reversal on Parkway four days
   earlier, where "MEET THE TEAM" took the accent face by direction.

   IT APPLIES TO EVERY SECTION, and that is a SECOND reversal, same day. When
   only "Meet our team" was promoted I drew the line at invitation versus
   instruction — "Meet our team" is the building speaking to you, "Before your
   appointment" is a label on a list — and left the other two as kickers. Sab
   named all four lines back: "Before your appointment / Plan your visit / Ready
   when you are / Book online in under a minute. arent these primitives here it
   seems like some changes arent propgating". Read as a bug report it is wrong,
   nothing failed to propagate; read as a design note it is the Parkway finding
   arriving a second time, and the oracle already carries that sentence: "a page
   with four handwritten kickers and two small-caps ones is not a system". The
   distinction I drew is real in prose and invisible on the page — what a reader
   sees is three section openings, two of them dressed differently for a reason
   they cannot recover. Consistency inside one page beats a correct taxonomy.

   WHAT THE RULE IS NOW. Every section heading on this page opens with the
   practice speaking, in the accent face, at 0.80 of its heading. .kicker is no
   longer the default for a section — it survives only where the eyebrow sits on
   photography and is a label rather than an address (the hero, and the first
   band of an inner page).

   THE RATIO IS MEASURED, NOT CHOSEN. Live at 1280, the hero pairs Caveat
   56.32px over Arial 70.4px — 0.80 exactly. Every term of the h2 clamp is
   multiplied by 0.80, not just the cap, so the relationship holds at every
   width instead of only above the clamp's upper breakpoint. That is the same
   argument the h2's own +15% was made with.
     2.15625 x 0.8 = 1.725 · 3.68 x 0.8 = 2.944 · 3.1625 x 0.8 = 2.53

   THE OPTICAL CORRECTION IS ITS OWN NUMBER, PER PAIRING, and this is the trap
   a reusable primitive sets. The hero's is -0.074em and it is NOT reusable: it
   is a property of Caveat's W over Arial's G, and no other pairing on this page
   is that. Measured by canvas actualBoundingBoxLeft, at 100px, as a fraction of
   the em:

       Caveat 600   W 0.134   M 0.036   B 0.075   P 0.077   R 0.076
       Arial  400   G 0.0532  A -0.0015  W 0.0122  B 0.0732

   The greeting sits at 0.80 of the heading's size, so the heading's bearing has
   to be divided by that ratio before the two can be subtracted:

       correction = headingBearing / 0.8 - greetingBearing

       hero       W over G     0.0532 / 0.8 - 0.134  = -0.0675em  (ships -0.074)
       #doctors   M over A    -0.0015 / 0.8 - 0.036  = -0.0379em
       #visiting  B over A    -0.0015 / 0.8 - 0.075  = -0.0769em
       #location  P over W     0.0122 / 0.8 - 0.077  = -0.0617em
       footer     R over B     0.0732 / 0.8 - 0.076  = +0.0155em

   Reproducing the hero's shipped value to 0.0065em is what earns the other four
   any trust at all — the formula was derived after the fact and checked against
   a number nobody was looking at. The spread is the argument: -0.0769 to
   +0.0155 is 0.09em, which at the section greeting's cap is over 3px of visible
   drift off the left edge. Borrowing one number across four pairings would have
   been wrong three times.

   THE SIGN IS NOT ALWAYS NEGATIVE, and the footer is what proves it. Every
   correction on this page was negative until the sign-off was measured, and a
   sign gate in the oracle had quietly turned that coincidence into a rule.
   Arial's B carries an unusually wide left bearing (0.0732em, wider than the G
   that drove the hero); scaled up by 1/0.8 it overshoots Caveat's R, so the
   greeting has to move RIGHT to hang off the same edge. Any gate on these
   numbers has to check that each pairing DECLARED one, not which way it points.

   THE FALLBACK IS ZERO, DELIBERATELY. It used to be the #doctors value, which
   meant a fifth greeting added tomorrow would silently inherit a measurement
   taken for a different letter and look approximately right — the exact failure
   this table exists to prevent. Uncorrected is visibly uncorrected. Like every
   optical constant here these are properties of THESE TWO FACES and die the day
   Arial is replaced. */
#doctors .accent-greeting {
  --greeting-optical: -0.0379em;
}

#visiting .accent-greeting {
  --greeting-optical: -0.0769em;
}

#location .accent-greeting {
  --greeting-optical: -0.0617em;
}

/* THE SECTION HEADINGS ALTERNATE DOWN THE PAGE.
   Sab, 2026-09-14: "can ew also alternate 'Before your appointment / A little
   preparation helps.' these please as well", naming #visiting's pairing.

   WHAT WAS ACTUALLY REPEATING. Three sections carry a greeting over a heading,
   but measured at 1440 only two of them look alike: #doctors and #visiting are
   both a full-bleed 1124px block hung on the left, while #location's already
   sits in a 537px column beside the map. So the repetition Sab is looking at is
   that pair, and moving one of them is the whole of the fix - hence one rule
   here rather than an nth-child scheme that would also disturb the split.

   IT MOVES, IT DOES NOT RE-ALIGN. text-align stays at start. Right-ALIGNING the
   block would put the greeting's last glyph over the heading's last glyph,
   and every --greeting-optical above is a LEFT-edge measurement - the bearing
   of the Caveat initial against the Arial initial beneath it. Those numbers
   would all silently become wrong, and wrong in a way that still looks
   deliberate. Pushing the block right with margin-left:auto leaves both edges
   exactly where they were relative to each other, so the measured corrections
   keep meaning what they were measured to mean.

   WHICH ONE MOVES: #doctors. I shipped this the other way round first and Sab
   corrected it on sight - "this needs to the right aligned" against Meet our
   team, "this needs to be left aligned" against Before your appointment. He is
   right on the type as well as the preference: "A familiar face. A place to
   turn." sets on one line at this width and "A little preparation helps." does
   not, so constraining #visiting forced a two-line heading with "helps." alone
   on the second, while #doctors takes the constraint without wrapping at all.

   width:fit-content, NOT a max-width in ch. The first version capped at 52ch,
   which is a guess at the measure that happens to be narrower than this heading
   wants. Sizing to the content lets the longest line decide the block's width,
   so the heading never wraps to satisfy a number nobody measured, and
   margin-left:auto then pushes whatever that width turns out to be to the
   right.

   TO UNDO: delete this rule. Nothing else references it.
   */
#doctors > .mk-inner > header.stack {
  width: fit-content;
  margin-left: auto;
}

.accent-greeting {
  display: block;
  font-family: var(--font-accent);
  font-weight: 600;
  font-size: clamp(1.725rem, 2.944vw, 2.53rem);
  line-height: 1.1;
  letter-spacing: normal;
  text-transform: none;
  color: var(--accent-ink);
  /* .stack's 1.25rem gap is spacing for separate blocks. These two lines are
     one sentence, so most of it is pulled back out. */
  margin-bottom: -0.75rem;
  margin-left: var(--greeting-optical, 0);
}

/* THE SIGN-OFF, given the same pairing in the footer. Sab named it in the same
   breath as the two section eyebrows. The footer is shared chrome across all
   five layouts, so SiteFooter.astro reads these four from the layout and
   Nepean is the only layout that answers — see the note in that file.

   The ratio is the same 0.80, taken against the footer's own display line,
   which is the UNSCALED h2 clamp (1.875/3.2/2.75) rather than this page's
   +15% one: 1.875 x 0.8 = 1.5 · 3.2 x 0.8 = 2.56 · 2.75 x 0.8 = 2.2.
   The pull matches .accent-greeting's, because .fb-copy carries the same
   1.25rem gap .stack does and these two lines are likewise one sentence.
   The optical correction is POSITIVE here and only here — R over B; the table
   above works through why. */
:root {
  --footer-greeting-size: clamp(1.5rem, 2.56vw, 2.2rem);
  --footer-greeting-leading: 1.1;
  --footer-greeting-pull: -0.75rem;
  --footer-greeting-optical: 0.0155em;
}

/* The institutional voice. Small, letterspaced and uppercase reads as a label;
   Caveat at the same size read as the building talking to you three times in a
   row about nothing — which is why the section eyebrows were demoted here on
   2026-09-10, before the greeting scale above gave them somewhere better to go.

   NO LONGER THE DEFAULT for a section heading on this page: all three now take
   .accent-greeting by direction (2026-09-14). What is left is the case this
   class is actually good at — an eyebrow over PHOTOGRAPHY, where it sits on a
   black scrim and is a label rather than an address. That is the hero here, and
   the first band of every inner page, both handled by the override below. */
/* The eyebrow was already at the reference's 0.16em tracking before we looked —
   the only drift was that ours sat a shade small and light to hold its own
   against a 400-weight display line. Matched to the measured 12.5px / 600. */
.kicker {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--accent-ink);
}

/* Except over photography, where the eyebrow sits on a black scrim and the
   accent would be the one thing on the page fighting its background. Both
   references colour the eyebrow only where it lands on the pale ground. */
#hero .kicker,
.innerpage > .mk-section:first-of-type .kicker {
  color: inherit;
}

.stack {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* THE ICON CARDS in #visiting, which were six outlined Box placeholders until
   2026-09-14. Sab: "ive just downloaded a bunch of icons as well can attempt to
   use some of these?", and then twice more.

   I ARGUED AGAINST THESE AND THE OBJECTION IS WORTH KEEPING, because it is what
   the styling here is answering rather than ignoring. The packs are rounded,
   heavy-stroke and illustrative — two-tone navy and orange, drawn for a
   friendly app — and this page is hairline rules, a 400-weight display face and
   one accent. Dropped in raw they would have been the loudest thing on it.

   So three things are done TO them rather than with them. The vendor's two
   tones are remapped to this page's two (ink and accent) at conversion, so no
   foreign colour ships. They are held small — 3rem against an 11rem card — so
   they read as a mark beside a label rather than as illustration. And the card
   keeps the hairline border the Box had, so the band's construction is
   unchanged and only its contents got more specific.

   The border is the SAME hairline the placeholder used. That is deliberate: the
   six cards are still the same grid, still the same rhythm, and a reviewer
   comparing this against yesterday's build should see one thing change, not
   two. */
/* height BECAME min-height when the cards gained a blurb and a button. A fixed
   11rem was right while every card held exactly a mark and a word; with a line
   of description under it, a fixed height is a clipping bug waiting for the
   longest label. The number is kept rather than dropped so the shortest card
   still holds the grid's rhythm instead of collapsing around its content. */
.icon-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  min-height: 11rem;
  padding: 1.5rem;
  text-align: center;
  /* THE GROUND IS THE CARD PRIMITIVE'S OTHER HALF, and these cards were built
     without it — hairline only, deliberately open on what was then a plain
     white band. Sab, 2026-09-15, after the ring field became a document-wide
     layer: "can we make sur ethese have the card primtive so that theyre not
     ust transparent?" The circles were passing THROUGH them and a see-through
     card stopped reading as a card. The service row's warm step is the same
     primitive the hairline already quoted, so it is quoted here too,
     declaration for declaration. The ground sits UNDER the lit state by
     cascade: every lit selector carries a pseudo-class this one does not. */
  background: var(--paper-raised);
  background: color-mix(in srgb, var(--rule) 16%, var(--paper-raised));
}

.ic-blurb {
  margin: -0.25rem 0 0;
  max-width: 30ch;
  font-size: 0.875rem;
  line-height: 1.5;
  /* Sits under a label already set in uppercase at 0.75rem. Full-strength ink
     at this size would out-weigh the thing it describes. */
  color: color-mix(in srgb, var(--ink) 78%, var(--paper));
}

/* THE GUIDE BUTTON IS `.btn .btn-quiet` AND HAS NO RULE OF ITS OWN. It used to
   be `.ic-more`, a fourth set of button metrics that existed because this card
   wanted a control that opens a dialog rather than going anywhere and should
   not carry a box — which is precisely what `.btn-quiet` already meant. See ONE
   BUTTON PRIMITIVE, further down.

   `[hidden]` NOW NEEDS A RULE, AND THIS IS A BUG THE REFACTOR INTRODUCED rather
   than a tidy-up. The old `.ic-more` declared no display, so the UA sheet's
   `[hidden] { display: none }` was unopposed and the button genuinely shipped
   hidden. `.btn` declares `display: inline-block`, and ANY author display
   declaration beats the UA sheet — so spelling the guide button `.btn
   .btn-quiet` and stopping there would have shipped all six buttons VISIBLE to
   a reader whose JavaScript never ran, which is the exact failure the hidden
   attribute exists to prevent.

   THE ORACLE WOULD NOT HAVE CAUGHT IT. Its gate greps the built HTML for the
   attribute, and the attribute is still there — the markup is unchanged and
   only the cascade moved. A gate that checks the attribute is not checking the
   behaviour, so the rule below is what holds it, and a source gate now asserts
   the rule below exists. */
.btn[hidden] {
  display: none;
}

/* =========================================================================
   THE GUIDE DIALOG
   Sab, 2026-09-14: "i want to include a little description + a popup to give
   the users more guide for their doctor appointments".

   NATIVE <dialog>, not a div with a z-index. showModal gives the focus trap,
   the Escape key, the inert background and the top-layer stacking for free, and
   every one of those is a thing hand-rolled modals get wrong. It also means the
   backdrop is ::backdrop rather than an extra element the markup has to carry.

   The dialog is NOT given the corner-fade treatment the cards have. That fade
   exists to make a grid of rectangles feel less ruled; a dialog is a single
   object over a dimmed page and needs its edge to be definite.
   ========================================================================= */
.guide {
  width: min(46rem, calc(100vw - 2rem));
  max-height: min(40rem, calc(100vh - 4rem));
  /* dvh override: with a phone's browser chrome showing, 100vh is the tallest
     the viewport ever gets, so the vh cap can leave the dialog's bottom — and
     the Close button, which lives at the end of the content — under the URL
     bar. The dynamic unit tracks what is actually on screen. */
  max-height: min(40rem, calc(100dvh - 4rem));
  padding: 0;
  border: 1px solid var(--rule-strong);
  /* Cream, and it stays cream now that the page ground is white. A modal sits
     over a dimmed page with nothing of the page's own ground behind it, so it
     supplies its own; white-on-dimmed-white would read as a hole punched in the
     screen rather than a sheet laid on it. */
  background: var(--paper-band);
  color: var(--ink);
}

.guide::backdrop {
  /* The page behind, dimmed with the page's own ink rather than neutral black,
     so the dim reads as part of this palette and not as a browser default. */
  background: color-mix(in srgb, var(--ink) 62%, transparent);
}

.guide-inner {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  padding: 2rem;
  text-align: left;
}

/* This div carries tabindex="-1" so it can take the dialog's opening focus and
   the dialog opens on its title rather than scrolled down to the Close button.
   Suppressing the ring here is safe in a way it would NOT be on a link or a
   button: -1 means it is unreachable by Tab, so nobody arrives at it by
   keyboard and finds an invisible cursor. The controls inside keep their own
   focus styles. */
.guide-inner:focus {
  outline: none;
}

.guide h2 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  line-height: var(--display-leading);
  letter-spacing: var(--display-tracking);
}

.guide-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.5rem 2rem;
}

.guide h3 {
  margin: 0 0 0.5rem;
  font-family: var(--font-text);
  font-weight: var(--label-weight);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-ink);
}

.guide ul {
  margin: 0;
  padding-left: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.guide li {
  font-size: 0.9375rem;
  line-height: 1.55;
}

/* THE DISCLAIMER IS NOT A FOOTNOTE AND IS NOT SET LIKE ONE. Everything above it
   is general guidance rather than this practice's policy, and this line is what
   makes that true for the reader instead of only true in the source. Setting it
   smaller and greyer — the reflex for anything that reads like fine print —
   would hide the one sentence that has to land. */
.guide-note {
  margin: 0;
  padding: 1rem 1.25rem;
  border-left: 2px solid var(--accent);
  background: var(--paper-raised);
  font-size: 0.9375rem;
  line-height: 1.55;
}

.guide .btn {
  align-self: flex-start;
}

@media (max-width: 40rem) {
  .guide-cols {
    grid-template-columns: minmax(0, 1fr);
  }

  .guide-inner {
    padding: 1.5rem;
  }
}

/* =========================================================================
   THE CARD BORDERS FADE OUT AT TWO OPPOSITE CORNERS
   Sab, 2026-09-14: "can we have the corners be gradients so not perfect fulls
   im thinking the top left corner and botom right corner or something".

   A DRAWN BORDER PLUS A MASK, not four gradient backgrounds. The obvious route
   is background-image with padding-box/border-box clipping, which needs the
   card to own its background and falls apart the moment a card sits on a tinted
   band. Masking a pseudo-element that carries an ordinary 1px border keeps the
   border an actual border - it still follows the box, still hairlines at any
   width - and the mask only decides how much of it is painted.

   135deg RUNS THE GRADIENT LINE CORNER TO CORNER, top-left to bottom-right, so
   the two transparent ends land exactly on the two corners Sab named. The stops
   are asymmetric on purpose: 14% of fade against a 1px line is barely a
   suggestion, and the point is that the eye should notice the rectangle is not
   closed.

   -webkit-mask-image is still carried alongside the standard property. Safari
   only unprefixed mask-image in 15.4, and this is a clinic site.
   ========================================================================= */
.service,
.icon-card {
  position: relative;
}

/* The hairline stays --rule, the warm sand. It was briefly --rule with the
   accent mixed through it, on the theory that a card edge should belong to the
   palette rather than sit near it — but the accent is the cool end of this
   palette and 28% of it was enough to turn every card edge on the page a shade
   greener. That is one of the two things Sab meant by "drab". A warm line is
   what makes a white card on a white page read as warm rather than clinical —
   and now that every fill has come off these cards, this hairline and the mark's
   ring are the only warmth left in the band. It is load-bearing in a way it was
   not when there was a cream ground behind it to help. */
.service::before,
.icon-card::before {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid var(--rule);
  pointer-events: none;
  -webkit-mask-image: var(--corner-fade);
  mask-image: var(--corner-fade);
}

/* BIGGER, AND ON A GROUND. Sab, 2026-09-14: "can we make the icons larger?",
   then "can we also introduce the theme colors here as well?" — asked of the
   service card, and answered the same way in both places because these two
   marks are the same artwork doing the same job.

   THE ARGUMENT THIS OVERTURNS IS DIRECTLY ABOVE, and it is worth keeping rather
   than deleting: the marks were held to 3rem because the packs are rounded,
   heavy-stroke and illustrative against a page of hairlines, and at full size
   they would have been the loudest thing on it. That reasoning was about a bare
   mark floating on paper. A mark inside a tinted disc is a different object —
   the disc is what carries the weight, so the artwork can grow without becoming
   the loudest thing, and the growth now reads as deliberate rather than as an
   icon that got away.

   THE NUMBERS: 6.75rem box, 1.375rem of padding, so the artwork itself lands at
   4rem — a third larger than it was. box-sizing is border-box for everything on
   this page, so the padding is inside the box and the ring is exactly 6.75rem.

   THE BOX GREW FROM 6rem AND THE ARTWORK DID NOT, which is the correction and
   not a nudge. Sab, 2026-09-14: "for the icons it looks like some padding causes
   it to break as well can we fix that?" — with the billing mark, whose verified
   badge sits in the bottom-right of its own 64-unit viewBox.

   THE GEOMETRY, because "add more padding" is the intuitive fix and it is the
   wrong one. A SQUARE artwork inside a CIRCLE is only safe out to the inscribed
   square, and the artwork's corners reach s * sqrt(2) / 2 from the centre while
   the ring sits at half the box. Growing the ring buys clearance without
   shrinking the mark, which is why both rings moved and neither artwork did.

   MEASURE THE INK, NOT THE BOX — and this rule's first pass got that wrong.
   It budgeted the artwork's declared box and reported 2.7px of clearance here
   and 3.2px on .service-icon. Those numbers are void. Every mark in
   public/icons/gmc was then measured for real, by inlining each file and taking
   getBBox on its contents: the drawn ink is 84-89% of the viewBox on all 21, so
   the true pre-fix figures were 7.9px and 5.6px. Larger than claimed, and still
   not the point.

   THE POINT IS THE RATIO, NOT THE GAP. Sab, on the same marks a second time:
   "the icon is still being cliped can you see?" — pointing at svc-driving, whose
   badge sits in the bottom-right corner of its own viewBox. Nothing was touching
   anything: the measured air was 13.9px here and 11.5px on .service-icon. What
   he is seeing is the ink's circumscribed circle running at 0.74 of the ring's
   inner radius, which against a 1px hairline reads as tangency whether or not it
   is. A gap you can measure is not a gap you can see.

   SO THE BUDGET IS A RATIO AND IT IS 0.65. Half the ink's diagonal must sit
   inside 0.65 of the ring's inner radius; at 0.74 the mark crowds its ring and
   at ~0.45 it looks lost in it. Both rings were sized from that number with the
   artwork pinned to the pixel it already was: 6.75rem -> 7.75rem holds 62px of
   artwork and moves 39.1/53 = 0.74 to 39.1/61 = 0.64; .service-icon goes
   5.5rem -> 6.25rem, holds 50px, and moves 31.5/43 = 0.73 to 31.5/49 = 0.64.
   Measured live in the browser at both sizes, not derived.

   IF A MARK IS EVER REDRAWN, recompute the ratio rather than the padding, and
   measure the ink rather than trusting the viewBox — a viewBox says nothing
   about where the artist stopped drawing.

   THE RING IS THE ONE ROUNDED SHAPE IN THE CONTENT, and that is a real
   divergence, not an oversight: this page squares off everything. The footer
   CTA used to be cited here as precedent; it is a square button as of the same
   day, so this shape now stands on its own argument alone — a circle is what
   stops the mark's own outline reading as a second card nested inside the first,
   which is exactly what a rounded rectangle here would do.
   If it reads as foreign, `border-radius: 0` here and on .service-icon is the
   entire revert. */
.icon-card img {
  /* Sab, 2026-09-14, pointing at a guide-card ring: "please make the icons
     also slightly larger here". Artwork grows 64px -> 68px and the ring is
     re-derived from the ratio above, not padded: reach = 0.63 x 68 = 42.8px,
     ring >= 2 x (42.8 / 0.65 + 1px) = 133.9px, landed at 8.5rem. The ink's
     half-diagonal scales to 41.5px against an inner radius of 67 = 0.62 —
     still inside the budget, and the mark is measurably larger, not just
     further from its hairline. */
  width: 8.5rem;
  height: 8.5rem;
  padding: 2.125rem;
  /* A RING, NOT A DISC. The fill was --rule at 60% into cream and Sab called it
     "a really drab cream". It was: a desaturated warm neutral contributes only a
     value step, and a small value step off white reads as grime rather than as
     a ground. The line is the same token doing the job it is actually good at.
     transition here so the ring flips with the card rather than after it. */
  border: 1px solid var(--rule);
  border-radius: 999px;
  transition: filter 120ms ease;
  /* The marks are drawn on a 64-unit grid with their own internal padding, so
     they optically sit a touch high against a centred label. */
  display: block;
}

.icon-card span {
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.4;
}

.centred {
  align-items: center;
  text-align: center;
}

.row {
  display: flex;
  align-items: center;
}

.row.gap {
  gap: 1rem;
}

/* Every service card is the same height, not just the ones sharing a row.
   Default grid behaviour sizes each row to its own tallest card, which gave five
   rows of five different heights and read as a ragged list rather than a set of
   equal offerings. `grid-auto-rows: 1fr` forces one height across the whole grid,
   taken from the longest card. It costs whitespace under the short cards, which
   is the right trade: this band is a menu, and a menu whose items are different
   sizes implies a ranking the practice has not asked for. */
/* NO TINTED BANDS ON THE CONTENT, and this is the second half of a correction
   I made in two goes — the first go was me splitting a difference Sab had
   already closed.

   He said it twice. "lets ensure the background is the ideal white color as
   well pelase right now it looks abit drab" — and then, after I moved the
   document ground to #fff but kept cream on #services and #visiting, "the
   background still looks cream? it looks very strange to me", with a screenshot
   of the services band. Of course it did. #services is the tallest section on
   this page by a wide margin, so "cream on the card bands only" still means
   cream fills the screen for most of a scroll. A band that occupies most of the
   viewport is not a band; it is the ground with extra steps, and the area
   argument in the palette block condemns it exactly as it condemned the first
   version.

   WHAT I WAS PROTECTING, and why it was not worth paying for: a white card on a
   white ground has no value contrast, so the card has to be carried entirely by
   its hairline. That is a real cost. It is still the better one — the thing it
   replaces was a card and a ground about 2% apart in value, which does not read
   as "raised", it reads as smudged, and smudged is what he was pointing at.

   --paper-band IS NOT DEAD and must not be garbage-collected by the next reader.
   It carries the footer type and the guide dialog, which are the two places
   cream does unambiguous work: warm type on a dark field, and a modal with no
   page ground behind it to borrow. */
#services .grid-3 {
  grid-auto-rows: 1fr;
}

.service {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.5rem;
  /* The border moved to ::before so its corners can be masked — see THE CARD
     BORDERS FADE OUT AT TWO OPPOSITE CORNERS, above. */
  /* ONE WARM STEP OFF WHITE — and this time Sab asked for it himself. The
     pure-white rest state was his own arrival point after three drab rounds
     (CARD GROUNDS, above), but that verdict was reached on a plain white
     band; the ground is a textured one now, the ring field showing through,
     and against texture the card edge has a job the hairline was doing
     alone: "I think we do need abit of background speeration by making the
     service card colors slighly different", 2026-09-14. The step is the
     quietest warm one the tokens can make — 16% --rule into the raised
     paper, declared twice so engines without color-mix keep the white — and
     the lit state still takes the whole card to --accent-deep, so the
     inversion remains the loudest thing about it. */
  background: var(--paper-raised);
  background: color-mix(in srgb, var(--rule) 16%, var(--paper-raised));
}

/* NEARLY THE SAME AS .icon-card img AND STILL NOT THE SAME RULE, which is the
   point the separation was keeping open and it has now been used: both marks
   grew and both took the ring, but these sit at 8rem where the visiting
   cards go to 8.5rem. The visiting row is six centred cards where the mark is the
   subject; these sit left, above a heading, as the quiet part of a card whose
   subject is the text, and there are fifteen of them down one page rather than
   six across one row. The step narrowed from a full size to half a step
   because three rounds of the same "still clipped" reading made air the
   louder requirement — and air is bought with ring, never with mark.

   NO EXTRA GAP. The card's own 0.5rem does it. A mark set further from its
   heading than the heading is from its paragraph regroups the card into two
   parts, which is the opposite of what an icon is for here.

   margin-bottom: 0.25rem is the exception to that and is optical, not
   structural: the disc is a circle against a flush-left heading, so the 0.5rem
   gap measured to its bounding box reads tighter than the same gap under a
   square mark. */
.service-icon {
  display: block;
  /* THIRD PASS ON THE SAME PERCEPTION, Sab 2026-09-14 with a screenshot of
     svc-driving lit: "icons are clipped whats the padding inside of the icons
     for the service cards?" The artwork's own padding is 3.5 units of 64
     (~5.5%) a side, and svc-driving spends all of it corner to corner — so at
     0.62 of the inner radius its corners read as touching the 1px ring even
     with 21px of air on every diagonal. The budget moves again, bought
     entirely in ring: 8rem box, 2.25rem of padding, the artwork PINNED at the
     content box it already had (112 − 2×28 − 2 border = 54; 128 − 2×36 − 2 =
     54, the same 54). Ink half-diagonal 33.8px against an inner radius of 63
     = 0.54; worst-corner air 21px -> 29px. Measured live after the move. */
  width: 8rem;
  height: 8rem;
  padding: 2.25rem;
  margin-bottom: 0.25rem;
  /* Ring, for the reason given on .icon-card img. Fifteen of these down one
     page is where the drab fill was doing the most damage. */
  border: 1px solid var(--rule);
  border-radius: 999px;
  transition: filter 120ms ease;
}

/* This wrapper used to be the scroll reveal's target — a mask was parked on
   .is-masked and GSAP travelled it down the card. The reveal is a whole-card
   fade now (see the script in index.astro), so the wrapper is back to its one
   honest job: grouping the detail so the hover crossfade below has a single
   stacking context to swap layers inside.

   The fail-open rule that shaped the old comment survives the swap intact:
   NOTHING in this stylesheet hides a card. The entrance fade is gsap.from —
   the card's resting state here is fully visible, and only a running script
   with motion allowed ever hides it. Blocked JS, a dead CDN, a JS error three
   lines earlier, reduced motion — every failure resolves to a complete,
   readable card, and the oracle gates the stylesheet side of that. */
.service-detail {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.service h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  margin: 0;
  line-height: 1.15;
}

.service p {
  font-size: 0.875rem;
  max-width: 34ch;
}

/* Placeholder body, held back from the sourced descriptions sitting beside it.
   Greyscale forbids a grey, so this is opacity rather than a lighter ink: at a
   glance the real copy reads first and the lorem reads as pending, which is the
   honest hierarchy while ten of the fifteen cards are still waiting on words. */
.service .lorem {
  opacity: 0.45;
}

/* =========================================================================
   BULLETS AT REST, PARAGRAPH ON HOVER
   Sab, 2026-09-14: "can we add make the service cards bullet points then
   onhover wipe svg wipe to reveal a paragarph of text? attempt to include
   the same amount of text length and chars so that the cards all look the
   same".

   TWO LAYERS IN ONE CELL. The points are the resting body of the card; the
   paragraph occupies the same grid cell over them, masked out of existence
   at rest and wiped in on hover. The wipe is the same soft-edged diagonal
   the scroll reveal speaks — a mask gradient with a feathered stop — driven
   here by mask-position rather than GSAP, so there is no JS in the hover
   path: a script failure cannot take the resting content with it, the same
   failure shape .service-detail was built to guarantee.

   THE PARAGRAPH IS THE SAME TEXT ON ALL FIFTEEN, and that is the discipline
   speaking, not laziness. mockup-content.ts keeps sourced descriptions apart
   from placeholder because an earlier draft wrote plausible clinic copy and
   four of its claims were false. Uniform cards are bought with a uniform
   placeholder; the sourced facts moved up into the points.

   THE BULLETS ARE SQUARE. The icon ring is the one rounded shape in the
   content and it stands on a written argument; a round bullet dot would be
   a second circle asserting nothing.
   ========================================================================= */
.service-detail:has(.service-points) {
  display: grid;
}

.service-detail:has(.service-points) > * {
  grid-area: 1 / 1;
}

.service-points {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.45rem;
  align-content: start;
  transition: opacity 200ms ease;
}

/* THE CROSSFADE, NOT A GHOST. Sab, 2026-09-14, mid-review, with a screenshot:
   "it looks like its incorretly working here" — the paragraph was fading in
   OVER the points, so mid-transition both texts were readable at once. The
   points now fade OUT while the paragraph rises IN: one detail area, one text
   at a time, and the handover reads as a swap rather than a double exposure.
   Same selectors as every other lit rule, so the first-card-lit guard keeps
   working unchanged. */
a.service:hover .service-points,
a.service:focus-visible .service-points,
:where(.grid-3):not(:has(> a.service:hover, > a.service:focus-visible)) > a.service:first-child .service-points {
  opacity: 0;
}

.service-points li {
  position: relative;
  padding-left: 0.95rem;
  font-size: 0.875rem;
  line-height: 1.45;
}

.service-points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.4em;
  width: 0.4rem;
  height: 0.4rem;
  background: var(--accent);
}

.service-detail .service-more {
  margin: 0;
  /* A STEP SMALLER, 2026-09-14: "the service cards also have alot of white
     space is there anything we can do about that". The white space is this
     cell's height, which the grid sets to the TALLER of its two stacked
     texts — at rest the points show and this paragraph hides inside their
     cell, and every pixel it has beyond the points is the hole under the
     bullets. The slot cannot collapse (the hover reveal must not shift
     layout, and the uniform slot is the uniformity contract), so the lever
     is how tall the hidden text is: a step down in size and the full column
     width instead of the shared 34ch cap takes it from six wrapped lines to
     five, and the hole roughly halves. Supplementary copy that exists only
     on hover can be the smallest text on the card; it is, deliberately. */
  font-size: 0.8125rem;
  line-height: 1.45;
  max-width: none;
  /* NO BACKGROUND OF ITS OWN. Sab, 2026-09-14: "whent he user hovers over the
     services card primitive the text paragraph that reveals itself has a
     background we dont need it ot have one since the whole card changes the
     background color anyway". The panel dates from the wipe, when the
     paragraph slid over the points as a surface in its own right; the card
     now inverts to --accent-deep beneath it, so the paragraph needs nothing
     but its inverse ink. Removing the field also removes the last place the
     two texts could ghost through each other mid-crossfade. */
  color: var(--ink-inverse);
  /* FADE UP FROM THE BOTTOM, not a wipe. Sab, 2026-09-14, mid-review: "can we
     remove that svg wipe animation please for the services card", then "can we
     instead fade them in from the bottom so to speak". The reveal keeps its
     two states — hidden at rest, shown when the card is lit — and only the
     motion changed: opacity plus a short rise, instead of the travelling mask
     edge. Retiring the mask also retires its apparatus: no @supports gate (an
     opacity call predates every engine this page supports) and no no-mask
     failure mode to design for. */
  opacity: 0;
  transform: translateY(0.6rem);
  transition:
    opacity 320ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
}

a.service:hover .service-more,
a.service:focus-visible .service-more,
:where(.grid-3):not(:has(> a.service:hover, > a.service:focus-visible)) > a.service:first-child .service-more {
  opacity: 1;
  transform: none;
}

/* THE READ-MORE AFFORDANCE IS THE QUIET BUTTON PRIMITIVE, same shape as the
   guide cards' "More on this": underlined micro-label, no box. Sab,
   2026-09-14: "can we also have the uservice cards have a link to 'read more'
   or something like this?" The whole card is already the link — a real link or
   button nested in an <a> is invalid and double-fires — so this is a span
   wearing the primitive, an affordance for the eye and nothing more. Quiet is
   the right modifier for the same reason it is on the guide card: the least
   urgent control on the card does not carry a box, and at rest this one is the
   only thing on the card that admits the detail page exists.
   align-self because .service is a column flex; an inline-block child would
   otherwise stretch to the card's full width. */
.service .btn {
  align-self: flex-start;
}

/* Sits between the service grid and the allied-health group: facts that are
   services in substance but never get listed as one. */
.svc-note {
  font-size: 0.875rem;
  line-height: 1.6;
  max-width: 78ch;
  margin-top: 1.5rem;
}

/* THE TRUST MARKER BAND. Sab, 2026-09-14: "Also on this floor" and its tenancy
   chips go, replaced by "a 3 card overlapping section as a trust marker" in
   "the same color as whats in teh footer". The band takes var(--footer-field)
   — the footer's own token, not a copied hex — so band and footer can never
   drift apart. The override carries the same body:has() arithmetic as THE
   BANDS STEP ASIDE, one class stronger: this band must out-rank both the
   Section component's scoped white and that transparency rule.

   THE OVERLAP IS RETIRED. The cards launched straddling the seam — tops in
   the white section, bottoms on the dark — built to the letter of
   "overlapping". Seen in place the same hour: "can we not make it
   overlapping here as well can we do something else i think?" The straddle
   is gone — no offset, no negative margin — and the cards sit inside the
   band, so the three facts read as one quiet strip rather than as a seam
   dispute.

   THE INK DROPS BELOW THE FIELD, Sab 2026-09-15: "make it such that the
   circle background is bascially an z element that occupies the while
   document / page". The band's background used to be this rule's own —
   which put it ABOVE the document-wide ring layer and buried it, which in
   turn is why the band grew paper-tone rings of its own and the seam read
   as offset fragments. Now the section goes transparent and a ::before
   carries the ink at z-index -2, one step below the field's -1: the same
   document circles continue straight through the dark ground, light where
   difference flips them, and the band-local ring layer is deleted. The
   section is positioned (relative) so the pseudo resolves against the
   band, but keeps z-index auto so the -2 still lands in the root stacking
   context, under the field. */
body:has(.bg-rings) .mk-section.mk-section.trust {
  position: relative;
  background-color: transparent;
}

body:has(.bg-rings) .mk-section.mk-section.trust::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--footer-field);
  z-index: -2;
}

/* FOOTER LANGUAGE ON THE FOOTER'S FIELD. The cards are ghosts in the footer's
   own dialect — transparent ground, its 18% paper-band hairline (the same
   rule the footer's boxes and dividers draw), its ink — so the strip reads
   as the footer's voice arriving early rather than as a foreign panel bolted
   into the page. The one softening is opacity on the caveat line, the same
   move the footer's secondary text makes. */
.trust-card {
  border: 1px solid var(--footer-rule);
  padding: 1.5rem;
  color: var(--footer-ink);
}

/* THE MARK INVERTS WITH THE BAND. These marks are drawn in the page's dark
   ink — invisible on the footer's own field they now sit on. The same
   one-declaration flip the inverted service and guide cards use takes the
   drawing to solid white: brightness(0) flattens, invert(1) lifts. Alpha
   survives the filter, so the knocked-out counters stay knocked out. Small
   by design — a 2.5rem mark under a 1.25rem heading is a companion, not a
   second subject; the band's inverted arcs are the texture, not these. */
.trust-card img.trust-icon {
  display: block;
  width: 2.5rem;
  height: auto;
  margin-bottom: 0.6rem;
  filter: brightness(0) invert(1);
}

.trust-card h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  margin: 0 0 0.4rem;
}

.trust-card p {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.5;
  opacity: 0.75;
}

/* THE INVERTED FIELD, ON THE BAND. Sab: "can we make this section abit more
   interesting as well? perhaps we can make the circular background be
   inverted on here so we stack hte background of this sectiokn underneath?"
   The page's paper texture, re-voiced for the dark: the same hairline circle
   vocabulary at low alpha in the footer's paper tone, plus one dashed ring
   in the accent quoting the page field's one dashed ring. The band's own
   background stays underneath; the arcs stack on it and are clipped to the
   band, so the texture belongs to this strip and never bleeds onto the
   white sections either side. */
.trust {
  position: relative;
  overflow: hidden;
}

/* The band's own ring layer is gone (2026-09-15): the document field runs
   through the dark ground via the difference blend, so a second set of
   paper-tone circles here would be the very offset-fragment seam Sab
   flagged. The trust-ring-spin keyframes stay — the hero's dashed accent
   ring still turns with them. */

@keyframes trust-ring-spin {
  to {
    transform: rotate(1turn);
  }
}

/* THE BAND BECOMES A SPLIT, EQUAL HALVES, PHOTO FULL HEIGHT. Sab 2026-09-15:
   "canwe please have it be a 2 column grid with the 3 stacked into rows inside
   of the right section and a powerful image on th eleft? for now we can take a
   greyscale image", then "can we make this 2 col grid both equial width make
   the image full height ofthe section". The section takes the layout's .bleed
   variant — its mk-inner padding drops to zero and the split owns the air
   instead: the photograph runs the band's full height, edge to edge, and the
   card column carries the section padding that mk-inner surrendered. The
   photograph is a stand-in until the practice commissions one — the hero's
   own picture reused rather than another layout's, because the client is
   comparing five layouts and their photographs should not migrate. Greyscale
   is a filter, not a re-export, so the real photograph drops in with no
   reprocessing. The stacking duty (paint above the band's arcs) moved here
   from .trust-cards with the wrapper. */
.trust-split {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  min-height: 100%;
}

.trust-photo {
  margin: 0;
  overflow: hidden;
  position: relative;
}

/* THE PHOTOGRAPH IS A PARALLAX LAYER (2026-09-16, Sab: "can we make the
   image here parallax please like the hero section"). The hero's recipe,
   scaled to a mid-page figure: the img overscans 15% past both edges, and
   the page's scroll handler (the same rAF-throttled, reduced-motion-aware
   one that drives the hero) translates it with the figure's distance from
   the viewport centre, clamped to the overscan so no edge can ever show.
   Transform on the img, never background-position: compositor work, no
   repaints. Below 900px the figure is a stacked banner and the layer goes
   back to static flow — the flourish is a wide-screen one.

   QUIETENED SAME DAY (Sab: "the primitive here has too much parallax").
   The hero's 0.15 travel factor is calibrated against a full-viewport
   photograph; this band is a fraction of that height, so the same travel
   reads as a much larger share of the picture. The script now runs 0.06
   here while the hero keeps 0.15. The 15% overscan stays: it is what lets
   any factor stay edge-safe. */
.trust-photo img {
  position: absolute;
  inset: -15% 0;
  display: block;
  width: 100%;
  height: 130%;
  object-fit: cover;
  filter: grayscale(1);
  transform: translateY(var(--trust-parallax, 0px));
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .trust-photo img {
    transform: none;
  }
}

@media (max-width: 900px) {
  .trust-photo img {
    position: static;
    inset: auto;
    height: auto;
    transform: none;
  }
}

.trust-cards {
  display: grid;
  grid-auto-rows: 1fr;
  gap: 1rem;
  padding: clamp(2.5rem, 6vw, 6rem);
}

/* THE BOX BECOMES A RULE. Each of these is one fact with a caveat under it, and
   a border on four sides around eleven characters spends a lot of ink saying
   nothing — the box was the loudest thing in the group and the least meaningful.
   A single accent rule down the left marks the item, groups the name with its
   note, and lets the eye read three of them as a list rather than as three
   objects. */
.chip {
  border-left: 2px solid var(--accent);
  padding: 0.1rem 0 0.1rem 0.85rem;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  /* --accent-deep even though the tinted ground it was chosen against is gone.
     On white #376586 passes AA at 6.23:1, but these are 0.75rem uppercase chips
     carrying a fee caveat — the deepest step of the ramp is the same token the
     filled buttons use and measures 7.58:1 here, so the quietest text on the
     page is not also its faintest. */
  color: var(--accent-deep);
}

/* THE BADGE KEEPS ITS COLOUR. Sab: "for bulk billing can we use the bulk
   billing logo please". The other two trust marks are stock art flipped to
   inverse ink; a Medicare program badge is recognised BY its colours, so this
   one keeps them — which is also why it does not take the .trust-icon class:
   that rule carries the brightness(0) invert(1) filter that would blank it. */
.trust-card img.trust-badge {
  display: block;
  height: 2.25rem;
  width: auto;
  /* At 390 the cell's inner width (~302px) is narrower than the badge's
     natural width at this height (~313px); max-width caps it and object-fit
     keeps the cap from squashing the lockup. */
  max-width: 100%;
  object-fit: contain;
  object-position: left center;
  margin-bottom: 0.6rem;
}

/* THE SEE-ALL DOCTORS CELL. One invitation among seven profiles: footer-field
   ink so the cell reads with the trust band's inverted moment, and the AMC
   cross drawn in GMC dialect — one hairline stroke, square corners, nothing
   else. A dashed accent echo offset behind it read as misregistration at
   card scale (Sab: "this looks abit strange here can we make it simpler?")
   and is gone the same hour. Hover lifts the ground to --accent-deep, the
   lit-card move the services grid uses. */
.doctor-cta {
  background-color: var(--footer-field);
  padding: 1.5rem;
  /* The <a> fills the cell with flex-grow, not height:100% — the grid row's
     height comes from the tallest sibling and percentage heights against a
     grid item's child did not resolve, which left the group top-collapsed
     over a dead ink void (seen live, fixed same hour). */
  display: flex;
  transition: background-color 120ms ease;
}

.doctor-cta a {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  flex: 1;
}

/* .doctor.doctor-cta, not .doctor-cta alone: `.doctor h3 { margin: 0 }` ties
   the bare selector and wins by file order, which silently undid the seat-at
   -bottom auto margin (seen live 2026-09-14). */
.doctor.doctor-cta h3 {
  color: var(--footer-ink);
  margin-top: auto;
}

.doctor-cta .cta-cross {
  width: 7rem;
  height: 7rem;
}

.doctor-cta .cta-cross path {
  fill: none;
  stroke: var(--footer-ink);
  stroke-width: 1;
}

.doctor-cta .cta-note {
  color: color-mix(in srgb, var(--footer-ink) 72%, transparent);
  font-size: 0.875rem;
}

.doctor-cta:hover,
.doctor-cta:has(a:focus-visible) {
  background-color: var(--accent-deep);
}

.doctor {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.doctor h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.125rem;
  margin: 0;
  line-height: 1.2;
}

.bio {
  font-size: 0.875rem;
  max-width: 30ch;
}

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

.doctor-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: auto;
  padding-top: 0.5rem;
}

/* =========================================================================
   ONE BUTTON PRIMITIVE, THREE MODIFIERS
   Sab, 2026-09-14: "we need to also ensure all buttons follow a similar
   primitive here".

   WHAT HE WAS LOOKING AT. Nepean had four button spellings and no two of them
   were related in the source. `.btn` and `.btn-quiet` shared a metrics rule but
   were otherwise independent; `.btn-lg` was written three hundred lines further
   down, under a "per-doctor profile pages" heading, repeating the type scale
   from memory at 0.8125rem/0.12em rather than 0.75rem/0.1em; and `.ic-more`,
   added the same day for the guide cards, was a fourth set of type metrics that
   happened to also be an underlined accent control. The markup matched the
   mess — `class="btn"`, `class="btn-quiet"`, `class="btn-lg"` and
   `class="ic-more"` were four alternatives, not a base plus modifiers, so a
   page could carry a button that inherited nothing.

   WHAT IT IS NOW. `.btn` is the primitive and carries everything every button
   on this page shares: the uppercase micro-label, the tracking, the box
   metrics, the accent, the pointer. The other three are MODIFIERS and require
   it — `class="btn btn-quiet"`, `class="btn btn-lg"`. A bare modifier now looks
   obviously wrong rather than quietly different, which is the property worth
   having: the next button someone adds either joins the primitive or fails to
   look like the page.

   `.ic-more` IS GONE, not renamed. It was `.btn btn-quiet` with different
   numbers, and the guide button is now spelled that way in IconCard.astro. Its
   one real argument — that a dialog opener is the least urgent control on the
   card and should not carry a box — is exactly what `.btn-quiet` means, so the
   argument survives and the duplicate declaration does not.

   `font: inherit` FIRST, then font-size. The shorthand resets size, so the two
   in the other order would silently drop the 0.75rem. It is here at all because
   half of these are `<button>`, which otherwise takes the UA's own font.

   THE OUTLINE IS DRAWN ON A PSEUDO so it can carry the corner fade, for the
   same reason `.service` and `.icon-card` do: a mask applies to the whole
   element, so masking `.btn` itself would dissolve its LABEL at the corners
   along with its border.
   ========================================================================= */
.btn {
  position: relative;
  display: inline-block;
  border: 0;
  background: none;
  padding: 0.55rem 1rem;
  font: inherit;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-ink);
  cursor: pointer;
  /* Same transition register as the header CTA this primitive now mirrors. */
  transition:
    background-color 120ms ease,
    color 120ms ease;
}

.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid var(--accent-ink);
  pointer-events: none;
  /* FULL BORDER (Sab, 2026-09-16): the corner-fade mask made the box read
     as unfinished — a button border should be a border. The fade stays on
     the cards and the guide dialog, which are compositions, not controls. */
}

/* The colour is repeated at (0,1,1) for links, which is a specificity fix
   rather than a style. Several containers reset their links with `.doctor a`,
   `a.service`, `.svc-siblings a` — all (0,1,1) — so the `.btn` declaration
   above is silently dropped inside exactly the cards carrying the most buttons;
   the first version of this rule was live and changed not one button on the
   homepage. It stays on `.btn` as well, because `<button class="btn">` is not
   an `a` and would otherwise take the UA's text colour. */
a.btn {
  color: var(--accent-ink);
}

/* THE HOVER IS THE HEADER'S GHOST-BECOMING-PRIMARY, PROPAGATED (Sab,
   2026-09-16: "make sure there is the same hover state ... the primitive in
   header should be propagated" — .hdr-book's registered treatment). The box
   fills with the deep accent and the label flips to its inverse, 120ms;
   focus-visible shares hover on purpose. Both tokens live in :root, so no
   fallbacks — and never currentColor for the fill: inside this very rule the
   label has already flipped, so currentColor would fill with the NEW label
   colour and read white-on-white (the exact trap documented on .hdr-book). */
.btn:hover,
.btn:focus-visible {
  background: var(--accent-deep);
  color: var(--ink-inverse);
}

/* QUIET STAYS QUIET: the modifier's contract is a control that should not
   compete — no box, so no fill either. It keeps the underline and darkens,
   as it always did. */
.btn-quiet:hover,
.btn-quiet:focus-visible {
  background: none;
  color: var(--accent-deep);
}

/* QUIET — the modifier for a control that should not compete. No box, so the
   drawn edge is suppressed rather than restyled; underlined, so it still reads
   as something you can press. `!important` on the underline because the same
   (0,1,1) link resets above set text-decoration: none, and this modifier has to
   win inside them. */
.btn-quiet::before,
.btn-lg::before {
  display: none;
}

.btn-quiet {
  padding-inline: 0;
  text-decoration: underline !important;
  text-underline-offset: 0.25em;
}

/* LARGE — the filled, single-most-important action on a page: Book online, and
   the one button on a subpage that is not a link back into the page it came
   from. It was 0.8125rem/0.12em standing alone; it keeps that slightly larger
   type because it is the only button on the page that has to carry across a
   band, but it now inherits everything else from the primitive.

   Both colour rules are stated at (0,1,1) and (0,2,0) for the same reason the
   accent is above: `a.btn` would otherwise repaint the label of a filled button
   accent-on-accent-deep, which is legible enough to miss in review and wrong. */
.btn-lg {
  border: 1px solid var(--accent-deep);
  background: var(--accent-deep);
  color: var(--ink-inverse);
  padding: 1rem 1.75rem;
  font-size: 0.8125rem;
  letter-spacing: 0.12em;
  text-decoration: none;
}

a.btn-lg {
  color: var(--ink-inverse);
}

.btn-lg:hover,
.btn-lg:focus-visible {
  background: var(--accent-ink);
  border-color: var(--accent-ink);
  color: var(--ink-inverse);
}

nav.row span {
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}

.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: stretch;
  flex: 1;
}

/* A stack inside a stretched split still centres its own content vertically. */
.split > .stack {
  justify-content: center;
}

/* OPENING HOURS. Three rows of day-range against time, replacing the Box that
   stood in for them. A hairline above each row rather than a box around the set:
   the page's whole grammar is rules, and a bordered block here would have read
   as a fourth card in a section that has none.

   THE TIME IS THE SUBJECT, so it hangs right at a larger size in the accent ink
   while the day-range stays small and uppercase on the left. Both sit on one
   baseline, which is what makes three rows scannable as a column of times rather
   than read as three sentences.

   NOT THE ACCENT FACE, and the oracle is what settled that. The first version
   set the times in var(--font-accent) on the reasoning above — they are the
   subject, so give them the display face — and the accent-face budget check
   failed the build: three lines where the page allows two, the hero greeting and
   the footer sign-off. That budget is the reason the two lines that do have it
   land at all, and spending a third on an hours table would have been the exact
   erosion it was written to stop. Size and colour carry the emphasis instead,
   which is what the rest of this page does anyway.

   NOT text-transform: uppercase. Every other small label on this page is, and
   this one is deliberately not: "8:30am - 5:00pm" uppercased is "8:30AM -
   5:00PM", which is how their current site sets it and is the one place where
   matching the source would have made ours worse. */
.hours {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.hours-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 0.85rem 0;
  border-top: 1px solid var(--rule);
}

.hours-row:last-child {
  border-bottom: 1px solid var(--rule);
}

.hours dt {
  font-size: 0.8125rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.hours dd {
  margin: 0;
  font-size: 1.0625rem;
  font-weight: 500;
  color: var(--accent-ink);
  white-space: nowrap;
}
/* The profile detail blocks reuse dl.hours for availability, and their dd is
   a sentence (lorem until the practice supplies days), not a time range.
   nowrap there measured a 554px min-content inside a 308px column on a 390px
   phone, sizing the collapsed grid-3 track to 554 and pushing the page 205px
   sideways. Time ranges elsewhere keep the nowrap; sentences wrap. */
#profile-detail .hours dd {
  white-space: normal;
}

/* THE MAP FILLS ITS HALF OF THE SPLIT, which is why there is no aspect-ratio
   here: .split is align-items: stretch, so the iframe takes the height of the
   column beside it and the two halves end flush. min-height is the floor for the
   one case that breaks — the 900px stack, where there is no neighbouring column
   left to take height from.

   border, not none: an unbordered map butts Google's own white tiles against the
   page's white ground and the block loses its edge entirely. The hairline is the
   same --rule every other edge on this page uses, so the map reads as one more
   framed thing rather than as a hole in the layout. */
.map-embed {
  width: 100%;
  height: 100%;
  min-height: 22rem;
  border: 1px solid var(--rule);
  display: block;
}

/* INSIDE THE BUILDING, BALANCED (2026-09-16, Sab: the section read as a
   narrow text column in a wide field — "super empty", the ring fix made
   the whitespace plainer by cleaning it). The practice's own entrance
   photo takes the right half the way the map takes the right half of the
   section above: the figure is a flex column under the stretched split,
   the image flexes to fill the column so the halves end flush, and the
   same --rule hairline frames it. Natural colour, not the greyscale the
   hero and trust band carry: this asset's nearest siblings are the framed
   route photos on Getting Here, which run natural too. min-height is the
   900px stack floor, same as the map; stacked, flex:1 of an auto-height
   figure falls back to the image's own aspect ratio, so no media query. */
.split-building .building-photo {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.split-building .building-photo img {
  width: 100%;
  flex: 1;
  min-height: 22rem;
  object-fit: cover;
  border: 1px solid var(--rule);
  display: block;
}

/* NARROW MEASURES IN WIDE FIELDS (2026-09-16, Sab: "is this intentional? it
   seems to be a significant amount of right white space by default" — the
   About page read as a 64ch column stranded left in a full-bleed section).
   The audit found the same class on four surfaces. Two terminal states:

   - COMPANION SPLIT (about, new-patients, feedback, service detail): the
     measure keeps its left hang and the right half takes real or reserved
     content — photo slots (ImagePending), the complaints-pathway hold.
     The site's left-edge grammar stays intact; centring a column under a
     left-hung heading would break it.
   - CENTRED MEASURE (legal only): pure prose with no companion and no
     section heading of its own, so the column centres. A headingless
     measure stranded left reads as a bug, not a decision.

   The old .about wrapper (max-width 64ch, left-stuck) is gone with the
   composition that needed it; the story stack now rides the standard
   .stack rhythm inside the split. */
.split-feedback .complaints-hold {
  margin-top: 0;
  align-self: start;
}

.split-building .building-photo figcaption {
  font-size: 0.875rem;
  line-height: 1.5;
  opacity: 0.6;
  font-style: italic;
  margin-top: 0.625rem;
}

.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

.grid-4 {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
}

@media (max-width: 900px) {
  .split,
  .grid-3,
  .grid-4,
  .trust-split {
    grid-template-columns: 1fr;
  }

  /* Stacked, the photo would take its natural (tall) height and shove the
     three facts below the fold; the crop keeps it a banner. */
  .trust-photo img {
    height: auto;
    aspect-ratio: 16 / 10;
  }

  nav.row {
    display: none;
  }
}

/* --- hero ---
   The hero is type on a transparent background: no pills, no cards, no boxes,
   no panel behind the words, and no eyebrow above the h1. The type sits directly
   on the photograph. That rule holds for every hero in every layout, so a hero
   is only ever a heading, a supporting line and whatever the image is doing. */

/* The header floats over this band again (Sab, 2026-09-10: "all variations will
   have this transparent look"), having spent a few hours in the flow on a white
   band of its own. It costs this hero nothing, because the band runs the full
   viewport and align="end" puts the type at the bottom — the header has the top
   of the photograph entirely to itself. The padding here is breathing room, not
   clearance.

   The pages that DO pay for it are the ones whose first element starts at the
   top of the page: #profile below, and the shallow banner on Mulgoa. */
#hero .mk-inner {
  padding-top: clamp(4rem, 10vh, 7rem);
}

/* The doctor profile opens on a breadcrumb and an h1 on white, with no hero to
   absorb the floating header, so it has to reserve the room outright. */
#profile .mk-inner {
  padding-top: clamp(9rem, 17vh, 12.5rem);
}

/* --- the hero photograph ---------------------------------------------------
   The band was type on white. It is now type over a photograph, because the
   thing the client is actually choosing between is not two arrangements of
   text: it is whether the first screen sells the place or serves the patient.
   Nepean argues the first, and it cannot make that argument without an image.

   THE FILE IS GREYSCALE ON DISK, not colour under a filter. `filter:
   grayscale(1)` is one failed stylesheet away from a full-colour photograph
   landing in the middle of a strictly black-and-white wireframe, and the whole
   point of the greyscale rule is that nobody argues about a hue that is not
   there yet. Baking it in also halves the file. The oracle asserts it by
   reading the JPEG's component count, which is 1 for greyscale and 3 for
   colour, so the check fails if anyone drops a colour file in here.

   IT IS A PLACEHOLDER AND IT IS NOT A PHOTOGRAPH OF THIS PRACTICE. It stands in
   for the shot the practice still owes us. Provenance and the audit that
   quarantined the rest of the set are in public/images/placeholder/README.md. */
/* THE PHOTOGRAPH IS A LAYER RATHER THAN A BACKGROUND, so that the parallax can
   be a transform. Animating background-position on a full-bleed image repaints
   it every frame; translating a layer is handed to the compositor.

   `overflow: hidden` is load-bearing, not tidiness: the layer is deliberately
   taller than the band and would otherwise spill over the section below it. */
#hero {
  overflow: hidden;
}

/* Inset -15% top and bottom, so the layer stands 15vh proud on each side. The
   shift applied in index.astro is clamped to exactly 15vh for that reason — at
   full travel the top edge lands flush and never reveals the band behind it.
   The overscan and the travel are the same number on purpose.

   --hero-parallax is written by script and defaults to 0 here, which is also
   what a reduced-motion visitor gets: the script never writes the property, so
   this declaration is the whole behaviour rather than a fallback. */
#hero::before {
  content: "";
  position: absolute;
  inset: -15% 0;
  background-image: url("/images/placeholder/hero-nepean.jpg");
  background-size: cover;
  background-position: center 58%;
  background-repeat: no-repeat;
  transform: translate3d(0, var(--hero-parallax, 0px), 0);
  will-change: transform;
}

/* The scrim. NOT a background for the text, which is the distinction that
   matters: the type has no panel, no card and no fill behind it, it sits
   directly on the photograph, and this gradient only keeps the photograph from
   being arbitrarily bright underneath it. It is weighted to the bottom because
   the band is align="end" and the type is down there, and it lifts again at the
   very top because the header floats over the same image. Through the middle it
   is nearly clear, which is where the photograph gets to be a photograph.

   IT IS TWO LAYERS NOW, AND ONLY THE TOP ONE IS THE SCRIM. Sab, 2026-09-14:
   "for the hero image as well can we please have a subtle color overlay or
   gradient?" — so the second layer is a brand wash and the first keeps doing
   the legibility job it was already doing. Splitting them is the whole trick:
   tinting the scrim itself would have meant strengthening the tint wherever the
   type needed more cover, which is exactly backwards, because the type sits at
   the bottom and the wash is most visible through the clear middle.

   THE PHOTOGRAPH IS GREYSCALE ON DISK, which is what makes a wash this weak
   read at all — 22% of --accent-deep over a neutral grey is a visible cast,
   where over a colour photograph it would be mud. The file stays greyscale; the
   colour is entirely in this rule and is one line away from being gone.

   NO LITERALS. The scrim was rgba(0,0,0,a) and the wash cannot be, because a
   brand colour written here as a hex would be a palette value living 1400 lines
   from :root — which the oracle fails on, correctly. color-mix() takes the alpha
   off the token instead, so these layers track --ink and --accent-deep and there
   is still exactly one place the palette is defined.

   THE SCRIM MOVED FROM BLACK TO --ink and got DEEPER, and the second half is
   the part worth reading. A wash costs legibility — it lifts the darkest pixels
   under the type — so the scrim had to pay it back before the wash could ship.
   The property held to was "no line of hero type may read worse than it did
   under the old black scrim", and it was measured rather than assumed:
   the photograph was sampled through the real ::before geometry (cover, centre
   58%, inset -15%), both gradient layers composited over each sampled pixel,
   and the WORST pixel inside each text box taken. Against the old black scrim,
   per line: 1.69 / 1.94 / 4.27 / 2.92 / 3.19 / 5.90 / 15.1. At these stops:
   2.01 / 2.34 / 4.66 / 3.30 / 3.60 / 5.87 / 11.08. Better on five of seven,
   unchanged on one, and the one that fell is a claim line at 11:1.

   THOSE FIRST NUMBERS ARE NOT PASSES AND THEY WERE NOT PASSES BEFORE. White
   type at 2.3:1 over the brightest part of a sky is a real legibility problem,
   it predates this rule, and it belongs to the PHOTOGRAPH — a placeholder that
   is far too bright behind the h1. The fix is the real practice photograph, or
   a darker crop, not more scrim; the scrim is already carrying more than it
   should. Recorded here so the next person does not read these stops as a
   contrast solution. */
#hero::after {
  content: "";
  position: absolute;
  inset: 0;
  /* FALLBACK FIRST, and it is the scrim this replaced. color-mix() is Chrome
     111 / Safari 16.2 / Firefox 113, so a browser older than that takes this
     declaration and the next one is simply dropped. The failure mode is then
     "no tint", never "no scrim" — the type keeps its cover either way, which is
     the only part of this rule that is not decoration. */
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.72) 0%,
    rgba(0, 0, 0, 0.46) 30%,
    rgba(0, 0, 0, 0.12) 58%,
    rgba(0, 0, 0, 0.34) 100%
  );
  background:
    linear-gradient(
      to top,
      color-mix(in srgb, var(--ink) 80%, transparent) 0%,
      color-mix(in srgb, var(--ink) 58%, transparent) 30%,
      color-mix(in srgb, var(--ink) 20%, transparent) 58%,
      color-mix(in srgb, var(--ink) 42%, transparent) 100%
    ),
    linear-gradient(
      118deg,
      color-mix(in srgb, var(--accent-deep) 22%, transparent) 0%,
      color-mix(in srgb, var(--accent) 14%, transparent) 46%,
      color-mix(in srgb, var(--accent-deep) 6%, transparent) 100%
    );
  pointer-events: none;
}

/* Above the scrim, and knocked out white. */
#hero .mk-inner {
  position: relative;
  z-index: 1;
  color: var(--ink-inverse);
}

/* THE HERO'S RINGS, INVERTED — see the markup note in index.astro. Strokes
   are the trust band's inverted palette, lifted for a photograph: 32% paper
   where the band sits on flat ink, the dashed ring at 45%. z-index 1 is the
   whole trick: it beats the ::before photograph and ::after scrim (both
   z-auto) and ties with #hero .mk-inner, which wins the tie by coming later
   in the tree — above the picture, below the words. The accent ring reuses
   trust-ring-spin rather than minting a second 120s keyframe for the same
   motion. overflow: hidden clips the big sweep that runs off-canvas by
   design. */
.hero-rings {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  /* 100vh matches #hero's min-height floor; the scroll handler's fit() snaps
     this to the band's real height when content pushes it taller. */
  height: 100vh;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}

.hero-rings circle {
  fill: none;
  stroke: var(--paper-band);
  stroke: color-mix(in srgb, var(--paper-band) 32%, transparent);
  stroke-width: 1;
}

.hero-rings .hero-ring--accent {
  stroke: var(--accent);
  stroke: color-mix(in srgb, var(--accent) 45%, transparent);
  stroke-dasharray: 3 6;
  transform-box: fill-box;
  transform-origin: center;
  animation: trust-ring-spin 120s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .hero-rings .hero-ring--accent {
    animation: none;
  }
}

/* The corner label is wireframe chrome and stays black on white on purpose: it
   is not part of the design, and it should not quietly start looking like it. */

/* The name was briefly set to fill the band at 12vw. It was too large, because at that
   size the practice name stops reading as a name and starts reading as a poster,
   and it crowded everything under it. There is no #hero h1 override any more: the
   heading takes the global h1 size, tracking and line-height it had before. */

/* `#hero .accent` sized the "Welcome to" eyebrow and went with it. Nothing else
   in the page uses that selector, so it is deleted rather than left dormant. */

/* The third line of the same left edge. "make it all lined up on the left"
   (Sab, 2026-09-13) — so the lede hangs off the h1's ink too, not off the
   container. Its L starts 1.42px inside its box against the G's 3.83px, which
   left it 2.41px proud of the name; small enough to pass unnoticed on its own
   and not small enough to survive sitting under a corrected greeting.
   0.124em is that 2.41px in lede ems. This one buys less than the greeting's
   does and it is worth saying so: the lede sits at its 17px floor until 1259px,
   while the h1 it is chasing keeps growing, so the correction over-shoots at
   the narrow end. Across the same sweep the worst drift goes from 2.48px to
   1.21px — better, not solved. Same caveat as the accent above: it is a
   measured property of Arial, and it dies with Arial. */
#hero .lede {
  font-size: clamp(1.0625rem, 1.35vw, 1.375rem);
  max-width: 52ch;
  margin-top: 0.75rem;
  margin-left: 0.124em;
}

/* THE HERO IS A SPLIT: the greeting and the name left, the offerings right.
   Sab, 2026-09-13 — and it was built the other way round first, on the
   reasoning that a patient should meet "open 7 days, bulk billing" before the
   practice name. Sab flipped it within the hour. He is right: the name is the
   one thing a hero is obliged to say, and burying it behind a scan column made
   the offerings do work the services section below already does properly.
   The claims stay narrower than the copy because they are scanned rather than
   read, and both columns hang off the same baseline so the band still reads as
   one block sitting on the photograph. */
.hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: end;
  width: 100%;
}

/* Hairline-ruled type, NOT pills and NOT a panel. The distinction is the whole
   reason these were allowed back into the hero: a rule above a line of text is
   typography, a filled capsule around it is furniture. White at alpha rather
   than a palette token, because this sits on a photograph and has to stay
   legible whatever the photograph is doing underneath it.

   justify-self: end pins the rules to the hero's right edge instead of letting
   a 28ch block float in the middle of a wider column. The type inside stays
   left-aligned — a ragged left edge under a hairline reads as a mistake. */
.hero-claims {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  max-width: 28ch;
  justify-self: end;
}

.hero-claim {
  border-top: 1px solid rgba(255, 255, 255, 0.32);
  padding-top: 0.7rem;
}

.hero-claim b {
  display: block;
  font-weight: 600;
  font-size: 0.98rem;
  line-height: 1.25;
}

/* Rows with no sourced second line simply have no span. The list is built to
   look deliberate when that happens, because a visible gap is the point. */
.hero-claim span {
  display: block;
  margin-top: 0.2rem;
  font-size: 0.8125rem;
  line-height: 1.4;
  opacity: 0.78;
}

@media (max-width: 900px) {
  /* Single column, so the claims land under the name — the same order the DOM
     already has, with no reordering to keep in sync. Two-up because four
     full-width rows plus the name and the lede do not fit a 100vh band on a
     phone, and the hero losing its bottom edge is worse than tighter claims.
     justify-self reverts because there is no wider column to hang off. */
  .hero-split {
    grid-template-columns: 1fr;
    gap: 1.75rem;
  }

  .hero-claims {
    max-width: none;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.75rem 1.25rem;
    justify-self: stretch;
  }
}

/* --- per-doctor profile pages --- */

/* .crumb is gone with the two back-links it styled — see the note in
   services/[slug].astro. It set type metrics and never set a colour, which is
   how a UA-blue link ended up sitting on a photographic hero; that is worth
   recording because the rule looked complete and the omission was the bug. No
   Nepean page carries the class now, so the rule goes rather than waiting
   around for a caller. The other four layouts have their own sheets and their
   own crumbs, and neither is touched by this. */

/* .btn-lg moved up into the button primitive block, where it is a modifier on
   .btn rather than a fourth independent button. It was written here because the
   per-doctor page was the first thing that needed it; that is not a reason for
   it to live under a "per-doctor profile pages" heading now that five pages
   use it. */

/* Subpages have no hero for the header to float over, so the first band
   clears it explicitly. */
#profile .mk-inner {
  padding-top: 12rem;
}

/* Each allied-health chip carries its own hours/billing caveat. The whole
   point is that these are not the clinic's own services. */
.chip {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

/* THE NOTE COMES OUT OF UPPERCASE. It inherited the name's casing and tracking
   because it sits inside the same element, so "about two days a week" was set
   like a label when it is a sentence — and uppercase costs most exactly where
   the words are least predictable. Name and note now differ in case, colour and
   size, which is three ways of saying "these are not the same kind of thing"
   and lets the note keep a readable size. */
.chip em {
  font-style: normal;
  font-size: 0.8125rem;
  text-transform: none;
  letter-spacing: 0.01em;
  color: var(--ink);
  opacity: 0.72;
}

/* =========================================================================
   INNER PAGE HEADER CLEARANCE
   SiteHeader is position: absolute with no fill (see SiteHeader.astro), so the
   first section of every inner page sits visually behind it. The clearance
   is reserved once, in one rule, on every page that wears the .innerpage
   body class. Subsequent sections keep the natural .mk-inner padding - the
   header has scrolled away with the page by then.
   ========================================================================= */
.innerpage > .mk-section:first-of-type .mk-inner {
  padding-top: clamp(9rem, 17vh, 12.5rem);
}

/* =========================================================================
   SERVICES INDEX
   Same .service cards the homepage already renders, inside a vertical stack
   of group bands. The card itself is unchanged from #services .service - all
   this adds is the grouping container and an anchor variant for the index,
   where each card links to its detail page.
   ========================================================================= */
.svc-groups {
  display: flex;
  flex-direction: column;
  gap: clamp(2rem, 4vw, 3.5rem);
}

.svc-group {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.svc-group-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  margin: 0;
  line-height: 1.15;
}

.svc-group .grid-3 {
  grid-auto-rows: 1fr;
}

@media (max-width: 900px) {
  .svc-group .grid-3 {
    grid-template-columns: 1fr;
  }
}

/* Service cards link to the detail page — on the services index, and since
   2026-09-14 on the homepage too. Sab: "for each service can we also ensure the
   user is able to click onto". The card itself inherits the #services .service
   treatment, so only the link-specific behaviour is here.

   THE WHOLE CARD IS THE LINK, not a "read more" at the bottom of it. Fifteen
   cards with fifteen trailing links is fifteen more things to read in a band
   whose job is to let someone find one service quickly, and a card that looks
   clickable but only responds at one line is the more annoying failure.

   THREE SIGNALS THAT IT IS CLICKABLE, because one is not enough when the
   element is an entire card: the cursor, the heading's underline, and the
   ground lifting from the 4% wash to the 10%. The ground is the one that
   carries at a glance — it is what tells you the card under the pointer is a
   single object rather than a heading that happens to be underlined.

   focus-visible is on the same rule as hover and that is deliberate: this is a
   link, it is reachable by Tab, and a keyboard reader gets the same "this card
   is the target" feedback a mouse reader does. The focus ring is NOT suppressed
   here — the ground change is in addition to it, not instead of it. */
a.service {
  color: inherit;
  text-decoration: none;
}

/* THE HOVER IS AN INVERSION. Sab, 2026-09-14: "cant we jsut make it an outline
   then on hover invert the colors of the selected card?" — and then, of the
   #visiting row, "can we also do the same for this please".

   WHY THIS BEATS THE TINT IT REPLACES. The old hover was a warm neutral mixed
   to sit between the resting card and the disc on it, which is a value that has
   to be tuned to a hundredth and is still nearly invisible. Inverting needs no
   new value, reuses two tokens the page already has, and the step is maximal —
   there is no question about whether the pointer is on a card.

   BOTH CARD TYPES, AND THEY ARE NOT THE SAME AFFORDANCE. .service is a link and
   the whole card is the target, so hover and focus-visible are the honest pair.
   .icon-card is NOT a link — its only control is the "More on this" button
   inside it — so it takes :focus-within instead, which lights the card when the
   button inside it is tabbed to. Worth knowing that this is the one place on the
   page where a hover response does not mean "click anywhere here"; the button
   stays the visible target and is what a reader actually aims at. */
a.service,
.icon-card {
  transition:
    background-color 120ms ease,
    color 120ms ease;
}

/* THE INVERTED FIELD IS --accent-deep, NOT --ink. Sab, 2026-09-14, looking at a
   hovered card: "i think this color is still too dark here can we do anything?"
   It was #12312d, which is the page's text colour and is near-black — inverting
   to it gives a maximal step and a card that reads as a hole in the page rather
   than as a highlighted card. #2d5876 is the same token the filled buttons use,
   so a lit card and a primary button now agree, and it is a colour rather than
   an absence of one.

   IT COSTS ALMOST NOTHING IN CONTRAST because the field was never the constraint
   — white on #2d5876 is 7.58:1, which is AA for body text at any size, against
   14:1 on the old near-black. Both pass; one of them is a design and the other
   is a maximum.

   IF IT IS STILL TOO DARK, --accent-ink (#376586, 6.23:1 under white) is the one
   token step left before the field stops carrying white type at all. Below that
   the inversion has to give up white and the whole affordance is a different
   design, not a lighter value — #6e9dbe is 2.9:1 and cannot hold text. */
/* =========================================================================
   FIRST CARD LIT AT REST, AND ONLY ONE LIT AT A TIME
   Sab, 2026-09-14: "can we make the first card already highlighted as well?
   like this for both areas and when the user hovers over another one it
   dehovers the first one?"

   THE MECHANISM IS FALLING OUT OF A MATCH, NOT A SECOND STATE. Rather than
   invent a .is-default-lit class with rules to add and remove it, the lit
   condition itself grew one more way to be true: the first card of each area
   is lit while its grid contains NO hovered or keyboard-focused card. Hover
   the second card and the first card simply stops matching — it falls back
   to the resting styles every card already has — and pointing anywhere else
   puts it back. One truth, no toggling, and the resting default survives a
   script failure the same way every static state on this page does: it is
   CSS, all the way down.

   :has() IS BASELINE since late 2023 and this site already gated a
   progressive enhancement on mask support; if a browser lacks :has() the
   selector fails whole and the first card rests unlit — the page reads
   exactly as it did before this ask, nothing half-lit. :where() wraps the
   grid so the wrapper contributes no specificity and the lit rule stays
   where the cascade put it.
   ========================================================================= */
a.service:hover,
a.service:focus-visible,
.icon-card:hover,
.icon-card:focus-within,
:where(.grid-3):not(:has(> a.service:hover, > a.service:focus-visible)) > a.service:first-child,
:where(.grid-3):not(:has(> .icon-card:hover, > .icon-card:focus-within)) > .icon-card:first-child {
  background: var(--accent-deep);
  color: var(--ink-inverse);
}

/* THE PLACEHOLDER COPY HAD TO BE LIFTED TO PAY FOR THE LIGHTER FIELD, and this
   is the only place the field change is not free. .lorem is held at 0.45 so
   pending copy reads as pending, and 0.45 white over the old near-black measured
   4.07:1. The same 0.45 over #2d6f76 is 2.44:1, because a lighter field leaves
   less room beneath white. 0.78 puts it back to 4.20:1 — at or above where it
   was — and a card whose real copy is solid white still shows the lorem as the
   quieter of the two, which is the entire job of this rule. When the ten
   remaining cards get real words, this whole pair goes. */
a.service:hover .lorem,
a.service:focus-visible .lorem,
:where(.grid-3):not(:has(> a.service:hover, > a.service:focus-visible)) > a.service:first-child .lorem {
  opacity: 0.78;
}

/* EVERY DESCENDANT THAT SETS ITS OWN COLOUR HAS TO BE NAMED, because inheritance
   only carries the ones that did not. .lorem is deliberately absent from this
   list: it is held back with opacity rather than a lighter ink precisely so it
   follows whatever colour it lands on, and it does. .ic-blurb and the quiet
   button both state a colour and would otherwise stay dark ink on dark ink —
   the blurb invisible, the button at about 1.5:1 against its own card. */
.icon-card:hover .ic-blurb,
.icon-card:focus-within .ic-blurb,
.icon-card:hover .btn,
.icon-card:focus-within .btn,
a.service:hover .btn,
a.service:focus-visible .btn,
:where(.grid-3):not(:has(> .icon-card:hover, > .icon-card:focus-within)) > .icon-card:first-child .ic-blurb,
:where(.grid-3):not(:has(> .icon-card:hover, > .icon-card:focus-within)) > .icon-card:first-child .btn,
:where(.grid-3):not(:has(> a.service:hover, > a.service:focus-visible)) > a.service:first-child .btn {
  color: var(--ink-inverse);
}

/* AND THE BUTTON'S OUTLINE HAS TO COME WITH IT, which the near-black field was
   hiding. .btn::before draws its border in --accent-ink, and against the old
   #12312d card that was a visible 3.9:1 line. Against #2d6f76 it measured
   1.23:1 live — the label stayed white and legible while the box around it
   simply disappeared, leaving the one control on the card looking like text.
   A lighter field narrows the gap between the accent ramp's steps and anything
   drawn in one of them against another, which is the general trap here: on an
   inverted card the outline is chrome and belongs to --ink-inverse with the
   label, not to the accent it uses at rest. */
.icon-card:hover .btn::before,
.icon-card:focus-within .btn::before,
:where(.grid-3):not(:has(> .icon-card:hover, > .icon-card:focus-within)) > .icon-card:first-child .btn::before {
  border-color: var(--ink-inverse);
}

/* THE MARK IS A TWO-TONE SVG IN AN <img>, so its colours cannot be reached from
   CSS. brightness(0) flattens it to black and invert(1) lifts that to white, so
   the mark goes solid white on the inverted card while keeping its teal-and-ink
   drawing at rest. Alpha survives a filter, so the knocked-out counters stay
   knocked out rather than filling in.

   THE SAME FILTER WHITENS THE RING, which is not a coincidence to be tidied
   away: the ring IS this element's border, so one declaration flips the mark and
   its outline together and they cannot drift apart in the inverted state. */
a.service:hover .service-icon,
a.service:focus-visible .service-icon,
.icon-card:hover img,
.icon-card:focus-within img,
:where(.grid-3):not(:has(> a.service:hover, > a.service:focus-visible)) > a.service:first-child .service-icon,
:where(.grid-3):not(:has(> .icon-card:hover, > .icon-card:focus-within)) > .icon-card:first-child img {
  filter: brightness(0) invert(1);
}

a.service:hover h3,
a.service:focus-visible h3,
:where(.grid-3):not(:has(> a.service:hover, > a.service:focus-visible)) > a.service:first-child h3 {
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

/* Motion is opt-out everywhere else on this page and is here too, even though
   120ms of colour is the mildest thing on it. The rule is the rule. */
@media (prefers-reduced-motion: reduce) {
  a.service,
  .icon-card,
  .service-icon,
  .icon-card img,
  .service-detail .service-more,
  .service-points {
    transition: none;
  }
}

/* =========================================================================
   SERVICE DETAIL
   Two columns on desktop: the body on the left (kept to a reading measure
   so long-form copy stays legible), and the siblings list on the right as
   a hairline-rule column. Single column on narrow screens.
   ========================================================================= */
/* DELETED 2026-09-15: the .svc-detail two-column grid (2fr/1fr) and the
   .svc-siblings bordered sidebar. The MPMC structure port gave the service
   detail template an about-prose section and a Related-services card grid
   (services/[slug].astro), so nothing renders these selectors any more —
   the rules went with the markup, per the house rule that dead CSS is
   deleted with a dated note, not left to rot. .svc-detail-body stays: the
   about prose still wears it. */

.svc-detail-body {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  max-width: 60ch;
}

.svc-detail-body > p {
  margin: 0;
  font-size: 1rem;
  line-height: 1.6;
}

.svc-detail-body .lorem {
  opacity: 0.45;
}

.svc-detail-body .btn-lg {
  align-self: flex-start;
  margin-top: 0.5rem;
}

/* =========================================================================
   FEES
   Two jobs: render feeRows in a table that distinguishes sourced rows from
   placeholder rows at a glance, and give PRIVATE_BILLING_NOTICE its own
   bordered band so it cannot be mistaken for a footnote.
   Unsourced rows are drawn at reduced opacity in italic. Greyscale forbids
   a grey fill, so opacity carries the hierarchy instead.
   ========================================================================= */
.fee-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

.fee-table th,
.fee-table td {
  text-align: left;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}

.fee-table th {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.8125rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-top: 1px solid var(--rule);
  border-bottom: 2px solid var(--rule-strong);
}

.fee-table tbody tr:not(.fee-sourced) td {
  opacity: 0.55;
  font-style: italic;
}

.fee-table .fee-tag {
  display: inline-block;
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-left: 0.5rem;
  opacity: 0.7;
  font-style: normal;
}

/* The private-billing notice — its own 2px-bordered band so a reader cannot
   skim past it as a footnote. Mirrors .footer-urgent's pattern from
   SiteFooter.astro, with a heavier rule because the brief asks for it to be
   PROMINENT and indirection is exactly what this block exists to prevent. */
.private-billing {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  border: 2px solid var(--accent);
  padding: 1.5rem 1.75rem;
  margin-top: 2rem;
}

.private-billing h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.125rem;
  margin: 0;
  line-height: 1.25;
}

.private-billing p {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.55;
  max-width: 68ch;
}

/* =========================================================================
   NEW PATIENTS — CHECKLIST
   Each row is a marker square plus a label and an optional body. Markers are
   wireframe squares rather than rendered checkboxes because no row is
   checkable in a wireframe.
   ========================================================================= */
.checklist {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  list-style: none;
  padding: 0;
  margin: 0;
  max-width: 48rem;
}

.checklist li {
  display: grid;
  grid-template-columns: 1.5rem 1fr;
  gap: 0.75rem;
  align-items: start;
}

.checklist-marker {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  border: 1px solid var(--rule);
  margin-top: 0.3rem;
}

.checklist-body {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.checklist-label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1rem;
  line-height: 1.3;
}

.checklist-detail {
  font-size: 0.875rem;
  line-height: 1.5;
  opacity: 0.6;
  font-style: italic;
}

/* =========================================================================
   VISIT US — address, hours, map.
   .practice-info stacks labelled blocks; .hours-table is a compact two-row
   table that pairs day-range with opening times. Mockup Box component
   handles the map placeholder.
   ========================================================================= */
.practice-info {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  max-width: 56ch;
}

.practice-info-block {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.practice-info-label {
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.practice-info-value {
  font-size: 0.9375rem;
  line-height: 1.55;
  white-space: pre-line;
}

/* (.hours-table was deleted 2026-09-15: visit-us renders the homepage's
   dl.hours primitive now, and the dead table rules were the only readers.) */

/* =========================================================================
   ABOUT
   Three sections under a narrow measure. Lorem body sits at 55% opacity so
   it reads as pending against the lede above.
   ========================================================================= */
.about-section {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.about-section h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.375rem;
  margin: 0;
  line-height: 1.2;
}

.about-section p {
  margin: 0;
  font-size: 1rem;
  line-height: 1.6;
}

.about-section .lorem {
  opacity: 0.55;
}

/* =========================================================================
   FEEDBACK — form
   One column of labelled fields. Labels are uppercase eyebrows above the
   input. Required is a small star. The form posts via GET to the thanks
   route, so the thank-you page is reachable and nothing is ever posted
   anywhere real.
   Radio and checkbox inputs are drawn as wireframe controls (a circle and
   a square), with .checked expressed as an inset fill: greyscale forbids
   colour, so the checked state is a solid mark inside the control.
   ========================================================================= */
.feedback-form {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
  max-width: 36rem;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.field-label {
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.field-required {
  margin-left: 0.25rem;
  font-weight: 700;
}

.field-help {
  font-size: 0.75rem;
  line-height: 1.4;
  opacity: 0.65;
  font-style: italic;
  margin: 0;
}

.field-input,
.field-textarea,
.field-select {
  border: 1px solid var(--rule);
  background: var(--paper-raised);
  font-family: var(--font-text);
  font-size: 0.9375rem;
  padding: 0.65rem 0.75rem;
  width: 100%;
  color: var(--ink);
  border-radius: 0;
  line-height: 1.5;
}

.field-textarea {
  min-height: 9rem;
  resize: vertical;
}

.field-select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2rem;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink) 50%),
    linear-gradient(135deg, var(--ink) 50%, transparent 50%);
  background-position: calc(100% - 1rem) 55%, calc(100% - 0.5rem) 55%;
  background-size: 0.5rem 0.5rem, 0.5rem 0.5rem;
  background-repeat: no-repeat;
}

.field-radio {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.field-radio-option {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.9375rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

.field-radio-option input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 1rem;
  height: 1rem;
  border: 1px solid var(--rule);
  border-radius: 50%;
  margin: 0;
  background: var(--paper-raised);
  flex: 0 0 auto;
  display: inline-block;
}

.field-radio-option input[type="radio"]:checked {
  background: var(--accent-ink);
  box-shadow: inset 0 0 0 2px var(--paper-raised);
}

.field-checkbox {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-size: 0.9375rem;
  line-height: 1.4;
}

.field-checkbox input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 1rem;
  height: 1rem;
  border: 1px solid var(--rule);
  margin: 0.2rem 0 0 0;
  flex: 0 0 auto;
  display: inline-block;
  background: var(--paper-raised);
}

.field-checkbox input[type="checkbox"]:checked {
  background: var(--accent-ink);
  box-shadow: inset 0 0 0 2px var(--paper-raised);
}

/* (.field-submit was deleted 2026-09-15: the feedback form submits through
   the .btn primitive now, and these bespoke rules were its only reader. The
   .field-* form primitives above are untouched.) */

/* The external complaints pathway: an outlined hold, not an address. NSW
   general practices do point patients at the Health Care Complaints
   Commission, but the exact wording is a compliance decision this practice
   has not made yet. Drawn as a labelled dashed block, distinct from the
   form above and from a footnote. */
.complaints-hold {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  border: 1px dashed var(--rule-dashed);
  padding: 1.25rem 1.5rem;
  margin-top: 2.5rem;
  max-width: 48rem;
}

.complaints-hold-label {
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.complaints-hold p {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.5;
  opacity: 0.75;
  font-style: italic;
  max-width: 60ch;
}

/* =========================================================================
   LEGAL — measure column
   One column, narrow measure, headings + paragraphs only. Designed to serve
   privacy, terms, and 404 with the same shape, which is why the headings
   here are sized for long-form readability rather than as section anchors.
   ========================================================================= */
.legal-page {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
  max-width: 60ch;
  /* Centred measure — the narrow-measures rule above: legal is pure prose
     with no companion content and no section heading of its own, so a
     left-stranded column reads as a bug. */
  margin-inline: auto;
}

.legal-section {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.legal-section h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  margin: 0;
  line-height: 1.25;
}

.legal-section p {
  margin: 0;
  font-size: 1rem;
  line-height: 1.6;
  max-width: 60ch;
}

.legal-section .lorem {
  opacity: 0.55;
}


/* --- INNER-PAGE HERO -------------------------------------------------------
   Every inner page opens on the photograph, the same way the homepage does.

   Sab, 2026-09-10: "can we make sure every page has a hero please with an
   image ... because this practice will be visual first." The inner pages opened
   on white until now, which meant a client clicking off the homepage watched the
   layout stop being image-led at exactly the moment they started reading. If
   visual-first is the argument, it has to survive the second click.

   Shorter than the homepage on purpose. The homepage hero sells the place and
   gets a full screen for it; an inner page has a job to do below the fold, and a
   second full-viewport photograph just pushes the content the reader came for
   off the screen.

   ONE PHOTOGRAPH PER LAYOUT, and that is an asset constraint, not a design
   choice. Five frames survived the audit in public/images/placeholder/README.md
   and the oracle holds each layout to its own, so all eleven Nepean pages wear
   hero-nepean.jpg. --hero-focus varies the crop per page (see .hf-*) so the set
   reads as different framings of one place rather than as the same file pasted
   eleven times. Real per-page imagery needs the practice's shoot. */
.innerpage > .mk-section:first-of-type {
  position: relative;
  min-height: 46svh;
  color: var(--ink-inverse);
  background-image:
    linear-gradient(rgba(0, 0, 0, 0) 34%, rgba(0, 0, 0, 0.62)),
    url("/images/placeholder/hero-nepean.jpg");
  background-size: cover;
  background-position: center var(--hero-focus, 55%);
  background-repeat: no-repeat;
  /* THE HOMEPAGE HERO WASH, PROPAGATED (2026-09-15). Sab, on /new-patients:
     "our hero ... doesnt appear to have the colors" — of course it didn't:
     the wash Sab asked for on 2026-09-14 ("a subtle color overlay or
     gradient") shipped in #hero::after and never travelled to the inner
     heroes, which were still wearing the raw greyscale photograph under the
     old black scrim. These are the SAME TWO LAYERS at the SAME STOPS as the
     homepage rule — the --ink legibility scrim, then the 118deg brand wash —
     because a propagated primitive that drifted would be two decisions, not
     one. The first declaration is the color-mix-less fallback and stays
     exactly what shipped before: an engine that drops the second block loses
     the wash, never the scrim or the photograph. Scoped for free: .innerpage
     is worn only by Nepean inner bodies (the four greyscale layouts ship a
     bare <body>), so their signed-off heroes are untouched. */
  background-image:
    linear-gradient(
      to top,
      color-mix(in srgb, var(--ink) 80%, transparent) 0%,
      color-mix(in srgb, var(--ink) 58%, transparent) 30%,
      color-mix(in srgb, var(--ink) 20%, transparent) 58%,
      color-mix(in srgb, var(--ink) 42%, transparent) 100%
    ),
    linear-gradient(
      118deg,
      color-mix(in srgb, var(--accent-deep) 22%, transparent) 0%,
      color-mix(in srgb, var(--accent) 14%, transparent) 46%,
      color-mix(in srgb, var(--accent-deep) 6%, transparent) 100%
    ),
    url("/images/placeholder/hero-nepean.jpg");
}

/* THE CROP LADDER, shared by all five layouts under their own stylesheets. Six
   focal points, assigned round-robin down each layout's page list. They are
   percentages into the frame's height, not art direction: the point is that two
   consecutive pages never show the identical crop. */
.hf-1 { --hero-focus: 28%; }
.hf-2 { --hero-focus: 42%; }
.hf-3 { --hero-focus: 55%; }
.hf-4 { --hero-focus: 66%; }
.hf-5 { --hero-focus: 78%; }
.hf-6 { --hero-focus: 36%; }

/* RELOCATION NOTICE (2026-09-16) — permanent wayfinding band + first-visit
   modal, ported from the delivered wireframe (gmc-client-requirements-ia
   wireframes/home.html) for workstream gmc-scope-gap-closure. Tokens only,
   so the palette gates are untouched; the classes render on nothing but the
   Nepean homepage, so the four signed-off greyscale layouts are unaffected. */
.reloc-band {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.9rem 1.5rem;
  background: var(--paper-band);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.reloc-band p { margin: 0; }
.reloc-band strong { font-family: var(--font-display); }

.reloc-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  background: color-mix(in srgb, var(--ink) 55%, transparent);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 900;
}
.reloc-backdrop[hidden] { display: none; }
.reloc-card {
  background: var(--paper-raised);
  /* 640px (Sab, 2026-09-16): the annotated old/new entrance photo is the
     payload — at 480px its labels were barely legible. */
  max-width: 640px;
  width: 100%;
  /* Height cap (Sab, 2026-09-16): "needs to be responsive i.e 100vh max
     height". On a short window the card used to overflow the fixed backdrop
     from both ends, and with the page scroll locked the overflowed content
     was unreachable. max-height resolves against the backdrop's padded box —
     viewport minus its 20px padding — so the card can never stand taller
     than the screen. Everything inside is the card's own scroll region
     (.reloc-scroll) and the close button stays pinned outside it. */
  max-height: 100%;
  display: flex;
  flex-direction: column;
  /* Horizontal padding lives on the scroll region below, so the scrollbar
     renders at the card's true edge instead of floating mid-card. */
  padding: 36px 0 30px;
  position: relative;
  border-top: 4px solid var(--accent);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.28);
  box-shadow: 0 24px 60px color-mix(in srgb, var(--ink) 28%, transparent);
}
/* The scroll region is a flex child, so min-height: 0 is what lets it shrink
   inside the capped card — without it the child's content minimum wins and
   the card overflows again. margin-top clears the close button's span
   (top 10px + ~36px) so text never scrolls under the glyph. */
.reloc-scroll {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 34px;
  margin-top: 10px;
}

/* DESKTOP FIT (2026-09-16, Sab: "getting internal scrolling here — make sure
   the real elements inside are 100vh, on desktop screens at least"). The
   card keeps its viewport cap, so something inside must give height back,
   and the photo is the only member that can: the scroll region becomes a
   flex column and the photo absorbs whatever the text leaves over,
   letterboxed by object-fit: contain so the practice's own arrow and
   New/Old Entrance labels never crop. The 14rem floor is where the card
   returns to scrolling — a short landscape phone starves the payload past
   that, and scrolling is the honest answer there. */
.reloc-scroll {
  display: flex;
  flex-direction: column;
}

.reloc-scroll .reloc-photo {
  flex: 1 1 auto;
  min-height: 14rem;
  object-fit: contain;
  /* Border off (Sab, 2026-09-16: "remove the border from the image"). The
     shared .reloc-photo/.route-photo frame draws at the element box's edge,
     but once contain letterboxes a flexed photo, the bitmap sits inside the
     box with side gaps — the border read as a frame floating around the
     picture rather than around its edge. The Getting Here photos keep
     theirs: outside this flex context the box hugs the bitmap exactly. */
  border: 0;
}
.reloc-close {
  position: absolute;
  top: 10px;
  right: 14px;
  background: none;
  border: 0;
  font-size: 28px;
  line-height: 1;
  color: var(--accent-ink);
  cursor: pointer;
  padding: 4px 8px;
}
.reloc-close:hover { color: var(--ink); }
.reloc-flag {
  display: inline-block;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: var(--label-weight);
  color: var(--accent-ink);
  margin-bottom: 10px;
}
.reloc-title {
  font-family: var(--font-display);
  font-size: 28px;
  line-height: 1.2;
  color: var(--ink);
  margin: 0 0 14px;
}
.reloc-body {
  font-family: var(--font-text);
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--ink);
  opacity: 0.78;
  margin: 0 0 12px;
}
.reloc-actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 22px;
}
@media (max-width: 520px) {
  .reloc-card { padding: 30px 0 24px; }
  .reloc-scroll { padding: 0 22px; margin-top: 16px; }
  .reloc-title { font-size: 24px; }
  .reloc-actions .btn { flex: 1 1 100%; justify-content: center; }
}

/* PRACTICE-SERVED RELOCATION IMAGERY (2026-09-16) — the live site's own
   annotated entrance photos (GPM-OldToNewThumbnail.jpg, GPM-TownTceThumbnail.jpg
   -> /images/relocation/), pointed at by Sab mid-build. Shared frame style
   for the relocation modal and the Getting Here route steps. Tokens only. */
.reloc-photo,
.route-photo {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--rule);
  margin: 6px 0 4px;
}
.route-photo-wide {
  max-width: 720px;
}

/* MODAL ENTRANCE (2026-09-16, Sab): the notice fades in over the page while
   the card rises the last few pixels — arrival without alarm. Pure CSS on the
   hidden->shown flip, so dismissal stays instant; reduced-motion visitors get
   the notice with no travel. No colour literals: palette-gated idiom. */
@keyframes reloc-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes reloc-rise-in {
  from { opacity: 0; transform: translateY(14px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.reloc-backdrop { animation: reloc-fade-in 0.2s ease-out both; }
.reloc-card { animation: reloc-rise-in 0.26s ease-out both; }
@media (prefers-reduced-motion: reduce) {
  .reloc-backdrop,
  .reloc-card { animation: none; }
}
