/**
 * Branding overrides for compiled styles.css
 *
 * The prototype-exported styles.css hardcodes hex values (#dce0e8 for borders,
 * #1b212a for text, #fff for backgrounds, #10c348 for success green, etc.)
 * instead of using CSS custom properties. This file overrides those selectors
 * so tenant branding colors propagate correctly.
 *
 * Loading order: styles.css -> branding-overrides.css -> branding-css.php
 * Specificity: selectors match the compiled CSS exactly (same or higher specificity).
 */

/* ==========================================================================
   GLOBAL — body/html background coverage
   ========================================================================== */

/* Always reserve scrollbar space so content does not shift horizontally
   when a tall pane is swapped for a short one (e.g. switching between
   settings or activity-edit sections). The track is styled transparent
   so short pages do not show an empty scrollbar artifact — only the
   thumb is visible when there is something to scroll. */
html {
    overflow-y: scroll;
    /* Firefox */
    scrollbar-width: thin;
}
html::-webkit-scrollbar {
    width: 10px;
}
html::-webkit-scrollbar-track {
    background: transparent;
}
html::-webkit-scrollbar-thumb {
    background: var(--bs-gray-400);
    border-radius: 5px;
}
html::-webkit-scrollbar-thumb:hover {
    background: var(--bs-gray-500);
}

/* Ensure the page background fills the full viewport even on short-content
   pages (e.g. wishlist with few items). Without this, the body ends at the
   content height and the remaining viewport shows the html element's default
   white background.
   The flex column layout makes .page-wrapper grow to fill the full viewport
   height so the footer is always pushed to the bottom (sticky footer pattern). */
body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

/* Ensure page-wrapper grows to fill body's flex space and can push the footer
   to the bottom via .footer { margin-top: auto } already in styles.css.
   overflow: visible is required so sticky positioning on .header works —
   overflow: hidden on a parent element prevents position: sticky. */
.page-wrapper {
    flex: 1;
    overflow: visible;
}

/* ── Sticky header (TASK-422) ──
   position: sticky keeps the header at the top of the viewport while scrolling.
   z-index: 99 is below Bootstrap modals (1050+) and offcanvas (1045) so they
   can appear on top of the header without overlap issues. */
.header,
.header--unlogged {
    position: sticky;
    top: 0;
    z-index: 99;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0);
    transition: box-shadow 0.4s ease;
}

.header.is-sticky,
.header--unlogged.is-sticky {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07);
}

.header--unlogged {
    padding: 12px 4px 11px;
}

.header--unlogged .container-fluid {
    min-height: 42px;
}

/* ==========================================================================
   FORM CONTROLS — borders, text, backgrounds
   ========================================================================== */

.form-control {
    border-color: var(--ss-border);
    color: var(--ss-text);
    background-color: var(--ss-surface);
}

.form-control:focus {
    color: var(--ss-text);
    background-color: var(--ss-surface);
    border-color: var(--ss-border);
    box-shadow: none;
}

.form-control::file-selector-button {
    color: var(--ss-text);
}

.form-select {
    border-color: var(--ss-border);
    color: var(--ss-text);
    background-color: var(--ss-surface);
}

.form-select:focus {
    border-color: var(--ss-border);
    box-shadow: none;
}

.form-check-input {
    border-color: var(--ss-border);
}

.form-check-input:focus {
    border-color: var(--ss-border);
    box-shadow: none;
}

.form-check-input:checked {
    background-color: var(--bs-secondary);
    border-color: var(--bs-secondary);
}

.was-validated .form-check-input:valid:checked,
.form-check-input.is-valid:checked {
    background-color: var(--bs-secondary);
    border-color: var(--bs-secondary);
}

.form-switch .form-switch-input {
    border-color: var(--ss-border);
}

/* Checked toggle — replace hardcoded blue (#0B93F5) SVG circle with primary
   brand color (red). Track stays default (light). SVG data URIs cannot use
   CSS custom properties, so the color is set dynamically in branding-css.php. */

.input-group-text {
    border-color: var(--ss-border);
    color: var(--ss-text);
}

.form-range::-webkit-slider-thumb {
    background-color: var(--bs-primary);
}

.form-range::-moz-range-thumb {
    background-color: var(--bs-primary);
}

/* Floating label background behind text */
.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label::after {
    background-color: var(--ss-surface);
}

.form-floating > .form-control:focus ~ label::after,
.form-floating > .form-control:not(:placeholder-shown) ~ label::after,
.form-floating > .form-control-plaintext ~ label::after,
.form-floating > .form-select ~ label::after {
    background-color: var(--ss-surface);
}

/* Required-field marker — TASK-2W57M.
   Asterisk rendered by components/form-field.php when required=true and a
   visible label is present. The marker is aria-hidden so AT relies on the
   input's own `required` attribute; the colour matches the tenant
   Fehlerfarbe so it reads as the same "this must be filled" signal as the
   .is-invalid border. */
.form-required-marker {
    color: var(--bs-danger);
    margin-left: 0.15rem;
    font-weight: 500;
    /* .form-label is `display: flex; justify-content: space-between` (see
       compiled styles.css), which would otherwise push the marker to the
       right edge of the label row. `margin-right: auto` absorbs the
       leftover flex space into the marker's right margin, keeping the
       asterisk immediately after the label text. No-op outside a flex
       parent. */
    margin-right: auto;
}

/* Validation states — override hardcoded green/red borders */
.was-validated .form-control:valid,
.form-control.is-valid {
    border-color: var(--ss-border);
    background-color: var(--ss-surface);
}

/* The compiled styles.css adds `outline: 3px solid #eff4f7` to .is-invalid,
   which paints a light-gray halo outside the red border and reads as a
   second border against the page background (WTWY-633). Reset it here. */
.was-validated .form-control:invalid,
.form-control.is-invalid {
    background-color: var(--ss-surface);
    border-color: var(--bs-danger);
    outline: 0;
}

.was-validated .form-select:valid,
.form-select.is-valid {
    border-color: var(--ss-border);
    background-color: var(--ss-surface);
}

.was-validated .form-select:invalid,
.form-select.is-invalid {
    background-color: var(--ss-surface);
    border-color: var(--bs-danger);
    outline: 0;
}

/* Terms/values boxes */
.box-terms-wrapper .box-terms button {
    border-color: var(--ss-border);
}

.box-values input:focus {
    border-color: var(--ss-border);
}

/* ==========================================================================
   CUSTOM SELECT — panel, options, opener
   ========================================================================== */

/* FOUC prevention: hide static sort/filter dropdowns until customSelect wraps them.
   Only targets .dashboard-sort-select — always in the DOM at DOMContentLoaded and
   always wrapped by customSelect. customSelect's own CSS hides the native <select>
   inside the container once wrapping is complete. */
.dashboard-sort-select {
    visibility: hidden;
}

/* Hide the custom-select-opener when Tom Select is active in the same container
   (TASK-338: prevents duplicate dropdown UI on selects initialized by both systems) */
.custom-select-container:has(.ts-wrapper) .custom-select-opener {
    display: none;
}

.form .custom-select-opener {
    border-color: var(--ss-border);
    background-color: var(--ss-surface);
}

.custom-select-container .custom-select-panel {
    background-color: var(--ss-surface);
    transition: max-height .3s ease-out, padding-top .3s ease-out;
    padding-top: 0;
}

.custom-select-container.is-open .custom-select-panel {
    max-height: 20em;
    overflow-y: hidden;
    padding-top: 5px;
}

.custom-select-container .custom-select-panel .custom-select-option:first-child {
    margin-top: 0;
}

/* TASK-408: Fix custom select panel always visible.
   The compiled styles.css has a duplicate rule that overrides max-height:0
   with max-height:20em, making the panel always open. Reset to hidden state;
   the .is-open rule still opens it correctly via higher specificity. */
.custom-select-container:not(.is-open) .custom-select-panel {
    max-height: 0;
    overflow: hidden;
}

/* TASK-408: Hide check marks when dropdown is closed.
   The .is-selected::before checkmark (content: "✔") should only be visible
   when the panel is open (add/edit mode). In display mode the panel is closed
   and the checkmark must not appear next to the selected option. */
.custom-select-container:not(.is-open) .custom-select-option.is-selected::before {
    display: none;
}

.custom-select-container .custom-select-panel .custom-select-option {
    background: var(--ss-surface);
}

/* Focus/selected states */
.custom-select-container .custom-select-panel .custom-select-option.has-focus,
.custom-select-container .custom-select-panel .custom-select-option.is-selected {
    background: var(--bs-body-bg);
}

/* ==========================================================================
   FORM RADIO BUTTONS
   ========================================================================== */

.form-radio-buttons {
    background: var(--bs-body-bg);
}

.form-radio-buttons .btn-check + .btn:hover {
    background: var(--ss-surface);
}

.form-radio-buttons .btn-check:checked + .btn {
    background: var(--ss-surface);
}

/* Type1 variant (interest filter chips) — container is transparent,
   individual pills have their own background */
.form-radio-buttons--type1 {
    background: none;
}

.form-radio-buttons--type1 .btn-check + .btn {
    background: var(--bs-body-bg);
    color: var(--ss-text);
}

.form-radio-buttons--type1 .btn-check + .btn:hover {
    color: var(--ss-surface);
    background: var(--ss-text);
}

.form-radio-buttons--type1 .btn-check:checked + .btn {
    color: var(--ss-surface);
    background: var(--ss-text);
}

/* ==========================================================================
   TAGS INPUT
   ========================================================================== */

.tags-input {
    background: var(--ss-surface);
}

/* ==========================================================================
   TYPOGRAPHY — headings, blockquote, emphasis
   ========================================================================== */

h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 {
    color: var(--ss-text-title);
}

/* Reset global overrides inside birdeatsbug widget so SDK styles are not overridden */
#birdeatsbug-sdk h1,
#birdeatsbug-sdk h2,
#birdeatsbug-sdk h3,
#birdeatsbug-sdk h4,
#birdeatsbug-sdk h5,
#birdeatsbug-sdk h6 {
    color: inherit;
}

#birdeatsbug-sdk svg path[fill],
#birdeatsbug-sdk svg circle[fill] {
    fill: currentColor;
}

/* Body/paragraph text uses the content text color */
p, .card-text, .info-list, .accordion-body, .tab-pane {
    color: var(--ss-text-content);
}

/* Activity card info list: single-line layout with dot separators between
   items (time · location · participants). Only the middle item (location)
   shrinks and ellipsises when space is tight — the time and participant
   count stay at their natural width so the layout matches the Figma
   reference design. */
.card--activity .info-list {
    flex-wrap: nowrap;
    min-width: 0;
}
.card--activity .info-list li {
    flex: 0 0 auto;
    min-width: 0;
    white-space: nowrap;
}
.card--activity .info-list li:nth-child(2) {
    flex: 0 1 auto;
    overflow: hidden;
}
.card--activity .info-list li .info-text {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    max-width: 100%;
}
/* Lock icon dimensions so they render at a consistent, readable size
   regardless of each SVG's intrinsic viewBox (clock 15x14, pin 12x14,
   participants 14x14). */
.card--activity .info-list li > svg {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    margin-right: 6px;
}
/* Dot separator between info items. The compiled stylesheet only sets
   this inside .terms-table — restore it for the activity card layout to
   match the Figma reference (time · location · participants). */
.card--activity .info-list li + li::before {
    content: "";
    flex: 0 0 3px;
    width: 3px;
    height: 3px;
    margin: 0 8px;
    border-radius: 50%;
    background-color: var(--ss-text-content);
    align-self: center;
}

.blockquote-footer {
    color: var(--ss-text-title);
}

.first-letter::first-letter {
    color: var(--ss-text-title);
}

/* ==========================================================================
   BUTTONS — standardized hover, active, disabled states (TASK-381)
   Compiled styles.css hardcodes hex colors for all button variants.
   Override with CSS custom properties so tenant branding propagates.
   ========================================================================== */

