/* Gillish Canvas — frontend block styles.
 *
 * Loaded on every page that renders Canvas 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 Canvas-side CSS rules
 * are the structural ones that don't fit Gutenberg's controls
 * (responsive collapse, layout-essentials).
 */

/* ─── Container ─────────────────────────────────────────── */
/* Container is a Gutenberg-supports-driven primitive — every
 * styling choice (background, padding, border-radius, shadow,
 * typography, layout direction) comes from the author's sidebar
 * controls and is applied via Gutenberg's auto-generated classes
 * + inline styles. The only Canvas-side rule is `box-sizing` so
 * `padding` doesn't push the rendered width out of grid cells. */
.gc-container {
    box-sizing: border-box;
}

/* ─── Repeater + RepeaterRow ──────────────────────────── */
/* Phase 7 foundation primitive. The role-driven semantic markup
 * (role=list on the wrapper, role=listitem on each row) carries
 * the structural meaning; CSS adds the spacing rhythm.
 *
 * Block-gap from the author's sidebar choice (when Repeater
 * eventually supports it) flows through Gutenberg's
 * `--wp--style--block-gap` custom property. The base spacing
 * (16px) shows up only when an enclosing theme hasn't set a
 * block gap, so themes with their own rhythm stay untouched. */
.gc-repeater {
    display: flex;
    flex-direction: column;
    gap: var(--wp--style--block-gap, 16px);
}
.gc-repeater__row {
    /* Row IS the visual; no chrome at rest. Margin reset prevents
     * a child block's default margin from collapsing into the
     * Repeater's gap and breaking the rhythm. */
    margin: 0;
}

/* ─── Columns ──────────────────────────────────────────── */
/* Flex layout — each column sets its own `--gc-col-width` /
 * `flex-basis` via the inline style emitted by render_column().
 * Empty width = auto column that fills remaining row space. The
 * variation picker seeds %% widths at insert; the drag handles
 * between columns adjust those %% pairs as the designer tunes.
 *
 * Below 768px every column collapses to 100% basis regardless of
 * saved width — multi-column rows on phones produce illegible
 * 60-pixel columns. The collapse is opinionated, not configurable:
 * the variation library doesn't ship a "stay narrow on mobile"
 * option because every author who's tried that has regretted it. */
.gc-columns {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    /* Block-gap from the author's sidebar choice flows through
     * Gutenberg's `--wp--style--block-gap` custom property; the
     * flex `gap` falls back to it. */
    gap: var(--wp--style--block-gap, 16px);
}
.gc-column {
    box-sizing: border-box;
    /* `min-width: 0` prevents flex items from overflowing when
     * inner content has long unbreakable strings. Standard
     * flex sizing trick — without this, a long URL inside a
     * 50% column pushes the column to its content width and
     * collapses its sibling. */
    min-width: 0;
    /* Default to "auto column, fill remaining space". Inline
     * style on the wrapper overrides this when the designer
     * picked a variation or set a width manually. */
    flex: 1 1 var(--gc-col-width, 0);
}
@media (max-width: 767px) {
    .gc-column {
        flex-basis: 100%;
    }
}

/* ─── Library: Author Bio Card ──────────────────────────── */
/* v2 layout: attribute-driven block. The render callback emits a
 * fixed BEM structure (__photo, __content, __name, __title, __bio,
 * __socials). No InnerBlocks, no theme.json-paintable inner
 * elements; the block paints its own minimal structural styles
 * + inherits the surrounding theme typography via the wrapper.
 *
 * Pure structural styling. No opinionated colors, no font choices
 * (text inherits from theme), no background. The author-set
 * background / text / border / spacing flow through
 * get_block_wrapper_attributes() so Style sidebar choices apply
 * via Gutenberg's `.has-*` classes. */
.gc-library-author-bio {
    box-sizing: border-box;
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas: "photo content";
    column-gap: 1.5rem;
    align-items: start;
}
/* Photo: circular thumbnail sized via the --gc-photo-size custom
 * property the wrapper sets from the photoSize block attribute.
 * Authors pick 48–320px via the sidebar RangeControl; default
 * 128px. The image is object-fit: cover + border-radius: 50% so
 * an arbitrary photo crops to a clean circle. */
.gc-library-author-bio__photo {
    grid-area: photo;
    width: var(--gc-photo-size, 128px);
    height: var(--gc-photo-size, 128px);
    object-fit: cover;
    border-radius: 50%;
    display: block;
    margin: 0;
}
/* Empty photo placeholder shown in the editor canvas when no
 * photo is selected. Frontend never renders this — the PHP render
 * skips the <img> when authorPhotoUrl is empty. Inherits the
 * same size as the real photo so designers see the final
 * footprint before they upload. */
.gc-library-author-bio__photo--placeholder {
    width: var(--gc-photo-size, 128px);
    height: var(--gc-photo-size, 128px);
    border-radius: 50%;
    background: linear-gradient( 135deg,
        oklch(94% 0.005 250 / 1) 0%,
        oklch(88% 0.005 250 / 1) 100%
    );
}
/* Content column. Use explicit margins on the children instead
 * of a flex `gap` so we can control spacing per-element — the
 * social-icon row needs to sit closer to the bio than the bio
 * sits to the title. */
.gc-library-author-bio__content {
    grid-area: content;
    min-width: 0;
}
.gc-library-author-bio__name {
    margin: 0;
    font-weight: 600;
}
.gc-library-author-bio__title {
    margin: 0.1rem 0 0;
    opacity: 0.7;
    font-size: 0.92em;
}
.gc-library-author-bio__bio {
    margin: 0.6rem 0 0;
}
/* Socials sit close to the bio — 0.5rem instead of the larger
 * block-gap the rest of the content uses. The user's specific
 * note: "knappene for sosiale medier må være litt nærmere
 * teksten" (the social buttons must be a bit closer to the text). */
.gc-library-author-bio__socials {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0.5rem 0 0;
    padding: 0;
    list-style: none;
}
.gc-library-author-bio__social-item {
    margin: 0;
    padding: 0;
}
/* Default (medium) social-button size. Modifier classes below
 * override for small / large. */
.gc-library-author-bio__social-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    color: currentColor;
    text-decoration: none;
    border-radius: 50%;
    opacity: 0.85;
    transition: opacity 120ms ease-out;
}
.gc-library-author-bio__social-link:hover,
.gc-library-author-bio__social-link:focus-visible {
    opacity: 1;
}
.gc-library-author-bio__social-link svg,
.gc-library-author-bio__social-svg svg {
    width: 18px;
    height: 18px;
    display: block;
}
/* The editor's preview wraps the SVG in a __social-svg span so
 * `dangerouslySetInnerHTML` can drop the SVG markup without
 * fighting React's reconciliation on a self-closing element.
 * Frontend's PHP render skips this wrapper — same selectors apply. */
.gc-library-author-bio__social-svg {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 0;
}

/* Size modifiers — wrapper class set by both editor + render. */
.gc-library-author-bio--icons-small .gc-library-author-bio__social-link {
    width: 24px;
    height: 24px;
}
.gc-library-author-bio--icons-small .gc-library-author-bio__social-link svg,
.gc-library-author-bio--icons-small .gc-library-author-bio__social-svg svg {
    width: 14px;
    height: 14px;
}
.gc-library-author-bio--icons-large .gc-library-author-bio__social-link {
    width: 40px;
    height: 40px;
}
.gc-library-author-bio--icons-large .gc-library-author-bio__social-link svg,
.gc-library-author-bio--icons-large .gc-library-author-bio__social-svg svg {
    width: 22px;
    height: 22px;
}
/* Below 480px, the photo stacks above the text. The single
 * narrow-screen breakpoint matches the convention the rest of
 * gc-blocks-frontend uses (Columns collapses at 768px). Author
 * Bio is denser and only needs to break later, when the photo
 * actually gets too cramped next to the text. */
@media (max-width: 479px) {
    .gc-library-author-bio {
        grid-template-columns: 1fr;
        grid-template-areas:
            "photo"
            "content";
        row-gap: 1rem;
    }
    .gc-library-author-bio__photo,
    .gc-library-author-bio__photo--placeholder {
        width: 72px;
        height: 72px;
    }
}

/* ─── Library: Trust Badges ─────────────────────────────── */
/* Horizontal strip of trust signals. Each badge's height comes from
 * the --gc-badge-height custom property the wrapper sets; width
 * scales to the image's aspect ratio. Grayscale-by-default keeps
 * mixed brand-color logos from clashing on the page. */
.gc-library-trust-badges {
    box-sizing: border-box;
}
.gc-library-trust-badges__heading {
    margin: 0 0 0.75rem;
    font-size: 0.85em;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    opacity: 0.7;
    text-align: center;
}
.gc-library-trust-badges--align-start .gc-library-trust-badges__heading       { text-align: left; }
.gc-library-trust-badges--align-end   .gc-library-trust-badges__heading       { text-align: right; }
.gc-library-trust-badges--align-space-between .gc-library-trust-badges__heading { text-align: left; }
.gc-library-trust-badges__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 1.5rem 2rem;
    margin: 0;
    padding: 0;
    list-style: none;
}
.gc-library-trust-badges--align-center        .gc-library-trust-badges__list { justify-content: center; }
.gc-library-trust-badges--align-start         .gc-library-trust-badges__list { justify-content: flex-start; }
.gc-library-trust-badges--align-end           .gc-library-trust-badges__list { justify-content: flex-end; }
.gc-library-trust-badges--align-space-between .gc-library-trust-badges__list { justify-content: space-between; }
.gc-library-trust-badges__item {
    margin: 0;
    padding: 0;
}
.gc-library-trust-badges__link {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
    color: inherit;
}
.gc-library-trust-badges__img {
    display: block;
    height: var(--gc-badge-height, 48px);
    width: auto;
    max-width: 100%;
    object-fit: contain;
    transition: filter 180ms ease-out, opacity 180ms ease-out;
}
.gc-library-trust-badges--grayscale .gc-library-trust-badges__img {
    filter: grayscale(100%);
    opacity: 0.75;
}
.gc-library-trust-badges--grayscale .gc-library-trust-badges__img:hover,
.gc-library-trust-badges--grayscale .gc-library-trust-badges__link:hover .gc-library-trust-badges__img,
.gc-library-trust-badges--grayscale .gc-library-trust-badges__link:focus-visible .gc-library-trust-badges__img {
    filter: grayscale(0);
    opacity: 1;
}
.gc-library-trust-badges__placeholder {
    padding: 1.5rem;
    border: 1px dashed currentColor;
    border-radius: 4px;
    opacity: 0.5;
    text-align: center;
    font-size: 0.9em;
    font-style: italic;
}

/* ─── Library: Testimonial ──────────────────────────────── */
/* Two layout modes: stacked (default) and side-by-side. Side-by-side
 * uses a flex row with a larger photo to the left; collapses back to
 * stacked under 600px viewport. */
.gc-library-testimonial {
    box-sizing: border-box;
    margin: 0;
    /* Locked sidebar pattern (Phase 7b): role-based Color panel.
     * Custom overrides ride `--gc-testimonial-bg/-text/-link`;
     * a named role falls through to `--gc-testimonial-*-default`
     * from ColorRoles::runtime_css(); with neither, theme-neutral. */
    background-color: var( --gc-testimonial-bg,   var( --gc-testimonial-bg-default,   transparent ) );
    color:            var( --gc-testimonial-text, var( --gc-testimonial-text-default, inherit ) );
}
.gc-library-testimonial a {
    color: var( --gc-testimonial-link, var( --gc-testimonial-link-default, currentColor ) );
}
/* Border-opacity override (fires only when the bridge/PHP declares
 * the variable). `!important` beats Gutenberg's inline border-color. */
.gc-library-testimonial[style*="--gc-testimonial-border-color:"] {
    border-color: var( --gc-testimonial-border-color ) !important;
}
.gc-library-testimonial__quote {
    margin: 0;
    font-size: 1.15em;
    line-height: 1.5;
    quotes: "\201C" "\201D" "\2018" "\2019";
}
.gc-library-testimonial__quote p {
    margin: 0;
}
/* Decorative opening quote glyph using ::before. Theme can override
 * by setting `quotes: none` on the wrapper. */
