/**
 * Ability Chiropractic — block styles.
 *
 * SPECIFICITY CONTRACT
 * Every block root carries two classes, `ab` and `ab-<block>`, so every descendant
 * selector reads .ab .ab-x = (0,2,0). That beats `body p` (0,0,2), `h2` (0,0,1) and every
 * other non-important rule in theme.scss without a single !important of our own. Where the
 * theme also targets the element by name, we name the element too: `.ab h2.ab-sh__h` is
 * (0,2,1). That is the prototype's own `h3.t2pin` lesson generalised into a rule.
 *
 * All internal spacing is expressed as `gap` and `padding`, never `margin`, because
 * theme.scss:95 locks heading margins with !important. Those two channels no existing rule
 * touches, which is what keeps the rest of this file clean.
 */

/* =========================================================================
   The only two !important counter-rules in this file.
   ====================================================================== */

/* Neutralises thegem-child/css/theme.scss:95 — h1,h2,h3,h4 { margin: 0 0 25px !important } */
.ab h1,
.ab h2,
.ab h3,
.ab h4 {
    margin: 0 !important;
}

/* =========================================================================
   Testimonial video deck — isolated from the Core Values .ab-deck component
   ====================================================================== */
.ab-video-deck {
    /* --ab-video-card used to be declared here as well, as a viewport-height budget. It was
       unreachable: the same selector at the same specificity redeclares it further down with
       no media query on either, so the later one always won. Removed rather than reconciled,
       because a dead declaration in the one place someone will look to change the card size
       costs an afternoon. The live value is in the deck's own block below. */
    width: 100%;
}

/* =========================================================================
   ab-videoband — the header beside the deck
   ====================================================================== */

/* Asked for by class on ability_section, the way ab-aside is, and placed explicitly for the
   same reason: the band carries three children — header, deck, button — and an auto-placed
   grid would stack them. The header is centred against the deck rather than sitting above
   it, so the two columns share a middle line instead of the copy floating at the top of a
   very tall neighbour. Below 1024 the grid is off entirely and the three stack, which is
   what a phone wants anyway. */
@media (min-width: 1024px) {
    .ab-videoband .ab-sec__inner {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        /* The deck spans both rows so the copy and its button stay a single centred block
           beside it, rather than the button being pushed to a row of its own height. */
        grid-template-rows: auto auto;
        align-items: center;
        column-gap: var(--ab-s-16);
        row-gap: var(--ab-s-6);
    }

    .ab-videoband .ab-sec__inner > .ab-sh {
        grid-row: 1;
        grid-column: 1;
        align-self: end;
        /* The header's own 32px of trailing space is rhythm for a block underneath it.
           Beside one, it is just an offset that breaks the centring. */
        padding-bottom: 0;
    }

    /* WPBakery wraps a raw-html block in .wpb_raw_html > .wpb_wrapper, so the deck is a
       grandchild and the wrapper is what the grid actually places. Margin AND padding to
       zero: the rhythm mirror gives a wrapped block 24px of trailing space, which here is
       24px of dead band under the deck and none above it. */
    .ab-videoband .ab-sec__inner > .wpb_raw_html > .wpb_wrapper {
        margin: 0;
        padding-bottom: 0;
    }

    .ab-videoband .ab-sec__inner > .wpb_raw_html {
        grid-row: 1 / span 2;
        grid-column: 2;
        margin-block: auto;
    }

    /* Doubled up on the element's own two classes, because WPBakery ships
       `.wpb_content_element { margin-bottom: 24px }` and that 24px was the entire
       imbalance: the band measured 96 above the deck and 120 below on padding that is 96
       both ways. Four shorter selectors were tried first and every one of them lost. */
    .ab-videoband .ab-sec__inner > .wpb_raw_html.wpb_content_element {
        margin-bottom: 0;
    }

    /* The dots hang below the stage, so centring the deck's box alone still leaves the film
       reading high. Their height comes back as padding on top, and it is the FILM that ends
       up on the band's middle line — which is where the eye puts it, not the deck's box.

       This tracks the dots rather than the card, so it stays correct at any card size. */
    .ab-videoband .ab-video-deck {
        padding-top: calc(var(--ab-s-2) + 16px);

        /* 340px made a 604px card, which stood taller than the copy beside it and pushed the
           band past a screen. 264 puts it at 469 — measured off the mockup, and close enough
           to the copy column's own height that the two read as a pair.

           Changed through the WIDTH, never by capping the height or touching the poster's
           `aspect-ratio: 9/16`. Everything derives from this one value: the card's width, the
           stage's width (+46px for the depth offset) and the stage's height (× 16/9). More to
           the point, the lightbox FLIP takes a UNIFORM scale from the width alone —
           `scale = fromRect.width / to.width` in ability-blocks.js — on the stated assumption
           that the card and the film are both 9:16. Shorten the card any other way and the
           film overhangs the poster on the first frame: it still animates, it just stops
           looking like it grew out of the thing that was pressed.

           Desktop only. Below this breakpoint the card already uses the width a phone has. */
        --ab-video-card: 264px;
    }

    /* Under the copy, not under the deck: it belongs to the sentence that introduces the
       stories, and in the right column it was reading as a control of the pager above it. */
    .ab-videoband .ab-sec__inner > .ab-btn {
        grid-row: 2;
        grid-column: 1;
        align-self: start;
        justify-self: start;
    }

    /* The reviews run the full band, under both columns.
     *
     * Every other child of this grid is placed explicitly above — header, deck, button — and
     * the reviews were the one that was not, so auto-placement dropped them into the first
     * free cell: row 3, column 1. Half the band, with the right half empty beside them. The
     * Trustindex widget is built to lay its cards out in three columns (`ti-col-3`, a 300px
     * review target width); in a 555px column it can only ever show one, which is what the
     * band was rendering.
     *
     * ONE selector now, where there used to be three. ability_reviews returns a container of
     * ours whichever way it resolves — .ab-reviews for the authored cards, and the same class
     * plus .ab-reviews--live wrapping whatever the plugin hands back. Needing one selector per
     * possible plugin output was the tell that the block was missing a container of its own.
     *
     * Placement is all that is left in this band. The strip's own treatment — the full bleed,
     * the four-card pin, the fade at both ends — moved to .ab-reviews--live down in the
     * ability_reviews section, so every page that uses the block gets it and not this one
     * alone.
     *
     * Row 3 keeps the order the band already has — copy, button, then the reviews across the
     * bottom — rather than moving anything. */
    .ab-videoband .ab-sec__inner > .ab-reviews {
        grid-row: 3;
        grid-column: 1 / -1;
    }
}

/* =========================================================================
   ability_media with a film / [video_reel] — the deck and its lightbox
   ====================================================================== */

/* The values deck's mechanic, brought to desktop: one custom property drives translate,
   scale, opacity and blur together, so the card behind reads as depth rather than as a
   second rectangle. The numbers are .ab-deck__card's own, unchanged. What differs is the
   breakpoint — ability_values_deck becomes a grid above 1024 because nine cards do not
   belong in a pile, but two do, and a 9:16 film beside a paragraph is the shape this
   arrangement is for. */
.ab-video-deck {
    --ab-video-card: min(340px, 78vw);

    /* Explicit: the wrapper measured 24px taller than the deck inside it, and that surplus
       is what tipped the band 96 above / 120 below. */
    margin: 0;
    position: relative;
    display: grid;
    justify-items: center;
    gap: var(--ab-s-2);
}

.ab-video-deck__stage {
    position: relative;
    width: calc(var(--ab-video-card) + 46px);
    height: calc(var(--ab-video-card) * 16 / 9);
    touch-action: pan-y;
}

/* `pan-y` is a promise the stage makes to the browser: hand me the horizontal gesture, I am
   going to consume it. Below three films it consumes nothing — the drag listeners are not
   bound at all — so the promise is a lie that costs the user a pinch-zoom and any horizontal
   gesture the page above might want. Released, not narrowed. */
.ab-video-deck:not(.ab-video-deck--paged) .ab-video-deck__stage {
    touch-action: auto;
}

.ab .ab-video-deck__card {
    --d: 0;

    position: absolute;
    top: 0;
    left: 0;
    overflow: hidden;
    width: var(--ab-video-card);
    border-radius: var(--ab-r-lg);
    background: var(--ab-b100);
    box-shadow: var(--ab-shadow-card);
    transform-origin: center right;
    transform: translateX(calc(var(--d) * 34px)) scale(calc(1 - var(--d) * .062));
    opacity: min(1, calc(3 - var(--d)));
    filter: blur(calc(var(--d) * 2.6px)) brightness(calc(1 + var(--d) * .13));
    transition: transform .38s var(--ab-ease-out), filter .38s var(--ab-ease-out),
        opacity .38s var(--ab-ease-out);
    will-change: transform, filter, opacity;
}

.ab .ab-video-deck__card[data-ab-video-front] {
    z-index: 2;
    box-shadow:
        -2px 26px 54px -14px rgba(7, 20, 38, .28),
        10px 14px 34px -10px rgba(7, 20, 38, .22),
        0 3px 8px rgba(7, 20, 38, .12);
}

.ab .ab-video-deck__open {
    position: relative;
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    user-select: none;
    touch-action: pan-y;
}

.ab .ab-video-deck__poster {
    display: block;
    width: 100%;
    aspect-ratio: 9 / 16;
    object-fit: cover;
    /* The browser's own image drag was eating the gesture: pointerdown arrived, the native
       drag-and-drop took the pointer, and no move or release ever reached the deck. */
    -webkit-user-drag: none;
    user-select: none;
    pointer-events: none;
}

/* Only under the cue, and only as far as it needs to go: a scrim over the whole frame would
   be tinting a photograph, which the system does not do. */
.ab .ab-video-deck__scrim {
    position: absolute;
    inset: auto 0 0;
    height: 46%;
    background: linear-gradient(to top, rgba(7, 20, 38, .68), rgba(7, 20, 38, 0));
    pointer-events: none;
}

.ab .ab-video-deck__cue {
    position: absolute;
    inset: auto 0 0;
    display: flex;
    align-items: center;
    gap: var(--ab-s-3);
    padding: var(--ab-s-4);
    color: var(--ab-white);
    text-align: left;
}

.ab .ab-video-deck__ring {
    display: grid;
    flex: 0 0 auto;
    place-items: center;
    width: 52px;
    height: 52px;
    border: 2px solid rgba(255, 255, 255, .9);
    border-radius: var(--ab-r-pill);
    background: rgba(12, 26, 44, .3);
    backdrop-filter: blur(2px);
    transition: transform .3s var(--ab-ease-out), background .3s var(--ab-ease-out);
}

.ab .ab-video-deck__open:hover .ab-video-deck__ring,
.ab .ab-video-deck__open:focus-visible .ab-video-deck__ring {
    transform: scale(1.07);
    background: rgba(12, 26, 44, .5);
}

.ab .ab-video-deck__ring svg { width: 22px; height: 22px; margin-left: 3px; }

.ab .ab-video-deck__label {
    font-family: var(--ab-font);
    font-size: var(--ab-t-small);
    font-weight: 700;
    line-height: 1.3;
}

.ab-video-deck__dots {
    display: flex;
    justify-content: center;
    gap: 8px;
    /* Close to the cards it pages: a pager belongs to the thing above it. */
    padding-top: var(--ab-s-2);
}

.ab .ab-video-deck__dot {
    width: 8px;
    height: 8px;
    padding: 0;
    border: 0;
    border-radius: var(--ab-r-pill);
    background: var(--ab-bd);
    box-shadow: 0 0 0 1.5px var(--ab-dot-ring);
    cursor: pointer;
    transition: width .25s var(--ab-ease-out), background .25s var(--ab-ease-out);
}

.ab .ab-video-deck__dot.is-on {
    width: 22px;
    background: var(--ab-b600);
}

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

/* --- The lightbox ---------------------------------------------------------- */

.ab-lightbox {
    width: 100vw;
    max-width: none;
    height: 100svh;
    max-height: none;
    overflow: hidden;
    padding: 0;
    border: 0;
    background: transparent;
}

/* Keyed on the class, not on [open]: showModal() sets `open` synchronously, so a rule
   hanging off it has no frame at opacity 0 to transition FROM and the scrim just appears.
   The class is added in a rAF, which is that missing frame. */
.ab-lightbox::backdrop {
    background: rgba(7, 20, 38, .82);
    backdrop-filter: blur(6px);
    opacity: 0;
    transition: opacity .28s cubic-bezier(.22, 1, .32, 1);
}

.ab-lightbox.is-in::backdrop { opacity: 1; }

/* Slightly slower than the film's 260ms return, so the page does not snap back before the
   card has caught it. */
.ab-lightbox.is-out::backdrop { opacity: 0; transition: opacity .3s cubic-bezier(.22, 1, .32, 1); }

.ab-lightbox__inner {
    /* The sheet's border-box reset is scoped to `.ab`, and a dialog is outside it — without
       this the padding is added to the 100% and the film runs edge to edge. */
    box-sizing: border-box;
    /* A column now, because the track has to take the height that is left after the dots
       rather than a share of it. Relative, because the arrows hang off its padding box —
       inside the track they would scroll away with the film. */
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
    padding: clamp(16px, 4vw, 48px);
    opacity: 0;
    transform: scale(.97);
    transition: opacity .28s cubic-bezier(.22, 1, .32, 1), transform .28s cubic-bezier(.22, 1, .32, 1);
}

.ab-lightbox.is-in .ab-lightbox__inner {
    opacity: 1;
    transform: none;
}

/* The film's own arrival is a FLIP from the card, run in JS — the container only fades, or
   the two animations fight and the film slides inside a box that is also moving. */
.ab-lightbox.is-flipping .ab-lightbox__inner {
    transform: none;
    transition: opacity .18s linear;
}

/* On the way out the inner does not fade at all: the film has to stay visible for the whole
   journey back to the card, and it is the backdrop that carries the dissolve. */
.ab-lightbox.is-out .ab-lightbox__inner {
    opacity: 1;
    transform: none;
    transition: none;
}

.ab-lightbox.is-out .ab-lightbox__close { opacity: 0; transition: opacity .12s linear; }

/* The films sit on a horizontally scroll-snapping track, one per view. The swipe IS the
   native scroll — the same contract the reviews and doctors carousels run on, and the reason
   there is no gesture code behind this: JS only keeps the dots honest and lets them drive
   the scroll.

   `min-height: 0` is not decoration. A flex item's default `min-height: auto` refuses to
   shrink below its content, so the track would grow to the tallest film and push the dots
   off the bottom of the dialog instead of taking the height it is given. */
