/* ==========================================================================
   PCSA Portal — public site
   ==========================================================================
   Layered on top of app.css, which owns the tokens, buttons, cards, forms and
   badges. This file only adds what the public-facing pages need: the marketing
   shell, hero treatments, long-form prose and the directory surfaces. Sharing
   the token layer is what keeps the public site and the member portal feeling
   like one product rather than a website with an app bolted on the side.
   ========================================================================== */

body.site { background: var(--surface); color: var(--ink-700); }

/* The `hidden` attribute means hidden, always.
   Any component rule that sets `display` beats the browser's own
   `[hidden] { display: none }`, so a `<div class="x" hidden>` quietly renders.
   That has now been patched three separate times in this project — .lightbox,
   .suggest and the comment reply banner — which is the signal to fix the class
   of bug rather than the next instance of it. */
[hidden] { display: none !important; }


/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */

.site-header {
    background: rgba(255, 255, 255, .88);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-bottom: 1px solid var(--line);
    position: sticky;
    top: 0;
    z-index: 50;
}

.site-header .inner {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 24px;
    /* Sized from the logo, not the other way round: 72px for the original 55px
       logo, 96px once it grew to 77px and a fixed 72px bar let it overflow. The
       logo is 62px now and the bar stays at 96px — that is 17px of air above and
       below rather than 9px, which the header was short of before. */
    height: 96px;
    display: flex;
    align-items: center;
    gap: 28px;
}

/* 55px originally, then 77px at the association's request, then down 20% to
   62px — the 77px logo nearly filled the bar, leaving about 9px of air above
   and below it. */
.site-header .logo img { height: 62px; width: auto; display: block; }

.site-nav { display: flex; align-items: center; gap: 4px; margin-left: auto; }
/* The logo grew from ~220px wide to 307px, which is 87px taken straight out of
   the navigation's budget. Tightening the item padding gives most of it back;
   the rest comes from collapsing to the menu button a little earlier, below. */
.site-nav a:not(.btn) { white-space: nowrap; }

/* "Find a Practitioner" as a button — the one thing most visitors came to do.
   `--stage-4` is the toddler green from the logo, and the text is the chrome
   navy already used in the header: 6.96:1. White on this green is 1.69:1 and
   completely unreadable, which is the obvious-looking choice and the wrong one. */
.site-nav a.nav-cta:not(.btn) {
    /* --stage-4 is #BDD070, the artwork's own green — the palette was
       re-derived from the SVG, so the token and the literal are now the same
       colour and the token is the one to use. */
    background: var(--stage-4);
    color: var(--brand-800);
    font-weight: 600;
    padding: 9px 15px;
    border-radius: var(--radius-sm);
}
.site-nav a.nav-cta:not(.btn):hover {
    background: var(--stage-4-mid);
    color: var(--brand-900);
}
/* `.active` gives nav links their own background; on the button that would
   replace the green with a tint, so the button keeps its own. */
.site-nav a.nav-cta.active:not(.btn) { background: var(--stage-4); color: var(--brand-800); }


/* `:not(.btn)` matters. `.site-nav a` is more specific than `.btn-primary`, so
   without it the nav's ink colour won the cascade and the Join button rendered
   dark slate on brand blue — 1.57:1, against the 4.5:1 WCAG AA needs. Excluding
   buttons here fixes every button placed in the nav, not just this one. */
.site-nav a:not(.btn) {
    padding: 8px 13px;
    border-radius: var(--radius-sm);
    font-size: 14.5px;
    font-weight: 500;
    color: var(--ink-700);
    transition: background-color .12s, color .12s;
}
.site-nav a:not(.btn):hover { background: var(--brand-50); color: var(--brand-700); }
.site-nav a.active:not(.btn) { color: var(--brand-700); }

/* The desktop bar carries no icons. Six coloured discs in a row, a few
   centimetres from a logo made of five coloured discs, is two marks arguing —
   and the labels are short enough to scan without help. In the flyout the same
   items are stacked one per line, which is exactly where a glyph earns its
   place. */
.site-nav .b-icon { display: none; }

.site-nav .sep { width: 1px; height: 22px; background: var(--line); margin: 0 8px; }

/* Mobile menu — a checkbox toggle keeps it working with zero JS */
.site-burger { display: none; background: none; border: 0; padding: 8px; cursor: pointer; color: var(--ink-900); margin-left: auto; }
.site-burger svg { width: 24px; height: 24px; }

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */

.hero {
    position: relative;
    background: linear-gradient(165deg, var(--brand-800) 0%, var(--brand-900) 78%);
    color: #fff;
    overflow: hidden;
}

/* The mark's five discs, drifting large and soft behind the copy. */
.hero::after {
    content: '';
    position: absolute;
    left: -10%; right: -10%; bottom: -46%;
    height: 720px;
    background-image: var(--mark-circles);
    opacity: .5;
    filter: blur(3px);
    pointer-events: none;
}

/* --------------------------------------------------------------------------
   Home hero: the photograph is the page

   The image used to sit in its own column, because white type over a photograph
   is a contrast problem that moves every time the photograph does. Full-bleed
   now, by request — so the contrast is solved rather than avoided: `.hero-scrim`
   is a *directional* wash, heaviest where the copy is and lightest on the far
   right, which keeps the heading on the same navy it was designed for while
   letting the picture stay a picture. A flat overlay strong enough to be safe
   would have killed the photograph, which is the reason to go directional.
   -------------------------------------------------------------------------- */

.hero-home {
    min-height: clamp(520px, 74vh, 700px);
    display: flex;
    align-items: center;
}
.hero-home .inner { width: 100%; }
.hero-home .hero-copy { max-width: 620px; min-width: 0; }

/* --- The photographs --- */
.hero-slides { position: absolute; inset: 0; z-index: 0; }

/* Three rules, and the order of them is the cross-fade.
   The incoming slide fades up *over* the outgoing one, which stays fully lit
   underneath until the fade has finished. Fading one out while fading the other
   in looks like the obvious thing to do and is wrong: at the midpoint both sit
   near half opacity, the composite is only about three-quarters covered, and
   the navy behind shows through as a dip. Holding the old slide opaque means
   the hero is always exactly one whole photograph. */
.hero-slide {
    position: absolute; inset: 0;
    opacity: 0; z-index: 0;
    transition: opacity 1.15s ease-in-out;
}
.hero-slide.was-on { opacity: 1; z-index: 1; transition: none; }
.hero-slide.is-on  { opacity: 1; z-index: 2; }

.hero-slide img {
    width: 100%; height: 100%;
    object-fit: cover;
    object-position: 70% 50%;   /* faces and hands sit right of the copy */
    display: block;
}

/* One drift, shared by every slide, running from page load on a single
   timeline — so any two slides are at the *same* scale at any instant and a
   cross-fade can never show a size change.

   This used to run only on `.is-on`, which meant removing that class removed
   the animation and the outgoing image snapped back to its base scale — a 4%
   shrink (measured: 1.1018 to 1.0600) at the exact moment both slides were
   visible. That is the size shift. Per-slide Ken Burns is the prettier idea and
   it cannot be made seamless by tuning; the scales have to be equal, not
   close. */
.hero-slide img { animation: hero-drift 34s ease-in-out infinite alternate; }
@keyframes hero-drift {
    from { transform: scale(1.04); }
    to   { transform: scale(1.11); }
}

/* --- The wash that makes white type safe over any of them --- */
.hero-scrim {
    position: absolute; inset: 0; z-index: 1; pointer-events: none;
    background:
        /* The .90 stop runs to 46% because that is where the copy column ends
           at its widest — measured, not guessed. Inside it the worst possible
           case (a pure white photograph behind the type) still gives white
           better than 8:1, so the heading is safe against any image that will
           ever be dropped in here. Past 62% the wash thins out and the
           photograph is allowed to be a photograph; no text goes there. */
        linear-gradient(100deg,
            rgba(27, 42, 79, .95) 0%,
            rgba(27, 42, 79, .90) 46%,
            rgba(27, 42, 79, .60) 72%,
            rgba(27, 42, 79, .34) 100%),
        /* a floor, so the buttons never land on a bright patch */
        linear-gradient(to top, rgba(9, 16, 32, .55) 0%, rgba(9, 16, 32, 0) 42%);
}

/* The mark's discs still drift through, but over a photograph they have to be
   far quieter than they are over flat navy or the whole thing turns to soup. */
.hero-home::after { opacity: .16; z-index: 2; }
.hero-home .inner { z-index: 3; }