.gc-library-testimonial__quote::before {
    content: open-quote;
    font-size: 2.4em;
    line-height: 0;
    vertical-align: -0.3em;
    margin-right: 0.1em;
    opacity: 0.35;
}
.gc-library-testimonial__quote--placeholder {
    opacity: 0.5;
    font-style: italic;
}
.gc-library-testimonial__caption {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-top: 1rem;
    font-style: normal;
    font-size: 0.95em;
}
.gc-library-testimonial__photo {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    object-fit: cover;
    flex-shrink: 0;
    display: block;
}
.gc-library-testimonial__photo--placeholder {
    background: linear-gradient( 135deg,
        oklch(94% 0.005 250 / 1) 0%,
        oklch(88% 0.005 250 / 1) 100%
    );
}
.gc-library-testimonial__cite {
    display: flex;
    flex-direction: column;
    font-style: normal;
    line-height: 1.3;
}
.gc-library-testimonial__name {
    font-weight: 600;
}
.gc-library-testimonial__title {
    opacity: 0.7;
    font-size: 0.9em;
}
.gc-library-testimonial__rating {
    margin-left: auto;
    color: oklch(75% 0.16 80);
    letter-spacing: 0.1em;
    font-size: 1.05em;
    line-height: 1;
}
/* Side-by-side layout: feature photo on the left, quote + caption
 * stacked on the right. Markup mirrors the layout: PHP renders the
 * photo as a sibling of <blockquote>+<figcaption> in this mode (as
 * a .gc-library-testimonial__photo-feature wrapper). Stacked mode
 * keeps the photo inside the caption row. */
.gc-library-testimonial--layout-side-by-side {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 1.5rem;
    align-items: center;
}
.gc-library-testimonial--layout-side-by-side > .gc-library-testimonial__photo-feature {
    grid-row: 1 / span 2;
    align-self: center;
}
.gc-library-testimonial--layout-side-by-side > .gc-library-testimonial__photo-feature .gc-library-testimonial__photo {
    width: 112px;
    height: 112px;
}
.gc-library-testimonial--layout-side-by-side > .gc-library-testimonial__quote {
    grid-column: 2;
}
.gc-library-testimonial--layout-side-by-side > .gc-library-testimonial__caption {
    grid-column: 2;
    margin-top: 0.6rem;
}
@media (max-width: 599px) {
    .gc-library-testimonial--layout-side-by-side {
        display: block;
    }
    .gc-library-testimonial--layout-side-by-side > .gc-library-testimonial__photo-feature {
        margin-bottom: 0.75rem;
    }
    .gc-library-testimonial--layout-side-by-side > .gc-library-testimonial__photo-feature .gc-library-testimonial__photo {
        width: 72px;
        height: 72px;
    }
}

/* ─── Library: Section Divider ──────────────────────────── */
/* The wrapper exposes two CSS variables:
 *   --gc-divider-width:     percentage (e.g. "80%") — only honoured
 *                           by variants that span horizontally
 *                           (line / wave / label).
 *   --gc-divider-thickness: UNITLESS number, 1–8. Each variant adds
 *                           its own unit:
 *                             - line / label → border-width in px
 *                             - wave         → SVG stroke-width
 *                             - dots / *     → font-size scaler
 *                           Unitless lets the same variable mean
 *                           different things per variant without a
 *                           CSS calc() that mixes px and em. */
.gc-library-section-divider {
    box-sizing: border-box;
    text-align: center;
    /* Phase 7b colour resolution. Every inner mark (the <hr> rule,
     * dots / asterisks glyphs, the wave SVG `stroke="currentColor"`,
     * the label text + flanking ::before/::after rules) inherits
     * `currentColor`, so setting the resolved colour ONCE here
     * cascades to all of them. Precedence: per-instance Custom
     * override (`--gc-divider-text`, inline on the wrapper) →
     * role-default (`--gc-divider-text-default`, from the
     * `--<role>` class below or ColorRoles runtime CSS) →
     * `currentColor` (theme-neutral default, unchanged behaviour
     * for dividers with no colour picked). Background follows the
     * same ladder but defaults to `transparent` so an uncoloured
     * divider paints no band. */
    color: var( --gc-divider-text, var( --gc-divider-text-default, currentColor ) );
    background-color: var( --gc-divider-bg, var( --gc-divider-bg-default, transparent ) );
}
.gc-library-section-divider__rule {
    border: 0;
    /* The <hr> gets its own `color` from the UA / editor / theme
     * stylesheet (Chrome resolves it to grey), so a bare
     * `currentColor` on the border resolves to that grey instead
     * of inheriting the wrapper's resolved divider colour. Read
     * the divider-colour variable chain DIRECTLY here; the final
     * `currentColor` fallback preserves the pre-Phase-7b
     * theme-neutral default for dividers with no role / Custom
     * colour picked (no visual regression on existing content).
     * Sibling marks (__mark span, __wave svg, label ::before /
     * ::after pseudo, __label-text span) don't need this — none
     * of them carry a UA colour override, so they inherit the
     * wrapper `color` correctly. */
    border-top: calc( var(--gc-divider-thickness, 3) * 1px ) solid var( --gc-divider-text, var( --gc-divider-text-default, currentColor ) );
    opacity: var(--gc-divider-opacity, 0.55);
    width: var(--gc-divider-width, 100%);
    margin: 0 auto;
}
/* Dots / asterisks. Thickness scales the glyph size (1 → 1em,
 * 8 → ~1.84em) — the only knob that visibly affects compact glyph
 * dividers. Width does nothing for these styles and the editor
 * hides the slider accordingly. Opacity is the wrapper-level
 * tonal-weight knob; default 0.55 matches the rule variants so a
 * single slider drives every divider style consistently. */
.gc-library-section-divider__mark {
    display: inline-block;
    letter-spacing: 0.4em;
    font-size: calc( 1em + (var(--gc-divider-thickness, 3) - 1) * 0.12em );
    opacity: var(--gc-divider-opacity, 0.55);
}
.gc-library-section-divider__wave {
    display: block;
    width: var(--gc-divider-width, 100%);
    margin: 0 auto;
    color: currentColor;
    opacity: var(--gc-divider-opacity, 0.55);
    /* Amplitude is sized by the SVG's viewBox aspect ratio (120:8),
     * NOT by thickness. Thickness is the STROKE width (set on the
     * path below); the old `height: max(16px, thickness*4px)`
     * conflated the two and squished the wave into a near-flat
     * ripple — 16px tall over a ~645px column is ~8px amplitude on
     * a ~160px wavelength (user-reported 2026-05-15: "veldig lite
     * wave", and correct the moment `height` was disabled in
     * devtools). `aspect-ratio` reproduces that disabled-height
     * behaviour — the SVG sizes to its viewBox proportion —
     * explicitly and cross-browser, so the wave keeps a proper,
     * proportionate undulation at any width regardless of stroke
     * weight. `height: auto` is the initial value; stated for
     * clarity against any inherited height. */
    height: auto;
    aspect-ratio: 120 / 8;
}
.gc-library-section-divider__wave path {
    /* SVG accepts a unitless stroke-width as user units (= px in
     * this context). `vector-effect: non-scaling-stroke` keeps the
     * stroke at the exact width regardless of how
     * `preserveAspectRatio="none"` stretches the path horizontally,
     * so the user-set thickness reads true. */
    stroke-width: var(--gc-divider-thickness, 3);
    vector-effect: non-scaling-stroke;
}
/* Drop-shadow — ALL variants. Canvas's StyleAttribute writes a
 * rectangular `box-shadow` onto the wrapper. A divider is a thin
 * mark inside an invisible full-width band, so a wrapper box-shadow
 * paints a glowing rectangle around the whole block instead of
 * hugging the line / dots / asterisks / wave / label. Two-part fix
 * (PHP + editor add `--has-shadow` whenever a shadow is configured,
 * for every variant):
 *
 *   (a) Suppress the wrapper's rectangular box-shadow.
 *   (b) Re-apply the shadow as `filter: drop-shadow()`. The wrapper
 *       background is transparent, so the filter only renders
 *       around the actual painted mark — the shadow takes the
 *       mark's alpha shape, not the band rectangle. (If the author
 *       sets a custom background, the wrapper IS a solid band and a
 *       band-shaped shadow is then the correct result.)
 *
 * Spread handling: CSS `drop-shadow()` has no spread parameter, so
 * PHP + editor fold spread into the effective blur before writing
 * `--gc-divider-shadow`. Authors dragging the spread slider see a
 * wider glow as expected. */
.gc-library-section-divider--has-shadow {
    box-shadow: none !important;
}
/* Shadow strength decoupled from the mark's tonal fade. The mark
 * carries `opacity: var(--gc-divider-opacity, 0.55)` so plain
 * dividers read subtle by default — but `opacity` fades the mark
 * AND its shadow, so a divider shadow could never be more than
 * ~55% as strong as the Badge box's (which has no such fade).
 * That's the recurring "shadow too weak / wave doesn't show"
 * report (2026-05-15): drop-shadow strength is proportional to
 * source alpha, so a thin stroke at 55% casts almost nothing.
 *
 * When an author deliberately adds a drop-shadow they want it to
 * read — a barely-visible line doesn't get a glow by accident.
 * So whenever `--has-shadow` is set the mark renders at full
 * opacity, letting the per-geometry shadow below (box-shadow for
 * the line, drop-shadow for the glyph / text / wave) land at the
 * author's chosen strength. The universal block-opacity (applied
 * on the WRAPPER by StyleAttribute) is unaffected — an author who
 * dials the whole block down still gets that. */
.gc-library-section-divider--has-shadow .gc-library-section-divider__rule,
.gc-library-section-divider--has-shadow .gc-library-section-divider__mark,
.gc-library-section-divider--has-shadow .gc-library-section-divider__wave,
.gc-library-section-divider--has-shadow .gc-library-section-divider__label-text,
.gc-library-section-divider--has-shadow.gc-library-section-divider--label::before,
.gc-library-section-divider--has-shadow.gc-library-section-divider--label::after {
    opacity: 1;
}
/* Shadow technique is matched to each mark's geometry — one size
 * does NOT fit all:
 *
 *   - Line `__rule`: the <hr> genuinely IS a thin rectangle
 *     (Npx tall × width%), so a real `box-shadow` on it casts a
 *     correct, strong line glow. drop-shadow of a hairline is
 *     physically near-invisible (the bug that started this), and
 *     a box-shadow here hugs the line — NOT the full-height band
 *     the suppressed *wrapper* box-shadow produced. Reads the
 *     universal `--gc-shadow-*` props (incl. spread, which CSS
 *     drop-shadow lacks) inherited from the wrapper.
 *
 *   - Dots / asterisks `__mark` + label `__label-text` + wave
 *     `__wave`: these are GLYPHS / TEXT / a thin curve, not
 *     rectangles. A box-shadow would be a rectangle around the
 *     element box (wrong shape). `filter: drop-shadow()` follows
 *     the alpha so each dot / letter / the curve casts its own
 *     glow.
 *
 * ONE shadow source for every variant: the universal
 * `--gc-shadow-*` / `--gc-shadow-fn` custom properties emitted by
 * StyleAttribute (frontend) and buildEditorStyle (editor) — the
 * SAME pipeline the working line box-shadow uses. The line uses
 * the granular props (box-shadow supports real spread); the
 * drop-shadow variants use `--gc-shadow-fn`, the ready-made
 * `drop-shadow(...)` literal with spread folded into blur (CSS
 * drop-shadow has no spread param). The old bespoke
 * `--gc-divider-shadow` had its own stricter colour regex that
 * rejected colour formats StyleAttribute accepts, so the line
 * worked while every drop-shadow variant was dead
 * (user-reported 2026-05-15: "fungerer absolutt gull på line,
 * fungerte ikke i det hele tatt på resten"). Unifying onto
 * `--gc-shadow-fn` makes the drop-shadow variants work exactly
 * when the line works — same colour pipeline, no divergence.
 *
 * A SINGLE drop-shadow / box-shadow, no stack. The mark renders
 * at full opacity when shadowed (see the rule above), so the
 * shadow is exactly the geometry + colour the author set with the
 * sliders — 1:1, no hidden code multiplier (user direction
 * 2026-05-15). */
/* Box-shadow for the thin-rectangle marks: the line's <hr> AND
 * the label variant's flanking ::before / ::after rules. A
 * box-shadow on a thin element is a correct, strong line glow.
 * The label rules were previously unshadowed — only the label
 * font got a shadow (user-reported 2026-05-15: "det er kun
 * fonten som får skygge"); now the whole label divider (text +
 * both flanking rules) is shadowed. */
