/*
 * GreekFunder component layer — reusable gf-* classes (design-bible §2–§5).
 * Bootstrap-first: a rule lives here only when Bootstrap has no equivalent.
 * Page-specific CSS (rare) goes in /dist/pages/<page>.css instead.
 */

/* The hidden attribute always wins — Bootstrap's display utilities (.d-flex
   etc.) are !important and come AFTER reboot's [hidden] rule in its own
   bundle, so a JS-toggled `el.hidden = true` on a utility-classed element is
   otherwise a visual no-op (bit the checkout fee row, audit 2026-06-12).
   This file loads after Bootstrap, so this re-assertion ends the argument. */
[hidden] {
    display: none !important;
}

/* ── Layout chrome ─────────────────────────────────────────────────────────── */

.gf-main {
    padding: 1.5rem 0 3rem;
}

/* Description text keeps its authored line breaks (fundraiser/event
   descriptions are plain text; newlines are paragraph intent). */
.gf-prewrap { white-space: pre-line; }

/* ── Rich descriptions (admin-authored, sanitized HTML) ───────────────────────
   Rendered ONLY via partials/ui/richDescription.ejs (the lone <%- %> site). The
   allowlist is bold/italic/underline/lists/links/color spans
   (services/moderation/htmlSanitizer.ts). `pre-line` preserves authored newlines
   in legacy plain-text rows (zero migration) AND alongside <br>/<p> structure.
   All styling lives here under style-src 'self' — no inline styles. */
.gf-rich { white-space: pre-line; }
.gf-rich p { margin: 0 0 0.5rem; }
.gf-rich p:last-child { margin-bottom: 0; }
.gf-rich ul,
.gf-rich ol { margin: 0 0 0.5rem; padding-left: 1.5rem; }
.gf-rich li { margin-bottom: 0.25rem; }
.gf-rich a { color: var(--gf-primary); text-decoration: underline; }

/* Fixed text-color palette for the editor's color tool. A CLOSED set — only
   these classes survive sanitization (htmlSanitizer RICH_COLOR_CLASSES), so a
   span can carry color without any inline style (CSP stays style-src 'self').
   Brand colors first (blue/gold/ink track the theme tokens), then the most-used
   document emphasis colors. Keep in sync with descriptionEditor.js PALETTE. */