/* --- Which photograph --- */
.hero-dots { display: flex; gap: 9px; margin-top: 30px; }
.hero-dot {
    width: 34px; height: 4px; padding: 0; border: 0; border-radius: 99px;
    background: rgba(255, 255, 255, .34); cursor: pointer;
    transition: background-color .2s, width .2s;
}
.hero-dot:hover { background: rgba(255, 255, 255, .6); }
.hero-dot.is-on { background: #fff; width: 46px; }
.hero-dot:focus-visible { outline: 2px solid #fff; outline-offset: 4px; }

@media (max-width: 900px) {
    /* Shorter, and the copy gets the full width. The scrim turns vertical
       because there is no longer a left column to protect — the type runs the
       whole way across, so the wash has to as well. */
    .hero-home { min-height: 0; }
    .hero-home .hero-copy { max-width: none; }
    .hero-slide img { object-position: 62% 42%; }
    .hero-scrim {
        background:
            linear-gradient(180deg,
                rgba(27, 42, 79, .90) 0%,
                rgba(27, 42, 79, .84) 45%,
                rgba(27, 42, 79, .92) 100%);
    }
}

/* Movement is the first thing to go. No drift, no rotation — the JS checks the
   same query before it starts a timer, so the photograph simply sits there. */
@media (prefers-reduced-motion: reduce) {
    .hero-slide img { animation: none; transform: scale(1.04); }
    .hero-slide { transition: none; }
}

.hero .inner {
    max-width: 1200px;
    margin: 0 auto;
    padding: 84px 24px 92px;
    position: relative;
    z-index: 1;
}

.hero .eyebrow {
    display: inline-flex; align-items: center; gap: 8px;
    font-size: 12px; font-weight: 600; letter-spacing: .14em; text-transform: uppercase;
    color: var(--brand-400);
    background: rgba(128, 152, 232, .16);
    border: 1px solid rgba(128, 152, 232, .34);
    padding: 6px 14px; border-radius: 99px;
    margin-bottom: 22px;
}

.hero h1 {
    color: #fff;
    font-size: clamp(32px, 5.2vw, 54px);
    line-height: 1.08;
    /* Eased from -.028em when headings moved to Radio Canada Big: it is
       narrower than Inter, so Inter's tracking closes it up too far. */
    letter-spacing: -.018em;
    font-weight: 600;
    max-width: 15ch;
}

.hero p.lede {
    margin-top: 20px;
    font-size: clamp(16px, 1.8vw, 19px);
    line-height: 1.6;
    color: rgba(255, 255, 255, .74);
    max-width: 58ch;
}

.hero .cta-row { margin-top: 34px; display: flex; gap: 12px; flex-wrap: wrap; }
.hero .btn-secondary { background: rgba(255,255,255,.10); border-color: rgba(255,255,255,.26); color: #fff; box-shadow: none; }
.hero .btn-secondary:hover { background: rgba(255,255,255,.18); border-color: rgba(255,255,255,.4); color: #fff; }

/* Compact hero for interior pages */
/* Every page except the home page shares one hero height, so moving between
   them does not shift the content up and down. `min-height` rather than
   `height`: a page with an unusually long heading grows instead of clipping it,
   and a clipped heading is a worse failure than a slightly taller band. */
.hero.hero-sm { min-height: 350px; display: flex; align-items: center; }
/* 30px, not 40: the tallest of these heroes (a town page — breadcrumbs, a
   two-line heading, a lede and a button) needs 287px, and 40px either side
   pushed it to 367 while every other page sat at 350. The content is
   flex-centred, so on shorter pages this padding is never the thing you see. */
.hero.hero-sm .inner { width: 100%; padding: 30px 24px; }
.hero.hero-sm h1 { font-size: clamp(27px, 3.6vw, 38px); max-width: 22ch; }
.hero.hero-sm::after { bottom: -60%; opacity: .38; }

/* --------------------------------------------------------------------------
   Sections & prose
   -------------------------------------------------------------------------- */

.section { padding: 72px 0; }
.section.tint { background: var(--mist); }
.section .inner { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
.section-head { max-width: 68ch; margin-bottom: 40px; }
/* A centred section has to centre the box, not only the text in it.
   `.section-head` is a 68ch column: left alone it sits flush left inside a
   wider `.inner`, so its centred text lands well left of the `.cta-row`
   below, which centres itself. Reads as a broken layout, and was. */
.center .section-head { margin-inline: auto; }
.section-head .eyebrow {
    font-size: 11.5px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase;
    color: var(--brand-600); margin-bottom: 10px;
}
.section-head h2 { font-size: clamp(24px, 3vw, 33px); letter-spacing: -.022em; }
.section-head p { margin-top: 12px; font-size: 16.5px; color: var(--ink-500); line-height: 1.62; }

/* Long-form editorial (the parent-facing content pages) */
.prose { max-width: 72ch; font-size: 16.5px; line-height: 1.72; color: var(--ink-700); }
.prose > * + * { margin-top: 1.1em; }
.prose h2 { font-size: 25px; margin-top: 2em; letter-spacing: -.02em; }

/* An opening paragraph that sets up the page — larger, and a shade darker, so
   the eye starts there rather than in the middle of the first section. */
.prose .lede { font-size: 19px; line-height: 1.62; color: var(--ink-900); }

/* A quoted study, set apart from the sentence introducing it. `q` rather than
   blockquote because these sit inline within a paragraph. */
.prose q { font-style: italic; color: var(--ink-900); }
.prose h3 { font-size: 19px; margin-top: 1.7em; }
.prose ul, .prose ol { padding-left: 1.3em; }
.prose li + li { margin-top: .45em; }
.prose a { color: var(--brand-600); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
.prose a:hover { color: var(--brand-700); }
.prose img { border-radius: var(--radius-lg); box-shadow: var(--shadow-md); }
.prose blockquote {
    border-left: 3px solid var(--brand-400);
    padding: 4px 0 4px 20px;
    color: var(--ink-500);
    font-size: 17.5px;
}
.prose figure figcaption { font-size: 13.5px; color: var(--ink-500); margin-top: 8px; }

/* --- Event specification bar -----------------------------------------------
   An event page is read by a practitioner deciding whether two days out of a
   practice is worth it, so the facts they weigh come before the prose. This
   replaces four stat tiles: tiles are what every dashboard does, they took a
   third of the first screen, and an event has no statistics — it has a date, a
   place, a format and a points value.

   Set as a specification strip instead: hairline-separated columns, label above
   value, the way a programme's masthead or a piece of equipment's spec panel
   reads. Quiet, because the programme below is where the page spends its
   boldness. */
.spec-bar {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
    border-top: 2px solid var(--ink-900);
    border-bottom: 1px solid var(--line);
    margin-bottom: 40px;
}

.spec {
    padding: 15px 22px 16px;
    border-left: 1px solid var(--line);
}
.spec:first-child { border-left: 0; padding-left: 0; }

.spec .k {
    display: block;
    font-size: 10px; font-weight: 600; letter-spacing: .12em;
    text-transform: uppercase; color: var(--ink-400);
    margin-bottom: 6px;
}
.spec .v {
    display: block; font-size: 19px; font-weight: 600;
    color: var(--ink-900); line-height: 1.25;
    letter-spacing: -.015em;
    font-variant-numeric: tabular-nums;
}
.spec .s { display: block; font-size: 12.5px; color: var(--ink-500); margin-top: 3px; line-height: 1.45; }
.spec .s a { color: var(--brand-600); }

@media (max-width: 620px) {
    .spec-bar { grid-template-columns: 1fr 1fr; margin-bottom: 30px; }
    .spec { padding: 13px 16px 14px; }
    .spec:nth-child(odd) { border-left: 0; padding-left: 0; }
    .spec:nth-child(n+3) { border-top: 1px solid var(--line); }
}

/* --- The programme spine ---------------------------------------------------
   The signature of this page, and the thing a practitioner actually scrutinises
   before paying. A two-day timetable is a real sequence, so it is drawn as one:
   a continuous rule down the page with a node per session and the days as
   labelled segments on it. The rule is the passage of the two days — the
   structure carries information rather than decorating.

   Times sit in their own tabular gutter so they form a readable column; session
   titles are set in the certificate's serif. */
.spine { position: relative; margin-top: 6px; }

/* The rule itself. Drawn on the container rather than per row so it is
   genuinely continuous through the day breaks. Inset to pass through the
   centre of the nodes. */
.spine::before {
    content: '';
    position: absolute;
    left: 71px; top: 10px; bottom: 10px;
    width: 2px;
    background: var(--line);
}

.spine-day {
    position: relative;
    display: flex; align-items: baseline; gap: 14px;
    padding: 26px 0 14px 96px;
}
.spine-day:first-child { padding-top: 4px; }
.spine-day .n {
    font-size: 10.5px; font-weight: 600; letter-spacing: .13em;
    text-transform: uppercase; color: var(--stage-4-deep);
}
.spine-day .d { font-size: 13.5px; color: var(--ink-500); }

/* The day marker sits on the rule and is filled, so a day boundary reads as a
   heavier stop than a session. */
.spine-day::before {
    content: '';
    position: absolute; left: 66px; top: 50%; margin-top: -6px;
    width: 12px; height: 12px; border-radius: 50%;
    background: var(--stage-4); border: 2px solid var(--surface);
    box-shadow: 0 0 0 2px var(--stage-4-soft);
}

.spine-item {
    position: relative;
    display: grid;
    grid-template-columns: 56px 1fr;
    gap: 40px;
    padding: 13px 0 13px 0;
}
.spine-item + .spine-item { border-top: 1px solid var(--line); }

.spine-item .t {
    font-size: 13px; font-weight: 600; color: var(--ink-900);
    font-variant-numeric: tabular-nums; letter-spacing: .01em;
    padding-top: 3px; text-align: right;
}
.spine-item .t .to { display: block; font-weight: 400; color: var(--ink-400); font-size: 12px; margin-top: 1px; }

/* The node. Hollow, so it reads as lighter than the day marker above it. */
.spine-item::before {
    content: '';
    position: absolute; left: 68px; top: 20px;
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--surface); border: 2px solid var(--ink-300);
}

.spine-item .ttl {
    font-family: var(--font-display);
    font-weight: 600; font-size: 17px; line-height: 1.34;
    color: var(--ink-900); letter-spacing: -.006em;
}
.spine-item .by { font-size: 13.5px; color: var(--ink-700); margin-top: 4px; font-weight: 500; }
.spine-item .where { font-size: 12.5px; color: var(--ink-400); margin-top: 1px; }
.spine-item .about { font-size: 14px; color: var(--ink-500); margin-top: 6px; line-height: 1.55; max-width: 60ch; }

/* A break has no speaker and no detail, so it is set back to let the teaching
   sessions carry the eye. */
.spine-item.is-break .ttl { font-family: var(--font); font-size: 14.5px; font-weight: 400; color: var(--ink-500); letter-spacing: 0; }

@media (max-width: 620px) {
    .spine::before { left: 5px; }
    .spine-day { padding: 22px 0 12px 26px; }
    .spine-day::before { left: 0; }
    .spine-item { grid-template-columns: 1fr; gap: 0; padding-left: 26px; }
    .spine-item::before { left: 2px; top: 18px; }
    .spine-item .t { text-align: left; padding-top: 0; margin-bottom: 3px; }
    .spine-item .t .to { display: inline; margin-left: 6px; }
    .spine-item .ttl { font-size: 16.5px; }
}

/* --- Detail rows in a sidebar panel ----------------------------------------
   The same idea at sidebar scale: an icon, a label, a value. Replaces a <dl>
   whose <dt> and <dd> were distinguishable only by weight. */
.detail-row { display: flex; gap: 11px; align-items: flex-start; }
.detail-row + .detail-row { margin-top: 15px; padding-top: 15px; border-top: 1px solid var(--line); }
.detail-row > svg { width: 18px; height: 18px; flex: none; margin-top: 1px; color: var(--ink-400); }
.detail-row .k {
    display: block; font-size: 10.5px; font-weight: 600; letter-spacing: .09em;
    text-transform: uppercase; color: var(--ink-400); margin-bottom: 2px;
}
.detail-row .v { display: block; color: var(--ink-900); font-weight: 500; font-size: 14.5px; line-height: 1.4; }
.detail-row .s { display: block; font-size: 12.5px; color: var(--ink-500); margin-top: 2px; line-height: 1.45; }

/* A section heading inside the prose. The h3s were only four points larger than
   the body around them, so a reader skimming for "Who it is for" had nothing to
   skim. A rule above and tighter tracking make them findable. */
.prose h3 {
    font-size: 17px; font-weight: 600; color: var(--ink-900);
    letter-spacing: -.005em;
    margin-top: 2em; padding-top: 1.1em;
    border-top: 1px solid var(--line);
}
.prose h3:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

/* Research citations — numbered, small, hanging indent */
.citations { font-size: 14px; line-height: 1.6; color: var(--ink-500); counter-reset: cite; }
.citations li { margin-bottom: 10px; }
.citations li em { font-style: normal; color: var(--ink-700); }

/* --------------------------------------------------------------------------
   Feature grids
   -------------------------------------------------------------------------- */

.feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); gap: 24px; }

.feature {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 26px;
    box-shadow: var(--shadow-sm);
    transition: transform .16s ease, box-shadow .16s ease;
}
.feature:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
/* The feature card's icon is a BrandIcon — the same disc the mobile nav and the
   logo use — rather than the tinted rounded-square chip that used to sit here.
   One icon language across the site was the point; two, three rows apart, was
   the thing to fix.

   The chip also cycled `--stage-1..5` by `nth-child`, so the same idea wore a
   different colour depending on where it landed in a grid — "CPD points"
   orange on one page and green on the next. BrandIcon assigns colour by
   meaning, so an icon now carries its own tint wherever it appears, and the
   cycling rules went with the chip. */
.feature .b-icon { width: 46px; height: 46px; margin-bottom: 16px; }

.feature h3 { font-size: 17px; margin-bottom: 7px; }
.feature p { font-size: 14.5px; color: var(--ink-500); line-height: 1.6; }

/* --- Panel cards: Contact, Opening hours, Tickets -------------------------
   Written as `.card.panel-card`, not `.panel-card`. The zeroed aside defaults
   above are one class, so a bare modifier ties with them and the winner is
   whichever is written last in the file — which is not a thing to depend on.
   Two classes settles it outright, and the markup always carries both.
   One definition for all three, because they are one idea. They were three
   near-copies and the third had already drifted — a 17.5px sentence-case head
   against two 13px uppercase ones. A shared rule is what stops that happening
   again; the only things a panel declares for itself now are its tint and, for
   Contact, the white type that tint can carry.

   The heads are micro-labels rather than headings: they name a panel somebody
   is scanning past, and the content beneath is what should carry weight. */
.card.panel-card {
    border: 1.5px solid var(--stage-4);
    box-shadow: 0 6px 20px rgba(104, 117, 53, .10);
    overflow: hidden;   /* the head fills to the edges; the radius has to clip it */
}
.card.panel-card > .card-head {
    background: var(--stage-4);
    border-bottom: 0;
    padding: 13px 20px;
}
.card.panel-card > .card-head h2 {
    font-size: 13px;
    font-weight: 600;
    color: var(--ink-900);
    letter-spacing: .08em;
    text-transform: uppercase;
    margin: 0;
}

/* Contact is the primary of the set — the deep green, which is the only one of
   the two that carries white (5.02:1; the light green is 1.69:1). */
.card.panel-card.contact-card { border-color: var(--stage-4-deep); box-shadow: 0 6px 20px rgba(104, 117, 53, .12); }
.card.panel-card.contact-card > .card-head { background: var(--stage-4-deep); }
.card.panel-card.contact-card > .card-head h2 { color: #fff; }

/* --- The contact panel on a practice listing ------------------------------
   Framed in green rather than the site's default card chrome, because it is the
   one block a parent is on the page to find.

   `--stage-4-deep`, not `--stage-4`. The brand green `#BDD070` carries white at
   1.69:1 and is unreadable — the same trap already recorded on the nav CTA. This
   is the deep tone of the same hue and takes white at 5.02:1. The border is the
   identical green so the head and the frame read as one object rather than two
   decisions. */
.contact-card > .card-body { padding: 18px 20px 20px; }

/* One scale, three steps: the label recedes, the detail reads, the phone leads.
   Everything was 15px before, which is not a hierarchy — a parent scanning for a
   number had nothing to land on. */
.contact-list { display: flex; flex-direction: column; gap: 15px; }
.contact-row .k {
    font-size: 11px; font-weight: 600; letter-spacing: .085em;
    text-transform: uppercase; color: var(--ink-400); margin-bottom: 3px;
}
.contact-row .v {
    font-size: 15.5px; font-weight: 500; color: var(--ink-900); line-height: 1.45;
}
.contact-row .v a { color: var(--brand-700); text-decoration: none; }
.contact-row .v a:hover { color: var(--brand-600); text-decoration: underline; }

/* The phone is the point of the panel. */
.contact-row .v.is-lead { font-size: 21px; font-weight: 600; letter-spacing: -.01em; }
.contact-row .v.is-lead a { color: var(--ink-900); }
.contact-row .v.is-lead a:hover { color: var(--brand-700); }

/* An address and an email have no spaces to break at, and a 300px column is
   narrower than either. */
.contact-row .v.is-wrap { overflow-wrap: anywhere; font-size: 14.5px; }

.contact-row .v a .ext {
    width: 12px; height: 12px; vertical-align: -1px;
    margin-left: 3px; opacity: .55;
}

/* Opening hours: the same frame, one step lighter — contact leads, hours
   supports.

   `--stage-4` is the logo's own light green and it cannot carry white: 1.69:1,
   the trap recorded twice already. `--ink-900` on it is 9.52:1, and it is the
   colour every heading on the site is set in anyway. */

/* --- Open / closed, from the published hours ------------------------------
   State is carried by colour *and* by the word, never colour alone — "Open now"
   and "Closed" read the same to somebody who cannot tell the dot's green from
   its grey. The dot is reinforcement, not the message. */
.open-state {
    display: inline-flex; align-items: baseline; gap: 7px;
    padding: 7px 13px; border-radius: 99px;
    font-size: 13.5px; line-height: 1.2; white-space: nowrap;
}
.open-state .dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: currentColor; flex: none; align-self: center;
}
.open-state strong { font-weight: 600; }
.open-state .det { font-weight: 400; opacity: .78; }

.open-state.is-open { background: var(--success-100); color: var(--success-600); }
.open-state.is-soon { background: var(--warning-100); color: var(--warning-600); }
.open-state.is-shut { background: #eceff3;            color: var(--ink-500); }

/* On the green panel head the pill would be a third green on a second green, so
   it goes plain white there and lets the head carry the colour. */
.open-state.on-head {
    background: rgba(255, 255, 255, .92);
    padding: 4px 10px; font-size: 11.5px; font-weight: 600;
    letter-spacing: .02em; text-transform: none; gap: 5px;
}
.hours-card > .card-head {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
}

/* In a result card the status is a fact in the meta row, not a badge. Flat, so
   the dot carries the colour and the label carries the meaning — a filled chip
   would be a fourth colour on a card that already has a Verified pill and a row
   of service badges below it.

   Both this and `.on-head` above are (0,2,0), exactly like the `.is-*` rules
   they override, so they only win on source order: keep them below. */
.open-state.on-meta {
    background: none; padding: 0; gap: 5px;
    font-size: inherit; line-height: inherit;
}
.open-state.on-meta .dot { width: 6px; height: 6px; }


.hours-note {
    margin: 12px 0 0; font-size: 11.5px; color: var(--ink-400); line-height: 1.5;
}

/* The open/closed sentence in full, at the foot of the table it comes from.
   Same size and rhythm as the holidays note above it, because they are the same
   kind of remark — a footnote to the table, not a second heading. The state
   colour is carried by the dot alone; the text stays in the note's grey so the
   two lines read as a pair rather than one of them shouting. */
.hours-note.is-state { margin-top: 6px; display: flex; align-items: baseline; gap: 7px; }
.hours-note.is-state .dot {
    width: 6px; height: 6px; border-radius: 50%; flex: none;
    position: relative; top: -1px;
}
.hours-note.is-state strong { font-weight: 600; color: var(--ink-500); }
.hours-note.is-state.is-open .dot { background: var(--success-600); }
.hours-note.is-state.is-soon .dot { background: var(--warning-600); }
.hours-note.is-state.is-shut .dot { background: var(--ink-400); }

/* --- Contact panel actions ------------------------------------------------
   Call, WhatsApp and Request an appointment, moved out of the sticky bar and
   into the panel that holds the details they act on. Full-width and stacked:
   the aside is 340px, three buttons in a row would each be ~100px, and a
   340px-wide target is the easier one to hit on a phone anyway. */
.contact-actions {
    display: grid; gap: 8px;
    margin-top: 18px; padding-top: 16px;
    border-top: 1px solid var(--line);
}
.contact-actions .btn { width: 100%; justify-content: center; }

/* The green rule belongs to the practice panel's own framing, not to the
   actions pattern — the contact page reuses `.contact-actions` inside a plain
   white card, where a light-green line would read as a stray. */
.contact-card .contact-actions { border-top-color: var(--stage-4); }

/* Nothing above it to separate from. */
.contact-actions.is-bare { border-top: 0; margin-top: 0; padding-top: 0; }

/* WhatsApp's own green, because the mark is WhatsApp's own — anything else and
   the button reads as the house palette borrowing a logo.

   The label is ink, not white, and that is measured rather than taste: white on
   #25D366 is 1.98:1, which fails every WCAG threshold there is, including the
   3:1 one for graphics. WhatsApp get away with it because their own white sits
   on the darker #075E54, or is a glyph nobody has to read. Ink on the brand
   green is 8.12:1 and keeps the colour that makes the button recognisable at a
   glance, which is the entire point of using it. */
.btn.btn-whatsapp {
    background: #25D366; border-color: #25D366; color: var(--ink-900); font-weight: 600;
}
.btn.btn-whatsapp:hover { background: #1FBE5B; border-color: #1FBE5B; color: var(--ink-900); }
.btn.btn-whatsapp:focus-visible { outline: 2px solid var(--ink-900); outline-offset: 2px; }
.brand-mark { width: 17px; height: 17px; flex: none; }

/* The map's own foot, so Directions sits under the thing it gives directions to.
   Named `.map-panel`, not `.map-card`: `.map-card` was already taken — it is the
   little logo-and-name popup the directory's map builds in JS when a pin is
   clicked, and it is a flex row. Reusing the name turned this card into a row
   too and squeezed the 280px map down to 66px beside the button. */
.map-panel > .card-foot { padding: 12px 14px; }
.btn.btn-block { width: 100%; justify-content: center; }

/* --- The reviews-count tooltip --------------------------------------------
   The moderation policy, hung off the count rather than set as a paragraph.
   Shown on hover, and on focus so it is reachable by tap and by keyboard — a
   hover-only tooltip is invisible to every phone visitor, which is most of
   them. `visibility` rather than `display` so the fade has something to
   animate and so the text keeps its box for a screen reader. */
.tip-wrap { position: relative; display: inline-block; font-weight: 400; }
.count-tip {
    color: var(--ink-500); cursor: help;
    border-bottom: 1px dotted var(--ink-400);
}
.count-tip::before { content: '('; border: 0; }
.count-tip::after  { content: ')'; border: 0; }
.count-tip:focus-visible { outline: 2px solid var(--brand-600); outline-offset: 3px; border-radius: 2px; }

/* Left-aligned to the trigger, not centred on it. The count always follows the
   word "Reviews" at the left edge of the content column, so a 268px box centred
   on it hangs off the page — measured, not guessed: centred, its left edge
   landed at roughly -10px and the first character of every line was cut. */
.tip {
    position: absolute; left: 0; bottom: calc(100% + 9px);
    width: max-content; max-width: min(268px, calc(100vw - 40px));
    background: var(--ink-900); color: #fff;
    font-size: 12.5px; font-weight: 400; line-height: 1.5; letter-spacing: 0;
    text-transform: none; text-align: left;
    padding: 9px 12px; border-radius: 8px;
    box-shadow: 0 8px 24px rgba(26, 33, 48, .22);
    opacity: 0; visibility: hidden; transition: opacity .13s ease, visibility .13s;
    z-index: 30; pointer-events: none;
}
.tip::after {
    content: ''; position: absolute; top: 100%; left: 2px;
    border: 6px solid transparent; border-top-color: var(--ink-900);
}
.tip-wrap:hover .tip,
.count-tip:focus .tip,
.count-tip:focus + .tip { opacity: 1; visibility: visible; }

@media (prefers-reduced-motion: reduce) { .tip { transition: none; } }

/* The action bar now holds one button and a line of explanation. */
.action-bar .bar-note { font-size: 12.5px; color: var(--ink-500); align-self: center; }

/* A card whose whole body is one piece of media. The head normally carries the
   top corners; with no head the card has to clip them itself, or a square video
   pokes out past the radius. */
.media-card { overflow: hidden; }

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

.price-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 20px; }

.price-card {
    background: var(--surface);
    border: 1.5px solid var(--line);
    border-radius: var(--radius-lg);
    padding: 26px 24px;
    display: flex; flex-direction: column;
    transition: border-color .14s, box-shadow .14s, transform .14s;
}
.price-card:hover { border-color: var(--brand-400); box-shadow: var(--shadow-md); transform: translateY(-2px); }
.price-card.featured { border-color: var(--brand-600); box-shadow: 0 0 0 3px rgba(58, 93, 168, .1), var(--shadow-md); }
.price-card .tier { font-size: 13px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--brand-600); }
.price-card .amount { font-size: 38px; font-weight: 600; color: var(--ink-900); margin-top: 12px; letter-spacing: -.03em; line-height: 1; }
.price-card .amount span { font-size: 15px; font-weight: 400; color: var(--ink-500); letter-spacing: 0; }
.price-card .desc { font-size: 14px; color: var(--ink-500); margin-top: 12px; flex: 1; line-height: 1.55; }
.price-card .btn { margin-top: 22px; width: 100%; }

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

.site-footer {
    background: var(--brand-900);
    color: rgba(255, 255, 255, .62);
    padding: 56px 0 32px;
    font-size: 14px;
    position: relative;
    overflow: hidden;
}
.site-footer::after {
    content: '';
    position: absolute;
    left: -6%; right: -6%; bottom: -120px;
    height: 260px;
    background-image: var(--mark-circles);
    opacity: .28;
    pointer-events: none;
}
.site-footer .inner { max-width: 1200px; margin: 0 auto; padding: 0 24px; position: relative; z-index: 1; }
.site-footer .cols { display: grid; grid-template-columns: 1.5fr repeat(3, 1fr); gap: 36px; }
/* 40% larger than the original 200px. */
.site-footer img.mark { width: 280px; height: auto; margin-bottom: 16px; }
.site-footer h4 { color: #fff; font-size: 13px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; margin-bottom: 14px; }
.site-footer a { color: rgba(255, 255, 255, .62); display: block; padding: 4px 0; }
.site-footer a:hover { color: #fff; }
.site-footer .legal {
    margin-top: 44px; padding-top: 22px;
    border-top: 1px solid rgba(255, 255, 255, .12);
    display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
    /* Was .45, which is 4.08:1 on --brand-900 — under the 4.5:1 that 13px text
       needs, and it had gone unnoticed because the *links* in this row take
       their colour from `.site-footer a` (.62) and always passed. Putting a
       link next to the copyright is what made the mismatch visible. .55 is
       5.31:1 and still reads as the quiet line it is meant to be. */
    font-size: 13px; color: rgba(255, 255, 255, .55);
}
.site-footer .legal a { display: inline; padding: 0; }
.site-footer .legal .links { display: flex; gap: 18px; flex-wrap: wrap; }
/* The studio credit, beside the copyright. Its separator is generated rather
   than written into the markup: a literal "·" between two sentences is read
   aloud, and belongs to neither of them. */
.site-footer .legal .credit::before { content: '·'; margin: 0 9px; opacity: .55; }

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

/* Was 900px. The logo is 307px wide rather than 220px, so the horizontal nav
   now needs about 1 170px to sit on one line; below that it wrapped and
   overflowed the bar. Collapsing to the menu button earlier is the honest
   consequence of the larger logo — a wrapped nav is worse than a tidy menu. */
@media (max-width: 1080px) {
    .site-nav {
        display: none;
        /* Hangs off the bottom of the header rather than a copy of its height.
           This was `top: 96px`, which is the same number written down twice —
           change the bar and the flyout silently floats away from it or overlaps
           it. `.site-header .inner` is positioned below, so 100% is its edge. */
        position: absolute; top: 100%; left: 0; right: 0;
        background: var(--surface);
        border-bottom: 1px solid var(--line);
        flex-direction: column; align-items: stretch;
        padding: 12px; gap: 2px;
        box-shadow: var(--shadow-lg);
    }
    .site-nav.open { display: flex; }
    .site-nav .sep { display: none; }
    .hero.hero-sm { min-height: 260px; }
    .site-nav a:not(.btn) { padding: 9px 12px; }

    /* Stacked, one per line — so each row becomes icon + label. */
    .site-nav a:not(.btn) { display: flex; align-items: center; gap: 12px; min-height: 48px; }
    .site-nav .b-icon { display: block; width: 30px; height: 30px; }
    /* The button no longer inherits the rule above, so give it its own comfortable
       size here — otherwise the most important call to action on the page ends up
       the smallest tap target in the menu. */
    /* min-height rather than padding arithmetic: the button carries a border and
       a smaller font than the nav links, so matching by padding alone drifts the
       moment either changes. 44px is the accessible minimum. */
    .site-nav a.btn { padding: 11px 16px; justify-content: center; min-height: 44px; }
    /* The green CTA sets its own padding, which overrode the comfortable mobile
       one above and left it a 40px tap target — under the 44px minimum, and the
       most important item in the menu. Same treatment as the Join button. */
    /* Left-aligned with the rest, not centred. Once every other row is an icon
       followed by a label, a centred one reads as a mistake — and the 44px
       minimum tap target is held by min-height either way. */
    .site-nav a.nav-cta:not(.btn) {
        padding: 9px 12px; min-height: 48px;
        display: flex; align-items: center; justify-content: flex-start; gap: 12px;
    }
    .site-burger { display: block; }
    .site-header .inner { position: relative; }
    .section { padding: 52px 0; }
    .site-footer .cols { grid-template-columns: 1fr 1fr; gap: 28px; }
}

@media (max-width: 560px) {
    .site-footer .cols { grid-template-columns: 1fr; }
    .hero .inner { padding: 56px 20px 62px; }
    .hero .cta-row .btn { width: 100%; }
    /* The hero is much taller once the copy wraps, which drags the circle band
       up behind the buttons. Anchor it to the floor and soften it instead. */
    .hero::after { bottom: -62%; height: 420px; opacity: .34; }
    .hero .eyebrow { font-size: 11px; letter-spacing: .1em; }
}

/* ==========================================================================
   Directory
   ========================================================================== */

/* --- Search bar: the three inputs a parent actually needs --- */
/* Flex, not a four-column grid: the distance select is only present once there
   is a place to measure from, and a fixed `auto auto` track left a gap where it
   used to be. Flex simply closes up, and wraps on a phone without a media query. */
.dir-search {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    padding: 14px;
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
}
.dir-search .field { flex: 1 1 210px; min-width: 0; }
.dir-search .select, .dir-search .btn { flex: 0 0 auto; }
.dir-search .btn { min-width: 108px; }

/* Once the fields have stacked, a narrow button sitting against the left edge
   is a small target below two full-width ones. It takes the row. */
@media (max-width: 560px) {
    .dir-search .field, .dir-search .select, .dir-search .btn { flex: 1 1 100%; }
}
.dir-search .field { position: relative; }
.dir-search .input { background: var(--surface); }
.dir-search .locate {
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    background: none; border: 0; padding: 7px; cursor: pointer; color: var(--brand-600);
    display: flex; border-radius: 6px;
}
.dir-search .locate:hover { background: var(--brand-50); }
.dir-search .locate svg { width: 18px; height: 18px; }
.dir-search .locate.busy { opacity: .5; pointer-events: none; }

/* Typeahead */
.suggest {
    position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 30;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius); box-shadow: var(--shadow-lg);
    max-height: 320px; overflow-y: auto; padding: 5px;
}
.suggest a { display: block; padding: 9px 12px; border-radius: 7px; color: var(--ink-900); }
.suggest a:hover, .suggest a.active { background: var(--brand-50); }
.suggest .s-label { font-size: 14px; font-weight: 500; }
.suggest .s-sub { font-size: 12.5px; color: var(--ink-500); }
.suggest .s-kind {
    font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em;
    color: var(--brand-600); font-weight: 600; float: right; margin-top: 2px;
}

/* --- Layout: filters beside results, map above on mobile --- */
.dir-layout { display: grid; grid-template-columns: 260px 1fr; gap: 28px; align-items: start; }

.dir-filters { position: sticky; top: 92px; }

.dir-filters-head { padding-bottom: 12px; border-bottom: 1px solid var(--line); }
.dir-filters-head h2 { font-size: 15px; color: var(--ink-900); margin: 0; }

/* Each vocabulary is a <details>. Fifty-nine checkboxes rendered open was about
   two thousand pixels of sidebar — so what could be filtered was invisible
   precisely because all of it was on screen. Closed, the seven headings fit
   above the fold. */
.dir-filters .group { border-bottom: 1px solid var(--line); }

.dir-filters summary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 13px 0;
    cursor: pointer;
    list-style: none;                 /* the default triangle, replaced below */
    color: var(--ink-900);
    font-size: 13.5px;
    font-weight: 550;
}
.dir-filters summary::-webkit-details-marker { display: none; }
.dir-filters summary:hover { color: var(--brand-700); }
.dir-filters summary:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; border-radius: 4px; }
.dir-filters summary .h { flex: 1 1 auto; min-width: 0; }

/* How many of this group are on, so a closed group still answers for itself. */
.dir-filters summary .on {
    flex: none;
    min-width: 19px;
    height: 19px;
    padding: 0 5px;
    border-radius: 99px;
    background: var(--brand-600);
    color: #fff;
    font-size: 11.5px;
    font-weight: 650;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.dir-filters summary .chev {
    flex: none; width: 16px; height: 16px; color: var(--ink-400);
    transition: transform .15s ease;
}
.dir-filters .group[open] > summary .chev { transform: rotate(180deg); }

/* The cap is a stop for a vocabulary that grows, not a squeeze on the ones we
   have. Measured rather than guessed: a row is 32px, so the longest list today
   — thirteen medical aids, thirteen languages — comes to 424px and fits. Two
   earlier guesses (268 then 360) both put a scrollbar inside a disclosure inside
   a sidebar, three levels of hiding for a list that was never too long. */
.dir-filters .opts { padding-bottom: 12px; max-height: 430px; overflow-y: auto; }
.dir-filters label {
    display: flex; align-items: flex-start; gap: 9px;
    font-size: 14px; padding: 5px 0; cursor: pointer; color: var(--ink-700);
}
.dir-filters label:hover { color: var(--ink-900); }
.dir-filters input[type="checkbox"], .dir-filters input[type="radio"] {
    width: 16px; height: 16px; accent-color: var(--brand-600); flex-shrink: 0; margin-top: 2px;
}
.dir-filters .n { margin-left: auto; font-size: 12px; color: var(--ink-400); font-variant-numeric: tabular-nums; }
/* A filter that would return nothing is dimmed to say so — but .45 put it at
   2.32:1 on white, which is unreadable rather than merely quiet. .75 is 4.52:1
   and still visibly secondary. "Leads nowhere" has to stay legible: it is
   information, not decoration. */
.dir-filters label.empty-option { opacity: .75; }

/* Removing one filter is a plain link to this page without it — a real URL,
   shareable, and it works with the script off. */
.chip a { display: flex; padding: 2px; border-radius: 50%; color: inherit; }
.chip a:hover { background: var(--brand-100); }
.chip a svg { width: 13px; height: 13px; }
.chip-clear { font-size: 13px; color: var(--ink-500); text-decoration: underline; }
.chip-clear:hover { color: var(--ink-900); }

/* Active filter chips */
.chips { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 18px; }
.chip {
    display: inline-flex; align-items: center; gap: 7px;
    background: var(--brand-50); border: 1px solid var(--brand-100);
    color: var(--brand-700); font-size: 13px; font-weight: 500;
    padding: 5px 8px 5px 12px; border-radius: 99px;
}
.chip button { background: none; border: 0; cursor: pointer; color: inherit; display: flex; padding: 2px; border-radius: 50%; }
.chip button:hover { background: var(--brand-100); }
.chip svg { width: 13px; height: 13px; }

/* --- Result cards --- */
.dir-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
.dir-count { font-size: 15px; color: var(--ink-500); }
.dir-count strong { color: var(--ink-900); font-weight: 600; }

.result-list { display: grid; gap: 16px; }

.result {
    display: grid; grid-template-columns: 92px 1fr; gap: 18px;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius-lg); padding: 20px;
    transition: border-color .14s, box-shadow .14s, transform .14s;
}
.result:hover { border-color: var(--brand-400); box-shadow: var(--shadow-md); transform: translateY(-1px); }

/* Events sit on flat grey rather than white-on-white. The fill is what defines
   the block, so the resting border is transparent rather than removed — the
   1px is still in the box model, so the hover state below can bring a colour in
   without the card shifting by a pixel.

   Scoped to events on purpose: `.result` is also the directory's practitioner
   card, where a white card on the page's own tint is doing a different job. */
.result-event {
    background: var(--surface-neutral);
    border-color: transparent;
}
.result-event:hover { border-color: var(--brand-400); background: var(--surface-neutral); }
.result .avatar-lg {
    width: 92px; height: 92px; border-radius: var(--radius);
    background: var(--brand-50); color: var(--brand-600);
    display: flex; align-items: center; justify-content: center;
    font-size: 26px; font-weight: 600; overflow: hidden;
}
.result .avatar-lg img { width: 100%; height: 100%; object-fit: cover; }

/* A logo is never cropped — see the note on .profile-photo.is-logo below. The
   tinted brand background is right behind initials and behind a portrait, but a
   logo is usually drawn for white, so the frame becomes white too. */
.result .avatar-lg.is-logo { background: #fff; padding: 8px; border: 1px solid var(--line); }
.result .avatar-lg.is-logo img { object-fit: contain; }
.result h3 { font-size: 17.5px; margin-bottom: 3px; }
.result h3 a { color: var(--ink-900); }
.result h3 a:hover { color: var(--brand-700); }
.result .who { font-size: 14px; color: var(--ink-700); margin-bottom: 6px; }
/* Place, distance and open/closed. Three items wrap on a phone, and a literal
   `·` between them cannot survive that: whichever item lands first on the
   second line starts it with what reads as a bullet. So the separation is the
   gap and the change of colour and weight, which wrap without leaving debris. */
.result .where { font-size: 13.5px; color: var(--ink-500); display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.result .where svg { width: 14px; height: 14px; flex-shrink: 0; }
.result .dist { color: var(--brand-700); font-weight: 600; }
.result .tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 11px; }
.result .actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }

/* --- Map --- */
.dir-map { height: 420px; border-radius: var(--radius-lg); overflow: hidden; border: 1px solid var(--line); margin-bottom: 20px; }
.dir-map.tall { height: 560px; }

/* Hidden by default and revealed inside the small-screen breakpoint below.
   Declared HERE, before that breakpoint: an equally specific rule later in the
   file wins regardless of the media query, which is exactly how the first
   version of this silently never appeared. */
.dir-map-toggle, .dir-filter-toggle { display: none; }
.dir-map-toggle button, .dir-filter-toggle button {
    border: 1px solid var(--line); background: var(--surface); color: var(--ink-700);
    border-radius: var(--radius-sm); padding: 7px 13px; font-size: 13.5px;
    font-weight: 600; cursor: pointer;
}
.dir-map-toggle button:hover,
.dir-filter-toggle button:hover { border-color: var(--brand-400); color: var(--brand-700); }
/* --------------------------------------------------------------------------
   Map info window
   --------------------------------------------------------------------------
   The card shown when a practice pin is clicked. It is one link: the name is
   the thing you click, so there is no separate "View profile" line.

   Google reserves a 48px row above the content for its close button — on a card
   with 57px of content that row was almost half the bubble and read as a bug.
   Collapsing it and floating the button over the corner is what removes the
   empty band. These selectors belong to the Maps JavaScript API rather than to
   us, so they are grouped and labelled here rather than scattered. */

.gm-style-iw.gm-style-iw-c { padding: 0 !important; border-radius: 12px; }
.gm-style-iw-d { overflow: visible !important; padding: 0 !important; max-height: none !important; }

.gm-style-iw-chr { height: 0; min-height: 0; overflow: visible; }
.gm-style-iw-ch { display: none; }
/* `!important` throughout because the Maps API sets position, width and height
   on this button as an inline style, and an inline style beats a stylesheet
   rule. Without it the button keeps `position: relative` and lands on top of
   the practice name instead of in the corner. */
.gm-style-iw-chr .gm-ui-hover-effect {
    position: absolute !important; top: 0 !important; right: 0 !important; left: auto !important;
    width: 30px !important; height: 30px !important;
    opacity: .45; z-index: 3;
}
.gm-style-iw-chr .gm-ui-hover-effect:hover { opacity: .9; }
.gm-style-iw-chr .gm-ui-hover-effect > span { margin: 7px !important; }

.map-card {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 30px 12px 12px;
    min-width: 190px; max-width: 280px;
    text-decoration: none; color: inherit;
    border-radius: 12px;
    transition: background-color .12s;
}
.map-card:hover { background: var(--brand-50); }

/* Contained, never cropped — a logo cropped to a square loses the wordmark,
   which is the half that identifies the practice. */
.map-card-logo {
    width: 46px; height: 46px; flex: 0 0 46px;
    object-fit: contain; background: #fff;
    border: 1px solid var(--line); border-radius: 9px; padding: 4px;
    box-sizing: border-box;
}
.map-card-logo.is-initials {
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--brand-50); border-color: transparent;
    color: var(--brand-700); font-weight: 700; font-size: 14px; padding: 0;
}

.map-card-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.map-card-name {
    font-weight: 700; font-size: 14.5px; line-height: 1.3; color: var(--ink-900);
}
.map-card:hover .map-card-name { color: var(--brand-700); }
.map-card-where { font-size: 12.5px; color: var(--ink-500); }

/* --- Profile pages --- */
.profile-head { display: grid; grid-template-columns: auto 1fr; gap: 24px; align-items: start; }
.profile-photo {
    width: 132px; height: 132px; border-radius: var(--radius-lg); overflow: hidden;
    background: rgba(255,255,255,.12); display: flex; align-items: center; justify-content: center;
    font-size: 40px; font-weight: 600; color: #fff; flex-shrink: 0;
}
.profile-photo img { width: 100%; height: 100%; object-fit: cover; }

/* A LOGO must never be cropped. `cover` is right for a photograph — filling the
   frame is what you want — but a logo cropped to a square loses the wordmark,
   which is the half that identifies the practice: "Fahrenheit Seafood & Grill"
   becomes "ahrenh Seafood &". `contain` fits the whole mark inside the frame and
   pads it instead.

   This applies wherever a logo appears, not only here: see
   `.result .avatar-lg.is-logo` above for the search cards and nearby lists. */
.profile-photo.is-logo { background: #fff; padding: 10px; }
.profile-photo.is-logo img { object-fit: contain; }

.action-bar {
    position: sticky; top: 72px; z-index: 20;
    background: rgba(255,255,255,.94);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    border-bottom: 1px solid var(--line);
    padding: 12px 0;
}
.action-bar .inner { max-width: 1200px; margin: 0 auto; padding: 0 24px; display: flex; gap: 10px; flex-wrap: wrap; }

.profile-grid { display: grid; grid-template-columns: 1fr 340px; gap: 32px; align-items: start; }
/* 40, and it means 40. This said `gap: 20px` and rendered 40: every child is a
   `.card`, so app.css's `.card + .card { margin-top: 20px }` was adding its own
   on top of the grid gap. Identical pixels to before — the point is that the
   next person to change this number gets what they type.

   The override has to out-specify `.card + .card` (0,2,0), and `> *` (0,1,0)
   does not — the first attempt at this rendered 60px, the grid gap plus the
   margin it had failed to cancel. Naming both classes settles it. */
.profile-aside { position: sticky; top: 150px; display: grid; gap: 40px; }
.profile-aside > .card + .card { margin-top: 0; }
.profile-aside > .card { margin-bottom: 0; }

.info-block { margin-bottom: 30px; }
.info-block h2 { font-size: 19px; margin-bottom: 12px; }

/* --- The practice page's left column spaces itself -------------------------
   `.info-block` carries `margin-bottom: 30px`, but the column also holds two
   plain `.card`s — the video and the photo gallery — which are not info-blocks
   and so were never given it. The gallery sat flush against "Services offered"
   with no gap at all, and `.card + .card` set the pair 20px apart where
   everything else on the page is 30.

   Fixed the way `.profile-aside` two lines above already does it: the column is
   a flex column with a `gap`, and its children's own vertical margins are
   neutralised so one rule decides the rhythm. Per-element margins are what let a
   block arrive with none. */
.profile-grid > div:first-child {
    display: flex;
    flex-direction: column;
    gap: 30px;
}
/* Has to out-specify app.css's `.card + .card` (0,2,0) as well as
   `.info-block` (0,1,0). `.profile-grid > div:first-child > *` is (0,2,1),
   which does win — but stating the card case explicitly means the rule does not
   quietly depend on that arithmetic staying true. */
.profile-grid > div:first-child > * {
    margin-top: 0;
    margin-bottom: 0;
}
.profile-grid > div:first-child > .card + .card { margin-top: 0; }

/* A section heading with a control opposite it — "Reviews (1)" and "Write a
   review". The h2 needs `margin: 0` so the two sit on a shared baseline, which
   is why this row previously carried an inline one; but that also removed the
   space under the whole row, leaving the heading flush on the card below. The
   row owns the spacing instead, matching `.info-block h2`'s 12px so the rhythm
   is the same as every other section on the page. */
.block-head {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 12px;
}
.block-head h2 { margin-bottom: 0; }

/* The site header is `position: sticky` and 96px tall, so anything scrolled to
   by anchor or by script lands *underneath* it. `scrollIntoView` and `:target`
   both measure against the viewport and neither knows the header is there —
   which is what `scroll-margin-top` is for. 96 plus room to breathe; app.css
   already uses the same number for the same reason. */
#enquiry,
.enquiry-done { scroll-margin-top: 112px; }

/* --- Social channels -------------------------------------------------------
   One tone, not six brand colours. Six logos in their own palettes is six things
   shouting over the page and over each other, and none of them belongs to the
   association — a single tone makes them read as one row of controls belonging
   to *this* site. Every one of those brands publishes a monochrome treatment for
   exactly this situation, so nothing is being misused. */
.social-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 16px;
}
.social-row .lbl {
    font-size: 11px;
    letter-spacing: .1em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--ink-500);
    margin-right: 2px;
}
.social-row a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 9px;
    border: 1px solid var(--line);
    background: var(--surface);
    color: var(--ink-500);
    transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.social-row a:hover {
    color: var(--brand-700);
    border-color: var(--brand-400);
    background: var(--brand-50);
}
.social-row a:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; }
.social-row .brand-mark { width: 17px; height: 17px; }