.gc-library-section-divider--has-shadow:not(.gc-library-section-divider--wave) .gc-library-section-divider__rule,
.gc-library-section-divider--has-shadow.gc-library-section-divider--label::before,
.gc-library-section-divider--has-shadow.gc-library-section-divider--label::after {
    /* Spread is floored at 0 for the line. `box-shadow` spread
     * SHRINKS the shadow box on negative values; the `__rule` /
     * label flanking border is only a 1-8px hairline, so any
     * negative spread collapses the shadow's short axis to nothing
     * and the glow vanishes entirely (user-reported 2026-05-15:
     * "Minus spread fungerer ikke i det hele tatt. Pluss fungerer
     * det veldig bra"). A hairline has nothing to shrink inward,
     * so negative spread has no honest meaning here. Flooring it
     * makes negative spread a graceful no-op (shadow stays at its
     * blur-only size) instead of destroying the shadow. Positive
     * spread is unchanged — it expands the band, which works. */
    box-shadow:
        var(--gc-shadow-x, 0px) var(--gc-shadow-y, 0px)
        var(--gc-shadow-blur, 0px) max(0px, var(--gc-shadow-spread, 0px))
        var(--gc-shadow-color, transparent);
}
/* Text marks (dots / asterisks `__mark`, label `__label-text`)
 * vs the SVG wave need DIFFERENT shadow primitives. The old
 * `filter: drop-shadow()` ×10 was wrong for BOTH: CSS `filter`
 * is a CHAIN — each pass shadows the PREVIOUS pass's output, not
 * the original. So 10× is not a 10×-alpha boost; it recursively
 * smears the source. On a sparse glyph that smear saturates by
 * ~blur 6 (the top of the slider then does nothing — a hidden
 * strength ceiling, the opposite of the intent) and on the large
 * wave path it is ~O(10 × area × blur) and freezes the browser
 * above blur 60 (both user-reported 2026-05-15: dots/asterisks
 * "effekten er dårlig … burde gitt effekt over hele skalaen";
 * wave "låser browser seg grunnet rendering").
 *
 * (a) Text marks → `text-shadow`. A text-shadow LIST is NOT a
 * chain: every entry shadows the SAME original glyph and the
 * results composite. Repeating one entry N× builds alpha
 * additively (≈ 1−(1−a)^N) with NO recursion, NO bounding-box
 * growth, and cost linear in N — so a thin glyph reads strongly
 * AND the effect keeps scaling smoothly across the WHOLE slider
 * instead of saturating. This is the visibility compensation the
 * 10× chain was failing to be. `text-shadow` has no spread, so
 * spread is folded into the blur via calc(), exactly as
 * StyleAttribute folds it for `--gc-shadow-fn` (max(0, blur +
 * spread)); dots/asterisks now respond to spread over the full
 * range, not just 0-30.
 *
 * (b) SVG wave → a SINGLE `filter: drop-shadow()`. text-shadow
 * can't shadow an SVG path, so the wave keeps drop-shadow, but
 * ONE pass (not a chain) removes the O(N) cliff entirely: blur
 * is usable to 200 with no freeze or stutter. The wave is a
 * thicker, larger source than a hairline and already read
 * acceptably, so one honest pass is the right strength. */
/* ONE text-shadow entry, reused. `calc()` folds spread into blur
 * exactly as StyleAttribute folds it for `--gc-shadow-fn`
 * (max(0, blur+spread)) so the spread slider works over its whole
 * range. Defined once; repeated N× below — a text-shadow list
 * composites from the SAME glyph (non-recursive), so each repeat
 * just raises the alpha floor for a thin source. No chain, no
 * saturation, no bbox growth, cost linear and cheap. */
.gc-library-section-divider--has-shadow:not(.gc-library-section-divider--wave) .gc-library-section-divider__mark,
.gc-library-section-divider--has-shadow:not(.gc-library-section-divider--wave) .gc-library-section-divider__label-text {
    --gc-divider-ts: var(--gc-shadow-x, 0px) var(--gc-shadow-y, 0px) calc(max(0px, var(--gc-shadow-blur, 0px) + var(--gc-shadow-spread, 0px))) var(--gc-shadow-color, transparent);
}
/* Label words are solid letterforms — 5× reads well and the user
 * confirmed labelled is correct (2026-05-15: "Line og labelled
 * fungerer som de skal"), so it is left at 5× and untouched. */
.gc-library-section-divider--has-shadow:not(.gc-library-section-divider--wave) .gc-library-section-divider__label-text {
    text-shadow:
        var(--gc-divider-ts), var(--gc-divider-ts), var(--gc-divider-ts),
        var(--gc-divider-ts), var(--gc-divider-ts);
}
/* Dots / asterisks are SPARSE glyphs (small, letter-spaced 0.4em)
 * — far less ink than label words, so the 5× that reads on solid
 * label text is too faint here (user 2026-05-15: line + labelled
 * correct, "De tre andre gir for svak glow"). text-shadow is
 * non-recursive and cheap, so the entry is repeated 16× — purely
 * an alpha floor for a thin source, NOT a chain: no saturation,
 * blur still scales across the whole slider. */
.gc-library-section-divider--has-shadow:not(.gc-library-section-divider--wave) .gc-library-section-divider__mark {
    text-shadow:
        var(--gc-divider-ts), var(--gc-divider-ts), var(--gc-divider-ts), var(--gc-divider-ts),
        var(--gc-divider-ts), var(--gc-divider-ts), var(--gc-divider-ts), var(--gc-divider-ts),
        var(--gc-divider-ts), var(--gc-divider-ts), var(--gc-divider-ts), var(--gc-divider-ts),
        var(--gc-divider-ts), var(--gc-divider-ts), var(--gc-divider-ts), var(--gc-divider-ts);
}
/* SVG wave path: text-shadow can't apply, and CSS `filter` has no
 * non-recursive list — chain depth is the only knob. 1× was too
 * faint (user 2026-05-15: "De tre andre gir for svak glow"); 10×
 * froze the browser above blur 60. 4× is the pragmatic middle:
 * strong enough for a thin stroke, well under the freeze
 * threshold the user hit at 10×. The proper alpha-dense fix is an
 * inline SVG <filter> (markup change in SectionDividerModule.php
 * + editor.js) — deferred unless 4× still reads weak. */
.gc-library-section-divider--has-shadow.gc-library-section-divider--wave .gc-library-section-divider__wave {
    filter:
        var( --gc-shadow-fn, drop-shadow(0 0 0 transparent) )
        var( --gc-shadow-fn, drop-shadow(0 0 0 transparent) )
        var( --gc-shadow-fn, drop-shadow(0 0 0 transparent) )
        var( --gc-shadow-fn, drop-shadow(0 0 0 transparent) );
}
/* Labelled divider: rule with centered text. The text sits on a
 * solid background (theme-controlled via wrapper bg) so the rule
 * appears to pass behind it.
 *
 * The width slider applies here too — the wrapper shrinks to the
 * configured percentage and auto-margins re-centre it. (Line and
 * wave shrink the inner mark; label's mark IS the flex row, so the
 * wrapper itself has to shrink.) */
.gc-library-section-divider--label {
    position: relative;
    display: flex;
    align-items: center;
    gap: 1rem;
    width: var(--gc-divider-width, 100%);
    margin-left: auto;
    margin-right: auto;
    /* The label's default size lives on the WRAPPER, not on
     * `__label-text`. The author's native typography (Font / Size
     * / Letter case / Letter spacing / …) is written by
     * get_block_wrapper_attributes (frontend) / useBlockProps
     * (editor) as an INLINE style on this wrapper; inline beats
     * this class default, so the author's choice wins and
     * `__label-text` inherits it.
     *
     * Only `font-size` carries a baked default here. Letter case
     * and letter spacing intentionally carry NO `--label` default:
     * a baked `text-transform` / `letter-spacing` was found to
     * obstruct the author's Typography choice for those two
     * specifically (user-reported 2026-05-15: "Letter case og
     * letter spacing fungerer ikke"), while Font / Size / Line
     * height / Decoration / Orientation, which have no such baked
     * default, responded correctly. Dropping the two defaults
     * makes all seven typography controls behave the same. The
     * label no longer auto-uppercases — an unstyled label renders
     * normal-case at 0.85em; the author adds case / tracking
     * through Typography when they want it.
     *
     * The `::before` / `::after` rules carry no text, so
     * text-transform / letter-spacing don't affect them and
     * font-size doesn't drive their thickness-var border. */
    font-size: 0.85em;
}
.gc-library-section-divider--label::before,
.gc-library-section-divider--label::after {
    content: "";
    flex: 1;
    border-top: calc( var(--gc-divider-thickness, 3) * 1px ) solid currentColor;
    opacity: var(--gc-divider-opacity, 0.55);
}
.gc-library-section-divider__label-text {
    display: inline-block;
    flex-shrink: 0;
    /* No font-size / text-transform / letter-spacing here — they
     * live on the `--label` wrapper so the author's native
     * typography (inline on the wrapper) overrides them and this
     * element inherits the resolved value. See the wrapper rule. */
    opacity: var(--gc-divider-opacity, 0.55);
}

/* ─── Library: Hero ─────────────────────────────────────── */
/* Six variations share one class root; the variant modifier
 * (`--split-right`, `--centered`, etc.) drives layout. All
 * variations work without an image; the overlay-bg variant
 * silently degrades to centered when no image is set (handled in
 * PHP + editor — no CSS branching needed).
 *
 * Theme-neutral: no opinionated colors, no inline fonts. The
 * wrapper inherits theme typography + tokens via
 * `get_block_wrapper_attributes`. The two pieces Canvas ships
 * inline are: image object-position (focal-point), and overlay
 * darken value (`--gc-hero-overlay` 0..1). */
.gc-library-hero {
    box-sizing: border-box;
    position: relative;
    padding-block: clamp(2.5rem, 5vw, 5rem);
}
.gc-library-hero__content {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    min-width: 0;
}
.gc-library-hero__heading {
    margin: 0;
    line-height: 1.1;
}
.gc-library-hero__heading--placeholder {
    opacity: 0.4;
    font-style: normal;
}
.gc-library-hero__subheading {
    margin: 0;
    font-size: 1.125em;
    line-height: 1.55;
    max-width: 60ch;
    opacity: 0.78;
}
.gc-library-hero__ctas {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 0.5rem;
}
/* CTAs are theme-neutral. Primary inherits currentColor / surface
 * inversion; secondary is outlined. Padding + radius + weight
 * are explicit because button density matters across themes that
 * may not have opinions on link styling. */
.gc-library-hero__cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.7em 1.4em;
    border-radius: 4px;
    font-weight: 600;
    font-size: 0.95em;
    line-height: 1.2;
    text-decoration: none;
    transition: opacity 120ms ease-out, transform 120ms ease-out;
}
/* Primary CTA: solid surface, inverted text. Cannot use
 * `background: currentColor` + `color: var(--bg)` together —
 * the two compute to the same color (currentColor resolves to
 * THIS element's color property, which is set to the background
 * preset in the same rule, so bg ends up the same as text →
 * label is invisible). Two independent token reads instead, with
 * OKLCH fallbacks when the theme doesn't expose preset colors. */
.gc-library-hero__cta--primary {
    background: var(--wp--preset--color--foreground, oklch(22% 0.02 250));
    color:      var(--wp--preset--color--background, oklch(98% 0.005 250));
}
.gc-library-hero__cta--secondary {
    border: 1px solid currentColor;
    color: currentColor;
    background: transparent;
}
.gc-library-hero__cta:hover,
.gc-library-hero__cta:focus-visible {
    opacity: 0.88;
}
.gc-library-hero__media {
    min-width: 0;
}
.gc-library-hero__image {
    display: block;
    width: 100%;
    height: 100%;
    max-height: 480px;
    object-fit: cover;
    border-radius: 4px;
}
.gc-library-hero__media--placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 10;
    border: 1px dashed currentColor;
    border-radius: 4px;
    opacity: 0.45;
    font-size: 0.9em;
    font-style: normal;
    padding: 1rem;
    text-align: center;
}
/* ─── Hero variants ─────────────────────────────────────── */
/* split: two-column grid, equal-width above 960px, stacked below.
 * `align-items: center` keeps the text vertically anchored to the
 * image's mid-point — same rhythm Stripe and Linear use. */
.gc-library-hero--split-right,
.gc-library-hero--split-left {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(1.5rem, 4vw, 3rem);
    align-items: center;
}
.gc-library-hero--split-left .gc-library-hero__content {
    order: 2;
}
.gc-library-hero--split-left .gc-library-hero__media {
    order: 1;
}
@media (max-width: 768px) {
    .gc-library-hero--split-right,
    .gc-library-hero--split-left {
        grid-template-columns: 1fr;
    }
    .gc-library-hero--split-left .gc-library-hero__content,
    .gc-library-hero--split-left .gc-library-hero__media {
        order: initial;
    }
}
/* centered: narrow column, single column, everything centred. */
.gc-library-hero--centered {
    text-align: center;
    max-width: 720px;
    margin-inline: auto;
}
.gc-library-hero--centered .gc-library-hero__content {
    align-items: center;
}
.gc-library-hero--centered .gc-library-hero__subheading {
    max-width: 56ch;
}
.gc-library-hero--centered .gc-library-hero__ctas {
    justify-content: center;
}
/* left-aligned: full-width column, text-left. */
.gc-library-hero--left-aligned .gc-library-hero__content {
    max-width: 760px;
}
/* overlay-bg: full-bleed background-image + darken overlay +
 * centred text on top. The image lives as background-image on the
 * wrapper (object-position via background-position). */
