/* E156 — WCAG 2.1 AA text contrast. This bundled Bootstrap's muted-text
   utilities resolve to #6c757d (.text-secondary via --bs-secondary-rgb)
   and #212529 @ 50% ≈ #909396 (.text-body-tertiary) — both under 4.5:1
   on the app's light surfaces (esp. the #f8f9fa / #e9ecef section
   backgrounds). Re-point the muted-TEXT utilities directly to AA-passing
   greys; deliberately NOT touching --bs-secondary-rgb, which also drives
   .bg-secondary / .btn-secondary / badges. Scoped to non-dark so
   Bootstrap's own dark palette is untouched (the shell renders
   data-bs-theme="auto"; a real dark theme sets data-bs-theme="dark").
   Higher specificity + !important to beat the utility's own !important. */
:root:not([data-bs-theme="dark"]) {
    /* Feeds --search-muted / --blog-public-muted and any other
       var(--bs-secondary-color) consumer. */
    --bs-secondary-color: #5c6067;
    --bs-tertiary-color: #62666d;

    /* Bootstrap's default brand blue #0d6efd is only ~3.7-4.4:1 on white
       and the pale tinted panels used throughout the plugin UIs (#f5f9ff,
       #f8f9fa, #f9fafb, #e4efff) - fine as a border/fill, under AA
       wherever it's used as TEXT (which plugin CSS does liberally:
       eyebrows, section labels, active nav links, category chips, the
       .btn-outline-primary link on the cart sign-in prompt, .text-primary
       body copy on the default landing page). Rather than chase each
       `color: var(--bs-primary)` call site per plugin, darken the token
       itself on the default (un-themed) light theme. #0a58ca ≈ 5.9:1 on
       white, still clearly "brand blue". -rgb must move in lockstep or
       rgba(var(--bs-primary-rgb), 1) consumers like .text-primary stay
       light. A real dark theme (data-bs-theme="dark") is untouched; a
       themed site that sets its own --bs-primary overrides this. */
    --bs-primary: #0a58ca;
    --bs-primary-rgb: 10, 88, 202;
    --bs-primary-text-emphasis: #084298;
    --bs-link-color: #0a58ca;
    --bs-link-color-rgb: 10, 88, 202;
    --bs-link-hover-color: #084298;
    --bs-link-hover-color-rgb: 8, 66, 152;
}
:root:not([data-bs-theme="dark"]) .text-secondary,
:root:not([data-bs-theme="dark"]) .text-body-secondary,
:root:not([data-bs-theme="dark"]) .text-muted {
    color: #5c6067 !important;
}
:root:not([data-bs-theme="dark"]) .text-body-tertiary {
    color: #62666d !important;
}
/* Bootstrap COMPILES .btn-outline-primary / -secondary with a LITERAL
   --bs-btn-color (#0d6efd / #6c757d), not var(--bs-primary) - so the
   token darkening above doesn't reach them. Override the resting text
   token for both (hover/active flip to a solid fill, left alone). Used by
   the cart's "Sign in" prompt and the booking form's "Apply ZIP". */
:root:not([data-bs-theme="dark"]) .btn-outline-primary {
    --bs-btn-color: #0a58ca;
}
:root:not([data-bs-theme="dark"]) .btn-outline-secondary {
    --bs-btn-color: #5c6067;
}
/* Deeply-nested comment replies stack a translucent tint per level, so
   4+ deep the card background darkens to ~#c2cbd8 and the small .btn-link
   "Reply" / .comment-date drop under 4.5:1. Darken both with enough
   margin to clear the deepest compounded background. */
:root:not([data-bs-theme="dark"]) #comments-list .comment-reply-button {
    --bs-btn-color: #084298;
    color: #084298;
}
:root:not([data-bs-theme="dark"]) #comments-list .comment-date,
:root:not([data-bs-theme="dark"]) #comments-list .comment-react-score,
:root:not([data-bs-theme="dark"]) #comments-list .text-muted {
    color: #43474d !important;
}

/* E156 — skip-to-content link (WCAG 2.4.1). Bootstrap's
   .visually-hidden-focusable un-hides it on focus; this makes it a real,
   legible control sitting above the sticky nav. */