/* Inside the practice's contact card, which sits on its own tinted panel. */
.contact-card .social-row { padding: 14px 18px 2px; border-top: 1px solid var(--line); margin-top: 14px; }

/* In the footer, where the ground is --brand-900 rather than a white card.
   Restated rather than inherited: `.site-footer a` and `.social-row a` have the
   same specificity, so which one owns `display` and `padding` is decided purely
   by which appears later in this file — a dependency that would break silently
   the first time these blocks are reordered. Colours are white at low alpha
   rather than palette tokens because every token here is mixed for a light
   surface; the glyph lands at 6.9:1 on the chip, against 3:1 for a graphic. */
.site-footer .social-row { margin-top: 20px; gap: 10px; }
.site-footer .social-row a {
    display: inline-flex;
    padding: 0;
    color: rgba(255, 255, 255, .72);
    border-color: rgba(255, 255, 255, .18);
    background: rgba(255, 255, 255, .06);
}
.site-footer .social-row a:hover {
    color: #fff;
    border-color: rgba(255, 255, 255, .45);
    background: rgba(255, 255, 255, .12);
}
.site-footer .social-row a:focus-visible { outline: 2px solid var(--stage-4); outline-offset: 2px; }

/* --- Articles --------------------------------------------------------------
   A measure of about 68 characters for the body. Long-form reading is the one
   thing on this site where line length is the whole of the typography, and the
   directory's wide columns are the wrong shape for it. */