.gc-library-hero--overlay-bg {
    background-size: cover;
    background-repeat: no-repeat;
    color: oklch(98% 0.005 250);  /* legibility floor for text-on-image */
    overflow: hidden;
    text-align: center;
    min-height: clamp(280px, 40vw, 480px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding-inline: clamp(1rem, 4vw, 3rem);
}
.gc-library-hero--overlay-bg .gc-library-hero__content {
    align-items: center;
    position: relative;
    z-index: 1;
    max-width: 720px;
}
.gc-library-hero--overlay-bg .gc-library-hero__subheading {
    opacity: 0.92;  /* slight lift over the standard 0.78; image is the contrast challenge */
}
.gc-library-hero--overlay-bg .gc-library-hero__ctas {
    justify-content: center;
}
.gc-library-hero__overlay {
    position: absolute;
    inset: 0;
    background: oklch(15% 0.02 250 / var(--gc-hero-overlay, 0.4));
    pointer-events: none;
    z-index: 0;
}
/* Overlay-bg lives on a dark image+overlay. Primary CTA gets a
 * LIGHT background + dark text to pop against the dark backdrop;
 * fixed OKLCH (the theme tokens go the wrong direction against
 * the overlay's forced-light text color). Secondary gets a
 * brighter border than its currentColor would give, since
 * currentColor here is white-ish and a pure-white border on a
 * dark image is too crisp. */
.gc-library-hero--overlay-bg .gc-library-hero__cta--primary {
    background: oklch(98% 0.005 250);
    color:      oklch(20% 0.02 250);
}
.gc-library-hero--overlay-bg .gc-library-hero__cta--secondary {
    border-color: oklch(98% 0.005 250 / 0.7);
}
/* minimal: heading + one CTA, narrow centred column. */
.gc-library-hero--minimal {
    text-align: center;
    max-width: 560px;
    margin-inline: auto;
}
.gc-library-hero--minimal .gc-library-hero__content {
    align-items: center;
    gap: 1.5rem;
}
.gc-library-hero--minimal .gc-library-hero__ctas {
    justify-content: center;
}

/* ─── Library: FAQ ──────────────────────────────────────── */
.gc-library-faq {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    /* Locked sidebar pattern (Phase 7b): role-based Color panel.
     * Per-instance Custom overrides ride `--gc-faq-bg/-text/-link`
     * (inline on the wrapper); a named role falls through to
     * `--gc-faq-*-default` emitted by ColorRoles::runtime_css();
     * with neither, the block stays theme-neutral. */
    background-color: var( --gc-faq-bg,   var( --gc-faq-bg-default,   transparent ) );
    color:            var( --gc-faq-text, var( --gc-faq-text-default, inherit ) );
}
.gc-library-faq a {
    color: var( --gc-faq-link, var( --gc-faq-link-default, currentColor ) );
}
/* Border-opacity override (only fires when the CSS variable is
 * declared by the editor bridge / PHP rewrite). `!important`
 * beats Gutenberg's own inline border-color on the wrapper. */
.gc-library-faq[style*="--gc-faq-border-color:"] {
    border-color: var( --gc-faq-border-color ) !important;
}
.gc-library-faq__item {
    border: 1px solid currentColor;
    border-color: color-mix( in oklab, currentColor 15%, transparent );
    border-radius: 6px;
    padding: 0;
    overflow: hidden;
}
.gc-library-faq__question {
    list-style: none;
    cursor: pointer;
    padding: 0.85rem 1rem;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    user-select: none;
}
/* Hide native disclosure triangle; we draw our own. */
.gc-library-faq__question::-webkit-details-marker { display: none; }
.gc-library-faq__question::marker                  { content: ""; }
/* Drawn chevron via ::after — rotates when [open]. */
.gc-library-faq__question::after {
    content: "";
    width: 10px;
    height: 10px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg) translateY(-2px);
    opacity: 0.55;
    flex-shrink: 0;
    transition: transform 180ms ease-out;
}
.gc-library-faq__item[open] .gc-library-faq__question::after {
    transform: rotate(-135deg) translateY(2px);
}
.gc-library-faq__answer {
    padding: 0 1rem 1rem;
    line-height: 1.55;
}
.gc-library-faq__answer p:first-child { margin-top: 0; }
.gc-library-faq__answer p:last-child  { margin-bottom: 0; }
.gc-library-faq__placeholder {
    padding: 1.5rem;
    border: 1px dashed currentColor;
    border-radius: 4px;
    opacity: 0.5;
    text-align: center;
    font-size: 0.9em;
    font-style: italic;
}

/* ─── Library: Table of Contents ─────────────────────────── */
.gc-library-toc {
    box-sizing: border-box;
}
.gc-library-toc__title {
    margin: 0 0 0.75rem;
    font-size: 0.85em;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    opacity: 0.7;
}
.gc-library-toc__title--summary {
    cursor: pointer;
    list-style: none;
}
.gc-library-toc__title--summary::-webkit-details-marker { display: none; }
.gc-library-toc__title--summary::marker                  { content: ""; }
.gc-library-toc__list {
    margin: 0;
    padding: 0 0 0 1.25rem;
    line-height: 1.6;
}
.gc-library-toc__list--nested {
    padding-left: 1rem;
    margin: 0.2rem 0 0.4rem;
}
.gc-library-toc--numbered .gc-library-toc__list {
    list-style: decimal;
}
.gc-library-toc--numbered .gc-library-toc__list--nested {
    list-style: lower-alpha;
}
.gc-library-toc__item {
    margin: 0;
}
.gc-library-toc__link {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px solid transparent;
    transition: border-color 120ms ease-out, opacity 120ms ease-out;
    opacity: 0.85;
}
.gc-library-toc__link:hover,
.gc-library-toc__link:focus-visible {
    border-bottom-color: currentColor;
    opacity: 1;
}
/* Collapsible mobile: <details> open by default on desktop; below
 * 768px the user can collapse it. We don't auto-collapse on mobile
 * — author choice — but the affordance is there. */
.gc-library-toc--collapsible .gc-library-toc__details > summary {
    position: relative;
    padding-right: 1.5rem;
}
.gc-library-toc--collapsible .gc-library-toc__details > summary::after {
    content: "";
    position: absolute;
    right: 0;
    top: 0.4em;
    width: 8px;
    height: 8px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    opacity: 0.55;
    transition: transform 180ms ease-out;
}
.gc-library-toc--collapsible .gc-library-toc__details[open] > summary::after {
    transform: rotate(-135deg);
}
/* Editor-preview-only styling — softer, with a note below the list. */
.gc-library-toc--editor-preview {
    border: 1px dashed currentColor;
    border-color: color-mix( in oklab, currentColor 20%, transparent );
    padding: 1rem;
    border-radius: 4px;
}

/* ─── Pricing Table ─────────────────────────────────────────
 * Side-by-side pricing cards with five variations:
 *   - two-tier / three-tier / four-tier / single-offer — share
 *     the same `.gc-library-pricing__grid` shell; column count
 *     varies via the wrapper's variant class.
 *   - comparison — a wholly different layout: header row + per-
 *     feature rows in a CSS grid driven by `--gc-pricing-cols`.
 *
 * Theme-neutral: the card surface uses `color-mix` with the
 * theme's currentColor so it adapts to dark / light themes
 * without inline colors. The highlight accent is the one
 * piece Canvas reads inline — via `--gc-pricing-accent`. */
.gc-library-pricing {
    box-sizing: border-box;
    --gc-pricing-accent: var(--wp--preset--color--foreground, oklch(45% 0.16 250));
    --gc-pricing-card-surface: color-mix(in oklab, currentColor 4%, transparent);
    --gc-pricing-card-border: color-mix(in oklab, currentColor 14%, transparent);
    --gc-pricing-muted: color-mix(in oklab, currentColor 60%, transparent);
    /* Locked sidebar pattern (Phase 7b): role-based Color panel.
     * Distinct from --gc-pricing-accent (the highlighted-tier
     * accent). Custom overrides ride --gc-pricing-bg/-text/-link;
     * a named role falls through to --gc-pricing-*-default from
     * ColorRoles::runtime_css(); with neither, theme-neutral. */
    background-color: var( --gc-pricing-bg,   var( --gc-pricing-bg-default,   transparent ) );
    color:            var( --gc-pricing-text, var( --gc-pricing-text-default, inherit ) );
}
.gc-library-pricing a {
    color: var( --gc-pricing-link, var( --gc-pricing-link-default, currentColor ) );
}
/* Border-opacity override (fires only when the bridge/PHP declares
 * the variable). `!important` beats Gutenberg's inline border-color. */
.gc-library-pricing[style*="--gc-pricing-border-color:"] {
    border-color: var( --gc-pricing-border-color ) !important;
}
.gc-library-pricing__header {
    text-align: center;
    margin-bottom: clamp(1.5rem, 3vw, 2.5rem);
    max-width: 720px;
    margin-inline: auto;
}
.gc-library-pricing__heading {
    margin: 0;
    line-height: 1.2;
}
.gc-library-pricing__subheading {
    margin: 0.75rem 0 0;
    font-size: 1.0625em;
    line-height: 1.55;
    opacity: 0.78;
}

/* ─── Card grid (default + two/three/four/single-offer) ─── */
.gc-library-pricing__grid {
    display: grid;
    gap: clamp(1rem, 2vw, 1.5rem);
    align-items: stretch;
}
.gc-library-pricing--two-tier .gc-library-pricing__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}
.gc-library-pricing--three-tier .gc-library-pricing__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}
.gc-library-pricing--four-tier .gc-library-pricing__grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}
.gc-library-pricing--single-offer .gc-library-pricing__grid {
    grid-template-columns: minmax(0, 480px);
    justify-content: center;
}
/* Responsive: stack two-up below 960, single-column below 640. */
@media (max-width: 1100px) {
    .gc-library-pricing--four-tier .gc-library-pricing__grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
@media (max-width: 720px) {
    .gc-library-pricing--two-tier .gc-library-pricing__grid,
    .gc-library-pricing--three-tier .gc-library-pricing__grid,
    .gc-library-pricing--four-tier .gc-library-pricing__grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ─── Card ─── */
.gc-library-pricing__card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: clamp(1.25rem, 2vw, 1.75rem);
    background: var(--gc-pricing-card-surface);
    border: 1px solid var(--gc-pricing-card-border);
    border-radius: 8px;
    box-sizing: border-box;
}
.gc-library-pricing__card.is-highlighted {
    border-color: var(--gc-pricing-accent);
    border-width: 2px;
    /* Compensate for the extra border so the card stays grid-aligned. */
    padding: calc(clamp(1.25rem, 2vw, 1.75rem) - 1px);
}
/* `.gc-library-pricing__badge` is now a POSITIONING wrapper around
 * the real Badge HTML (Library/Badge shape). The wrapper carries
 * absolute positioning + the color-role override that pipes the
 * pricing accent into the badge's bg/text custom properties; the
 * visual treatment (silhouette, padding, font, icon) comes from
 * the inner `.gc-library-badge--*` classes. Pre-v2.2 this rule
 * carried background + color + padding directly; those moved to
 * the Badge classes. The two-class chain on the wrapper means
 * authors can override either layer cleanly. */
.gc-library-pricing__badge {
    position: absolute;
    top: -0.7em;
    left: 50%;
    transform: translateX(-50%);
    /* Override the badge's role-driven default colors so the
     * recommendation badge inherits the pricing-accent palette and
     * the auto-computed accent-text color (PricingProModule's
     * compose_accent_style emits `--gc-pricing-accent-text` derived
     * from the accent's relative luminance). When the author hasn't
     * set an accent, the fallback chain lands on Canvas Ink + near-
     * white from the Badge brand-ink role's defaults. */
    --gc-badge-bg-default:   var(--gc-pricing-accent, oklch(20% 0.04 250));
    --gc-badge-text-default: var(--gc-pricing-accent-text, oklch(98% 0.005 250));
    /* Remove the legacy text-chip declarations (background, color,
     * padding, font-size, border-radius) — the inner .gc-library-badge
     * carries those now. The `font-weight` declaration below stays
     * because it's redundant-but-harmless and tightens the wrapper's
     * paint to match the badge's. */
    font-weight: 600;
    letter-spacing: 0.02em;
    white-space: nowrap;
}
.gc-library-pricing__name {
    margin: 0;
    font-size: 1.25em;
    line-height: 1.2;
}
.gc-library-pricing__description {
    margin: 0;
    font-size: 0.95em;
    line-height: 1.5;
    opacity: 0.75;
}
.gc-library-pricing__price {
    display: flex;
    align-items: baseline;
    gap: 0.05em;
    margin: 0.25rem 0;
    line-height: 1;
}
.gc-library-pricing__price--after {
    /* "29 kr/mo": insert a hair-space gap between amount and currency. */
    gap: 0.2em;
}
.gc-library-pricing__currency {
    font-size: 1.2em;
    font-weight: 500;
    opacity: 0.7;
}
.gc-library-pricing__amount {
    font-size: 2.5em;
    font-weight: 700;
    line-height: 1;
}
.gc-library-pricing__period {
    font-size: 0.95em;
    font-weight: 500;
    opacity: 0.65;
    margin-left: 0.15em;
}
.gc-library-pricing__features {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.6em;
    flex-grow: 1;
}
.gc-library-pricing__feature {
    position: relative;
    padding-left: 1.5em;
    line-height: 1.5;
    font-size: 0.95em;
}
/* Checkmark glyph drawn with two `border-right` + `border-bottom`
 * edges rotated 45° — pure CSS, no glyph font, no SVG. Color
 * defaults to a muted `currentColor` derivative so the accent
 * stays bound to the highlighted tier (One-Voice Rule: Canvas
 * Ink ≤10% of surface). The highlighted tier earns the accent
 * via the override one rule down. */
.gc-library-pricing__feature::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.45em;
    width: 0.5em;
    height: 0.85em;
    border-right: 2px solid color-mix(in oklab, currentColor 55%, transparent);
    border-bottom: 2px solid color-mix(in oklab, currentColor 55%, transparent);
    transform: rotate(45deg);
    transform-origin: 70% 70%;
}
.gc-library-pricing__card.is-highlighted .gc-library-pricing__feature::before {
    border-right-color: var(--gc-pricing-accent);
    border-bottom-color: var(--gc-pricing-accent);
}
.gc-library-pricing__cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.75em 1.4em;
    border-radius: 4px;
    font-weight: 600;
    font-size: 0.95em;
    line-height: 1.2;
    text-decoration: none;
    transition: opacity 120ms ease-out, transform 120ms ease-out;
    background: var(--gc-pricing-card-surface);
    border: 1px solid currentColor;
    color: inherit;
    margin-top: auto;
}
.gc-library-pricing__card.is-highlighted .gc-library-pricing__cta {
    background: var(--gc-pricing-accent);
    /* `--gc-pricing-accent-text` is emitted by PricingProModule's
     * compose_accent_style helper based on the accent's relative
     * luminance (pale accents → Canvas Ink; dark accents → near-
     * white). Closes the v2.1 contrast trap where white text on a
     * light-yellow accent failed WCAG AA. Theme-preset fallback
     * stays for the no-accent-set case. */
    color: var(--gc-pricing-accent-text, var(--wp--preset--color--background, oklch(98% 0.005 250)));
    border-color: transparent;
}
.gc-library-pricing__cta:hover,
.gc-library-pricing__cta:focus-visible {
    opacity: 0.88;
}