/* --- Primary button: uses --bs-primary (tenant's main CTA color) --- */
.btn-primary {
    --bs-btn-bg: var(--bs-primary);
    --bs-btn-border-color: var(--bs-primary);
    --bs-btn-hover-bg: var(--bs-primary);
    --bs-btn-hover-border-color: var(--bs-primary);
    --bs-btn-active-bg: var(--bs-primary);
    --bs-btn-active-border-color: var(--bs-primary);
    --bs-btn-disabled-bg: var(--bs-primary);
    --bs-btn-disabled-border-color: var(--bs-primary);
    --bs-btn-color: var(--ss-surface);
    --bs-btn-hover-color: var(--ss-surface);
    --bs-btn-active-color: var(--ss-surface);
    --bs-btn-disabled-color: var(--ss-surface);
}

/* --- Dark button: uses --ss-text (tenant's text color) --- */
.btn-dark {
    --bs-btn-bg: var(--ss-text);
    --bs-btn-border-color: var(--ss-text);
    --bs-btn-hover-bg: var(--ss-text);
    --bs-btn-hover-border-color: var(--ss-text);
    --bs-btn-active-bg: var(--ss-text);
    --bs-btn-active-border-color: var(--ss-text);
    --bs-btn-disabled-bg: var(--ss-text);
    --bs-btn-disabled-border-color: var(--ss-text);
    --bs-btn-color: var(--ss-surface);
    --bs-btn-hover-color: var(--ss-surface);
    --bs-btn-active-color: var(--ss-surface);
    --bs-btn-disabled-color: var(--ss-surface);
}

/* --- Danger button: uses --bs-danger --- */
.btn-danger {
    --bs-btn-bg: var(--bs-danger);
    --bs-btn-border-color: var(--bs-danger);
    --bs-btn-hover-bg: var(--bs-danger);
    --bs-btn-hover-border-color: var(--bs-danger);
    --bs-btn-active-bg: var(--bs-danger);
    --bs-btn-active-border-color: var(--bs-danger);
    --bs-btn-disabled-bg: var(--bs-danger);
    --bs-btn-disabled-border-color: var(--bs-danger);
    --bs-btn-color: var(--ss-surface);
    --bs-btn-hover-color: var(--ss-surface);
    --bs-btn-active-color: var(--ss-surface);
    --bs-btn-disabled-color: var(--ss-surface);
}

/* --- Success / terciary button: uses --bs-success --- */
.btn-success,
.btn-terciary {
    --bs-btn-bg: var(--bs-success);
    --bs-btn-border-color: var(--bs-success);
    --bs-btn-hover-bg: var(--bs-success);
    --bs-btn-hover-border-color: var(--bs-success);
    --bs-btn-active-bg: var(--bs-success);
    --bs-btn-active-border-color: var(--bs-success);
    --bs-btn-disabled-bg: var(--bs-success);
    --bs-btn-disabled-border-color: var(--bs-success);
    --bs-btn-color: var(--ss-surface);
    --bs-btn-hover-color: var(--ss-surface);
    --bs-btn-active-color: var(--ss-surface);
    --bs-btn-disabled-color: var(--ss-surface);
}

.btn-outline-dark {
    --bs-btn-color: var(--ss-text);
    --bs-btn-border-color: var(--ss-text);
    --bs-btn-hover-color: var(--ss-surface);
    --bs-btn-hover-bg: var(--ss-text);
    --bs-btn-hover-border-color: var(--ss-text);
    --bs-btn-active-color: var(--ss-surface);
    --bs-btn-active-bg: var(--ss-text);
    --bs-btn-active-border-color: var(--ss-text);
    --bs-btn-disabled-color: var(--ss-text);
    --bs-btn-disabled-border-color: var(--ss-text);
}

.btn-outline-danger {
    --bs-btn-color: var(--bs-danger);
    --bs-btn-border-color: var(--bs-danger);
    --bs-btn-hover-color: var(--ss-surface);
    --bs-btn-hover-bg: var(--bs-danger);
    --bs-btn-hover-border-color: var(--bs-danger);
    --bs-btn-active-color: var(--ss-surface);
    --bs-btn-active-bg: var(--bs-danger);
    --bs-btn-active-border-color: var(--bs-danger);
    --bs-btn-disabled-color: var(--bs-danger);
    --bs-btn-disabled-border-color: var(--bs-danger);
}

.btn-outline-success,
.btn-outline-terciary {
    --bs-btn-color: var(--bs-success);
    --bs-btn-border-color: var(--bs-success);
    --bs-btn-hover-color: var(--ss-surface);
    --bs-btn-hover-bg: var(--bs-success);
    --bs-btn-hover-border-color: var(--bs-success);
    --bs-btn-active-color: var(--ss-surface);
    --bs-btn-active-bg: var(--bs-success);
    --bs-btn-active-border-color: var(--bs-success);
    --bs-btn-disabled-color: var(--bs-success);
    --bs-btn-disabled-border-color: var(--bs-success);
}

/* --- Utility buttons --- */

.btn-circle {
    background-color: var(--bs-tertiary-bg);
}

.btn-square {
    background-color: var(--ss-surface);
}

.btn-more {
    color: var(--ss-text);
}

/* Override styles.css global "svg path[stroke] { stroke: var(--bs-body-color) }"
   so SVG icons inside buttons inherit the button's own text color. */
.btn svg path[stroke],
.btn svg circle[stroke] {
    stroke: currentColor;
}

/* btn-back — canonical back/previous navigation button.
   Body text color on body background, with border. Use only for wizard "Zurück". */
.btn-back,
.btn.btn-back {
    --bs-btn-color: var(--ss-text);
    --bs-btn-bg: var(--bs-body-bg);
    --bs-btn-border-color: var(--ss-border);
    --bs-btn-hover-color: var(--ss-text);
    --bs-btn-hover-bg: var(--bs-body-bg);
    --bs-btn-hover-border-color: var(--ss-border);
    --bs-btn-active-color: var(--ss-text);
    --bs-btn-active-bg: var(--ss-border);
    --bs-btn-active-border-color: var(--ss-border);
    box-shadow: none;
}

/* btn-route — "Show route" button below location map.
   Surface background, border, plain text. No icon. */
.btn-route,
.btn.btn-route {
    --bs-btn-color: var(--ss-text);
    --bs-btn-bg: var(--ss-surface);
    --bs-btn-border-color: var(--ss-border);
    --bs-btn-hover-color: var(--ss-text);
    --bs-btn-hover-bg: var(--ss-surface);
    --bs-btn-hover-border-color: var(--ss-border);
    --bs-btn-active-color: var(--ss-text);
    --bs-btn-active-bg: var(--ss-border);
    --bs-btn-active-border-color: var(--ss-border);
    box-shadow: none;
    width: 100%;
}

/* --- Disabled state: consistent opacity for all buttons --- */
.btn:disabled,
.btn.disabled {
    opacity: 0.65;
}

/* --- Input-group button: visually integrates with adjacent input fields.
   Matches input border, background, and text color — not a standalone CTA.
   Use for stepper (+/-) and inline action buttons inside .input-group. --- */
.btn-input-group,
.btn.btn-input-group {
    --bs-btn-color: var(--ss-text);
    --bs-btn-bg: var(--ss-surface);
    --bs-btn-border-color: var(--ss-border);
    --bs-btn-hover-color: var(--ss-text);
    --bs-btn-hover-bg: var(--ss-border);
    --bs-btn-hover-border-color: var(--ss-border);
    --bs-btn-active-color: var(--ss-text);
    --bs-btn-active-bg: var(--ss-border);
    --bs-btn-active-border-color: var(--ss-border);
    --bs-btn-disabled-color: var(--ss-text);
    --bs-btn-disabled-bg: var(--ss-surface);
    --bs-btn-disabled-border-color: var(--ss-border);
    box-shadow: none;
}

.btn-wishlist.active .bookmark-filled path {
    fill: var(--ss-text);
}

/* ==========================================================================
   TABS
   ========================================================================== */

.tabs .nav-tabs .nav-link.active {
    border-color: var(--bs-primary);
    color: var(--ss-text);
}

.tabs .nav-tabs--type1 .nav-link.active {
    border-color: var(--ss-text);
    color: var(--ss-text);
}

/* Type 2 tabs (activity/location detail): solid black 2px border, title-color active tab */
.nav-tabs.nav-tabs--type2,
.tabs .nav-tabs.nav-tabs--type2 {
    border-bottom: 2px solid var(--ss-text) !important;
    border-color: var(--ss-text) !important;
}

.nav-tabs--type2 .nav-link {
    color: var(--bs-secondary-color);
}

.nav-tabs--type2 .nav-link.active {
    border-top-color: transparent;
    border-left-color: transparent;
    border-right-color: transparent;
    border-bottom: 2px solid var(--bs-primary);
    border-radius: 0;
    background-color: transparent;
    color: var(--ss-text);
    margin-bottom: -2px;
}


.tabs--accordion .accordion-item .accordion-button[aria-expanded="true"] {
    border-color: var(--bs-primary);
}

/* Activity/location detail: spacious tab content (matches Figma design) */
.section-item-page .tab-content {
    padding-top: 1.5rem;  /* 24px gap between tab bar and content */
}

/* TASK-YTHMS: tighten gap between activity-detail tab navigation and content
   to match the Figma reference (activity-detail/beschreibung.png). Scoped to
   .tabs--accordion so location-detail and other tab uses are unaffected. */
.tabs.tabs--accordion .nav-tabs--type2 {
    margin-bottom: 0.5rem;
}
.tabs.tabs--accordion > .tab-content {
    padding-top: 0.5rem;
}

.section-item-page .accordion-body {
    padding-bottom: 1rem;  /* breathing room at bottom of each tab */
}

/* Section divider between activity sections must be the tenant bg color,
   not a hardcoded gray. styles.css compiled `.cards-more` with
   `border-top: 10px solid #eff4f7` from Bootstrap's var(--bs-body-bg). */
.cards-more {
    border-top-color: var(--bs-body-bg);
}

.section-content-page .tabs .nav-tabs {
    border-color: var(--ss-text);
}

/* Remove max-width constraint on container in split-view content pages */
.page-wrapper--content .section-intro .container-fluid,
.page-wrapper--content .section-intro .container-sm,
.page-wrapper--content .section-intro .container-md,
.page-wrapper--content .section-intro .container-lg,
.page-wrapper--content .section-intro .container-xl,
.page-wrapper--content .section-intro .container-xxl {
    max-width: none;
}

/* Force headings on content pages to black */
.page-wrapper--content h1,
.page-wrapper--content h1 *,
.page-wrapper--content h2,
.page-wrapper--content h2 *,
.page-wrapper--content h3,
.page-wrapper--content h3 *,
.page-wrapper--content h4,
.page-wrapper--content h4 *,
.page-wrapper--content h5,
.page-wrapper--content h5 *,
.page-wrapper--content h6,
.page-wrapper--content h6 * {
    color: #000 !important;
}

/* ==========================================================================
   ACCORDION
   ========================================================================== */

.accordion,
.accordion .accordion-item,
.accordion .accordion-button,
.accordion .accordion-button:not(.collapsed),
.accordion .accordion-body {
    background-color: transparent;
}

.accordion .accordion-button:not(.collapsed) {
    color: var(--bs-primary);
}

/* Ensure collapsed buttons always have full border-radius (items have gaps, not stacked) */
.accordion .accordion-button.collapsed {
    border-bottom-left-radius: var(--bs-accordion-inner-border-radius) !important;
    border-bottom-right-radius: var(--bs-accordion-inner-border-radius) !important;
}

/* ==========================================================================
   HEADER SEARCH
   ========================================================================== */

.header-search .fields {
    background: var(--bs-tertiary-bg);
}

.header-search [type="submit"]:hover {
    background-color: var(--ss-text);
    color: var(--ss-surface);
}

.header-search [type="submit"]:hover svg path {
    fill: var(--ss-surface);
}

/* Center the search field on desktop by absorbing free space symmetrically.
   The header uses justify-content:space-between with logo (left) and
   header-meta (right). At the 1800px+ breakpoint the logo shrinks to 150px
   while header-meta stays 183px, shifting the search off-centre.
   Auto margins on a flex item override the parent's space-between and pull
   the element to the visual centre. */
@media (min-width: 1024px) {
    .header-search {
        margin-left: auto;
        margin-right: auto;
    }
}

/* ==========================================================================
   NOTIFICATIONS
   ========================================================================== */

.dropdown-notifications .btn-circle .notification-active {
    background-color: var(--ss-accent);
    border-color: var(--ss-surface);
}