/* **The article page needs its own container, because there is no global one.**
   `.inner` is only ever styled scoped — `.section .inner`, `.hero .inner`,
   `.site-footer .inner` — so a bare `<div class="inner">` on this page inherited
   no max-width, no auto margin and no padding at all. Every element sat flush
   against the left edge of the window with no gutter, which is what "does not
   display well" looked like. */
.article-page .inner { max-width: 1200px; margin: 0 auto; padding: 0 24px; }

/* The measure for long-form reading. 780px puts a 17.5px line at roughly 70
   characters, which is where it should be — the directory's wide columns are
   the wrong shape for an article. */
.article-page .inner.narrow { max-width: 780px; }

/* A tinted band behind the headline, so the article starts somewhere rather
   than simply appearing under the navigation. */
.article-head {
    padding: 52px 0 34px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--line);
    margin-bottom: 34px;
}
.article-head h1 { font-size: clamp(30px, 4.6vw, 44px); line-height: 1.12; text-wrap: balance; }
.article-head .standfirst {
    font-size: 19px;
    line-height: 1.6;
    color: var(--ink-700);
    margin-top: 16px;
    max-width: 60ch;
}

.byline {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 18px;
    font-size: 13.5px;
    color: var(--ink-500);
}
.byline img { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; border: 1px solid var(--line); }
.byline .who { font-weight: 600; color: var(--ink-900); }
a.who { text-decoration: underline; text-decoration-color: var(--ink-300); text-underline-offset: 3px; }
a.who:hover { color: var(--brand-700); text-decoration-color: currentColor; }
.byline .sep { color: var(--ink-300); }