/* ─── Comparison variant ─── */
/* The wrapper sets `--gc-pricing-cols` (count of tiers). Every
 * row becomes a CSS grid: one wide label column + N tier
 * columns. The label column is fixed-flexible so feature names
 * have room to read while tier columns share equal width. */
.gc-library-pricing--comparison .gc-library-pricing__comparison {
    display: grid;
    gap: 0;
    border: 1px solid var(--gc-pricing-card-border);
    border-radius: 8px;
    overflow: hidden;
    background: var(--gc-pricing-card-surface);
}
.gc-library-pricing--comparison .gc-library-pricing__header-row,
.gc-library-pricing--comparison .gc-library-pricing__row,
.gc-library-pricing--comparison .gc-library-pricing__footer-row {
    display: grid;
    grid-template-columns: minmax(180px, 1.4fr) repeat(var(--gc-pricing-cols, 3), minmax(0, 1fr));
    align-items: center;
}
.gc-library-pricing--comparison .gc-library-pricing__header-row {
    background: color-mix(in oklab, currentColor 6%, transparent);
}
.gc-library-pricing--comparison .gc-library-pricing__row + .gc-library-pricing__row {
    border-top: 1px solid var(--gc-pricing-card-border);
}
.gc-library-pricing--comparison .gc-library-pricing__footer-row {
    background: color-mix(in oklab, currentColor 3%, transparent);
    border-top: 1px solid var(--gc-pricing-card-border);
}
.gc-library-pricing--comparison .gc-library-pricing__row-label {
    padding: 0.85em 1em;
    font-weight: 500;
    line-height: 1.4;
}
.gc-library-pricing--comparison .gc-library-pricing__col {
    position: relative;
    padding: 1.25em 1em;
    text-align: center;
    border-left: 1px solid var(--gc-pricing-card-border);
    display: flex;
    flex-direction: column;
    gap: 0.4em;
    align-items: center;
}
.gc-library-pricing--comparison .gc-library-pricing__col.is-highlighted {
    background: color-mix(in oklab, var(--gc-pricing-accent) 8%, transparent);
}
.gc-library-pricing--comparison .gc-library-pricing__col .gc-library-pricing__badge {
    position: relative;
    top: auto;
    left: auto;
    transform: none;
    align-self: center;
    margin-bottom: 0.25em;
}
.gc-library-pricing--comparison .gc-library-pricing__col .gc-library-pricing__name {
    font-size: 1.1em;
}
.gc-library-pricing--comparison .gc-library-pricing__col .gc-library-pricing__price {
    justify-content: center;
}
.gc-library-pricing--comparison .gc-library-pricing__col .gc-library-pricing__amount {
    font-size: 1.75em;
}
.gc-library-pricing--comparison .gc-library-pricing__cell {
    padding: 0.85em 1em;
    text-align: center;
    border-left: 1px solid var(--gc-pricing-card-border);
    min-height: 1em;
}
/* Comparison checkmark: defaults to a strong `currentColor`
 * derivative so the accent stays bound to the highlighted
 * column (same discipline as the card-view feature glyph).
 * The highlighted column promotes its checks to full accent
 * via the override below. */
.gc-library-pricing--comparison .gc-library-pricing__check {
    display: inline-block;
    color: color-mix(in oklab, currentColor 78%, transparent);
    font-size: 1.1em;
    font-weight: 700;
    line-height: 1;
}
.gc-library-pricing--comparison .gc-library-pricing__cell.is-included:has(+ .gc-library-pricing__cell),
.gc-library-pricing--comparison .gc-library-pricing__col.is-highlighted ~ * .gc-library-pricing__check {
    /* Reserved for future "highlighted column checks" wiring — the
     * actual scope happens via PHP-emitted column-index classes
     * (see PricingTableModule::render_comparison). Falls through
     * silently when those classes aren't present. */
}
.gc-library-pricing--comparison .gc-library-pricing__col--footer {
    padding: 1em;
}
.gc-library-pricing--comparison .gc-library-pricing__col--footer .gc-library-pricing__cta {
    margin-top: 0;
    width: 100%;
}
/* Responsive: comparison grid below 720px stacks tiers as cards
 * since the grid would compress past readability. The data shape
 * doesn't change — every tier is still authored against the same
 * feature list — only the visual reframes as one-card-per-tier. */
@media (max-width: 720px) {
    .gc-library-pricing--comparison .gc-library-pricing__comparison {
        display: block;
    }
    .gc-library-pricing--comparison .gc-library-pricing__header-row,
    .gc-library-pricing--comparison .gc-library-pricing__row,
    .gc-library-pricing--comparison .gc-library-pricing__footer-row {
        display: block;
    }
    .gc-library-pricing--comparison .gc-library-pricing__row-label {
        font-weight: 600;
        padding-bottom: 0;
    }
    .gc-library-pricing--comparison .gc-library-pricing__col,
    .gc-library-pricing--comparison .gc-library-pricing__cell {
        border-left: 0;
        border-top: 1px solid var(--gc-pricing-card-border);
    }
    .gc-library-pricing--comparison .gc-library-pricing__header-row {
        background: transparent;
    }
    .gc-library-pricing--comparison .gc-library-pricing__col {
        text-align: left;
        align-items: flex-start;
    }
    .gc-library-pricing--comparison .gc-library-pricing__col .gc-library-pricing__price,
    .gc-library-pricing--comparison .gc-library-pricing__col .gc-library-pricing__badge {
        align-self: flex-start;
    }
    /* Stacked comparison view: each cell loses its column header
     * when the grid collapses to a block list. The accessible-
     * responsive-table pattern is to surface the tier name in a
     * `::before` from a `data-tier-name` attribute the PHP render
     * emits. Long feature lists stay readable; each row carries
     * its tier context. */
    .gc-library-pricing--comparison .gc-library-pricing__cell {
        display: flex;
        justify-content: space-between;
        align-items: center;
        text-align: left;
        padding: 0.6em 1em;
        position: relative;
    }
    .gc-library-pricing--comparison .gc-library-pricing__cell::before {
        content: attr(data-tier-name);
        font-weight: 500;
        font-size: 0.9em;
        opacity: 0.75;
        padding-right: 0.75em;
    }
    .gc-library-pricing--comparison .gc-library-pricing__cell.is-excluded {
        opacity: 0.55;
    }
    .gc-library-pricing--comparison .gc-library-pricing__cell.is-excluded::after {
        content: attr(data-not-included-label);
        font-style: italic;
        font-size: 0.9em;
    }
}

/* ─── Affiliate Disclosure ─────────────────────────────────
 * A single-paragraph compliance note. Visually subtle: half-tone
 * text, an optional info icon, body line-length capped so the
 * note stays readable on wide layouts. The icon is inline SVG
 * with currentColor so the theme paints it; no decorative
 * elevation. Theme inherits.
 *
 * Restraint IS the design here: this block exists to satisfy a
 * legal requirement, not to grab attention. Authors who want
 * a louder treatment can opt in via the universal Canvas Style
 * panel (background tint, border color, etc.). */
.gc-library-affiliate-disclosure {
    display: flex;
    align-items: flex-start;
    gap: 0.6em;
    box-sizing: border-box;
    padding-block: 0.6em;
    opacity: 0.78;
    font-size: 0.9em;
    line-height: 1.55;
}
.gc-library-affiliate-disclosure__icon {
    flex-shrink: 0;
    margin-top: 0.2em;
    color: inherit;
}
.gc-library-affiliate-disclosure__text {
    margin: 0;
    max-width: 70ch;
}
.gc-library-affiliate-disclosure--no-icon {
    /* When the icon is suppressed, align the paragraph at the same
     * indent as a regular paragraph would land. Keeps the disclosure
     * visually anchored to its surrounding text. */
    gap: 0;
}
.gc-library-affiliate-disclosure__text a {
    color: inherit;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.2em;
}
.gc-library-affiliate-disclosure__text a:hover,
.gc-library-affiliate-disclosure__text a:focus-visible {
    text-decoration-thickness: 2px;
}

/* ─── Pro-locked discovery card (editor-only) ────────────────
 * Inline upgrade card the editor renders for free-tier authors.
 * Frontend never sees this block (render returns empty), so the
 * styles only matter inside the block editor. Subtle dotted
 * outline signals "not a real block yet" without screaming for
 * attention; the call-to-action is the headline + body, not
 * the chrome. Restraint matches the rest of Canvas's library. */
.gc-library-pro-locked {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: 0.75em;
    padding: clamp(1.25rem, 2vw, 1.75rem);
    border: 1px dashed color-mix(in oklab, currentColor 24%, transparent);
    border-radius: 8px;
    background: color-mix(in oklab, currentColor 3%, transparent);
}
.gc-library-pro-locked__head {
    display: flex;
    align-items: center;
    gap: 0.5em;
}
.gc-library-pro-locked__icon {
    flex-shrink: 0;
    opacity: 0.65;
}
.gc-library-pro-locked__title {
    margin: 0;
    font-size: 1.1em;
    font-weight: 600;
    letter-spacing: -0.005em;
}
.gc-library-pro-locked__body {
    margin: 0;
    font-size: 0.95em;
    line-height: 1.55;
    max-width: 70ch;
    opacity: 0.78;
}
.gc-library-pro-locked__ctas {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5em;
    margin-top: 0.25em;
}
.gc-library-pro-locked__cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.55em 1.1em;
    border-radius: 4px;
    font-weight: 600;
    font-size: 0.9em;
    line-height: 1.2;
    text-decoration: none;
    transition: opacity 120ms ease-out;
}
.gc-library-pro-locked__cta--primary {
    background: var(--wp--preset--color--foreground, oklch(22% 0.02 250));
    color:      var(--wp--preset--color--background, oklch(98% 0.005 250));
}
.gc-library-pro-locked__cta--secondary {
    border: 1px solid color-mix(in oklab, currentColor 28%, transparent);
    color: inherit;
    background: transparent;
}
.gc-library-pro-locked__cta:hover,
.gc-library-pro-locked__cta:focus-visible {
    opacity: 0.88;
}

/* ─── Discount Banner ───────────────────────────────────────
 * Three variants share a common attribute set + class root:
 *
 *   .gc-library-discount-banner          (always)
 *   .gc-library-discount-banner--bar     (full-width strip)
 *   .gc-library-discount-banner--card    (in-flow contained block)
 *   .gc-library-discount-banner--floating-corner  (position: fixed)
 *
 * Restraint by default: tinted neutrals, accent only on the coupon
 * pill + CTA. Authors can amplify via the universal Canvas Style
 * panel (background tint, border, etc.). */