.gf-text-blue { color: var(--gf-primary); }   /* brand primary */
.gf-text-gold { color: var(--gf-accent); }    /* brand accent */
.gf-text-ink { color: var(--gf-ink); }        /* near-black */
.gf-text-red { color: #c0392b; }
.gf-text-green { color: #1e7e34; }
.gf-text-purple { color: #6f42c1; }

/* Restrict the description editor's color tool to the fixed palette swatches:
   hide SunEditor's free-text hex input + its submit so an off-palette color
   (which the server drops — only .gf-text-* classes survive) can't be typed and
   then silently lost on save. The "remove color" button stays. */
.sun-editor ._se_color_picker_input,
.sun-editor ._se_color_picker_submit { display: none; }

/* ── Navbar — two visual states (design-bible §3) ─────────────────────────────
   Solid by default. Pages with a hero set <body data-nav-transparent>; nav.js
   adds .gf-nav-overlay until the page scrolls past the threshold. */

.gf-nav {
    background-color: var(--gf-surface);
    border-bottom: 1px solid var(--gf-border);
    transition: background-color 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}

.gf-nav.gf-nav-scrolled {
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}

.gf-nav.gf-nav-overlay {
    background-color: transparent;
    border-bottom-color: transparent;
}

.gf-nav .navbar-brand {
    font-family: var(--gf-font-display);
    font-weight: 700;
    font-size: 1.4rem;
}

/* Accent underline that grows in on hover/active (desktop nav only — the
   collapsed mobile menu reads better without it). */
@media (min-width: 992px) {
    .gf-nav .nav-link {
        position: relative;
    }

    .gf-nav .nav-link::after {
        content: '';
        position: absolute;
        left: 0.5rem;
        right: 0.5rem;
        bottom: 0.15rem;
        height: 2px;
        border-radius: 2px;
        background: var(--gf-accent);
        transform: scaleX(0);
        transform-origin: left;
        transition: transform 0.2s ease;
    }

    .gf-nav .nav-link:hover::after,
    .gf-nav .nav-link.active::after {
        transform: scaleX(1);
    }
}

@media (prefers-reduced-motion: reduce) {
    .gf-nav .nav-link::after { transition: none; }
}

/* Tier-1 touch-target floor (design-bible §3) — utility classes alone top out
   around 42px for navbar buttons. */
.gf-nav .btn {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
}

/* ── Footer — dark ink band grounding every page ──────────────────────────── */

.gf-footer {
    background-color: var(--gf-ink);
    color: rgba(255, 255, 255, 0.78);
}

.gf-footer a {
    color: rgba(255, 255, 255, 0.78);
    text-decoration: none;
}

.gf-footer a:hover { color: var(--gf-accent); }

.gf-footer .gf-footer-brand {
    font-family: var(--gf-font-display);
    font-weight: 700;
    color: #fff;
    font-size: 1.35rem;
}

.gf-footer .gf-footer-heading {
    color: #fff;
    font-size: 1rem;
}

.gf-footer hr { border-color: rgba(255, 255, 255, 0.14); }

.gf-footer .gf-footer-fine { color: rgba(255, 255, 255, 0.55); }

.gf-footer .gf-footer-tagline { max-width: 24rem; }

/* The crest webp is dark-on-transparent — invert it on the ink band. */
.gf-footer .gf-footer-logo { filter: brightness(0) invert(1); }

/* Forced colors strips the ink band to Canvas but leaves filters running —
   drop the invert only on light forced themes (dark themes keep the working
   white-on-black invert). */
@media (forced-colors: active) and (prefers-color-scheme: light) {
    .gf-footer .gf-footer-logo { filter: none; }
}

/* ── Live-page money figures (features/livePageProgress.ejs) ──────────────── */

/* Big serif raised/goal figures on the funding-goal card. */
.gf-goal-figure {
    font-family: var(--gf-font-display);
    font-weight: 700;
    color: var(--gf-ink);
    font-size: 2.4rem;
    line-height: 1.05;
}

/* xs: two 2.4rem figures collide in a 360px card — step down. The card's
   Raised/Goal flex row also wraps as a backstop for five-digit goals. */
@media (max-width: 575.98px) {
    .gf-goal-figure { font-size: 1.5rem; }
}

/* Goal-card progress: Bootstrap .progress with a chunkier track. */
.gf-goal-bar { height: 14px; }

/* Flex-child text truncation needs an explicit min-width reset (Bootstrap has
   no min-width utility). */
.gf-min-w-0 { min-width: 0; }

/* Tier-1 touch-target floor (design-bible §3, same 44px contract as
   .gf-nav .btn) for donor-page CTAs that aren't already btn-lg — Bootstrap's
   btn-sm is ~31px and even the default btn is ~38px. */
.gf-btn-touch {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* ── Image frames (design-bible §5) — every uploaded image renders in one ──── */

.gf-img-cover {
    aspect-ratio: 4 / 3;
    object-fit: cover;
    width: 100%;
}

.gf-img-square {
    aspect-ratio: 1 / 1;
    object-fit: cover;
    width: 100%;
}

.gf-img-circle {
    aspect-ratio: 1 / 1;
    object-fit: cover;
    width: 100%;
    border-radius: 50%;
}

/* ── Image upload control (partials/ui/imageUploadField.ejs + imageUpload.js) ─
   The frame previews the EXACT final crop before save. The empty state shows
   a placeholder icon inside the same frame. */

.gf-image-upload-frame {
    position: relative;
    background-color: var(--gf-bg);
    border: 1px solid var(--gf-border);
    border-radius: 0.5rem;
    overflow: hidden;
    margin-bottom: 0.75rem;
}

.gf-frame-cover  { max-width: 400px; }
.gf-frame-square { max-width: 240px; }
.gf-frame-circle { max-width: 150px; border-radius: 50%; }

/* Reserve the frame's shape even when no image is present yet. */
.gf-image-upload-frame .gf-image-upload-placeholder {
    aspect-ratio: 4 / 3;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--gf-muted);
    font-size: 2rem;
}

.gf-frame-square .gf-image-upload-placeholder,
.gf-frame-circle .gf-image-upload-placeholder {
    aspect-ratio: 1 / 1;
}

/* ── Illustrations & avatars ──────────────────────────────────────────────── */

/* Transparent-background artwork (greek gods, hero art) — floats on the page
   background; never give it a frame/box-shadow (the box reads as a white
   card). drop-shadow() follows the alpha silhouette, so THAT one is allowed. */
.gf-illustration {
    max-height: 40vh;
    width: auto;
}

.gf-drop-shadow {
    filter: drop-shadow(0 24px 50px rgba(29, 27, 38, 0.16));
}

/* Square icon chip for feature/highlight tiles. */
.gf-icon-chip {
    width: 52px;
    height: 52px;
    display: grid;
    place-items: center;
    border-radius: 12px;
    background: rgba(var(--gf-primary-rgb), 0.08);
    color: var(--gf-primary);
    font-size: 1.25rem;
    border: 1px solid rgba(var(--gf-primary-rgb), 0.15);
}

/* Circular people/team imagery at fixed sizes (leaderboard avatars reuse this). */
.gf-avatar {
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 50%;
}

.gf-avatar-lg { width: 140px; }
.gf-avatar-sm { width: 56px; }
.gf-avatar-xs { width: 42px; }

/* Oversized serif open-quote for testimonial cards. */
.gf-quote-mark {
    font-family: var(--gf-font-display);
    font-size: 2.6rem;
    line-height: 0.4;
    color: var(--gf-accent-deep);
}

/* ── Cards & motion ───────────────────────────────────────────────────────── */

/* THE content card on the gray page background. Bootstrap's bare
   `card shadow-sm border-0` melts into --gf-bg — the hairline border plus a
   layered shadow (tight contact line + soft ambient) keep the edge legible
   (design-bible §1). */
.gf-card {
    background-color: var(--gf-surface);
    border: 1px solid var(--gf-border);
    border-radius: 0.5rem;
    box-shadow: 0 1px 2px rgba(29, 27, 38, 0.05), 0 8px 24px rgba(29, 27, 38, 0.07);
}

.gf-card-hover {
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.gf-card-hover:hover {
    transform: translateY(-2px);
    box-shadow: var(--bs-box-shadow) !important;
}

@keyframes gf-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

.gf-fade-in {
    animation: gf-fade-in 0.6s ease-out;
}

@media (prefers-reduced-motion: reduce) {
    .gf-fade-in { animation: none; }
    .gf-card-hover, .gf-card-hover:hover { transition: none; transform: none; }
}

/* ── Status pills (lifecycle badges, live indicators, type chips) ─────────── */

.gf-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-weight: 600;
    font-size: 0.78rem;
    padding: 0.3rem 0.75rem;
    border-radius: 999px;
    background: var(--gf-bg);
    color: var(--gf-ink);
    border: 1px solid var(--gf-border);
}

/* Pill text colors are the AA-on-tint shades (pill font is 0.78rem — the
   parent semantic colors fail 4.5:1 on these tinted backgrounds). */
.gf-pill-live {
    background: rgba(25, 135, 84, 0.1);
    color: var(--bs-success-text-emphasis);
    border-color: rgba(25, 135, 84, 0.25);
}

.gf-pill-accent {
    background: rgba(193, 149, 63, 0.14);
    /* Darker than --gf-accent-deep: that token is AA on white/--gf-bg only —
       on THIS tint (composites to ~#f6f0e4 over white) it measures 4.4:1.
       #7d5f17 is 5.3:1 on the tint (audit 2026-06-12). */
    color: #7d5f17;
    border-color: rgba(193, 149, 63, 0.3);
}

.gf-pill-info {
    background: rgba(var(--gf-primary-rgb), 0.1);
    color: #14509f;
    border-color: rgba(var(--gf-primary-rgb), 0.25);
}

.gf-pill .gf-pill-dot {
    width: 7px;
    height: 7px;
    border-radius: 999px;
    background: currentColor;
}

.gf-pill-dot-pulse { animation: gf-pulse 1.4s ease-in-out infinite; }

@keyframes gf-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.25; }
}

@media (prefers-reduced-motion: reduce) {
    .gf-pill-dot-pulse { animation: none; }
}

/* Pill pinned to a photo's bottom-right corner (SnapIt submission points).
   Pair with a `position-relative` wrapper around the <img>. The base .gf-pill
   surface is opaque, so legibility holds on any photo; the shadow separates
   it from like-colored image regions. */
.gf-img-badge {
    position: absolute;
    right: 0.5rem;
    bottom: 0.5rem;
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.25);
}