/* Clip child backgrounds to the dropdown's 20px radius so invite items
   (which have their own background) don't bleed past the rounded corners. */
.dropdown-notifications .dropdown-menu {
    overflow: hidden;
}

.dropdown-notifications .notifications-header ul button {
    font-weight: 400;
    color: var(--bs-secondary-color);
}

.dropdown-notifications .notifications-header ul button.active {
    font-weight: 700;
    color: var(--ss-text);
}

.dropdown-notifications .notifications-list {
    background-color: transparent;
    padding-top: 0;
}

/* Notification figures: circular container matching avatar dimensions.
   Avatars already have border-radius:50% + object-fit:cover inline; the bulb
   icon needs to be centered and padded inside the same circle shape. */
.dropdown-notifications .notifications-item figure,
.dropdown-notifications .notifications-update-item figure {
    border-radius: 50%;
    border: 1px solid var(--ss-border);
    background: var(--ss-surface);
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Avatar images: fill the circle edge-to-edge. */
.dropdown-notifications .notifications-item figure img:not([src*="bulb"]),
.dropdown-notifications .notifications-update-item figure img:not([src*="bulb"]) {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Bulb icon: centered with breathing room inside the circle. */
.dropdown-notifications .notifications-item figure img[src*="bulb"],
.dropdown-notifications .notifications-update-item figure img[src*="bulb"] {
    width: 55%;
    height: 55%;
    object-fit: contain;
}

/* item-body must grow to fill available width so item-date is pushed to the far right. */
.dropdown-notifications .notifications-item .item-body,
.dropdown-notifications .notifications-update-item .item-body {
    flex: 1;
    min-width: 0;
}

.dropdown-notifications .notifications-list .notifications-item figure .item-status {
    border-color: var(--ss-surface);
    background-color: var(--bs-secondary);
}

/* Invite items (friend request + activity invitation) sit on the standard
   page background to visually distinguish them from plain notifications. */
.dropdown-notifications .notifications-list .notifications-item--invite {
    background-color: var(--bs-body-bg);
    border-bottom: 0 !important;
}

.dropdown-notifications .notifications-list .notifications-item--invite figure .item-status {
    border-color: var(--bs-body-bg);
}

/* Invite action buttons: reject = surface, accept = secondary. */
.dropdown-notifications .notifications-item--invite .btn-reject,
.dropdown-notifications .notifications-item--invite .btn-invitation-decline {
    --bs-btn-bg: var(--ss-surface);
    --bs-btn-border-color: var(--ss-border);
    --bs-btn-color: var(--ss-text);
    --bs-btn-hover-bg: var(--ss-surface);
    --bs-btn-hover-border-color: var(--ss-border);
    --bs-btn-hover-color: var(--ss-text);
    --bs-btn-active-bg: var(--ss-border);
    --bs-btn-active-border-color: var(--ss-border);
    --bs-btn-active-color: var(--ss-text);
}

.dropdown-notifications .notifications-item--invite .btn-accept,
.dropdown-notifications .notifications-item--invite .btn-invitation-accept {
    --bs-btn-bg: var(--bs-secondary);
    --bs-btn-border-color: var(--bs-secondary);
    --bs-btn-color: var(--ss-surface);
    --bs-btn-hover-bg: var(--bs-secondary);
    --bs-btn-hover-border-color: var(--bs-secondary);
    --bs-btn-hover-color: var(--ss-surface);
    --bs-btn-active-bg: var(--bs-secondary);
    --bs-btn-active-border-color: var(--bs-secondary);
    --bs-btn-active-color: var(--ss-surface);
}

/* Filter pills in the dropdown header — custom click handler, not Bootstrap tabs. */
.dropdown-notifications .notifications-header ul .nav-link[data-filter] {
    cursor: pointer;
}

/* Filter rules: hide items whose kind doesn't match the active filter. */
.dropdown-notifications .notifications-list[data-filter="invite"] > [data-notification-kind="update"],
.dropdown-notifications .notifications-list[data-filter="update"] > [data-notification-kind="invite"] {
    display: none;
}

/* Notification item body text: remove top margin and align line-height with the date column. */
.dropdown-notifications .notifications-list .notifications-item .item-body p {
    margin-top: 0;
    line-height: 1.3;
}

/* Date column: match line-height and always pin to top-right regardless of body height. */
.dropdown-notifications .notifications-list .notifications-item .item-date {
    line-height: 1.3;
    align-self: flex-start;
}

/* ==========================================================================
   USER CARDS — display-teaser, display-card, display-member
   ========================================================================== */

.user.display-teaser figure .icon {
    background-color: var(--bs-tertiary-bg);
}

.user.display-card .card figure .icon {
    background-color: var(--bs-tertiary-bg);
}

.user.display-card .card .position {
    background: var(--bs-body-bg);
    margin-top: 0.5rem;
}

.user.display-card .card.role-admin .card-img-top {
    border-color: var(--ss-quaternary) !important;
}

.user.display-card .card.role-organisator .card-img-top {
    border-color: var(--ss-accent) !important;
}

.user.display-card .card.role-admin .badge {
    background-color: var(--ss-quaternary);
}

.user.display-card .card.role-organisator .badge {
    background-color: var(--ss-accent);
}

.user.display-card .card .friend-icon--connected {
    color: var(--ss-accent);
}

.user.display-card .card .friend-icon--pending {
    color: var(--ss-quinary);
}

.user.display-card .card.role-admin .card-img-top::after,
.user.display-card .card.role-organisator .card-img-top::after {
    border-color: var(--ss-surface);
}

.user.display-member .position {
    background: var(--bs-body-bg);
    margin-top: 0.5rem;
}

/* ==========================================================================
   CARDS — date badge, image badges, small cards
   ========================================================================== */

.card .card-img-top .card-date,
.invitation-hero .card-date {
    background-color: var(--ss-text);
    color: var(--ss-surface);
}

.card .card-img-top .card-date .reload svg path[stroke],
.card .card-img-top .card-date .reload svg circle[stroke],
.invitation-hero .card-date .reload svg path[stroke],
.invitation-hero .card-date .reload svg circle[stroke] {
    stroke: var(--ss-surface);
}

.invitation-hero .card-date {
    position: absolute;
    left: 10px;
    bottom: 10px;
    z-index: 1;
    font-weight: 500;
    border-radius: 7px;
    padding: 0 0 0 10px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    letter-spacing: .04em;
}

.invitation-hero .card-date .reload {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    border-left: 1px solid var(--bs-dark-border-subtle);
    padding: 6px 10px;
    margin-left: 10px;
}

/* Visually center the calendar-reload icon whose weight is skewed
   by the reload arrows in the bottom-right quadrant. */
.reload svg {
    transform: translate(-1px, -1px);
}

.card-small a {
    color: var(--ss-text);
}

.card-small a p {
    color: var(--ss-text);
}

/* ==========================================================================
   IMAGE SLIDER
   ========================================================================== */

.image-slider .slider-date {
    background-color: var(--ss-text);
    color: var(--ss-surface);
}

/* Override styles.css global "svg path[stroke] { stroke: var(--bs-body-color) }"
   so SVG icons inside the slider date badge inherit the badge text color. */
.image-slider .slider-date svg path[stroke],
.image-slider .slider-date svg circle[stroke] {
    stroke: var(--ss-surface);
}

/* ==========================================================================
   FILTER
   ========================================================================== */

.filter {
    background-color: transparent;
    padding: 8px 5px 5px;
}

/* ==========================================================================
   CALENDAR LEGEND
   ========================================================================== */

/* TASK-KGW9A (WTWY-675): legend dots use the tenant branding palette
 * — Angemeldet = Akzentfarbe, Abgemeldet = Quinärfarbe, Warteliste =
 * Sekundärfarbe. Previously hardcoded to Bootstrap defaults
 * (success / body-bg / danger) which ignored tenant theming. */
.calendar-legend ul li:nth-child(1)::before {
    background-color: var(--ss-accent);
}

.calendar-legend ul li:nth-child(2)::before {
    background-color: var(--ss-quinary);
}

/* ==========================================================================
   CALENDAR DATE DOTS (VanillaCalendar)
   TASK-KGW9A (WTWY-675): map activity-date highlights to the tenant
   branding palette so the calendar matches its legend:
     - Angemeldet (selectedHolidays) → Akzentfarbe (--ss-accent)
     - Abgemeldet (disableDates + selected) → Quinärfarbe (--ss-quinary)
     - Warteliste → Sekundärfarbe — see legend rule near the bottom of
       this file. Date-dot rule is omitted because the calendar JS
       (assets/app/js/activity-detail-calendar.js) does not yet receive
       waitlist data; the legend tile is a placeholder. Date-dot styling
       can be added in a follow-up once the data plumbing lands.

   IMPORTANT: The compiled CSS nests selectors under [data-vc-theme=light],
   adding +1 attribute specificity. All overrides MUST include this prefix
   to match or exceed the compiled specificity.
   ========================================================================== */

/* Base selected date (fallback — all activity dates) */
[data-vc-theme=light] .vc-date[data-vc-date-selected] .vc-date__btn,
[data-vc-theme=light] .vc-date[data-vc-date-selected] .vc-date__btn:hover {
    background-color: var(--ss-quinary);
}

/* Registered dates (selectedHolidays) — Akzentfarbe */
[data-vc-theme=light] .vc-date[data-vc-date-holiday][data-vc-date-selected] .vc-date__btn,
[data-vc-theme=light] .vc-date[data-vc-date-holiday][data-vc-date-selected] .vc-date__btn:hover,
[data-vc-theme=light] .vc-date[data-vc-date-weekend][data-vc-date-selected] .vc-date__btn,
[data-vc-theme=light] .vc-date[data-vc-date-weekend][data-vc-date-selected] .vc-date__btn:hover {
    background-color: var(--ss-accent);
    color: var(--ss-surface);
}

/* Unregistered dates (disableDates + selected) — Quinärfarbe */
[data-vc-theme=light] .vc-date[data-vc-date-disabled][data-vc-date-selected] .vc-date__btn,
[data-vc-theme=light] .vc-date[data-vc-date-disabled][data-vc-date-selected] .vc-date__btn:hover {
    background-color: var(--ss-quinary);
    color: var(--ss-text);
}

/* Next/prev month selected dates — keep transparent */
[data-vc-theme=light] .vc-date[data-vc-date-selected][data-vc-date-month=next] .vc-date__btn,
[data-vc-theme=light] .vc-date[data-vc-date-selected][data-vc-date-month=next] .vc-date__btn:hover,
[data-vc-theme=light] .vc-date[data-vc-date-selected][data-vc-date-month=prev] .vc-date__btn,
[data-vc-theme=light] .vc-date[data-vc-date-selected][data-vc-date-month=prev] .vc-date__btn:hover {
    background-color: transparent;
}

/* Date text color — use branding */
[data-vc-theme=light] .vc-date__btn,
[data-vc-theme=light] .vc-date__btn:hover {
    color: var(--ss-text);
}

/* Today highlight — keep transparent bg */
[data-vc-theme=light] .vc-date[data-vc-date-today] .vc-date__btn,
[data-vc-theme=light] .vc-date[data-vc-date-today] .vc-date__btn:hover {
    color: var(--ss-text);
}

/* ==========================================================================
   WIZARD PROGRESS INDICATOR (step-count)
   Compiled CSS uses #10c348 (green) and #d8dee3 (grey).
   Figma design uses Color 4 (quaternary, #F0872E) for active steps.
   ========================================================================== */

.section-form .step-count span {
    background-color: var(--ss-border);
}

.section-form .step-count[data-step="1"] span:nth-child(1) {
    background-color: var(--ss-quaternary);
}

.section-form .step-count[data-step="2"] span:nth-child(1),
.section-form .step-count[data-step="2"] span:nth-child(2) {
    background-color: var(--ss-quaternary);
}

.section-form .step-count[data-step="3"] span:nth-child(1),
.section-form .step-count[data-step="3"] span:nth-child(2),
.section-form .step-count[data-step="3"] span:nth-child(3) {
    background-color: var(--ss-quaternary);
    box-shadow: 0 1px 7px 0 rgba(var(--ss-quaternary-rgb), 0.3);
}

.section-form .step-count[data-step="4"] span:nth-child(1),
.section-form .step-count[data-step="4"] span:nth-child(2),
.section-form .step-count[data-step="4"] span:nth-child(3),
.section-form .step-count[data-step="4"] span:nth-child(4) {
    background-color: var(--ss-quaternary);
    box-shadow: 0 1px 7px 0 rgba(var(--ss-quaternary-rgb), 0.3);
}

.section-form .step-count[data-step="5"] span {
    background-color: var(--ss-quaternary);
    box-shadow: 0 1px 7px 0 rgba(var(--ss-quaternary-rgb), 0.3);
}

.section-form .step-count[data-step="5"] span:last-child {
    background-color: var(--ss-border);
    box-shadow: none;
}

.section-form .step-count[data-step="6"] span {
    background-color: var(--ss-quaternary);
    box-shadow: 0 1px 7px 0 rgba(var(--ss-quaternary-rgb), 0.3);
}

/* ==========================================================================
   SECTION FORM — background
   ========================================================================== */

.section-form .form {
    background-color: var(--ss-surface);
}

/* ==========================================================================
   TERMS TABLE
   ========================================================================== */

.terms-table .members-list li {
    background-color: var(--bs-tertiary-bg);
}

.terms-table .members-list li span {
    color: var(--ss-text);
}

/* ==========================================================================
   BLOCKQUOTE WRAPPER
   ========================================================================== */

.blockquote-wrapper {
    background-color: var(--bs-tertiary-bg);
}

/* ==========================================================================
   MODALS / MAP / MISC
   ========================================================================== */

/* AC 1: Modal background uses surface color */
.modal .modal-content {
    background-color: var(--ss-surface);
}

/* Bootstrap adds padding-right to body when a modal opens to compensate for
   the scrollbar it expects to hide. Since our scrollbar lives on html (not body),
   it never disappears — so Bootstrap's compensation is wrong and causes a shift. */
body.modal-open {
    padding-right: 0 !important;
}

/* TASK-440: Increase modal padding to match Figma (generous spacing on all sides).
   Override Bootstrap CSS custom properties at the .modal scope so that
   var(--bs-modal-padding) and var(--bs-modal-header-padding) resolve to the
   larger value, then also set explicit directional padding on each element to
   ensure all four sides are affected uniformly. */
.modal {
    --bs-modal-padding: 1.5rem;
    --bs-modal-header-padding: 1.5rem;
    --bs-modal-header-padding-x: 1.5rem;
    --bs-modal-header-padding-y: 1.5rem;
}

.modal .modal-body {
    padding-top: 1.5rem;
    padding-right: 1.5rem;
    padding-bottom: .5rem;
    padding-left: 1.5rem;
}

.modal .modal-header {
    padding-top: 1.5rem;
    padding-right: 1.5rem;
    padding-bottom: 1.5rem;
    padding-left: 1.5rem;
}

.modal .modal-footer {
    padding-top: 1.5rem;
    padding-right: 1.5rem;
    padding-bottom: 1.5rem;
    padding-left: 1.5rem;
}

.modal .btn-close {
    background-color: var(--ss-surface);
    color: var(--ss-text);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.65rem;
    padding: 0.5rem;
}

.modal .actions button {
    background-color: var(--ss-surface);
}

/* ==========================================================================
   USER DETAIL MODAL (TASK-325)
   ========================================================================== */

/* Modal width tightened to match Figma reference (~360px) */
.modal-user .modal-dialog {
    max-width: 360px;
}

/* Avatar sized down to match Figma reference (100×100) */
#modalMember .display-member figure {
    width: 100px;
    height: 100px;
    margin-bottom: 12px;
}

/* AC 7: Admin/Organizer badge in top-right corner */
#modalMember .modal-role-badge {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 1;
}