.gc-library-discount-banner {
    box-sizing: border-box;
    display: flex;
    align-items: center;
    gap: 1rem;
    line-height: 1.5;
    --gc-banner-accent: var(--wp--preset--color--foreground, oklch(22% 0.02 250));
    --gc-banner-surface: color-mix(in oklab, currentColor 5%, transparent);
    --gc-banner-border:  color-mix(in oklab, currentColor 14%, transparent);
    --gc-banner-muted:   color-mix(in oklab, currentColor 60%, transparent);
}
.gc-library-discount-banner__copy {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.35rem 0.85rem;
}
.gc-library-discount-banner__heading {
    margin: 0;
    font-weight: 600;
}
.gc-library-discount-banner__body {
    margin: 0;
    opacity: 0.78;
    font-size: 0.95em;
}
.gc-library-discount-banner__countdown {
    display: inline-flex;
    align-items: center;
    padding: 0.15em 0.55em;
    border: 1px solid var(--gc-banner-border);
    border-radius: 4px;
    font-size: 0.85em;
    font-weight: 500;
    color: var(--gc-banner-muted);
    white-space: nowrap;
}
.gc-library-discount-banner__actions {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
}
/* Coupon pill: button-styled but reads as a code chip. The visible
 * code uses a slightly tighter, mono-leaning treatment so a glanced
 * "EARLYBIRD20" feels distinct from the body copy. Letter-spacing
 * 0.04em helps the all-caps codes most affiliate programmes ship. */
.gc-library-discount-banner__coupon {
    display: inline-flex;
    align-items: center;
    gap: 0.5em;
    padding: 0.4em 0.85em;
    border: 1px dashed var(--gc-banner-border);
    border-radius: 4px;
    background: transparent;
    color: inherit;
    cursor: pointer;
    font: inherit;
    transition: border-color 120ms ease-out, background-color 120ms ease-out;
}
.gc-library-discount-banner__coupon:hover,
.gc-library-discount-banner__coupon:focus-visible {
    border-color: currentColor;
    background: var(--gc-banner-surface);
}
.gc-library-discount-banner__coupon-code {
    font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.9em;
    font-weight: 600;
    letter-spacing: 0.04em;
}
.gc-library-discount-banner__coupon-action {
    font-size: 0.82em;
    font-weight: 500;
    opacity: 0.75;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.gc-library-discount-banner__cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.55em 1.1em;
    border-radius: 4px;
    background: var(--gc-banner-accent);
    color: var(--wp--preset--color--background, oklch(98% 0.005 250));
    font-weight: 600;
    font-size: 0.95em;
    line-height: 1.2;
    text-decoration: none;
    transition: opacity 120ms ease-out;
}
.gc-library-discount-banner__cta:hover,
.gc-library-discount-banner__cta:focus-visible {
    opacity: 0.88;
}

/* ─── 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 four 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-pricing__cta,
.gc-library-discount-banner__cta,
.gc-library-pro-locked__cta {
    min-height: 44px;
    box-sizing: border-box;
}
.gc-library-hero__cta:focus-visible,
.gc-library-pricing__cta:focus-visible,
.gc-library-discount-banner__cta:focus-visible,
.gc-library-pro-locked__cta:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 3px;
}

/* ─── Variant: bar ─── */
.gc-library-discount-banner--bar {
    padding: 0.85rem 1.25rem;
    border-block: 1px solid var(--gc-banner-border);
    background: var(--gc-banner-surface);
}

/* ─── Variant: card ─── */
.gc-library-discount-banner--card {
    padding: clamp(1rem, 2vw, 1.5rem);
    border: 1px solid var(--gc-banner-border);
    border-radius: 8px;
    background: var(--gc-banner-surface);
    flex-wrap: wrap;
}
.gc-library-discount-banner--card .gc-library-discount-banner__copy {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.4rem;
}
.gc-library-discount-banner--card .gc-library-discount-banner__heading {
    font-size: 1.1em;
}

/* ─── Variant: floating-corner ─── */
.gc-library-discount-banner--floating-corner {
    position: fixed;
    z-index: 70;
    max-width: 320px;
    padding: 1rem 1.1rem;
    border: 1px solid var(--gc-banner-border);
    border-radius: 8px;
    background: var(--wp--preset--color--background, oklch(98% 0.005 250));
    box-shadow: 0 4px 24px color-mix(in oklab, currentColor 12%, transparent);
    flex-wrap: wrap;
    gap: 0.6rem;
}
.gc-library-discount-banner--floating-corner .gc-library-discount-banner__copy {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.35rem;
}
.gc-library-discount-banner--corner-bottom-right { bottom: 1rem; right:  1rem; }
.gc-library-discount-banner--corner-bottom-left  { bottom: 1rem; left:   1rem; }
.gc-library-discount-banner--corner-top-right    { top:    1rem; right:  1rem; }
.gc-library-discount-banner--corner-top-left     { top:    1rem; left:   1rem; }

/* Editor-preview override: drop the position: fixed so the editor
 * canvas shows the banner in-flow at its insertion point rather
 * than pinning it to a corner of the editor iframe (which would
 * read as broken to the designer). The fixed positioning only
 * applies on the live frontend. */
.gc-library-discount-banner--editor-preview.gc-library-discount-banner--floating-corner {
    position: relative;
    inset: auto;
    top: auto;
    right: auto;
    bottom: auto;
    left: auto;
    margin-block: 0.5em;
}

/* Mobile: floating-corner reframes as a sticky-bottom toast. The
 * brief locks this — a fixed-position card eating ~half a phone
 * screen is hostile; a full-width sticky bottom is the standard
 * mobile-toast affordance. */
@media (max-width: 720px) {
    .gc-library-discount-banner--floating-corner {
        max-width: none;
        left:  1rem;
        right: 1rem;
        bottom: 1rem;
        top: auto;
        flex-direction: row;
        align-items: center;
    }
    /* All four corner anchors collapse to the same mobile layout. */
    .gc-library-discount-banner--corner-bottom-right,
    .gc-library-discount-banner--corner-bottom-left,
    .gc-library-discount-banner--corner-top-right,
    .gc-library-discount-banner--corner-top-left {
        bottom: 1rem;
        top: auto;
        left:  1rem;
        right: 1rem;
    }
    .gc-library-discount-banner--bar {
        flex-wrap: wrap;
        gap: 0.6rem;
    }
}

/* ─── Pricing Pro ────────────────────────────────────────────
 * Extends Pricing Table: reuses every .gc-library-pricing__* rule
 * for cards, prices, features, CTAs. Pricing Pro's own children
 * scope only the Pro-specific chrome: the billing-mode toggle, the
 * savings badge, the dual-price visibility swap, the sticky
 * comparison header. */

/* Billing-mode toggle pill. Two buttons in a one-row container,
 * connected visually as a single segmented control. The active
 * button reads the wrapper's `data-billing-mode` (or pressed
 * state); the JS toggle keeps the two in sync. */
.gc-library-pricing-pro__toggle {
    display: inline-flex;
    align-self: center;
    margin: 0 auto clamp(1rem, 2vw, 1.5rem);
    padding: 0.25em;
    border: 1px solid var(--gc-pricing-card-border);
    border-radius: 999px;
    background: var(--gc-pricing-card-surface);
}
.gc-library-pricing-pro__toggle-option {
    display: inline-flex;
    align-items: center;
    gap: 0.45em;
    padding: 0.45em 1.1em;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: inherit;
    cursor: pointer;
    font: inherit;
    font-size: 0.95em;
    font-weight: 500;
    line-height: 1;
    transition: background-color 120ms ease-out, color 120ms ease-out;
}
/* Active-state styling reads `aria-checked` (the 2026-05-13 a11y
 * upgrade switched the toggle from role="group"+aria-pressed to
 * role="radiogroup"+aria-checked). The selector covers both
 * `<button role="radio">` on the frontend and the editor preview's
 * `<span role="radio">` so the visual matches across surfaces. */
.gc-library-pricing-pro__toggle-option[aria-checked="true"] {
    background: var(--gc-pricing-accent);
    color: var(--wp--preset--color--background, oklch(98% 0.005 250));
    font-weight: 600;
}
.gc-library-pricing-pro__toggle-option:not([aria-checked="true"]):hover {
    background: color-mix(in oklab, currentColor 8%, transparent);
}
/* Keyboard focus ring on the toggle option. Roving tabindex moves
 * focus between options as the user arrows; the ring needs to be
 * visible without depending on the button's default browser ring
 * (which the surrounding pill chrome would otherwise occlude). */
.gc-library-pricing-pro__toggle-option:focus-visible {
    outline: 2px solid var(--gc-pricing-accent);
    outline-offset: 2px;
}

/* ─── Tier icons (Pricing Pro) ──────────────────────────────
 * Hero-positioned identity mark above the tier name. The
 * pixel size flexes by variant (cards have more room than
 * comparison-header columns):
 *
 *   --md  → 28px (two-tier, three-tier, single-offer)
 *   --sm  → 24px (four-tier; narrower cards)
 *   --xs  → 20px (comparison header row cells)
 *
 * Color is always currentColor — the highlighted-tier card
 * shifts currentColor through the existing `.is-highlighted`
 * rules, and the icon follows for free. */
.gc-library-pricing-pro__tier-icon {
    display: block;
    margin-bottom: 0.85em;
    color: inherit;
    line-height: 1;
    /* Hard cap so a Dashicons-font fallback can't blow out
     * the box height; an SVG icon respects width/height
     * anyway. */
    flex-shrink: 0;
}
.gc-library-pricing-pro__tier-icon--md {
    width: 28px;
    height: 28px;
    font-size: 28px;
}
.gc-library-pricing-pro__tier-icon--sm {
    width: 24px;
    height: 24px;
    font-size: 24px;
}
.gc-library-pricing-pro__tier-icon--xs {
    width: 20px;
    height: 20px;
    font-size: 20px;
    margin-bottom: 0.55em;
}
/* Dashicons-font fallback: WP's `.dashicons` rule sets a
 * `font-size: 20px; width: 20px; height: 20px;` baseline
 * via core. Our size modifier rules above override those
 * with higher specificity (two classes vs one), so the
 * Canvas-side sizing wins. */
.gc-library-pricing-pro__tier-icon--dashicon::before {
    /* Required so the dashicon glyph fills the resized box. */
    font-size: inherit;
    width: inherit;
    height: inherit;
}

/* ─── Feature-glyph variants (Pricing Pro) ──────────────────
 * Pricing Pro emits each feature `<li>` with a `data-glyph`
 * attribute. The default `.gc-library-pricing__feature::before`
 * rule (free Pricing Table CSS) renders the checkmark; the
 * rules below override that ::before for the four non-default
 * glyphs (dot / arrow / minus / custom / none) when the
 * attribute matches.
 *
 * Pricing Pro scope: each selector lives under
 * `.gc-library-pricing-pro` so the free Pricing Table's
 * checkmark-only behaviour stays unchanged. The attribute is
 * only emitted on the Pro block. */