.ab-lightbox__track {
    /* The `.ab` border-box reset does not reach inside the dialog, and the padding below has
       to come out of the height flex hands this, not be added to it. */
    box-sizing: border-box;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.ab-lightbox__track::-webkit-scrollbar { display: none; }

/* `center`, not `start`: the slot inside is `width: fit-content`, so a portrait film and a
   landscape one are different widths and snapping to their left edges would land them in
   different places. */
/* The close button hangs 18px past the film's top and right edges, and a scrollport clips on
   BOTH axes as soon as either one of them stops being `visible` — measured, the top 18px were
   cut at 1440 and the right 18px at 390, where the film fills the width. The room comes out of
   the film rather than out of the button: moving the button to the viewport corner is the one
   thing .ab-lightbox__slot exists to avoid.

   It sits on the slide and not on the track because scrollLeft is set from `offsetLeft`, which
   counts a scroll container's own padding — the track has to stay unpadded or every programmatic
   scroll lands one padding short. border-box for the same reason .ab-lightbox__inner declares
   it: the sheet's reset is scoped to `.ab` and a dialog is outside it, so without this the
   padding is added to the 100% and each slide is wider than one view. */
.ab-lightbox__slide {
    box-sizing: border-box;
    flex: 0 0 100%;
    padding: var(--ab-s-6);
    scroll-snap-align: center;
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
}

/* The slot shrinks to the film so the close button has something to hang off. Anchored to
   the viewport it sat in a far corner with nothing under it, which reads as chrome of the
   page rather than of the thing it closes. */
.ab-lightbox__slot {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: fit-content;
    max-width: 100%;
    height: 100%;
    margin-inline: auto;
}

/* The film's own shape, once it knows it. --ab-lb-ratio is written from videoWidth and
   videoHeight on loadedmetadata; 9/16 is only the placeholder for the frame or two before
   that, because the vertical testimonials are the common case. Hardcoding it letterboxed the
   16:9 clinic tour inside a portrait box with black bands top and bottom.

   Both dimensions auto, with both caps declared, so whichever one runs out first is the one
   that binds — a portrait film in a wide window is capped by height, the same film in a narrow
   one by width. `height: 100%` used to be here instead, and because an explicit height always
   binds the box stayed full-height while the width capped under it: at 390 the film sat in a
   310×732 box with 90px of black above and below it.

   The note this replaces said auto/auto collapsed the element to 0×0 before the file reported
   a size, and the FLIP measures immediately. Two things changed. Every slide now carries a
   `poster`, so the intrinsic size is the poster's from the first frame; and the FLIP has always
   measured inside shape(), which is gated on loadedmetadata — there is no window in which it
   reads an unsized box. */
.ab-lightbox video {
    display: block;
    width: auto;
    max-width: 100%;
    height: auto;
    max-height: 100%;
    aspect-ratio: var(--ab-lb-ratio, 9 / 16);
    border-radius: var(--ab-r-lg);
    background: #000;
    box-shadow: 0 30px 80px -20px rgba(0, 0, 0, .7);
}

/* On the film's own top-right corner, half over the edge: unmistakably the control that
   dismisses THIS, and it never covers the native controls along the bottom. */
.ab-lightbox__close {
    position: absolute;
    top: -18px;
    right: -18px;
    z-index: 2;
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    padding: 0;
    border: 1px solid rgba(255, 255, 255, .35);
    border-radius: var(--ab-r-pill);
    background: rgba(12, 26, 44, .5);
    color: #fff;
    cursor: pointer;
    transition: background .2s cubic-bezier(.22, 1, .32, 1), transform .2s cubic-bezier(.22, 1, .32, 1);
}

.ab-lightbox__close:hover,
.ab-lightbox__close:focus-visible {
    background: rgba(12, 26, 44, .78);
    transform: scale(1.06);
}

.ab-lightbox__close svg { width: 20px; height: 20px; }

/* Step buttons — desktop only.
 *
 * The swipe is the whole control on a touch screen and it is invisible; with a mouse there is
 * nothing to swipe with and an 8px dot is a poor target. These are that control made visible.
 * Below 1024 they stay off: the finger already has the track, and two 48px circles over a
 * portrait film on a 390px screen cover the thing they are there to navigate.
 *
 * `display: none` is the default rather than the exception, so the [hidden] the JS sets for a
 * single film keeps working at every width.
 */
.ab-lightbox__nav { display: none; }

@media (min-width: 1024px) {
    .ab-lightbox__nav {
        position: absolute;
        top: 50%;
        z-index: 3;
        display: grid;
        place-items: center;
        width: 48px;
        height: 48px;
        padding: 0;
        transform: translateY(-50%);
        border: 1px solid rgba(255, 255, 255, .35);
        border-radius: var(--ab-r-pill);
        background: rgba(12, 26, 44, .5);
        color: #fff;
        cursor: pointer;
        transition: background .2s cubic-bezier(.22, 1, .32, 1), opacity .2s linear;
    }

    /* The attribute still wins over the rule above it. */
    .ab-lightbox__nav[hidden] { display: none; }

    .ab-lightbox__nav--prev { left: 0; }
    .ab-lightbox__nav--next { right: 0; }

    /* One glyph, turned. The accordion's chevron points down and is copied verbatim from the
       location switcher precisely so the two cannot drift; redrawing it sideways here would
       start a third copy. */
    .ab-lightbox__nav svg {
        width: 15px;
        height: 15px;
    }

    .ab-lightbox__nav--prev svg { transform: rotate(90deg); }
    .ab-lightbox__nav--next svg { transform: rotate(-90deg); }

    .ab-lightbox__nav:hover:not(:disabled),
    .ab-lightbox__nav:focus-visible { background: rgba(12, 26, 44, .78); }

    .ab-lightbox__nav:focus-visible {
        outline: 3px solid var(--ab-on-dark);
        outline-offset: 3px;
    }

    /* Dimmed and inert at the ends rather than removed: a control that disappears takes its
       own position with it and the remaining one jumps across the film. */
    .ab-lightbox__nav:disabled {
        opacity: .3;
        cursor: default;
    }

    /* They belong to the film, and on the way out the film is travelling home. */
    .ab-lightbox.is-out .ab-lightbox__nav { opacity: 0; transition: opacity .12s linear; }
}

/* The dots keep the colour each state already has and gain a white hairline outside them —
   the same device the footer's Schedule button uses. On a near-black scrim an 8px Border-grey
   dot and a Blue 600 one are both dark, and the outline is what separates them from the
   ground rather than a second palette. The contextual token that carries it is set on the
   on-dark block of this sheet, which the dialog never enters: it is deliberately outside
   `.ab` (see .ab-lightbox__inner), so it is declared here. */
.ab-lightbox {
    --ab-dot-ring: var(--ab-on-dark);
}

@media (prefers-reduced-motion: reduce) {
    .ab-lightbox::backdrop,
    .ab-lightbox__inner { transition: none; }

    /* A dot press still moves the track; it just arrives instead of travelling. */
    .ab-lightbox__track { scroll-behavior: auto; }
}


/* The deck that used to live here — a near-black panel, a blurred backdrop and cards
   driven by .is-active/.is-next — is gone. Its rules survived the rewrite above and
   kept winning `pointer-events: none` on every card, because the replacement never
   declared that property and a bare class beat nothing at all. See the deck block near
   the top of this sheet. */

/* Neutralises thegem-child/css/theme.scss:391 — b, strong { font-weight: 700 !important } */
.ab b.ab-swash {
    font-weight: inherit !important;
}

/* =========================================================================
   Shared primitives
   ====================================================================== */

.ab {
    font-family: var(--ab-font);
    color: var(--ab-fg);
    box-sizing: border-box;
}

.ab *,
.ab *::before,
.ab *::after {
    box-sizing: inherit;
}

/* Scoped element reset.
 *
 * The parent theme styles bare elements — thegem-new-css.css sets
 * `blockquote { padding: 67px 87px; background: #f3f5f6; font-size: 24px }`, and there are
 * similar rules on figure, cite and lists. A class selector only wins where it declares
 * the same property, so anything left undeclared silently inherits the theme's value: the
 * pull quote came out 138px wide, one word per line, before this block existed.
 *
 * Every element the library emits is neutralised here once, so each block's own rules only
 * have to say what it *is*, not what it is not. Selectors name the element, which is the
 * convention this file uses wherever the theme also targets by element.
 */
.ab blockquote,
.ab figure,
.ab figcaption,
.ab article,
.ab nav,
.ab ul,
.ab ol,
.ab li,
.ab cite,
.ab p {
    padding: 0;
    margin: 0;
    background: none;
    border: 0;
    color: inherit;
    font-size: inherit;
    font-style: inherit;
    font-weight: inherit;
    line-height: inherit;
    text-transform: none;
    quotes: none;
}

.ab ul,
.ab ol {
    list-style: none;
}

.ab ul li::before,
.ab ol li::before {
    content: none;
}

.ab img,
.ab svg {
    max-width: 100%;
}

.ab a {
    text-decoration: none;
}

.ab button {
    font-family: inherit;
}

.ab-sprite {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
}

.ab .ab-mark {
    display: block;
    width: 100%;
    height: auto;
}

/* fill, stroke and color are inherited properties, so they cross the <use> shadow
   boundary — which is why one symbol serves every appearance of the mark. */
.ab .ab-mark--fill,
.ab .ab-mark--tint {
    fill: currentColor;
    stroke: none;
}

.ab .ab-mark--line {
    fill: none;
    stroke: currentColor;
    stroke-width: 1;
}

/* The lightbox is a <dialog> and deliberately outside `.ab` — that class carries the
   border-box reset .ab-lightbox__inner explicitly opts out of — so its own live region has to
   be named here or it prints visibly in the middle of the film. */
.ab .screen-reader-text,
.ab-lightbox .screen-reader-text {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* Empty image slot: a correctly proportioned box, never a collapsed layout. */
.ab .ab-img-empty {
    display: flex;
    position: absolute;
    inset: 0;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--ab-s-2);
    background: var(--ab-b100);
    color: var(--ab-b600);
}

.ab .ab-img-empty__mark {
    width: 56px;
    opacity: .25;
}

.ab .ab-img-empty__note {
    font-size: var(--ab-t-eyebrow);
    font-weight: 700;
    letter-spacing: .14em;
    text-transform: uppercase;
    opacity: .7;
}

/* =========================================================================
   ability_section — the band
   ====================================================================== */

.ab-sec {
    position: relative;
    /* Fallback for engines without `clip`; the @supports block below replaces it. */
    overflow: hidden;
    background: var(--ab-white);
}

/* The home hero is a WPBakery row, not one of these blocks, and it carries 120px of
   padding top and bottom — a desktop number that survives to the phone. Measured at 390:
   331px of content inside 240px of padding is a 572px band in an 844px screen, 68% of the
   fold, so the headline reads and nothing under it does. The cap is the same shape as the
   statement card's — a viewport fraction with a floor and a ceiling — applied to the
   padding, because a row with content in it cannot be capped by height without clipping.
   7svh lands the band at about half the fold and leaves the rest to what follows. */
@media (max-width: 1023.98px) {

    .entry-content .vc_row.hero {
        /* !important only because WPBakery writes its row padding that way — its
           .vc_custom_* rule ships `padding-top: 250px !important`. Specificity alone
           cannot reach past that. */
        padding-top: clamp(28px, 7svh, 120px) !important;
        padding-bottom: clamp(28px, 7svh, 120px) !important;
    }
}

/* The band has to clip its watermark sideways, but `overflow: hidden` would make it a
   scroll container and kill every sticky descendant inside it — the same trap as
   `#page { overflow: hidden }` in the parent theme, and it silently broke the Our
   Philosophy pile until it was measured. The stretched vc_row above it has the same
   problem, so both are corrected here.
   Verified: with `hidden` the first card measured top:-394 while asking for 112. */
@supports (overflow: clip) {
    .ab-sec,
    .vc_row:has(.ab-sec) {
        overflow-x: clip;
        overflow-y: visible;
    }
}

.ab-sec--tint {
    --ab-seam: var(--ab-seam-light);

    background: var(--ab-b100);
}

.ab-sec--b50 {
    --ab-seam: var(--ab-seam-light);

    background: var(--ab-b50);
}

/* Blue 700 is the copy-bearing ground: white on it measures 5.36:1, where Blue 600
   manages only 4.03:1 and fails AA for 16px text. Blue 600 stays available for bands
   that carry headings only. */
.ab-sec--blue,
.ab-sec--blue600,
.ab-sec--media {
    --ab-fg: var(--ab-on-dark);
    --ab-fg-2: var(--ab-on-dark-2);
    --ab-fg-3: var(--ab-on-dark-3);
    --ab-accent: var(--ab-aqua);
    --ab-rule: var(--ab-on-dark-bd);
    --ab-seam: var(--ab-seam-dark);
    --ab-dot-ring: var(--ab-on-dark);

    color: var(--ab-on-dark);
}

/* A band riding a photograph or a video paints no ground of its own — the media IS the
   ground, and it belongs to whatever placed it there. Everything else about the band is
   unchanged, which is why this is a ground value rather than a colour input: the copy
   still has to read on-dark, and that is the one decision `ground` has always made.
   `transparent` is explicit because .ab-sec carries a white background by default. */
.ab-sec--media {
    background: transparent;
}

.ab-sec--blue {
    background: var(--ab-b700);
}

.ab-sec--blue600 {
    background: var(--ab-b600);
}

/* Two bands sharing a ground merge into one surface and the section boundary is lost.
   Alternating ground is the rule (see the `ground` param), so this is a safety net for a
   page composed against it — not permission to break it. The class is set in PHP: WP
   Bakery gives every band its own .wpb_wrapper and interleaves full-width helper divs, so
   no two bands are ever DOM siblings and an adjacent-sibling selector cannot reach. */
.ab-sec--seam {
    box-shadow: inset 0 1px 0 var(--ab-seam);
}

.ab-sec--has-bg {
    background-image: var(--ab-sec-bg);
    background-position: center;
    background-size: cover;
}

.ab-sec--scrim-dark::before,
.ab-sec--scrim-blue::before {
    content: "";
    position: absolute;
    z-index: 0;
    inset: 0;
    pointer-events: none;
}

.ab-sec--scrim-dark::before {
    background: rgba(11, 31, 56, .62);
}

.ab-sec--scrim-blue::before {
    background: rgba(58, 109, 166, .78);
}

.ab-sec--bleed {
    left: 50%;
    width: 100vw;
    margin-left: -50vw;
}

/* --- The media scrim, site-wide ---------------------------------------------
   Every WP Bakery row with "background overlay" switched on gets a
   `.gem-vc-background-overlay` div and a colour from the row's own picker, which TheGem
   writes as an inline <style> INSIDE THE BODY:

       .thegem-custom-XXXX .gem-vc-background-overlay { background-color: #00000040; }

   That is (0,2,0) and later in the document than every stylesheet, so a bare
   `.gem-vc-background-overlay` rule here loses on both counts. This is (0,3,0) and wins,
   which is the point: the scrim over a video or a photograph is a system decision, the same
   on every page, not something that gets re-picked per row and drifts.

   **Know this before you debug it:** changing a row's overlay colour in the WP Bakery picker
   now has no effect on the front end. The token is the value. TheGem offers no blend-mode
   control at all, so that half was never per-row to begin with.

   `mix-blend-mode` on a sibling that precedes the content in DOM order blends only with what
   is painted under it — the video — and leaves the copy above untouched. */
.vc_row.gem-vc-background-overlay-container .gem-vc-background-overlay {
    background-color: var(--ab-scrim);
    mix-blend-mode: multiply;
}

/* A band has to reach the row's edge, and TheGem gives .vc_column-inner a 21px gutter that
   would otherwise show as a pale strip down both sides of every coloured section. Scoped
   with :has() so only columns that actually contain a band lose their padding — every other
   column on the site keeps it. `bleed="self"` remains the escape hatch for older engines. */
.vc_row > .vc_column_container > .vc_column-inner:has(> .wpb_wrapper > .ab-sec),
.vc_row > .vc_column_container > .vc_column-inner:has(> .wpb_wrapper > .ab-footer),
.vc_row .vc_column-inner:has(> .wpb_wrapper > .ab-ribbon) {
    padding-right: 0;
    padding-left: 0;
}

/* The header wraps its rows in TheGem's .container, which caps them at 1322px. A proof
   band has to reach both edges, so the cap is lifted only for the row holding one. */
.vc_row:has(> .container > .wpb_column > .vc_column-inner > .wpb_wrapper > .ab-ribbon) > .container {
    max-width: none;
    padding-right: 0;
    padding-left: 0;
}

/* Full bleed at every viewport width, not just narrow ones.
 *
 * WPBakery's stretch_row is "background full width, content contained": its JS pulls the row
 * out to the viewport with a negative `left`, then pads it back in by the container's offset
 * — measured inline as `padding-left/right: 291.5px` at a 1905px viewport. The band lives in
 * that padding box, so it inherits the inset. At 1440 the offset is about 26px a side and
 * invisible, which is why this survived earlier testing; at 1920 it is 583px.
 *
 * The padding is written inline by JavaScript, so `!important` is the only way a stylesheet
 * reaches it — this is the narrow case where it is the right tool rather than a shortcut.
 * Removing it is safe because every band supplies its own inner max-width: the colour bleeds
 * to the edge, the content stays at 1170.
 */
.vc_row.wpb_row[data-vc-full-width]:has(.ab-sec),
.vc_row.wpb_row[data-vc-full-width]:has(.ab-ribbon),
.vc_row.wpb_row[data-vc-full-width]:has(.ab-footer) {
    padding-left: 0 !important;
    padding-right: 0 !important;
}

/* NO DEAD SPACE BETWEEN BANDS.
 *
 * A full-bleed band system has one hard rule: consecutive coloured bands must meet exactly,
 * with no strip of page background showing through. The builder works against that by
 * default — `.wpb_row` carries a 1.1em bottom margin (17.6px, which showed as a white line
 * between every section) and `.thegem-template-header .vc_row` adds 10px of vertical padding
 * (the white gap under the review ribbon). Neither is !important, so naming .wpb_row as well
 * as .vc_row is enough to win at (0,3,0).
 *
 * Ability blocks own their vertical rhythm through `ground` and `pad_top`/`pad_bottom`, so
 * any row hosting one contributes nothing of its own. Scoped with :has() — every other row on
 * the site keeps the builder's spacing.
 */
.vc_row.wpb_row:has(.ab-sec),
.vc_row.wpb_row:has(.ab-ribbon),
.vc_row.wpb_row:has(.ab-footer) {
    margin-top: 0;
    margin-bottom: 0;
    padding-top: 0;
    padding-bottom: 0;
}

/* Same rule one level down: a column wrapper between two bands must not add space either. */
.vc_row.wpb_row:has(.ab-sec) > .vc_column_container > .vc_column-inner,
.vc_row.wpb_row:has(.ab-ribbon) .vc_column-inner,
.vc_row.wpb_row:has(.ab-footer) > .vc_column_container > .vc_column-inner {
    padding-top: 0;
    padding-bottom: 0;
}

/* And the same rule INSIDE a band. The 1.1em above is not only on `.wpb_row` — the builder
 * puts it on `.wpb_content_element` too, which is every raw-html payload. Measured at 19.8px,
 * and it is wrong in both positions it can occupy: as a last child it ADDS to the band's own
 * pad_bottom (the home's locations band closed on 84px against the 64 every other band uses),
 * and between two blocks it undercuts the 24px step.
 *
 * Raw html is the only thing that reaches .ab-sec__inner with no ab-* class for the rhythm
 * list below to name — the video reel and both location-filter blocks on the home — so it is
 * named here instead, with the same :last-child exemption the list uses.
 */
.ab-sec__inner > .wpb_content_element {
    margin-bottom: 0;
}

.ab-sec__inner > .wpb_content_element:not(:last-child) {
    margin-bottom: var(--ab-s-6);
}

.ab-sec--pt-none { padding-top: 0; }
.ab-sec--pt-sm   { padding-top: var(--ab-s-6); }
.ab-sec--pt-md   { padding-top: var(--ab-s-8); }
.ab-sec--pt-lg   { padding-top: var(--ab-s-12); }

.ab-sec--pb-none { padding-bottom: 0; }
.ab-sec--pb-sm   { padding-bottom: var(--ab-s-6); }
.ab-sec--pb-md   { padding-bottom: var(--ab-s-8); }
.ab-sec--pb-lg   { padding-bottom: var(--ab-s-12); }

.ab-sec--center {
    text-align: center;
}

/* `align="right"` — the copy column sits against the right rail.
 *
 * It exists so a band with NO figure can still take its turn in the R1 alternation. R1 reads
 * the figure's side and is blind to a prose band, which is right for a CENTRED rest but wrong
 * for a full-width band left-aligned like its neighbour: the reader's eye lands on the same
 * edge twice. There is no `text-align: right` here — the type stays ragged-right like every
 * other band, and what moves is the column.
 *
 * The width is the aside's own copy column rather than a fresh number: that grid is
 * `minmax(0, 460px)` for the figure and `1fr` for the copy across a 64px gap, so the copy is
 * whatever the inner's content box has left. Written as that same subtraction so a change to
 * the figure column carries here instead of leaving two numbers to drift apart.
 *
 * (0,3,0) — `.ab` and `.ab-sec--right` land on the same element — because `.ab .ab-body--read`
 * sets its own 68ch max-width at (0,2,0) and would otherwise win or tie by source order.
 *
 * Below 1024 the asides stack and there is no column to mirror, so this does nothing there. */
@media (min-width: 1024px) {
    .ab.ab-sec--right .ab-sec__inner > * {
        max-width: calc(100% - 460px - var(--ab-s-16));
        margin-right: 0;
        margin-left: auto;
    }
}

.ab-sec__inner {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: var(--ab-max);
    margin-right: auto;
    margin-left: auto;
    padding-right: var(--ab-gutter);
    padding-left: var(--ab-gutter);
}

.ab-sec__inner--narrow { max-width: var(--ab-max-narrow); }
.ab-sec__inner--wide   { max-width: var(--ab-max-wide); }

.ab-sec__inner--full {
    max-width: none;
    padding-right: 0;
    padding-left: 0;
}

/* Rhythm BETWEEN blocks inside a band — the design system's 24px step.
 *
 * This is the opposite of the no-dead-space rule above, and the two must not be confused:
 * bands meet flush, the blocks inside them do not. Most blocks carry their own trailing
 * space (.ab-sh 32px, .ab-body 24px), but a quote's padding sits *inside* its fill and a
 * media frame ends at the image edge, so a paragraph after one lands flush against it.
 *
 * `margin` rather than `padding` deliberately: padding on the quote would extend the blue
 * fill, and on the media frame the image. :last-child is exempt — trailing space at the end
 * of a band is dead space, which is what the band's own pad_bottom is for.
 */
.ab-sec__inner > .ab-quote:not(:last-child),
.ab-sec__inner > .ab-media:not(:last-child),
.ab-sec__inner > .ab-stmt-wrap:not(:last-child),
.ab-sec__inner > .ab-stack:not(:last-child),
.ab-sec__inner > .ab-stats:not(:last-child),
.ab-sec__inner > .ab-btns:not(:last-child),
.ab-sec__inner > .ab-clinics:not(:last-child),
.ab-sec__inner > .ab-pills:not(:last-child),
.ab-sec__inner > .ab-deck:not(:last-child) {
    margin-bottom: var(--ab-s-6);
}

/* A group of panels or steps takes the header's 32 rather than the 24 above, because it is
   almost always flanked: a section header sits over it and a button under it, and `.ab-sh`
   contributes 32px of its own. At 24 below, the group reads as hanging off the button
   instead of sitting between the two — measured at 32 above and 24 below in the home's
   conditions band, which is what the asymmetry looks like on the page. */
.ab-sec__inner > .ab-panels:not(:last-child),
.ab-sec__inner > .ab-steps:not(:last-child) {
    margin-bottom: var(--ab-s-8);
}

/* =========================================================================
   ability_media with a film — the frame becomes a player
   ====================================================================== */

/* Nothing downloads until the click. The home page's tour is 72 MB, and `preload="metadata"`
   on a band most visitors scroll past still costs a request and a range read per view. The
   poster is the frame; the <video> is built by ability-blocks.js when the button is pressed. */
.ab .ab-media--film .ab-media__frame {
    position: relative;
    display: block;
}

.ab .ab-media__play {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    background: none;
    color: var(--ab-white);
    cursor: pointer;
}

.ab .ab-media__play-ring {
    display: grid;
    place-items: center;
    width: 84px;
    height: 84px;
    border: 2px solid rgba(255, 255, 255, .9);
    border-radius: var(--ab-r-pill);
    background: rgba(12, 26, 44, .28);
    backdrop-filter: blur(2px);
    transition: transform .3s var(--ab-ease-out), background .3s var(--ab-ease-out);
}

.ab .ab-media__play:hover .ab-media__play-ring,
.ab .ab-media__play:focus-visible .ab-media__play-ring {
    transform: scale(1.06);
    background: rgba(12, 26, 44, .44);
}

.ab .ab-media__play svg {
    width: 34px;
    height: 34px;
    margin-left: 4px; /* the triangle's optical centre sits left of its box */
}

@media (max-width: 767.98px) {
    .ab .ab-media__play-ring {
        width: 64px;
        height: 64px;
    }

    .ab .ab-media__play svg {
        width: 26px;
        height: 26px;
    }
}

/* =========================================================================
   ab-filmstrip — a film with two photographs receding behind it
   ====================================================================== */

/* Asked for by class on ability_section, the way ab-aside is. The band carries three
   children: the header, then a .ab-filmstrip__stage holding the two asides and the film.
   Below 1024 only the film is left — the whole composition is a depth effect and it needs a
   front plane with something to sit in front of. Two-up under the film the photographs stop
   being behind anything and become two more pictures. */
@media (min-width: 1024px) {
    /* Placed explicitly, the way ab-aside is, and for the same reason: the band carries
       four children — the header and three frames — and an auto-placed grid would drop the
       header into the first column. No column gap, because the photographs are meant to run
       UNDER the film rather than sit beside it, and the overlap is the translate below.
       Compose the band at width="wide" (1280) and the film lands near 760 instead of the
       702 the legacy row gave it. */
    .ab-filmstrip .ab-sec__inner {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 3.9fr) minmax(0, 1fr);
        align-items: center;
    }

    .ab-filmstrip .ab-sec__inner > .ab-sh {
        grid-column: 1 / -1;
    }

    .ab-filmstrip .ab-sec__inner > .ab-media--film { grid-column: 2; }
    .ab-filmstrip .ab-sec__inner > .ab-filmstrip__aside--start { grid-column: 1; }
    .ab-filmstrip .ab-sec__inner > .ab-filmstrip__aside--end { grid-column: 3; }

    /* Same reset, same reason and the same specificity trick as .ab-aside's below: the child
       combinator ties the rhythm rule's (0,3,0) and wins on source order. Placed here rather
       than there because the failure is different — these three frames are ONE grid row, so
       the 24px is not a step between blocks, it is 24px of extra row height plus a 12px
       misalignment of the film and the left aside against the right one, which `align-items:
       center` then splits. Measured 24/24/0 across the three. Only above 1024: below it the
       band stacks and the 24px step is exactly right. */
    .ab-filmstrip .ab-sec__inner > .ab-media {
        margin-bottom: 0;
    }

    /* The film is the front plane, so it stacks over the photographs and casts on them.
       The shadow is .ab-deck__card.is-front's, unchanged: three layers with a long soft
       top throw, which is what makes the deck read as depth rather than as a drop shadow.
       Borrowing it keeps the two bands speaking the same language. */
    .ab-filmstrip .ab-media--film {
        position: relative;
        z-index: 1;
    }

    /* The shadow rides the FRAME, not the figure. The radius lives on `.ab-media__frame`
       (`.ab-media--r-lg` sets it), and a box-shadow traces the border box of the element
       carrying it — on the figure, which has no radius, it came out as a sharp-cornered
       rectangle poking past the video's 20px corners. Written at (0,3,0) so it beats
       `.ab .ab-media__frame` further down the sheet. `overflow: hidden` there clips
       descendants, never the element's own outer shadow. */
    .ab-filmstrip .ab-media--film .ab-media__frame {
        box-shadow:
            -2px 26px 54px -14px rgba(7, 20, 38, .62),
            10px 14px 34px -10px rgba(7, 20, 38, .42),
            0 3px 8px rgba(7, 20, 38, .22);
    }

    /* The recession is the deck's own idiom: one custom property driving translate, scale
       and brightness together. The deck's sign for brightness, not the stack's — the deck
       is what recedes on a DARK ground, where distance reads hazier and lighter, and this
       band is Blue 700. translateX is what puts the photograph under the film rather than
       beside it: 34px per unit of depth, so 65px of overlap at 1.9. */
    .ab-filmstrip__aside {
        --d: 1.9;
        --dir: 1;
        position: relative;
        z-index: 0;
        /* Kept so the photograph honours the corner radius below; there is no longer a blur
           to contain. */
        overflow: hidden;
        height: 74%;
        margin: 0;
        border-radius: var(--ab-r-lg);
        /* A translucent rim, so a photograph whose own backdrop is already blue — the
           right-hand one is — keeps an edge against the band instead of melting into it.
           Inset rather than outset: at .54 opacity an outer ring would sit on the band's blue
           at full strength and read as a stroke around the card. */
        box-shadow: inset 0 0 0 1px rgb(255 255 255 / .6);
        transform: translateX(calc(var(--d) * 34px * var(--dir))) scale(calc(1 - var(--d) * .062));
        /* The one departure from .ab-deck__card. The deck fades with `opacity: min(1, 3 - d)`,
           which is still 1 at this depth because it only starts fading a card once a third
           is behind it. A flat .54 lets the band's blue through the photograph. */
        opacity: .54;
    }

    /* --dir mirrors the depth transform: the start card leans left, the end card right. */
    .ab-filmstrip__aside--start { --dir: 1; }
    .ab-filmstrip__aside--end   { --dir: -1; }

    /* Both selectors are written long on purpose. The frame's own rules are `.ab
       .ab-media__frame` (0,2,0) and the image's are `.ab-media--auto .ab-media__frame
       .ab-img` (0,3,0), both further down the sheet — a shorter selector here ties and
       loses on source order, and the symptom is specific: the photograph stops short of
       the figure it is supposed to fill. */
    .ab .ab-filmstrip__aside .ab-media__frame {
        display: block;
        height: 100%;
    }

    .ab .ab-filmstrip__aside .ab-media__frame .ab-img {
        display: block;
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    /* Brightness only. The blur that used to ride along here is gone with the rest of the
       treatment. The lift stays — it is what makes the card sit BEHIND the film rather than
       beside it, and it is independent of focus. */
    .ab .ab-filmstrip__aside .ab-media__frame .ab-img {
        filter: brightness(calc(1 + var(--d) * .13));
    }

    /* THE EDGE TREATMENT IS GONE, by decision. The receding photographs are sharp, opaque to
       their own edges, and end on the rounded rectangle the border-radius draws.
     *
     * What was here: a second, heavily blurred copy of the photograph cross-faded over the
     * sharp one, and a nine-stop smoothstep mask dissolving each card outward from the film.
     * With them went --fs-soft and --fs-strip, --fs-axis, the ::after's consumption of
     * --ab-media-src, the @supports branch that hid the plane where mask-image is unsupported,
     * and the reduced-motion branch that dropped it and lifted opacity to .7 in compensation.
     *
     * What stayed, because these were separate effects sharing one element rather than parts
     * of the blur: the depth transform, the .54 opacity that lets the band's blue through, the
     * brightness lift, and the translucent inset rim. The cards still recede.
     *
     * Reinstating any of it means bringing a whole set back together — the plane needed its
     * own negative inset so a round blur would not sample transparency past its box, and the
     * card's dissolve existed because clipping a blur to the figure leaves a crisp outline
     * drawn through soft pixels. Neither is a line you can add on its own. */
}

@media (max-width: 1023.98px) {
    /* Doubled up because `.ab-media { display: flex }` is the same (0,1,0) and sits further
       down the sheet — on a single class this rule tied and lost, and the photographs came
       back full width under the film, which is exactly what dropping them prevents. */
    .ab-media.ab-filmstrip__aside {
        display: none;
    }
}

/* Watermark: cropped by a corner, behind everything. Being cut is what stops it reading
   as a logo placement, and it must never compete with the text in front of it. */
/* The span is the whole band and it CROPS the mark; the offsets that used to sit here are
   on the svg inside it now. The comment above has always said "cropped by a corner… being
   cut is what stops it reading as a logo placement", but nothing was doing the cutting:
   `.ab-sec` runs `overflow-x: clip; overflow-y: visible`, which it has to, or every sticky
   descendant dies. So a hero mark asking for `top: -140px` on a short band hung 140px out
   of it and over whatever came next — measured on the home's locations band. Clipping here
   leaves the section's own overflow alone. */
.ab .ab-wm {
    position: absolute;
    z-index: 0;
    inset: 0;
    overflow: hidden;
    color: rgba(73, 129, 194, .5); /* = --ab-b600 @ 50% */
    opacity: .5;
    pointer-events: none;
}

.ab .ab-wm > .ab-mark {
    position: absolute;
}

/* The hero mark spans the full band and sits directly behind the heading, so at the
   hairline weight it reads as drawn line art rather than a watermark. It takes the same
   flat Link Water fill the prototype uses there — barely separated from the Blue 100
   ground, which is the whole point. */
.ab .ab-wm--hero .ab-mark {
    fill: #DDE7F3;
    stroke: none;
}

.ab .ab-wm--hero {
    color: #DDE7F3;
    opacity: .85;
}

.ab-sec--blue .ab-wm--hero,
.ab-sec--blue600 .ab-wm--hero {
    opacity: .16;
}

.ab-sec--blue .ab-wm--hero .ab-mark,
.ab-sec--blue600 .ab-wm--hero .ab-mark {
    fill: var(--ab-white);
}

.ab-sec--blue .ab-wm,
.ab-sec--blue600 .ab-wm {
    color: var(--ab-on-dark-bd);
}

.ab .ab-wm--top-right > .ab-mark {
    top: -40px;
    right: 8px;
    width: 152px;
}

.ab .ab-wm--bottom-right > .ab-mark {
    right: -40px;
    bottom: -80px;
    width: 240px;
}

.ab .ab-wm--hero > .ab-mark {
    top: -90px;
    left: 56%;
    width: 420px;
}

/* =========================================================================
   ability_section_header
   ====================================================================== */

.ab-sh {
    display: flex;
    flex-direction: column;
    padding-bottom: var(--ab-s-8);
}

.ab-sh:last-child {
    padding-bottom: 0;
}

.ab-sh--left   { text-align: left; }
.ab-sh--center { text-align: center; }

.ab .ab-sh__eyebrow {
    padding-bottom: var(--ab-s-3);
    color: var(--ab-accent);
    font-size: var(--ab-t-eyebrow);
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: .16em;
    text-transform: uppercase;
}

.ab h1.ab-sh__h,
.ab h2.ab-sh__h,
.ab h3.ab-sh__h {
    max-width: 18ch;
    padding-bottom: var(--ab-s-4);
    color: var(--ab-fg);
    font-family: var(--ab-font);
    font-weight: 800;
    text-wrap: balance;
}

.ab h1.ab-sh__h--h1 {
    font-size: var(--ab-t-display);
    line-height: 1;
    letter-spacing: -.035em;
}

.ab h2.ab-sh__h--h2 {
    font-size: var(--ab-t-heading);
    line-height: 1.05;
    letter-spacing: -.03em;
}

.ab h3.ab-sh__h--h3 {
    font-size: var(--ab-t-subhead);
    line-height: 1.15;
    letter-spacing: -.025em;
}

.ab-sh--center h1.ab-sh__h,
.ab-sh--center h2.ab-sh__h,
.ab-sh--center h3.ab-sh__h,
.ab-sec--center .ab-sh h1.ab-sh__h,
.ab-sec--center .ab-sh h2.ab-sh__h,
.ab-sec--center .ab-sh h3.ab-sh__h {
    max-width: 20ch;
    margin-right: auto !important;
    margin-left: auto !important;
}

/* The closing band's heading runs to three lines, not four.
 *
 * The 20ch above is the design system's measure for every centred heading, and it is right
 * for the short ones — "Our Core Values", "Ready to Feel the Difference?" — which is most of
 * them. This band's heading is a 102-character sentence, and at 20ch (about 430px measured at
 * 35px) `text-wrap: balance` settles on four lines of ~26 characters. Four short centred
 * lines read as a stack rather than as a sentence.
 *
 * 26ch is about 560px, which is inside the band's own narrow inner (760px less two 40px
 * gutters = 680px), so nothing else has to move. Balance then finds three lines.
 *
 * Scoped to this band on purpose. Raising the 20ch above would reflow EVERY centred heading
 * on the site — the locations hero, the conditions CTA, Gahanna, the block library — none of
 * which asked for it. If the client wants the longer measure everywhere, that is a change to
 * the shared rule and a decision to take once, not a side effect of fixing one band. */
.ab-home-closing .ab-sh h1.ab-sh__h,
.ab-home-closing .ab-sh h2.ab-sh__h,
.ab-home-closing .ab-sh h3.ab-sh__h {
    max-width: 26ch;
}

/* A heading that carries the swash gets the leading the mark needs.
 *
 * The mark is .3em tall and lives between its word's baseline and the next line's ascenders.
 * At the display line-heights above — 1 for h1, 1.05 for h2 — that band measures about .31em,
 * so the mark filled it exactly and its ink crossed the tops of the line below. Measured on
 * the home page at 35px: baseline 1458, ascenders of the next line 1469, mark 1465-1470.
 *
 * Two things could give: the mark or the line. The mark is a client-approved design element
 * and shrinking it would show on every heading that does NOT wrap — the closing band's is on
 * a last line, with nothing under it and nothing wrong with it. So the line gives, and only
 * for the headings that actually carry a mark: +.13em of leading each, which is about 4.5px
 * at h2 and turns a .31em band into .44em. Every other heading keeps the tight display
 * rhythm the design system asks for.
 *
 * `:has()` rather than a class from PHP because the swash is decided inside
 * ability_blocks_swash(), several call sites away from whoever renders the heading, and a
 * class threaded through all of them is a second source of truth for the same fact. */
.ab h1.ab-sh__h--h1:has(.ab-swash) { line-height: 1.14; }
.ab h2.ab-sh__h--h2:has(.ab-swash) { line-height: 1.18; }
.ab h3.ab-sh__h--h3:has(.ab-swash) { line-height: 1.28; }

.ab .ab-sh__sub {
    max-width: 56ch;
    color: var(--ab-fg-2);
    font-size: var(--ab-t-body);
    line-height: 1.6;
}

/* A subhead may run to a second paragraph. .ab-sh is a flex column with no gap — every
   child owns its own trailing space — so the break is padding on the follower, which
   leaves the last paragraph flush with the block's own padding-bottom. */
.ab .ab-sh__sub + .ab-sh__sub {
    padding-top: var(--ab-s-4);
}

.ab-sh--center .ab-sh__sub,
/* A centred band centres its button row too — the flex row otherwise packs left
   under a centred heading, which reads as a mistake rather than a choice. */
.ab-sec--center .ab-btns {
    justify-content: center;
}

.ab-sec--center .ab-sh .ab-sh__sub {
    margin-right: auto;
    margin-left: auto;
}

/* The aqua swash. The only gestural mark in the system, and it never spans two words. */

/* The stroke stretches to the word: `preserveAspectRatio='none'` in the SVG, `100% 100%` here.
 *
 * It used to stop around the middle of any word longer than about four letters — under
 * "healthier" it read as a typo rather than as emphasis, while "Real" and "More" looked fine,
 * which is why it survived this long.
 *
 * TWO things were clamping it, and fixing either one alone changes nothing on screen:
 *
 *   1. `background-size: contain` fits the image inside the box without distorting it. The
 *      box's height is a fixed .3em, and the artwork is 249x32, so the stroke could only ever
 *      be .3 x (249/32) = 2.33em wide however long the word was.
 *   2. An SVG used as a background image still applies its OWN `preserveAspectRatio`, which
 *      defaults to `xMidYMid meet` — fit and centre. So background-size only sets the
 *      viewport the SVG draws into; the drawing inside it keeps its 249:32 ratio and sits
 *      letterboxed in the middle. `background-size: 100% 100%` on its own is silently
 *      undone by this, which is the part worth remembering.
 *
 * A gestural brush stroke is exactly the kind of artwork that may be stretched on one axis:
 * no geometry to preserve, no text inside it. Now the mark scales WITH the word — the box is
 * the word inset by -2% either side — instead of being clamped by its own aspect ratio. */
.ab .ab-swash {
    position: relative;
    display: inline-block;
    font-weight: inherit;
}

.ab .ab-swash::after {
    content: "";
    position: absolute;
    top: 100%;
    right: -2%;
    left: -2%;
    height: .3em;
    /* Anchored a fixed fraction of the FONT SIZE below the word, not to the bottom of the
       inline-block's line box. Those differ by the bottom half-leading, so without this the
       mark drifts down as soon as a heading is given more leading — half of every em added
       goes underneath the box and the mark rides it, which is why the leading rule below
       only bought half the clearance it should have. -.15em puts the stroke about where the
       client's reference has it: close under its own word, with the space it gains from the
       leading spent on the line below rather than on the gap above. */
    margin-top: -.15em;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 249 32' preserveAspectRatio='none'%3E%3Cpath d='M14.5 10.5C58 2.5 148 3 243 19c-58-4.5-124-6-217 6.5-9 1.2-13-6.5-11.5-15Z' fill='%23A1E1E2'/%3E%3C/svg%3E") no-repeat center bottom / 100% 100%;
    pointer-events: none;
}

/* =========================================================================
   ability_text — body copy
   ====================================================================== */

.ab-body {
    padding-bottom: var(--ab-s-6);
}

.ab-body:last-child {
    padding-bottom: 0;
}

.ab .ab-body p {
    padding-bottom: var(--ab-s-4);
    color: var(--ab-fg);
    font-size: var(--ab-t-body);
    line-height: 1.6;
    text-wrap: pretty;
}

/* Content lists. The global reset silences ul/li because most block markup uses
   lists for LAYOUT; body copy is the one place a list is a list the author
   wrote, so the affordance returns here and nowhere else. Marker in the brand
   blue so a long clinical list still reads as Ability, not as a default page. */
.ab .ab-body ul,
.ab .ab-body ol {
    margin: 0 0 16px;
    padding-left: 22px;
}

.ab .ab-body ul {
    list-style: disc;
}

.ab .ab-body ol {
    list-style: decimal;
}

.ab .ab-body li {
    margin: 4px 0;
}

.ab .ab-body li::marker {
    color: var(--ab-blue-500, #4781C3);
}

/* --- The dot list ------------------------------------------------------------
   Asked for by `el_class` on ability_text, because it is a treatment WITHIN a block, not a
   relationship between blocks the way ab-aside is.

   The disc above is 4px at body weight, which is right for a list inside a paragraph and
   thin when the list IS the section — /conditions/back-pain/ carried four of those in a row
   and nothing in the column said where to look first.

   Written as `.ab-body.ab-list--dot` (0,3,1) rather than `.ab-list--dot` (0,2,1): the rule it
   replaces is `.ab .ab-body ul`, which ties at (0,2,1) and would win on source order alone if
   this block ever moved above it.

   The dot takes --ab-accent and the label --ab-fg, so both follow the band: on a blue section
   the accent is already remapped to aqua and the ink to white (see the --ab-sec--blue block),
   and the list needs no on-dark rule of its own. */
.ab .ab-body.ab-list--dot ul {
    list-style: none;
    /* The list owns the whole gap on both sides — the two rules below strip the spacing its
       neighbours would otherwise contribute, so the figure is symmetric and does not change
       with whatever happens to sit around it.
       It used to read 36px above and 15px below: the paragraph's own 16px padding stacked on
       the list's top margin, while underneath only the last row's margin was left. */
    margin: var(--ab-list-gap, var(--ab-s-12)) 0;
    /* Indented, because the list hangs off the sentence that opens it. With the dot aligned to
       the heading it read as another block of the section rather than the continuation of
       "Common causes of back pain include:". */
    padding-left: var(--ab-s-8);
    /* A grid even at one column, so the space between rows is `row-gap` and not a margin on
       every item. A trailing margin cannot be reached by :last-child once the list splits: in
       the two-column grid the last row is sized by the taller of its two items, so the margin
       under item five reappears under item ten and the bottom gap grows by 15px again. */
    display: grid;
    row-gap: 15px;
}

/* :has() rather than a class on the paragraph, because the copy is author HTML from the
   editor — the rhythm cannot depend on someone remembering to mark the lead-in. */
.ab .ab-body.ab-list--dot > p:has(+ ul) {
    padding-bottom: 0;
}

/* When the list ends the block, what comes next is a different block and the band's own 24px
   rhythm is already between them — measured 72px below against 48 above. So the list pays the
   difference here, not the whole gap, and the figure stays symmetric either way. */
.ab .ab-body.ab-list--dot > ul:last-child {
    margin-bottom: calc(var(--ab-list-gap, var(--ab-s-12)) - var(--ab-s-6));
}

.ab .ab-body.ab-list--dot li {
    position: relative;
    margin: 0;
    padding-left: 28px;
    color: var(--ab-fg);
    font-weight: 700;
}


/* ::before rather than ::marker: a marker cannot be sized, and 13px is the whole point. */
.ab .ab-body.ab-list--dot li::before {
    content: "";
    position: absolute;
    top: .35em;
    left: 0;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    background: var(--ab-accent);
}

/* Two columns, for a long list beside a photograph — ten rows tower over a 4:3 frame and
   leave the figure column ending halfway up the band.

   Grid and not `columns`. `columns` balances by HEIGHT, so the moment one label wraps the
   split stops being 5 and 5 — measured on Understanding Back Pain, it went 6 and 4 with two
   labels broken. Explicit rows fix the split, and reading order stays down-then-across.

   The row count is the one thing the CSS cannot derive from the list. Five is right for the
   ten-item case this was built for; --ab-list-rows changes it from the block's own design box. */
.ab .ab-body.ab-list--2col ul {
    grid-auto-flow: column;
    grid-template-rows: repeat(var(--ab-list-rows, 5), auto);
    column-gap: var(--ab-s-6);
}

/* `measure="read"` caps the block at 68ch, which is the measure of a PARAGRAPH. A list in two
   columns is not a paragraph, and under the cap the longest label on this page —
   "Pregnancy-related changes", 216px measured — did not fit its column and broke in two. */
.ab .ab-body.ab-list--2col {
    max-width: none;
}

@media (max-width: 767px) {
    .ab .ab-body.ab-list--dot ul {
        padding-left: var(--ab-s-4);
    }

    /* Two columns inside 354px are 160px each, and half the labels split. */
    .ab .ab-body.ab-list--2col ul {
        grid-auto-flow: row;
        grid-template-rows: none;
    }
}

.ab .ab-body p:last-child {
    padding-bottom: 0;
}

/* The step body is author copy too, so the same list affordance returns there. */
.ab .ab-steps__copy ul,
.ab .ab-steps__copy ol {
    margin: 0 0 12px;
    padding-left: 22px;
    list-style: disc;
}

.ab .ab-steps__copy ol {
    list-style: decimal;
}

.ab .ab-steps__copy li {
    margin: 4px 0;
}

.ab .ab-steps__copy li::marker {
    color: var(--ab-blue-500, #4781C3);
}

.ab .ab-body--small p {
    color: var(--ab-fg-2);
    font-size: var(--ab-t-small);
    line-height: 1.55;
}

.ab .ab-body--read {
    max-width: 68ch;
}

.ab-sec--center .ab-body--read {
    margin-right: auto;
    margin-left: auto;
}

.ab .ab-body a {
    color: var(--ab-accent);
}

/* Safety net: a stray vc_column_text dropped into a band still reads correctly, it just
   does not carry the reading measure. */
.ab-sec .wpb_text_column p {
    color: var(--ab-fg);
    font-size: var(--ab-t-body);
    line-height: 1.6;
}

/* =========================================================================
   ability_button
   ====================================================================== */

.ab.ab-btn,
.ab .ab-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ab-s-2);
    height: 52px;
    padding: 0 26px;
    border: 2px solid transparent;
    border-radius: var(--ab-r-pill);
    background: var(--ab-b600);
    color: var(--ab-white);
    font-family: var(--ab-font);
    font-size: var(--ab-t-button);
    font-weight: 700;
    line-height: 1;
    letter-spacing: .06em;
    text-decoration: none;
    text-transform: uppercase;
    cursor: pointer;
    transition: background .16s ease, color .16s ease, border-color .16s ease;
}

.ab-btn--sm { height: 44px; padding: 0 18px; }
.ab-btn--lg { height: 58px; padding: 0 32px; }

/* Two or more buttons ride in a row: buttons are inline elements with no
   spacing channel of their own, so the group provides it — a wrapping flex
   row whose gap survives line wraps, which sibling margins cannot do. */
.ab.ab-btns,
.ab .ab-btns {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ab-s-3);
}