.cms-skip-link:focus,
.cms-skip-link:focus-visible {
    position: fixed;
    top: 0.5rem;
    left: 0.5rem;
    z-index: 2000;
    padding: 0.5rem 0.9rem;
    background: var(--bs-body-bg, #fff);
    color: var(--bs-body-color, #212529);
    border: 2px solid var(--bs-primary, #0d6efd);
    border-radius: 0.375rem;
    text-decoration: none;
    font-weight: 600;
}

/* E156 — WCAG 2.3.3 (Animation from Interactions) / 2.2.2 (Pause, Stop,
   Hide). Honour the OS "reduce motion" setting on all public output: the
   custom nav-dropdown keyframes (_primary_navigation.html), the
   :target block-flash and fullscreen fades (below in this file),
   hand-authored content transitions, and anything Bootstrap's own
   reduced-motion rules miss. Uses ~0ms rather than 0 so transitionend /
   animationend still fire and JS that awaits them doesn't hang; keeps
   iteration-count at 1 so infinite spinners settle instead of freezing
   mid-frame. scroll-behavior:auto disables smooth-scroll jumps.
   styles.css is public-only, so this never reaches the admin UI. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* Quill alignment styles */
.ql-align-center {
    text-align: center;
}
.ql-align-right {
    text-align: right;
}
.ql-align-justify {
    text-align: justify;
}
.ql-align-left {
    text-align: left;
}

/* Custom bg colors */
.bg-primary-light {
    background-color: #f8f9fa;
}
.bg-secondary-light {
    background-color: #e9ecef;
}
/* The 7 extended color swatches below (beyond Bootstrap's 8 semantic
   colors) are now driven by a --cms-color-* custom property per swatch,
   consumed via var(..., <original hex>) so an admin can override one
   sitewide from Theme Settings' existing brand_tokens JSON textarea (see
   docs/BLOCK_TYPE_DESIGN_TOKENS.md) with zero visual change until they do -
   the var() fallback IS the shipped default. Part of the Content Block
   Type Styling plan's Phase 0 (docs/BLOCK_TYPE_STYLING_PLAN.md). Text
   color/link underline/close-button filter deliberately stay fixed
   literals, not tokens - all 7 swatches are light/mid-tone enough for
   black text to stay legible, and re-deriving contrast per swatch (the
   way Theme Settings' own 8 semantic colors already do via
   themeContrastHex) is out of scope for this pass. */
.bg-pink {
    background-color: var(--cms-color-pink, #ffc0cb);
}
/* pink alert */
.alert.bg-pink {
    background-color: var(--cms-color-pink, #ffc0cb);
    color: #000;
    border-color: var(--cms-color-pink, #ffc0cb);
}
.alert.bg-pink a {
    color: #000;
    text-decoration: underline;
}
.alert.bg-pink a:hover {
    color: #000;
    text-decoration: underline;
}
.alert.bg-pink a:focus {
    color: #000;
    text-decoration: underline;
}
/* Custom CSS to force the close button to be white for the pink alert */
.alert.bg-pink .btn-close {
    filter: none;
}
/* lavender alert */
.alert.bg-lavender {
    background-color: var(--cms-color-lavender, #e6e6fa);
    color: #000;
    border-color: var(--cms-color-lavender, #e6e6fa);
}
.alert.bg-lavender a {
    color: #000;
    text-decoration: underline;
}
.alert.bg-lavender a:hover {
    color: #000;
    text-decoration: underline;
}
.alert.bg-lavender a:focus {
    color: #000;
    text-decoration: underline;
}
.alert.bg-lavender .btn-close {
    filter: none;
}
/* violet alert */
.alert.bg-violet {
    background-color: var(--cms-color-violet, #ee82ee);
    color: #000;
    border-color: var(--cms-color-violet, #ee82ee);
}
.alert.bg-violet a {
    color: #000;
    text-decoration: underline;
}
.alert.bg-violet a:hover {
    color: #000;
    text-decoration: underline;
}
.alert.bg-violet a:focus {
    color: #000;
    text-decoration: underline;
}
.alert.bg-violet .btn-close {
    filter: none;
}
/* orange alert */
.alert.bg-orange {
    background-color: var(--cms-color-orange, #ffa500);
    color: #000;
    border-color: var(--cms-color-orange, #ffa500);
}
.alert.bg-orange a {
    color: #000;
    text-decoration: underline;
}
.alert.bg-orange a:hover {
    color: #000;
    text-decoration: underline;
}
.alert.bg-orange a:focus {
    color: #000;
    text-decoration: underline;
}
.alert.bg-orange .btn-close {
    filter: none;
}
/* goldenrod alert */
.alert.bg-goldenrod {
    background-color: var(--cms-color-goldenrod, #daa520);
    color: #000;
    border-color: var(--cms-color-goldenrod, #daa520);
}
.alert.bg-goldenrod a {
    color: #000;
    text-decoration: underline;
}
.alert.bg-goldenrod a:hover {
    color: #000;
    text-decoration: underline;
}
.alert.bg-goldenrod a:focus {
    color: #000;
    text-decoration: underline;
}
.alert.bg-goldenrod .btn-close {
    filter: none;
}
/* silver alert */
.alert.bg-silver {
    background-color: var(--cms-color-silver, #c0c0c0);
    color: #000;
    border-color: var(--cms-color-silver, #c0c0c0);
}
.alert.bg-silver a {
    color: #000;
    text-decoration: underline;
}
.alert.bg-silver a:hover {
    color: #000;
    text-decoration: underline;
}
.alert.bg-silver a:focus {
    color: #000;
    text-decoration: underline;
}
.alert.bg-silver .btn-close {
    filter: none;
}
/* copper alert */
.alert.bg-copper {
    background-color: var(--cms-color-copper, #b87333);
    color: #000;
    border-color: var(--cms-color-copper, #b87333);
}
.alert.bg-copper a {
    color: #000;
    text-decoration: underline;
}
.alert.bg-copper a:hover {
    color: #000;
    text-decoration: underline;
}
.alert.bg-copper a:focus {
    color: #000;
    text-decoration: underline;
}
.alert.bg-copper .btn-close {
    filter: none;
}


img.hover-shadow {
  transition: 0.3s;
}

.hover-shadow:hover {
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}

.small {
    font-size: 0.875rem;
}
.x-small {
    font-size: 0.75rem;
}
.xx-small {
    font-size: 0.625rem;
}

/* Custom CSS to force the close button to be white */
[data-bs-theme=dark] .lightbox > .modal-dialog > .modal-content > .modal-body > .btn-close {
    filter: none !important;
}
/* This rule targeted ONLY the -icon span for a long time, which never
   actually fixed anything - confirmed live (2026-08-14) via a real
   invisible-Prev/Next-arrow report in the lightbox photo viewer.
   Bootstrap's real inverting filter (--bs-carousel-control-icon-filter,
   which resolves to invert(1) grayscale(100) under [data-bs-theme=dark])
   is declared on .carousel-control-prev/.carousel-control-next THEMSELVES
   (the <button>, confirmed via the actual compiled vendor.css rule:
   `.carousel-control-prev{...filter:var(--bs-carousel-control-icon-filter);...}`)
   - NOT on the child .carousel-control-*-icon <span> that renders the
   actual chevron glyph. A CSS `filter` on a parent applies to that
   element's entire rendered subtree as one composited layer, so the
   inversion was always still fully in effect on the button regardless of
   this rule setting filter:none on the icon span alone - getComputedStyle
   on the icon reported 'none' (this rule "worked" by that narrow measure)
   while the BUTTON's own computed filter still read 'invert(1)
   grayscale(1)', silently inverting the white icon SVG to black - on the
   lightbox's own always-black modal backdrop, a black icon is completely
   invisible, not just low-contrast. Fixed by targeting the real element. */
[data-bs-theme="dark"] .carousel-control-prev,
[data-bs-theme="dark"] .carousel-control-next {
    filter: none !important;
}
/* Same Bootstrap dark-theme quirk as the arrow icons just above, other
   half: --bs-carousel-caption-color flips from #fff to #000 under
   [data-bs-theme=dark] (Bootstrap assumes a carousel might want a
   `.carousel-dark`-style inverted look site-wide) - confirmed live building
   the `carousel` content-block type (docs/DEVELOPMENT_BACKLOG.md E46): a
   real caption over a real uploaded image rendered invisible-on-light-image
   black text in dark theme despite looking correct in light theme. A
   carousel slide's caption should be styled based on the image behind it,
   not the site's overall theme - same reasoning as the arrow-icon fix. */
[data-bs-theme="dark"] .carousel-caption {
    color: #fff;
}


/* ── Form submission inline feedback ───────────────────────────────────────── */
.form-submit-success {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    padding: 2rem 1.5rem;
    text-align: center;
    animation: fs-fadein 0.25s ease;
}
.form-submit-success__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    background: #198754;
    color: #fff;
    font-size: 1.5rem;
    line-height: 1;
}
.form-submit-success__text {
    margin: 0;
    font-size: 1rem;
    color: inherit;
}
.form-submit-success__reset {
    background: none;
    border: 1px solid currentColor;
    border-radius: 0.375rem;
    padding: 0.375rem 0.875rem;
    font-size: 0.875rem;
    cursor: pointer;
    opacity: 0.75;
    transition: opacity 0.15s;
}
.form-submit-success__reset:hover {
    opacity: 1;
}

.form-submit-error {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.625rem 0.875rem;
    margin-bottom: 0.75rem;
    border-radius: 0.375rem;
    background: #f8d7da;
    color: #842029;
    font-size: 0.875rem;
    animation: fs-fadein 0.2s ease;
}
[data-bs-theme=dark] .form-submit-error {
    background: #2c0b0e;
    color: #ea868f;
}
.form-submit-error__icon {
    flex-shrink: 0;
    font-size: 1rem;
}
.form-submit-error__text {
    flex: 1;
}
.form-submit-error__close {
    background: none;
    border: none;
    padding: 0 0.25rem;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    opacity: 0.6;
    color: inherit;
    transition: opacity 0.15s;
}
.form-submit-error__close:hover {
    opacity: 1;
}

/* Baseline vertical rhythm between content blocks, sitewide - confirmed live
   (getComputedStyle) that .content-block previously had zero margin/padding
   anywhere (only `position: relative`, in blue.css/cyan.css, needed for the
   inline editor's pencil buttons) - every block on every page butted flush
   against the next with no breathing room at all. A type that already
   supplies its own visual container (.card, .alert, .accordion, etc.) still
   benefits from this - it's space BETWEEN blocks, not inside one - and nothing
   here overrides a type's own internal spacing. The last block on a page
   getting a trailing bottom margin too is intentional, not worth special-
   casing: the footer already has its own top spacing/border that comfortably
   absorbs it. */
/* Tokenized per docs/BLOCK_TYPE_DESIGN_TOKENS.md (Phase 0,
   docs/BLOCK_TYPE_STYLING_PLAN.md) - a plain sitewide token, not a
   per-type one: unlike every other var() in this file, this margin isn't a
   type-specific design choice (every type gets the identical value), so it
   isn't surfaced on any one type's own StyleDefaults form - override it via
   Theme Settings' Custom CSS instead if needed. */
.content-block {
    margin-bottom: var(--cms-block-spacing-bottom, 2rem);
}

/* .cms-row-breakout - an opt-in utility for a Template Builder row's own
   Classes field (see _builder_content.html) that lets ONE row escape the
   page-level .container's max-width and bleed full viewport-width, while
   every sibling row stays properly contained - a hero banner conventionally
   spans edge-to-edge even on an otherwise-contained page; nothing else
   needs this. The standard CSS technique: counter the container's own
   horizontal centering by pushing left/right margins out to the viewport
   edge, then set width back to 100vw so content re-fills that space. Not a
   default behavior of any row - a row only gets this by explicitly naming
   the class, same as any other row/col Classes value. */
.cms-row-breakout {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
}

/* .cms-row-breakout's own 100vw is unavoidably wider than the true visible
   viewport whenever a real (non-overlay) scrollbar reserves its own gutter -
   100vw includes that gutter, while the 50% used for the negative margins
   above is based on the containing block's width (viewport minus the
   scrollbar), so the two disagree by exactly the scrollbar's width and the
   row's right edge sticks out past the screen edge. Confirmed live this is
   invisible in headless Chromium (always overlay scrollbars, 0px gutter) but
   real on a normal desktop browser - this is the actual, confirmed cause of
   a persistent sitewide horizontal scroll on any page using the class.
   overflow-x:hidden on body clips that few-pixel sliver invisibly rather
   than exposing a page-level horizontal scrollbar - safe sitewide since
   nothing here should ever rely on real body-level horizontal scroll (a wide
   table/code block should already scroll inside its own container, not the
   page - see gallery_block.html/file_block.html, none of which do this). */
body {
    overflow-x: hidden;
}

/* .block-title/.block-subtitle - the generic, type-agnostic label pair
   content_block.html renders above ANY block's own content when that block's
   ShowTitle/ShowSubtitle are on (independent of a type's own internal
   heading/subtext fields, e.g. hero's Heading or card's Heading - this is a
   page-authoring-level label, not part of a type's own Content). Confirmed
   live these had zero CSS anywhere - same plain 16px/400-weight body text as
   ordinary paragraph copy, indistinguishable from a block's own content.
   Styled here as a small "eyebrow" pair (uppercase, letter-spaced, brand-
   colored label + a muted description line) specifically so it reads as
   page-authoring metadata sitting ABOVE a block, not as part of the block's
   own visual design - deliberately smaller/quieter than a real heading so it
   never competes with a type's own internal heading (hero/card/etc.).
   var(--bs-primary)/var(--bs-secondary-color) are Bootstrap's own theme
   tokens, so this follows whichever color theme (blue.css/cyan.css) is
   active with no separate color choice of its own to keep in sync. */
.block-title {
    font-size: .8125rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--bs-primary);
    margin-bottom: .25rem;
}
.block-subtitle {
    font-size: .9375rem;
    color: var(--bs-secondary-color);
    margin-bottom: .75rem;
}

/* Widget-shell corner-radius/shadow, tokenized per
   docs/BLOCK_TYPE_DESIGN_TOKENS.md (Phase 0,
   docs/BLOCK_TYPE_STYLING_PLAN.md) - booking_widget/upcoming_events/
   donation_progress/newsletter_signup/location all used to hardcode the
   exact same literal "card rounded-4 shadow-sm" combo with no override
   hook at all; each type gets its own independent token pair here
   (rather than one shared value) since ContentBlockType.StyleDefaults is
   keyed per type code. Fallbacks match Bootstrap's own rounded-4/
   shadow-sm utility values exactly, so default rendering is unchanged.
   NOTE the token names below are hyphenated (booking-widget, not
   booking_widget) even though the real type Code has an underscore -
   backend/utils/theme.go's themeTokenKeyRe only allows [a-z0-9-] in a
   --custom-property name, same gotcha style_tokens.js's team_members
   entry already documents. */
.bk-widget {
    border-radius: var(--cms-block-booking-widget-border-radius, 1rem);
    box-shadow: var(--cms-block-booking-widget-box-shadow, 0 .125rem .25rem rgba(0, 0, 0, .075));
}
.ue-widget {
    border-radius: var(--cms-block-upcoming-events-border-radius, 1rem);
    box-shadow: var(--cms-block-upcoming-events-box-shadow, 0 .125rem .25rem rgba(0, 0, 0, .075));
}
.dp-widget {
    border-radius: var(--cms-block-donation-progress-border-radius, 1rem);
    box-shadow: var(--cms-block-donation-progress-box-shadow, 0 .125rem .25rem rgba(0, 0, 0, .075));
}
.nls-widget {
    border-radius: var(--cms-block-newsletter-signup-border-radius, 1rem);
    box-shadow: var(--cms-block-newsletter-signup-box-shadow, 0 .125rem .25rem rgba(0, 0, 0, .075));
}
.loc-widget {
    border-radius: var(--cms-block-location-border-radius, 1rem);
    box-shadow: var(--cms-block-location-box-shadow, 0 .125rem .25rem rgba(0, 0, 0, .075));
}

/* modal/offcanvas trigger button color, tokenized per
   docs/BLOCK_TYPE_DESIGN_TOKENS.md (Phase 0,
   docs/BLOCK_TYPE_STYLING_PLAN.md) - overrides just the --bs-btn-*
   custom properties Bootstrap's own .btn-primary already sets (keeping
   .btn's real interactive/focus/transition behavior), rather than
   replacing the class outright. Every state (rest/hover/active) maps to
   the SAME one token - a deliberate simplification over a separate
   hover-darkened shade, matching this feature's other "one token, one
   admin-facing knob" precedent. Fallback is Bootstrap's own --bs-primary,
   so default rendering is unchanged. */
.cms-block-modal-trigger-btn {
    --bs-btn-bg: var(--cms-block-modal-trigger-color, var(--bs-primary));
    --bs-btn-border-color: var(--cms-block-modal-trigger-color, var(--bs-primary));
    --bs-btn-hover-bg: var(--cms-block-modal-trigger-color, var(--bs-primary));
    --bs-btn-hover-border-color: var(--cms-block-modal-trigger-color, var(--bs-primary));
    --bs-btn-active-bg: var(--cms-block-modal-trigger-color, var(--bs-primary));
    --bs-btn-active-border-color: var(--cms-block-modal-trigger-color, var(--bs-primary));
}
.cms-block-offcanvas-trigger-btn {
    --bs-btn-bg: var(--cms-block-offcanvas-trigger-color, var(--bs-primary));
    --bs-btn-border-color: var(--cms-block-offcanvas-trigger-color, var(--bs-primary));
    --bs-btn-hover-bg: var(--cms-block-offcanvas-trigger-color, var(--bs-primary));
    --bs-btn-hover-border-color: var(--cms-block-offcanvas-trigger-color, var(--bs-primary));
    --bs-btn-active-bg: var(--cms-block-offcanvas-trigger-color, var(--bs-primary));
    --bs-btn-active-border-color: var(--cms-block-offcanvas-trigger-color, var(--bs-primary));
}

/* Highlight flash for a content block linked to directly via its alias
   permalink - /public/{slug}/{alias}/target redirects to
   /public/{slug}#{alias}, and content_block.html emits a real id="{{.Alias}}"
   on the block wrapper, so :target matches with no JS needed for either the
   scroll (native browser fragment behavior) or this flash. See
   docs/CONTENT_BLOCK_ALIAS_PERMALINKS.md. */
.content-block:target {
    animation: cms-block-target-flash 2s ease-out;
}
@keyframes cms-block-target-flash {
    0% { box-shadow: 0 0 0 4px rgba(255, 193, 7, 0.6); }
    100% { box-shadow: 0 0 0 4px rgba(255, 193, 7, 0); }
}

/* text block type: preserve line breaks entered in the editor's plain
   textarea - without this, .cms-ice-content-target's default inline
   white-space collapses multi-paragraph text to one line on the public
   page even though the editor shows it multi-line. Covers both render
   paths (content_block.html and blocks_by_alias.html), which both nest
   the target under a .content-block-text ancestor either way. Size classes
   below back the text block's optional block-level style options (see
   text_block.html's text_block_classes template) - alignment/bold reuse
   Bootstrap's own text-start/text-end/text-center/text-justify/fw-bold
   utilities already loaded site-wide, so only the size scale needs new
   classes here. */
.content-block-text .cms-ice-content-target {
    white-space: pre-line;
}
/* Sitewide default size scale for the "text" block type - see
   docs/BLOCK_TYPE_DESIGN_TOKENS.md (Phase 0, docs/BLOCK_TYPE_STYLING_PLAN.md). */
.cms-text-block-sm { font-size: var(--cms-block-text-size-sm, .875rem); }
.cms-text-block-lg { font-size: var(--cms-block-text-size-lg, 1.25rem); }
.cms-text-block-xl { font-size: var(--cms-block-text-size-xl, 1.5rem); }

/* Gallery block "slideshow"/"thumbnails" layouts - both render as a
   Bootstrap Carousel (gallery_block.html); fixed-height, object-fit:cover
   slides keep the carousel from jumping height between images of different
   aspect ratios. "thumbnails" replaces the dot indicators with real image
   thumbnails - it reuses Bootstrap's own .carousel-indicators class (for
   free active-state management via Carousel.js) so cms-gallery-thumbnails
   only needs to override the parts that don't fit a thumbnail strip. */
/* Sizes/spacing below are the "gallery" block type's sitewide defaults -
   see docs/BLOCK_TYPE_DESIGN_TOKENS.md (Phase 0,
   docs/BLOCK_TYPE_STYLING_PLAN.md). Fixed-height, object-fit:cover slides
   keep the carousel from jumping height between images of different aspect
   ratios - that behavior (object-fit:cover) stays a literal, only the
   height itself is tokenized. */
.cms-gallery-slideshow .carousel-item img,
.cms-gallery-thumbnails .carousel-item img {
    height: var(--cms-block-gallery-slideshow-height, 420px);
    object-fit: cover;
}
@media (max-width: 576px) {
    .cms-gallery-slideshow .carousel-item img,
    .cms-gallery-thumbnails .carousel-item img {
        height: var(--cms-block-gallery-slideshow-height-sm, 240px);
    }
}
.cms-gallery-thumbnails.carousel-indicators {
    position: static;
    margin: var(--cms-block-gallery-indicators-margin-top, .75rem) 0 0;
    padding: 0;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--cms-block-gallery-thumbnails-gap, .5rem);
}
.cms-gallery-thumbnails.carousel-indicators button {
    box-sizing: border-box;
    width: var(--cms-block-gallery-thumbnails-width, 72px);
    height: var(--cms-block-gallery-thumbnails-height, 54px);
    padding: 0;
    border: 2px solid transparent;
    border-radius: var(--cms-block-gallery-thumbnails-radius, 4px);
    background: none;
    opacity: var(--cms-block-gallery-thumbnails-opacity, .6);
    overflow: hidden;
    flex: 0 0 auto;
}
.cms-gallery-thumbnails.carousel-indicators button img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.cms-gallery-thumbnails.carousel-indicators button:hover {
    opacity: var(--cms-block-gallery-thumbnails-opacity-hover, .85);
}
.cms-gallery-thumbnails.carousel-indicators button.active {
    opacity: 1;
    border-color: var(--bs-primary, #0d6efd);
}

/* "slider" layout - a Bootstrap Carousel grouping several images per
   carousel-item (gallery_block.html/groupGalleryImages). Default Bootstrap
   indicators overlay the bottom of the slide - fine for a single full-bleed
   slideshow photo, but a slider's carousel-item is a grid of separate tiles
   with visible gaps, so the dots move below it instead of floating on top.
   Shares --cms-block-gallery-indicators-margin-top with the thumbnails
   strip above - both are "push the indicator row below the tiles" and the
   same default value, so one token covers both rather than two identical
   ones. */
.cms-gallery-slider .carousel-indicators {
    position: static;
    margin: var(--cms-block-gallery-indicators-margin-top, .75rem) 0 0;
}

/* "justified" layout - see docs/FILE_AND_GALLERY_BLOCK_REDESIGN.md and
   gallery_justified/main.js. Base state (no JS, or before it runs) is a
   plain flex-wrap of equal-height, natural-width images - already a
   reasonable-looking approximation on its own. Once main.js computes real
   edge-to-edge justified rows via the `justified-layout` package, it adds
   .cms-gallery-justified--computed and switches every item to absolute
   positioning driven by inline top/left/width/height styles. */
.cms-gallery-justified {
    display: flex;
    flex-wrap: wrap;
    gap: var(--cms-block-gallery-justified-gap, 8px);
}
.cms-gallery-justified .cms-gallery-justified-item {
    position: relative;
    height: var(--cms-block-gallery-justified-item-height, 220px);
    overflow: hidden;
    border-radius: var(--cms-block-gallery-justified-radius, 4px);
}
.cms-gallery-justified .cms-gallery-justified-item img {
    display: block;
    height: 100%;
    width: auto;
    object-fit: cover;
}
.cms-gallery-justified.cms-gallery-justified--computed {
    display: block;
    position: relative;
    height: var(--cms-gallery-justified-height, auto);
}
.cms-gallery-justified.cms-gallery-justified--computed .cms-gallery-justified-item {
    position: absolute;
    height: auto;
}
.cms-gallery-justified.cms-gallery-justified--computed .cms-gallery-justified-item img {
    width: 100%;
    height: 100%;
}

@keyframes fs-fadein {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}
/* Business Profile footer (docs/CMS_PLATFORM_ROADMAP.md §19) */
.business-profile-logo {
    max-height: 40px;
    max-width: 160px;
    object-fit: contain;
}
.business-profile-social a {
    font-size: 1.1rem;
}

/* Base component primitives (docs/SNIPPETS_COMPONENTS_PLAN.md §5). Sizes
   tokenized per docs/BLOCK_TYPE_DESIGN_TOKENS.md (Phase 0,
   docs/BLOCK_TYPE_STYLING_PLAN.md). */
.divider-block-space {
    height: var(--cms-block-divider-space-height, 2rem);
}
.icon-block {
    font-size: var(--cms-block-icon-size, 1.5rem);
}
.icon-block-link {
    color: inherit;
}

/* team_members widget - Phase 2 pilot of docs/BLOCK_TYPE_STYLING_PLAN.md
   (the widget-tier types render entirely client-side, see
   frontend/src/team_members/widget/main.js - CLAUDE.md's own "How the 13
   client-rendered widget types actually get their look" section). These
   two values used to be bare literals baked into that JS file's own
   template-literal markup string; extracting them into real, token-driven
   CSS classes is what makes them reachable by the same Style Defaults
   admin form + live preview every server-rendered type already has, with
   zero new architecture - see docs/BLOCK_TYPE_DESIGN_TOKENS.md. This is
   the template Phase 3 rolls out to the other 12 widget families. */
.tm-widget-item {
    width: var(--cms-block-team-members-card-width, 160px);
}
/* List layout (E191): one member per row, photo left. */
.tm-widget-stack { display: flex; flex-direction: column; gap: 1rem; }
.tm-widget-row { display: flex; gap: 1.25rem; align-items: flex-start; text-align: left; padding: 1rem 1.25rem; border: 1px solid var(--bs-border-color); border-radius: .75rem; background: var(--bs-body-bg); }
.tm-widget-row .tm-widget-photo { flex: none; }
.tm-widget-row .tm-widget-details { min-width: 0; }
.tm-widget-blurb { max-width: 70ch; color: var(--bs-secondary-color); }
.tm-widget-links a { color: var(--bs-secondary-color); font-size: 1.15rem; }
.tm-widget-links a:hover { color: var(--bs-primary); }
.tm-widget-more { display: inline-block; margin-top: .25rem; }
.tm-widget-avatar {
    width: var(--cms-block-team-members-avatar-size, 96px);
    height: var(--cms-block-team-members-avatar-size, 96px);
    object-fit: cover;
}

/* *********************************** */
/* ******** comments styles *********** */
/* *********************************** */ 

/* =========================================================
   CMS Comments — Bootstrap 5.3+
   ========================================================= */
/* =========================================================
   CMS Comments
   Hybrid Bootstrap 5.3 card and threaded discussion layout
   ========================================================= */

#container-comments {
    --comment-gap: 0.85rem;
    --comment-indent: 1rem;
    --comment-padding: 1rem;
    --comment-border: var(--bs-border-color);
    --comment-thread-color: var(--bs-border-color);
    --comment-root-surface: var(--bs-body-bg);
    --comment-reply-surface: rgba(var(--bs-secondary-rgb), 0.055);
    --comment-deep-reply-surface: rgba(var(--bs-secondary-rgb), 0.035);
    --comment-reply-hover: rgba(var(--bs-primary-rgb), 0.055);

    color: var(--bs-body-color);
}

#comments {
    max-width: 960px;
    margin-inline: auto;
    padding-inline: clamp(0.75rem, 3vw, 2rem);
}

/* =========================================================
   Title
   ========================================================= */

.comments-page-title {
    padding-bottom: 0.75rem;
    color: var(--bs-heading-color);
    font-weight: 600;
    border-bottom: 1px solid var(--comment-border);
}

/* =========================================================
   Comment form
   ========================================================= */

#comment-form-wrapper {
    padding: clamp(1rem, 3vw, 1.5rem);
    background-color: var(--bs-tertiary-bg);
    border: 1px solid var(--comment-border);
    border-radius: var(--bs-border-radius-lg);
}

.comment-form-field {
    margin-bottom: 0.75rem;
}

.comment-form-field .form-label {
    color: var(--bs-emphasis-color);
    font-weight: 600;
}

#comment-content {
    min-height: 7rem;
    resize: vertical;
    background-color: var(--bs-body-bg);
}

#comment-content:focus {
    border-color: rgba(var(--bs-primary-rgb), 0.65);
    box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.15);
}

.comment-form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

#comment-cancel.hidden {
    display: none !important;
}

#comment-status:empty {
    display: none;
}

#comment-status.is-success {
    color: var(--bs-success-text-emphasis);
}

#comment-status.is-error {
    color: var(--bs-danger-text-emphasis);
}

/* =========================================================
   Root comment list
   ========================================================= */

#comments-list .comment-list {
    display: grid;
    gap: var(--comment-gap);
    min-width: 0;
}

/* =========================================================
   Comments
   ========================================================= */

#comments-list .comment {
    position: relative;
    display: block;
    min-width: 0;
    padding-block: 0.7rem;
    padding-inline: 0;
    color: var(--bs-body-color);
    overflow-wrap: anywhere;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    transition:
        background-color 150ms ease,
        border-color 150ms ease,
        box-shadow 150ms ease;
}

/* Top-level conversations use a contained card. */
#comments-list .comment-list > .comment {
    padding: var(--comment-padding);
    background-color: var(--comment-root-surface);
    border: 1px solid var(--comment-border);
    border-radius: var(--bs-border-radius-lg);
    box-shadow: 0 0.125rem 0.35rem rgba(0, 0, 0, 0.045);
}

/* Replies are subtle panels rather than nested cards. */
#comments-list .comment-children > .comment {
    margin-block: 0.35rem;
    padding: 0.75rem;
    background-color: var(--comment-reply-surface);
    border-radius: var(--bs-border-radius);
}

/* Prevent increasingly dark surfaces in deep conversations. */
#comments-list
    .comment-children
    .comment-children
    > .comment {
    background-color: var(--comment-deep-reply-surface);
}

#comments-list .comment-list > .comment:focus-within {
    border-color: rgba(var(--bs-primary-rgb), 0.5);
}

@media (hover: hover) {
    #comments-list
        .comment-list
        > .comment:hover:not(:has(.comment:hover)) {
        border-color: rgba(var(--bs-primary-rgb), 0.4);
        box-shadow: 0 0.2rem 0.55rem rgba(0, 0, 0, 0.07);
    }

    #comments-list
        .comment-children
        > .comment:hover:not(:has(.comment:hover)) {
        background-color: var(--comment-reply-hover);
    }
}

/* =========================================================
   Comment header
   ========================================================= */

#comments-list .comment-header {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    flex-wrap: wrap;
    gap: 0.25rem;
    min-width: 0;
    line-height: 1.25;
}

