/* Gillish Cairnstone — frontend block styles.
 *
 * Loaded on every page that renders Cairnstone blocks (frontend) AND
 * inside the Sandbox iframe (so the editor canvas matches the
 * frontend, M3 territory). Single stylesheet, mobile-first.
 *
 * Each block's selectors live here — no per-block CSS files for
 * the runtime-driven blocks. Conditional Content keeps its own
 * stylesheet (`gc-conditional-block.css`) for the variant chrome.
 *
 * Per the SANDBOX-PIVOT direction: blocks lean on Gutenberg
 * `supports` for color / spacing / typography / border / shadow.
 * Author choices flow through `get_block_wrapper_attributes()`
 * as classes + inline styles, so the only Cairnstone-side CSS rules
 * are the structural ones that don't fit Gutenberg's controls
 * (responsive collapse, layout-essentials).
 */

/* Screen-reader-only utility. Blocks emit `<span class="gc-sr-only">` labels
 * (e.g. "Included" / "Not included" beside a ✓/✗ glyph, the "Feature" corner,
 * the recommended-plan copy, billing-period legends) so assistive tech reads the
 * meaning while sighted users see only the icon. This class hides them VISUALLY
 * but keeps them in the accessibility tree (the standard 1px-clip pattern). It is
 * UNLAYERED + !important so it always wins — a missing definition here leaks every
 * one of those labels as visible text on the page. */
/* A word boundary on BOTH SIDES of every screen-reader-only label.
 *
 * Bought on 2026-08-24, when NVDA read these labels welded to the text beside
 * them: ProsCons said "Good:Drains freely, so frost cannot split it", and
 * PricingCards said "Included3 projects". ProsCons already had a space between
 * the two tags in its markup and it made no difference, which is the tell.
 *
 * `position: absolute` takes this span OUT OF FLOW, so whitespace around it is
 * never rendered and never becomes a separator in the text stream. Generated
 * content INSIDE the span is different: it is part of this element's own text,
 * so it survives to the accessibility tree and does the separating.
 *
 * One rule here rather than a space added at each emit site, because every block
 * that uses this class has the same problem the moment its label sits beside
 * anything - and a per-site fix is one that the next block forgets. Invisible by
 * construction: the element is clipped to 1px.
 *
 * BOTH sides, because a trailing space alone was not enough: the recommended
 * ribbon in ComparisonTable FOLLOWS the price, and with an ::after only NVDA
 * still said "$29/moMOST POPULAR". A label can sit on either side of the text it
 * qualifies, so the boundary has to exist on either side too. */
.gc-sr-only::before,
.gc-sr-only::after {
    content: " " !important;
}

.gc-sr-only {
    position: absolute !important;
    inline-size: 1px !important;
    block-size: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip-path: inset(50%) !important;
    white-space: nowrap !important;
    border: 0 !important;
}

/* ─── Page-level: full-width breakout skew fix ──────────── */
/* A full-width (align:full) block breaks out with `width:100vw` + a
 * `calc(50% - 50vw)` negative margin (WordPress' standard alignfull). `100vw`
 * counts the vertical scrollbar's width, but the content column (the `50%`)
 * does NOT — so on any page tall enough to scroll, a full-width block lands a
 * few px too wide and shifts off-centre, leaving a thin crooked sliver / a
 * stray horizontal scrollbar at the page edge. Reserving the scrollbar gutter
 * on the scroll container makes `100vw` equal the usable width, so the breakout
 * lines up exactly. This is a page-level rule by necessity (the gutter lives on
 * the root scroller, not the block) — it loads with any Cairnstone block, which
 * is exactly when a full-width Cairnstone block could be on the page. */
html {
    scrollbar-gutter: stable;
}