.ab .ab-btns--center {
    justify-content: center;
}

/* Centres a lone button in a left-aligned band (an inline-flex needs to become
   block-level for auto margins to bite). Scoped ≥480 so it never out-specifies
   the mobile full-width treatment, which is already visually centred. */
@media (min-width: 480px) {
    .ab.ab-btn--center,
    .ab .ab-btn--center {
        display: flex;
        width: fit-content;
        margin-right: auto;
        margin-left: auto;
    }
}

.ab-btn--primary:hover,
.ab-btn--primary:focus-visible {
    background: var(--ab-b700);
    color: var(--ab-white);
}

.ab.ab-btn--secondary,
.ab .ab-btn--secondary {
    border-color: var(--ab-b600);
    background: transparent;
    color: var(--ab-b600);
}

.ab-btn--secondary:hover,
.ab-btn--secondary:focus-visible {
    background: var(--ab-b600);
    color: var(--ab-white);
}

.ab.ab-btn--on-blue,
.ab .ab-btn--on-blue {
    background: var(--ab-white);
    color: var(--ab-b600);
}

.ab-btn--on-blue:hover,
.ab-btn--on-blue:focus-visible {
    background: var(--ab-b100);
    color: var(--ab-b700);
}

.ab.ab-btn--ghost,
.ab .ab-btn--ghost {
    border-color: var(--ab-white);
    background: transparent;
    color: var(--ab-white);
}

.ab-btn--ghost:hover,
.ab-btn--ghost:focus-visible {
    background: var(--ab-white);
    color: var(--ab-b600);
}

/* The primary button, kept, plus the edge it loses on a dark ground.
 *
 * The other three variants each answer a ground the primary cannot sit on, and two of them
 * answer it by giving up the fill: on-blue turns white, ghost turns to outline. That works,
 * but a filled button and an outlined one are not the same rank on the page — trading the
 * fill away to stay visible also trades away the "this is the action" the fill was carrying.
 *
 * Blue 600 on Blue 700 is a 1.31:1 edge. Nothing about the shape survives it: the pill
 * dissolves into the band and the label is left floating. So the fill stays and the boundary
 * is drawn instead, in --ab-ring-on-dark, which carries the contrast arithmetic.
 *
 * border-color and not a border: .ab-btn already carries `border: 2px solid transparent`, so
 * the ring costs no layout and the variant cannot change the button's size. The docked CTA in
 * booking.css reaches the same ring through an inset shadow instead, for the same reason in
 * reverse — it has no border to colour and adding one would move it. */
.ab.ab-btn--on-dark,
.ab .ab-btn--on-dark {
    border-color: var(--ab-ring-on-dark);
}

/* Hover moves the ring, not the fill — the one place this variant deliberately parts ways
   with the primary it is otherwise identical to.
 *
 * The primary hovers to Blue 700, and copying that here would make the button hover INTO
 * the footer's own ground: the fill would match the band exactly and the thing would read
 * as a ghost button at the one moment the visitor is pointing at it. Since the ring is what
 * this variant is for, the ring is what answers. Solid white is a 4.7:1 edge on Blue 700,
 * so the boundary roughly doubles and the shape asserts itself rather than dissolving. */
.ab-btn--on-dark:hover,
.ab-btn--on-dark:focus-visible {
    border-color: var(--ab-white);
}

.ab.ab-btn--nolink,
.ab .ab-btn--nolink {
    border-color: var(--ab-bd);
    background: var(--ab-bd);
    color: var(--ab-ink3);
    cursor: not-allowed;
}

.ab-btn:focus-visible {
    outline: 3px solid var(--ab-b700);
    outline-offset: 3px;
}

.ab .ab-btn__arrow {
    flex: 0 0 auto;
    width: 19px;
    height: 19px;
    fill: currentColor;
}

.ab-btn--fw-always,
.ab-btn--fw-mobile {
    display: flex;
    width: 100%;
}

/* Unavailable — the FSD disabled state. A <span>, never an anchor, so there is
   nothing to focus and no navigation to promise. Hover and focus restate the
   same values on purpose: nothing may change on hover — including for the
   variant class riding underneath, whose hover rules sit at (0,2,0) and lose
   to these states at (0,3,0) without !important. */
.ab.ab-btn--unavailable,
.ab .ab-btn--unavailable,
.ab.ab-btn--unavailable:hover,
.ab .ab-btn--unavailable:hover,
.ab.ab-btn--unavailable:focus-visible,
.ab .ab-btn--unavailable:focus-visible {
    border-color: var(--ab-bd);
    background: var(--ab-bd);
    color: var(--ab-ink3);
    cursor: not-allowed;
}

/* =========================================================================
   ability_media
   ====================================================================== */

.ab-media {
    display: flex;
    flex-direction: column;
    gap: var(--ab-s-2);
    width: 100%;
}

.ab .ab-media__frame {
    position: relative;
    display: block;
    overflow: hidden;
    width: 100%;
    background: var(--ab-b100);
}

.ab-media--r-lg .ab-media__frame  { border-radius: var(--ab-r-lg); }
.ab-media--r-none .ab-media__frame { border-radius: 0; }

.ab-media--16-9 .ab-media__frame { aspect-ratio: 16 / 9; }
.ab-media--4-3 .ab-media__frame  { aspect-ratio: 4 / 3; }
.ab-media--3-4 .ab-media__frame  { aspect-ratio: 3 / 4; }
.ab-media--1-1 .ab-media__frame  { aspect-ratio: 1 / 1; }
.ab-media--fixed-352 .ab-media__frame { height: 352px; }

.ab .ab-media__frame .ab-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.ab-media--auto .ab-media__frame {
    background: none;
}

.ab-media--auto .ab-media__frame .ab-img {
    height: auto;
}

.ab .ab-media__caption {
    color: var(--ab-fg-2);
    font-size: var(--ab-t-small);
    line-height: 1.55;
}

/* --- The condition badge -----------------------------------------------------
   A blue tile carrying the mark and a short label, resting on a photograph's top-left corner.

   Not an overlay. It sits ON the corner, it does not cover the picture, tint it or wash its
   contrast — the block's own law, "never tint a photograph", is untouched. What it does do is
   turn a graphic into markup: the same tile is already BURNED INTO the pixels of
   Ability-Chiropractic-founders.webp, blue tile, aqua mark and all, reading "Founded by
   brothers Brian & Chris Mabry". As a component the label becomes text — selectable,
   translatable, and a colour we can correct.

   Overhung by padding on the figure with the tile at top/left zero — the method
   .ab-home-who .ab-stmt-wrap--plate uses, for the reason spelled out there: the figure is a
   grid item inside ab-aside, and a tile with a negative `left` escapes its cell and lands on
   the copy column beside it.

   Outside .ab-media__frame on purpose. The frame is `overflow: hidden`, so anything proud of
   its corner is clipped; the play button gets away with living inside because it never leaves
   the box. */
.ab .ab-media--badged {
    position: relative;
    padding-top: 20px;
    padding-left: 20px;
}

.ab .ab-cbadge {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
    padding: 15px 20px 13px;
    border-radius: var(--ab-r-lg);
    /* Blue 700, not the #4781C1 sampled off the original asset. The label is white at 15px,
       and b600 measures 4.04:1 where AA asks 4.5. b700 is 5.36:1. The burned-in badge gets
       away with b600 because its type is large enough to only owe 3:1. */
    background: var(--ab-b700);
    box-shadow: var(--ab-shadow-card);
}

.ab .ab-cbadge__i {
    display: block;
    width: 34px;
    color: var(--ab-aqua);
}

.ab .ab-cbadge__t {
    color: var(--ab-white);
    font-family: var(--ab-font);
    font-size: 15px;
    font-weight: 700;
    line-height: 1;
    white-space: nowrap;
}

@media (max-width: 767px) {
    /* The photograph runs edge to edge below the aside breakpoint, so a tile hanging off the
       left would sit in the gutter or off screen. It moves onto the picture instead. */
    .ab .ab-media--badged {
        padding-top: 14px;
        padding-left: 0;
    }

    .ab .ab-cbadge {
        left: 10px;
        padding: 11px 15px 10px;
    }

    .ab .ab-cbadge__i {
        width: 26px;
    }

    .ab .ab-cbadge__t {
        font-size: 13px;
    }
}

/* =========================================================================
   ability_pull_quote
   ====================================================================== */

/* These four name the element because they set properties the scoped reset above also
   sets, and `.ab blockquote` (0,1,1) would otherwise beat a bare `.ab-quote` (0,1,0). */
.ab blockquote.ab-quote {
    position: relative;
    overflow: hidden;
    border-radius: var(--ab-r-lg);
    background: var(--ab-b100);
}

.ab blockquote.ab-quote--outlined {
    border: 1px solid var(--ab-bd);
    background: var(--ab-white);
}

.ab blockquote.ab-quote--bare {
    overflow: visible;
    border: 0;
    border-radius: 0;
    background: transparent;
}

/* Typographic characters in Inter, not an icon — and the text is set inside them. */
.ab-quote::before,
.ab-quote::after {
    position: absolute;
    z-index: 1;
    color: var(--ab-b700);
    font-family: var(--ab-font);
    font-size: 56px;
    font-weight: 800;
    line-height: 1;
    pointer-events: none;
    user-select: none;
}

.ab-quote::before { content: "\201C"; top: 12px; left: 20px; }
.ab-quote::after  { content: "\201D"; right: 24px; bottom: 4px; }

.ab-quote--bare::before { left: 0; }
.ab-quote--bare::after  { right: 4px; }

.ab .ab-quote__wm {
    position: absolute;
    top: -24px;
    right: -36px;
    z-index: 0;
    width: 164px;
    color: #DDE7F3;
    pointer-events: none;
}

.ab .ab-quote__text {
    position: relative;
    z-index: 2;
    padding: 48px 40px 56px 60px;
    color: var(--ab-ink);
    font-family: var(--ab-font);
    font-size: var(--ab-t-subhead);
    font-style: italic;
    font-weight: 400;
    line-height: 1.4;
    letter-spacing: -.012em;
    text-wrap: pretty;
}

.ab-quote--bare .ab-quote__text {
    padding-right: 24px;
    padding-left: 56px;
}

.ab .ab-quote__cite {
    position: relative;
    z-index: 2;
    display: block;
    padding: 0 40px 24px 60px;
    color: var(--ab-fg-2);
    font-size: 12.5px;
    font-style: normal;
    font-weight: 700;
}

/* =========================================================================
   ability_statement_card
   ====================================================================== */

.ab-stmt-wrap {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--ab-s-4);
    width: 100%;
}

.ab .ab-stmt-wrap__photo {
    position: relative;
    display: block;
    overflow: hidden;
    width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: var(--ab-r-lg);
    background: var(--ab-b100);
}

.ab .ab-stmt-wrap__photo .ab-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.ab-stmt {
    border-radius: var(--ab-r-lg);
    background: var(--ab-b600);
    color: var(--ab-white);
    padding: 26px 28px;
}

.ab .ab-stmt__mark {
    display: block;
    width: 52px;
    padding-bottom: var(--ab-s-4);
    color: var(--ab-aqua);
}

.ab .ab-stmt__claim {
    color: var(--ab-white);
    font-size: var(--ab-t-subhead);
    font-weight: 800;
    line-height: 1.14;
    letter-spacing: -.03em;
    text-wrap: balance;
}

.ab .ab-stmt--long .ab-stmt__claim {
    font-size: 18px;
}

/* Over a photograph: the card breaks the frame's corner. That overhang is the point —
   with matching radii it reads as two loose rectangles. */
/* The squared joint is the device: the photo gives up the corner the card occupies, so the
   two read as one object rather than two loose rectangles. */
.ab-stmt-wrap--over .ab-stmt-wrap__photo {
    border-radius: 16px 0 16px 16px;
}

.ab-stmt-wrap--over-compact .ab-stmt-wrap__photo {
    border-radius: 0 16px 16px 16px;
}

/* The hero plate sits in the frame's own inset rather than eating a corner, so the
   photograph keeps all four. */
.ab-stmt-wrap--plate .ab-stmt-wrap__photo {
    border-radius: 16px;
    /* 3:2, the ratio the design system's own statement-card page uses (520x330). The old
       35/32 was all but square: 969px tall in a 1090px column, against roughly 680px of
       viewport left once the header and the ribbon have taken their 219px. The plate fell
       below the fold on the one screen it exists to open. */
    aspect-ratio: 3 / 2;
    /* A ceiling, because a ratio alone overflows a short window — but fitting is not the
       bar. At exactly the height of the fold the hero used 99% of it and nothing below
       showed, which is the same complaint as overflowing. Roughly two thirds of the
       viewport leaves the next band peeking, and the second term keeps that true on a
       short window where two thirds would still reach past the chrome. */
    max-height: min(62svh, calc(100svh - 300px));
}
.ab-stmt-wrap--plate .ab-stmt-wrap__photo .ab-img,
.ab-stmt-wrap--plate .ab-stmt-wrap__photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.ab-stmt-wrap--over,
.ab-stmt-wrap--over-compact,
.ab-stmt-wrap--plate {
    padding-top: 32px;
}

.ab-stmt-wrap--over .ab-stmt,
.ab-stmt-wrap--over-compact .ab-stmt,
.ab-stmt-wrap--plate .ab-stmt {
    position: absolute;
    z-index: 2;
    top: 0;
    border-radius: 16px 16px 16px 0;
}

.ab-stmt-wrap--over .ab-stmt {
    right: 0;
    width: 62%;
    padding: 18px 20px;
}

.ab-stmt-wrap--over-compact .ab-stmt {
    left: 0;
    width: 52%;
    padding: 16px;
}

.ab-stmt-wrap--over-compact .ab-stmt__mark,
.ab-stmt-wrap--plate .ab-stmt__mark {
    width: 26px;
    padding-bottom: var(--ab-s-2);
}

.ab-stmt-wrap--over-compact .ab-stmt__claim,
.ab-stmt-wrap--plate .ab-stmt__claim {
    font-size: var(--ab-t-small);
    line-height: 1.2;
}

/* Plate: the hero's corner card. Right-anchored, narrow, list-like. */
.ab-stmt-wrap--plate {
    padding-right: 30px;
}

.ab-stmt-wrap--plate .ab-stmt {
    right: 0;
    left: auto;
    width: 152px;
    padding: 14px 16px;
    border-radius: 14px;
}

/* Who We Help hangs its plate off the TOP LEFT corner, overhanging by the same 32px on both
 * axes so the corner reads as one offset rather than as a card that slipped upward.
 *
 * Mirrored by the same method the top already uses, not by a negative offset. The overhang up
 * there is `padding-top: 32px` on the wrapper with the plate at `top: 0` — the photograph is
 * inset and the plate sits in the space that makes. A negative `left` would have produced the
 * same picture and a different mechanism: the plate would escape the wrapper, which is a grid
 * item, and overlap whatever the column is beside.
 *
 * Scoped rather than changed in the rule above, which is the shared one: About's
 * "Ohio / North Carolina / Texas", the Locations hero and every clinic page's own plate all
 * read from it, and none of them asked to move.
 *
 * Both properties on the plate, not just `left`. The base rule sets `right: 0; left: auto`, so
 * setting `left` alone would anchor it to both edges and stretch it across the photograph. */
.ab-home-who .ab-stmt-wrap--plate {
    padding-left: 32px;
}

.ab-home-who .ab-stmt-wrap--plate .ab-stmt {
    right: auto;
    left: 0;
}

.ab-stmt-wrap--standalone .ab-stmt {
    max-width: 330px;
}

/* =========================================================================
   ability_closing_line
   ====================================================================== */

.ab .ab-closing {
    padding-top: var(--ab-s-6);
    color: var(--ab-accent);
    font-size: var(--ab-t-body);
    font-weight: 700;
    line-height: 1.6;
}

.ab-sec--blue .ab-closing,
.ab-sec--blue600 .ab-closing {
    color: var(--ab-on-dark);
}

/* A closing couplet reads as one thought: the second line rides close. A button
   after a closing line or a body block needs its own beat, or the CTA crowds
   the copy (Rodrigo, Sep 2). */
.ab .ab-closing + .ab-closing {
    padding-top: 0;
    margin-top: var(--ab-s-3);
}

/* A closing line that lands under its own body copy is the end of that thought, not a new
   block, and the rhythm was stacking two block-level steps to say otherwise: .ab-body's 24px
   trailing step plus .ab-closing's 24px lead — 48px, three times the 16px between two
   paragraphs, which read as a section break inside a section.

   The last paragraph contributes nothing to either number: `.ab-body p:last-child` already
   zeroes its own 16px. So the 16px has to be put back deliberately rather than uncovered —
   zeroing both halves and expecting the paragraph step to remain leaves the closing line
   flush against the line above it, which is the opposite mistake.

   Adjacent-sibling only: a closing line that opens a band, or follows a list or a figure,
   still needs its full step and keeps it. :has() rather than a class, so no composition has
   to remember to ask — the sheet already uses it for the row reset and the dot lists. */
.ab .ab-body:has(+ .ab-closing) {
    padding-bottom: 0;
}

.ab .ab-body + .ab-closing {
    padding-top: var(--ab-s-4);
}

.ab .ab-closing + .ab-btn,
.ab .ab-closing + .ab-btns,
.ab .ab-body + .ab-btn,
.ab .ab-body + .ab-btns {
    margin-top: var(--ab-s-6);
}

.ab .ab-closing[data-ab-reveal].is-armed {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity .55s var(--ab-ease-out), transform .55s var(--ab-ease-out);
}

.ab .ab-closing[data-ab-reveal].is-revealed {
    opacity: 1;
    transform: none;
}

/* =========================================================================
   ability_stack — Our Philosophy
   ====================================================================== */

.ab-stack {
    /* Single source of truth for the sticky offsets. The JS driver reads these back with
       getComputedStyle rather than carrying its own copy, which is how the prototype's
       two-pixel drift happened. */
    --ab-top-1: 112px;
    --ab-top-2: 124px;
    --ab-top-3: 136px;
    --ab-range: 230px;
    --ab-fade: 72px;

    position: relative;
}

.ab .ab-stack__heading {
    padding-bottom: var(--ab-s-4);
    color: var(--ab-fg);
    font-size: var(--ab-t-heading);
    font-weight: 800;
    line-height: 1.05;
    letter-spacing: -.032em;
}

/* The pinned heading and its mask. The mask bleeds past the section padding: confined to
   the content box it leaves the card's own shadow uncovered, which shows as two static
   vertical bands the fade never reaches. */
.ab h2.ab-stack__heading--pin {
    position: sticky;
    z-index: 5;
    /* --ab-header is TheGem's fixed sticky header, measured and set by the JS driver.
       Pinned at 0 the heading disappeared behind it entirely. */
    top: var(--ab-header, 0px);
    margin: 0 calc(var(--ab-gutter) * -1) !important;
    padding: 16px var(--ab-gutter) 12px;
    background: var(--ab-b100);
}

.ab-sec--white .ab-stack__heading--pin { background: var(--ab-white); }
.ab-sec--b50 .ab-stack__heading--pin   { background: var(--ab-b50); }

/* stops = --ab-b100 */
.ab .ab-stack__heading--pin::after {
    content: "";
    position: absolute;
    top: 100%;
    right: 0;
    left: 0;
    height: 96px;
    pointer-events: none;
    background: linear-gradient(to bottom,
        var(--ab-b100) 0%,
        rgba(238, 244, 250, .88) 10%,
        rgba(238, 244, 250, .60) 24%,
        rgba(238, 244, 250, .34) 38%,
        rgba(238, 244, 250, .18) 52%,
        rgba(238, 244, 250, .07) 70%,
        rgba(238, 244, 250, 0) 100%);
}

.ab-stack__cards {
    padding-top: 30px;
}

.ab .ab-stack__card {
    position: relative;
    overflow: hidden;
    border-radius: var(--ab-r-lg);
    background: var(--ab-white);
    box-shadow: 0 -16px 30px rgba(7, 20, 38, .16), 0 4px 18px rgba(28, 52, 84, .14);
}

.ab .ab-stack__card + .ab-stack__card {
    margin-top: var(--ab-s-3);
}

.ab .ab-stack__card--flat {
    padding: 22px 20px 24px;
}

.ab .ab-stack__card--flat.ab-stack__card--dark {
    background: var(--ab-b700);
}

.ab .ab-stack__card--photo {
    height: 496px;
}

.ab .ab-stack__bg {
    position: absolute;
    inset: 0;
    background-image: var(--ab-card-bg);
    background-position: center;
    background-size: cover;
}

/* The veil pulls blue so a photographic card still belongs to the family. */
.ab .ab-stack__card--photo .ab-stack__veil {
    position: absolute;
    inset: 0;
    background:
        linear-gradient(102deg, rgba(8, 24, 46, .94) 0%, rgba(10, 30, 56, .85) 40%, rgba(14, 44, 80, .42) 70%, rgba(20, 60, 104, .16) 100%),
        linear-gradient(200deg, rgba(30, 78, 134, .34) 0%, rgba(8, 24, 46, 0) 55%);
}

.ab .ab-stack__card--photo.ab-stack__card--light .ab-stack__veil {
    background:
        radial-gradient(110% 80% at 86% 6%, rgba(160, 200, 236, .5) 0%, rgba(160, 200, 236, 0) 62%),
        linear-gradient(158.7deg, var(--ab-white) 0%, var(--ab-b100) 52%, #DCE9F6 100%);
}

.ab .ab-stack__fig {
    position: absolute;
    right: -1px;
    bottom: -2px;
    width: 144px;
    height: auto;
    object-fit: contain;
    object-position: bottom right;
    pointer-events: none;
}

.ab .ab-stack__wm {
    position: absolute;
    top: -40px;
    right: 8px;
    z-index: 1;
    width: 152px;
    color: rgba(73, 129, 194, .5); /* = --ab-b600 @ 50% */
    opacity: .5;
    pointer-events: none;
}

.ab .ab-stack__card--dark .ab-stack__wm {
    color: var(--ab-on-dark-bd);
}

.ab .ab-stack__cont {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--ab-s-3);
}

.ab .ab-stack__card--photo .ab-stack__cont {
    width: 268px;
    height: 100%;
    padding: var(--ab-s-6);
    justify-content: flex-end;
}

.ab .ab-stack__num {
    display: block;
    color: var(--ab-b600);
    font-size: 35px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: -.05em;
    font-variant-numeric: tabular-nums;
}

.ab .ab-stack__card--dark .ab-stack__num {
    color: var(--ab-aqua);
}

.ab h3.ab-stack__title {
    color: var(--ab-ink);
    font-size: var(--ab-t-subhead);
    font-weight: 800;
    line-height: 1.18;
    letter-spacing: -.03em;
}

.ab .ab-stack__card--dark .ab-stack__title {
    color: var(--ab-white);
}

/* The only aqua on the card, and not the gestural marker — this one is a straight bar. */
.ab .ab-stack__rule {
    display: block;
    width: 40px;
    height: 3px;
    border-radius: 2px;
    background: var(--ab-aqua);
}