/* Wider than the text and narrower than the window: the picture gets to be the
   larger object without becoming a full-bleed banner the text then hangs off. */
.article-shot { margin: 0 0 34px; }
.article-shot img {
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: var(--radius-lg);
    border: 1px solid var(--line);
}

/* `.prose` carries `max-width: 72ch`, which at this size is 732px — narrower
   than the 780px column it sits in, and with no auto margin, so it hugged the
   left and left the body 48px off-centre against the headline above it. The
   column owns the measure here; the prose rule must not also try to. */
.article-body { font-size: 17.5px; line-height: 1.72; max-width: none; }
.article-body h2 { font-size: 25px; margin: 38px 0 12px; }
.article-body h3 { font-size: 19.5px; margin: 30px 0 10px; }
.article-body figure { margin: 30px 0; }
.article-body figure img { width: 100%; border-radius: var(--radius); border: 1px solid var(--line); }
.article-body figcaption { font-size: 13.5px; color: var(--ink-500); margin-top: 8px; }
.article-body blockquote {
    margin: 28px 0;
    padding: 4px 0 4px 22px;
    border-left: 3px solid var(--stage-4);
    font-size: 19px;
    color: var(--ink-900);
}

.article-gallery {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 16px;
    margin: 36px 0;
}
.article-gallery img {
    width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
    border-radius: var(--radius); border: 1px solid var(--line);
}
.article-gallery figcaption { font-size: 13px; color: var(--ink-500); margin-top: 6px; }