/* ─── Universal CTA discipline ──────────────────────────────
 * Memory-rule retroactive audit (2026-05-13): every CTA-emitting
 * library block must clear two universal CTR-trigger criteria
 * the per-block CSS rules above don't enforce on their own.
 *
 * 1. WCAG 2.2 Target Size Minimum (2.5.8). Padding alone doesn't
 *    guarantee a 44 CSS-px tappable height because em-units scale
 *    with theme font-size and per-variant padding values vary
 *    (Hero 0.7em; Pricing 0.75em; Discount Banner 0.55em; Pro-
 *    locked 0.55em — the smallest cases compute to ~32-36 px on
 *    a default theme). `min-height: 44px` floors the cross-axis
 *    so the tap area meets the criterion universally. Padding
 *    still drives inline-axis sizing; the visual rhythm of each
 *    block doesn't change.
 *
 * 2. WCAG 2.4.7 Focus Visible. Every per-block CTA rule today
 *    collapses `:hover` and `:focus-visible` into the same
 *    `opacity: 0.88` state. Mouse users see the hover dim and
 *    move on; keyboard users land on the button via Tab and see
 *    only the same subtle dim. The keyboard-driven discovery
 *    pathway needs a distinct visual anchor. A 2 px outline at
 *    `currentColor` with 3 px offset lands instantly (no
 *    transition on outline because focus rings should be
 *    immediate, never animated).
 *
 * The selector chain targets the shipped CTA classes by
 * name. Adding a new library block's CTA picks up the discipline
 * for free as long as the class follows the `gc-library-*__cta`
 * naming convention AND the block adds itself to this list.
 * Explicit enumeration over a wildcard prefix because new CSS
 * touching a long selector list is easier to audit than CSS
 * targeting "everything that starts with X".
 */
.gc-library-hero__cta,
.gc-library-discount-banner__cta {
    min-height: 44px;
    box-sizing: border-box;
}
.gc-library-hero__cta:focus-visible,
.gc-library-discount-banner__cta:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 3px;
}
/* The content-column compensation above only makes sense when the
 * badge sits directly in the full-width post flow, where `100%`
 * resolves to the alignfull viewport and the offset lands the badge
 * at content-column-left next to the surrounding text. Inside a
 * Cairnstone Container the badge's containing block is the Container's
 * own (often wider-than-content-size) content box, so the same calc
 * resolves to `(containerWidth - contentSize) / 2` and shoves the
 * badge toward the Container's horizontal centre, away from the
 * left-aligned heading/text beside it (verified locally:
 * a 1000px Container produced margin-left: 177.5px). Inside a
 * Container the badge must align to the Container's content start
 * like every other left block, so the compensation is cancelled. */
.wp-block-gillish-cairnstone-container .gc-library-badge.wp-block-gillish-cairnstone-badge,
.gc-container .gc-library-badge.wp-block-gillish-cairnstone-badge {
    margin-left: 0 !important;
}

/* ─── Color roles ──────────────────────────────────────────
 * Each role defines two custom properties for the wrapper to
 * read via fallback chain. Per-instance overrides land on the
 * SAME custom property NAMES (gc-badge-bg / gc-badge-text);
 * authoring a color just means the override wins via the
 * standard cascade.
 *
 * OKLCH everywhere per DESIGN.md. Contrast pairs hit WCAG AA
 * at small bold text size (≥ 4.5:1) on white surrounding
 * surfaces; mid-grey surroundings remain within AA for the
 * neutral role's bordered variant.
 */
.gc-library-badge--trust-blue {
    --gc-badge-bg-default:   oklch(45% 0.12 245);
    --gc-badge-text-default: oklch(98% 0.005 250);
    /* Brand-coherent link defaults per role — added 2026-05-15
     * for Phase 7b's locked sidebar. Cool-toned role → warm
     * amber link (complementary contrast). Lightened 2026-06-12
     * (v2.3.229) so the link clears WCAG AA on the role bg too.
     * Matches ColorRoles::defaults() in PHP for parity when the
     * runtime CSS hasn't loaded (e.g. theme override timing). */
    --gc-badge-link-default: oklch(92% 0.11 85);
}
.gc-library-badge--brand-ink {
    /* Slug kept for back-compat post-2026-06-10 stone-blue migration
     * (see ColorRoles::defaults() docblock). */
    --gc-badge-bg-default:   #236B8F;
    --gc-badge-text-default: oklch(98% 0.005 250);
    --gc-badge-link-default: oklch(94% 0.09 80);
}
.gc-library-badge--accent {
    /* Background darkened 2026-06-12 (v2.3.229) from oklch(58% …) so
     * the white text clears WCAG AA on it. */
    --gc-badge-bg-default:   oklch(48% 0.17 28);
    --gc-badge-text-default: oklch(98% 0.005 250);
    /* Warm-toned role → light cool link (hue-separated from the
     * white text; a dark link can't clear AA on this mid-darkness
     * surface, so the link is a light tint that passes too). */
    --gc-badge-link-default: oklch(93% 0.06 230);
}
.gc-library-badge--alert {
    --gc-badge-bg-default:   oklch(47% 0.16 12);
    --gc-badge-text-default: oklch(98% 0.005 250);
    --gc-badge-link-default: oklch(93% 0.06 230);
}
.gc-library-badge--neutral {
    /* No filled background; outline + text color both read from
     * `currentColor` so the badge picks up the surrounding text
     * color and stays readable in any context. */
    --gc-badge-bg-default:   transparent;
    --gc-badge-text-default: inherit;
    --gc-badge-link-default: oklch(45% 0.12 245);
    box-shadow: inset 0 0 0 1px color-mix(in oklab, currentColor 35%, transparent);
}
.gc-library-badge--high-contrast {
    /* Near-black surface, near-white text (clears AAA at ~18:1), bright
     * amber link well clear of AA on the dark surface. Added 2026-06-12
     * (v2.3.229) as the sixth built-in role; this static fail-safe was
     * filled in v2.3.231 to match the other five (runtime_css() already
     * emitted it). Matches ColorRoles::defaults(). */
    --gc-badge-bg-default:   oklch(16% 0 0);
    --gc-badge-text-default: oklch(99% 0 0);
    --gc-badge-link-default: oklch(90% 0.15 95);
}