.ab .ab-stack__body,
.ab .ab-stack__body p {
    color: var(--ab-ink2);
    font-size: 14px;
    line-height: 1.66;
}

.ab .ab-stack__card--photo .ab-stack__body,
.ab .ab-stack__card--photo .ab-stack__body p {
    max-width: 212px;
    font-size: var(--ab-t-body);
    line-height: 26px;
}

.ab .ab-stack__card--dark .ab-stack__body,
.ab .ab-stack__card--dark .ab-stack__body p {
    color: var(--ab-on-dark-2);
}

.ab .ab-stack__closing {
    padding-top: var(--ab-s-4);
    color: var(--ab-fg);
    font-size: var(--ab-t-body);
    line-height: 1.6;
}

.ab .ab-stack__closing.is-armed {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity .55s var(--ab-ease-out), transform .55s var(--ab-ease-out);
}

.ab .ab-stack__closing.is-revealed {
    opacity: 1;
    transform: none;
}

/* The mechanic. One custom property drives all four transforms; the front card never
   shrinks. Skipped wholesale under reduced motion — the cards fall back to a plain list. */
@media (prefers-reduced-motion: no-preference) and (max-width: 1023px) {
    .ab-stack:not(.ab-stack--plain):not(.is-static) .ab-stack__card {
        --p: 0;

        position: sticky;
        transform-origin: center top;
        transform: scale(calc(1 - var(--p) * .05)) translateY(calc(var(--p) * -6px));
        filter: blur(calc(var(--p) * 1.4px)) brightness(calc(1 - var(--p) * .16));
        will-change: transform, filter;
    }

    .ab-stack:not(.ab-stack--plain):not(.is-static) .ab-stack__card:nth-child(1) { top: calc(var(--ab-top-1) + var(--ab-header, 0px)); }
    .ab-stack:not(.ab-stack--plain):not(.is-static) .ab-stack__card:nth-child(2) { top: calc(var(--ab-top-2) + var(--ab-header, 0px)); }
    .ab-stack:not(.ab-stack--plain):not(.is-static) .ab-stack__card:nth-child(3) { top: calc(var(--ab-top-3) + var(--ab-header, 0px)); }
}

/* =========================================================================
   ability_values_deck — Our Core Values
   ====================================================================== */

.ab-deck {
    position: relative;
}

.ab-deck__stage {
    position: relative;
    touch-action: pan-y;
}

.ab .ab-deck__card {
    --d: 0;
    --tint: 11, 31, 56;

    position: absolute;
    top: 0;
    left: 0;
    width: calc(100% - 74px);
    border-radius: var(--ab-r-lg);
    background: rgb(var(--tint));
    transform-origin: center right;
    transform: translateX(calc(var(--d) * 34px)) scale(calc(1 - var(--d) * .062));
    opacity: min(1, calc(3 - var(--d)));
    filter: blur(calc(var(--d) * 2.6px)) brightness(calc(1 + var(--d) * .13));
    user-select: none;
    -webkit-user-select: none;
    will-change: transform, filter, opacity;
}

.ab .ab-deck__card--t1 { --tint: 22, 69, 111; }
.ab .ab-deck__card--t2 { --tint: 34, 99, 143; }

.ab .ab-deck__card.is-front {
    position: relative;
    cursor: grab;
    box-shadow:
        -2px 26px 54px -14px rgba(7, 20, 38, .62),
        10px 14px 34px -10px rgba(7, 20, 38, .42),
        0 3px 8px rgba(7, 20, 38, .22);
}

.ab .ab-deck__card.is-behind { pointer-events: none; }
.ab .ab-deck__card.is-dragging { cursor: grabbing; }
.ab .ab-deck__card.is-leaving { z-index: 40; }

.ab .ab-deck__card.is-eased {
    transition:
        transform .46s cubic-bezier(.35, .8, .35, 1),
        filter .46s cubic-bezier(.35, .8, .35, 1),
        opacity .46s cubic-bezier(.35, .8, .35, 1);
}

.ab .ab-deck__card.is-rising {
    transition:
        transform .42s cubic-bezier(.35, .8, .35, 1) .16s,
        filter .42s cubic-bezier(.35, .8, .35, 1) .16s,
        opacity .42s cubic-bezier(.35, .8, .35, 1) .16s;
}

.ab-deck__box {
    /* Load-bearing: without it the absolutely-positioned .ab-deck__media resolves against
       .ab-deck__card instead, escapes this box entirely, and the overflow below never clips
       it — which showed as square corners on every photograph. */
    position: relative;
    overflow: hidden;
    border-radius: var(--ab-r-lg);
}

.ab-deck__box > * {
    position: relative;
}

.ab .ab-deck__box {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    min-height: 440px;
}

.ab .ab-deck__media {
    position: absolute;
    inset: 0;
    background-image: var(--ab-card-bg);
    background-position: center;
    background-size: cover;
}

.ab .ab-deck__media::before,
.ab .ab-deck__media::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.ab .ab-deck__media::before {
    background: linear-gradient(to top,
        rgba(var(--tint), .98) 0%,
        rgba(var(--tint), .94) 20%,
        rgba(var(--tint), .66) 44%,
        rgba(var(--tint), .14) 74%,
        rgba(var(--tint), 0) 92%);
}

.ab .ab-deck__media::after {
    opacity: 0;
    background: linear-gradient(to top,
        rgba(var(--tint), .99) 0%,
        rgba(var(--tint), .96) 46%,
        rgba(var(--tint), .74) 78%,
        rgba(var(--tint), .32) 100%);
    transition: opacity .42s cubic-bezier(.22, 1, .32, 1);
}

.ab .ab-deck__card.is-open .ab-deck__media::after { opacity: 1; }

.ab .ab-deck__in {
    position: relative;
    z-index: 2;
    padding: var(--ab-s-6) 20px;
    color: var(--ab-on-dark);
}

.ab h3.ab-deck__title {
    color: var(--ab-on-dark);
    font-size: var(--ab-t-subhead);
    font-weight: 800;
    line-height: 1.14;
    letter-spacing: -.032em;
    text-shadow: 0 1px 16px rgba(5, 14, 28, .55);
}

.ab .ab-deck__wrap {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows .4s cubic-bezier(.22, 1, .32, 1);
}

.ab .ab-deck__card.is-open .ab-deck__wrap { grid-template-rows: 1fr; }

.ab .ab-deck__wrap > div {
    overflow: hidden;
    min-height: 0;
    mask-image: linear-gradient(to bottom, #000 calc(100% - 20px), transparent);
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 20px), transparent);
}

.ab .ab-deck__desc,
.ab .ab-deck__desc p {
    padding-bottom: 20px;
    color: var(--ab-on-dark-2);
    font-size: var(--ab-t-small);
    line-height: 1.65;
}

.ab .ab-deck__act {
    display: inline-flex;
    align-items: center;
    gap: var(--ab-s-2);
    height: 44px;
    margin-top: var(--ab-s-4);
    padding: 0 20px;
    border: 1.5px solid var(--ab-on-dark-bd);
    border-radius: var(--ab-r-pill);
    background: transparent;
    color: var(--ab-on-dark);
    font-family: inherit;
    font-size: 14px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    cursor: pointer;
}

.ab .ab-deck__act b {
    font-size: var(--ab-t-body);
    font-weight: 700;
    line-height: 1;
}

.ab .ab-deck__act:focus-visible {
    outline: 3px solid var(--ab-aqua);
    outline-offset: 3px;
}

/* The house dot pattern — the deck's and the review carousel's dots are one
   recipe, not two. */
.ab .ab-deck__dots,
.ab .ab-reviews__dots,
.ab .ab-docs__dots,
.ab-lightbox__dots {
    display: flex;
    /* Long rosters (the home doctors band) overflow a single dot row at 390 —
       wrapping is invisible for the usual 2-5 dots and saves the 20+ case. */
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--ab-s-2);
    padding-top: var(--ab-s-6);
}

.ab .ab-deck__dot,
.ab .ab-reviews__dot,
.ab .ab-docs__dot,
.ab-lightbox__dot {
    width: 8px;
    height: 8px;
    padding: 0;
    border: 0;
    border-radius: var(--ab-r-pill);
    background: var(--ab-bd);
    /* Hairline, not a border: a border would grow the 8px box and the active dot's width
       animation would start from a different size. box-shadow paints outside the box and
       costs no layout. Transparent on light grounds, so nothing changes there. */
    box-shadow: 0 0 0 1.5px var(--ab-dot-ring);
    cursor: pointer;
    transition: width .32s cubic-bezier(.22, 1, .32, 1), background .32s ease;
}

.ab .ab-deck__dot.is-on,
.ab .ab-reviews__dot.is-on,
.ab .ab-docs__dot.is-on,
.ab-lightbox__dot.is-on {
    width: 24px;
    background: var(--ab-b600);
}

.ab .ab-deck__dot:focus-visible,
.ab .ab-reviews__dot:focus-visible,
.ab .ab-docs__dot:focus-visible,
.ab-lightbox__dot:focus-visible {
    outline: 3px solid var(--ab-b700);
    outline-offset: 3px;
}

/* With JS off, or inside the WPBakery editor, every card is simply visible. */
.ab-deck:not(.is-live) .ab-deck__card {
    position: relative;
    width: 100%;
    transform: none;
    opacity: 1;
    filter: none;
}

.ab-deck:not(.is-live) .ab-deck__card + .ab-deck__card {
    margin-top: var(--ab-s-3);
}

.ab-deck:not(.is-live) .ab-deck__wrap { grid-template-rows: 1fr; }
.ab-deck:not(.is-live) .ab-deck__act,
.ab-deck:not(.is-live) .ab-deck__dots { display: none; }

/* A card whose whole blurb already fits gets no expander, and its copy is simply open.
   "READ MORE +" over one sentence promises a second half that does not exist — the same
   complaint the doctor cards got, answered the same way: the script measures the description
   and marks the cards that genuinely overflow (initDeck, fitsUnasked). Hiding the button
   without opening the row would be worse than the button: the wrap rests at 0fr, so the
   sentence would be there and invisible. */
.ab .ab-deck__card:not(.is-expandable) .ab-deck__act { display: none; }
.ab .ab-deck__card:not(.is-expandable) .ab-deck__wrap { grid-template-rows: 1fr; }

@media (prefers-reduced-motion: reduce) {
    .ab .ab-deck__card,
    .ab .ab-deck__wrap,
    .ab .ab-deck__media,
    .ab .ab-deck__media::after,
    .ab .ab-deck__dot,
    .ab .ab-reviews__dot,
    .ab .ab-docs__dot,
    .ab .ab-docs__more-chv,
    .ab .ab-booking__wait,
    .ab .ab-cond-seg__tab,
    .ab .ab-stack__card,
    .ab .ab-closing[data-ab-reveal],
    .ab .ab-stack__closing {
        transition: none !important;
    }

    .ab-panels--tabs.is-live .ab-panel {
        animation: none !important;
    }
}

/* =========================================================================
   ability_review_ribbon
   ====================================================================== */

.ab-ribbon {
    /* The header's .wpb_wrapper is a flex container, so without this the band shrinks to
       the width of its own text instead of painting the full row. */
    width: 100%;
    /* Blue 700, not 600. The claims are 13px/700, which WCAG counts as normal text and
       therefore needs 4.5:1. White on Blue 600 measures 4.03:1 and fails; Blue 700 gives
       5.36:1. Ability's own decision: "Use Blue 700 for sections carrying white text." */
    background: var(--ab-b700);
    color: var(--ab-on-dark);
    /* The ribbon is header chrome, not a section, so it sits outside the ground
       alternation and can land against a band of its own colour with no way to alternate
       out of it. Hence unconditional: over a light section the hairline reads as a soft
       edge, over blue it holds the two surfaces apart. Inset shadow rather than
       border-bottom so it never adds to the band's height. */
    box-shadow: inset 0 -1px 0 var(--ab-seam-dark);
}

.ab .ab-ribbon__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 6px 14px;
    max-width: var(--ab-max);
    margin: 0 auto;
    padding: 10px var(--ab-gutter);
}

.ab .ab-ribbon__claim {
    display: inline-flex;
    align-items: center;
    gap: var(--ab-s-2);
    color: var(--ab-on-dark);
    font-size: var(--ab-t-small);
    font-weight: 700;
    line-height: 1.3;
    text-decoration: none;
}

.ab a.ab-ribbon__claim:hover .ab-ribbon__text,
.ab a.ab-ribbon__claim:focus-visible .ab-ribbon__text {
    text-decoration: underline;
}

.ab a.ab-ribbon__claim:focus-visible {
    outline: 3px solid var(--ab-white);
    outline-offset: 3px;
    border-radius: var(--ab-r-sm);
}

/* The only aqua on the band. Real glyphs rather than the raster stars.webp the previous
   ribbon shipped, so they stay crisp and recolour with the token. */
.ab .ab-ribbon__stars {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    /* Laura (Aug 27): yellow stars, not aqua — see --ab-star-gold in ability-tokens.css. */
    color: var(--ab-star-gold, #FEDD00);
}

.ab .ab-ribbon__stars svg {
    display: block;
    width: 14px;
    height: 14px;
    fill: currentColor;
}

.ab .ab-ribbon__rule {
    flex: 0 0 auto;
    width: 1px;
    height: 14px;
    background: var(--ab-on-dark-bd);
}

.ab .ab-ribbon__logo {
    display: block;
    width: auto;
    /* The lockup holds two marks side by side, so it needs real height to stay legible —
       below about 32px the Crew badge closes up into a smudge. */
    max-height: 34px;
}

/* Only one of the two marks is ever on screen: the lockup from 640px up, the partner's shield
   alone below it. Never both. */
.ab .ab-ribbon__logo--sm {
    display: none;
}

/* Below 640px the two claims stack, and a vertical hairline between stacked lines reads as
   a stray mark rather than a separator. */
@media (max-width: 639px) {
    .ab .ab-ribbon__inner {
        flex-direction: column;
        gap: 6px;
        padding: var(--ab-s-2) var(--ab-s-3);
        text-align: center;
    }

    .ab .ab-ribbon__rule {
        display: none;
    }

    .ab .ab-ribbon__claim {
        justify-content: center;
        font-size: 12px;
    }

    /* The lockup steps aside here and the partner's shield takes the band on its own.
       Twenty-six pixels had to hold two marks and the rule between them, which left the shield
       about ten pixels wide — a smudge — and spent the rest on the practice's own black mark,
       repeating the logo standing two centimetres above it in the header. Alone, the shield can
       have 38: the most the band carries without growing, and enough to be seen. */
    .ab .ab-ribbon__logo {
        display: none;
    }

    .ab .ab-ribbon__logo--sm {
        display: block;
        max-height: 38px;
    }

    .ab .ab-ribbon__stars svg {
        width: 12px;
        height: 12px;
    }
}

/* ---------------------------------------------------------------------------
 * ability_booking — the scheduler on the page rather than behind a click.
 * ------------------------------------------------------------------------- */

/* Any band the composition names with the section anchor param is a fragment target,
   and the header is sticky — same measurement as .ab-booking below. TheGem's jQuery
   smooth scroll (scroll-to-anchor) computes its own offset; this covers the no-JS
   fragment jump and middle-click-in-new-tab arrivals. */
.ab-section[id] {
    scroll-margin-top: 168px;
}

.ab.ab-booking {
    width: 100%;
    /* The Schedule CTAs link here as a plain fragment, and the header is sticky, so without a
       scroll margin the jump parks the top of the form underneath it. Measured, not guessed:
       the sticky header is 147px at 375 and 187px at desktop, plus a step of air. The wider value
       is set alongside the two-column rules further down, and it takes effect from 768 — not from
       1024, which is what this comment used to claim. */
    scroll-margin-top: 168px;
    /* The waiting state is absolutely positioned against this box. */
    position: relative;
    /* The widget is a form, not a band: it keeps a reading-width measure even when the section
       around it is wider, because a date picker stretched to 1090px puts the day columns and
       the confirm button at opposite ends of the screen. */
    max-width: 720px;
    margin-right: auto;
    margin-left: auto;
}

.ab .ab-booking__frame {
    display: block;
    width: 100%;
    /* No border and no radius of our own: the widget paints its own card, and a second frame
       around it reads as two nested boxes. The height is the block's `height` parameter and
       nothing adjusts it afterwards: GoHighLevel's form_embed.js would have auto-resized the frame,
       but it is deliberately never loaded here — see the long note in booking-embed.php. This
       comment used to claim the opposite. */
    border: 0;
    background: transparent;
}

/* ---------------------------------------------------------------------------
 * The waiting state.
 *
 * Absent unless the script arms it. That inversion is the rule from DESIGN-SYSTEM.md — nothing is
 * ever hidden without JavaScript — and here it has teeth: a visitor with JS off would otherwise be
 * left staring at a placeholder that nothing can ever remove, with the real form sitting behind it.
 *
 * The frame paints its own white card, so once it loads it covers this anyway; the fade is what
 * stops the two being visible at once through the frame's transparent background.
 * ------------------------------------------------------------------------- */

.ab .ab-booking__wait {
    display: none;
}

.ab-booking.is-waiting:not(.ab-booking--none) .ab-booking__wait {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: flex;
    flex-direction: column;
    gap: var(--ab-s-6);
    padding: var(--ab-s-8) var(--ab-s-6);
    border: 1px solid var(--ab-bd);
    border-radius: var(--ab-r-md);
    background: var(--ab-white);
    pointer-events: none;
}

/* Move 3 on the way out — the view switch, the same one the overlays take. A tap brought the
   visitor here and content is arriving, which is exactly what that move is for. */
.ab-booking.is-ready .ab-booking__wait {
    opacity: 0;
    transition: opacity .26s cubic-bezier(.2, .8, .2, 1);
}

/* The silhouette copies the widget's real first paint — the "Enter details" step, not the
   calendar the old 7-column grid promised: branding block (logo, eyebrow, two title lines,
   three step lines), then five labelled fields and a Continue button pinned bottom-right.
   Same silhouette as booking.css's modal copy; if it changes, both change. */
.ab .ab-booking__wait-logo,
.ab .ab-booking__wait-title,
.ab .ab-booking__wait-line,
.ab .ab-booking__wait-heading,
.ab .ab-booking__wait-label,
.ab .ab-booking__wait-input,
.ab .ab-booking__wait-btn {
    display: block;
    border-radius: var(--ab-r-sm);
    /* Blue 50, not grey: a neutral placeholder on a white card reads as a disabled form. */
    background: var(--ab-b50);
}

.ab .ab-booking__wait-brand {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
    padding: var(--ab-s-2) 0 var(--ab-s-3);
}

.ab .ab-booking__wait-logo {
    width: 56px;
    height: 56px;
    margin-bottom: 6px;
}

.ab .ab-booking__wait-title {
    width: min(75%, 340px);
    height: 20px;
}

.ab .ab-booking__wait-title--2 {
    width: min(45%, 200px);
}

.ab .ab-booking__wait-line {
    width: min(60%, 280px);
    height: 11px;
}

.ab .ab-booking__wait-form {
    display: flex;
    flex-direction: column;
    gap: var(--ab-s-2);
}

.ab .ab-booking__wait-heading {
    width: min(35%, 150px);
    height: 18px;
    margin-bottom: var(--ab-s-2);
}

.ab .ab-booking__wait-label {
    width: 90px;
    height: 11px;
    margin-top: 10px;
}

.ab .ab-booking__wait-input {
    width: 100%;
    height: 48px;
}

.ab .ab-booking__wait-btn {
    width: 120px;
    height: 44px;
    margin-top: auto;
    align-self: flex-end;
}

/* The fallback, when a clinic has no scheduler: the button centres like the form it replaces. */
.ab.ab-booking--none {
    display: flex;
    justify-content: center;
}

@media (min-width: 1024px) {
    .ab .ab-ribbon__inner {
        gap: var(--ab-s-6);
        padding: 12px var(--ab-gutter);
    }

    .ab .ab-ribbon__claim {
        font-size: 15px;
    }

    .ab .ab-ribbon__stars svg {
        width: 16px;
        height: 16px;
    }

    .ab .ab-ribbon__rule {
        height: 18px;
    }

    .ab .ab-ribbon__logo {
        max-height: 44px;
    }
}

/* =========================================================================
   ability_steps — What to Expect on Your First Visit
   ====================================================================== */

.ab-steps {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.ab .ab-steps__item {
    display: flex;
    overflow: hidden;
    border-radius: var(--ab-r-md);
    background: var(--ab-white);
}

/* Top-aligned, not centred: on a two-paragraph step a centred numeral drifts into the
   middle of the body copy and stops reading as an index. */
.ab .ab-steps__num {
    display: flex;
    flex: 0 0 50px;
    align-items: flex-start;
    justify-content: center;
    padding-top: 14px;
    background: var(--ab-b600);
    color: var(--ab-white);
    font-size: 19px;
    font-weight: 800;
    letter-spacing: -.03em;
    font-variant-numeric: tabular-nums;
}

.ab-steps--plain .ab-steps__num {
    display: none;
}

.ab .ab-steps__body {
    padding: 14px 16px;
}

.ab h3.ab-steps__title {
    color: var(--ab-ink);
    font-size: 15px;
    font-weight: 800;
    line-height: 1.3;
    letter-spacing: -.02em;
}

.ab .ab-steps__copy,
.ab .ab-steps__copy p {
    padding-top: 6px;
    color: var(--ab-ink2);
    font-size: 13.5px;
    line-height: 1.55;
}

.ab .ab-steps__copy p:first-child {
    padding-top: 0;
}

/* =========================================================================
   ability_stats — the counter strip
   Hairlines read the contextual rule token, so the strip works on any
   ground: on-dark alpha lines on a blue band, the border token on white.
   ====================================================================== */

.ab.ab-stats,
.ab .ab-stats {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    overflow: hidden;
    margin: 0;
    border: 1px solid var(--ab-rule);
    border-radius: var(--ab-r-md);
}

.ab .ab-stats__cell {
    display: flex;
    /* Value above label visually; label-first in the DOM so it reads
       "Founded, 2010" — a definition, which is what a dl is. */
    flex-direction: column-reverse;
    gap: 7px;
    padding: 18px 6px;
    text-align: center;
}

.ab .ab-stats__cell + .ab-stats__cell {
    border-left: 1px solid var(--ab-rule);
}

.ab .ab-stats__value {
    margin: 0;
    color: var(--ab-fg);
    font-size: 28px;
    font-weight: 800;
    letter-spacing: -.04em;
    line-height: 1;
    /* The count-up rewrites the number every frame; tabular figures keep the
       cell from breathing while it runs. */
    font-variant-numeric: tabular-nums;
}

.ab .ab-stats__label {
    color: var(--ab-fg);
    font-size: var(--ab-t-eyebrow);
    font-weight: 700;
    letter-spacing: .14em;
    text-transform: uppercase;
    opacity: .85;
}

/* Blue bar — the FSD stat row on its own Blue 600 plate. Always three, always
   across — including on mobile: the values are short enough to hold three
   columns at 390px, and stacking them costs three screen heights. The bar
   carries its own ground, so every contextual read the strip lives on — the
   --ab-rule frame and hairlines, the --ab-fg type — is pinned to the on-blue
   values instead. Radius and corner clipping arrive from the strip base rule. */
.ab.ab-stats--bar,
.ab .ab-stats--bar {
    /* Always three, always across — including on mobile. */
    grid-template-columns: repeat(3, 1fr);
    max-width: 520px; /* FSD stat row — the bar is an object inside the band, not a band; no layout token at this width. */
    border: 0; /* Neutralises the strip's --ab-rule frame — the plate is its own edge. */
    background: var(--ab-b600);
}

.ab .ab-stats--bar .ab-stats__cell {
    padding: var(--ab-s-6) 10px; /* 10px: FSD stat row — tighter than the spacing step so three cells hold at 390px. */
    color: var(--ab-white);
}

.ab .ab-stats--bar .ab-stats__cell + .ab-stats__cell {
    border-left: 1px solid rgba(255, 255, 255, .28); /* Lighter than --ab-on-dark-bd on purpose, FSD stat row. */
}

.ab .ab-stats--bar .ab-stats__value {
    color: var(--ab-white);
    font-size: 34px; /* Off the ladder deliberately, FSD stat row — do not snap to the responsive heading token. */
}

.ab .ab-stats--bar .ab-stats__label {
    color: var(--ab-white);
    font-size: 10.5px; /* FSD stat row — half a point under the eyebrow token, deliberately its own size. */
}

/* =========================================================================
   ability_find_us
   ====================================================================== */

/* Fluid below 1024 and centred — the mockup's 354px is the 390 viewport's inner width,
   not a card width; pinned at 354 it left a dead strip beside itself on anything wider. */
.ab-findus {
    overflow: hidden;
    width: 100%;
    margin-right: auto;
    margin-left: auto;
    border: 1px solid var(--ab-bd);
    border-radius: 16px;
    background: var(--ab-white);
}

.ab .ab-findus__shot {
    position: relative;
    display: block;
    height: 230px;
    background: var(--ab-b100);
}

.ab .ab-findus__shot .ab-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.ab-findus__body {
    padding: 18px;
}

.ab .ab-findus__block + .ab-findus__block {
    margin-top: 20px;
    padding-top: 20px;
    border-top: 1px solid var(--ab-bd);
}

.ab h3.ab-findus__h {
    padding-bottom: 8px;
    color: var(--ab-b600);
    font-size: 17px;
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: -.025em;
}

.ab .ab-findus__name {
    margin: 0 0 2px;
    font-weight: 700;
    color: var(--ab-ink);
}

.ab .ab-findus__addr {
    color: var(--ab-ink);
    font-size: 15px;
    font-weight: 800;
    line-height: 1.4;
}

.ab .ab-findus__addr a {
    color: inherit;
}

.ab .ab-findus__near {
    padding: 12px 0 5px;
    color: #A3C0E0;
    font-size: 10px;
    font-weight: 800;
    letter-spacing: .14em;
    text-transform: uppercase;
}

.ab .ab-findus__near-list li {
    color: var(--ab-ink2);
    font-size: 13.5px;
    line-height: 1.55;
}

.ab .ab-findus__note {
    padding-top: 3px;
    color: var(--ab-ink2);
    font-size: 13.5px;
    line-height: 1.5;
}

.ab .ab-findus__link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding-top: 12px;
    color: var(--ab-b600);
    font-size: 13.5px;
    font-weight: 800;
}

.ab .ab-findus__link svg {
    width: 15px;
    height: 15px;
    fill: currentColor;
}

.ab .ab-findus__link:hover {
    text-decoration: underline;
}

.ab .ab-findus__hours {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}

.ab .ab-findus__hours td {
    padding: 10px 0;
    border-bottom: 1px solid var(--ab-bd);
    color: var(--ab-ink);
}

.ab .ab-findus__hours tr:last-child td {
    border-bottom: none;
}

.ab .ab-findus__hours td:last-child {
    text-align: right;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* The hours are the most-checked fact on a clinic page; today should never require
   counting rows to find. */
.ab .ab-findus__hours tr.is-today td {
    color: var(--ab-b600);
    font-weight: 800;
}

.ab .ab-findus__cta {
    margin-top: 18px;
}

/* =========================================================================
   ability_doctors
   ====================================================================== */

/* Full width at every size — the band spans the section; card width is the carousel
   basis below 1024 and the capped grid track above it. */
.ab-docs {
    width: 100%;
}

/* A band, not a card — edge to edge like the L4 clinic mock, breaking out of the
   centred inner with the same recipe as .ab-sec--bleed. The section's overflow-x
   clip absorbs the scrollbar-width excess of 100vw. */
.ab-docs__band {
    position: relative;
    left: 50%;
    width: 100vw;
    margin-left: -50vw;
    /* Vertical on the scale, horizontal on the gutter like every other inner. The top is the
       section ladder's own step — this band IS the block's top edge, since the section it sits
       in runs pad_top="none" so the blue can start flush. The bottom is the reserve the cards
       climb into: 96px = 64px of blue behind the card plus the 32px step under the heading. */
    padding: var(--ab-s-8) var(--ab-gutter) var(--ab-s-24);
    background: var(--ab-b600);
    text-align: center;
}

.ab .ab-docs__eyebrow {
    padding-bottom: var(--ab-s-3);
    color: var(--ab-aqua);
    font-size: var(--ab-t-eyebrow);
    font-weight: 700;
    letter-spacing: .16em;
    text-transform: uppercase;
}

.ab h2.ab-docs__heading {
    color: var(--ab-white);
    font-size: 26px;
    font-weight: 800;
    line-height: 1.06;
    letter-spacing: -.03em;
}

.ab .ab-docs__sub {
    padding-top: var(--ab-s-3);
    color: var(--ab-white);
    font-size: 14px;
    line-height: 1.55;
    opacity: .92;
}

/* The cards ride up over the band — that overlap is what ties the two together. Below
   1024 the list is the same peek carousel the reviews use (the prototype's .rl): one card
   parked at the gutter, the next peeking past the right edge, dots below. The 20px head
   and 34px foot of in-track padding keep the card shadow from clipping against the scroll
   container; the extra 20px of negative top margin restores the 72px ride-over. */
.ab-docs__track {
    /* Above the band: the band is positioned (for its breakout), so without a
       stacking order of its own the ride-over would paint underneath it. */
    position: relative;
    z-index: 1;
    display: flex;
    gap: var(--ab-s-3);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
}

/* Roster cards vary — most provider records carry only chip, name and credential,
   and the default stretch would give every one the white belly of the tallest card
   (the ones with a passion panel). Content height, ragged bottoms. The clinic
   pages' grid keeps the stretch: two full cards there SHOULD equalise. */
.ab .ab-docs__track {
    /* The bleed has to be symmetric, and the gutter is the only value that makes it so:
       a full gutter of padding against a full gutter of negative margin puts the clip edge
       on the viewport edge at both ends, so a card scrolling out to the left leaves the
       screen exactly the way one scrolling out to the right does. Anything narrower — this
       carried no left padding at all, then 12px — clips the outgoing card on a flat edge
       inside the page, which reads as a bug rather than as a carousel. It doubles as the
       shadow's room: 18px against the 10px reach of --ab-shadow-card-sm. The head and foot
       cover its 8px up and 12px down.

       Vertically the three numbers are one sum, so they move together: the head is 16px of
       shadow room, the negative margin is that head plus the 64px of blue the card is meant to
       cover, and the foot is the 24px step down to the dots. Change one and the ride-over moves.
       The foot doubles as the dots' separation, which is why .ab-docs__dots below zeroes the
       house 24px rather than adding to it. */
    align-items: flex-start;
    margin-top: calc(-1 * (var(--ab-s-16) + var(--ab-s-4)));
    margin-inline: calc(-1 * var(--ab-gutter));
    padding: var(--ab-s-4) var(--ab-gutter) var(--ab-s-6);
    scroll-padding-left: var(--ab-gutter);
    scrollbar-width: none;
}

.ab-docs__track::-webkit-scrollbar {
    display: none;
}

/* The track's 18px foot already separates the dots from the cards. */
.ab .ab-docs__dots {
    padding-top: 0;
}

.ab-docs:not(.is-live) .ab-docs__dots {
    display: none;
}

/* The reference sheet's card chrome, not a lifted one: `.c` there is a 1px --ab-bd
   hairline over --ab-shadow-card-sm at --ab-r-md, and the doctor carousel is built on it
   (`.c.doc3`). The 16px radius over 0 10px 30px/.18 this carried before was three times
   the reference's blur at three times its opacity — heavy enough to read as a different
   component, to need 40px of clearance the mobile track never had, and to leave the
   photograph with no edge of its own where it meets the blue band. The hairline is what
   draws that edge. */
.ab .ab-docs__card {
    flex: 0 0 min(74%, 330px);
    overflow: hidden;
    border: 1px solid var(--ab-bd);
    border-radius: var(--ab-r-md);
    background: var(--ab-white);
    box-shadow: var(--ab-shadow-card-sm);
    scroll-snap-align: start;
}

/* A roster of one is not a carousel.
 *
 * The 74% above is the peek: the remaining 26% is where the next card shows, and that sliver
 * of the ground is the whole reason a visitor knows to swipe. With one card there is no next
 * card, so the 26% is just empty space on the right and the band reads as a layout that
 * failed to fill — a card shoved against the left gutter with a hole beside it. Seven of the
 * clinic pages have exactly one doctor, so this is the common case, not the edge one.
 *
 * :only-child rather than a count threaded through PHP: the track's children ARE the cards,
 * so the DOM already knows the answer and the block does not have to be told to say it. The
 * JS agrees from the other side — initDocs() only wires the snap track when there is more
 * than one card, and the dots stay hidden without .is-live, so nothing here is fighting a
 * carousel that thinks it is running.
 *
 * `margin-inline: auto` on the item rather than `justify-content` on the track, because the
 * track is the scroll container: centring it there would move the scroll origin as well, and
 * a container whose content fits should not have one. */
.ab .ab-docs__card:only-child {
    flex-basis: min(100%, 330px);
    margin-inline: auto;
}

/* The featured row does not scroll, at any width.
 *
 * That is the rule, not a mobile compromise: the row exists to say these two doctors are not the
 * same as the other twenty-six, and a row that can be pushed out of frame is a row a visitor can
 * miss. So below 1024 it wraps and stacks instead of becoming a second peek track — one card per
 * line, centred, at the same 330px cap the lone-card case uses. Two peek carousels stacked on a
 * phone would also be two horizontal gestures competing for the same finger.
 *
 * The ride-over stays on the lead row and comes off the roster row below it, because only the
 * first track meets the blue band. */
.ab .ab-docs__track--lead {
    flex-wrap: wrap;
    justify-content: center;
    overflow: visible;
    scroll-snap-type: none;
}

.ab .ab-docs__track--lead .ab-docs__card {
    flex-basis: min(100%, 330px);
}

.ab .ab-docs--has-lead .ab-docs__track:not(.ab-docs__track--lead) {
    margin-top: 0;
}

/* Two states, and the card's own scroll is what travels between them.
   State 1, at rest: the photograph fills the top, the body sits under it at its
   resting height, and the card is exactly those two stacked. 23 of the 25 records
   carry only chip, name and credential, so their content ends inside that height,
   there is nothing to scroll and the card never leaves state 1 — which is also what
   gives every card the same height and keeps the dots off the floor.
   State 2, scrolled: the body rides up over the photograph. The card is the scroll
   container and the photograph is sticky inside it, so the photograph holds still
   while the body crosses it, and the travel can never exceed the card's own height —
   the row of three stays aligned to the pixel. The photograph blurs and swells in
   step with the travel, so the copy always lands on a quiet ground.
   --ab-docs-rest is the body's share of that resting height; the script reads it back to
   size the card, because CSS cannot name that number — the photograph is 4:3 of a fluid
   card width, so it moves with every viewport. Carousel only: the clinic pages' grid
   equalises by stretching, which is right when two full cards share a row. */
/* A scrollport, never a drag target. The `overflow: hidden` on the rule above is what does the
   work now: a hidden box is still a scroll container, so scrollTo(), the sticky photograph and
   --ab-docs-p all keep working, while the browser stops routing touch drags and the wheel to it.
   That is the whole fix for the scroll trap — a finger landing on a card scrolls the page, and
   .ab-docs__more is the way in. The scrollbar-hiding this rule used to carry went with the drag:
   a hidden box has no scrollbar to hide.
   Do NOT "correct" this to `overflow: clip` on CLAUDE.md's clip-over-hidden rule. A clip box is
   not a scroll container: the sticky photograph would fall back to static and scrollTo() would
   become a silent no-op, so the card would simply stop opening with no error anywhere. */
.ab .ab-docs__card {
    --ab-docs-rest: 120px;
}

/* Closed, the card must never take a drag — that is the whole point of the fix above. Open, it
   may, and on a narrow phone it has to: the photograph is 4:3 of the card, so it shrinks as the
   viewport narrows, while the copy grows as it wraps. The two move in opposite directions, and by
   305px the body overruns the card by about 60px — a tail that would be unreachable if a tap were
   the only travel available. Measured, not assumed: at 390 the body is 291 against a 315 card and
   nothing is lost; at 305 it is 329 against 268.
   The trap this reintroduces is a different animal from the one removed. That one ambushed a
   finger aimed at the page; this one only ever catches a finger inside a card the visitor chose to
   open, which is the gesture they meant. Tapping the chevron again closes it and hands the page
   back.
   The scrollbar stays hidden here as it always was — the gradient and the chevron are the
   affordance, and a native bar would sit on top of them. */
.ab .ab-docs__card.is-open {
    overflow-y: auto;
    scrollbar-width: none;
}

.ab .ab-docs__card.is-open::-webkit-scrollbar {
    display: none;
}

.ab .ab-docs__card .ab-docs__media {
    position: sticky;
    top: 0;
    z-index: 0;
    overflow: hidden;
}

/* The swell is not decoration: a blur samples past the image's edges, so a blurred
   image sitting flush in its box goes soft and half-transparent at the rim. Growing it
   by 8% keeps real pixels under the blur radius at every edge. */
.ab .ab-docs__card .ab-docs__media .ab-img {
    transform: scale(calc(1 + var(--ab-docs-p, 0) * .08));
    filter: blur(calc(var(--ab-docs-p, 0) * 8px));
}

/* Opaque and above, or the photograph would read through the sheet as it crosses. The
   lift grows with the travel: at rest the body's top edge merely meets the photograph's
   bottom and there is nothing to cast. */
/* A card with nothing under the name is a shorter card, not a short card padded out to match
   the others. The resting height exists to give a consistent box to a body that HAS something to
   scroll; on a record carrying only a chip and a name it is 260px of white with nothing in it,
   and the two the home features are exactly those two records. Asked for by what the body
   contains rather than by a class on the card, so it stays true as the records get filled in:
   the moment an editor adds a specialty, the card is the same height as every other. */
.ab .ab-docs__body:not(:has(.ab-docs__spec, .ab-docs__passion, .ab-docs__copy)) {
    min-height: 0;
}

.ab .ab-docs__body {
    position: relative;
    z-index: 1;
    min-height: var(--ab-docs-rest);
    background: var(--ab-white);
    box-shadow: 0 -10px 24px rgba(20, 40, 70, calc(var(--ab-docs-p, 0) * .16));
}

/* 4:3 landscape, flush — the L3 Metro-gateway card's crop (≈262×200 at 390). This card
   supersedes the earlier 3:4 portrait note for the doctor card specifically; standalone
   doctor photography elsewhere keeps the portrait rule. */
.ab .ab-docs__media {
    position: relative;
    display: block;
    aspect-ratio: 4 / 3;
    background: var(--ab-b700);
}

.ab .ab-docs__media .ab-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* 30%, not top: the 2026 studio shots are 4:3 like the frame, so position never
       bites them — but the legacy portraits carry wall above the head, and top-anchoring
       showed exactly that wall (or a face peeking from the card's bottom edge). Interim
       until every doctor has a 2026 shot; JP owns the proper per-face centering. */
    object-position: 50% 30%;
}

.ab .ab-docs__body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--ab-s-2);
    padding: 16px;
}