#comments-list .comment-author {
    min-width: 0;
    color: var(--bs-emphasis-color);
    font-size: 0.875rem;
    font-weight: 650;
    overflow-wrap: anywhere;
}

#comments-list .comment-date {
    flex-shrink: 0;
    color: var(--bs-secondary-color);
    font-size: 0.75rem;
    font-weight: 400;
    white-space: normal;
}

#comments-list .comment-date::before {
    margin-inline: 0.15rem 0.35rem;
    content: "•";
    color: var(--bs-secondary-color);
}

/* =========================================================
   Comment content
   ========================================================= */

#comments-list .comment-body {
    margin-top: 0.45rem;
    color: var(--bs-emphasis-color);
    font-size: 0.975rem;
    font-weight: 400;
    line-height: 1.6;
    white-space: pre-wrap;
}

#comments-list .comment-children .comment-body {
    padding-inline-start: 0.65rem;
    border-inline-start: 3px solid rgba(var(--bs-primary-rgb), 0.35);
}

/* =========================================================
   Comment actions
   ========================================================= */

#comments-list .comment-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    min-height: 0;
    margin-top: 0.55rem;
}

#comments-list .comment-footer:empty {
    display: none;
}

#comments-list .comment-footer .btn-link {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.65rem;
    color: var(--bs-primary);
    font-size: 0.8125rem;
    font-weight: 650;
    line-height: 1.25;
    text-decoration: none;
    background-color: rgba(var(--bs-primary-rgb), 0.09);
    border: 1px solid rgba(var(--bs-primary-rgb), 0.22);
    border-radius: 999px;
}