#modalMember .modal-role-badge .badge--admin {
    background-color: var(--ss-quaternary);
    color: var(--ss-surface);
    padding: 4px 12px;
    border-radius: 6px;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
}

#modalMember .modal-role-badge .badge--organizer {
    background-color: var(--ss-accent);
    color: var(--ss-surface);
    padding: 4px 12px;
    border-radius: 6px;
    font-size: 12px;
    font-weight: 600;
}

/* Request-sent pill badge (friend request pending state) */
#modalMember .badge--request-sent {
    background-color: rgba(var(--ss-quinary-rgb), 0.4);
    color: var(--ss-text);
    padding: 10px 18px;
    min-height: 40px;
    border-radius: 999px;
    font-weight: 500;
    font-size: 0.8125rem;
    line-height: 1.2;
    text-transform: none;
    letter-spacing: normal;
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

#modalMember .badge--request-sent__icon {
    flex-shrink: 0;
}

#modalMember .badge--request-sent__icon-seal {
    fill: var(--ss-quinary) !important;
}

#modalMember .badge--request-sent__icon-check {
    fill: var(--bs-body-bg) !important;
}

/* Tab content: no inner scrollbars, no pane borders (de-iframe look) */
#modalMember .tab-content,
#modalMember .team-member .tab-content {
    height: auto;
    overflow: visible;
}

#modalMember .tab-content .tab-pane {
    border-top: none;
    padding-top: 12px;
}

/* AC 3: Selected tab text is bold */
#modalMember .nav-tabs .nav-link.active {
    font-weight: 600;
}

.map-wrapper .btn-map-close {
    background-color: var(--ss-surface);
}

.map-wrapper .btn-map-close svg {
    width: 15px;
    height: 15px;
    display: block;
    margin: auto;
}

/* ==========================================================================
   CARD BACKGROUNDS (light theme)
   ========================================================================== */

[data-bs-theme="light"] .card {
    background-color: var(--ss-surface) !important;
}

/* ==========================================================================
   TOM SELECT — branding overrides for tom-select.bootstrap5.css
   ========================================================================== */

/* Height alignment — match .form-control (padding .5rem .8rem, line-height 1.85)
   so Tom Select fields are the same height as text inputs in auth/registration forms. */
.section-intro .ts-wrapper.single .ts-control,
.section-intro .ts-wrapper.multi .ts-control {
    padding: .5rem .8rem;
    line-height: 1.85;
    font-size: 0.875rem;
    min-height: unset;
}

/* Widget wrapper — border, background, and height matching .form-control
   (padding .5rem .8rem, line-height 1.85, font-size 0.875rem to match project input sizing) */
.ts-wrapper.single .ts-control,
.ts-wrapper.multi .ts-control {
    border-color: var(--ss-border);
    background-color: var(--ss-surface);
    color: var(--ss-text);
    padding: .5rem .8rem;
    line-height: 1.85;
    font-size: 0.875rem;
    min-height: unset;
    gap: 5px;
}

/* Hold the control to a fixed height that matches .form-control (42px) regardless
   of whether items are selected. Tom Select's Bootstrap5 theme shrinks top/bottom
   padding on `.has-items` states, which caused the widget to jump from 46px empty
   to 38px filled. Align both states on the form-control sizing. */
.ts-wrapper.multi.has-items .ts-control,
.ts-wrapper.single.has-items .ts-control {
    padding: .5rem .8rem;
}

/* Neutralise the 3px item bottom-margin and the 1px vertical padding added by the
   Bootstrap5 theme so the tag's outer box height matches the inner input — keeping
   the widget at a fixed 42px regardless of empty/filled state. */
.ts-wrapper.multi .ts-control > .item,
.ts-wrapper.single .ts-control > .item {
    margin: 0;
    padding-top: 0;
    padding-bottom: 0;
}

/* Focus state */
.ts-wrapper.single.focus .ts-control,
.ts-wrapper.multi.focus .ts-control {
    border-color: var(--ss-border);
    box-shadow: none;
}

/* Invalid state — Tom Select hides the original .form-control-style input,
   so the standard `.form-control.is-invalid` border rule never reaches the
   visible widget. Mirror it onto the adjacent .ts-wrapper. */
.tagsInput.is-invalid + .ts-wrapper .ts-control,
.is-invalid > .ts-wrapper .ts-control {
    border-color: var(--bs-danger);
}

/* Dropdown menu */
.ts-dropdown {
    background-color: var(--ss-surface);
    border-color: var(--ss-border);
    color: var(--ss-text);
}

/* Dropdown option — hover/active state */
.ts-dropdown .option.active {
    background-color: var(--bs-body-bg);
    color: var(--ss-text);
}

/* Selected option highlight in dropdown */
.ts-dropdown .option.selected {
    background-color: var(--bs-primary);
    color: var(--ss-surface);
}

/* Multi-select tags (items) */
.ts-wrapper.multi .ts-control > .item {
    background-color: var(--bs-primary);
    border-color: var(--bs-primary);
    color: var(--ss-surface);
}

/* Remove button in TomSelect tag pills — centered, primary color (TASK-387) */
/* Selector targets the plugin-remove_button structure to apply globally across all
   TomSelect instances: registration, activity wizard, activity edit, profile settings */
.ts-wrapper.plugin-remove_button .item .remove {
    background-color: var(--bs-primary);
    border-color: var(--bs-primary);
    color: var(--ss-surface);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    padding: 0 6px;
}

/* Input text color inside the control. Also pin font-size/line-height to match the
   control's own sizing so the input doesn't push the widget taller than other form
   fields. The Bootstrap5 Tom Select theme uses `line-height: inherit !important`
   and `font-size: inherit` on `.ts-control > input`, which resolves to the body's
   16px/29.6px — pushing the empty widget to ~47px. Force .875rem/1.85 with
   `!important` to match the control's own sizing and hold a stable 42px height. */
.ts-control > input {
    color: var(--ss-text);
    font-size: 0.875rem !important;
    line-height: 1.85 !important;
}

/* Placeholder text */
.ts-control > input::placeholder {
    color: rgba(var(--ss-text-rgb), 0.5);
}

/* Optgroup header */
.ts-dropdown .optgroup-header {
    color: var(--ss-text);
    background-color: var(--bs-body-bg);
}

/* No results message */
.ts-dropdown .no-results {
    color: var(--ss-text);
}

/* Loading spinner — TASK-1N3Z9 (WTWY-661).
 * The Bootstrap 5 Tom Select theme renders the loading indicator as a 30×30 .spinner
 * at the top of the dropdown's option list. That takes vertical space inside the
 * dropdown and shifts the option list up when loading completes, producing the UI
 * "jump" reported in the ticket. Hide that in-dropdown spinner and render a small
 * 16×16 inline indicator absolutely positioned at the right edge of the .ts-control
 * input area instead. The control already has position: relative, and the
 * --ts-pr-caret / --ts-pr-clear-button custom properties expose the right-side
 * padding the widget reserves for its caret / clear button, so we offset our
 * spinner just to the left of those without overlapping. */
.ts-dropdown .spinner {
    display: none;
}

.ts-wrapper.loading > .ts-control::after {
    content: "";
    position: absolute;
    top: 50%;
    right: calc(var(--ts-pr-caret, 0px) + var(--ts-pr-clear-button, 0px) + 0.5rem);
    width: 16px;
    height: 16px;
    margin-top: -8px;
    border: 2px solid var(--ss-border);
    border-top-color: var(--bs-secondary-color);
    border-radius: 50%;
    animation: ts-loading-spin 0.8s linear infinite;
    pointer-events: none;
}

@keyframes ts-loading-spin {
    to { transform: rotate(360deg); }
}

/* Header search dropdown overflow — TASK-VNW4C (WTWY-662).
 * styles.css ships `.search-drop { left: 5px; width: calc(100vw - 10px); ... }`.
 * `100vw` resolves to the layout viewport width including the vertical
 * scrollbar gutter, but the sticky header (the dropdown's containing block)
 * is sized to the document's content area, which excludes that gutter on
 * platforms with persistent scrollbars (Windows, Linux). The dropdown ends
 * up `scrollbarWidth - 0` pixels wider than its container and forces a
 * page-level horizontal scrollbar whenever it's open. Replace the explicit
 * width with a symmetric `right: 5px` so the dropdown tracks the
 * containing block instead of the viewport. */
.search-drop {
    width: auto;
    right: 5px;
}

/* ==========================================================================
   GEOCODING SUGGESTIONS (autocomplete dropdown in wizard step 4)
   Extracted from inline <style> block in step-4.php
   ========================================================================== */

.geocoding-suggestions {
    list-style: none;
    margin: 0;
    padding: 0;
    border: 1px solid var(--ss-border);
    border-top: none;
    position: absolute;
    left: 0;
    right: 0;
    background: var(--ss-surface);
    z-index: 1000;
    max-height: 200px;
    overflow-y: auto;
    border-radius: 0 0 6px 6px;
}