/* ── Leaderboard kit (features/leaderboardRow.ejs + fundraiserLive.js) ────────
   The legacy row treatment the founder asked back (2026-06-12): every entry is
   its own bordered, softly-shadowed white row — big rank number, 56px circle
   avatar, large name, serif amount on the right. No podium, no per-row bars. */

.gf-lb-row {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1rem;
    margin-bottom: 0.5rem;
    border-radius: 0.375rem;
    border: 1px solid var(--gf-border);
    background: var(--gf-surface);
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}

/* Plain oversized rank number in a fixed slot (legacy: 1.5rem bold, 44px min). */
.gf-lb-rank {
    min-width: 44px;
    flex: none;
    text-align: center;
    font-family: var(--gf-font-display);
    font-weight: 700;
    font-size: 1.5rem;
    color: var(--gf-ink);
}

.gf-lb-name {
    font-weight: 700;
    font-size: 1.35rem;
    line-height: 1.2;
}

.gf-lb-amount {
    font-family: var(--gf-font-display);
    font-weight: 700;
    font-size: 1.25rem;
    color: var(--gf-ink);
    white-space: nowrap;
}

/* Initial-letter avatar fallback when an entity has no image. */
.gf-avatar-initials {
    display: grid;
    place-items: center;
    font-family: var(--gf-font-display);
    font-weight: 700;
    color: #fff;
    background: var(--gf-primary-hover);
    aspect-ratio: 1 / 1;
    border-radius: 50%;
}