#comments-list .comment-footer .btn-link::before {
    content: "↩";
    font-size: 0.9rem;
    font-weight: 700;
    line-height: 1;
}

#comments-list .comment-footer .btn-link:hover {
    color: var(--bs-white);
    background-color: var(--bs-primary);
    border-color: var(--bs-primary);
}

#comments-list .comment-footer .btn-link:focus-visible {
    color: var(--bs-primary);
    background-color: rgba(var(--bs-primary-rgb), 0.14);
    border-color: var(--bs-primary);
    outline: 0;
    box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.2);
}

/* =========================================================
   Thread structure
   ========================================================= */

#comments-list .comment-children {
    position: relative;
    display: block;
    min-width: 0;
    margin: 0;
    margin-top: 0.15rem;
    margin-inline-start: 0.25rem;
    padding: 0;
    padding-inline-start: var(--comment-indent);
    border-inline-start: 2px solid var(--comment-thread-color);
}

#comments-list .comment-children > .comment + .comment {
    border-top: 1px solid rgba(var(--bs-secondary-rgb), 0.14);
}

@media (hover: hover) {
    #comments-list .comment-children:hover {
        border-inline-start-color: rgba(var(--bs-primary-rgb), 0.55);
    }
}

/* =========================================================
   Responsive adjustments
   ========================================================= */