.gc-library-pricing-pro .gc-library-pricing__feature[data-glyph="dot"]::before {
    width: 0.5em;
    height: 0.5em;
    border-radius: 50%;
    border: 0;
    background: color-mix(in oklab, currentColor 55%, transparent);
    top: 0.6em;
    transform: none;
}
.gc-library-pricing-pro .is-highlighted .gc-library-pricing__feature[data-glyph="dot"]::before {
    background: var(--gc-pricing-accent);
}
.gc-library-pricing-pro .gc-library-pricing__feature[data-glyph="arrow"]::before {
    /* Two-border chevron pointing right (180° rotation of the
     * default checkmark's bottom-right corner). Reads as
     * "upgrade-to-next-tier" cue. */
    width: 0.5em;
    height: 0.5em;
    border-right: 2px solid color-mix(in oklab, currentColor 55%, transparent);
    border-bottom: 2px solid color-mix(in oklab, currentColor 55%, transparent);
    border-top: 0;
    border-left: 0;
    background: transparent;
    transform: rotate(-45deg);
    top: 0.5em;
}
.gc-library-pricing-pro .is-highlighted .gc-library-pricing__feature[data-glyph="arrow"]::before {
    border-right-color: var(--gc-pricing-accent);
    border-bottom-color: var(--gc-pricing-accent);
}
.gc-library-pricing-pro .gc-library-pricing__feature[data-glyph="minus"]::before {
    width: 0.7em;
    height: 2px;
    border: 0;
    background: color-mix(in oklab, currentColor 45%, transparent);
    top: 0.75em;
    transform: none;
}
.gc-library-pricing-pro .is-highlighted .gc-library-pricing__feature[data-glyph="minus"]::before {
    background: var(--gc-pricing-accent);
}
.gc-library-pricing-pro .gc-library-pricing__feature[data-glyph="custom"]::before {
    /* Custom glyph is the author-typed string rendered via
     * `content: attr(data-custom-glyph)`. CSS gives it the same
     * footprint as the checkmark's bounding box and clamps
     * width so a stray long string doesn't break the layout
     * (PHP already truncates to 4 chars; this is belt-and-
     * braces). */
    content: attr(data-custom-glyph);
    width: auto;
    max-width: 1.5em;
    height: auto;
    border: 0;
    background: transparent;
    transform: none;
    top: 0;
    left: 0;
    position: absolute;
    font-size: 1em;
    line-height: 1.5;
    color: color-mix(in oklab, currentColor 70%, transparent);
    overflow: hidden;
    white-space: nowrap;
}
.gc-library-pricing-pro .is-highlighted .gc-library-pricing__feature[data-glyph="custom"]::before {
    color: var(--gc-pricing-accent);
}
.gc-library-pricing-pro .gc-library-pricing__feature[data-glyph="none"]::before {
    /* No visible glyph; the `padding-left: 1.5em` on the parent
     * `<li>` is retained so feature-text vertical alignment
     * stays consistent across rows that have a glyph and rows
     * that don't. */
    display: none;
}
.gc-library-pricing-pro .gc-library-pricing__feature[data-glyph="none"] {
    /* Cancel the parent's left padding when the glyph is
     * suppressed and the visual hierarchy can drop the gutter. */
    padding-left: 0;
}
/* Savings badge ("Save 16%") sits next to the Annual option. The
 * badge is a persuasion device: it earns its loudness when the
 * visitor is looking at monthly prices (selling the upgrade), and
 * should recede once the visitor has chosen annual (job done).
 *
 * The single rule below paints the badge at a quiet 12% tint
 * inheriting `currentColor` from the button. When the button is
 * NOT checked, `currentColor` is the dark surrounding text color,
 * and the 12% tint reads as a subtle pill — the persuasion state.
 * When the button IS checked, the button's color flips to the
 * background preset (light on Canvas Ink), and the same 12% tint
 * of `currentColor` becomes a barely-visible mark on the accent
 * fill — the receded "you already chose this" state. No second
 * rule needed; the recede falls out of the inherited color flip
 * via the wrapper's `[aria-checked="true"]` rule that already
 * sets the button's color. (The toggle ships as role="radiogroup"
 * + aria-checked, NOT role="group" + aria-pressed, since the
 * choice is exclusive — see PricingProModule's render_toggle()
 * for the discussion.)
 *
 * Drops the brightening-on-pressed rule the first cut shipped
 * with (per the 2026-05-13 critique P3 finding — that rule made
 * the badge MORE prominent once chosen, which is the wrong
 * direction). */
.gc-library-pricing-pro__savings {
    display: inline-flex;
    align-items: center;
    padding: 0.1em 0.55em;
    border-radius: 999px;
    background: color-mix(in oklab, currentColor 12%, transparent);
    color: inherit;
    font-size: 0.78em;
    font-weight: 600;
    letter-spacing: 0.02em;
}
/* Wrap the header above the toggle so the toggle sits centred
 * regardless of variant. The grid below the toggle handles its
 * own column layout. */
.gc-library-pricing-pro {
    display: flex;
    flex-direction: column;
    align-items: stretch;
}

/* Dual-price visibility swap. Both monthly + annual blocks are in
 * the DOM; the wrapper's billing-mode class hides one of them.
 * Zero-cost toggle: the JS only swaps the wrapper class. */
.gc-library-pricing-pro__price-stack {
    display: contents;
}
.gc-library-pricing-pro--billing-monthly .gc-library-pricing-pro__price--annual,
.gc-library-pricing-pro--billing-annual  .gc-library-pricing-pro__price--monthly {
    display: none;
}

/* Sticky comparison header. The header-row of the comparison grid
 * pins to the viewport top while the visitor scrolls a long
 * feature list.
 *
 * Offset accounting: `top` reads from `--gc-sticky-offset`, which
 * defaults to 0 for anonymous visitors. Logged-in WordPress users
 * carry `body.admin-bar`; we set the variable to match WP core's
 * admin-bar height (32px at >=783px viewport per WP core's
 * standard media query, 46px below). Sites with their own fixed
 * site header can extend `--gc-sticky-offset` per-site to add
 * their own header height on top.
 *
 * The `blur(2px)` backdrop-filter is purposeful (helps the sticky
 * row read against the scrolled feature rows underneath); the
 * decorative saturate boost was dropped in the 2026-05-13 polish
 * pass. */
.gc-library-pricing-pro__comparison .gc-library-pricing-pro__header-row {
    position: sticky;
    top: var(--gc-sticky-offset, 0px);
    z-index: 2;
    background: var(--gc-pricing-card-surface);
    backdrop-filter: blur(2px);
}
/* WP admin bar height is 32px on >=783px viewports, 46px below.
 * The breakpoint matches WP core's `wp-admin-bar` stylesheet so
 * the sticky header tracks the bar's actual position 1:1. */
body.admin-bar {
    --gc-sticky-offset: 32px;
}
@media (max-width: 782px) {
    body.admin-bar {
        --gc-sticky-offset: 46px;
    }
}

@media (max-width: 720px) {
    /* Sticky header is hostile on small screens (eats vertical
     * space the user just gained back by collapsing the grid).
     * Drop the sticky positioning on mobile; the layout stacks
     * anyway. The custom-property override doesn't apply here
     * because `position: static` ignores `top` entirely. */
    .gc-library-pricing-pro__comparison .gc-library-pricing-pro__header-row {
        position: static;
        backdrop-filter: none;
    }
}

/* ─── Badge ─────────────────────────────────────────────────
 * The Canvas-voiced trust / status / announcement mark. Three
 * silhouettes (pill / shield / tab), five named color roles
 * (trust-blue / brand-ink / accent / alert / neutral), optional
 * icon slot. Linked variant inherits the universal CTA discipline
 * (44px tap target, distinct focus-visible outline).
 *
 * Color resolution: every silhouette + color-role class pair
 * reads `var(--gc-badge-bg, <role-default>)` and
 * `var(--gc-badge-text, <role-default>)`. Per-instance overrides
 * land via wrapper inline style as `--gc-badge-bg` /
 * `--gc-badge-text`; absent overrides fall through to the role
 * defaults.
 */
.gc-library-badge {
    /* `--gc-badge-radius` is the AUTHOR-SET value (px from the
     * radius slider). When the silhouette uses it (rectangle /
     * square), the silhouette class sets
     * `--gc-badge-radius-resolved` to read that var with a sensible
     * default. When the silhouette doesn't use it (pill / circle /
     * clip-path family), the silhouette class hardcodes
     * `--gc-badge-radius-resolved` to the natural value. The
     * fallback chain keeps un-classed badges rendering as pills. */
    --gc-badge-radius-resolved: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4em;
    padding: 0.35em 0.85em;
    font-size: 0.78em;
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: 0.02em;
    background: var(--gc-badge-bg, var(--gc-badge-bg-default));
    color:      var(--gc-badge-text, var(--gc-badge-text-default));
    border-radius: var(--gc-badge-radius-resolved);
    text-decoration: none;
    vertical-align: baseline;
    /* The wrapper renders inline-flex by default so it can sit
     * alongside surrounding text without forcing a line break.
     * The block-level override below handles the case where Badge
     * is a top-level Gutenberg block. */
}

/* v2.2.27 frontend block-layout fix.
 *
 * When Badge is a top-level Gutenberg block (not embedded inline
 * inside another block via Shared\Badge::render_inline()), WP
 * adds `wp-block-gillish-canvas-badge` to the wrapper. Three bugs
 * surfaced on the frontend when this class was treated the same
 * as the inline-flex default:
 *
 * 1. Three sibling Badge blocks rendered on a single horizontal
 *    line instead of stacking — because inline-flex spans flow
 *    as inline-level siblings.
 * 2. Badges started at the left viewport edge (outside the
 *    constrained content column) — because inline elements
 *    don't pick up the surrounding block-level layout's
 *    constraints.
 * 3. `filter: drop-shadow` on clip-path silhouettes wasn't
 *    visually painting — Chromium clips filter effects to the
 *    inline line-box when the filtered element is inline-level,
 *    which means a 108px-blur drop-shadow drawn around a
 *    triangle was being culled at the line edge.
 *
 * All three vanish with `display: flex` (block-level). The badge
 * keeps its flex internal layout (icon + label) and intrinsic
 * width via `width: fit-content` so it doesn't stretch to fill
 * the row. Standalone badge rows stack naturally; the filter
 * paint region is the block element's full overflow, not a
 * line-box. */
.gc-library-badge.wp-block-gillish-canvas-badge {
    display: flex;
    width: fit-content;
    /* WP block themes ship `.is-layout-constrained > *` with
     * `margin-left: auto !important; margin-right: auto !important;`
     * which centers every direct child of the content area.
     *
     * For a fit-content Badge inside an alignfull parent that's
     * also layout-constrained, "centered" puts the badge at the
     * MIDDLE of the alignfull viewport, not at the content
     * column. The surrounding text uses content-size width
     * with margin: auto and naturally sits at content-column-
     * left (its full-width box's left edge IS content-column-
     * left). Authors expect the badge to sit at the same
     * vertical alignment.
     *
     * v2.2.30 fix: compute the left margin as
     *   max(0px, (parent-width - content-size) / 2)
     * which is exactly the content-column-left offset. The
     * trailing `auto` margin-right consumes remaining space so
     * the badge sticks to the left edge of where the surrounding
     * text starts.
     *
     * `max(0px, ...)` clamps on narrow viewports where the
     * content-size exceeds the parent (mobile). The `100%` in
     * the calc resolves to the parent's content-box width.
     *
     * Authors who want a centered badge can apply Gutenberg's
     * built-in Center alignment which adds the `aligncenter`
     * class WP-core handles separately. */
    margin-left: max( 0px, calc( ( 100% - var( --wp--style--global--content-size, 100% ) ) / 2 ) ) !important;
    margin-right: auto !important;
}
.gc-library-badge__icon {
    width: 0.95em;
    height: 0.95em;
    flex-shrink: 0;
}
.gc-library-badge__label {
    /* The label IS the content; nothing more than baseline rules. */
}

/* ─── Silhouettes ───────────────────────────────────────────
 * Seven shapes across two families:
 *
 *   Round-corner-rect family (pill / rectangle / square / circle):
 *     Each sets `--gc-badge-radius-resolved` directly. Pill and
 *     circle have fixed natural curves; rectangle and square
 *     read the author-tunable `--gc-badge-radius` slider value
 *     with a sensible fallback. Square and circle force a 1:1
 *     aspect ratio so the shape stays geometric regardless of
 *     label length (author's responsibility to pick short labels).
 *
 *   Clip-path family (shield / tab / triangle):
 *     Polygons that ignore border-radius. Each sets a clip-path
 *     and adjusts padding so the label lands inside the visible
 *     area; the editor sidebar hides the radius slider when one
 *     of these is active.
 */
.gc-library-badge--pill {
    --gc-badge-radius-resolved: 999px;
}
.gc-library-badge--rectangle {
    --gc-badge-radius-resolved: var(--gc-badge-radius, 4px);
}
.gc-library-badge--square {
    --gc-badge-radius-resolved: var(--gc-badge-radius, 4px);
    aspect-ratio: 1 / 1;
    padding-inline: 0.6em;
    /* 1:1 cell; the label has to fit inside, so the inline
     * padding shrinks to give it room. Authors pick short labels
     * for square silhouettes (one or two letters / digits work
     * best — "v2", "Pro", "$"). */
}
.gc-library-badge--circle {
    --gc-badge-radius-resolved: 50%;
    aspect-ratio: 1 / 1;
    padding-inline: 0.6em;
}
/* v2.2.31 silhouette restructure for clip-path family.
 *
 * Before v2.2.31, `clip-path` lived on `.gc-library-badge--shield`
 * etc. directly. That caused two cascading problems:
 *
 * 1. `clip-path` on an element creates a stacking context (per
 *    CSS Masking spec, same as `opacity < 1`). The author-set
 *    drop-shadow rendered via `::before` pseudo at `z-index: -1`
 *    landed INSIDE that stacking context — above the badge's
 *    own bg, not below it — so the shadow color bled INTO the
 *    badge instead of behind it.
 *
 * 2. `filter: drop-shadow` applied directly on the clip-path
 *    element didn't paint reliably in real browsers despite
 *    computing the correct filter value (verified Chrome +
 *    Firefox).
 *
 * Fix: move `clip-path` from the silhouette class onto a `::before`
 * pseudo INSIDE the badge. The pseudo paints the badge's bg color
 * in the silhouette shape. The outer badge element has no clip-
 * path at all, so:
 *
 *   - No stacking context from clip-path on outer → standard
 *     positioning rules apply.
 *   - `filter: drop-shadow` on outer paints freely without being
 *     clipped — the shadow extends OUTSIDE the badge's bounding
 *     box following the rasterized alpha of the pseudo + icon +
 *     label.
 *
 * The icon and text render normally inside the badge's content
 * area (above the pseudo via implicit stacking). Icon/label
 * naturally fit within the silhouette because padding is tuned
 * to keep them inside the polygon outline.
 *
 * Outer's `background` is reset to transparent so the badge's
 * own bg (inherited from `.gc-library-badge`) doesn't double-paint
 * outside the pseudo's clipped shape. */