.geocoding-suggestions li {
    padding: 8px 12px;
    cursor: pointer;
    font-size: 14px;
    color: var(--ss-text);
}

.geocoding-suggestions li:hover {
    background-color: var(--bs-body-bg);
}

/* ==========================================================================
   SECTION HEADER — sort dropdown responsive visibility (TASK-331)
   The compiled CSS hides .custom-select-container:last-child (the sort
   dropdown) below 1024px. Override to keep it visible at all widths.
   ========================================================================== */

@media (max-width: 1023.98px) {
    .listing .section-header .custom-select-container:last-child {
        display: block;
    }
}

/* Consistent section-header height across all listing pages (activities, locations, colleagues).
   The right row is 41px when a btn-sm CTA is present; enforce the same min-height everywhere. */
.listing .section-header > div + div {
    min-height: 41px;
}

/* Section-header alignment lives in site/snippets/app/dashboard/styles.php
   (single owner — see TASK-Y1DDX). That snippet is loaded by every template
   that uses `.listing` (dashboard, activities, locations, colleagues,
   wishlist), so the inline rule has the same global reach the rule here
   previously had, and its later cascade position means !important is no
   longer needed to beat Bootstrap's `.align-items-center` utility. */

/* Listing section titles render at 18px. Scoped to .listing so detail-page
   and modal h2 elements are unaffected. */
.listing .section-header h2 {
    font-size: 18px;
}

/* TASK-MKCMW — Filter modal main heading (Filter Activities/Locations/Colleagues) shrunk
   to 16px per Figma. One rule covers all three modals via the shared .filter-header chrome
   in components/filter-panel.php. */
.filter .filter-header h3 {
    font-size: 16px;
}

/* TASK-MKCMW — checkbox-group facets (Sprache/Schwierigkeit/Intensität) inside the
   activities filter. Stacks dynamically-rendered .form-check rows with consistent spacing. */
.filter-checkbox-group .form-check + .form-check {
    margin-top: 0.25rem;
}

/* Wishlist has only a title div (no sort/filter divs), so enforce the same height
   as the first div on other listing pages (driven by select element height). */
.listing--wishlist .section-header > div {
    min-height: 41px;
}

@media (min-width: 1800px) {
    .listing .section-header {
        padding: 34px 0 0;
        margin: 0 0 20px;
    }
}

/* ==========================================================================
   WAVE 4 — Comprehensive override coverage (override-coverage gate)
   All selectors below were flagged by scripts/check-override-coverage.sh
   as having hardcoded colors in _styles.css without branding overrides.
   ========================================================================== */

/* --- Accordion (base + button text) --- */
.accordion .accordion-button {
    color: var(--ss-text);
}

.accordion .accordion-body::before {
    background-color: var(--ss-border);
}

/* Registration interests step: accordion sits on bg-body (beige), not on a
   white surface card, so the default surface/tertiary backgrounds create
   visible white boxes. Make them transparent to blend with the page bg. */
.section-intro .accordion {
    background-color: transparent;
}

.section-intro .accordion .accordion-item {
    background-color: transparent;
}

.section-intro .accordion .accordion-button:not(.collapsed) {
    background-color: transparent;
}

.section-intro .accordion .accordion-body {
    background-color: transparent;
}

/* --- Alerts --- */
.alert.alert-info {
    background-color: var(--bs-body-bg);
}

.alert.alert-info strong {
    color: var(--ss-text);
}

/* Prevent Bootstrap was-validated from green-styling form-check inputs/labels */
.was-validated .form-check-input:valid {
    border-color: var(--bs-border-color);
}
.was-validated .form-check-input:valid ~ .form-check-label {
    color: inherit;
}

/* --- Badge text --- */
.badge {
    color: var(--ss-surface);
}

/* --- Background utilities --- */
.bg-gray100 {
    background-color: var(--bs-tertiary-bg);
}

.terms-table th {
    background-color: var(--bs-body-bg);
}

.bg-gray200 {
    background-color: var(--bs-secondary-bg);
}

/* Search-dropdown interest pills inherit the canonical .badge.bg-body styling
   (docs/prototypes/components/badge.html). The override below only neutralises
   _styles.css rules that target `.search-interests ul li a` directly — the
   badge classes already paint background and color, so anything we set here
   has to clear cleanly so the badge wins. */
.search-drop .search-interests ul li a {
    background-color: transparent;
}

/* Cities column: kill the Bootstrap default link-blue that leaks into the
   building icon (`stroke="currentColor"`) and the city label. The list lives
   inside the white .search-drop surface, so the body color is the right tone.
   Cities are secondary navigation rendered at the small text scale (13/19),
   weight 400, primary text color — see TASK-3FMST typography spec. The size
   override has to live on this selector because styles.css declares
   `.search-drop .search-places ul li a { font-size: 1rem }` at the same
   specificity, which would otherwise shadow a `.text-xs` utility on the ul. */
.search-drop .search-places ul li a {
    color: var(--ss-text);
    font-weight: 400;
    font-size: 0.8125rem;
    line-height: 1.4615;
}

/* Keep the icon at its declared 16x16 box even when the city label is long
   enough to wrap to a second line (e.g. "Matten bei Interlaken"). Without
   `flex-shrink: 0` the SVG gets squeezed by the flex container and renders
   as a tall, distorted sliver. */
.search-drop .search-places ul li svg,
.search-drop .search-places ul li img {
    flex-shrink: 0;
}

.calendar-legend ul {
    background-color: var(--bs-body-bg);
}

.bg-gray400 {
    background-color: var(--ss-border);
}

/* --- Blockquote (full specificity to override _styles.css) --- */
.blockquote-wrapper .blockquote {
    color: var(--ss-text);
}

.blockquote-wrapper .blockquote-footer {
    color: var(--ss-text);
}

.blockquote-wrapper .blockquote-footer::before {
    background-color: var(--ss-quaternary);
}

/* --- Btn-close — vertically centered, proportional, branding color (TASK-387) --- */
.btn-close {
    color: var(--ss-text);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.25rem;
    font-size: 0.75rem;
    opacity: 0.65;
}

.btn-close:hover {
    opacity: 1;
}

/* --- Calendar legend --- */
.calendar-legend ul {
    color: var(--ss-text);
}

.calendar-legend ul li:nth-child(3)::before {
    /* TASK-KGW9A (WTWY-675): Warteliste = Sekundärfarbe. */
    background-color: var(--bs-secondary);
}

/* --- Card badge on image --- */
.card .card-img-top .badge {
    color: var(--ss-surface);
    background-color: var(--bs-secondary);
}

/* --- Image slider badge + pagination --- */
.image-slider .badge {
    color: var(--ss-surface);
    background-color: var(--bs-secondary);
}

.image-slider .swiper-pagination .swiper-pagination-bullet.swiper-pagination-bullet-active {
    border-color: var(--ss-surface);
}

/* --- Collapsible toggle --- */
.collapsible .btn-collapsible {
    color: var(--bs-secondary-color);
}

/* --- POI modal: opening hours table (Figma: left-aligned day, right-aligned time, row separators) --- */
.poi-modal-opening-hours tr {
    border-bottom: 1px solid var(--ss-border);
}
.poi-modal-opening-hours tr:last-child {
    border-bottom: 0;
}

/* --- POI modal: collapsible heading has no border/background box --- */
#poiModalOpeningHoursSection .btn-collapsible,
#poiModalActivitiesSection .btn-collapsible {
    background: none;
    border: 0;
    padding: 0;
    margin: 0 0 .5rem;
}

/* --- POI modal: content sections align with the cover image edges.
   Content sections use the same -.5rem offset as the cover image
   so their edges align with the modal wall. --- */
.poi-modal-section {
    margin-left: -.5rem;
    margin-right: -.5rem;
}

/* --- Custom select panel decoration (border via ::before pseudo-element) --- */
.custom-select-container .custom-select-panel::before {
    border-color: var(--ss-border);
}

/* --- Dropdown menus --- */
.dropdown-menu {
    background-color: var(--ss-surface);
    color: var(--ss-text);
    --bs-dropdown-link-active-bg: var(--bs-primary);
}

.dropdown-notifications .dropdown-menu,
.dropdown-user .dropdown-menu {
    box-shadow: 0 4px 12px rgba(var(--ss-text-rgb), 0.13);
}

.section-content-page .content-page-header .dropdown .dropdown-menu {
    background-color: var(--ss-surface);
}

/* --- Hamburger menu icon --- */
.dropdown-user .dropdown-user-toggle .toggler-icon .toggler-icon-inner,
.dropdown-user .dropdown-user-toggle .toggler-icon .toggler-icon-inner::before,
.dropdown-user .dropdown-user-toggle .toggler-icon .toggler-icon-inner::after {
    background: var(--bs-secondary-color);
}

/* --- User menu logout link: match nav item font size --- */
.user-logout a {
    font-size: 0.875rem;
}

/* --- Filter inner shadow --- */
.filter .inner {
    position: relative;
    box-shadow: 0 4px 12px rgba(var(--ss-text-rgb), 0.13);
}

/* Prevent scroll chaining: when filter reaches top/bottom, don't scroll the page behind it */
.filter {
    overscroll-behavior: contain;
}

/* Hide native scrollbar on .filter-main and #poiModalBody — replaced by custom JS thumb */
.filter-main,
#poiModalBody {
    scrollbar-width: none;
}
.filter-main::-webkit-scrollbar,
#poiModalBody::-webkit-scrollbar {
    display: none;
}

/* Custom scrollbar track + thumb injected by filter-scrollbar.js */
.filter-scrollbar-track {
    position: absolute;
    right: 2px;
    width: 3px;
    pointer-events: none;
    z-index: 1;
}
.filter-scrollbar-thumb {
    position: absolute;
    width: 3px;
    left: 0;
    background: var(--bs-gray-400);
    border-radius: 3px;
    pointer-events: auto;
    cursor: pointer;
    transition: background 0.15s;
}
.filter-scrollbar-thumb:hover {
    background: var(--bs-gray-500);
}

/* --- Footer separator --- */
.footer nav ul li + li::before {
    background-color: var(--bs-secondary-color);
}

.footer nav ul li {
    display: flex;
    align-items: center;
    line-height: 1;
}

footer.footer button.btn-link {
    font-size: .9375rem;
    line-height: 1;
}

/* --- Form: custom select opener (full specificity) --- */
.form .custom-select-container .custom-select-opener {
    border-color: var(--ss-border);
    background-color: var(--ss-surface);
}

/* --- Form: checkbox indeterminate --- */
.form-check-input[type=checkbox]:indeterminate {
    background-color: var(--bs-primary);
}

/* --- Form: checked label text --- */
.form-check:not(.form-check-inline) .form-check-input:checked + .form-check-label {
    color: var(--ss-text);
}

/* --- Form: placeholder text ---
   Use the 40% "very muted" tier (--bs-tertiary-color), not the 55% muted tier
   (--bs-secondary-color). The 55% tier is what regular body/hint text uses,
   so placeholders rendered at that weight look almost identical to filled
   fields. The 40% tier is reserved for "very muted" content — exactly the
   visual weight a placeholder should carry. */
.form-control::-moz-placeholder {
    color: var(--bs-tertiary-color);
    opacity: 1;
}

.form-control::placeholder {
    color: var(--bs-tertiary-color);
    opacity: 1;
}

/* --- Form: disabled floating labels --- */
.form-floating > :disabled ~ label,
.form-floating > .form-control:disabled ~ label {
    color: var(--bs-secondary-color);
}

/* --- Form: valid focus border + background --- */
.was-validated .form-control:valid:focus,
.form-control.is-valid:focus {
    border-color: var(--ss-border);
    background-color: var(--ss-surface);
}

.was-validated .form-select:valid:focus,
.form-select.is-valid:focus {
    border-color: var(--ss-border);
    background-color: var(--ss-surface);
}

/* --- Form: select focus ring (Firefox) --- */
.form-select:-moz-focusring {
    color: var(--ss-text);
}

/* --- Form: image list background --- */
.form-image-list {
    background: rgba(var(--bs-body-bg-rgb), 0.55);
}