@media (max-width: 767.98px) {
    #container-comments {
        --comment-gap: 0.65rem;
        --comment-indent: 0.7rem;
        --comment-padding: 0.8rem;
    }

    #comments {
        padding-inline: 0.25rem;
    }

    #comments-list .comment-list > .comment {
        border-radius: var(--bs-border-radius);
    }

    #comments-list .comment-children {
        margin-inline-start: 0.1rem;
    }

    #comments-list .comment-children > .comment {
        padding: 0.6rem;
    }

    #comments-list .comment-author {
        font-size: 0.8125rem;
    }

    #comments-list .comment-date {
        font-size: 0.6875rem;
    }

    #comments-list .comment-body {
        font-size: 0.9rem;
    }

    #comments-list .comment-children .comment-body {
        padding-inline-start: 0.45rem;
        border-inline-start-width: 2px;
    }

    #comments-list .comment-footer .btn-link {
        padding: 0.3rem 0.55rem;
    }
}

@media (max-width: 419.98px) {
    #container-comments {
        --comment-indent: 0.5rem;
        --comment-padding: 0.7rem;
    }

    #comments-list .comment-list > .comment {
        box-shadow: none;
    }

    .comment-form-actions .btn {
        flex: 1 1 auto;
    }
}
.comment-reactions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.comment-reactions .btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.65rem;
    color: var(--bs-secondary-color);
    font-size: 0.8125rem;
    font-weight: 650;
    line-height: 1.25;
    text-decoration: none;
    background-color: rgba(var(--bs-secondary-rgb), 0.09);
    border: 1px solid rgba(var(--bs-secondary-rgb), 0.22);
    border-radius: 999px;
}
/* =========================================================
   Accessibility
   ========================================================= */