/* Nothing in the card says it opens, and until this existed nothing let it open safely either:
   the card was its own drag scroller, so a finger aimed at the page got the card instead and the
   page stopped until the finger came up. The control replaces that gesture with a tap.
   It is a scrim the copy fades into with a chevron sitting in it, pinned to the floor of the
   scrollport by `sticky`, and it costs no height — the negative margin cancels its own box, so
   every card still measures exactly photograph plus resting body.
   No fourth motion token either. The tap calls scrollTo() on the card, the existing scroll
   listener fires on a programmatic scroll exactly as it did on a drag, and --ab-docs-p carries
   the travel to the blur, the sheet's shadow and this gradient. It is the same mechanic; only
   the input changed.
   The gradient lives on ::before rather than on the button so it can keep fading against
   --ab-docs-p while the chevron stays at full strength — fading the button itself would take the
   glyph with it, and the control has to stay findable to be tapped a second time to close. */
.ab .ab-docs__more {
    position: sticky;
    bottom: 0;
    z-index: 2;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    width: 100%;
    /* 56, and the negative margin still cancels it, so the card measures the same as before.
       40px was the scrim's height and the target's height at once, and a 40px band whose glyph
       sits in the bottom 16 of it is a control that looks smaller than it is. The whole band is
       the target — full width, 56 tall, comfortably past the 44px minimum — while the disc below
       is what the eye is actually offered. */
    height: 56px;
    margin-top: -56px;
    padding: 0 0 10px;
    border: 0;
    background: none;
    color: var(--ab-b600);
    cursor: pointer;
}

.ab .ab-docs__more::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, var(--ab-white) 60%, rgba(255, 255, 255, 0));
    opacity: calc(1 - var(--ab-docs-p, 0));
    pointer-events: none;
}

/* The chevron sits IN something now.
 *
 * It was a bare glyph on a gradient, and once a card is fully scrolled that gradient is
 * transparent — --ab-docs-p drives its opacity — so the chevron ended up floating over the last
 * line of the bio with nothing under it. It read as a mark on the text rather than as a control,
 * which is exactly what a visitor reported: it does not look like a button.
 *
 * A disc gives it a surface of its own that does not depend on the scrim: white ground, the
 * library's hairline, one step of the shadow ladder. It is the same chrome the card itself wears,
 * so it reads as part of the component and not as a floating badge. */
.ab .ab-docs__more-chv {
    position: relative;
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 8px;
    border: 1px solid var(--ab-bd);
    border-radius: var(--ab-r-pill);
    background: var(--ab-white);
    box-shadow: var(--ab-shadow-card-sm);
    box-sizing: border-box;
    transition: transform .2s ease, background .16s ease, color .16s ease;
}

.ab .ab-docs__more:hover .ab-docs__more-chv,
.ab .ab-docs__more:focus-visible .ab-docs__more-chv {
    background: var(--ab-b600);
    color: var(--ab-white);
}

/* The flip is on the glyph, never on the button: the button carries the gradient, and rotating
   it would stand the gradient on its head. */
.ab .ab-docs__card.is-open .ab-docs__more-chv {
    transform: rotate(180deg);
}

/* The script arms only the cards that genuinely overflow, so a card with nowhere to go never
   shows a control. Hidden rather than not-emitted so one measurement pass can arm and disarm on
   resize without touching the DOM. */
.ab .ab-docs__card:not(.is-armed) .ab-docs__more {
    display: none;
}

/* offset -3, against the library's +3 everywhere else: the card clips, so a ring drawn outward
   from a child pinned to its floor is cut away and the control looks unfocusable. */
.ab .ab-docs__more:focus-visible {
    outline: 3px solid var(--ab-b700);
    outline-offset: -3px;
    border-radius: var(--ab-r-sm);
}

/* Air under the copy where the travel ends — 16px against the floor read as cramped. It has to
   clear the control's full 40px, not the 32 the old scrim needed: that scrim went transparent at
   the end of the travel, this chevron stays opaque, so the last line would sit under it.
   Scoped to the armed cards on purpose: applying it to all of them would push a card whose
   content just fits into overflowing and arm a control that reveals nothing but its own padding.
   Stable in both directions — padding only ever increases overflow. */
.ab .ab-docs__card.is-armed .ab-docs__body {
    padding-bottom: calc(var(--ab-s-8) + var(--ab-s-2));
}

.ab .ab-docs__chip {
    display: inline-block;
    padding: 5px 12px;
    border-radius: var(--ab-r-pill);
    background: var(--ab-b100);
    color: var(--ab-b700);
    font-size: var(--ab-t-eyebrow);
    font-weight: 700;
}

/* 16px/-.025em, measured off the reference sheet's own h4 — 19px was oversized for a
   card this width and made the name shout over the chip above it. */
.ab h3.ab-docs__name {
    color: var(--ab-ink);
    font-size: 16px;
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: -.025em;
}

.ab .ab-docs__cred {
    color: var(--ab-ink2);
    font-size: 12.5px;
    font-weight: 700;
    line-height: 1.5;
}

/* The focus-area line: a tick in a fixed column, the label beside it.
 *
 * It was a bold "+" until the reference sheet was read back. A plus is the glyph for "add" or
 * "expand" — it is what this card's own expander would wear — and this line is neither: it is a
 * statement about the doctor that is already true. A tick says that; a plus asks to be pressed.
 *
 * Blue 600 for the disc, white for the check. The sheet draws the disc in the aqua tint, which
 * measures 1.6:1 against white and disappears at 20px on any screen that is not the designer's.
 * Blue 600 is 4.03:1 — it fails AA for 16px TEXT, which is why no copy sits on it anywhere, and
 * clears the 3:1 that WCAG 1.4.11 asks of a graphic that carries meaning. */
.ab .ab-docs__spec {
    display: flex;
    gap: 7px;
    align-items: flex-start;
    color: var(--ab-ink2);
    font-size: var(--ab-t-small);
    line-height: 1.45;
}

.ab .ab-docs__tick {
    flex: 0 0 auto;
    /* Optically centred on the first line rather than aligned to its top: the disc is 18px
       against a 1.45 line box, so top alignment leaves it riding a couple of pixels high. */
    margin-top: 1px;
    color: var(--ab-b600);
}

.ab .ab-docs__tick svg {
    display: block;
    width: 18px;
    height: 18px;
}

.ab .ab-docs__passion {
    align-self: stretch;
    padding: 12px 13px;
    border-radius: 10px;
    background: var(--ab-b100);
}

.ab .ab-docs__passion p {
    color: var(--ab-ink2);
    font-size: var(--ab-t-small);
    line-height: 1.5;
}

/* p.<class>, not the bare class: the label is a <p> inside .ab-docs__passion, so the
   paragraph rule above — one element more specific — was winning colour and size, and the
   label rendered at Ink 2 / 13px instead of Blue 600 / 11px. Only the properties the
   paragraph rule does not set (uppercase, tracking) were surviving, which is exactly why
   it looked almost right. Reference: `.ebs` in the preview sheet. */
.ab .ab-docs__passion p.ab-docs__passion-label {
    padding-bottom: 5px;
    color: var(--ab-b600);
    font-size: var(--ab-t-eyebrow);
    font-weight: 700;
    letter-spacing: .14em;
    text-transform: uppercase;
}

.ab .ab-docs__rule {
    align-self: stretch;
    height: 1px;
    margin: 6px 0;
    background: var(--ab-bd);
}

.ab .ab-docs__copy,
.ab .ab-docs__copy p {
    color: var(--ab-ink2);
    font-size: 13.5px;
    line-height: 1.45;
}

/* =========================================================================
   ability_condition_panels
   ====================================================================== */

.ab-panels {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.ab .ab-panel {
    overflow: hidden;
    border: 1px solid var(--ab-bd);
    border-radius: var(--ab-r-md);
    background: var(--ab-white);
}

.ab .ab-panel--blue {
    border-color: var(--ab-b600);
    background: var(--ab-b600);
}

.ab h3.ab-panel__title {
    padding: 17px 18px 4px;
    color: var(--ab-b600);
    font-size: 20px;
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: -.02em;
}

.ab .ab-panel--blue .ab-panel__title {
    color: var(--ab-white);
}

.ab .ab-panel__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 18px;
    border-top: 1px solid var(--ab-bd);
    color: var(--ab-ink);
    font-size: 15px;
    font-weight: 700;
}

.ab .ab-panel--blue .ab-panel__row {
    border-top-color: rgba(255, 255, 255, .24);
    color: var(--ab-white);
}

.ab .ab-panel__row:hover .ab-panel__label,
.ab .ab-panel__row:focus-visible .ab-panel__label {
    text-decoration: underline;
}

/* A row with no page of its own: lighter weight, muted ink, and no arrow. The arrow is a
   promise that the row goes somewhere. Thirteen of the twenty conditions have no page yet. */
.ab .ab-panel__row--soft {
    font-weight: 400;
    color: var(--ab-ink2);
    cursor: default;
}

.ab .ab-panel--blue .ab-panel__row--soft {
    color: var(--ab-on-dark-2);
}

.ab .ab-panel__count {
    padding: 0 18px 4px;
    color: var(--ab-ink3);
    font-size: var(--ab-t-small);
    line-height: 1.5;
}

.ab .ab-panel--blue .ab-panel__count {
    color: var(--ab-on-dark-3);
}

/* Arrow, not a plus: the row navigates away rather than expanding in place. */
.ab .ab-panel__go {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--ab-aqua);
    color: var(--ab-b700);
}

.ab .ab-panel__go svg {
    width: 15px;
    height: 15px;
    fill: currentColor;
}

/* --- layout="tabs" — the Conditions version B frame ------------------------
   The segmented control is Finder Controls' segment, widened; each panel's
   title becomes its tab. Everything here is inert until JS adds is-live, so
   with JavaScript off and inside the WPBakery editor the block renders the
   stacked, titled panels layout unchanged. The container's own 16px column
   gap already separates the control from the visible panel — no margins. */

.ab .ab-cond-seg {
    display: none;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 2px;
    padding: 2px;
    border: 2px solid var(--ab-b600);
    border-radius: var(--ab-r-pill);
}

.ab-panels--tabs.is-live .ab-cond-seg {
    display: grid;
}

.ab .ab-cond-seg__tab {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 40px;
    padding: 0 10px;
    border: 0;
    border-radius: var(--ab-r-pill);
    background: transparent;
    color: var(--ab-b600);
    font-family: var(--ab-font);
    font-size: var(--ab-t-eyebrow);
    /* 800, not the eyebrow's 700 — the mock sets the segment heavier and the
       face exists (theme.scss declares Inter 800). */
    font-weight: 800;
    letter-spacing: .06em;
    line-height: 1.1;
    text-align: center;
    text-transform: uppercase;
    cursor: pointer;
    transition: background-color .22s cubic-bezier(.2, .8, .2, 1), color .22s cubic-bezier(.2, .8, .2, 1);
}

.ab .ab-cond-seg__tab.is-on {
    background: var(--ab-b600);
    color: var(--ab-white);
}

.ab .ab-cond-seg__tab:focus-visible {
    outline: 3px solid var(--ab-b700);
    outline-offset: 3px;
}

/* The title lives in the control now; the h3 stays in the markup as the
   no-JS/editor fallback. Same specificity as the base title rule (0,2,1) —
   source order carries it. */
.ab-panels--tabs.is-live h3.ab-panel__title {
    display: none;
}

/* With the title hidden the count line becomes the panel's first child, and it carried no top
   padding because the title above it used to provide that rhythm — so it sat flush against the
   panel's border. It takes over the title's own 17px, which keeps the top of the panel spaced
   the same whether the title is showing or the segment is standing in for it. Scoped to
   .is-live on purpose: without JavaScript the title renders and nothing needs to move. */
.ab-panels--tabs.is-live .ab-panel__count {
    padding-top: 6px;
}

.ab-panels--tabs.is-live .ab-panel[hidden] {
    display: none;
}

/* The view-switch move: the control swaps instantly, the incoming panel rises
   in — the prototype's vIn, "same behaviour as the List/Map switch in
   Locations". JS replays it via the animation-reset trick. */
@keyframes ab-cond-in {
    from {
        opacity: 0;
        transform: translateY(6px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.ab-panels--tabs.is-live .ab-panel {
    animation: ab-cond-in .26s cubic-bezier(.2, .8, .2, 1) both;
}

/* =========================================================================
   ability_reviews
   ====================================================================== */

/* Below 1024 the row is the client-approved carousel: one card parked at the
   left gutter plus a peek of the next (the prototype's 74% basis), dots below.
   The negative margin runs the track to the right viewport edge, as the
   prototype's review list does. overflow-x:auto is deliberate — the track IS a
   scroll container; the #page overflow trap does not apply because nothing
   inside a review card is sticky. */
.ab-reviews__track {
    display: flex;
    gap: var(--ab-s-3);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    margin-right: calc(-1 * var(--ab-gutter));
    padding-right: var(--ab-gutter);
    scrollbar-width: none;
}

.ab-reviews__track::-webkit-scrollbar {
    display: none;
}

/* With JS off, or inside the WPBakery editor, the dots are dead controls; the
   track still scrolls natively, so every card stays reachable without them.
   is-idle is the live equivalent: every card fits, nothing to page. */
.ab-reviews:not(.is-live) .ab-reviews__dots,
.ab-reviews.is-idle .ab-reviews__dots,
.ab-docs.is-idle .ab-docs__dots {
    display: none;
}

.ab .ab-review {
    flex: 0 0 min(74%, 330px);
    width: 100%;
    max-width: 330px;
    scroll-snap-align: start;
    padding: 16px;
    border: 1px solid var(--ab-bd);
    border-radius: var(--ab-r-md);
    /* No shadow — this card is deliberately flatter than the clinic card. */
    background: #F4F5F7;
}

.ab-review__top {
    display: flex;
    align-items: flex-start;
    gap: 11px;
}

.ab .ab-review__avatar {
    position: relative;
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--ab-b100);
    color: var(--ab-b600);
}

.ab .ab-review__avatar .ab-img {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
}

.ab .ab-review__initials {
    font-size: 14px;
    font-weight: 800;
}

.ab .ab-review__guide {
    position: absolute;
    right: -2px;
    bottom: -2px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border: 2.5px solid #F4F5F7;
    border-radius: 50%;
    background: #E8710A;
    color: var(--ab-white);
}

.ab .ab-review__guide svg {
    width: 9px;
    height: 9px;
    fill: currentColor;
}

.ab .ab-review__id {
    flex: 1;
    min-width: 0;
}

.ab .ab-review__id b {
    display: block;
    color: var(--ab-b600);
    font-size: 15px;
    font-weight: 700;
    letter-spacing: -.01em;
}

.ab .ab-review__id em {
    display: block;
    padding-top: 1px;
    color: var(--ab-ink3);
    font-size: 12.5px;
    font-style: normal;
}

.ab .ab-review__google {
    flex: 0 0 auto;
    margin-left: auto;
}

.ab .ab-review__google svg {
    display: block;
    width: 19px;
    height: 19px;
}

/* Google's amber, not the brand blue. Recolouring these would present a third-party
   rating as our own graphic. */
.ab .ab-review__stars {
    padding: 10px 0 8px;
    color: #F5A623;
    font-size: 18px;
    letter-spacing: .06em;
}

/* Long reviews scroll inside the card instead of stretching it, so a row keeps one height. */
.ab .ab-review__body {
    max-height: 132px;
    overflow-y: auto;
    padding-right: 10px;
    color: var(--ab-ink);
    scrollbar-width: thin;
    scrollbar-color: #C3CBD3 transparent;
}

.ab .ab-review__body p {
    color: var(--ab-ink);
    font-size: 14.5px;
    line-height: 1.6;
}

.ab .ab-review__body::-webkit-scrollbar {
    width: 5px;
}

.ab .ab-review__body::-webkit-scrollbar-thumb {
    border-radius: 3px;
    background: #C3CBD3;
}

/* =========================================================================
   ability_footer
   ====================================================================== */

.ab-footer {
    position: relative;
    overflow: hidden;
    padding: 30px var(--ab-gutter) 34px;
    /* Blue 700, not the brand Blue 600: this band carries white text, and white on Blue 600
       is 4.04:1 — below AA. Ability's UX/UI recap settles it in favour of accessibility and
       keeps Blue 600 for the logo and for grounds that carry no white copy. */
    background: var(--ab-b700);
    color: var(--ab-on-dark);
    /* The mirror of the ribbon's edge, for the same reason and with the same token. The
       footer is chrome, not a section, so `$seam` in section.php never sees it: that flag
       only fires when two ability_sections in a row share a ground. Blue 700 is fixed here,
       so a page whose last band is also blue — the home ends on The Story — hands the footer
       a join with nothing at it, measured at 0px between two identical surfaces. Inset
       rather than border-top so it never adds 1px to the footer's height. */
    box-shadow: inset 0 1px 0 var(--ab-seam-dark);
}

.ab .ab-footer__wm {
    position: absolute;
    top: -44px;
    right: -52px;
    z-index: 0;
    width: 320px;
    color: var(--ab-on-dark-bd);
    /* At 320px on a 390px band the mark reaches the link columns, and it must never compete
       with text. If it shows through the copy the tint is wrong, not the position. */
    opacity: .16;
    pointer-events: none;
}

@media (min-width: 1024px) {
    .ab .ab-footer__wm {
        width: 380px;
        opacity: .28;
    }
}

.ab-footer__inner {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    gap: 30px;
    max-width: var(--ab-max);
    margin: 0 auto;
}

.ab .ab-footer__logo {
    display: block;
    max-width: 100%;
}

.ab .ab-footer__logo img {
    display: block;
    width: 100%;
    height: auto;
}

.ab-footer__brand {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 18px;
}

.ab .ab-footer__cta {
    width: 100%;
}

.ab .ab-footer__socials {
    display: flex;
    gap: 10px;
    padding: 0;
    list-style: none;
}

.ab .ab-footer__socials li { margin: 0; }

.ab .ab-footer__socials a {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: var(--ab-aqua);
    color: var(--ab-b600);
    text-decoration: none;
    transition: background .16s ease;
}

.ab .ab-footer__socials a:hover { background: var(--ab-white); }

.ab .ab-footer__socials svg {
    display: block;
    width: 20px;
    height: 20px;
}

.ab nav.ab-footer__nav {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 26px;
}

.ab h2.ab-footer__col-title {
    padding-bottom: 11px;
    border-bottom: 1px solid rgba(255, 255, 255, .42);
    color: var(--ab-on-dark);
    font-size: var(--ab-t-body);
    font-weight: 400;
    line-height: 1.3;
    letter-spacing: -.01em;
    text-transform: none;
}

.ab .ab-footer__list {
    padding: 0;
    list-style: none;
}

.ab .ab-footer__list li {
    padding-top: var(--ab-s-2);
    margin: 0;
}

/* The mobile trim. Ability's decision of 2026-08-19 — "25 links + CTA + phone is excessive
   on mobile... Desktop can accommodate more" — and the reference sheet's own footer already
   shows the cut at three links a column. Hidden, not removed: the links stay in the menu,
   stay editable, and come back at 1024. Which items survive is the block's `mobile` param,
   set per column in the footer composition, so this rule holds no editorial content. */
@media (max-width: 1023.98px) {
    .ab .ab-footer__list li.ab-footer__item--wide {
        display: none;
    }
}

.ab .ab-footer__list a {
    color: var(--ab-on-dark);
    font-size: var(--ab-t-body);
    line-height: 1.4;
    text-decoration: none;
}

.ab .ab-footer__list a:hover,
.ab .ab-footer__list a:focus-visible {
    color: var(--ab-aqua);
    text-decoration: underline;
}

.ab .ab-footer__legal {
    position: relative;
    z-index: 1;
    max-width: var(--ab-max);
    margin: 0 auto;
    padding-top: var(--ab-s-6);
    color: var(--ab-on-dark-3);
    font-size: var(--ab-t-small);
}

/* Nothing inside the new footer needs the container the parent theme wraps it in. */
.custom-footer > .container:has(.ab-footer) {
    max-width: none;
    padding: 0;
}

/* =========================================================================
   ability_hours
   ====================================================================== */

/* Fluid and centred, capped where the clinic card caps — a fact box, never a
   band-wide table. */
.ab-hours {
    width: 100%;
    max-width: 520px; /* the clinic card's cap — no layout token lands here */
    margin-right: auto;
    margin-left: auto;
    padding: 18px; /* the clinic card's inner inset — off the 8pt ladder, no token */
    border: 1px solid var(--ab-bd);
    border-radius: var(--ab-r-md);
    background: var(--ab-white);
}

/* Bare variant: the table alone, for a card that already owns the shell — which
   then also owns the width. */
.ab.ab-hours--bare {
    max-width: none;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
}

/* Same visual voice as .ab-findus__h — one heading role for every clinic-fact panel. */
.ab h3.ab-hours__title {
    padding-bottom: var(--ab-s-2);
    color: var(--ab-b600);
    font-size: 17px; /* the clinic-card panel heading — between --ab-t-body and --ab-t-subhead, no token */
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: -.025em;
}

.ab .ab-hours__table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14.5px; /* intermediate scale between --ab-t-small and --ab-t-body — measured off the FSD panel */
}

.ab .ab-hours__table td {
    padding: 11px 0; /* the FSD panel's row step — no spacing token lands on 11 */
    border-bottom: 1px solid var(--ab-bd);
    color: var(--ab-ink);
}

.ab .ab-hours__table tr:last-child td {
    border-bottom: none;
}

/* tabular-nums so the times set as a column, not a ragged edge. "Closed" sits in the
   same ink as any time — a closed day is information, not an alert. */
.ab .ab-hours__table td:last-child {
    text-align: right;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* The hours are the most-checked fact on a clinic page; today should never require
   counting rows to find. */
.ab .ab-hours__table tr.is-today td {
    color: var(--ab-b600);
    font-weight: 800;
}

/* =========================================================================
   ability_clinic_cards / ability_clinic_card — the clinic card grid
   ====================================================================== */

/* One column on mobile; the grid owns every seam, so a card never carries a
   margin in any direction. */
.ab.ab-clinics {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--ab-s-4);
}

/* The card is one flex column on an 8px step: the header row and every meta
   row ride the same gap, which is what keeps this section margin-free. */
.ab .ab-clinic {
    display: flex;
    flex-direction: column;
    gap: var(--ab-s-2);
    padding: 18px; /* the FSD card inset — between --ab-s-4 and --ab-s-6, no token */
    border: 1px solid var(--ab-bd);
    border-radius: var(--ab-r-md);
    background: var(--ab-white);
    box-shadow: 0 2px 10px rgba(28, 52, 84, .06); /* token ink, below the card ladder — resting, never elevated */
}

/* Name and pill baseline-justified, as the finder sets them — a centred pill
   floats once the name wraps. */
.ab .ab-clinic__top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--ab-s-3);
}