/* --- Previous / next ---
   Two halves of a row, each naming where it goes. An arrow on its own asks
   somebody to click and find out. */
.article-nav {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    margin-top: 44px;
    padding-top: 28px;
    border-top: 1px solid var(--line);
}
.article-nav a {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 16px 18px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink-900);
    transition: border-color .16s ease, box-shadow .16s ease, transform .16s ease;
}
.article-nav a:hover {
    border-color: var(--brand-500);
    box-shadow: var(--shadow-sm);
    transform: translateY(-1px);
    color: var(--ink-900);
}
.article-nav .dir {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
    font-weight: 600; color: var(--brand-600);
}
.article-nav .dir svg { width: 14px; height: 14px; }
.article-nav .t { font-size: 15.5px; font-weight: 550; line-height: 1.35; }
.article-nav .next { text-align: right; align-items: flex-end; }

@media (max-width: 620px) {
    .article-nav { grid-template-columns: 1fr; }
    .article-nav .next { text-align: left; align-items: flex-start; }
    .article-nav .next .dir { flex-direction: row-reverse; }
}

.author-card {
    display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap;
    margin: 44px 0 0; padding: 22px;
    background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-lg);
}
.author-card img { width: 72px; height: 72px; border-radius: 50%; object-fit: cover; flex: none; }
.author-card h2 { font-size: 19px; margin: 4px 0 6px; }
.author-card p { color: var(--ink-500); margin: 0 0 14px; font-size: 14.5px; }

/* The one thing every article must say, and the reason it is set apart rather
   than left as a last paragraph somebody skims past. */
.article-note {
    margin: 32px 0 0; padding: 16px 18px;
    border-left: 3px solid var(--brand-600); background: var(--brand-50);
    border-radius: 0 var(--radius) var(--radius) 0;
}
.article-note p { margin: 0; font-size: 14.5px; color: var(--ink-700); }

/* --- The list --- */
.article-lead {
    display: grid; grid-template-columns: 1.15fr 1fr; gap: 0;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius-lg); overflow: hidden;
    box-shadow: var(--shadow-sm); margin-bottom: 28px;
    color: inherit; transition: box-shadow .16s ease, transform .16s ease;
}
.article-lead:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); color: inherit; }
.article-lead .shot { min-height: 260px; }
.article-lead .shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.article-lead .words { padding: 30px 32px; align-self: center; }
.article-lead h2 { font-size: 27px; line-height: 1.2; margin: 8px 0 12px; text-wrap: balance; }
.article-lead p { color: var(--ink-700); font-size: 15.5px; line-height: 1.6; }

.article-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 22px; }
.article-card {
    display: flex; flex-direction: column;
    background: var(--surface); border: 1px solid var(--line);
    border-radius: var(--radius-lg); overflow: hidden;
    box-shadow: var(--shadow-sm); color: inherit;
    transition: box-shadow .16s ease, transform .16s ease;
}
.article-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); color: inherit; }
.article-card .shot { aspect-ratio: 16 / 9; }
.article-card .shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.article-card .words { padding: 20px 22px 22px; display: flex; flex-direction: column; flex: 1; }
.article-card h3 { font-size: 18px; line-height: 1.3; margin-bottom: 8px; text-wrap: balance; }
.article-card p { color: var(--ink-500); font-size: 14.5px; line-height: 1.55; flex: 1; }
.article-card .byline { margin-top: 14px; }

@media (max-width: 860px) {
    .article-lead { grid-template-columns: 1fr; }
    .article-lead .shot { min-height: 0; aspect-ratio: 16 / 9; }
    .article-lead .words { padding: 24px; }
}

/* --- The enquiry form's answer --------------------------------------------
   Replaces the form in place rather than reporting from the top of the page.
   Somebody who has just pressed Send is looking at the button they pressed;
   confirming it a screen and a half above that meant scrolling back up past
   the whole listing to find out whether anything had happened.

   Deliberately not the flash treatment. A flash is the site talking about a
   page it has just loaded; this is the answer to a question asked right here,
   and it sits in the footprint the question occupied. */
.enquiry-done { text-align: center; }
.enquiry-done .card-body { padding: 34px 26px 30px; }

.enquiry-done .done-mark {
    width: 46px;
    height: 46px;
    margin: 0 auto 15px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--success-600);
    color: #fff;                 /* 5.16:1 against the disc */
}
.enquiry-done .done-mark svg { width: 24px; height: 24px; }

.enquiry-done h3 {
    font-size: 18px;
    color: var(--ink-900);
    margin: 0 0 8px;
}
.enquiry-done p {
    color: var(--ink-500);
    margin: 0 auto 20px;
    max-width: 44ch;
    font-size: 15px;
    line-height: 1.6;
}

/* The mark is the one moving part, and only where motion is welcome: it draws
   itself the way a tick is written, which is the gesture the panel is making. */
@media (prefers-reduced-motion: no-preference) {
    .enquiry-done:not([hidden]) .done-mark { animation: enquiry-pop .34s cubic-bezier(.2, .9, .3, 1.3) both; }
    .enquiry-done:not([hidden]) .done-mark svg polyline {
        stroke-dasharray: 26;
        stroke-dashoffset: 26;
        animation: enquiry-tick .3s .16s ease-out forwards;
    }
}
@keyframes enquiry-pop  { from { transform: scale(.75); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes enquiry-tick { to { stroke-dashoffset: 0; } }


/* --- The event page's ticket panel ----------------------------------------
   Same family as the practice aside: the logo's light green on the head, the
   neutral grey on the rows. The head is set larger and in sentence case here
   rather than as an uppercase micro-label — this panel is a price list somebody
   reads down, not a field label.

   `overflow: hidden` because the head fills to the card's edges and the rows
   sit flush; without it the green corners square off past the radius. */
/* The aside selector is carried on purpose. `.event-page .profile-aside > .card`
   and its `> .card-head` already paint every panel in this column the neutral
   grey, at three classes deep — a bare `.ticket-card > .card-head` is two, loses
   the cascade, and the head silently came out grey. Matching the existing shape
   and adding the modifier is what wins it, and says why. */

/* The rows carry the grey; the rules between them are green, so the panel reads
   as one object rather than a green lid on a default table. */
/* The head carries the ticket glyph beside its label. Layout comes free from
   `.card-head .titled`; only the icon needs saying. Sized down from the
   `.card-head` default of 19px, which was set against a 16px sentence-case title
   — this one is a 13px uppercase micro-label, and 19px beside it reads as an
   icon with a caption rather than a labelled panel. It takes the title's ink
   rather than `--brand-600`: the house blue on the logo's light green is two
   unrelated hues arguing, where the ink is what the `h2` already uses. */
.ticket-card > .card-head .titled { gap: 8px; }
.ticket-card > .card-head .sec-icon { width: 15px; height: 15px; color: var(--ink-900); }

.ticket-card table.data td { background: var(--surface-neutral); }
.ticket-card table.data tr td { border-bottom: 1px solid var(--stage-4); }
.ticket-card table.data tr:last-child td { border-bottom: 0; }
.ticket-card table.data tbody tr:hover td { background: var(--surface-neutral); }

/* 13px, not 15px: the aside is 340px wide and the price sits beside the name in
   the same row, so at 15px a ticket called "Full conference — in person" wrapped
   to two lines and the row grew a step taller than its neighbours. A price list
   is read down the column of figures; the names only have to be legible, and a
   ragged left column fights that. */
.ticket-card table.data .row-title {
    font-size: 13px; font-weight: 500; color: var(--ink-900);
}
.ticket-card table.data td.num {
    font-size: 17px; font-weight: 600; color: var(--ink-900);
    white-space: nowrap; font-variant-numeric: tabular-nums;
}

/* --- The event page's optional hero image ---------------------------------
   Only when one has been uploaded: `.has-event-image` is the switch, and without
   it the hero is exactly the single column it has always been.

   The alignment is the requirement. The image stretches the full height of the
   copy column, so its top edge lands on the top of the Events pill and its
   bottom on the bottom of Register now — it is one grid row, and both columns
   are the height of the taller. `object-fit: cover` means the picture fills
   that box whatever height the title happens to make it, which is why the
   upload is specified at 16:9: the crop is then barely anything. */
.hero-sm.has-event-image .inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
    gap: 44px;
    align-items: stretch;
}
.hero-sm.has-event-image .event-hero-copy { min-width: 0; }

/* The image is taken out of flow, and that is the whole trick.

   With the <img> in flow at `height: 100%`, the percentage is against a parent
   whose own height is indefinite, so it silently falls back to the file's
   intrinsic aspect — the picture then *drove* the row height instead of
   following it, and sat 44px below the Register button. Measured 1.556:1, which
   is exactly the old 1400x900 upload, not the 16:9 the CSS appeared to ask for.

   Absolutely positioned, the figure contributes no height of its own: the row is
   the copy's height, the figure stretches to it, and the image fills that box.
   Top of the Events pill to the bottom of Register now, whatever the title does. */
.event-hero-figure {
    position: relative;
    min-width: 0;
    min-height: 240px;          /* a short title must not squash it to a strip */
}
.event-hero-figure img {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    /* 16:9 is the upload spec; `cover` is what honours it here. The frame takes
       whatever height the copy dictates — the alignment that was actually asked
       for — and a 16:9 source loses almost nothing to the crop. */
    object-fit: cover;
    display: block;
    border-radius: var(--radius-lg);
    box-shadow: 0 18px 44px rgba(9, 16, 32, .42);
    outline: 1px solid rgba(255, 255, 255, .10);
    outline-offset: -1px;
}

@media (max-width: 900px) {
    /* Stacked, image second — on a phone the title and Register now should be
       reachable without scrolling past a picture. Same call as the home hero. */
    .hero-sm.has-event-image .inner { grid-template-columns: 1fr; gap: 26px; }
    .event-hero-figure { order: 2; }
    /* Back in flow on a phone, where it is a band above nothing and can simply
       be 16:9. */
    .event-hero-figure { position: static; min-height: 0; }
    .event-hero-figure img { position: static; aspect-ratio: 16 / 9; height: auto; }
}

/* --- Event page type scale -------------------------------------------------
   Section heads, prose, subheads and facts had all sat between 15.5px and 19px,
   which is no scale at all: a reader skimming for the programme had nothing to
   skim by. These are the steps, each a clear jump rather than a nudge. */
.event-page .info-block > h2 {
    font-size: 27px;
    letter-spacing: -.014em;
    margin-bottom: 16px;
}
.event-page .prose h3 { font-size: 20.5px; letter-spacing: -.006em; }
.event-page .prose { font-size: 16.5px; }

/* The opening paragraph carries the argument for attending, so it is set up a
   step and darker — the eye should start there, not halfway down. */
.event-page .prose > p:first-child {
    font-size: 18.5px; line-height: 1.6; color: var(--ink-900);
}
.event-page .prose > p:first-child strong { font-weight: 600; }

/* --- Event page panels ----------------------------------------------------
   The same flat neutral grey the event cards use on the listing, so the page a
   card leads to is made of the same material the card was.

   Scoped to `.event-page`, not applied to `.form-panel` and `.card` globally:
   those two classes carry every form on the site and every card in the admin,
   and repainting the lot from here would be a site-wide change wearing the
   costume of a small one.

   The neutral border matters as much as the fill. `--line` is blue-grey, taken
   from the brand navy, and on a genuinely neutral panel it reads as a different
   material laid over the top. */
.event-page .form-panel {
    background: var(--surface-neutral);
    border-color: var(--surface-neutral-2);
}

/* The foot keeps its own reasoning — a step darker so the form has a visible
   end — in neutral rather than blue-grey. */
.event-page .form-panel .form-foot {
    background: var(--surface-neutral-2);
    border-top-color: #d8d8d8;
}

/* The Tickets panel beside the form, and its head. */
/* `:where()` drops these to a single class of specificity on purpose. They are
   the *default* look for a panel in this column, and a default that outranks its
   own modifiers is not a default — that is exactly what happened to the ticket
   panel's green head, which rendered grey because a three-class ancestor
   selector quietly beat it. Zeroed, any `.panel-card` simply wins. */
:where(.event-page .profile-aside) > .card {
    background: var(--surface-neutral);
    border-color: var(--surface-neutral-2);
}
:where(.event-page .profile-aside) > .card > :where(.card-head) {
    background: var(--surface-neutral-2);
    border-bottom-color: #d8d8d8;
}

/* Cards *inside* the form stay white. They are elements sitting on the panel —
   a ticket to choose, a notice to read — and the whole point of the panel going
   grey is that they now have more contrast against it, not less. */
.tag-row { display: flex; gap: 8px; flex-wrap: wrap; }

.hours-table { width: 100%; font-size: 14px; border-collapse: collapse; }
.hours-table td { padding: 6px 0; border-bottom: 1px solid var(--line); }
.hours-table tr:last-child td { border-bottom: 0; }
.hours-table td:last-child { text-align: right; color: var(--ink-500); font-variant-numeric: tabular-nums; }
.hours-table tr.today td { color: var(--brand-700); font-weight: 600; }
.hours-table tr.today td:last-child { color: var(--brand-700); }

.map-embed { height: 240px; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); }