/* --- Form: validation error message --- */
.invalid-feedback {
    color: var(--bs-danger);
}

/* --- Form: hint text (.form-text) turns red when the preceding field is invalid.
   Applies to regular controls, selects, and the custom .multiselect dropdown.
   The hint is always a later sibling of the field within the same wrapper. --- */
.form-control.is-invalid ~ .form-text,
.form-select.is-invalid ~ .form-text,
.multiselect.is-invalid ~ .form-text,
.dropdown.multiselect.is-invalid ~ .form-text {
    color: var(--bs-danger);
}

/* --- Google Maps info window --- */
.gm-style .gm-style-iw-c {
    background-color: var(--ss-surface);
}

.gm-style .gm-style-iw-c .info-win-content,
.gm-style .gm-style-iw-c .info-win-content::before {
    background-color: var(--ss-surface);
}

/* --- Header search --- */
.header-search .fields input {
    color: var(--bs-secondary-color);
}

#searchInput {
    font-size: .8rem !important;
    line-height: 1 !important;
}

.header-search .header-search-wrapper {
    background-color: var(--ss-surface);
}

.header-search .search-options {
    background: rgba(var(--ss-surface-rgb), 0.7);
    justify-content: flex-start;
    position: relative;
}

.header-search .search-options > .icon {
    position: relative;
    top: -1px;
}

.header-search .search-options .custom-select-container {
    flex: 1;
    min-width: 0;
    position: static;
}

.header-search .search-options .custom-select-container select {
    pointer-events: none;
    outline: none;
}

.header-search .search-options .custom-select-container .custom-select-panel {
    left: 0;
    top: 112%;
}

.header-search .search-options .custom-select-opener,
.header-search .search-options .custom-select-opener:focus,
.header-search .search-options .custom-select-opener:focus-visible {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    min-width: 0;
    outline: none;
    box-shadow: none;
}

.header-search [type=submit] {
    background: transparent;
    color: var(--bs-secondary-color);
}

/* --- Header (unlogged) language selector --- */
.header--unlogged .header-language .custom-select-container .custom-select-opener {
    color: var(--ss-text);
}

.header--unlogged .header-language .custom-select-container .custom-select-panel .custom-select-option {
    color: var(--ss-text);
}

.header--unlogged .header-language .custom-select-container .custom-select-panel .custom-select-option.has-focus,
.header--unlogged .header-language .custom-select-container .custom-select-panel .custom-select-option.is-selected {
    color: var(--ss-text);
}

/* --- Map search input --- */
.map-wrapper .map-search .fields input {
    color: var(--bs-secondary-color);
}

/* --- Modal edit button --- */
.modal .actions button.btn-edit {
    background-color: var(--ss-surface);
}

/* --- Multiselect --- */
.multiselect .btn,
.multiselect .form-select {
    color: var(--bs-tertiary-color);
}

.multiselect .values span {
    color: var(--ss-text);
}

/* --- Nav pills --- */
.nav-pills {
    --bs-nav-pills-link-active-bg: var(--bs-primary);
    --bs-nav-pills-link-active-color: var(--ss-surface);
}

/* --- Notification --- */
.notification {
    background-color: var(--ss-surface);
    border-color: var(--ss-border);
}

/* --- Password check list --- */
.password-check-list ul .btn {
    color: var(--ss-text);
    background-color: var(--ss-surface);
    border-color: var(--ss-surface);
    transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.password-check-list ul .btn.active {
    color: var(--ss-surface);
    background-color: var(--bs-success);
    border-color: var(--bs-success);
}

/* Failed-rule chip after a server-side BASPO rejection (TASK-B14SX). The
   `:not(.active)` clause guarantees the green satisfied state still wins
   the moment client-side JS marks a rule as fulfilled, regardless of CSS
   source order. */
.password-check-list ul .btn.is-invalid:not(.active) {
    color: var(--ss-surface);
    background-color: var(--bs-danger);
    border-color: var(--bs-danger);
}

/* --- Search drop --- */
.search-drop {
    background-color: var(--ss-surface);
}

/* The base styles.css ships .search-drop { display: none } but never
   pairs it with a "shown" class — the JS uses the `hidden` HTML attribute
   to toggle visibility. Without this override the dropdown stays
   invisible even when hidden=false. !important is needed because the
   bare .search-drop rule from _styles.css can outrank the override on
   load order in some browser caches even though specificity (0,2,0)
   should beat (0,1,0). */
.search-drop:not([hidden]) {
    display: block !important;
}

/* Keyboard focus indicator on search-drop items (TASK-752). The base
   markup uses .search-item for activity/location/colleague rows and bare
   <li><a> for cities/interests; both highlight via the same modifier class
   on the focused element. */
.search-drop .search-item--focused,
.search-drop a.search-item--focused {
    background-color: var(--bs-secondary-bg);
    border-radius: 6px;
}

/* TASK-753: Compact map-popup row layout for activity/location/colleague
   rows. _styles.css ships .search-item with width 25/33/50% (4-up/3-up/2-up
   grid) and a 70x70 portrait thumbnail — both assume the wide search overlay
   from the prototype. Inside the dashboard's three 1/3-width columns this
   produces overflowing titles and overlapping rows; the same row markup also
   reads heavy on the listing-page variant. Override to a 48x48 squared
   thumbnail + single-line truncated meta to match the marker popup cards
   shared between map and dropdown surfaces. */
.search-drop .search-item {
    width: 100%;
    margin: 0 0 8px;
}

.search-drop .search-item a {
    gap: 10px;
    min-width: 0;
}

.search-drop .search-item figure {
    width: 48px;
    height: 48px;
    flex: 0 0 48px;
    margin: 0;
}

.search-drop .search-item figure img {
    border-radius: 6px;
}

/* Colleague avatars are always circular (matches user-avatar convention used
   elsewhere in the app — card.php participants, header user-menu, etc.). The
   selector targets the colleagues column on every variant: same-scope listing,
   cross-scope cells whose `cell.scope === 'colleagues'`, and the dashboard
   fan-out — all of which render colleague rows into the same container. */
.search-drop [data-search-results="colleagues"] .search-item figure,
.search-drop [data-search-results="colleagues"] .search-item figure img {
    border-radius: 50%;
}

.search-drop .search-item .item-main {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
}

/* Activity rows render as .card-small (not .search-item — see tpl-row-activity
   in app/header/search-drop.php), so the .search-item width override above
   doesn't reach them. Without an explicit basis the d-md-flex flex-wrap
   parent shrink-fits each card to its title, producing the overlapping
   pack visible in the activities listing dropdown (cards spilling onto
   neighbour titles, images overlapping text). Force a clean two-column
   grid on the listing variant. The dashboard variant keeps cards
   one-up because its 1/3-width column is too narrow for two side-by-side. */
.search-drop [data-search-results="activities"] .card-small {
    margin: 0 0 8px;
}

@media (min-width: 768px) {
    .search-drop [data-search-results="activities"] {
        column-gap: 8px;
    }
    .search-drop [data-search-results="activities"] .card-small {
        flex: 0 0 calc(50% - 4px);
        max-width: calc(50% - 4px);
        min-width: 0;
    }
    .search-drop--dashboard [data-search-results="activities"] {
        column-gap: 0;
    }
    .search-drop--dashboard [data-search-results="activities"] .card-small {
        flex: 0 0 100%;
        max-width: 100%;
    }
}

/* Result-row titles — colleagues, locations (.search-item), and the activity
   card (.card-small). Unified per TASK-3FMST typography spec: weight 500,
   15/23, primary text color. Selector covers both row containers so a single
   rule wins regardless of which template renders the row. */
.search-drop .search-item .item-main p,
.search-drop .card-small .item-main p {
    margin: 0;
    font-weight: 500;
    font-size: 0.9375rem;
    line-height: 1.5333;
    color: var(--ss-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.search-drop .search-item .info-list,
.search-drop .card-small .info-list {
    margin: 2px 0 0;
    line-height: 1.4615;
}

/* Result-row subtitles — info-list rows under the title in colleagues,
   locations, and activity cards. Per TASK-3FMST: weight 400, 13/19, muted. */
.search-drop .search-item .info-list li,
.search-drop .card-small .info-list li {
    color: var(--ss-text-muted);
    font-weight: 400;
    font-size: 0.8125rem;
    line-height: 1.4615;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}

/* "Mehr sehen →" link and "Keine Treffer" empty-state copy share the same
   typography per TASK-3FMST: weight 400, 15/23, muted. Both render at the
   bottom of a result column when there are too many or no hits. */
.search-drop .search-drop-more,
.search-drop .search-drop-empty {
    display: inline-block;
    margin-top: 8px;
    color: var(--ss-text-muted);
    text-decoration: none;
    font-weight: 400;
    font-size: 0.9375rem;
    line-height: 1.5333;
}

.search-drop .search-drop-more:hover {
    text-decoration: underline;
}

/* --- Section form step text --- */
.section-form .step-text {
    color: var(--ss-text);
}

/* --- Tabs --- */
.tabs .nav-tabs .nav-link {
    color: var(--bs-secondary-color);
}

.tabs .nav-tabs--type1 .nav-link {
    color: var(--bs-secondary-color);
}

.tabs--accordion .accordion-item .accordion-button[aria-expanded=true] {
    color: var(--ss-text);
}

/* Section item page tab override (media query specificity) */
.section-item-page .section-item-page .tabs .nav-tabs .nav-link.active {
    color: var(--ss-text);
}

/* --- Tags input --- */
.tags-input .tags-list .tag-item {
    color: var(--ss-text);
}

/* --- Terms table header text --- */
.terms-table th {
    color: var(--bs-secondary-color);
}

/* --- Termine tab: collapse rows past the "Mehr anzeigen" threshold (TASK-MGK2F).
       Server emits data-extra="1" on rows beyond the threshold; Alpine root flips
       the .schedule-list-collapsed class on click to reveal them. --- */
.schedule-list-collapsed tr[data-extra="1"] {
    display: none;
}

/* --- User card badge text + role border halos --- */
.user.display-card .card .badge {
    color: var(--ss-surface);
}

.user.display-card .card.role-admin .card-img-top::after,
.user.display-card .card.role-organisator .card-img-top::after {
    border-color: var(--ss-surface);
}

/* ==========================================================================
   AUTH SCREENS — 50/50 split layout with hero image
   The base styles.css already handles the split: .intro-banner is
   position:fixed on the right 50%, .wrapper takes the left 50% at lg+.
   We only need to ensure .wrapper is full-width when no hero image exists.
   ========================================================================== */

/* Use min-height + dvh so mobile Safari can shrink the visible viewport
   when the dynamic toolbar / virtual keyboard is up; the page can also
   grow taller than the viewport when content (label + field + button +
   keyboard overlay) overflows, and the document scrolls naturally.
   `100vh` is kept as a fallback for browsers without dvh support
   (Safari < 15.4, etc.). overflow:hidden was the lock that prevented
   real-device scrolling on iOS Safari / Android Chrome — see
   TASK-M68CA. */
.page-wrapper--login {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
}

.page-wrapper--login .section-intro {
    flex: 1;
    height: auto;
    min-height: unset;
    align-items: flex-start;
}

.page-wrapper--content .section-intro {
    flex: 1;
    height: auto;
    min-height: unset;
    overflow: hidden;
    align-items: flex-start;
}

.page-wrapper--login .footer,
.page-wrapper--content .footer {
    flex-shrink: 0;
    background-color: var(--ss-footer-bg, var(--ss-surface)) !important;
}

/* Split-view starts at lg (992px) — same breakpoint as d-lg-block on .intro-banner.
   Without this, the wrapper stays full-width at 992-1023px while the banner is
   already covering the right 50%, causing a visible jump at 1024px. */
@media (min-width: 992px) {
    .section-intro .intro-banner + .wrapper {
        width: 50%;
    }

    .header--unlogged.has-hero {
        width: 50%;
    }

    .page-wrapper--login:has(.intro-banner) .footer,
    .page-wrapper--content:has(.intro-banner) .footer,
    .page-wrapper--splitview:has(.intro-banner) .footer {
        width: 50%;
    }
}

@media (min-width: 1024px) {
    /* Base styles.css sets .section-intro .wrapper to 50% at 1024px+.
       Override to 100% by default so it is full-width when hero images are disabled. */
    .section-intro .wrapper {
        width: 100%;
    }

    .header--unlogged {
        width: 100%;
        padding: 12px 4px 11px;
    }

    /* Restore 50% when hero image exists (reinforces the 992px rule above) */
    .section-intro .intro-banner + .wrapper {
        width: 50%;
    }

    .header--unlogged.has-hero {
        width: 50%;
    }

    /* Constrain footer to the left column when hero image is visible */
    .page-wrapper--login:has(.intro-banner) .footer,
    .page-wrapper--content:has(.intro-banner) .footer,
    .page-wrapper--splitview:has(.intro-banner) .footer {
        width: 50%;
    }
}

/* Step 4 recommendation cards: single centered column below 550px. */
@media (max-width: 549px) {
    .form-section-recommendation .row-overflow {
        justify-content: center;
    }
    .form-section-recommendation .col-6 {
        flex: 0 0 auto;
        width: 85%;
    }
}

/* Step 4 recommendation cards: keep 2-col in split-view until 1700px.
   col-lg-4 (33%) kicks in at 992px, but in split-view the left half is
   only ~500px wide at that breakpoint — too narrow for 3 cards. */
@media (min-width: 992px) {
    .intro-banner + .wrapper .form-section-recommendation .col-lg-4 {
        width: 50%;
    }
}
@media (min-width: 1700px) {
    .intro-banner + .wrapper .form-section-recommendation .col-lg-4 {
        width: 33.333333%;
    }
}

@media (min-width: 1440px) {
    .header--unlogged {
        padding: 12px 41px 11px 40px;
    }
    .page-wrapper--content .section-intro .container-fluid,
    .page-wrapper--content .section-content-page .container-fluid {
        padding-left: 40px !important;
        padding-right: 41px !important;
    }
}

.section-intro .container-fluid {
    margin: 0 auto;
}


/* Chevron rotation for expandable date participant rows */
.btn-toggle-participants .chevron-icon {
    transition: transform 0.2s ease;
}
.btn-toggle-participants[aria-expanded="true"] .chevron-icon {
    transform: rotate(180deg);
}

/* ==========================================================================
   AVATAR UPLOAD BUTTON
   Uses tenant background color instead of white for the file upload trigger.
   ========================================================================== */

.btn-upload-avatar {
    background-color: var(--bs-body-bg);
    border-color: var(--ss-border);
    color: var(--ss-text);
}

.btn-upload-avatar:hover,
.btn-upload-avatar:active {
    background-color: var(--bs-body-bg);
    border-color: var(--ss-border);
    color: var(--ss-text);
}

/* ==========================================================================
   AVATAR INITIALS PLACEHOLDER
   Replaces stock-photo fallback with branded initials circle.
   ========================================================================== */

.avatar-initials {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    aspect-ratio: 1;
    border-radius: 50%;
    background-color: var(--bs-secondary);
    color: var(--ss-surface);
    font-family: 'Inter Tight', 'Inter', sans-serif;
    font-weight: 600;
    font-size: 1.5rem;
    text-transform: uppercase;
    user-select: none;
}

/* Extra-small variant — avatar stacks in schedule rows */
.avatar-initials--xs {
    width: 24px;
    height: 24px;
    aspect-ratio: auto;
    font-size: 0.5rem;
    border-radius: 50%;
}

/* Small variant — header toggle button, organizer avatar */
.avatar-initials--sm {
    width: 32px;
    height: 32px;
    aspect-ratio: auto;
    font-size: 0.75rem;
    border-radius: 50%;
}

/* Medium variant — dropdown menu avatar */
.avatar-initials--md {
    width: 48px;
    height: 48px;
    aspect-ratio: auto;
    font-size: 1rem;
    border-radius: 50%;
}

/* Modal card-img-top sizing — fill the parent figure (100×100 inside the
   user modal) so the initials variant matches the image variant pixel for
   pixel. Previously this rule pinned the initials to 120×120, overflowing
   the 100×100 figure by 20px and offsetting the circle vs the image. */
#modalMember .card-img-top .avatar-initials {
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
    font-size: 2rem;
}

/* Organizer avatar in activity cards — matches img sizing from compiled CSS */
.display-author figure .avatar-initials {
    width: 20px;
    height: 20px;
    aspect-ratio: auto;
    font-size: 0.45rem;
}

/* Avatar stack in schedule rows — matches members-list img sizing */
.members-list li .avatar-initials {
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
    font-size: 0.45rem;
}

/* ==========================================================================
   CURSOR POINTER — clickable non-link, non-button elements
   ========================================================================== */

/* Activity cards — use onclick for navigation */
.card--activity {
    cursor: pointer;
}

/* User cards — open modal on click */
.card--user {
    cursor: pointer;
}

/* Colleague name: 20px (1.25rem) in both card grid and modal views */
.card--user .card-title,
.user.display-member .card-title {
    font-size: 1.25rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
    min-width: 0;
}

/* Location cards in list view — open modal on click */
.card--location.location-simple {
    cursor: pointer;
}

/* Location card titles must never wrap — truncate with ellipsis.
   `min-width: 0` is required so the flex child shrinks below its
   intrinsic content width and `text-overflow: ellipsis` can engage
   when a bookmark button shares the title row. */
.card--location .card-title {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

/* Compact activity cards — link to activity detail */
.card-small {
    cursor: pointer;
}

/* card-small lays out figure + item-main in a flex row. `.item-main`
   needs `min-width: 0` and `overflow: hidden` so its `text-truncate`
   title actually clips with an ellipsis instead of overflowing. */
.card-small .item-main {
    min-width: 0;
    overflow: hidden;
}

/* Bookmark/wishlist toggle */
.btn-wishlist {
    cursor: pointer;
}

/* Activity card: bookmark sits inline in the title row (flex), not absolute.
   margin-top nudges the icon down from the line-box top onto the title's
   visible cap-height, so its top edge aligns with the top of the glyphs. */
.card--activity .card-title-row .btn-wishlist {
    position: static;
    flex-shrink: 0;
    line-height: 1;
    margin-top: 3px;
}

/* Map multi-activity modal: restore the standard activity card border and
   shadow. The compiled `.modal .card` rule strips both for modals where the
   card IS the modal body (user/location detail), but here the cards are a
   grid of previews inside the modal and should look like regular cards.
   Inset the figure by 11px on top/left/right so the image doesn't touch
   the restored border. */
#modalActivitiesGrid .card--activity,
#modalLocationsGrid .card--location {
    border: var(--bs-card-border-width) solid var(--bs-card-border-color);
    box-shadow: var(--bs-card-box-shadow);
    padding: 11px 11px 0 11px;
}

/* Multi-location and multi-activity modals render cards in a denser grid
   inside modal-lg, so cards are noticeably narrower than the dashboard
   variant. Scale typography proportionally so the title, info list, and
   date overlay line up with the smaller card footprint. */
#modalLocationsGrid .card--location .card-title,
#modalActivitiesGrid .card--activity .card-title {
    font-size: 0.875rem;
}
#modalLocationsGrid .card--location .info-list,
#modalActivitiesGrid .card--activity .info-list,
#modalActivitiesGrid .card--activity .user-wrapper .text-xs {
    font-size: 0.6875rem;
}
#modalActivitiesGrid .card--activity .card-img-top .card-date {
    font-size: 0.75rem;
}