/* Leaderboard avatars at the legacy 56px (42px on xs, with the indent math
   in the media block below). Both the initials fallback and the team image
   (a .gf-avatar <img>) take the same footprint so rows align either way. */
.gf-lb-row .gf-avatar-initials,
.gf-lb-row .gf-avatar {
    width: 56px;
    flex: none;
}

@media (max-width: 575.98px) {
    .gf-lb-row .gf-avatar-initials,
    .gf-lb-row .gf-avatar { width: 42px; }
}

/* Org-dashboard fundraiser list-row thumbnail — a fixed-width 4:3 box on the
   left of each row (same fixed-box pattern as .gf-cart-thumb: .gf-img-cover is
   fill-parent, so the wrapper supplies the box). The coverless state holds the
   same box so rows stay aligned with or without an uploaded image. */
.gf-row-thumb {
    width: 112px;
    flex: none;
    border-radius: 0.375rem;
    overflow: hidden;
}

.gf-row-thumb-empty {
    aspect-ratio: 4 / 3;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--gf-bg);
    color: var(--gf-muted);
    font-size: 1.4rem;
    border: 1px solid var(--gf-border);
    border-radius: 0.375rem;
}

@media (max-width: 575.98px) {
    .gf-row-thumb { width: 84px; }
}

/* Slim progress track for dense list rows (the goal card uses the chunkier
   .gf-goal-bar; admin list rows want a thinner line). */
.gf-progress-slim { height: 8px; }

/* Owner line on the cross-org Fundraisers listing cards (greek · chapter). */
.gf-card-org { color: var(--gf-muted); }

/* Coverless entity-card placeholder — holds the 4:3 cover frame so card grids
   stay aligned whether or not an image was uploaded. */
.gf-entity-placeholder {
    aspect-ratio: 4 / 3;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--gf-bg);
    color: var(--gf-muted);
    font-size: 2rem;
    border-bottom: 1px solid var(--gf-border);
}