/* Section Divider role defaults (Phase 7b). A divider is a thin
 * line / glyph, not a filled badge, so the role's PRIMARY tone
 * (the role record's bgColor — the colour that DEFINES the role)
 * paints the divider mark via `--gc-divider-text-default`. The
 * role's own textColor (white-on-coloured-bg in the built-ins)
 * would render an invisible white line on a light page, so it is
 * NOT used here. Background stays transparent for every role:
 * dividers don't paint a coloured band unless the author opts in
 * via Custom mode. Neutral maps to `currentColor` (its bgColor is
 * `transparent`, which would zero the mark) so the neutral choice
 * reads as "theme-neutral" rather than "invisible". These static
 * values are the fail-safe; ColorRoles::runtime_css() emits the
 * admin-edited values after this stylesheet so overrides win. */
.gc-library-section-divider--trust-blue {
    --gc-divider-bg-default:   transparent;
    --gc-divider-text-default: oklch(45% 0.12 245);
}
.gc-library-section-divider--brand-ink {
    --gc-divider-bg-default:   transparent;
    --gc-divider-text-default: #236B8F;
}
.gc-library-section-divider--accent {
    /* Mark colour tracks the role bg (darkened 2026-06-12, v2.3.229). */
    --gc-divider-bg-default:   transparent;
    --gc-divider-text-default: oklch(48% 0.17 28);
}
.gc-library-section-divider--alert {
    --gc-divider-bg-default:   transparent;
    --gc-divider-text-default: oklch(47% 0.16 12);
}
.gc-library-section-divider--neutral {
    --gc-divider-bg-default:   transparent;
    --gc-divider-text-default: currentColor;
}
.gc-library-section-divider--high-contrast {
    /* Adaptive mark (2.3.933, same law as the badge's adaptive role):
     * the old fixed near-black mark vanished on dark pages. The mark
     * is now the EXTREME of the page's own polarity, picked from the
     * ambient text colour's lightness: dark ambient ink (light page)
     * saturates to pure black, light ambient ink (dark page) to pure
     * white — always the maximum mark against that page. Engines
     * without relative colour syntax hit IACVT on the colour that
     * reads this var and fall to the inherited ambient ink, itself a
     * sound mark everywhere. runtime_css() deliberately skips this
     * slug so the admin catalogue cannot flatten the adaptivity
     * (structural identity, the badge/Neutral precedent). */
    --gc-divider-bg-default:   transparent;
    --gc-divider-text-default: oklch(from currentColor clamp(0, (l - 0.52) * 1000, 1) 0 0);
}
.gc-library-section-divider--high-contrast * {
    /* The var above re-evaluates wherever it is USED, and the theme
     * poisons part elements' own currentColor (TT25 paints a bare
     * `<hr>` mid-grey: measured 128,128,128, which sat ABOVE the
     * threshold and drew a white mark on a white page). Descendants
     * inherit the wrapper's resolved extreme instead, so every
     * re-evaluation happens against the fasit, not the theme's part
     * colour. */
    color: inherit;
}

/* The comparison-table's OWN colours (the base recommended fill + every look's
 * fills) live in @layer gc-look, declared BELOW the scoped-styling part layers
 * (gc-block/column/row/cell, emitted by StyleAttribute::render_parts). So an
 * author's per-part colour ALWAYS wins over the look's default fill: the look
 * fills what you don't touch, your colour overrides where you do. Structure
 * (borders, type, layout) rides along harmlessly — it sets no property a part
 * colour competes for. The block uses custom classes, so the theme's table
 * styles (scoped to .wp-block-table) do not bleed into this layer. */