.gc-library-badge--shield,
.gc-library-badge--tab,
.gc-library-badge--triangle {
    --gc-badge-radius-resolved: 0;
    background: transparent;
    position: relative;
}
.gc-library-badge--shield::before,
.gc-library-badge--tab::before,
.gc-library-badge--triangle::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--gc-badge-bg, var(--gc-badge-bg-default));
    z-index: -1;
}
.gc-library-badge--shield {
    padding: 0.65em 1em 0.8em;
    /* The clip notches the top + bottom corners; padding goes
     * slightly larger on the vertical axis so the text still
     * lands inside the clipped area. */
}
.gc-library-badge--shield::before {
    clip-path: polygon(50% 0, 100% 20%, 100% 75%, 50% 100%, 0 75%, 0 20%);
}
.gc-library-badge--tab {
    padding-right: 1.4em;
    /* The right-edge chevron costs some inline space; extra
     * padding restores the visual balance so the label doesn't
     * crash into the chevron. */
}
.gc-library-badge--tab::before {
    clip-path: polygon(0 0, 100% 0, 92% 50%, 100% 100%, 0 100%);
}
.gc-library-badge--triangle {
    /* Triangle is the trickiest silhouette to render legibly:
     * the polygon tapers from full width at the bottom to zero at
     * the apex, so a label centered vertically would land in the
     * narrow upper half and crop against the slanted edges.
     *
     * Two compensations:
     *   1. Heavy top padding (1.6em) pushes the label into the
     *      lower 60% of the polygon where the edges are wider
     *      apart than the label's width.
     *   2. Generous inline padding (1.8em) widens the entire
     *      polygon so even at the label's y-position, there's
     *      enough horizontal room before the slanted edges crop.
     *
     * Short labels (1-3 words) are still the right fit for this
     * silhouette; the help text in the silhouette picker calls
     * that out. Long labels like a full sentence will spill
     * against the edges regardless. */
    padding: 1.6em 1.8em 0.4em;
}
.gc-library-badge--triangle::before {
    clip-path: polygon(50% 0, 100% 100%, 0 100%);
}

/* ─── 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). Matches
     * ColorRoles::defaults() in PHP for parity when the runtime
     * CSS hasn't loaded (e.g. theme override timing). */
    --gc-badge-link-default: oklch(82% 0.14 75);
}
.gc-library-badge--brand-ink {
    --gc-badge-bg-default:   oklch(20% 0.04 250);
    --gc-badge-text-default: oklch(98% 0.005 250);
    --gc-badge-link-default: oklch(80% 0.15 70);
}
.gc-library-badge--accent {
    --gc-badge-bg-default:   oklch(58% 0.18 28);
    --gc-badge-text-default: oklch(98% 0.005 250);
    /* Warm-toned role → brand-ink link (high contrast, deliberate). */
    --gc-badge-link-default: oklch(20% 0.04 250);
}
.gc-library-badge--alert {
    --gc-badge-bg-default:   oklch(50% 0.16 12);
    --gc-badge-text-default: oklch(98% 0.005 250);
    --gc-badge-link-default: oklch(20% 0.04 250);
}
.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);
}

/* 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: oklch(20% 0.04 250);
}
.gc-library-section-divider--accent {
    --gc-divider-bg-default:   transparent;
    --gc-divider-text-default: oklch(58% 0.18 28);
}
.gc-library-section-divider--alert {
    --gc-divider-bg-default:   transparent;
    --gc-divider-text-default: oklch(50% 0.16 12);
}
.gc-library-section-divider--neutral {
    --gc-divider-bg-default:   transparent;
    --gc-divider-text-default: currentColor;
}

/* ─── Editor-only wrapper for top-level Badge positioning ───
 *
 * v2.2.10 added a block-level <div class="gc-library-badge-editor-wrap">
 * wrapping the inline-flex badge <span> in the editor preview so
 * Gutenberg's is-layout-constrained `margin-inline: auto` would
 * centre the wrapper at the content column (auto margins don't
 * centre inline-flex elements). The wrapper has zero visual
 * styling EXCEPT this suppression rule:
 *
 * `buildEditorStyle` in gc-inspector-panels.js applies the
 * dropShadow value as `box-shadow` directly to the wrapper element
 * (the one carrying useBlockProps). Box-shadow on a 645×30px
 * block-level rectangle renders as a rectangular shadow around the
 * full wrapper bounding box — NOT around the badge's actual
 * silhouette. Result: a rectangular shadow extending across the
 * content column, painting a giant colored strip behind a small
 * badge.
 *
 * The proper shadow for the badge silhouette comes from
 * `filter: drop-shadow` on the inner span (custom-property bridge
 * via `--gc-shadow-*`). The wrapper's box-shadow is unwanted noise.
 *
 * Forced `none !important` because `buildEditorStyle`'s output is
 * an inline style which has higher specificity than a plain rule.
 */
.gc-library-badge-editor-wrap {
    box-shadow: none !important;
}

/* ─── Linked variant — memory rule discipline ───────────────
 * When the badge has a linkUrl, it becomes a clickable button.
 * Memory rule: every CTA-emitting surface in Canvas must meet
 * the 44px tap-target floor AND have a distinct :focus-visible
 * indicator. The universal CTA-discipline block earlier in this
 * stylesheet handles `gc-library-*__cta` classes; the badge
 * carries its own class root so the discipline gets added here
 * directly.
 */
.gc-library-badge--linked {
    cursor: pointer;
    min-height: 44px;
    box-sizing: border-box;
    transition: opacity 120ms ease-out;
    /* Link-mode colour. Badge's linked variant IS an `<a>`, so the
     * badge's text colour becomes the link colour. Precedence:
     * per-instance `--gc-badge-link` override (Custom mode in the
     * inspector) → role-default `--gc-badge-link-default`
     * (from the .gc-library-badge--<role> class above) → falls
     * through to the regular text colour cascade when link
     * defaults aren't set on a role. Added 2026-05-15 for Phase
     * 7b's locked sidebar pattern. */
    color: var( --gc-badge-link, var( --gc-badge-link-default, var( --gc-badge-text, var( --gc-badge-text-default, currentColor ) ) ) );
}
.gc-library-badge--linked:hover {
    opacity: 0.88;
}
.gc-library-badge--linked:focus-visible {
    /* Distinct from :hover. Outline at currentColor with 3px
     * offset; no animation so the ring lands instantly per
     * WCAG 2.4.7 spirit. */
    outline: 2px solid currentColor;
    outline-offset: 3px;
}

/* The clip-path silhouettes (shield, tab, triangle) hide outlines
 * because clip-path crops everything outside the polygon, including
 * the outline. Substitute with a visible box-shadow ring that the
 * clip leaves alone, drawn INSIDE the clip area. */
.gc-library-badge--linked.gc-library-badge--shield:focus-visible,
.gc-library-badge--linked.gc-library-badge--tab:focus-visible,
.gc-library-badge--linked.gc-library-badge--triangle:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 2px currentColor;
}

/* ─── Drop shadow via universal Canvas Style panel ──────────
 * Badge consumes the universal Canvas Style panel's rich
 * dropShadow control (X / Y / blur / spread / color) like every
 * other block. PHP-side `StyleAttribute::build_declarations()`
 * emits both `box-shadow:...` (the standard property) AND the
 * shadow values as custom properties (`--gc-shadow-x` / -y /
 * -blur / -color) so the CSS rules below can convert the cropped
 * box-shadow into a clip-path-aware `filter: drop-shadow` for
 * the silhouettes that need it.
 *
 * Rectangular silhouettes (pill / rectangle / square / circle)
 * render the universal box-shadow normally — no override needed.
 *
 * Clip-path silhouettes (shield / tab / triangle) crop box-shadow
 * along with everything outside the polygon. The override unsets
 * box-shadow and rebuilds the same visual via filter: drop-shadow
 * which follows the clip outline. Spread is silently dropped —
 * filter: drop-shadow doesn't support it. Acceptable trade-off
 * for the relatively rare clip-path + spread combination.
 *
 * The fallback chain ensures rectangular badges with NO shadow
 * set get no filter applied (var() falls through to nothing →
 * filter is invalid → ignored).
 */
.gc-library-badge--shield,
.gc-library-badge--tab,
.gc-library-badge--triangle {
    box-shadow: none !important;
}
/* v2.2.31 restored `filter: drop-shadow` on the OUTER badge.
 *
 * Now that v2.2.31 moved clip-path from the outer to a `::before`
 * pseudo (see the silhouette restructure block above),
 * the outer element has no clip-path of its own and no
 * stacking context from clip-path. That removes both blocks
 * that prevented drop-shadow from painting in v2.2.26 and
 * earlier:
 *   - drop-shadow no longer competes with clip-path on the
 *     same element for the rasterization pipeline
 *   - no stacking context flipping z-index expectations
 *
 * Spread-fold (v2.2.26) still applies: filter:drop-shadow
 * has no `spread` parameter, so we fold spread into blur via
 * `max(0px, calc(blur + spread))`. The visual reach matches
 * what authors set for the rectangular silhouettes.
 *
 * `::before` for the silhouette bg (set earlier in this file)
 * uses an implicit z-index: auto and natural paint order, so
 * the outer badge's filter sees the pseudo's clipped bg +
 * the icon + the label as one rasterized layer. drop-shadow
 * follows that combined alpha — a halo following the polygon
 * outline. */
.gc-library-badge--shield[style*="--gc-shadow-color"],
.gc-library-badge--tab[style*="--gc-shadow-color"],
.gc-library-badge--triangle[style*="--gc-shadow-color"] {
    /* v2.2.32 intensity-match: stack drop-shadow twice.
     *
     * `filter: drop-shadow()` uses a Gaussian falloff with no
     * solid-color inner region the way `box-shadow`'s `spread`
     * does. Same effective radius, but the energy per pixel is
     * lower, so the clip-path silhouettes' glows appeared
     * subtler than the rectangular silhouettes' `box-shadow`
     * glows.
     *
     * Stacking the same drop-shadow twice in a single `filter`
     * declaration roughly doubles the alpha intensity per pixel
     * without changing the shape or extent. Two applications
     * is the sweet spot from user testing: three becomes too
     * heavy for the brighter shadow colors, one leaves a
     * noticeable gap vs the box-shadow rectangulars. */
    filter:
        drop-shadow(
            var(--gc-shadow-x, 0)
            var(--gc-shadow-y, 0)
            max( 0px, calc( var(--gc-shadow-blur, 0px) + var(--gc-shadow-spread, 0px) ) )
            var(--gc-shadow-color, transparent)
        )
        drop-shadow(
            var(--gc-shadow-x, 0)
            var(--gc-shadow-y, 0)
            max( 0px, calc( var(--gc-shadow-blur, 0px) + var(--gc-shadow-spread, 0px) ) )
            var(--gc-shadow-color, transparent)
        );
}

/* ─── Scratch block (throwaway sidebar sandbox) ──────────────
 * Frontend rules for `gillish-canvas/scratch`. Delete this whole
 * section when the scratch block is retired; the touchpoint list
 * in ScratchModule.php's docblock names this stylesheet section
 * explicitly.
 *
 * Custom-property bridge: the PHP renderer writes effective bg /
 * text into `--gc-scratch-bg` / `--gc-scratch-text` (after
 * resolving role default + per-instance overrides). The CSS reads
 * them with neutral fallbacks so a scratch block with no role +
 * no overrides still has a visible silhouette. */
.gc-scratch {
    display: block;
    padding: 16px 20px;
    border-radius: 6px;
    background-color: var( --gc-scratch-bg, oklch(96% 0.003 250) );
    color: var( --gc-scratch-text, oklch(20% 0.04 250) );
    border: 1px solid oklch(88% 0.01 250 / 0.6);
}
.gc-scratch__label {
    display: block;
    font-weight: 500;
    line-height: 1.4;
}
/* Link colour bridge. ScratchModule writes effective link
 * colour into `--gc-scratch-link` (role default in named-preset
 * mode, override in Custom mode). Anchors inside the scratch
 * wrapper inherit that colour; fallback to `currentColor` so a
 * block with no link colour set keeps the text colour. */
.gc-scratch a {
    color: var( --gc-scratch-link, currentColor );
}
/* Border opacity override. The editor (scratch's editor.js) and
 * the PHP renderer both write the colour-mixed border-colour
 * into `--gc-scratch-border-color` when borderOpacity is set.
 * The substring selector matches the inline style attribute so
 * the rule fires only when the variable is actually declared —
 * a block with full opacity never enters this branch and keeps
 * whatever border-colour Gutenberg's BorderControl emitted.
 * `!important` is the price of admission: Gutenberg writes
 * border-color as inline style, our rule needs to win regardless
 * of merge order or wrapper nesting. */
.gc-scratch[style*="--gc-scratch-border-color:"] {
    border-color: var( --gc-scratch-border-color ) !important;
}