@media (prefers-reduced-motion: reduce) {
    #comments-list .comment {
        transition: none;
    }
}


/* Builder-page column resize (drag-to-resize a column's width via a handle
   between two adjacent columns - see backend/models/template_builder.go's
   TemplateBuilderCol.WidthPercent doc comment). Scoped to the `md` breakpoint
   and up, matching Bootstrap's own col-md-* convention, so a custom width
   only applies on desktop - below that, columns keep stacking full-width
   exactly as they do without this feature. */
@media (min-width: 768px) {
    /* !important: a column can already carry a manually-typed Bootstrap
       width class (col-3/col-6/etc, still a real, supported use of the
       Classes field) with equal selector specificity to a bare
       .has-custom-width - without !important, whichever rule happens to be
       declared later in the cascade wins, which is exactly backwards for a
       feature whose whole point is to override whatever width was already
       set. Confirmed live: without this, dragging silently had zero visual
       effect on any column that also had a col-3/col-6-style class. */
    .has-custom-width {
        flex: 0 0 var(--col-width) !important;
        max-width: var(--col-width) !important;
    }
}

/* ---------------------------------------------------------------------------
   Comments: sort toolbar, collapsible threads, permalink, edit, flash (E158)
   ------------------------------------------------------------------------- */
#comments .comments-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.85rem;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--bs-border-color);
}
#comments .comments-count {
    font-size: 0.8125rem;
    font-weight: 650;
    color: var(--bs-secondary-color);
}
#comments .comments-sort-label .form-select {
    width: auto;
    min-width: 9.5rem;
    font-size: 0.8125rem;
}