@layer gc-look, gc-block, gc-column, gc-row, gc-cell;

/* ─── Shared symbol rating (Shared\Rating) ───────────── */
/* Used by Testimonial and Review Score Box, so it lives here rather than in
 * either block's file (the shared-CSS law). The markup is a decorative,
 * aria-hidden glyph run plus its numeric text; the number is the accessible
 * source of truth, so nothing here may hide it from assistive tech.
 *
 * DELIBERATELY MINIMAL. This sets no display, no colour, no letter-spacing and
 * no margin: the host block owns how its rating sits and how tight the glyphs
 * are. Testimonial already styles `.gc-library-testimonial__rating` with its
 * own spacing and a theme-derived colour, and an opinionated `.gc-rating` here
 * would silently retune a shipped block (an inline-flex wrapper alone would
 * activate a margin-top that is inert on an inline span today).
 *
 * Only the SINGLE fractional glyph is clipped. A half symbol is one filled
 * glyph laid over an empty one and cut to half its inline size. Whole symbols
 * are real repeated characters, so with this file missing a rating still shows
 * the right COUNT — the failure mode of the usual clip-the-whole-row technique
 * is every rating reading full, which is why it is not used here. */
/* ─── Review trio: one type scale and one rhythm ────────── */
/* Score Box, Pros and Cons and Review Box compose into ONE visual object, so
 * they cannot each pick their own sizes. Designed separately they produced four
 * unrelated font sizes and five unrelated spacing values in a single box, which
 * is exactly what it looked like. Declared here rather than in either block
 * because neither owns it: a value used by two blocks lives in the base sheet.
 *
 * ONE size change in the whole trio: the score number. Everything else is body
 * size and separates by WEIGHT. A label that is both smaller and bolder than
 * its neighbour is two signals doing one job.
 *
 * Spacing is three steps of one unit, not five arbitrary numbers. */
.gc-score-box,
.gc-pros-cons {
    --gc-rv-step: 0.4rem;          /* the unit: item padding, tight gaps      */
    --gc-rv-label-gap: 0.6rem;     /* a section label to the thing it labels  */
    /* Between the two lists. Taken from the theme's own block rhythm, because
     * Review Box spaces its parts with that same value: two different numbers
     * doing one job is what made the box read as assembled from strangers. The
     * 1.4rem is only the fallback for a theme that sets no rhythm at all. */
    --gc-rv-block-gap: var( --wp--style--block-gap, 1.4rem );
    --gc-rv-emphasis: 1.5em;       /* the ONE step up, spent on the number    */
}

.gc-rating__symbols {
    /* The glyph run is one unit: never let a score wrap mid-rating. */
    white-space: nowrap;
}
/* The numeric equivalent is a ratio ("4 / 5") and reads left-to-right in every
 * locale. Inside an RTL paragraph the bidi algorithm otherwise reorders the
 * digit-slash-digit run and paints it "5 / 4", turning a four-out-of-five score
 * into five-out-of-four. It applies here as well as on each consumer because
 * Shared\Rating hands the same string to every one of them. */
.gc-rating__text {
    direction: ltr;
    unicode-bidi: isolate;
}
/* An icon rating is drawn from the shared catalogue instead of characters. It
 * is sized in `em` so it tracks the surrounding text exactly as a glyph would,
 * and takes `currentColor` so it inherits whatever colour the host block or the
 * theme sets — the same rule that keeps the character run theme-safe. */
.gc-rating__icon {
    inline-size: 1em;
    block-size: 1em;
    fill: currentColor;
    vertical-align: -0.125em;
}
/* A dashicon is a font glyph, not a path: core sizes it in pixels, so it needs
 * the font-size set as well or the box shrinks around a 20px character. */
.gc-rating__icon--dashicon {
    font-size: 1em;
    line-height: 1;
}
/* A rating never dims an icon to fake the empty state: the empty slot draws
 * the OUTLINE twin of the same shape (a hollow star beside a solid one), so the
 * silhouette carries the score at full contrast. An icon with no outline twin
 * is not a rating symbol and is refused upstream, so no `--empty` opacity
 * modifier exists — the shape does the work, on black and on white alike. */
/* Icons carry their own inter-symbol gap: characters get one from their side
 * bearings, an SVG box does not, so without this the run reads as one shape. */