.ab h3.ab-clinic__name {
    color: var(--ab-ink);
    font-size: 20px; /* between --ab-t-body and --ab-t-subhead — the card's own scale */
    font-weight: 800;
    line-height: 1.25;
    letter-spacing: -.025em;
}

/* Three states, not two — and colour is never the only carrier: every pill
   states its condition in words. A dot is banned by the card spec. */
.ab .ab-clinic__status {
    display: inline-flex;
    align-items: center;
    padding: 4px 10px; /* the finder pill's inset */
    border-radius: var(--ab-r-pill);
    font-size: 10.5px; /* half a step under --ab-t-eyebrow — the pill never outweighs the name */
    font-weight: 800;
    white-space: nowrap;
}

/* Status tints/texts stay literal — the finder's documented trio: the text
   variants exist for AA contrast on the tints; roles map to --ab-ok /
   --ab-soon / --ab-shut. See DESIGN-SYSTEM.md and location-finder.css. */
.ab .ab-clinic__status--open {
    background: #DCF3E7;
    color: #12694C;
}

.ab .ab-clinic__status--soon {
    background: #FBE7D6;
    color: #96430F;
}

.ab .ab-clinic__status--closed {
    background: #ECEFF2;
    color: #5B646E;
}

/* Meta rows. Top-aligned, not centred — an address that wraps keeps its icon
   on the first line. */
.ab .ab-clinic__row {
    display: flex;
    align-items: flex-start;
    gap: 7px; /* the finder's icon seam — tighter than --ab-s-2 so the icon reads as a bullet */
    color: var(--ab-ink2);
    font-size: 12.5px; /* the card's meta size — half a step under --ab-t-small, from the FSD card */
    line-height: 1.45;
}

.ab .ab-clinic__row svg {
    flex: 0 0 auto;
    width: 15px; /* rides the meta line; stroke is currentColor so the tel row tints it */
    height: 15px;
}

.ab .ab-clinic__row a {
    color: inherit;
}

.ab .ab-clinic__row--tel {
    color: var(--ab-b600);
    font-weight: 800;
}

/* 8px column gap + 8px pad = the FSD card's 16px before the actions, without
   a margin. Always a column, always Schedule then Learn more. */
.ab .ab-clinic__actions {
    display: flex;
    flex-direction: column;
    gap: var(--ab-s-2);
    padding-top: var(--ab-s-2);
}

/* =========================================================================
   ability_pills — the location pill grid
   Label and arrow centre together: the pill itself is the inline-flex group,
   so the arrow trails the word instead of riding the pill's far edge.
   ====================================================================== */

.ab.ab-pills,
.ab .ab-pills {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--ab-s-3);
    max-width: 470px; /* the FSD card's grid measure — sits between the layout tokens */
}

.ab .ab-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 9px; /* measured off the FSD pill — off the 8pt scale */
    width: 100%;
    /* min-height, not height: a long label wraps on a phone ("Previous imaging or
       medical records (if available)") and a fixed 52px let the extra lines spill
       past the capsule (Laura's Sep-3 mobile pass). Single-line pills measure the
       same as before; wrapped ones grow their border with them. */
    min-height: 52px;
    padding: 8px 24px;
    border: 2px solid var(--ab-b600);
    border-radius: var(--ab-r-pill);
    background: var(--ab-white);
    color: var(--ab-b600);
    font-family: var(--ab-font);
    font-size: var(--ab-t-button);
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: .06em;
    text-decoration: none;
    text-transform: uppercase;
    transition: background .16s ease, color .16s ease;
}

.ab-pill:hover,
.ab-pill:focus-visible {
    background: var(--ab-b100);
}

.ab-pill:focus-visible {
    outline: 3px solid var(--ab-b700);
    outline-offset: 3px;
}
/* The selected pill fills solid. Equal specificity with the hover rule and after
   it in source, so the page you are on never washes back to the tint under the
   cursor. */
.ab .ab-pill--on {
    background: var(--ab-b600);
    color: var(--ab-white);
}

/* Static pills: a set of short statements, not a set of choices ("What Should I Bring",
   Your First Visit). Vero, Sep 4: outlined like the location grid they read as buttons that
   do nothing. Tinted, borderless, no hover, no hand cursor. Three classes deep so it
   outranks both the outline and the hover rule above. */
.ab .ab-pills--static .ab-pill,
.ab .ab-pills--static .ab-pill:hover {
    background: var(--ab-b100);
    border-color: transparent;
    color: var(--ab-b700);
    cursor: default;
}

.ab .ab-pill__arrow {
    flex: 0 0 auto;
    width: 19px; /* the button's arrow glyph at the button's size */
    height: 19px;
    fill: currentColor;
}

/* Mirror of the rhythm-between-blocks list at the top of this sheet — the 24px
   step below the grid when another block follows it. May be merged into that
   list instead. */
.ab-sec__inner > .ab-pills:not(:last-child) {
    margin-bottom: var(--ab-s-6);
}

/* =========================================================================
   Desktop
   ====================================================================== */

@media (min-width: 480px) {
    .ab-btn--fw-mobile {
        display: inline-flex;
        width: auto;
    }
}

@media (min-width: 768px) {
    .ab-sec--pt-sm { padding-top: var(--ab-s-8); }
    .ab-sec--pb-sm { padding-bottom: var(--ab-s-8); }

    .ab nav.ab-footer__nav {
        grid-template-columns: repeat(4, 1fr);
    }

    /* Two panels side by side. Stacked below this the white one comes first, which is why
       the pair is authored white-then-blue rather than relying on order here. */
    /* auto-fit, so a pair and a trio both lay out correctly: the clinic page uses two
       panels, the Conditions page uses three. The tabs layout never grids — one panel
       shows at a time, at reading measure, centred (the approved desktop translation
       of Conditions version B). */
    .ab-panels:not(.ab-panels--tabs) {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
        gap: 16px;
        align-items: start;
    }

    .ab.ab-booking {
        scroll-margin-top: 208px;
    }

    /* The tabs block runs the full container. It used to stop at --ab-max-narrow, which on a
       1170px band left 410px of empty page to the right of a single column of 55px rows —
       457px tall to show seven labels. */
    .ab.ab-panels--tabs {
        width: 100%;
    }

    /* The segment keeps the narrow measure even though the panel below it does not. Three tabs
       stretched across 1170 are 390px each holding a ten-character label, which reads as a
       control that lost its proportions; at 760 it stays the size the prototype drew. */
    .ab-panels--tabs .ab-cond-seg {
        /* width:100% is load-bearing. .ab-panels is a column flex container, and an auto
           inline margin on a flex item overrides the stretch it would otherwise get, sizing it
           to its content instead — the segment collapsed to 347px, the width of three labels,
           rather than centring at 760. Declaring the width first gives max-width something to
           cap. */
        width: 100%;
        max-width: var(--ab-max-narrow);
        margin-right: auto;
        margin-left: auto;
    }

    /* The rows flow into columns instead of one tall stack. auto-fit rather than a hard 4:
       272px is the widest track that still yields four columns in this band, and it clears the
       longest label in the set: "Pregnancy-Related Discomfort" measures 257px with its own
       padding, leaving 15px. The band's content box is 1090px, NOT the 1170px the section
       reports — that figure includes the inner's padding, and dividing by the wrong one is how
       this first shipped as three columns. Letting the count fall out of the width means a
       narrower band gets 3 or 2 columns instead of a wrapped mess.

       One rule, on the list, instead of one per row. The single column got its dividers from
       each row's own border-top, and in a grid that breaks the moment a row is not full: the
       last row of a five-item panel holds one cell, so the border under it spanned one column
       of four and read as a line someone forgot to finish. A rule that cannot be complete is
       worse than no rule, so the per-row borders come off and the list carries one full-width
       rule under the count line — which is always complete because it belongs to the container,
       not to the cells. The grid's own alignment is what separates the rows now. */
    .ab-panels--tabs .ab-panel__list {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(272px, 1fr));
        gap: 0;
        border-top: 1px solid var(--ab-bd);
    }

    .ab-panels--tabs .ab-panel--blue .ab-panel__list {
        border-top-color: rgba(255, 255, 255, .24);
    }

    /* The arrow rides its own label instead of the cell's right edge. In one full-width column
       space-between reads correctly — label at the start, arrow at the end of the same line. In
       a 272px cell it puts the arrow closer to the NEXT column's label than to its own, so
       "Back Pain -> Sciatica ->" stops saying which arrow belongs to which condition. */
    .ab-panels--tabs .ab-panel__list .ab-panel__row {
        justify-content: flex-start;
        border-top: 0;
    }

    /* Two-up while the third card centres itself on the next row. */
    .ab-stack--grid .ab-stack__cards {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--ab-s-4);
    }

    .ab-deck.is-grid .ab-deck__stage {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    /* auto-FILL, deliberately not auto-fit: a state with one clinic must render
       one card-width card against empty tracks, not a lone card stretched across
       the whole row — the national index has states like that today. */
    .ab.ab-clinics {
        grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    }
}