/* xs: the desktop row type is too big for a phone. Step the sizes + spacing
   down but keep the row on ONE line — rank · avatar · name (truncates) ·
   amount (right). The amount no longer wraps to a second indented line (which
   read as broken on phones); the name's gf-min-w-0 + text-truncate absorbs the
   overflow so the amount always stays put. A clean, compact leaderboard row. */
@media (max-width: 575.98px) {
    .gf-lb-row {
        gap: 0.5rem;
        padding: 0.75rem;
    }

    .gf-lb-rank {
        min-width: 1.6rem;
        font-size: 1.1rem;
    }

    .gf-lb-name { font-size: 1rem; }

    .gf-lb-amount { font-size: 1rem; }
}

/* ── Underline tabs (leaderboard panes, dashboard sections) ───────────────── */

.gf-tabs {
    border-bottom: 1px solid var(--gf-border);
}

.gf-tabs .nav-link {
    border: 0;
    border-bottom: 2.5px solid transparent;
    border-radius: 0;
    color: var(--gf-muted);
    font-weight: 600;
    padding: 0.55rem 1rem;
    background: transparent;
}

.gf-tabs .nav-link.active {
    color: var(--gf-ink);
    border-bottom-color: var(--gf-accent);
    background: transparent;
}

/* ── Amount chips (checkout presets, quick-donate) ────────────────────────── */

.gf-amount {
    flex: 1 1 auto;
    min-width: 64px;
    /* Tier-1 touch floor (§3) — these are tap targets on the phone-dominant
       checkout. */
    min-height: 44px;
    border: 1.5px solid var(--gf-border);
    background: var(--gf-surface);
    color: var(--gf-ink);
    font-weight: 700;
    border-radius: 0.375rem;
    padding: 0.6rem 0.4rem;
    transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.gf-amount:hover { border-color: var(--gf-primary); }

.gf-amount.active {
    background: var(--gf-primary);
    color: #fff;
    border-color: var(--gf-primary);
}

/* Equal-width legacy increment row ($5–$500). Sizing lives on the wrapper —
   the button's bare class="gf-amount" attribute is test-pinned. */
.gf-amount-row .gf-amount { flex: 1 1 0; }

/* Quantity inputs in cart item rows — a number field has no business spanning
   the column (legacy used a ~100px field). */
.gf-qty { max-width: 110px; }

/* Small admin-table thumbnail frame (shop-item image cells; .gf-img-square is
   fill-parent — the frame supplies the box, same rule as .gf-cart-thumb). */
.gf-admin-thumb {
    width: 60px;
    flex: none;
}

/* ── Ticket stub (ticket-event live page — the legacy signature) ──────────────
   Flex card: serif price block left, dashed divider with punched-circle
   cutouts (colored --gf-bg so they read as holes in the stub), ticket info +
   quantity right. */

.gf-ticket-stub {
    display: flex;
    background: var(--gf-surface);
    border: 1px solid var(--gf-border);
    border-radius: 0.5rem;
    overflow: hidden;
    /* Stubs sit directly on the page background (the punched circles read as
       holes only against --gf-bg) — the soft card shadow lifts them off it. */
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}

.gf-ticket-stub .gf-ticket-price {
    position: relative;
    flex: none;
    width: 7rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 1rem 0.5rem;
    background: rgba(var(--gf-primary-rgb), 0.06);
    border-right: 2px dashed var(--gf-border);
    font-family: var(--gf-font-display);
    font-weight: 700;
    font-size: 1.15rem;
    color: var(--gf-ink);
}

/* Punched circles straddling the dashed divider. */
.gf-ticket-stub .gf-ticket-price::before,
.gf-ticket-stub .gf-ticket-price::after {
    content: '';
    position: absolute;
    right: -9px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--gf-bg);
    border: 1px solid var(--gf-border);
}

.gf-ticket-stub .gf-ticket-price::before { top: -9px; }
.gf-ticket-stub .gf-ticket-price::after { bottom: -9px; }

.gf-ticket-stub .gf-ticket-body {
    flex: 1 1 auto;
    min-width: 0;
    padding: 1rem 1.25rem;
}

/* Sold-out stubs gray the SURFACES, never the text — an opacity dim would
   drag the "Sold out" status line below AA (audit 2026-06-12). The status
   line itself must use .gf-text-danger-deep: Bootstrap's .text-danger is
   only 4.3:1 on this gray. */