/* Activities and locations cluster modals have no footer, so the default
   shared `.modal .modal-body { padding-bottom: .5rem }` (which assumes a
   footer below provides bottom breathing room) leaves the grid flush with
   the modal panel's bottom edge. Restore comfortable bottom padding for
   these footer-less modals so the last row of cards isn't cropped visually. */
#modalActivities .modal-body,
#modalLocations .modal-body {
    padding-bottom: 1.5rem;
}

/* Interest tag items — removable tags */
.tag-item {
    cursor: pointer;
}

/* POI cards — selectable in wizard */
.poi-card-wrapper {
    cursor: pointer;
}

/* Activity hero slider images — clickable to open lightbox/fullscreen */
.slide-image {
    cursor: pointer;
}

/* Generic: any element with data-bs-toggle (modals, tooltips, dropdowns)
   that isn't already a link or button */
[data-bs-toggle]:not(a):not(button) {
    cursor: pointer;
}

/* Notification items — clickable; relative-positioned so the absolutely-
   positioned trash button anchors to the item's own bottom-right corner
   (not the dropdown-menu's). Mirrors the layout of .notifications-update-item. */
.notifications-item {
    cursor: pointer;
    position: relative;
}
.dropdown-notifications .notifications-list .notifications-item .btn-delete {
    position: absolute;
    right: 16px;
    bottom: 10px;
}

/* Algolia active filter badges — clickable to remove */
.algolia-active-filters .badge {
    cursor: pointer;
}

/* Accordion headers — collapsible sections */
.accordion-button {
    cursor: pointer;
}

/* Custom select opener */
.custom-select-opener {
    cursor: pointer;
}

/* Map markers — clickable for popups */
[data-marker-type] {
    cursor: pointer;
}

/* ── Map fullscreen — remove bottom gap (TASK-397) ── */
.section-map .map-wrapper {
    padding-bottom: 0;
}
body:has(.section-map.map-shown) .btn-to-top,
body:has(.section-map.map-full) .btn-to-top {
    display: none !important;
}

/* Hide footer when map is open so the map can fill the full viewport height */
body:has(.section-map.map-shown) .footer,
body:has(.section-map.map-full) .footer {
    display: none !important;
}

/* Extend map and column heights when footer is hidden.
   Formula: 100vh - header(66) - margin-top(.content) - padding-bottom(.content)
   column--main and column--map share the same formula. */
.section-map.map-shown .content {
    margin-bottom: 0;
}
.section-map.map-shown .column--main {
    min-height: calc(100vh - 111px); /* 66 + margin=5 + pb=40 */
}
.section-map.map-shown .column--map {
    height: calc(100vh - 111px); /* 66 + margin=5 + pb=40 */
}
@media (min-width: 640px) {
    .section-map.map-shown .column--main {
        min-height: calc(100vh - 111px);
    }
    .section-map.map-shown .column--map {
        height: calc(100vh - 111px);
    }
}
@media (min-width: 1440px) {
    .section-map.map-shown .column--main {
        min-height: calc(100vh - 116px); /* 66 + margin=10 + pb=40 */
    }
    .section-map.map-shown .column--map {
        height: calc(100vh - 116px); /* 66 + margin=10 + pb=40 */
    }
}
@media (min-width: 1800px) {
    .section-map.map-shown .column--main {
        min-height: calc(100vh - 101px); /* 66 + margin=10 + pb=25 */
    }
    .section-map.map-shown .column--map {
        height: calc(100vh - 101px); /* 66 + margin=10 + pb=25 */
    }
}
.section-map.map-full .column--map {
    z-index: 1050;
}
.section-map.map-full .map-wrapper {
    position: fixed;
    inset: 0;
    z-index: 1050;
    border-radius: 0;
    padding: 0;
}
.section-map.map-full .map-wrapper .map {
    border-radius: 0;
    height: 100vh;
}

/* ── Modal backdrop — darken overlay for better focus (TASK-384) ── */
.modal-backdrop {
    --bs-backdrop-opacity: 0.7;
}

/* ── Location card image placeholder — shown when no image is available (TASK-411) ── */
.card-img-top--no-image {
    background-color: var(--bs-gray-200, #e4eaee);
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 400 / 265;
}

.card-img-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ss-text-muted);
    opacity: 0.6;
}

/* -------------------------------------------------------------------------
 * Invite autocomplete dropdown — shared by wizard step 3 and edit invite
 * ------------------------------------------------------------------------- */