@media (min-width: 1024px) {
    /* ≥1024 — ability_pills: four across, the mobile measure released */
    .ab.ab-pills,
    .ab .ab-pills {
        grid-template-columns: repeat(4, minmax(0, 1fr));
        max-width: none;
    }

    /* The doctors band is its own section top — the section wrapping it runs pad_top="none"
       so the blue starts flush against the band above. So it climbs the ladder with everything
       else: 32 below 1024, 64 here, which is what makes its top edge match the 64 the section's
       own pad_bottom puts under the dots. It read 24 against 64 before, and the asymmetry was
       visible on the page. */
    .ab-docs__band { padding-top: var(--ab-s-16); }

    .ab-sec--pt-md { padding-top: var(--ab-s-16); }
    .ab-sec--pb-md { padding-bottom: var(--ab-s-16); }
    .ab-sec--pt-lg { padding-top: var(--ab-s-24); }
    .ab-sec--pb-lg { padding-bottom: var(--ab-s-24); }
    .ab-sec--pt-sm { padding-top: var(--ab-s-8); }
    .ab-sec--pb-sm { padding-bottom: var(--ab-s-8); }

    .ab .ab-wm--hero > .ab-mark        { top: -140px; left: auto; right: -80px; width: 640px; }
    .ab .ab-wm--bottom-right > .ab-mark { width: 380px; }

    .ab .ab-quote__text {
        padding: 56px 64px 64px 88px;
        font-size: 26px;
    }

    .ab .ab-body--two {
        column-count: 2;
        column-gap: var(--ab-s-12);
        max-width: none;
    }

    .ab .ab-body--two p {
        break-inside: avoid;
    }

    .ab-quote::before,
    .ab-quote::after {
        font-size: 68px;
    }

    /* Above 1024 the pile becomes a grid. The card CSS is unchanged: the grid is the
       mechanic's own rest state, which is why the replacement costs a media query rather
       than a second implementation. */
    .ab .ab-stack__heading--pin {
        position: static;
        margin: 0 !important;
        padding: 0 0 var(--ab-s-6);
        background: none;
    }

    .ab .ab-stack__heading--pin::after { content: none; }

    .ab-stack__cards {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: var(--ab-s-6);
        padding-top: 0;
    }

    .ab .ab-stack__card + .ab-stack__card { margin-top: 0; }

    /* Drop the upward half of the shadow. Its whole job is to make a card read as sitting ON
       the one behind it while the pile is stacking; side by side in a grid there is nothing
       behind, so it just smudges a dark band above each card. */
    .ab .ab-stack__card {
        box-shadow: var(--ab-shadow-card);
    }

    .ab .ab-stack__card--photo .ab-stack__cont { width: 100%; }

    .ab-deck.is-grid .ab-deck__stage {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: var(--ab-s-6);
    }

    .ab-deck.is-grid .ab-deck__card {
        position: relative;
        width: 100%;
        transform: none;
        opacity: 1;
        filter: none;
        /* The drag-deck base rule holds transform/filter/opacity layers on every card;
           in a static grid that is nine compositor layers doing nothing. */
        will-change: auto;
    }

    .ab-deck.is-grid .ab-deck__card + .ab-deck__card { margin-top: 0; }
    .ab-deck.is-grid .ab-deck__dots { display: none; }

    /* Copy left, photograph and plate right. The prototype stacks them because it is
       390px wide; at 1440 a stacked band leaves the right half empty.
       `ab-hero` is the About hero; `ab-split` is the same composition used mid-page (the
       location "Serving…" band), named separately so a rule meant for the hero cannot
       reach a band that merely borrows its layout. */
    .ab-hero .ab-sec__inner,
    .ab-split .ab-sec__inner {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 520px);
        gap: var(--ab-s-16);
        align-items: center;
    }

    .ab-hero .ab-sh,
    .ab-split .ab-sh {
        padding-bottom: 0;
    }

    /* Side by side, the stacking rhythm becomes a vertical offset inside the grid cell. */
    .ab-hero .ab-sec__inner > *,
    .ab-split .ab-sec__inner > * {
        margin-bottom: 0;
    }

    /* The Ability Experience: photograph left, copy right. The statement card keeps its
       squared joint with the photo, so it has to stay in the same grid cell.

       `ab-aside` is the same arrangement offered to any band, and it also accepts a plain
       `ability_media` in the figure column. It is a separate name for the reason `ab-split`
       is separate from `ab-hero`: a rule written for the Experience band must not silently
       reach a band that only borrows its layout.

       Explicit placement, not auto-flow, because these bands carry THREE children — header,
       figure, body. An auto-placed grid (`ab-hero`, `ab-split`) drops the third into row 2. */
    .ab-experience .ab-sec__inner,
    .ab-aside .ab-sec__inner {
        display: grid;
        grid-template-columns: minmax(0, 460px) minmax(0, 1fr);
        /* Column gap only. The card spans far more rows than exist so it stays beside the
           copy whatever the editor puts there, and every one of those surplus rows is empty
           — with a row gap they each contribute their full height, which measured 6 272px
           of blank blue band. The children carry their own vertical rhythm in padding. */
        column-gap: var(--ab-s-16);
        row-gap: 0;
        align-items: start;
        /* The rows are implicit and therefore `auto`, and an auto row ABSORBS the free space a
           row-spanning item creates. The figure spans 99 rows and is taller than the copy
           stack, so that surplus was handed to the ~96 empty rows and came back as dead band:
           86px under the Locations "Sibling Owned & Operated" photograph, measured. min-content
           sizes every row to its own content, so the empty ones are 0 and the grid ends exactly
           where the taller column does.

           Same family as the row-gap trap this block already guards against, and it survives a
           smaller span: reducing 99 to 6 only recovered 25 of the 86px, because the leak is the
           sizing of the rows and not their number. */
        grid-auto-rows: min-content;
    }

    .ab-experience .ab-stmt-wrap,
    .ab-aside .ab-stmt-wrap,
    .ab-aside .ab-media {
        grid-column: 1;
        grid-row: 1 / span 99;
    }

    /* The rhythm-between-blocks rule at the top of this sheet gives a figure 24px of trailing
       space so a paragraph after it does not land flush. In its own grid column there IS
       nothing after it, so those 24px are dead — and when the figure is the taller child, as
       on Conditions' "Care Designed Around You", they extend the whole band by 24px.

       Written with the child combinator on purpose: the rhythm rule is
       `.ab-sec__inner > .ab-stmt-wrap:not(:last-child)`, and :not() carries its argument's
       weight, so it scores (0,3,0). A descendant selector here would score (0,2,0) and lose
       silently. This ties at (0,3,0) and wins on source order.

       Only the figure is zeroed, so a second block in the copy column keeps its rhythm — and
       only above 1024, since below that the band stacks and the 24px are exactly right. */
    .ab-experience .ab-sec__inner > .ab-stmt-wrap,
    .ab-aside .ab-sec__inner > .ab-stmt-wrap,
    .ab-aside .ab-sec__inner > .ab-media {
        margin-bottom: 0;
    }

    .ab-experience .ab-sh,
    .ab-experience .ab-body,
    .ab-experience .ab-closing,
    .ab-aside .ab-sh,
    .ab-aside .ab-body,
    .ab-aside .ab-closing {
        grid-column: 2;
    }

    /* The copy column is not only prose. The Locations "Sibling Owned & Operated" band puts a
       stat row and a button beside its photograph, and anything not named here is auto-placed
       — which drops it into column 1, on top of the figure. Written with the child combinator
       because `.ab-btn` also names buttons nested inside other blocks; grid-column would be
       inert on those, but a selector that reaches them is a selector waiting to be wrong. */
    .ab-aside .ab-sec__inner > .ab-stats,
    .ab-aside .ab-sec__inner > .ab-steps,
    .ab-aside .ab-sec__inner > .ab-btn,
    .ab-aside .ab-sec__inner > .ab-btns,
    .ab-aside .ab-sec__inner > .ab-pills {
        grid-column: 2;
    }

    /* Figure right. The track widths swap with it, so the copy always keeps the wide column
       — otherwise the text is the one squeezed into 460px. Consecutive bands alternate sides
       instead of marching down one edge of the page. Same specificity as the rules above
       (0,2,0), so this wins on source order alone; keep it below them. */
    .ab-aside--flip .ab-sec__inner {
        grid-template-columns: minmax(0, 1fr) minmax(0, 460px);
    }

    .ab-aside--flip .ab-stmt-wrap,
    .ab-aside--flip .ab-media {
        grid-column: 2;
    }

    .ab-aside--flip .ab-sh,
    .ab-aside--flip .ab-body,
    .ab-aside--flip .ab-closing {
        grid-column: 1;
    }

    .ab-aside--flip .ab-sec__inner > .ab-stats,
    .ab-aside--flip .ab-sec__inner > .ab-steps,
    .ab-aside--flip .ab-sec__inner > .ab-btn,
    .ab-aside--flip .ab-sec__inner > .ab-btns,
    .ab-aside--flip .ab-sec__inner > .ab-pills {
        grid-column: 1;
    }

    /* Whatever hangs off the photograph's corner hangs off the OUTER one — the corner facing
     * the page edge, never the one facing the copy.
     *
     * Two things do this and they default to opposite corners: the statement card's plate is
     * right-anchored and ability_media's badge is left-anchored. Left alone, one of the two is
     * always pointing the wrong way. In an ab-aside the figure is in the left column, so the
     * outer corner is the page's left and the plate is the one that has to move; under --flip
     * the figure is on the right, so the outer corner is the page's right and the badge is.
     * A tile pointing inward crowds the column it is trying to introduce, and on the flip it
     * lands under the first line of the heading.
     *
     * Only from 1024, because outer only exists while there are two columns. Below that the
     * figure is the full width, both corners face the same nothing, and each block keeps the
     * corner it was designed with.
     *
     * Mirrored by moving the padding that makes the overhang, never by a negative offset —
     * .ab-home-who spells out why at length and it is the same trap here: the figure is a grid
     * item, and a plate escaping its cell lands on the copy column beside it. The plate needs
     * both properties because its base rule sets `right: 0; left: auto`, and setting one alone
     * anchors it to both edges and stretches it across the photograph.
     *
     * ab-home-who keeps its own unconditional copy of the plate half. It was a decision about
     * that band at every width, taken before this rule existed, and below 1024 this one does
     * not reach; inside the query the two agree exactly. */
    .ab-aside:not(.ab-aside--flip) .ab-stmt-wrap--plate {
        padding-right: 0;
        padding-left: 30px;
    }

    .ab-aside:not(.ab-aside--flip) .ab-stmt-wrap--plate .ab-stmt {
        right: auto;
        left: 0;
    }

    .ab-aside--flip .ab-media--badged {
        padding-right: 20px;
        padding-left: 0;
    }

    .ab-aside--flip .ab-media--badged .ab-cbadge {
        right: 0;
        left: auto;
    }

    /* The photograph runs to the bottom of the last state pill. At 4:3 it is 345px against a
       copy column of ~430, so it stopped a pill and a half short and the band read as two
       stacks of unequal length rather than one row.

       The figure already spans rows 1..99 of column 1, so stretching it is the same as
       saying "as tall as the other column" — the empty rows are min-content and add nothing,
       and row 100, where the plate sits, is outside the span. The frame then has both a
       definite height (flex) and a definite width (stretch), which is what retires the
       aspect-ratio; naming `auto` is belt and braces, and it beats `.ab-media--4-3
       .ab-media__frame` (0,2,0) on its own at (0,3,0). The image inside is already
       object-fit: cover, so the extra height crops rather than distorts. */
    .ab-home-locations .ab-sec__inner > .ab-media {
        /* Two rows, not ab-aside's 99. The span exists so a figure stays beside whatever the
           editor puts in the copy column, but a STRETCHED spanning item distributes its
           min-content height across every track it covers when they are smaller than it — so
           the photograph pushed ~1.1px into each of the 97 empty rows and cleared the pills by
           104px. `min-height: 0` does not fix it: what is distributed is the image's own
           min-content contribution, which a minimum of zero never clamps.

           This band's contents are written by page-sync-pages.php, not by hand, and the copy
           column holds exactly two blocks. Spanning those two means there are no empty rows to
           leak into and the figure's area is exactly the copy's height. It also frees rows 3+,
           so the plate below auto-places at row 3 instead of row 100. */
        grid-row: 1 / span 2;
        align-self: stretch;
    }

    /* WPBakery gives every .wpb_content_element 24px of trailing space. It is the right
       rhythm between two blocks in a column, but here it is the last thing above a plate that
       brings its own 32px, so all it did was drop the photograph 24px past the pill it is
       supposed to line up with. */
    .ab-home-locations .ab-sec__inner > .wpb_raw_html:has(.location-filters--states) {
        margin-bottom: 0;
    }

    .ab-home-locations .ab-media--4-3 .ab-media__frame {
        flex: 1;
        aspect-ratio: auto;
        min-height: 0;
    }

    /* The photograph is taken out of flow so the frame has no intrinsic height of its own.
       Shortening the span alone was not enough: whatever rows the stretched figure covers, it
       contributes the image's min-content height (490px here, against a copy column of 363) and
       the grid grows those rows to fit — the leak just moved from 97 empty rows to the two real
       ones, prising the heading and the pills apart. Absolute inside the frame, which is
       already position: relative and overflow: hidden, the contribution is zero, the rows size
       to the copy, and the stretched frame is what gives the image its height. object-fit:
       cover is inherited from the base rule, so the extra height crops rather than distorts. */
    .ab-home-locations .ab-media--4-3 .ab-media__frame .ab-img {
        position: absolute;
        inset: 0;
    }

    /* A pill is inline-flex, but a grid item is blockified and stretches to its track —
       so a button beside a photograph came out 700px wide. Locations worked around it per
       button with ab-btn--center; :not() leaves that one alone, since a centred button
       does its centring with auto margins and needs the free space justify-self removes. */
    .ab-aside .ab-sec__inner > .ab-btn:not(.ab-btn--center),
    .ab-aside--flip .ab-sec__inner > .ab-btn:not(.ab-btn--center) {
        justify-self: start;
    }

    /* Above 1024 the doctor cards and reviews go wide. Find Us becomes a horizontal card —
       storefront left, details right — rather than stretching a 354px phone card to 1170,
       which leaves a 230px photo running the full width of the page.
       --carousel opts out: a 20+ roster (the home band) keeps the peek track at every
       width — the grid would stack a wall of rows. */
    .ab-docs:not(.ab-docs--carousel) .ab-docs__track {
        display: grid;
        /* The ride-over, which only the carousel had. The band reserves 90px of bottom
           padding for cards to climb into, and the grid never claimed it: on a clinic page
           at this width the cards sat below the band with an empty blue strip above them,
           which reads as a gap rather than as an overlap. 64px is what the carousel achieves
           (its -80px margin against 16px of track head padding); this track has no head
           padding, so it takes the 64 directly — the two layouts must cover the same blue. */
        margin-top: calc(-1 * var(--ab-s-16));
        /* auto-fit with a 360px cap: empty tracks collapse, so two cards sit centred at
           card width instead of splitting the row into 533px halves. */
        grid-template-columns: repeat(auto-fit, minmax(300px, 360px));
        justify-content: center;
        gap: var(--ab-s-6);
        overflow: visible;
        margin-right: 0;
        padding-right: 0;
        padding-left: 0;
        scroll-snap-type: none;
        /* The peek track's bleed and head/foot are the base now; a grid wants neither. */
        align-items: stretch;
        margin-left: 0;
        padding-top: 0;
        padding-bottom: 0;
    }

    /* And a grid card is NOT a scroller. Equalising by stretching is right when two full
       cards share a row, so the resting height goes away — initCardSheets() reads it back,
       finds nothing, and leaves the card at its content height with no control.
       `hidden` and not `visible`: zeroing the resting height is what disarms the card, and
       `visible` additionally switched off the rounding, because a box only clips to its own
       border-radius while it clips at all. The photograph sits flush against the top edge with
       no radius of its own, so its square corners painted straight over the card's 12px ones.
       Nothing needs to escape this box — the card is stretched to the row's height, so its
       content can never be taller than it is. */
    .ab-docs:not(.ab-docs--carousel) .ab-docs__card {
        --ab-docs-rest: 0;

        overflow: hidden;
    }

    .ab .ab-docs:not(.ab-docs--carousel) .ab-docs__dots {
        display: none;
    }

    /* Three to a page, and only three. The track's content box is exactly three cards and
       two gaps wide, so every snap position shows three whole cards — a fourth never
       arrives half-cut. Keeping the side padding equal to the gap is what does it: an
       overflow container clips at its padding box, so the visible area ends precisely
       where the fourth card begins. That same padding is the shadow's room, and the
       negative margins put the cards back on the inner's grid.
       The head and foot are the base rule's, restated because only the sides change here. */
    .ab-docs--carousel .ab-docs__track {
        gap: calc(2 * var(--ab-s-3));
        margin-right: calc(-1 * var(--ab-s-3));
        margin-left: calc(-1 * var(--ab-s-3));
        padding: var(--ab-s-4) var(--ab-s-3) var(--ab-s-6);
        /* Without this the snapport starts at the padding box, so the first card's snap
           position is a padding-width in and the browser parks the track there on load —
           which reads as already scrolled. */
        scroll-padding-left: var(--ab-s-3);
    }

    .ab-docs--carousel .ab-docs__card {
        flex: 0 0 calc((100% - 4 * var(--ab-s-3)) / 3);
    }

    /* The resting height is a function of the card's width, and the card is not the width it was
     * on a phone.
     *
     * 120px is the phone's number. There the card is about 315 wide, the photograph is 4:3 of
     * that — 236 — and 120px of body under it is a card of 356: portrait, and the proportion the
     * reference sheet draws. At 1024 and up the same card is 347 wide, the photograph grows with
     * it to 259, and 120px of body under a 259px photograph is a card of 381 — near enough square,
     * with the body clipped at 120 of the 401 it actually needs. That is the whole bug: a value
     * measured on one card width applied at another.
     *
     * 260 is that proportion carried across rather than a new taste: it puts the body at about the
     * photograph's own height, which is what the phone's 120-against-236 already was. The card
     * lands near 519 and the ragged-bottom rule survives — every card is the same height, the
     * short records end inside it, and only the long bios keep the chevron.
     *
     * The grid path is untouched: it sets --ab-docs-rest to 0 and stretches instead, which is
     * right when two full cards share a row and wrong for a roster of twenty-eight. */
    .ab-docs--carousel .ab-docs__card {
        --ab-docs-rest: 260px;
    }

    /* Above 1024 the featured row is a row: the same card width the roster below it uses, so the
       two read as one system rather than as a wide card over narrow ones. Centred, because two
       cards at a third of the width each would otherwise sit against the left edge with a third
       of the band empty beside them — and the roster below is full, so the asymmetry would land
       as a mistake rather than as emphasis. */
    .ab-docs--carousel .ab-docs__track--lead .ab-docs__card {
        flex: 0 0 calc((100% - 4 * var(--ab-s-3)) / 3);
    }

    /* The gap between the two rows. The roster's own head padding is the shadow's room, not
       spacing, so without this the featured cards would sit on top of the row beneath them. */
    .ab-docs--has-lead .ab-docs__track:not(.ab-docs__track--lead) {
        margin-top: var(--ab-s-6);
    }

    .ab-findus {
        display: grid;
        grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
        max-width: none;
        align-items: stretch;
    }

    .ab .ab-findus__shot {
        height: 100%;
        min-height: 320px;
    }

    .ab-findus__body {
        padding: 28px 32px;
    }

    /* Three blocks side by side inside the wide card; the CTA spans them. */
    .ab-findus__body {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 0 32px;
        align-content: start;
    }

    .ab .ab-findus__block + .ab-findus__block {
        margin-top: 0;
        padding-top: 0;
        border-top: 0;
    }

    .ab .ab-findus__block:nth-child(n + 3) {
        margin-top: 20px;
        padding-top: 20px;
        border-top: 1px solid var(--ab-bd);
        grid-column: 1 / -1;
    }

    .ab .ab-findus__cta {
        grid-column: 1 / -1;
        width: auto;
        justify-self: start;
    }

    .ab-reviews__track {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
        gap: var(--ab-s-6);
        overflow: visible;
        margin-right: 0;
        padding-right: 0;
        scroll-snap-type: none;
    }

    .ab .ab-review {
        max-width: none;
    }

    .ab .ab-reviews__dots {
        display: none;
    }

    /* ---- the live strip — Trustindex, wearing the design system's frame ----
     *
     * Everything from here to the end of this comment block used to be scoped to
     * .ab-videoband, the home page's reel band, for a mechanical reason and not a design one:
     * source="trustindex" returned the plugin's markup bare, so there was no element of ours
     * to key on and the containing band was the only handle left. That class exists on
     * exactly one page, which is why the treatment was the home page's alone while the other
     * twenty-two pages carrying the same block showed the plugin's own layout.
     *
     * ability_reviews now wraps the plugin's output in .ab-reviews--live, so the treatment
     * belongs to the block and travels with it. Placement inside a particular band stays in
     * that band's rules; this is everything that is true of the strip wherever it lands. */
    .ab-reviews--live {
        /* One slot. Four of them are the clear zone and two halves are the fades, so
           10 + 4 x 20 + 10 = 100vw exactly. Every rule below derives from this — the card
           width, the track's inset, and both stops of the mask — because the three only work
           while they agree, and three literals in three places do not stay agreed. */
        --ab-rev-slot-w: 20vw;

        /* Out of the inner's 1170 cap and its gutters, to the band's own edges. A row of
           review cards is a continuous strip: stopping it at the measure makes it read as one
           more block of content, and cutting it at the viewport makes it read as something
           that keeps going — which is what it does.

           The standard escape: the parent's content box is the containing block, so `50%` is
           half of it and `50vw` half the viewport, and the pair centres a 100vw box on screen
           no matter how wide the column under it is. */
        width: 100vw;
        max-width: 100vw;
        margin-right: calc(50% - 50vw);
        margin-left: calc(50% - 50vw);

        /* Fades into the band at both ends rather than being clipped by it. The cards nearest
           the edge are partly out of frame by design — that is what says the strip continues —
           and a hard cut there reads as a rendering fault instead.

           The fade is HALF A CARD wide at each end, not an arbitrary percentage of the
           viewport. That is what makes four cards read as fully present and the fifth and
           sixth as passing through: the eye reads the clear zone as the content and the two
           half-slots as the strip continuing past the frame. Tie it to a percentage instead
           and the fade lands mid-card at one width and mid-gap at another. */
        /* The ramp is SHORT and sits ON the boundary, rather than spanning the whole half-slot.
         *
         * Spanning it was the bug: alpha went from 1 at 90vw to 0 at 100vw, and the fifth card
         * begins at exactly 90vw — so the entire ramp was painted over a card that was supposed
         * to be gone, and it read as a fifth column someone had dimmed. The left looked right
         * only because nothing is parked there at rest.
         *
         * 3vw instead. Opaque right up to the fourth card's edge, then out within a sliver, so
         * what shows of the next card is the hint that the strip continues and nothing more.
         * Both ends carry the same ramp so a card leaving on the left and one arriving on the
         * right move at the same rate — with a long ramp one side and a short one the other,
         * the row appears to slide faster in one direction. */
        --ab-rev-fade: 3vw;
        -webkit-mask-image: linear-gradient(to right, transparent calc(var(--ab-rev-slot-w) / 2 - var(--ab-rev-fade)), #000 calc(var(--ab-rev-slot-w) / 2), #000 calc(100% - var(--ab-rev-slot-w) / 2), transparent calc(100% - var(--ab-rev-slot-w) / 2 + var(--ab-rev-fade)));
        mask-image: linear-gradient(to right, transparent calc(var(--ab-rev-slot-w) / 2 - var(--ab-rev-fade)), #000 calc(var(--ab-rev-slot-w) / 2), #000 calc(100% - var(--ab-rev-slot-w) / 2), transparent calc(100% - var(--ab-rev-slot-w) / 2 + var(--ab-rev-fade)));
    }

    /* FOUR cards at rest, and the half-slot at each end EMPTY.
     *
     * The first attempt set the item to 20% and stopped there, which put five whole cards on
     * screen: the track starts at x=0, so slot one sat squarely under the left fade and read
     * as a fifth card someone had dimmed. Masking a card is not the same as not having one
     * there.
     *
     * So the track is inset by half a slot at each end instead. At rest the fade zones hold
     * nothing at all — they are padding — and the strip reads as exactly four cards. Scroll it
     * and the neighbours travel through those zones, arriving and leaving through the fade,
     * which is the whole point of the treatment.
     *
     * The arithmetic has to close: 10vw + 4 x 20vw + 10vw = 100vw. That is why the item is
     * sized in `vw` and not in `%` — a percentage resolves against the wrapper's CONTENT box,
     * which the padding has just made 80vw wide, so `20%` would quietly become 16vw and put
     * five cards back in the clear zone.
     *
     * Trustindex sizes its cards purely by class — `.ti-col-N .ti-review-item`, a flex
     * percentage, no inline style — and its loader picks N from the container width against
     * `data-review-target-width="300"`. At 100vw that lands on five or six depending on the
     * screen, so the count is pinned here rather than left to the plugin's arithmetic. Two
     * class names deep beats the plugin's one, which is why none of this needs `!important`.
     */
    .ab-reviews--live .ti-widget-container .ti-review-item {
        /* !important, and not as a shortcut. The plugin's own sizing is class-based and a
           three-class selector beat it cleanly — but its loader writes a pixel width INLINE
           on each item after boot, computed from the container against
           `data-review-target-width="300"`, and inline beats any selector. The symptom was
           precise: the track's half-slot padding landed correctly while the cards stayed at
           the plugin's own width, so five and a bit fitted in the clear zone instead of four.
           Same narrow case as the stretch_row padding further down this file — a value
           written by JavaScript is the one thing a stylesheet cannot otherwise reach. */
        flex: 0 0 var(--ab-rev-slot-w) !important;
        width: var(--ab-rev-slot-w) !important;
        max-width: var(--ab-rev-slot-w) !important;
    }

    /* The slider's own viewport. It already carries `overflow: hidden`, and overflow clips at
       the PADDING edge, so a card translated into this padding is still painted — it just
       arrives with the mask over it. That is what makes the arrival read as a fade rather than
       as a card appearing out of nothing at the boundary. */
    .ab-reviews--live .ti-reviews-container-wrapper {
        padding-right: calc(var(--ab-rev-slot-w) / 2);
        padding-left: calc(var(--ab-rev-slot-w) / 2);
    }

    /* The strip loads mid-list, not at its start.
     *
     * With the track flush to the padding, the first paint put card one against the left fade
     * with nothing behind it: the left edge read as the beginning of a list while the right
     * edge read as a strip continuing — the two ends of the same row disagreeing about what
     * they were. Pulling the row half a slot left means the card before the window is already
     * parked under the left fade on load, so both edges say the same thing from the first
     * frame and neither is a special case.
     *
     * On the row rather than the wrapper: the wrapper carries `overflow: hidden` and is the
     * clip box, so moving it would move the window instead of the contents. The slider's own
     * transform composes with this margin, so paging still works from wherever it lands. */
    .ab-reviews--live .ti-reviews-container-wrapper > .ti-review-item:first-child {
        margin-left: calc(var(--ab-rev-slot-w) / -2);
    }

    /* 100vw counts the scrollbar and the viewport does not, so the strip overhangs by its
       width and the page gains a horizontal scrollbar of its own. The band that contains it
       absorbs that, whichever band it is — asked for by :has() rather than by a class threaded
       through PHP, because which band holds a strip is decided by the page composition and not
       by anything the section itself knows.

       `clip` rather than `hidden`: hidden would make the band a scroll container, which
       changes what `position: sticky` and in-page anchors do inside it. */
    .ab-sec:has(.ab-reviews--live) {
        overflow-x: clip;
    }

    /* The numbered sequence reads left-to-right as equal cards across the container —
       the desktop translation of a mobile-only pattern, matching how the doctor and
       review cards go wide. Grid stretch keeps the three cards one height. */
    .ab-steps {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
        gap: var(--ab-s-6);
        max-width: none;
    }

    /* Four steps sit two by two — the auto-fit grid wraps them 3+1, which reads unbalanced. */
    .ab .ab-steps--four {
        grid-template-columns: repeat(2, 1fr);
    }

    .ab-footer {
        padding: 56px var(--ab-gutter) 48px;
    }

    .ab-footer__inner {
        display: grid;
        grid-template-columns: minmax(0, 1.4fr) minmax(0, 3fr);
        gap: 48px;
        align-items: start;
    }

    .ab .ab-footer__cta { width: auto; }
}

/* =========================================================================
   Motion — entrance and hover for the desktop card grids
   Entrance: the house reveal signature (opacity + 12px rise), staggered 60ms
   per sibling arriving in the same observer batch, capped at 5 steps. Armed
   only by JS, only at >=1024, never under reduced motion — with JS off, in
   the editor, or under RM the cards are simply visible.
   ====================================================================== */

@media (min-width: 1024px) {
    .ab-deck.is-grid .ab-deck__card.is-armed,
    .ab .ab-stack__card.is-armed {
        --ab-d: calc(min(var(--i, 0), 5) * 60ms);

        opacity: 0;
        transform: translateY(12px);
        /* box-shadow rides along undelayed so the hover below still animates
           after the one-shot reveal — is-armed never comes off. */
        transition:
            opacity .55s var(--ab-ease-out) var(--ab-d),
            transform .55s var(--ab-ease-out) var(--ab-d),
            box-shadow .2s ease;
    }

    .ab-deck.is-grid .ab-deck__card.is-revealed,
    .ab .ab-stack__card.is-revealed {
        opacity: 1;
        transform: none;
    }
}

/* Hover is light, never movement: these cards are not clickable, so a lift would
   promise a navigation that does not exist. The card answers as material — the
   shadow deepens one step and a photograph catches light. */
@media (hover: hover) and (pointer: fine) and (min-width: 1024px) {
    .ab .ab-stack__card,
    .ab-deck.is-grid .ab-deck__card {
        transition: box-shadow .2s ease;
    }

    /* One step up the ladder: the deck rests shadowless, the stack rests on step 1. */
    .ab-deck.is-grid .ab-deck__card:hover { box-shadow: var(--ab-shadow-card); }
    .ab .ab-stack__card:hover            { box-shadow: var(--ab-shadow-card-hover); }

    /* filter on the media brightens its scrim pseudo with it — the whole surface
       responds, and brightness is not a tint. */
    .ab-deck.is-grid .ab-deck__media { transition: filter .2s ease; }
    .ab-deck.is-grid .ab-deck__card:hover .ab-deck__media { filter: brightness(1.04); }
}

/* =========================================================================
   WPBakery editor parity
   The frontend editor wraps every child element in a .vc_element div, which
   becomes the grid child in place of the card: the wrapper stretches to the
   row, the card inside keeps auto height, and three equal cards render three
   heights. Flex passes the stretch through. The front end has no wrappers,
   so these selectors can never match there.
   ====================================================================== */

/* (0,3,0): the editor iframe ships `.compose-mode .vc_element { display: block }`
   at (0,2,0) after this sheet, so the house `.ab` prefix is load-bearing here. */
.ab .ab-stack__cards > .vc_element,
.ab .ab-deck__stage > .vc_element,
.ab .ab-reviews__track > .vc_element,
.ab .ab-btns > .vc_element {
    display: flex;
}

.ab .ab-stack__cards > .vc_element > .ab-stack__card,
.ab .ab-deck__stage > .vc_element > .ab-deck__card,
.ab .ab-reviews__track > .vc_element > .ab-review {
    width: 100%;
}

/* The wrapper is the flex/grid child now, so it carries the card's carousel
   basis; the card inside stretches to fill it. */
.ab .ab-reviews__track > .vc_element {
    flex: 0 0 min(74%, 330px);
    scroll-snap-align: start;
}

.ab .ab-reviews__track > .vc_element > .ab-review {
    flex: 1 1 auto;
    max-width: none;
}

/* Mirror of the rhythm-between-blocks rule at the top of this sheet: the wrapper
   is the child now, so it carries the 24px step, with the same :last-child
   exemption. */
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-quote),
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-media),
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-stmt-wrap),
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-stack),
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-stats),
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-btns),
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-clinics),
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-pills),
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-deck) {
    margin-bottom: var(--ab-s-6);
}

/* Same split as above, wrapped: panels and steps take 32. */
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-panels),
.ab-sec__inner > .vc_element:not(:last-child):has(> .ab-steps) {
    margin-bottom: var(--ab-s-8);
}

/* Clinic cards: the wrapper becomes the grid child; the card fills it. */
.ab .ab-clinics > .vc_element {
    display: flex;
}

.ab .ab-clinics > .vc_element > .ab-clinic {
    width: 100%;
}

/* The Ability Experience places its children explicitly by class; auto-placed
   wrappers drop the body copy under the photograph. Route each wrapper by what
   it carries. (.ab-experience is the .ab root itself, hence no .ab prefix.)
   Inert on the front end and below 1024 — no wrappers, or no grid. */
.ab-experience .ab-sec__inner > .vc_element:has(> .ab-stmt-wrap) {
    grid-column: 1;
    grid-row: 1 / span 99;
}

.ab-experience .ab-sec__inner > .vc_element:has(> .ab-sh),
.ab-experience .ab-sec__inner > .vc_element:has(> .ab-body),
.ab-experience .ab-sec__inner > .vc_element:has(> .ab-closing) {
    grid-column: 2;
}

/* -------------------------------------------------------------------------
 * Location switcher — the bottom sheet that moves a visitor between clinics.
 *
 * Every selector carries the .ab scope. The design system's own resets are
 * element-qualified (.ab p, .ab h3), so a bare class loses to them and the
 * sheet renders as body copy in the theme's font.
 *
 * The spec's three timings live here and nowhere else: the scrim fades at
 * 260ms while the sheet rises at 340ms, the push travels 100% of the width,
 * and the height animates only once the push has landed. Sequenced on purpose
 * — run together, the resize reads as a stutter rather than as motion.
 * ---------------------------------------------------------------------- */
.ab .ab-lsw,
.ab-lsw { position: relative; }

/* The trigger is a crumb trail in a pill, not a labelled box: the spec is explicit that
   this control replaces the breadcrumb, so it has to read as one. Measured off the spec —
   52px tall, 1.5px hairline, pill radius, 18px in on the text side and 8px on the chevron's,
   which is a filled 34px disc rather than a bare glyph. */