.gf-ticket-stub.gf-ticket-soldout {
    background: var(--gf-bg);
}

/* AA-safe danger text for gray surfaces (--gf-bg / .bg-light): Bootstrap's
   #dc3545 measures 4.3:1 there (AA fail). #b02a37 = 6.1:1 on --gf-bg,
   6.5:1 on white. */
.gf-text-danger-deep { color: #b02a37; }

.gf-ticket-stub.gf-ticket-soldout .gf-ticket-price {
    background: transparent;
    color: var(--gf-muted);
}

/* Fixed-size cart thumbnail frame — .gf-img-square is fill-parent by design
   (width: 100%), so a bare width attribute loses to it; the frame supplies
   the parent box. */
.gf-cart-thumb {
    width: 96px;
    flex: none;
}

@media (max-width: 575.98px) {
    .gf-ticket-stub .gf-ticket-price { width: 5.5rem; font-size: 1rem; }
}

/* ── Reveal-on-scroll (reveal.js adds .gf-reveal-in via IntersectionObserver) ─
   Hidden state is scoped under .gf-js (set by reveal.js itself) so content is
   never invisible when JS doesn't run. */

.gf-js .gf-reveal {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.gf-js .gf-reveal.gf-reveal-in {
    opacity: 1;
    transform: none;
}

@media (prefers-reduced-motion: reduce) {
    .gf-js .gf-reveal { opacity: 1; transform: none; transition: none; }
}

/* ── gf-form (design-bible §9) ────────────────────────────────────────────── */

/* Compact form-level failure alert, auto-created by gf-form.js when absent. */
.gf-form-alert {
    font-size: 0.9rem;
    padding: 0.6rem 0.9rem;
    margin-bottom: 1rem;
}

/* ── Empty state (ui/emptyState.ejs) ──────────────────────────────────────── */

.gf-empty {
    text-align: center;
    color: var(--gf-muted);
    padding: 2.5rem 1rem;
}

.gf-empty .gf-empty-icon {
    font-size: 2rem;
    display: block;
    margin-bottom: 0.5rem;
}

/* ── Share dialog (markup built by /dist/share.js — class contract §6) ─────── */

.share-button { cursor: pointer; }

.share-dialog {
    border: none;
    border-radius: 0.5rem;
    padding: 0;
    max-width: 24rem;
    width: 90%;
    box-shadow: var(--bs-box-shadow-lg);
}

.share-dialog::backdrop { background: rgba(0, 0, 0, 0.4); }

.share-dialog-body { padding: 1.25rem; }

.share-dialog-title {
    margin: 0 0 0.75rem;
    font-size: 1.1rem;
    font-weight: 700;
}

.share-dialog-image {
    display: block;
    max-width: 100%;
    max-height: 8rem;
    margin: 0 0 0.75rem;
    border-radius: 0.375rem;
}

.share-social {
    display: flex;
    gap: 0.5rem;
    margin: 0 0 0.75rem;
}

.share-social-link {
    flex: 1;
    text-align: center;
    padding: 0.5rem 0;
    border-radius: 0.375rem;
    background: var(--gf-bg);
    border: 1px solid var(--gf-border);
    text-decoration: none;
    color: var(--gf-body);
}

.share-social-link:hover {
    background: var(--gf-primary);
    border-color: var(--gf-primary);
    color: var(--gf-surface);
    text-decoration: none;
}

.share-copy-row { display: flex; gap: 0.5rem; }

.share-link-input {
    flex: 1;
    min-width: 0;
    border: 1px solid var(--gf-border);
    border-radius: 0.375rem;
    padding: 0.375rem 0.75rem;
}

.share-toast { color: var(--bs-success); margin: 0.5rem 0 0; }

.share-close-button {
    margin-top: 0.75rem;
    background: none;
    border: none;
    padding: 0;
    text-decoration: underline;
    cursor: pointer;
}

/* The TRANSITIONAL block that carried un-restyled pages through the overhaul
   was deleted in slice 9 — every page now sits on Bootstrap + the gf-*
   classes above. */