/* Verified mark — the directory's core trust signal */
.verified {
    display: inline-flex; align-items: center; gap: 6px;
    font-size: 12.5px; font-weight: 600;
    color: var(--success-600); background: var(--success-100);
    padding: 4px 11px; border-radius: 99px;
}
.verified svg { width: 13px; height: 13px; }

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

    /* On a phone the 420px map pushed every result below the fold, so a parent
       scrolled past most of a screen before seeing a single practitioner. The
       results come first and the map follows, revealed by the toggle beside the
       count. `order` rather than moving the markup, so the desktop layout and
       the map's own initialisation are untouched. */
    /* The filter column stacks ABOVE the results on a phone — around 2,400px of
       checkboxes before the first practitioner. That, not the map, was what
       actually buried the results. Both are now behind toggles beside the
       count, and the results come first. */
    .dir-layout { display: flex; flex-direction: column; }
    .dir-layout .dir-filters     { order: 2; }
    .dir-layout .dir-results-col { order: 1; }

    .dir-filters:not([data-open]) { display: none; }

    .dir-results-col { display: flex; flex-direction: column; }
    .dir-results-col .dir-head    { order: 1; }
    .dir-results-col #dir-results { order: 2; }
    .dir-results-col .dir-map     { order: 3; margin: 20px 0 0; height: 320px; }
    .dir-results-col .pager       { order: 4; }

    /* Collapsed by default; the toggle sets [data-open]. */
    .dir-results-col .dir-map:not([data-open]) { display: none; }
    .dir-map-toggle, .dir-filter-toggle { display: block; }
    .dir-filters { position: static; }
    .profile-grid { grid-template-columns: 1fr; }
    .profile-aside { position: static; }
}

@media (max-width: 760px) {
    .dir-search { grid-template-columns: 1fr; }
    .result { grid-template-columns: 64px 1fr; gap: 14px; padding: 16px; }
    .result .avatar-lg { width: 64px; height: 64px; font-size: 20px; }
    .profile-head { grid-template-columns: 1fr; }
    .profile-photo { width: 96px; height: 96px; }
    .action-bar .inner { padding: 0 20px; }
    .action-bar .btn { flex: 1 1 auto; }
}

/* --- Article pages --------------------------------------------------------
   Prose in a measured column with a sidebar that carries the one action a
   reader of these pages actually wants — finding a practitioner. */
.article-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 48px;
    align-items: start;
}
.article-aside { position: sticky; top: 24px; }

.plain-list { list-style: none; margin: 0; padding: 0; }
.plain-list li { padding: 7px 0; border-top: 1px solid var(--line); font-size: 14px; }
.plain-list li:first-child { border-top: 0; padding-top: 0; }

/* --- Contact --- */
.contact-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 40px;
    align-items: start;
}

/* The online-attendance counter. Big enough to read across a desk while a
   session runs, and tabular so the digits do not shuffle sideways every second
   as the width of a 1 differs from an 8. */
.attend-counter {
    font-size: clamp(38px, 7vw, 56px);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    letter-spacing: .02em;
    line-height: 1.1;
    color: var(--brand-600);
}

/* The honeypot. Off-screen rather than display:none — some bots skip hidden
   inputs, and a screen reader user is told to leave it blank if they reach it. */
.trap {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

@media (max-width: 900px) {
    .article-layout, .contact-layout { grid-template-columns: 1fr; gap: 32px; }
    .article-aside { position: static; }
}

/* --- The association's positioning statement -----------------------------
   Set larger than body copy and given room to breathe. This is the one
   paragraph on the site that says what the association is for, so it is
   allowed to occupy the page rather than sit in a column of features. */
.statement {
    max-width: 60ch;
    margin: 0 auto;
    text-align: center;
    font-size: clamp(18px, 2vw, 21px);
    line-height: 1.62;
    color: var(--ink-700);
}
.statement strong { color: var(--ink-900); font-weight: 600; }
.statement .attribution {
    margin-top: 22px;
    font-size: 14px;
    color: var(--brand-700);
    letter-spacing: .01em;
}

/* A feature card that is itself a link. */
.feature-link { text-decoration: none; color: inherit; display: block; transition: transform .14s, box-shadow .14s; }
.feature-link:hover { transform: translateY(-2px); }
.feature-link .more {
    display: inline-flex; align-items: center; gap: 5px;
    margin-top: 14px; font-size: 14px; font-weight: 500; color: var(--brand-600);
}
.feature-link .more svg { width: 15px; height: 15px; }
.feature-link:hover .more { color: var(--brand-700); }

/* --- Service and condition landing pages ---------------------------------- */
.term-layout { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 44px; align-items: start; }
.term-aside { position: sticky; top: 24px; }

.term-province {
    font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em;
    color: var(--ink-500); margin: 26px 0 10px;
}
.term-province:first-of-type { margin-top: 0; }
.term-province a { color: inherit; }
.term-province a:hover { color: var(--brand-600); }

.term-result {
    display: flex; align-items: center; gap: 12px;
    padding: 14px 16px; margin-bottom: 8px;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
    text-decoration: none; color: inherit;
    transition: border-color .13s, transform .13s, box-shadow .13s;
}
.term-result:hover { border-color: var(--brand-400); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.term-result .who { flex: 1; min-width: 0; }
.term-result .n { display: block; font-size: 15.5px; font-weight: 500; color: var(--ink-900); }
.term-result .s { display: block; font-size: 13px; color: var(--ink-500); margin-top: 2px; }
.term-result .chev { width: 18px; height: 18px; color: var(--ink-400); flex-shrink: 0; }

.term-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; }
.term-card {
    display: block; padding: 20px; text-decoration: none; color: inherit;
    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm); transition: transform .13s, box-shadow .13s;
}
.term-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.term-card .n { display: block; font-size: 16px; font-weight: 600; color: var(--ink-900); letter-spacing: -.01em; }
.term-card .d { display: block; font-size: 13.5px; color: var(--ink-500); margin-top: 6px; line-height: 1.55; }
.term-card .c { display: block; font-size: 12.5px; color: var(--brand-600); margin-top: 10px; font-weight: 500; }

/* A tag that leads somewhere gets the affordance; one that does not, does not. */
.badge-link { text-decoration: none; cursor: pointer; transition: filter .12s; }
.badge-link:hover { filter: brightness(.94); }

@media (max-width: 900px) {
    .term-layout { grid-template-columns: 1fr; gap: 30px; }
    .term-aside { position: static; }
}

.hero .hero-links { margin-top: 16px; font-size: 14.5px; color: rgba(255, 255, 255, .75); }
.hero .hero-links a { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

/* The telephone field appears on public forms too — see app.css for the rule. */
.phone-row { display: flex; gap: 8px; }
.phone-row .phone-code { width: auto; min-width: 116px; max-width: 190px; flex-shrink: 0; }
.phone-row .phone-number { flex: 1; min-width: 0; font-variant-numeric: tabular-nums; }
@media (max-width: 480px) {
    .phone-row { flex-direction: column; }
    .phone-row .phone-code { width: 100%; max-width: none; }
}

/* Practice photograph gallery (public listing). Mirrors the editor's grid so
   what a member arranges is what a parent sees. */
.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
.photo-grid .photo-card {
    position: relative; aspect-ratio: 4 / 3; border-radius: 10px; overflow: hidden;
    border: 1px solid var(--line); cursor: zoom-in;
}
.photo-grid .photo-card img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-grid .photo-card:hover img { transform: scale(1.03); transition: transform .25s ease; }

body.lightbox-open { overflow: hidden; }
.lightbox {
    position: fixed; inset: 0; z-index: 1000;
    display: flex; align-items: center; justify-content: center; gap: 8px;
    background: rgba(9, 14, 24, .88); padding: 24px;
}
.lb-stage { margin: 0; max-width: min(1100px, 92vw); max-height: 88vh; text-align: center; }
.lb-stage img { max-width: 100%; max-height: 82vh; border-radius: 10px; display: block; margin: 0 auto; }
.lb-stage figcaption { color: #e5e7eb; font-size: 14px; margin-top: 10px; }
.lightbox button {
    border: 0; background: rgba(255, 255, 255, .12); color: #fff; cursor: pointer;
    border-radius: 999px; width: 44px; height: 44px; font-size: 26px; line-height: 1; flex: 0 0 auto;
}
.lightbox button:hover { background: rgba(255, 255, 255, .24); }
.lb-close { position: absolute; top: 18px; right: 18px; }
@media (max-width: 640px) {
    .lb-prev, .lb-next { position: absolute; bottom: 18px; }
    .lb-prev { left: 18px; } .lb-next { right: 18px; }
}

/* Click-to-load video: a thumbnail until someone presses play. */
.video-frame {
    position: relative; aspect-ratio: 16 / 9; background: #0f172a;
    display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.video-frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.video-frame iframe { width: 100%; height: 100%; border: 0; display: block; }
.video-play {
    position: absolute; width: 68px; height: 68px; border-radius: 50%; border: 0;
    background: rgba(255, 255, 255, .92); cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 6px 22px rgba(0, 0, 0, .32); transition: transform .15s ease;
}
.video-frame:hover .video-play { transform: scale(1.06); }
.video-play .tri {
    width: 0; height: 0; margin-left: 5px;
    border-left: 20px solid var(--brand-600, #017ca4);
    border-top: 12px solid transparent; border-bottom: 12px solid transparent;
}
.video-frame.is-playing .video-play { display: none; }

/* The small preview beside the link box in the listing editor. */
.video-preview { display: flex; gap: 12px; align-items: center; margin-top: 10px; }
.video-preview img { width: 128px; border-radius: 8px; display: block; }

/* ------------------------------------------------------------ Media library */
.media-search { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 26px; }
/* The design system gives .input and .select width:100%, which is right in a
   stacked form and wrong in a toolbar — without this the row becomes three
   stacked full-width controls. */
.media-search .input { flex: 1 1 220px; width: auto; min-width: 220px; }
.media-search .select { flex: 0 0 auto; width: auto; min-width: 170px; }

.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); gap: 22px; }

.media-card {
    display: block; border: 1px solid var(--line); border-radius: var(--radius-lg);
    overflow: hidden; background: var(--surface); color: inherit; text-decoration: none;
    transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}
a.media-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--brand-400); }