.ab .ab-lsw__trigger,
.ab-lsw__trigger {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    width: 100%; height: 52px; padding: 0 8px 0 18px;
    background: var(--ab-white);
    border: 1.5px solid var(--ab-bd); border-radius: var(--ab-r-pill);
    font-family: var(--ab-font); text-align: left; cursor: pointer;
    transition: border-color .15s var(--ab-ease-out);
}
.ab .ab-lsw__trigger:hover { border-color: var(--ab-b100); }
.ab .ab-lsw__trigger:focus-visible { outline: 2px solid var(--ab-b600); outline-offset: 2px; }
.ab .ab-lsw__crumb {
    display: flex; align-items: center; gap: 6px; min-width: 0;
    font-size: 13.5px; font-weight: 400; color: var(--ab-ink2); text-transform: none;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ab .ab-lsw__crumb > span,
.ab .ab-lsw__crumb > b { overflow: hidden; text-overflow: ellipsis; }
.ab .ab-lsw__sep { color: var(--ab-ink3); flex: 0 0 auto; }
/* Same neutralisation as .ab-swash above: theme.scss:391 sets b, strong to 700 !important,
   so the spec's 800 on the current segment can only be reached the same way. */
.ab b.ab-lsw__here { font-weight: 800 !important; color: var(--ab-ink); }
.ab .ab-lsw__chv {
    display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
    width: 34px; height: 34px; border-radius: 50%;
    background: var(--ab-b600); color: var(--ab-white);
    transition: transform .18s var(--ab-ease-out);
}
.ab .ab-lsw__chv svg { width: 14px; height: 10px; display: block; fill: currentColor; }
.ab-lsw[data-open] .ab-lsw__chv { transform: rotate(180deg); }

.ab.ab-lsw__scrim,
.ab-lsw__scrim {
    position: fixed; inset: 0; z-index: 1200;
    background: rgba(12, 26, 44, .55); backdrop-filter: blur(1.5px);
    opacity: 0; transition: opacity 260ms var(--ab-ease-out);
}
.ab-lsw__scrim[hidden] { display: none; }
.ab-lsw__scrim[data-open] { opacity: 1; }

.ab.ab-lsw__sheet,
.ab-lsw__sheet {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 1201;
    background: var(--ab-white); color: var(--ab-ink);
    border-radius: 22px 22px 0 0; padding: 10px 18px 12px;
    box-shadow: 0 -14px 44px rgba(10, 26, 48, .34);
    transform: translateY(100%); transition: transform 340ms var(--ab-ease-out);
    max-height: 86vh; overflow: hidden;
}
.ab-lsw__sheet[hidden] { display: none; }
.ab-lsw__sheet[data-open] { transform: translateY(0); }
.ab-lsw__sheet.ab-lsw--sizing { transition: transform 340ms var(--ab-ease-out), height 340ms var(--ab-ease-out); }
@media (min-width: 640px) {
    .ab.ab-lsw__sheet,
    .ab-lsw__sheet { left: 50%; right: auto; width: min(420px, calc(100vw - 36px)); margin-left: -210px; }
}

.ab .ab-lsw__grip { width: 42px; height: 5px; border-radius: var(--ab-r-pill); background: #D6DCE2; margin: 0 auto 16px; }

/* flex-start, not the default stretch: stretched levels report each other's height,
   so the sheet measured level 1 as tall as level 0 and never shrank on the push. */
.ab .ab-lsw__track { display: flex; align-items: flex-start; width: 200%; transition: transform 340ms var(--ab-ease-out); }
/* The level scrolls, the sheet does not. Without this the sheet clipped at 86vh with
   overflow:hidden and everything past the fold — including the row that pushes to level 1 —
   became unreachable on a short window. It was reachable from script, which is exactly why
   testing it that way missed it. */
.ab .ab-lsw__level { width: 50%; flex: 0 0 50%; overflow-y: auto; overscroll-behavior: contain; }
.ab .ab-lsw__level[aria-hidden="true"] { visibility: hidden; }
.ab-lsw__sheet[data-level="1"] .ab-lsw__track { transform: translateX(-50%); }

.ab .ab-lsw__head { display: flex; align-items: center; gap: 10px; }
.ab .ab-lsw__back {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; flex: 0 0 auto;
    border: 0; border-radius: 50%; background: var(--ab-b100); color: var(--ab-b600); cursor: pointer;
}
.ab .ab-lsw__back svg { width: 10px; height: 13px; }
.ab .ab-lsw__title { font-size: 22px; font-weight: 800; letter-spacing: -.028em; line-height: 1.1; margin: 0 0 3px; color: var(--ab-ink); text-transform: none; }
.ab .ab-lsw__sub { font-size: 13px; line-height: 1.5; color: var(--ab-ink2); margin: 0 0 14px; text-transform: none; }
.ab .ab-lsw__group {
    font-size: 11px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
    color: var(--ab-b600); margin: 18px 0 2px;
}
.ab .ab-lsw__rule { height: 1px; background: var(--ab-bd); margin: 16px 0 0; }
.ab .ab-lsw__all { margin-top: 12px; }

.ab .ab-lsw__opt {
    display: flex; align-items: center; justify-content: space-between; gap: var(--ab-s-3);
    width: calc(100% + 12px); margin: 0 -6px; padding: 13px 6px;
    border: 0; border-bottom: 1px solid var(--ab-bd); border-radius: 8px;
    background: none; color: inherit; font-family: var(--ab-font); text-align: left; text-decoration: none;
    cursor: pointer; transition: background .15s var(--ab-ease-out);
}
.ab .ab-lsw__opt:hover { background: var(--ab-b50); }
.ab .ab-lsw__opt:last-of-type { border-bottom: none; }
.ab .ab-lsw__opt-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ab .ab-lsw__opt-name { font-size: 16px; font-weight: 800; letter-spacing: -.02em; line-height: 1.25; color: var(--ab-ink); text-transform: none; }
.ab .ab-lsw__opt-meta { font-size: 11px; line-height: 1.3; color: var(--ab-ink2); display: flex; align-items: center; gap: 6px; text-transform: none; }
.ab .ab-lsw__opt.is-current { background: var(--ab-b100); }
.ab .ab-lsw__opt.is-current .ab-lsw__opt-name { color: var(--ab-b700); }
.ab .ab-lsw__opt.is-current .ab-lsw__opt-meta { color: var(--ab-b700); opacity: .85; }

.ab .ab-lsw__dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: var(--ab-shut); }
.ab .ab-lsw__dot--open { background: var(--ab-ok); }
.ab .ab-lsw__dot--soon { background: var(--ab-soon); }
.ab .ab-lsw__dot--closed { background: var(--ab-shut); }

.ab .ab-lsw__dist {
    font-size: 13px; font-weight: 700; color: var(--ab-ink2);
    white-space: nowrap; font-variant-numeric: tabular-nums; flex: 0 0 auto;
}
.ab .ab-lsw__tick {
    width: 21px; height: 21px; border-radius: 50%; flex: 0 0 auto;
    background: var(--ab-b600); color: var(--ab-white);
    display: flex; align-items: center; justify-content: center;
}
.ab .ab-lsw__tick svg { width: 12px; height: 12px; }
.ab .ab-lsw__go {
    width: 26px; height: 26px; border-radius: 50%; flex: 0 0 auto;
    background: var(--ab-b100); color: var(--ab-b600);
    display: flex; align-items: center; justify-content: center;
}
.ab .ab-lsw__go svg { width: 10px; height: 13px; display: block; fill: currentColor; }

@media (prefers-reduced-motion: reduce) {
    .ab-lsw__scrim, .ab-lsw__sheet, .ab .ab-lsw__track { transition: none; }
}

/* -------------------------------------------------------------------------
 * ability_media_card — a photograph, the case for it, and one action.
 *
 * The aqua shelf is the component, not a flourish: the card sits proud of a
 * panel behind it, which is what keeps a saturated blue block from reading as
 * a hole in the page. Stacked on a phone, side by side from 1024 up — the
 * reference only draws the phone, and a photograph above its own copy is the
 * only stacking order that keeps them a pair.
 * ---------------------------------------------------------------------- */
.ab .ab-mcard { position: relative; }

.ab .ab-mcard--shelf {
    background: var(--ab-aqua);
    border-radius: var(--ab-r-md);
    padding-top: 8px;
}

.ab .ab-mcard__card {
    overflow: hidden;
    background: var(--ab-b700);
    color: var(--ab-white);
    border-radius: var(--ab-r-md);
}
.ab .ab-mcard--shelf .ab-mcard__card { border-top-left-radius: 0; border-top-right-radius: 0; }

.ab .ab-mcard__media { display: block; }
.ab .ab-mcard__media .ab-img { display: block; width: 100%; height: 100%; object-fit: cover; aspect-ratio: 3 / 2; }

.ab .ab-mcard__body { padding: 20px; }
.ab .ab-mcard__heading {
    font-size: 28px; font-weight: 800; line-height: 1.05; letter-spacing: -.032em;
    color: var(--ab-white); margin: 0 0 16px; text-wrap: balance; text-transform: none;
}
.ab .ab-mcard__copy { font-size: 16px; line-height: 1.6; color: var(--ab-white); margin: 0 0 14px; text-transform: none; }
.ab .ab-mcard__copy p { margin: 0 0 10px; color: inherit; }
.ab .ab-mcard__copy p:last-child { margin-bottom: 0; }

@media (min-width: 1024px) {
    /* The photograph takes the larger half: it is the reason the card exists. */
    .ab .ab-mcard__card { display: grid; grid-template-columns: 1.15fr 1fr; align-items: stretch; }
    .ab .ab-mcard--right .ab-mcard__media { order: 2; }
    /* Keep a definite ratio on desktop too. Left to grow to the file's natural height the
       photograph drove the whole card and the copy floated a screen below the fold. */
    .ab .ab-mcard__media { aspect-ratio: 3 / 2; }
    .ab .ab-mcard__media .ab-img { height: 100%; }
    .ab .ab-mcard__body { display: flex; flex-direction: column; justify-content: center; padding: 40px; }
    .ab .ab-mcard__cta { align-self: flex-start; width: auto; }
}

/* -------------------------------------------------------------------------
 * ability_header_preview — the real header, framed at two widths.
 *
 * The mobile pane is a fixed 390px viewport, so the frame is scaled rather
 * than stretched: a header shown at the wrong width documents a layout that
 * does not exist. The desktop pane fills its column and reflows with it.
 * ---------------------------------------------------------------------- */
.ab .ab-hdrp { display: grid; gap: var(--ab-s-6); }
@media (min-width: 1024px) {
    .ab .ab-hdrp { grid-template-columns: 1fr 390px; align-items: start; }
}

.ab .ab-hdrp__pane { margin: 0; min-width: 0; }
.ab .ab-hdrp__label {
    font-size: 11px; font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
    color: var(--ab-b600); margin: 0 0 var(--ab-s-2);
}
.ab .ab-hdrp__frame {
    position: relative; overflow: hidden;
    border: 1px solid var(--ab-bd); border-radius: var(--ab-r-md);
    background: var(--ab-white);
}
.ab .ab-hdrp__frame iframe { display: block; width: 100%; height: 100%; border: 0; }
/* The desktop pane renders at a real desktop width and is then scaled down to fit its
   column. Letting it render at the column's own width put the header into its burger
   state, so the pane labelled Desktop was documenting the mobile layout. */
.ab .ab-hdrp__pane--desktop .ab-hdrp__frame iframe {
    width: var(--ab-hdrp-w, 1440px);
    height: var(--ab-hdrp-h, 190px);
    transform: scale(var(--ab-hdrp-scale, 1));
    transform-origin: top left;
}
/* A 390px-wide frame is the phone viewport itself, not a squeezed desktop one. */
.ab .ab-hdrp__pane--mobile .ab-hdrp__frame { width: 390px; max-width: 100%; }
.ab .ab-hdrp__note {
    font-size: 12px; line-height: 1.5; color: var(--ab-ink2);
    margin: var(--ab-s-2) 0 0; text-transform: none;
}

/* ==========================================================================
   Education article body — single.php's reading column.

   NOT inside .ab on purpose: the reset flattens free-form article HTML
   (headings inherit font-size, lists lose bullets). This column styles the
   classic post markup against the same tokens instead.
   ========================================================================== */

.ab-post {
    background: var(--ab-white, #fff);
}

.ab-post-body {
    max-width: 760px;
    margin-inline: auto;
    padding: 48px 18px 56px;
    color: var(--ab-ink, #333);
    font-family: var(--ab-font, 'Inter', sans-serif);
    font-size: 17px;
    line-height: 1.7;
}

.ab-post-body h2,
.ab-post-body h3 {
    color: var(--ab-ink, #333);
    letter-spacing: -0.02em;
    line-height: 1.2;
}

.ab-post-body h2 { font-size: 28px; margin: 40px 0 16px; }
.ab-post-body h3 { font-size: 21px; margin: 32px 0 12px; }
.ab-post-body p { margin: 0 0 18px; }

.ab-post-body img {
    max-width: 100%;
    height: auto;
    border-radius: var(--ab-r-lg, 16px);
    margin: 8px 0 16px;
}

.ab-post-body ul,
.ab-post-body ol {
    margin: 0 0 18px;
    padding-left: 24px;
}

.ab-post-body li { margin: 6px 0; }

.ab-post-body a { color: var(--ab-blue-600, #23507C); }

.ab-post-body iframe {
    max-width: 100%;
    border-radius: var(--ab-r-lg, 16px);
}

/* The YouTube embeds came over from Squarespace as 200x113 oEmbed stubs; the
   attributes stay (the source is content), the rendering fills the column. */
.ab-post-body iframe[src*="youtube"] {
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
}

/* The Squarespace "Schedule" button at the foot of an imported article. The
   paragraph carries .ab so the DS button rules and tokens reach the link. */
.ab-post-body .ab-post-cta {
    margin: 28px 0;
}

.ab-post-body blockquote {
    margin: 24px 0;
    padding: 4px 0 4px 20px;
    border-left: 3px solid var(--ab-aqua, #A1E2E0);
    font-style: italic;
}

/* =========================================================================
   Contact Form 7 inside DS bands. The plugin ships unstyled controls; inside
   an .ab-sec they dress as DS controls so a form band reads as one system.
   Field width caps at a comfortable line, matching the reading measure.
   ====================================================================== */

.ab-sec .wpcf7 {
    max-width: 720px;
    font-family: var(--ab-font);
}

/* A centred band centres its copy, and a form is not copy.
 *
 * .ab-sec--center sets text-align on the band, which inherits straight into the form: every label
 * ended up centred over a field whose own box stayed left, so "Your Name" floated above the middle
 * of an input that started at the band's left edge. Two different alignments in one control.
 *
 * The block centres, the contents do not. margin-inline: auto puts the 720px form in the middle of
 * the band — which is what centring the band was asking for — and text-align: left puts every label
 * back on the left edge of the field it names, where a label belongs. The submit stays on that same
 * left edge with the fields: it is the end of the column, not a second centred thing beside it. */
.ab-sec--center .wpcf7 {
    margin-inline: auto;
    text-align: left;
}

.ab-sec .wpcf7 form > p {
    margin: 0 0 var(--ab-s-4);
}

.ab-sec .wpcf7 label {
    display: block;
    color: var(--ab-ink);
    font-size: 15px;
    font-weight: 600;
}

.ab-sec .wpcf7 .wpcf7-form-control-wrap {
    display: block;
    margin-top: var(--ab-s-2);
}

.ab-sec .wpcf7 input[type="text"],
.ab-sec .wpcf7 input[type="email"],
.ab-sec .wpcf7 input[type="tel"],
.ab-sec .wpcf7 input[type="url"],
.ab-sec .wpcf7 input[type="number"],
.ab-sec .wpcf7 input[type="date"],
.ab-sec .wpcf7 select,
.ab-sec .wpcf7 textarea {
    width: 100%;
    padding: 12px 16px;
    border: 1px solid var(--ab-bd);
    border-radius: var(--ab-r-md);
    background: var(--ab-white);
    color: var(--ab-ink);
    font: inherit;
    font-size: 16px;
    line-height: 1.4;
    transition: border-color .16s ease, box-shadow .16s ease;
}

.ab-sec .wpcf7 textarea {
    min-height: 140px;
    resize: vertical;
}

.ab-sec .wpcf7 input:focus,
.ab-sec .wpcf7 select:focus,
.ab-sec .wpcf7 textarea:focus {
    border-color: var(--ab-blue-500);
    box-shadow: 0 0 0 3px var(--ab-b100);
    outline: none;
}

.ab-sec .wpcf7 input[type="file"] {
    color: var(--ab-ink2);
    font-family: var(--ab-font);
    font-size: 15px;
}

.ab-sec .wpcf7 input[type="file"]::file-selector-button {
    margin-right: var(--ab-s-3);
    padding: 10px 18px;
    border: 2px solid var(--ab-b600);
    border-radius: var(--ab-r-pill);
    background: transparent;
    color: var(--ab-b600);
    font-family: var(--ab-font);
    font-size: var(--ab-t-button);
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background .16s ease, color .16s ease;
}

.ab-sec .wpcf7 input[type="file"]::file-selector-button:hover {
    background: var(--ab-b600);
    color: var(--ab-white);
}

.ab-sec .wpcf7 input[type="submit"] {
    height: 52px;
    padding: 0 26px;
    border: 2px solid transparent;
    border-radius: var(--ab-r-pill);
    background: var(--ab-b600);
    color: var(--ab-white);
    font-family: var(--ab-font);
    font-size: var(--ab-t-button);
    font-weight: 700;
    letter-spacing: .06em;
    line-height: 1;
    text-transform: uppercase;
    cursor: pointer;
    transition: background .16s ease;
}

.ab-sec .wpcf7 input[type="submit"]:hover,
.ab-sec .wpcf7 input[type="submit"]:focus-visible {
    background: var(--ab-b700);
}

.ab-sec .wpcf7 .wpcf7-spinner {
    margin-left: var(--ab-s-3);
}

.ab-sec .wpcf7 .wpcf7-not-valid-tip {
    margin-top: 4px;
    color: #B3261E;
    font-size: 13px;
}

.ab-sec .wpcf7 .wpcf7-response-output {
    margin: var(--ab-s-4) 0 0;
    padding: 12px 16px;
    border: 1px solid var(--ab-blue-500);
    border-radius: var(--ab-r-md);
    font-size: 15px;
}

/* =========================================================================
   ability_faq_accordion + ability_faq_item

   Moved here from css/theme.css on 2026-09-02. It was registered through vc_map
   like every other block while its styling sat in the theme sheet among the
   WPBakery overrides, which meant the one place an editor looks for how a block
   behaves did not describe it. The class names are unchanged, so no markup moved.
   ====================================================================== */

.faq-accordion {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.faq-accordion__item {
    overflow: hidden;
    border: 1px solid var(--ab-bd);
    border-radius: 10px;
    background: var(--ab-white);
    transition: background .25s ease, border-color .25s ease;
}

.faq-accordion__item.is-open {
    border-color: transparent;
    background: var(--ab-b50);
}

.faq-accordion__item.is-open .faq-accordion__icon svg {
    transform: rotate(180deg);
}

.faq-accordion__item.is-open .faq-accordion__trigger {
    align-items: flex-start;
}

.faq-accordion__trigger {
    display: flex;
    gap: 16px;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 20px 24px;
    border: 0;
    background: transparent;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.faq-accordion__question {
    flex: 1;
    color: var(--ab-ink);
    font-size: 18px; /* no token between body 16 and subhead 22 — deliberate */
    font-weight: 800;
    line-height: 1.3;
}

/* The location switcher's chevron, and the same one in both states.
 *
 * It used to be an aqua-100 tint carrying a blue glyph when closed and Blue 600 carrying a white
 * one when open, so the control read as two different buttons — a closed row looked disabled
 * beside an open one, which is the opposite of what a closed row means. .ab-lsw__chv settled this
 * already: one Blue 600 disc, one white chevron, and the rotation carries the state. The glyph is
 * that block's path copied verbatim rather than a second drawing of the same arrow. */
.faq-accordion__icon {
    display: flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--ab-b600);
    color: var(--ab-white);
}

.faq-accordion__icon svg {
    display: block;
    width: 14px;
    height: 10px;
    fill: currentColor;
    transition: transform .25s ease;
}

/* No padding on the panel — it belongs to the answer. The panel is what collapses, and a box at
   height 0 still paints its padding: 24px of it would leave every closed item wearing a strip of
   dead space, and the animation below would run from 24 to auto instead of from nothing. */
.faq-accordion__panel {
    padding: 0;
}

.faq-accordion__answer {
    padding: 0 24px 24px;
    color: var(--ab-ink);
    font-size: var(--ab-t-body);
    font-weight: 400;
    line-height: 1.6;
}

.faq-accordion__answer p {
    margin: 0 0 1em;
}

.faq-accordion__answer p:last-child {
    margin-bottom: 0;
}

/* The panel opens to its own height, and nothing measures it.
 *
 * The usual accordion reads scrollHeight in JS and writes a pixel height, which is true for
 * exactly one layout: it goes stale on a resize, on the font swapping in, and the first time an
 * editor pastes a longer answer. `interpolate-size: allow-keywords` lets a transition interpolate
 * `0` against `auto`, so the browser measures every frame and no number is cached anywhere — the
 * block works on copy nobody has written yet, which is the only way a design-system block can be
 * handed to an editor.
 *
 * It is set on .faq-accordion and not :root, though :root is the usual advice. The property
 * inherits, so scoping it here reaches every panel and reaches nothing else; switching it on
 * globally would quietly start animating every other `height: auto` transition on the site.
 *
 * @supports, and the fallback is today's behaviour rather than a broken one: without it the
 * [hidden] attribute keeps the UA's `display: none` and the panel snaps exactly as it always did.
 *
 * Hiding moves to `visibility` inside the query, and that part is not a preference.
 * `display: none` cannot be animated from — the height ends up resolving against a box that was
 * never laid out, and the panel opens to 0 — and `content-visibility: hidden` has the same
 * problem for the same reason: measured here, both left the panel stuck at height 0 through the
 * whole transition. `visibility` is the one that works, and it is not a downgrade: hidden content
 * stays out of the accessibility tree and out of the tab order, which is what the attribute was
 * buying. It also transitions natively, flipping to visible at the start of an opening and to
 * hidden at the end of a closing, with no `allow-discrete` needed. Measured: 0 → 4 → 57 → 76 out,
 * 76 → 72 → 19 → 0 back. */
@supports (interpolate-size: allow-keywords) {
    .faq-accordion {
        interpolate-size: allow-keywords;
    }

    .faq-accordion__panel {
        height: auto;
        overflow: hidden;
        visibility: visible;
        /* NOT --ab-ease-out. That is the house entrance, cubic-bezier(.22, 1, .32, 1), and it is
           front-loaded by design: measured on this panel it reached 97% of the open height at
           130ms of a 380ms transition, so the answer arrived in a third of its own duration and
           the rest was spent settling a box that already looked settled. Right for something
           flying in from off screen, wrong for a box growing in place. cubic-bezier(.4, 0, .2, 1)
           crosses halfway at roughly halfway, so the copy below is pushed down at a rate the eye
           can follow — which is the whole reason an accordion animates instead of snapping. */
        transition: height .38s cubic-bezier(.4, 0, .2, 1), visibility .38s;
    }

    /* display restated because the UA's `[hidden] { display: none }` loses to any author rule and
       there is no author rule setting display in the base — but a box that is `display: none` has
       no height to animate, so the attribute has to hide by other means here. */
    .faq-accordion__panel[hidden] {
        display: block;
        height: 0;
        visibility: hidden;
    }

    /* The height IS the motion, so there is nothing to reduce it to but instant. */
    @media (prefers-reduced-motion: reduce) {
        .faq-accordion__panel {
            transition: none;
        }
    }
}

/* The control sits between the subhead and the card, and it owes the same gap to each: with
   only a bottom margin it read as belonging to the card rather than to the pair. */
.ab .ab-panels--tabs:has(.ab-panel--photo) .ab-cond-seg {
    margin-top: var(--ab-s-6);
    margin-bottom: var(--ab-s-6);
}

/* -------------------------------------------------------------------------
 * A panel with a photograph in it.
 *
 * One photograph per panel, never one per row: three pictures carry the hub's twenty
 * conditions, which is the only reason this layout is affordable. With an image the panel
 * stops being a list on a card and becomes the card: the picture takes a third of the
 * width flush to the edge, and the rows split into two columns beside it.
 *
 * These selectors are scoped through .ab-panels--tabs on purpose. The tabs layout strips
 * the row borders, pins the arrow to the label and hides the panel title, all for a list
 * that runs the full width of the card; with a photograph beside it none of that holds,
 * and a plain .ab-panel--photo selector would tie on specificity and lose to source order.
 * ------------------------------------------------------------------------- */

.ab .ab-panel--photo {
    position: relative;
    overflow: hidden;
    /* A hairline, not just the shadow: on a white band the shadow alone is too soft to read as
       an edge, and the picture ends up looking like it is lying on the page. */
    border: 1px solid var(--ab-bd);
    border-radius: var(--ab-r-lg);
    /* The card carries its own ground and shadow: the hub's band is white, so without them
       a white panel on a white section has no edge and the photograph looks like it is
       floating loose on the page. */
    background: var(--ab-white);
    box-shadow: var(--ab-shadow-card);
    /* The band may be centred — the section's own reset — but a two-column list of links
       reads from its left edge, never from the middle. */
    text-align: left;
}

.ab .ab-panel--blue.ab-panel--photo {
    background: var(--ab-b700);
}

/* The title stops being a heading in the flow and becomes the group's eyebrow: the tabs
   layout hides it because the active segment already says the word, but beside a
   photograph the card needs to name itself. */
.ab .ab-panels--tabs.is-live .ab-panel--photo h3.ab-panel__title,
.ab .ab-panel--photo h3.ab-panel__title {
    display: block;
    margin: 0 0 26px;
    color: var(--ab-b600);
    font-size: 20px;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
}

.ab .ab-panel--blue.ab-panel--photo h3.ab-panel__title {
    color: var(--ab-on-dark);
}

.ab .ab-panel--photo .ab-panel__figure {
    margin: 0;
    overflow: hidden;
}

.ab .ab-panel--photo .ab-panel__figure .ab-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* The row carries its line underneath rather than on top, so the last row of each column
   has one too. On top, a border in a two-column grid leaves the last row's line spanning
   one column of two and reading as something someone forgot to finish. */
.ab .ab-panels--tabs .ab-panel--photo .ab-panel__row,
.ab .ab-panel--photo .ab-panel__row {
    justify-content: space-between;
    gap: 16px;
    padding: 18px 0;
    border-top: 0;
    border-bottom: 1px solid var(--ab-bd);
}

.ab .ab-panel--blue.ab-panel--photo .ab-panel__row {
    border-bottom-color: rgba(255, 255, 255, .24);
}

.ab .ab-panel--photo .ab-panel__label {
    font-size: 16px;
    font-weight: 700;
}

/* A row with no page still gets no arrow, but the arrow's width is reserved so the labels
   of one column do not sit at a different place from the other's. */
.ab .ab-panel--photo .ab-panel__row--soft::after {
    content: "";
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
}

/* Blue 600 filled, white arrow. Blue 600 rather than 700 because nothing sits on it but a
   glyph, and a glyph owes 3:1, not the 4.5:1 that disqualifies 600 for copy. */
.ab .ab-panel--photo .ab-panel__go {
    width: 32px;
    height: 32px;
    background: var(--ab-b600);
    color: var(--ab-white);
}

.ab .ab-panel--photo .ab-panel__go svg {
    width: 15px;
    height: 15px;
}

/* The list keeps no rule of its own — every row carries its own line. */
.ab .ab-panels--tabs .ab-panel--photo .ab-panel__list,
.ab .ab-panel--photo .ab-panel__list {
    border-top: 0;
}

.ab .ab-panel--photo .ab-wm--panel {
    z-index: 0;
    opacity: .2;
}

.ab .ab-panel--photo .ab-wm--panel > .ab-mark {
    right: -70px;
    bottom: -60px;
    width: 240px;
    height: auto;
}

/* The eyebrow and the rows ride above the mark. */
.ab .ab-panel--photo h3.ab-panel__title,
.ab .ab-panel--photo .ab-panel__list {
    position: relative;
    z-index: 1;
}

/* Below the split the panel becomes a sheet, the same object the doctor card already is on
   this site: a scrollport of a fixed resting height with the photograph sticky behind it and
   the white list riding up over it, moved by a chevron rather than by a drag. Stacked at full
   width the picture ate half a phone screen before a single condition was readable, and the
   sideways version that replaced it first was the wrong gesture — the rows are a list, and a
   list is read down.

   The travel is a programmatic scroll and the panel stays overflow:hidden, which is the whole
   point of the doctor card's rewrite: a vertical scroller nested inside the page's own steals
   a finger aimed at the page, so the button is the way in. */
@media (max-width: 1023.98px) {
    .ab .ab-panel--photo {
        /* The script arms on this value and on nothing else, so the sheet exists only here. */
        --ab-panel-rest: 260px;
        display: block;
        padding: 0;
        /* A real drag target, unlike the doctor card: overscroll-behavior stops the gesture
           chaining to the page once the list reaches its end, so a finger inside the card
           moves the card and a finger outside it moves the page. */
        overflow-y: auto;
        /* No overscroll containment: at either end of the list the gesture has to go back to
           the page, or the card becomes a trap you have to steer around. */
        -webkit-overflow-scrolling: touch;
        scrollbar-width: none;
    }

    .ab .ab-panel--photo::-webkit-scrollbar {
        display: none;
    }

    /* The last row dissolves into the card's floor instead of being guillotined by it: a cut row
       says the card ends there, a fading one says the list goes on. It is a sticky scrim and not
       a mask on the card, because a mask paints the box-shadow away with everything else and the
       card loses its edge. Tied to the travel, so it closes once there is nothing left to reach. */
    .ab .ab-panel--photo::after {
        content: "";
        position: sticky;
        bottom: 0;
        z-index: 2;
        display: block;
        height: 64px;
        margin-top: -64px;
        background: linear-gradient(to top, var(--ab-white), rgba(255, 255, 255, 0));
        opacity: calc(1 - var(--ab-panel-p, 0));
        pointer-events: none;
    }

    .ab .ab-panel--blue.ab-panel--photo::after {
        background: linear-gradient(to top, var(--ab-b700), rgba(58, 109, 166, 0));
    }

    /* Sticky, so the list travels over it instead of pushing it away, and its height comes
       from the script: a fixed share of what is left of the screen, not a ratio of the width.
       It stays inside the card because the card clips — the picture is part of the object,
       never a slab sitting on top of it. */
    /* The photograph never leaves. It is pinned to the card's ceiling above everything else and
       slides up by exactly as much as it can while keeping a 56px strip in view, so the list
       passes under a band of picture instead of erasing it. The travel is a transform, which
       costs the layout nothing — changing its height instead would move the content it is
       measuring against and the scroll would fight itself. */
    .ab .ab-panel--photo .ab-panel__figure {
        position: sticky;
        top: 0;
        /* A ground under the picture. blur() bleeds alpha at the image's own edges, so the last
           few pixels of the strip were translucent and the row underneath printed straight
           through them — which is the text that looked like it was sitting on the photograph. */
        background: var(--ab-white);
        /* Ten, not three. The rows are anchors and the parent theme positions its own links, so
           a row was making a stacking context that outranked the picture and printed its label
           across the strip. Nothing in this card is meant to paint over the photograph. */
        z-index: 10;
        height: var(--ab-panel-fig, 260px);
        transform: translateY(calc(-1 * (var(--ab-panel-fig, 260px) - 56px) * var(--ab-panel-p, 0)));
    }

    /* The same swell and blur the doctor card uses, on the same fraction: the photograph gives
       way as the list covers it, so what is behind the words stops competing with them. */
    .ab .ab-panel--photo .ab-panel__figure .ab-img {
        /* Six percent over size from the start, so the blurred edge falls outside the clip
           instead of inside the strip. */
        transform: scale(calc(1.06 + var(--ab-panel-p, 0) * .08));
        filter: blur(calc(var(--ab-panel-p, 0) * 8px));
    }

    /* The eyebrow is the leading edge of the sheet. Its shadow deepens with the travel, which
       is what says the white is lying over the picture rather than following it. */
    /* The eyebrow is the leading edge of the sheet, and it stops 56px short of the card's
       ceiling: a strip of the photograph stays in view the whole way down, so the picture is
       still part of the object at the end of the travel instead of a thing that was there
       before. The rows pass under it, which is what any sticky section header does. Its shadow
       deepens with the travel — that is what says the white is lying over the picture. */
    /* The eyebrow scrolls away with the list rather than pinning. Pinned it read as a band the
       rows slid behind and in front of at once — half a condition above it, another one cutting
       across it — and the strip of photograph the card keeps is already what says which group
       this is, because the active segment sits right above it. */
    .ab .ab-panels--tabs.is-live .ab-panel--photo h3.ab-panel__title,
    .ab .ab-panel--photo h3.ab-panel__title {
        position: relative;
        z-index: 1;
        margin: 0;
        /* 48 rather than 16 at the top: the eyebrow needs air between itself and the edge of
           the photograph, and it has to be real space and not a relative offset — nudging it
           down with `top` leaves its box where it was and drops the word onto the first row. */
        padding: 48px 20px 10px;
        /* 16px here, not the library's 11px eyebrow: the desktop card's 20px shouts on a phone,
           but 11 in a card this narrow reads as a caption rather than as the group's name. */
        font-size: 16px;
        font-weight: 800;
        letter-spacing: .06em;
        background: var(--ab-white);
        box-shadow: 0 -10px 24px rgba(20, 40, 70, calc(var(--ab-panel-p, 0) * .18));
    }

    /* The row scale is the library's own 15px, not the 16px the roomy desktop card can afford:
       a phone column is 330px wide and "Pregnancy-Related Discomfort" has to fit on one line. */
    .ab .ab-panel--photo .ab-panel__label {
        font-size: 15px;
    }

    /* The row must not position itself: the parent theme's link rules do, and a positioned row
       lands in the same stacking context as the picture and wins on document order. */
    .ab .ab-panel--photo .ab-panel__row {
        position: static;
        z-index: auto;
    }

    /* The last row's line is the card's own edge; drawing both is drawing it twice. */
    .ab .ab-panel--photo .ab-panel__list > li:last-child .ab-panel__row {
        border-bottom: 0;
    }

    .ab .ab-panel--photo .ab-panel__go {
        width: 28px;
        height: 28px;
    }

    .ab .ab-panel--photo .ab-panel__go svg {
        width: 13px;
        height: 13px;
    }

    .ab .ab-panels--tabs .ab-panel--photo .ab-panel__list,
    .ab .ab-panel--photo .ab-panel__list {
        position: relative;
        z-index: 1;
        display: block;
        /* The tabs layout declares repeat(auto-fit, minmax(272px, 1fr)) from 768px up; left
           standing it splits a phone list into two columns. */
        grid-template-columns: none;
        /* No floor padding: at the end of the travel the last row has to land flush against the
           card's edge, or the list looks like it stopped short of it. */
        padding: 0 20px;
        background: var(--ab-white);
    }

    .ab .ab-panel--blue.ab-panel--photo h3.ab-panel__title,
    .ab .ab-panel--blue.ab-panel--photo .ab-panel__list {
        background: var(--ab-b700);
    }

    /* The mark belongs to the roomy version. Behind the sheet it is never seen. */
    .ab .ab-panel--photo .ab-wm--panel {
        display: none;
    }
}

@media (min-width: 1024px) {
    /* A third for the photograph, two for the content. Two explicit rows — the eyebrow and
       then the list eating the rest — with the picture spanning both. Stacking them in one
       cell instead would depend on one's padding clearing the other, which breaks the
       moment a title wraps to two lines. */
    .ab .ab-panel--photo {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
        grid-template-rows: auto minmax(0, 1fr);
        min-height: 520px;
        padding: 0;
    }

    .ab .ab-panel--photo .ab-panel__figure {
        grid-column: 1;
        grid-row: 1 / 3;
        align-self: stretch;
    }

    .ab .ab-panel--photo h3.ab-panel__title {
        grid-column: 2;
        grid-row: 1;
        padding: 44px 56px 0;
    }

    .ab .ab-panels--tabs .ab-panel--photo .ab-panel__list,
    .ab .ab-panel--photo .ab-panel__list {
        grid-column: 2;
        grid-row: 2;
        align-self: stretch;
        display: grid;
        grid-template-columns: 1fr 1fr;
        column-gap: 48px;
        align-content: center;
        padding: 0 56px 44px;
    }

    /* The row fills its cell's height. A row with an arrow is 32px tall and one without is
       only text; without this the plain row's label sat higher than its neighbour's and the
       two columns drifted apart every other line. */
    .ab .ab-panel--photo .ab-panel__row {
        height: 100%;
    }
}

/* The staggered fade on a tab change. It needs no JavaScript: the tabs script hides panels
   with [hidden], which is display:none, and leaving display:none restarts a CSS animation.
   Ten steps because the longest panel holds eight rows and the eyebrow leads. */
@keyframes ab-panel-photo-in {
    from {
        opacity: 0;
        transform: translateY(10px);
    }

    to {
        opacity: 1;
        transform: none;
    }
}

.ab .ab-panels--tabs.is-live .ab-panel--photo h3.ab-panel__title,
.ab .ab-panels--tabs.is-live .ab-panel--photo .ab-panel__list > li {
    animation: ab-panel-photo-in .42s var(--ab-ease-out) both;
}

.ab .ab-panels--tabs.is-live .ab-panel--photo .ab-panel__list > li:nth-child(1) { animation-delay: .02s; }
.ab .ab-panels--tabs.is-live .ab-panel--photo .ab-panel__list > li:nth-child(2) { animation-delay: .06s; }
.ab .ab-panels--tabs.is-live .ab-panel--photo .ab-panel__list > li:nth-child(3) { animation-delay: .10s; }
.ab .ab-panels--tabs.is-live .ab-panel--photo .ab-panel__list > li:nth-child(4) { animation-delay: .14s; }
.ab .ab-panels--tabs.is-live .ab-panel--photo .ab-panel__list > li:nth-child(5) { animation-delay: .18s; }
.ab .ab-panels--tabs.is-live .ab-panel--photo .ab-panel__list > li:nth-child(6) { animation-delay: .22s; }
.ab .ab-panels--tabs.is-live .ab-panel--photo .ab-panel__list > li:nth-child(7) { animation-delay: .26s; }
.ab .ab-panels--tabs.is-live .ab-panel--photo .ab-panel__list > li:nth-child(8) { animation-delay: .30s; }

@media (prefers-reduced-motion: reduce) {
    .ab .ab-panels--tabs.is-live .ab-panel--photo h3.ab-panel__title,
    .ab .ab-panels--tabs.is-live .ab-panel--photo .ab-panel__list > li {
        animation: none;
    }
}