#comments-list .comment-collapse-toggle {
    margin-top: 0.35rem;
    padding: 0.2rem 0.5rem;
    font-size: 0.75rem;
    font-weight: 650;
}
#comments-list .comment-permalink,
#comments-list .comment-edit-button {
    color: var(--bs-secondary-color);
}
#comments-list .comment-edited {
    margin-inline-start: 0.4rem;
    font-size: 0.7rem;
    font-style: italic;
    color: var(--bs-secondary-color);
}

#comments-list .comment-edit-form {
    display: grid;
    gap: 0.5rem;
    margin: 0.5rem 0;
}
#comments-list .comment-edit-actions {
    display: flex;
    gap: 0.4rem;
}

#comments-list .comment.comment-flash {
    animation: comment-flash-kf 2.2s ease-out;
}
@keyframes comment-flash-kf {
    0%, 15% { background-color: color-mix(in srgb, var(--bs-warning) 28%, transparent); }
    100% { background-color: transparent; }
}
@media (prefers-reduced-motion: reduce) {
    #comments-list .comment.comment-flash { animation: none; outline: 2px solid var(--bs-warning); }
}

/* Carousel block overlay (E178): a tint over each slide image so the
   caption stays legible. Sits under Bootstrap's z-index:10 caption/controls
   and never intercepts clicks (the slide's link wraps the image). */
.carousel-item .carousel-overlay { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.carousel-overlay-dark.carousel-overlay-25 { background: rgba(0, 0, 0, .25); }
.carousel-overlay-dark.carousel-overlay-50 { background: rgba(0, 0, 0, .5); }
.carousel-overlay-dark.carousel-overlay-75 { background: rgba(0, 0, 0, .75); }
.carousel-overlay-light.carousel-overlay-25 { background: rgba(255, 255, 255, .25); }
.carousel-overlay-light.carousel-overlay-50 { background: rgba(255, 255, 255, .5); }
.carousel-overlay-light.carousel-overlay-75 { background: rgba(255, 255, 255, .75); }