/* Locked tiles are deliberately not links and must not pretend to be: no hover
   lift, no pointer cursor. Nothing here to click. */
.media-card.is-locked { cursor: default; opacity: .96; }
.media-card.is-locked .media-thumb img { filter: grayscale(.75) brightness(.72); }

.media-thumb {
    position: relative; aspect-ratio: 16 / 9; background: #0f172a;
    display: flex; align-items: center; justify-content: center; overflow: hidden;
}
.media-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.media-placeholder { color: rgba(255, 255, 255, .5); font-size: 34px; }

.media-play {
    position: absolute; width: 54px; height: 54px; border-radius: 50%;
    background: rgba(255, 255, 255, .93); display: flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 16px rgba(0, 0, 0, .3);
}
.media-play .tri {
    width: 0; height: 0; margin-left: 4px;
    border-left: 16px solid var(--brand-600, #017ca4);
    border-top: 10px solid transparent; border-bottom: 10px solid transparent;
}

.media-lock {
    position: absolute; display: inline-flex; align-items: center; gap: 7px;
    padding: 8px 14px; border-radius: 999px; font-size: 13px; font-weight: 600;
    background: rgba(15, 23, 42, .82); color: #fff;
}
.media-lock svg { width: 15px; height: 15px; }

.media-duration {
    position: absolute; right: 8px; bottom: 8px; padding: 2px 7px; border-radius: 5px;
    background: rgba(15, 23, 42, .84); color: #fff; font-size: 12px;
}

.media-meta { padding: 14px 16px 16px; }
.media-meta h3 { font-size: 16px; margin: 0 0 4px; line-height: 1.35; }

.related-video { padding: 9px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.related-video:last-of-type { border-bottom: 0; }

/* ------------------------------------------------------------ Video comments */
.comments { margin-top: 34px; }
.comments-head h2 { font-size: 18px; margin: 0 0 16px; }
.comments-locked .cta-row { margin-top: 14px; }

.comment-form { margin-bottom: 26px; }
.comment-form-row { display: flex; gap: 12px; align-items: flex-start; }
.comment-form-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; gap: 12px; }
.replying-to {
    font-size: 13px; margin-bottom: 8px; padding: 6px 10px; border-radius: 8px;
    background: var(--surface-2); display: flex; gap: 10px; align-items: center;
}

.comment { padding: 16px 0; border-top: 1px solid var(--line); }
.comment:first-child { border-top: 0; }
.comment-head { display: flex; gap: 10px; align-items: center; margin-bottom: 7px; }
.comment-head .who { font-weight: 600; font-size: 14px; display: block; }
.comment-head .when { font-size: 12px; color: var(--ink-500); }
.comment-body { font-size: 15px; line-height: 1.6; overflow-wrap: anywhere; }
.comment-body.is-removed { color: var(--ink-500); font-style: italic; font-size: 14px; }

.comment-actions { display: flex; gap: 14px; margin-top: 8px; }
.link-btn {
    border: 0; background: none; padding: 0; cursor: pointer;
    font-size: 13px; font-weight: 600; color: var(--ink-500);
}
.link-btn:hover { color: var(--brand-700, #016585); text-decoration: underline; }
.link-btn.danger:hover { color: var(--danger-600, #b91c1c); }

/* Replies are indented once and only once — two levels is the whole model, so
   there is no deeper rule to write. */
.comment-replies { margin: 10px 0 0 34px; border-left: 2px solid var(--line); padding-left: 14px; }
.comment-replies .comment { padding: 12px 0; }
.comment-replies[data-collapsed] .comment.is-extra { display: none; }
.show-replies { margin-top: 6px; }

.avatar, .comment .avatar {
    width: 34px; height: 34px; flex: 0 0 auto; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--brand-50, #e6f3f8); color: var(--brand-700, #016585);
    font-size: 12px; font-weight: 700;
}
.avatar.sm { width: 27px; height: 27px; font-size: 11px; }

.edit-box { margin-top: 8px; }
.edit-actions { display: flex; gap: 8px; margin-top: 8px; }

.comment-staff-note {
    padding: 10px 14px; border-radius: 8px; margin-bottom: 22px;
    background: var(--surface-2); border: 1px dashed var(--line);
}

/* Practice facilities — icon and label, scanned rather than read. */
.facility-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 13px; }
.facility-list li {
    display: flex; align-items: center; gap: 11px;
    font-size: 14.5px; color: var(--ink-700);
}
.facility-icon {
    width: 30px; height: 30px; flex: 0 0 30px; padding: 5px;
    border-radius: 9px; color: var(--brand-600);
    background: var(--brand-50); box-sizing: border-box;
}

/* --------------------------------------------------------------------------
   Error pages

   A 404 here is usually a stale link to a practice — a listing that moved, or
   a member whose membership lapsed. The wide variant turns that arrival into
   a search rather than a dead end.
   -------------------------------------------------------------------------- */

.err-wrap.err-wide { max-width: 620px; }


.err-search {
    display: flex; gap: 8px; margin: 24px 0 0;
}
.err-search input {
    flex: 1 1 auto; min-width: 0;
}
.err-search .btn { flex: 0 0 auto; }

.err-or {
    margin: 22px 0 10px; font-size: 12.5px; text-transform: uppercase;
    letter-spacing: .06em; color: var(--ink-400);
}

.err-provinces {
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
}
.err-provinces a {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 7px 12px; border-radius: 999px;
    background: var(--field-bg); border: 1px solid var(--line);
    font-size: 13.5px; color: var(--ink-700); text-decoration: none;
}
.err-provinces a:hover { background: #fff; border-color: var(--brand-400); color: var(--brand-700); }
.err-provinces a span {
    font-size: 11.5px; color: var(--ink-400); font-variant-numeric: tabular-nums;
}

.err-home { margin-top: 26px; font-size: 13.5px; }

@media (max-width: 520px) {
    .err-search { flex-direction: column; }
    .err-search .btn { width: 100%; }
}

/* --------------------------------------------------------------------------
   Inline links in body copy

   `.prose a` was already underlined; muted helper text was not, and a link in
   it was distinguished from the surrounding grey by hue alone — 1.21:1, which
   a colour-blind reader cannot see at all. Underline every inline link in
   running text, wherever it sits.
   -------------------------------------------------------------------------- */

.small a:not(.btn), .muted a:not(.btn), p a:not(.btn), li a:not(.btn) {
    text-decoration: underline;
    text-underline-offset: 2px;
    text-decoration-thickness: 1px;
}
/* Navigation, cards and chips are lists of links, not prose — underlining them
   would be noise, and they are already distinguishable by position and shape. */
.site-nav a, .site-footer a, .result-card a, .chips a, .taglist a,
.pager a, .breadcrumbs a, .err-provinces a {
    text-decoration: none;
}

/* The event title, in the certificate's face — see the @font-face note in
   app.css. Used here and on the session titles, nowhere else. */
/* Inter throughout, so hierarchy comes from weight and size rather than from a
   second family.

   The SIZE is deliberately not set here. `.hero.hero-sm h1` caps every hero but
   the home page at clamp(27px, 3.6vw, 38px), and that cap is a system decision:
   page-to-page hero drift is what makes a site feel assembled rather than
   designed. An earlier version of this rule set 44px and lost to that cap
   anyway, being one point less specific — which was the right outcome for the
   wrong reason.

   What is set is the weight. 700 against the 600 every other hero uses is the
   one step of emphasis an event title earns, and it costs no height. */
.hero .event-title {
    font-weight: 700;
    text-wrap: balance;
}

/* The qualifier after the em-dash. Sans, lighter and letterspaced, so the pair
   reads as one title with a standfirst rather than as two headings. */
.hero .event-subtitle {
    font-size: 15px; font-weight: 600;
    letter-spacing: .06em; text-transform: uppercase;
    color: var(--stage-4);
    margin-top: 12px;
}

@media (max-width: 640px) {
    .hero .event-title { max-width: none; }
    .hero .event-subtitle { font-size: 13px; letter-spacing: .05em; }
}

/* --------------------------------------------------------------------------
   The floating WhatsApp button
   --------------------------------------------------------------------------
   WhatsApp's green with the association's navy on it. Both brands are present
   on purpose: the green and the mark are what make it recognisable across a
   room, and the navy is what says it belongs to this site rather than being a
   third-party widget bolted on.

   The navy is also what makes it legible. White on #25D366 measures 1.98:1 —
   below even the 3:1 that non-text graphics need — which is why WhatsApp's own
   white sits on their darker #075E54. `--brand-900` on the brand green is
   7.10:1, so the lockup keeps the colour everyone recognises and the text is
   properly readable. */
.chat-fab {
    position: fixed; right: 20px; bottom: 20px; z-index: 60;
    display: flex; align-items: flex-start; gap: 6px;
    opacity: 0; transform: translateY(10px);
    transition: opacity .2s ease, transform .2s ease;
}
.chat-fab.is-in { opacity: 1; transform: none; }

.chat-fab-btn {
    display: flex; align-items: center; gap: 11px;
    background: #25D366; color: var(--brand-900);
    padding: 11px 18px 11px 13px; border-radius: 99px;
    box-shadow: 0 10px 28px rgba(27, 42, 79, .26), 0 2px 6px rgba(27, 42, 79, .16);
    text-decoration: none;
    transition: transform .14s ease, box-shadow .14s ease, background .14s ease;
}
.chat-fab-btn:hover {
    background: #22C55E; color: var(--brand-900);
    transform: translateY(-2px);
    box-shadow: 0 14px 34px rgba(27, 42, 79, .3), 0 2px 6px rgba(27, 42, 79, .18);
}
.chat-fab-btn:focus-visible { outline: 3px solid var(--brand-900); outline-offset: 3px; }

/* The mark sits in a navy disc — the same move the brand icons make, and what
   ties a third-party logo into the house set without altering the logo. */
.chat-fab-mark {
    width: 36px; height: 36px; border-radius: 50%; flex: none;
    background: var(--brand-900); color: #25D366;
    display: inline-flex; align-items: center; justify-content: center;
}
.chat-fab-mark .brand-mark { width: 21px; height: 21px; }

.chat-fab-text { display: flex; flex-direction: column; line-height: 1.25; }
.chat-fab-text strong { font-size: 14.5px; font-weight: 600; }
.chat-fab-text .lead  { font-size: 12px; opacity: .82; }

.chat-fab-close {
    width: 22px; height: 22px; flex: none; margin-top: -2px;
    border: 0; border-radius: 50%; padding: 0; cursor: pointer;
    background: var(--brand-900); color: #fff;
    display: inline-flex; align-items: center; justify-content: center;
    box-shadow: 0 2px 6px rgba(27, 42, 79, .3);
}
.chat-fab-close svg { width: 11px; height: 11px; }
.chat-fab-close:hover { background: #000; }
.chat-fab-close:focus-visible { outline: 2px solid var(--brand-900); outline-offset: 2px; }

/* On a phone the caption is most of the width of the screen, so it goes and the
   button becomes the circle it always was underneath. The label survives for a
   screen reader in the `sr-only` span the markup already carries. */
@media (max-width: 560px) {
    .chat-fab { right: 14px; bottom: 14px; }
    .chat-fab-btn { padding: 6px; gap: 0; }
    .chat-fab-text { display: none; }
    .chat-fab-mark { width: 44px; height: 44px; }
    .chat-fab-mark .brand-mark { width: 25px; height: 25px; }
}

@media (prefers-reduced-motion: reduce) {
    .chat-fab { transition: none; }
    .chat-fab-btn:hover { transform: none; }
}