.invite-autocomplete {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 1050;
  max-height: 240px;
  overflow-y: auto;
  background: var(--ss-surface);
  border: 1px solid var(--ss-border);
  border-top: 0;
  border-radius: 0 0 0.375rem 0.375rem;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

/* WTWY-630 / TASK-27BEK: focusing the search field reveals the dropdown
   via x-show before HTMX has fetched any results, so the empty container
   would otherwise render its borders/background/shadow as a thin strip
   directly under the input. Suppress the box when it has no children;
   once HTMX injects results (or the "no results" / "loading" rows), the
   selector no longer matches and the styled dropdown returns. */
.invite-autocomplete:empty {
  display: none;
}

.invite-autocomplete-item {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  cursor: pointer;
  transition: background-color 0.15s;
}

.invite-autocomplete-item:hover,
.invite-autocomplete-item.active {
  background: var(--bs-body-bg);
}

.invite-autocomplete-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

.invite-autocomplete-name {
  font-weight: 500;
  font-size: 14px;
}

.invite-autocomplete-loading,
.invite-autocomplete-empty {
  padding: 12px;
  text-align: center;
  color: var(--bs-secondary-color);
  font-size: 14px;
}

/* -------------------------------------------------------------------------
 * Selected-POI cards — shared by wizard step 6 and edit Infrastructure tab
 * (section-infrastructure snippet). Moved out of activity-wizard.css so the
 * edit page picks them up; CSS visibility was the only reason the trash
 * icon and "+" tile rendered correctly in the wizard but broken in edit.
 * ------------------------------------------------------------------------- */
.poi-add-btn {
  width: 80px;
  height: 80px;
  border-style: dashed;
  font-size: 1.5rem;
}

.poi-remove-btn {
  top: 2px;
  right: 2px;
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

/* -------------------------------------------------------------------------
 * Shared activity-form rules — used by activity-form/section-* snippets
 * which both the wizard (instances_activity_create) and the edit page
 * (instances_activity_edit) include. Moved out of activity-wizard.css so
 * the edit page picks them up.
 * ------------------------------------------------------------------------- */

/* Multiselect / custom dropdown validation error state.
   Bootstrap's built-in .is-invalid styles only target .form-control and
   .form-select; the custom .multiselect dropdown needs explicit styling. */
.multiselect.is-invalid .btn,
.multiselect.is-invalid .form-select,
.dropdown.multiselect.is-invalid .btn,
.dropdown.multiselect.is-invalid .form-select {
  border-color: var(--bs-danger);
}

/* Show the sibling .invalid-feedback when the multiselect has .is-invalid */
.multiselect.is-invalid ~ .invalid-feedback,
.dropdown.multiselect.is-invalid ~ .invalid-feedback {
  display: flex;
}

/* Number stepper: hide browser spinners on number inputs and constrain width */
.number-stepper input[type="number"]::-webkit-inner-spin-button,
.number-stepper input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.number-stepper input[type="number"] {
  -moz-appearance: textfield;
  max-width: 80px;
}

/* Invite chips: removable pills for invited users/emails */
.invite-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 20px;
  font-size: 13px;
  font-weight: 500;
  background: var(--bs-primary);
  color: var(--ss-surface);
  line-height: 1.4;
}

.invite-chip-avatar {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  object-fit: cover;
}

.invite-chip-email {
  background: var(--bs-body-bg);
  color: var(--bs-body-color);
}

.invite-chip-email .invite-chip-remove {
  color: var(--bs-body-color);
}

.invite-chip-remove {
  background: none;
  border: none;
  color: var(--ss-surface);
  font-size: 16px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
  opacity: 0.8;
}

.invite-chip-remove:hover {
  opacity: 1;
}

/* Declined invitation chips: visually distinct from sent/accepted so the
   organiser can see who turned the invitation down (TASK-G95Y1). */
.invite-chip--declined {
  text-decoration: line-through;
  opacity: 0.6;
}

.btn-delete {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.hr-dashed {
  border: 0;
  border-top: 1px dashed var(--bs-border-color);
  opacity: 1;
  margin: 0 0 24px;
}

/* Remove top offset from list-checked bullet icon */
.list-checked li:before {
  top: unset;
}

/* Match paragraph bottom margin for layout list blocks */
.list-checked {
  margin-bottom: 1rem;
}

/* White background style for accordion layout blocks */
.accordion--white .accordion-item,
.accordion--white .accordion-button,
.accordion--white .accordion-button:not(.collapsed),
.accordion--white .accordion-body {
  background-color: #fff !important;
}

/* Keep scroll-to-top button above the footer when footer is visible */
.btn-to-top {
  transition: bottom 0.3s ease;
}

/* Hide scroll-to-top on non-scrollable pages (login, registration, password) */
@media (min-width: 1800px) {
  .page-wrapper--login .d-xxl-block.btn-to-top {
    display: none !important;
  }
}

.btn-to-top.btn-to-top--above-footer {
  bottom: 87px;
}

.page-wrapper--content h2, .page-wrapper--content .h2 { font-size: 1.5rem; }
.page-wrapper--content h3, .page-wrapper--content .h3 { font-size: 1.375rem; }
.page-wrapper--content h4, .page-wrapper--content .h4 { font-size: 1.25rem; }
.page-wrapper--content h5, .page-wrapper--content .h5 { font-size: 1.125rem; }

.page-wrapper--content h2 ol,
.page-wrapper--content h2 ul {
  padding-left: 0;
  margin-bottom: 0;
  list-style-position: inside;
}

/* Dashboard activity/location grids (row--cols-4) enforce a consistent
   column count per breakpoint, overriding the per-card col-* ladder whose
   trailing `col-xxl` (flex:1 0 0%) causes uneven wrap counts (e.g. 3-3-2
   instead of 4-4) between 1200px and 1800px. Map-shown variants shrink the
   grid so cards stay legible alongside the map pane. */
@media (min-width: 1200px) {
  .listing .row--cols-4 > div {
    flex: 0 0 auto;
    width: 25%;
  }
}
@media (min-width: 1440px) {
  .section-map.map-shown .listing .row--cols-4 > div {
    width: 50%;
  }
}
@media (min-width: 1800px) {
  .section-map.map-shown .listing .row--cols-4 > div {
    width: 33.333%;
  }
}

/* ==========================================================================
   POI MODAL — canonical detail modal (TASK-550)
   ========================================================================== */

/* Desktop max-width cap. On mobile (< sm) Bootstrap's modal-fullscreen-sm-down
   takes over and overrides this to 100vw, so no media query guard is needed. */
.poi-modal {
    max-width: 460px;
}


/* ==========================================================================
   FULL-HEIGHT CONTENT AREA
   ========================================================================== */

/* Ensure the white .content background fills the remaining viewport on pages
   with little content, without causing scrolling.
   header=66px throughout. Footer and content margins change per breakpoint.

   .section-map targets .column--main (not .content) because at <1440px
   .section-map .content has flex-direction:column-reverse, which pushes the
   column to the bottom of a min-height-stretched container.

   .section-item-page targets .content directly (no column-reverse issue).
   Formula: 100vh - header(66) - footer - content margin(T+B).
   Padding-bottom is inside the box (box-sizing:border-box) and not subtracted. */

/* --- .section-map (wishlist, activities, locations, dashboard) --- */
/* column--main min-height includes pb so the white box always fills the viewport.
   column--map height matches the full .content box height (pb excluded from formula
   because the map should reach the bottom edge of .content, not stop at the inner
   flex area boundary). Formula: 100vh - header(66) - footer - margin(T+B). */
.section-map .column--main {
    min-height: calc(100vh - 222px); /* footer=106, margin=5+5, pb=40 */
}
/* .column--map sticky offset (header = 66px). Height is owned by styles.css
   at <1440px (0 by default, 60vh/55vh/etc. when .map-shown) so it cannot leak
   vertical space into the column-reverse stack; at >=1440px the two columns
   sit side by side and we match the main column's height below. */
.section-map .column--map {
    top: 66px;
}
@media (min-width: 640px) {
    .section-map .column--main {
        min-height: calc(100vh - 187px); /* footer=71, margin=5+5, pb=40 */
    }
}
@media (min-width: 1440px) {
    .section-map .column--main {
        min-height: calc(100vh - 197px); /* footer=71, margin=10+10, pb=40 */
    }
    .section-map .column--map {
        height: calc(100vh - 157px); /* footer=71, margin=10+10 */
    }
}
@media (min-width: 1800px) {
    .section-map .column--main {
        min-height: calc(100vh - 190px); /* footer=71, margin=10+18, pb=25 */
    }
    .section-map .column--map {
        height: calc(100vh - 190px); /* footer=71, margin=10+18, pb=25 — matches column--main */
    }
}

/* --- .section-item-page (logged-in content pages: about, privacy, etc.) --- */
.section-item-page .content {
    min-height: calc(100vh - 182px); /* footer=106, margin=5+5 */
}
@media (min-width: 640px) {
    .section-item-page .content {
        min-height: calc(100vh - 147px); /* footer=71, margin=5+5 */
    }
}
@media (min-width: 1440px) {
    .section-item-page .content {
        min-height: calc(100vh - 157px); /* footer=71, margin=10+10 */
    }
}
@media (min-width: 1800px) {
    .section-item-page .content {
        min-height: calc(100vh - 165px); /* footer=71, margin=10+18 */
    }
}

/* --- nav-tabs--type2: primary color active + hover (with and without .tabs wrapper) --- */
.tabs .nav-tabs .nav-link,
.nav-tabs--type2 .nav-link {
    border-bottom: 2px solid rgba(0, 0, 0, 0);
    margin: 0 0 -2px;
}
.tabs .nav-tabs .nav-link.active,
.nav-tabs--type2 .nav-link.active {
    border-bottom-color: var(--bs-primary);
    color: var(--bs-primary);
}
.tabs .nav-tabs .nav-link:not(.active):hover,
.tabs .nav-tabs .nav-link:not(.active):focus,
.nav-tabs--type2 .nav-link:not(.active):hover,
.nav-tabs--type2 .nav-link:not(.active):focus {
    color: var(--bs-primary);
    border-color: transparent;
    border-bottom-color: var(--bs-primary);
}



/* --- textarea: increase vertical padding --- */
textarea {
    padding-top: 10px !important;
    padding-bottom: 10px !important;
}

/* --- Tom Select: padding for single/multi controls with items --- */
.ts-wrapper.multi.has-items .ts-control,
.ts-wrapper.single.has-items .ts-control {
    padding: .5rem .8rem;
}

/* --- Tom Select: item margin and padding within control --- */
.ts-wrapper.multi .ts-control > .item,
.ts-wrapper.single .ts-control > .item {
    margin: 0;
    padding-top: 0;
    padding-bottom: 0;
}

/* --- Header search: input fills available space in flex container --- */
.fields #searchInput {
    flex: 1;
    min-width: 0;
}

/*
 * Alpine passwordToggle: style the reveal button without the legacy
 * `.field-password` hook (intentionally dropped to keep legacy main.js
 * handlers from binding alongside Alpine — see frontend.md Rule 8).
 * Mirrors the legacy `.field-password .btn-password` rule in styles.css.
 */
[x-data="passwordToggle"] .btn-password {
    background: url(../images/eye.svg) 50% no-repeat;
    border: none;
    padding: 0;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    width: 18px;
    height: 18px;
    position: absolute;
    right: 10px;
    top: auto;
    bottom: 12px;
}

/* TASK-ZEVVC — round corners on MapLibre popup so it matches the rest of
   the card aesthetic. overflow:hidden clips a leading image to the radius.
   The popup tip (.maplibregl-popup-tip) lives in a separate element and
   stays unrounded. The descendant selector beats the maplibre-gl.css
   single-class rule that ships with border-radius:3px. */
.maplibregl-popup .maplibregl-popup-content {
    border-radius: 12px;
    overflow: hidden;
}

/* TASK-EZ70P — global "button is busy" affordance.
   Triggered by `.is-loading` (toggled by btn-loading.js for form submits and
   for the htmx min-duration window) or by HTMX's own `.htmx-request` class
   (added automatically to the triggering element while a request is in
   flight). While in this state the original label is hidden (color:transparent
   keeps the button's bounding box stable so there is no layout shift), a
   Bootstrap-style spinner is centered on top using the button's own
   --bs-btn-color (no hardcoded colors), and the button becomes non-
   interactive. Scoped to .btn-primary so .btn-back / .btn-secondary remain
   unaffected (per task scope + frontend.md rule 2). */
.btn-primary.is-loading,
.btn-primary.htmx-request {
    position: relative;
    pointer-events: none;
    color: transparent !important;
    text-shadow: none;
}
.btn-primary.is-loading::after,
.btn-primary.htmx-request::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1rem;
    height: 1rem;
    margin: -0.5rem 0 0 -0.5rem;
    border: 0.15em solid var(--bs-btn-color);
    border-right-color: transparent;
    border-radius: 50%;
    animation: spinner-border 0.75s linear infinite;
}