.gc-rating__symbols--icons {
    display: inline-flex;
    align-items: center;
    gap: 0.15em;
}
.gc-rating__half {
    position: relative;
    display: inline-block;
}
/* An author who picks no empty icon leaves the half symbol with nothing in
 * flow beneath it, so the wrapper collapsed to zero width and the clipped copy
 * — being absolutely positioned — had nothing to be half OF: 4.5 out of 5 drew
 * four medals. Reserving one symbol's width restores the half. Scoped to the
 * icon variant because the character run always has an empty glyph, and a
 * minimum width there would retune a shipped rating. */
.gc-rating__symbols--icons .gc-rating__half {
    min-inline-size: 1em;
}
.gc-rating__half-fill {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
    /* Half the glyph, cut on the inline axis so it mirrors correctly in RTL. */
    inline-size: 50%;
    overflow: hidden;
}


/* ─── Disclosure indicator (shared: Accordion, Tabs folded) ───────────────────
 * The little chevron or plus that tells a reader a row opens. Geometry and glyph
 * live here because two blocks draw the same mark; the OPEN-state rotation stays
 * in each block's own file, because the state selector differs (details[open] for
 * Accordion, aria-expanded="true" for a folded tab).
 *
 * Always aria-hidden in markup: the state it depicts is already carried by the
 * native open attribute or by aria-expanded, so announcing it twice is noise. */
.gc-disclosure-ind {
    flex: 0 0 auto;
    inline-size: 0.6em;
    block-size: 0.6em;
    position: relative;
    transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.gc-disclosure-ind--none { display: none; }
.gc-disclosure-ind--chevron {
    border-inline-end: 2px solid currentColor;
    border-block-end: 2px solid currentColor;
    transform: rotate(45deg);
}
.gc-disclosure-ind--plusminus::before,
.gc-disclosure-ind--plusminus::after {
    content: "";
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 0;
    inline-size: 100%;
    block-size: 2px;
    background: currentColor;
    transform: translateY(-50%);
    transition: opacity 200ms ease-out;
}
.gc-disclosure-ind--plusminus::after { transform: translateY(-50%) rotate(90deg); }

@media (prefers-reduced-motion: reduce) {
    .gc-disclosure-ind,
    .gc-disclosure-ind--plusminus::before,
    .gc-disclosure-ind--plusminus::after {
        transition: none;
    }
}

/* ─── The Cairnstone player (Plyr) held off a theme that paints every button ──
 * Shared by the Video and Audio blocks, so it lives here once (CSS law: a rule
 * two blocks use is never copied into both). Moved from gc-video-embed.css in
 * 2.4.71, unchanged apart from the Audio selectors.
 *
 * Measured on csdevdark (Signify, a classic theme) 2026-09-25: its content
 * `button` rule filled every control red with a white border and 13px 28px
 * padding. Plyr's resting button rule sits at ONE class and loses to it;
 * Plyr's hover, focus and open states out-rank the theme on their own, so only
 * the resting state is restored, with Plyr's own values. */
.gc-video-embed .plyr button.plyr__control,
.gc-video-embed-dialog .plyr button.plyr__control,
.gc-audio-embed .plyr button.plyr__control {
    border: 0;
    box-shadow: none;
    text-transform: none;
    letter-spacing: normal;
    text-shadow: none;
    min-width: 0;
    min-height: 0;
    line-height: inherit;
}
/* Resting surface, only while no Plyr state applies. */
.gc-video-embed .plyr button.plyr__control:not(.plyr__control--overlaid):not(:hover):not(:focus-visible):not([aria-expanded="true"]),
.gc-video-embed-dialog .plyr button.plyr__control:not(.plyr__control--overlaid):not(:hover):not(:focus-visible):not([aria-expanded="true"]),
.gc-audio-embed .plyr button.plyr__control:not(:hover):not(:focus-visible):not([aria-expanded="true"]) {
    background: transparent;
    color: inherit;
}
/* Bar-level buttons only: the settings menu's own items keep Plyr's padding. */
.gc-video-embed .plyr .plyr__controls > button.plyr__control,
.gc-video-embed .plyr .plyr__volume > button.plyr__control,
.gc-video-embed .plyr .plyr__menu > button.plyr__control,
.gc-video-embed-dialog .plyr .plyr__controls > button.plyr__control,
.gc-video-embed-dialog .plyr .plyr__volume > button.plyr__control,
.gc-video-embed-dialog .plyr .plyr__menu > button.plyr__control,
.gc-audio-embed .plyr .plyr__controls > button.plyr__control,
.gc-audio-embed .plyr .plyr__volume > button.plyr__control,
.gc-audio-embed .plyr .plyr__menu > button.plyr__control {
    padding: calc( var( --plyr-control-spacing, 10px ) * 0.7 );
}
