﻿/* =========================================
   LOVEOURCLUB - BOOTSTRAP 5 CLEANED VERSION
   ========================================= */

/* -----------------------------------------
   FONTS - all @font-face declarations, Inter included, live in Content/loc-fonts.css, which every
   bundle carrying this file also carries. Inter used to be declared here as a single unsubset 730KB
   pair; it is now split by unicode-range alongside the rest of the library, so an English page
   fetches 71KB instead of 352KB. See loc-fonts.css and fonts/README-WebFonts.md.
   ----------------------------------------- */

/* A little typographic personality for website headings: slightly heavier + tighter tracking than the
   Bootstrap default, so titles read as "designed" rather than templated. Scoped to .website content. */
.website h1, .website h2, .website h3,
.website h4, .website h5, .website h6 {
    font-weight: 600;
    letter-spacing: -0.01em;
}

/* -----------------------------------------
   MARKETING (SaaS) PAGES - the public Home controller pages (_Layout). One fluid page title that scales
   with the viewport (replaces the old server-side mobile/desktop h2/h3 branch), and a centered, readable
   content column (replaces the per-row "margin: 2% 2%" inline styles).
   ----------------------------------------- */
.loc-page-title {
    font-size: clamp(1.6rem, 1rem + 2.4vw, 2.5rem);
    font-weight: 600;
    line-height: 1.2;
    margin-bottom: 0.5rem;
}

.loc-page-subtitle {
    font-size: clamp(1rem, 0.9rem + 0.6vw, 1.25rem);
    font-weight: 400;
    color: var(--bs-secondary-color, #6c757d);
    margin-bottom: 0;
}

/* Match the Bootstrap .container widths so marketing sections line up with the home page. */
.loc-marketing-section {
    width: 100%;
    margin: 1.5rem auto 0;
    padding: 0 var(--bs-gutter-x, 1.5rem);
}

@media (min-width: 576px) {
    .loc-marketing-section {
        max-width: 540px;
    }
}

@media (min-width: 768px) {
    .loc-marketing-section {
        max-width: 720px;
    }
}

@media (min-width: 992px) {
    .loc-marketing-section {
        max-width: 960px;
    }
}

@media (min-width: 1200px) {
    .loc-marketing-section {
        max-width: 1140px;
    }
}

@media (min-width: 1400px) {
    .loc-marketing-section {
        max-width: 1320px;
    }
}

/* -----------------------------------------
   NAVBAR (mobile scroll fix)
   ----------------------------------------- */
@media (max-width: 1199px) {
    .navbar {
        overflow-y: auto;
        max-height: 85vh;
        align-items: flex-start;
    }

    #navbar-title {
        display: block;
    }
}

@media (min-width: 1200px) {
    #navbar-title {
        display: none;
    }
}

/* -----------------------------------------
   NAVBAR DENSITY
   Bootstrap 5's default nav-link padding is taller/wider than the previous theme, which
   made the top menu wrap to extra rows. Because the content's top offset is set in JS from
   the navbar height ($("#navbar").outerHeight()), a taller navbar pushed content + footer
   past 100vh and produced page scrollbars. Tighten the spacing back down.
   ----------------------------------------- */
/* Doubled/descendant selectors raise specificity so these win over loc-theme-modern.css,
   which is linked after this file. Horizontal padding is left at the BS5 default (0 inside
   the navbar) — adding it would widen items and cause more wrapping. The vertical padding is
   the club theme's navbar height token (--loc-navbar-pad-y), falling back to the old compact
   value when no theme is loaded. */
.navbar.navbar {
    padding-top: var(--loc-navbar-pad-y, 0.25rem);
    padding-bottom: var(--loc-navbar-pad-y, 0.25rem);
}

.navbar .navbar-nav .nav-link {
    padding-top: 0.3rem;
    padding-bottom: 0.3rem;
}

/* -----------------------------------------
   NAVBAR (theme-driven style)
   The website navbar's appearance is a Club Theme token set, emitted as --loc-navbar-*
   variables by /ClubTheme.css (see ThemeController) and consumed here. The layouts add the
   .loc-navbar class and no longer bake in navbar-light/dark or bg-* utilities, so background,
   text/hover/active colour, height, logo size, border, shadow and link typography all come
   from the selected theme. Bootstrap's own --bs-navbar-* vars are set from ours so the brand,
   links and dropdown toggles pick up the colours without needing the light/dark classes.
   ----------------------------------------- */
/* .navbar.loc-navbar, NOT .loc-navbar. Bootstrap's own ".navbar" sets --bs-navbar-color and friends to a
   dark emphasis colour, at exactly the same specificity as ours - so which one wins is decided purely by
   which stylesheet is linked last, and the two shells disagree about that. The anonymous layout links
   Bootstrap first and this file after it; the member layout links this file first and Bootstrap after it.
   Same club, same theme, and the bar came out white when signed out and near-black when signed in.
   The compound selector wins in both orders, which is the fix that does not depend on link order being
   tidied up in two layouts at once. */
.navbar.loc-navbar {
    --bs-navbar-color: var(--loc-navbar-color, #fff);
    --bs-navbar-hover-color: var(--loc-navbar-hover-color, rgba(255, 255, 255, 0.8));
    --bs-navbar-active-color: var(--loc-navbar-active-color, #fff);
    --bs-navbar-brand-color: var(--loc-navbar-color, #fff);
    --bs-navbar-brand-hover-color: var(--loc-navbar-hover-color, rgba(255, 255, 255, 0.8));
    --bs-navbar-toggler-border-color: transparent;
    background: var(--loc-navbar-bg, var(--bs-primary, #2e7d32));
    border-bottom: var(--loc-navbar-border-bottom, none);
    box-shadow: var(--loc-navbar-shadow, none);
    -webkit-backdrop-filter: var(--loc-navbar-backdrop, none);
    backdrop-filter: var(--loc-navbar-backdrop, none);
}

.loc-navbar .navbar-nav .nav-link {
    font-weight: var(--loc-navbar-link-weight, 400);
    text-transform: var(--loc-navbar-link-transform, none);
    letter-spacing: var(--loc-navbar-link-spacing, normal);
    /* Nav links inherit the theme's --bs-body-line-height; a Relaxed/Spacious body setting makes them
       look oddly tall stacked in the bar. Pin a compact navbar line-height so the bar stays tidy
       regardless of the body text setting. */
    line-height: 1.2;
}

.loc-navbar .loc-navbar-logo {
    height: var(--loc-navbar-logo-h, 40px);
    width: auto;
    max-width: var(--loc-navbar-logo-maxw, 200px);
    object-fit: contain;
    float: left;
    position: relative;
}

.loc-navbar #navbar-title {
    color: var(--loc-navbar-color, #fff);
}

/* Recolour the hamburger to match the navbar text colour by masking the icon instead of relying
   on Bootstrap's fixed light/dark background-image. */
.loc-navbar .navbar-toggler-icon {
    background-image: none;
    background-color: var(--loc-navbar-color, #fff);
    -webkit-mask: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23000' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") center / 100% 100% no-repeat;
    mask: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23000' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") center / 100% 100% no-repeat;
}

/* When the bar collapses (navbar-expand-md → below 768px) it expands into a tall vertical menu.
   A transparent theme is unreadable there, so fall back to the solid mobile background + a dark
   text colour (both resolved server-side into the -mobile variables). */
@media (max-width: 767.98px) {
    /* Compound selector for the same reason as the block above - this one redefines the same variables. */
    .navbar.loc-navbar {
        background: var(--loc-navbar-bg-mobile, #fff);
        --bs-navbar-color: var(--loc-navbar-color-mobile, var(--loc-navbar-color, #fff));
        --bs-navbar-brand-color: var(--loc-navbar-color-mobile, var(--loc-navbar-color, #fff));
    }

    .loc-navbar .navbar-toggler-icon {
        background-color: var(--loc-navbar-color-mobile, var(--loc-navbar-color, #fff));
    }
}

/* -----------------------------------------
   THE MENU BUTTON (Theme.NavbarMenuMode)

   A bar with more groups than fit used to collapse ENTIRELY to a hamburger, so a club with nine menu
   groups showed none of them. With a menu button the club keeps its important links on the bar and puts
   the rest behind the button. Colours come from the same --loc-navbar-* tokens as the links, so a themed
   bar does not sprout an unstyled grey control.

   VISIBILITY IS SCOPED TO .navbar-expand-md AND NOT TO A WIDTH ALONE. Below the breakpoint the hamburger
   is the menu and holds everything, exactly as before - but NavbarAutoCollapse also REMOVES that class at
   any width when the bar overflows anyway. Keying off the class means one rule covers both: no menu
   button, and the menu's own groups revealed inside the hamburger, whenever the bar is not horizontal.
   ----------------------------------------- */
.loc-navmenu-trigger {
    display: none;
}

.loc-navbar .loc-navmenu-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--loc-navbar-color, #fff);
    border: 1px solid var(--loc-navbar-hover-color, rgba(255, 255, 255, 0.55));
    font-weight: var(--loc-navbar-link-weight, 400);
    text-transform: var(--loc-navbar-link-transform, none);
    letter-spacing: var(--loc-navbar-link-spacing, normal);
    white-space: nowrap;
}

.loc-navbar .loc-navmenu-btn:hover,
.loc-navbar .loc-navmenu-btn:focus {
    color: var(--loc-navbar-hover-color, rgba(255, 255, 255, 0.8));
}

/* Marked when the page being shown is one of the pages inside the panel. The panel is shut, so without
   this the bar would say the visitor is nowhere at all - see MarkActiveNav. */
.loc-navbar .loc-navmenu-btn.active {
    color: var(--loc-navbar-active-color, #fff);
    border-color: var(--loc-navbar-active-color, #fff);
}

/* The hamburger glyph beside the label, at the size of a word rather than of a tap target. */
.loc-navbar .loc-navmenu-icon {
    width: 1em;
    height: 1em;
}

/* The groups the club put behind the menu button are ALSO rendered in the collapsing list, so that below
   the breakpoint - where there is no menu button - the hamburger still holds the whole menu. They are
   hidden only while the bar is genuinely laid out horizontally. */
@media (min-width: 768px) {
    .navbar-expand-md .loc-nav-menuonly {
        display: none;
    }

    .navbar-expand-md .loc-navmenu-trigger {
        display: inline-flex;
    }
}

/* The panel, in either shape. A group's caption becomes a heading and its pages sit under it: inside an
   open panel there is nothing left to open, so a second level of dropdown would be a click for nothing. */
/* THE PANEL THAT HANGS BELOW THE BAR. One column, because its groups collapse: a stack of shut group
   headers is short by definition, and it is what makes the whole shape of the menu visible at once.
   Multiple columns were tried first and are the wrong answer once the groups fold - they spread four
   short headers across the width of the screen for no reason. */
.loc-navmenu-panel {
    min-width: 18rem;
    max-width: min(24rem, calc(100vw - 2rem));
    max-height: min(70vh, 34rem);
    overflow-y: auto;
    padding: 0.5rem 0;
}

/* ONE GROUP, VISIBLY. The panel holds several menu groups, and without a boundary between them they read
   as a single long list with the occasional label in it - the club's structure is in the markup but not on
   the screen. The rule between sections is what carries that structure; the heading can then stay quiet. */
.loc-navmenu-section + .loc-navmenu-section {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid rgba(0, 0, 0, 0.1);
}

.loc-navmenu-group,
.loc-navmenu-heading {
    margin: 0 0 0.35rem;
    padding: 0 1rem;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    opacity: 0.55;
}

/* THE GROUP AS A CONTROL. In the drawer the caption opens and closes its own group, so it stops being a
   label and becomes a row you press: full width, a caret that says which way it goes, and enough height
   to be a tap target. It is a <button> so it is reachable by keyboard and announced as one - a div with a
   click handler would be neither. */
.loc-navmenu-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    width: 100%;
    margin: 0;
    padding: 0.5rem 1rem;
    background: none;
    border: 0;
    text-align: left;
    cursor: pointer;
    opacity: 0.75;
}

.loc-navmenu-toggle:hover,
.loc-navmenu-toggle:focus-visible {
    background: rgba(0, 0, 0, 0.05);
    opacity: 1;
}

/* A chevron drawn from two borders rather than an icon font, so it inherits the text colour and needs no
   asset. Points down when the group is open, right when it is shut. */
.loc-navmenu-caret {
    flex: none;
    width: 0.45rem;
    height: 0.45rem;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg);
    transform-origin: center;
    transition: transform 0.15s ease;
}

.loc-navmenu-toggle.collapsed .loc-navmenu-caret {
    transform: rotate(-45deg);
}

@media (prefers-reduced-motion: reduce) {
    .loc-navmenu-caret {
        transition: none;
    }
}

/* Links belonging to a heading are stepped in under it, so the eye can see which pages the heading owns.
   A single-item group has no heading to sit under and keeps the flush edge - it is its own section. */
.loc-navmenu-section:not(.loc-navmenu-solo) .loc-navmenu-items .loc-navmenu-link {
    padding-left: 1.75rem;
}

/* Stepped in once more again under a section heading, which is itself already inside the group. Declared
   after the rule above on purpose: both selectors match a link that follows both, and the later one wins. */
.loc-navmenu-items .loc-navmenu-heading ~ .loc-navmenu-link {
    padding-left: 2.5rem;
}

/* A section heading inside a group sits under that group's own caption, so it is stepped in one level. */
.loc-navmenu-group ~ .loc-navmenu-heading {
    margin-top: 0.6rem;
    padding-left: 1.75rem;
    font-weight: 600;
    text-transform: none;
    letter-spacing: 0.02em;
    font-size: 0.8rem;
    opacity: 0.75;
}

.loc-navmenu-link {
    display: block;
    padding: 0.4rem 1rem;
    color: inherit;
    text-decoration: none;
}

.loc-navmenu-link:hover,
.loc-navmenu-link:focus,
.loc-navmenu-link.active {
    background: rgba(0, 0, 0, 0.06);
    color: inherit;
}

.loc-navmenu-link.active {
    font-weight: 600;
}

.loc-navmenu-drawer {
    width: min(22rem, 85vw);
}

.loc-navmenu-drawer .offcanvas-body {
    padding-left: 0;
    padding-right: 0;
}

/* BUTTONS ON THE BAR (NavPlacement.Button) - Join, Renew, Shop. Drawn from the navbar's own text colour
   rather than a Bootstrap contextual class, so they read as part of the bar whatever the club's theme is. */
.loc-navbar .loc-navbar-btn {
    background: var(--loc-navbar-color, #fff);
    color: var(--loc-navbar-bg, var(--bs-primary, #2e7d32));
    border: 1px solid var(--loc-navbar-color, #fff);
    font-weight: 600;
    white-space: nowrap;
    margin-left: 0.5rem;
}

.loc-navbar .loc-navbar-btn.loc-navbar-btn-outline {
    background: transparent;
    color: var(--loc-navbar-color, #fff);
}

.loc-navbar .loc-navbar-btn:hover,
.loc-navbar .loc-navbar-btn:focus {
    background: var(--loc-navbar-hover-color, rgba(255, 255, 255, 0.85));
    color: var(--loc-navbar-bg, var(--bs-primary, #2e7d32));
}

/* -----------------------------------------
   PAGE FRAME (theme-driven spacing)
   The website content area owns its own frame: outer padding + the gap between rows, both site-wide from
   the club theme (emitted as CSS variables by _PageSpacingVars). Rows stack in a flex column so --row-gap
   spaces them evenly (top row sits below the top padding, bottom row above the bottom padding). Values are
   rem; on phones they auto-scale down. Fallbacks (1rem) keep it sane if the theme vars are ever missing.
   ----------------------------------------- */
.website {
    /* The EFFECTIVE frame values. Rules that cancel the frame (a full-bleed banner or a band pulling past
       the row gap, the divider's space-after) must subtract what is actually applied, not the raw theme
       var - otherwise they over-pull wherever the frame is scaled (see the phone media query below).
       Always cancel with these, never with --row-gap / --page-pad-* directly. */
    --loc-row-gap-eff: var(--row-gap, 1rem);
    --loc-page-pad-top-eff: var(--page-pad-top, 1rem);
    --loc-page-pad-bottom-eff: var(--page-pad-bottom, 1rem);
    --loc-page-pad-side-eff: var(--page-pad-side, 1rem);

    display: flex;
    flex-direction: column;
    gap: var(--loc-row-gap-eff);
    /* The site always sits inside a .container-fluid, which adds Bootstrap's default gutter on each side.
       Negate it with the SAME inherited --bs-gutter-x so it cancels exactly (any gutter value/breakpoint),
       making the Club Theme's page padding the sole horizontal frame - page spacing 0 = truly edge-to-edge. */
    margin-left: calc(-0.5 * var(--bs-gutter-x, 1.5rem));
    margin-right: calc(-0.5 * var(--bs-gutter-x, 1.5rem));
    padding: var(--loc-page-pad-top-eff) var(--loc-page-pad-side-eff) var(--loc-page-pad-bottom-eff);
}

/* Phones scale the whole frame down. Only the *-eff vars change - gap/padding above read them, and so does
   every cancel, so the two stay in step at any breakpoint. */
@media (max-width: 767.98px) {
    .website {
        --loc-row-gap-eff: calc(var(--row-gap, 1rem) * 0.6);
        --loc-page-pad-top-eff: calc(var(--page-pad-top, 1rem) * 0.6);
        --loc-page-pad-bottom-eff: calc(var(--page-pad-bottom, 1rem) * 0.6);
        --loc-page-pad-side-eff: calc(var(--page-pad-side, 1rem) * 0.6);
    }
}

/* -----------------------------------------
   GLOBAL UTILITIES
   ----------------------------------------- */

.content-image {
    margin-inline: 5px;
}

.show-white-space {
    white-space: pre-wrap;
}

.cursor-wait {
    cursor: wait;
}

/* -----------------------------------------
   FORMS (SAFE MODERN APPROACH)
   ----------------------------------------- */

/* Replace global override with opt-in */
.form-control-fixed {
    max-width: 300px;
    min-width: 200px;
}

.form-control:disabled {
    background-color: var(--bs-secondary-bg);
}

/* Smaller variants */
/* For 1-2 digit numeric inputs (day, month) sitting inline with a wider field. */
.form-control-xxsmall {
    max-width: 80px;
    min-width: 50px;
}

.form-control-xsmall {
    max-width: 200px;
    min-width: 50px;
}

.form-control-small {
    max-width: 300px;
    min-width: 200px;
}

.form-control-large {
    max-width: 300px;
    min-width: 300px;
}

.form-control-xlarge {
    max-width: 100%;
    min-width: 200px;
}

.form-control-date {
    width: 270px;
    min-width: 270px;
}

/* Better focus handling */
.form-control:focus {
    box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.25);
}

/* Field labels sit on their own line ABOVE the control. A plain <label> is display:inline,
   so it only ends up "above" the field because a block .form-control drops onto the next
   line. Controls whose rendered markup is inline-block - notably Select2's
   .select2-container - would otherwise sit BESIDE the label. Making the label block keeps
   the label-on-top layout consistent no matter what control follows. */
.control-label {
    display: block;
}

/* Alphabet filter buttons (booking player selector): fix the width so every button is the
   same size regardless of the letter's glyph width ("I" vs "W"). */
.btn-letter {
    width: 30px;
    padding-left: 0;
    padding-right: 0;
    text-align: center;
}

/* Website builder: a row/column toggled to hidden (Active = false) stays visible in the editor
   so it can be toggled back, but is dimmed to signal it won't show to real viewers. */
.builder-row-hidden {
    opacity: 0.45;
}

/* Builder selection indicator: outlines the element the open toolbar is editing, colour-coded by level
   (row / column / widget) with a corner label, so it's obvious what's selected. Uses outline (no layout
   shift) and is added by website.js only in edit mode - never part of the saved markup. */
.builder-selected-row,
.builder-selected-column,
.builder-selected-widget {
    position: relative;
    outline-offset: -2px;
}

.builder-selected-row {
    outline: 2px dashed #0d6efd;
}

.builder-selected-column {
    outline: 2px dashed #20c997;
}

.builder-selected-widget {
    outline: 2px dashed #fd7e14;
}

.builder-selected-row::before,
.builder-selected-column::before {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 5;
    padding: 2px 6px;
    font-size: 0.7rem;
    line-height: 1;
    color: #fff;
    white-space: nowrap;
    border-bottom-right-radius: 4px;
    pointer-events: none;
}

.builder-selected-row::before {
    content: "Row";
    background: #0d6efd;
}

.builder-selected-column::before {
    content: "Column";
    background: #20c997;
}

/* A widget's label is always shown (.columncontent::before); selecting it just recolours that same label
   to orange - nothing moves, so selection reads clearly without the layout shifting. */
.builder-selected-widget::before {
    z-index: 6;
    background: #fd7e14;
    font-weight: 700;
}

/* Settings-toolbar heading naming what's being edited; colour matches the selection outline. */
/* Six tabs in a half-width dialog column. Tighter padding and a slightly smaller label keep them on one
   line; they still wrap rather than overflow if a club's browser is narrow enough. */
.loc-style-tabs .nav-link {
    padding: 0.35rem 0.6rem;
    font-size: 0.82rem;
}

/* FOLDED GROUPS inside the box editor.
   The panel holds five groups of controls and only the Style picker above them is needed most of the time.
   Folding the rest keeps the panel calm when it opens while leaving everything one click away - the previous
   flat list read as a wall and pushed people towards setting properties by hand instead of picking a style.

   A bare full-width button rather than a card header: these are subordinate to the panel's own header, and
   nesting card chrome inside card chrome makes the hierarchy harder to read, not easier. */
.loc-style-group {
    border-top: 1px solid var(--bs-border-color, #dee2e6);
    margin-top: 0.5rem;
}

.loc-style-group-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 0.5rem 0;
    border: 0;
    background: none;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--bs-body-color, #212529);
    text-align: left;
    cursor: pointer;
}

.loc-style-group-head::after {
    content: "9";
    display: inline-block;
    font-size: 1rem;
    line-height: 1;
    opacity: 0.55;
    transform: rotate(-90deg);
    transition: transform 0.15s ease;
}

/* Bootstrap puts .collapsed on the trigger while the target is CLOSED, so the open state is its absence. */
.loc-style-group-head.collapsed::after {
    transform: rotate(-90deg) translateX(2px);
}

.loc-style-group-head:not(.collapsed)::after {
    transform: rotate(-90deg) rotate(90deg);
}

.loc-style-group-head:hover {
    color: var(--bs-primary, #0d6efd);
}

.loc-style-group-body {
    padding-bottom: 0.5rem;
}

/* Breadcrumb inside the "Editing:" label - the ancestors of the selected element, as links, so a row or a
   column can be reached without knowing about the right-click menu. Inherits the label's colour so the
   segments read as part of it rather than as ordinary page links. */
.toolbar-crumb {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}

.toolbar-crumb:hover,
.toolbar-crumb:focus-visible {
    color: inherit;
    text-decoration-thickness: 2px;
}

.toolbar-crumb-sep {
    opacity: 0.6;
    padding: 0 0.15rem;
}

.toolbar-edit-label {
    margin: 0 0.75rem 0.5rem;
    padding: 4px 10px;
    font-weight: 600;
    color: #fff;
    border-radius: 4px;
}

.toolbar-edit-label-row {
    background: #0d6efd;
}

.toolbar-edit-label-column {
    background: #20c997;
}

.toolbar-edit-label-widget {
    background: #fd7e14;
}

/* THE ROW IS A CSS GRID
   A row lays its columns on twelve equal tracks and each column spans some of them (see ColumnSpans in
   ShowContent.cshtml). This replaced a flex row of percentage-width col-md-N columns, for one reason above
   all: grid sizes its tracks AFTER subtracting the gap, and a percentage cannot.

   That was the old bug. The builder always splits a row to total exactly 12/12, so the columns already
   filled the line and a gap had nowhere to come from - and flexbox wraps before it shrinks, so the last
   column dropped onto a line of its own. The fix was to make every column hand back its (n-1)/n share of
   the gap through --loc-col-gap-share, which worked only while every column sat on ONE line: the share is
   really per-line, so the moment a row wrapped into 2x2 the arithmetic was wrong. An fr track does the
   whole calculation itself, exactly, at any column count and any number of lines.

   .row.rowcontent, not .rowcontent: Bootstrap's own .row sets display:flex at the same specificity (0,1,0),
   and which of the two stylesheets loads last is not something this rule should depend on. The compound
   selector (0,2,0) wins either way.

   container-type makes the row a query container, so the reflow rules below measure THE ROW rather than the
   browser window - a row nested inside a narrow column now breaks when it actually runs out of room. Note
   that an element cannot query its own container, which is why the placement rules target the row's
   CHILDREN and the placement itself arrives as a variable rather than as grid-column directly (an inline
   property would beat them). */
.row.rowcontent {
    display: grid;
    /* minmax(0, ...) and not a bare 1fr: 1fr means minmax(auto, 1fr), which refuses to shrink below its
       content, so a single long word or an oversized image would widen its own track and push the row past
       the page. With a 0 minimum the track wins and the content scrolls or wraps inside it. */
    grid-template-columns: repeat(12, minmax(0, 1fr));
    container-type: inline-size;
    /* The effective column gap: the row's own setting if it has one, otherwise the theme's default
       (--column-gap), otherwise nothing. The row only publishes --loc-col-gap when it overrides, so this
       chain is what makes the row picker's "Default" mean the theme rather than zero. */
    --loc-col-gap-eff: var(--loc-col-gap, var(--column-gap, 0px));
    gap: var(--loc-col-gap-eff);
}

/* The column's own placement, computed server-side (ColumnPlacements) and handed over as a variable.
   :not(.loc-chrome-handle) skips the builder's row handle, which is appended to the row but absolutely
   positioned and has no business claiming a track. */
.row.rowcontent > *:not(.loc-chrome-handle) {
    grid-column: var(--loc-col-place, auto);
}

/* Default reflow: below a container width of 720px every column takes the whole row.
   720px is the width a Bootstrap container has at the md breakpoint, so a normal full-width row keeps
   stacking at about the window width it always did (this replaces the col-12 every column used to carry).
   It is measured on the ROW, though, so a row nested inside a narrow column stacks when that column is
   narrow rather than waiting for the phone - which is the behaviour the viewport breakpoint never had.

   Rows on the Automatic ladder override this from a per-row rule, which wins on id specificity. Rows that
   store their reflow per column (Steps / Custom) are excluded by selector instead - see .loc-reflow-bands
   below - so the two schemes can never both claim a width and leave the answer to source order. */
@container (max-width: 720px) {
    .row.rowcontent:not(.loc-reflow-bands) > *:not(.loc-chrome-handle) {
        grid-column: 1 / -1;
        order: var(--loc-col-order, 0);
    }
}

/* REFLOW STORED PER COLUMN (the Steps and Custom modes)
   A column carries a placement for each band as a variable and these two rules choose between them. No
   generated CSS at all: the thresholds are the same for every row using this scheme, so they can live in
   the stylesheet, and only the per-column widths differ - which is what the variables are for.

   Both fall back to --loc-col-place, so a column that has never been given a band width simply keeps its
   desktop placement rather than collapsing to a single track.

   The widths are container widths, not window widths, but they are named for the devices they correspond
   to on a normal full-width page: a boxed page inside a 1024px window gives its rows about 992px, and a
   phone about 400px. A row nested in a narrow column reaches them earlier, which is the point. */
@container (max-width: 991.98px) {
    .row.rowcontent.loc-reflow-bands > *:not(.loc-chrome-handle) {
        grid-column: var(--loc-col-place-md, var(--loc-col-place, auto));
    }
}

@container (max-width: 575.98px) {
    .row.rowcontent.loc-reflow-bands > *:not(.loc-chrome-handle) {
        grid-column: var(--loc-col-place-sm, var(--loc-col-place, auto));
        /* Stack order applies here and not above, because this is the band where such a row is stacked.
           A column moved to the top of the stack keeps its drawn position wherever the columns are still
           side by side, which is what makes it safe to set. */
        order: var(--loc-col-order, 0);
    }
}

/* WIDGET BOXES MATCH THEIR NEIGHBOURS
   A column already stretches to the row's height, but everything inside it is ordinary block flow at content
   height - so a widget carrying a border or a background hugged its own text, and two side by side in a row
   ended up different sizes with their borders not lining up. Carry the stretch down to the widget box.

   The widget box is .loc-widget on the published page and .columncontent in the builder, with the builder's
   .column-contents sitting between it and the column. Where a column holds more than one widget they share
   the height, which is what makes a stack of bordered widgets line up with a single tall one beside it. */
.rowcontent > *,
.rowcontent > * > .column-contents {
    display: flex;
    flex-direction: column;
    /* Space between widgets STACKED IN ONE COLUMN, from the club theme (--widget-gap, see WebsiteSpacing).
       The last spacing relationship the theme did not own: rows are spaced by --row-gap and columns by
       --column-gap, both as a flex gap on their container, but two widgets in one column had only their own
       margins between them - which is per-widget, so it drifted from page to page by construction.

       A gap on the CONTAINER rather than a margin on each widget, matching how row and column spacing
       already work, and so that no widget carries spacing a style would then have to reason about. Both
       selectors are listed because the stack lives at a different depth in the two contexts: the column
       itself when published, .column-contents in the builder. Only one of them holds widgets in any given
       context, so the other's gap has nothing to space and is harmless.

       0px and not 0: this feeds nothing that calcs today, but --row-gap had to learn that lesson already. */
    gap: var(--widget-gap, 0px);
}

.rowcontent > * > .loc-widget,
.rowcontent > * > .column-contents,
.rowcontent > * > .column-contents > .columncontent {
    flex: 1 1 auto;
}

/* EQUAL-HEIGHT CARDS
   The row's default vertical alignment is Stretch, so Bootstrap equalises the COLUMN boxes - but everything
   between a column and its card is ordinary block flow at content height, so the cards stayed ragged and
   their footers never lined up. Carry the stretch the rest of the way down to the card.

   The chain differs by context, so each link is matched by "an element that contains a card" rather than by
   name - which also keeps a second, non-card widget in the same column at its natural height:
     published: [column] > [placement] > .card
     builder:   [column] > .column-contents > .columncontent > .widget-wrapper > .card
   Matched on "> *" and not on .columncolumn: that class is only added on the builder branch, so scoping to
   it silently confined every rule below to the edit canvas - the published page kept ragged cards.
   Bootstrap already gives .card display:flex and .card-body flex:1, so once the card fills the column the
   footer is pinned to the bottom and the three line up. */
.rowcontent > *:has(.card),
.rowcontent > *:has(.card) > *:has(.card),
.rowcontent > *:has(.card) > *:has(.card) > *:has(.card),
.rowcontent > *:has(.card) > *:has(.card) > *:has(.card) > *:has(.card) {
    display: flex;
    flex-direction: column;
}

.rowcontent > *:has(.card) > *:has(.card),
.rowcontent > *:has(.card) > *:has(.card) > *:has(.card),
.rowcontent > *:has(.card) > *:has(.card) > *:has(.card) > *:has(.card),
.rowcontent > *:has(.card) .card {
    flex: 1 1 auto;
}

/* THE CANVAS PAINTS ITS OWN PAGE COLOUR.
   On a published page the club's body colour arrives on <body>, from --bs-body-bg on :root. The builder
   cannot have it there: it loads /ClubTheme.css with scoped=true, which hangs the club's variables on this
   element so they stop at its edge instead of re-theming the admin shell around it (see the link tag in
   WebsiteBuilder.cshtml). The variable therefore reaches the canvas but never reaches <body>, and without
   this the canvas would preview the club's page on the ADMIN's background.

   var() with no fallback on purpose: everywhere this class appears the variable is defined - by the scoped
   sheet on this element, or failing that inherited from :root - so there is no third answer to name. Any
   background-image the club set is inline on the same element and draws over this. */
.website-builder {
    background-color: var(--bs-body-bg);
}

/* Builder structure chrome: always-on subtle inset outlines so the row/column hierarchy is legible at a
   glance - blue = row, teal = column (matching the selection colours) - emphasised on hover, with a small
   corner label revealed on hover. Inset box-shadow (not outline) so it never fights the selection outline.
   Scoped to .website-builder so published pages are unaffected. */
.website-builder .rowcontent {
    position: relative;
    box-shadow: inset 0 0 0 1px rgba(13, 110, 253, 0.25);
}

.website-builder .rowcontent:hover {
    box-shadow: inset 0 0 0 1px rgba(13, 110, 253, 0.6);
}

.columncolumn {
    position: relative;
    min-height: 42px;
    box-shadow: inset 0 0 0 1px rgba(32, 201, 151, 0.35);
}

.columncolumn:hover {
    box-shadow: inset 0 0 0 1px rgba(32, 201, 151, 0.85);
}

/* Every widget carries a stable "Row X - Col Y - Type" label (set by website.js from the live DOM order),
   always visible so the layout reads at a glance with nothing moving on hover. Hover just bolds it;
   selection turns it orange (see .builder-selected-widget::before). The column keeps its outline (above)
   but no longer needs its own text label - which also removes the teal sliver behind the widget label. */
.columncontent {
    position: relative;
}

.columncontent:not(.parentPageColumn)::before {
    content: attr(data-widget-label);
    position: absolute;
    top: 0;
    left: 0;
    z-index: 4;
    padding: 2px 6px;
    font-size: 0.68rem;
    line-height: 1;
    color: #fff;
    white-space: nowrap;
    background: rgba(33, 37, 41, 0.6);
    border-bottom-right-radius: 4px;
    pointer-events: none;
}

.columncontent:not(.parentPageColumn):hover::before {
    font-weight: 700;
    background: rgba(33, 37, 41, 0.82);
}

/* ROW / COLUMN HANDLES (builder only)
   A small tab revealed on hover that selects the row or column it belongs to. Rows and columns were
   reachable only through a right-click menu that nothing on screen advertised; this puts a target where the
   pointer already is, and the sidebar breadcrumb (.toolbar-crumb) then lets you move back up.

   Hover-only, deliberately. A permanent column label used to live here and was taken out because it left a
   sliver of chrome behind every widget label - the row/column outlines already carry the structure at rest.

   Both sit at the TOP-RIGHT of their own box, stacked, because top-left is the widget label's corner. Inside
   the box rather than above it, so a row carrying a Surface preset (overflow:hidden) cannot clip them.
   z-index clears the widget label's 4. */
.website-builder .loc-chrome-handle {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 5;
    display: none;
    padding: 1px 7px;
    border: 0;
    border-bottom-left-radius: 4px;
    font-size: 0.68rem;
    line-height: 1.5;
    font-weight: 600;
    white-space: nowrap;
    color: #fff;
    cursor: pointer;
}

/* The column's sits below the row's, since the last column in a row shares its right edge. */
.website-builder .loc-chrome-column {
    top: 1.35rem;
}

/* Matching the outline colours already used for the two levels: blue = row, teal = column. */
.website-builder .loc-chrome-row {
    background: rgba(13, 110, 253, 0.85);
}

.website-builder .loc-chrome-column {
    background: rgba(32, 201, 151, 0.9);
}

.website-builder .rowcontent:hover > .loc-chrome-row,
.website-builder .columncolumn:hover > .loc-chrome-column,
.website-builder .loc-chrome-handle:focus-visible {
    display: block;
}

.website-builder .loc-chrome-handle:hover {
    filter: brightness(1.15);
}

/* Website builder toolbox: the widget-type tiles you drag onto the canvas. Uniform icon-over-label
   buttons in a two-column grid, with a hover lift to read as draggable. */
.toolbox-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
    padding: 8px;
}

.toolbox-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 12px 8px;
    text-align: center;
    font-size: 0.8rem;
    font-weight: 500;
    color: #333;
    background: #fff;
    border: 1px solid #dee2e6;
    border-radius: 8px;
    cursor: move;
    user-select: none;
    transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease, box-shadow 0.12s ease, transform 0.12s ease;
}

.toolbox-item i {
    font-size: 1.25rem;
    color: #0d6efd;
}

.toolbox-item:hover {
    color: #0d6efd;
    border-color: #0d6efd;
    background: #f0f6ff;
    box-shadow: 0 2px 6px rgba(13, 110, 253, 0.15);
    transform: translateY(-1px);
}

/* Column drop zones (Droppable.cshtml): inert and invisible at rest (z-index:-100, behind content). While
   a column or toolbox widget is being dragged (.dragging-column set by website.js), all four zones surface
   above the content as quiet target regions, and the zone under the cursor gets a bold, labelled highlight
   - so the drop target is obvious even when the column is full of content. */
.website-builder.dragging-column .droppable-wrapper > div {
    z-index: 60 !important;
    background: rgba(13, 110, 253, 0.06);
    border: 2px dashed rgba(13, 110, 253, 0.35);
    border-radius: 4px;
}

.website-builder.dragging-column .droppable-wrapper > div.ui-state-hover {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.75rem;
    font-weight: 600;
    color: #fff;
    text-align: center;
    background: rgba(13, 110, 253, 0.82);
    border-style: solid;
    border-color: #0d6efd;
    box-shadow: 0 0 0 2px #0d6efd;
}

.website-builder.dragging-column .droppable-top.ui-state-hover::after { content: "Row above"; }
.website-builder.dragging-column .droppable-bottom.ui-state-hover::after { content: "Row below"; }
.website-builder.dragging-column .droppable-left.ui-state-hover::after { content: "Column left"; }
.website-builder.dragging-column .droppable-right.ui-state-hover::after { content: "Column right"; }

/* Type picker popped from the "Add Widget to Column" context-menu action. */
.add-widget-menu {
    position: absolute;
    z-index: 1500;
    min-width: 150px;
    overflow: hidden;
    background: #fff;
    border: 1px solid #ccc;
    border-radius: 6px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

.add-widget-option {
    padding: 6px 12px;
    font-size: 0.9rem;
    cursor: pointer;
}

.add-widget-option:hover {
    background: #0d6efd;
    color: #fff;
}

/* Member summary widget: modern horizontal tiles. (Under BS5 the old .row layout stacked vertically
   because .row > * forces width:100% on non-column children; this uses a flex-wrap tile row instead.) */
.summary-dashboard {
    /* No padding of its own. The theme already pads every text-bearing widget, this one included (see the
       loc-widget-summary rule further down), so a second 1rem here inset the greeting past the title of
       whatever widget sat beside it - half of why a Member Summary never lined up with My Bookings.

       The query container for the tile grid below. On the dashboard and not on .summary-tiles itself,
       because an element cannot query its own container - the same constraint the page rows work around
       (see the row grid rules above). */
    container-type: inline-size;
}

/* WIDGET TITLES
   Every widget that shows a title of its own uses this, so two of them side by side line up without the
   club having to match them by hand.

   It used to be four widgets agreeing on <h5 class="mb-3"> by coincidence, the summary doing its own thing
   at h3, centred, with different margins, and the contact form borrowing .section-heading - the admin
   screens' convention, underline and all. A Member Summary next to My Bookings showed all three problems
   at once.

   The size is set here rather than left to whichever h-level the markup uses, so the heading LEVEL stays a
   semantic choice and the look does not depend on it. Flex with a gap because several of these titles carry
   an icon or an unread badge, which otherwise need their own inline margins and drift apart. */
.loc-widget-title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* Centred, so a row of widgets reads as a set of panels rather than a ragged column of left edges.
       Both properties are needed: justify-content centres the title's PARTS (the text, an icon, a badge)
       as a group, and text-align centres a long title's second line within its own part. */
    justify-content: center;
    text-align: center;
    gap: 0.4rem;
    margin: 0 0 1rem;
    /* Weight only. NO font-size, colour or family here on purpose.
       The element is an <h5>, so Bootstrap's type scale sizes it and the club theme's heading font and
       --bs-heading-color reach it already (see ThemeController: both are emitted scoped to .website and
       target h1-h6). Anything hardcoded here is a property that CANNOT match the headings a club types
       into its own text widget - and a `color: inherit` here would actively defeat the theme's heading
       colour, since a class outranks the h5 selector Bootstrap sets it on.

       Weight is the one exception: 600 is the house style for a heading used as a panel label, matching
       .section-heading. It is also the first thing to hand over when widget titles become a theme setting. */
    font-weight: 600;
}

/* WHAT A MEMBER WIDGET SHOWS A VISITOR
   Same shape as the empty state below and for the same reason - a member widget that draws nothing for a
   signed-out visitor leaves a blank column and unbalances the row it sits in. The difference is that this
   one has somewhere to send them, so it is a call to action rather than a shrug.

   min-height matches .loc-widget-empty, so a row of member widgets holds its shape for a visitor. */
.loc-widget-signin {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    min-height: 6rem;
    padding: 1rem;
    text-align: center;
}

.loc-widget-signin-text {
    margin: 0;
    color: var(--bs-secondary-color, #6c757d);
}

.loc-widget-signin-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
}

/* WIDGET EMPTY STATES
   What a widget draws when it has nothing to show: no unread news, no upcoming bookings, no events.

   Every one of these widgets ALWAYS draws, even when empty. Hiding one does not remove its column, so a
   three-column row would become two cards and a blank third - and a builder that hides what the published
   page shows stops being a preview. A modest, deliberate empty state is better than either.

   min-height so an empty widget still holds its side of a row open rather than collapsing to a line of
   text, which is what unbalances the row it sits in. */
.loc-widget-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    min-height: 6rem;
    padding: 1rem;
    text-align: center;
    color: var(--bs-secondary-color, #6c757d);
}

.loc-widget-empty > i {
    font-size: 1.4rem;
    opacity: 0.5;
}

/* Builder-only setup guidance - which setting makes content appear here. Never shown to a visitor, for
   whom it is somebody else's admin problem. */
.loc-widget-empty-hint {
    font-size: 0.85rem;
    opacity: 0.85;
}

/* WIDGET SUB-HEADINGS
   The label above a group INSIDE a widget - "Upcoming Bookings", "Event Registrations", "Family Members".
   They were a muted h6 in My Bookings and a small muted div in My Membership, so two widgets side by side
   disagreed about both the colour and the size of the same thing.

   Primary, because these are the widget's internal signposts and a brand colour makes them read as
   structure rather than as more grey text. --bs-primary is a theme token (see /ClubTheme.css), so this
   follows the club's own colour rather than pinning one.

   No font-size or family, for the same reason .loc-widget-title sets neither: the element is a heading, so
   the type scale and the theme's heading font already reach it. Flex with a gap because several of these
   carry a leading icon, which otherwise needs its own inline margin. */
.loc-widget-subtitle {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0 0 0.5rem;
    font-weight: 600;
    color: var(--bs-primary, #0d6efd);
}

/* BALANCED TILE LINES
   These were a wrapping flex row, which fits as many tiles as will fit and drops the remainder onto the
   next line: six tiles in a space that holds five rendered as five and a lone straggler.

   The tile count is known when the widget renders, so the arrangement is CHOSEN rather than discovered.
   TileVars in ShowSummary.cshtml works out how many tiles should share a line at each of the widths below
   - preferring an arrangement that divides the count evenly, so six goes to 3 + 3 and not 4 + 2 - and
   hands the answer over as one grid template per band. The thresholds are the same for every summary
   widget (the tile width is fixed), so only the templates need to differ and they can be variables.

   Each band falls back to the base template and then to a literal, so a summary rendered without the
   variables still lays out rather than collapsing to a single column. */
.summary-tiles {
    display: grid;
    grid-template-columns: var(--sm-tpl, repeat(auto-fit, minmax(0, 190px)));
    justify-content: center;
    gap: 1rem;
}

@container (max-width: 1219.98px) {
    .summary-tiles { grid-template-columns: var(--sm-b1220, var(--sm-tpl, repeat(auto-fit, minmax(0, 190px)))); }
}

@container (max-width: 1013.98px) {
    .summary-tiles { grid-template-columns: var(--sm-b1014, var(--sm-tpl, repeat(auto-fit, minmax(0, 190px)))); }
}

@container (max-width: 807.98px) {
    .summary-tiles { grid-template-columns: var(--sm-b808, var(--sm-tpl, repeat(auto-fit, minmax(0, 190px)))); }
}

@container (max-width: 601.98px) {
    .summary-tiles { grid-template-columns: var(--sm-b602, var(--sm-tpl, repeat(auto-fit, minmax(0, 190px)))); }
}

@container (max-width: 395.98px) {
    .summary-tiles { grid-template-columns: var(--sm-b396, var(--sm-tpl, repeat(auto-fit, minmax(0, 190px)))); }
}

.summary-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    /* Fills its grid track, which is itself capped at the 190px these tiles were drawn at (see the
       templates above). A fixed width here would stop the last band shrinking them to fit a phone. */
    width: 100%;
    padding: 1.25rem 1rem;
    background: #fff;
    border-radius: 14px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.summary-tile:hover {
    transform: translateY(-4px);
    box-shadow: 0 10px 24px rgba(0, 0, 0, 0.13);
}

.summary-tile-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 58px;
    height: 58px;
    margin-bottom: 0.85rem;
    font-size: 1.5rem;
    color: #fff;
    border-radius: 50%;
    cursor: pointer;
}

.summary-tile-text {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 2.75rem;
    margin-bottom: 0.85rem;
    text-align: center;
}

.summary-tile-primary {
    font-weight: 600;
    line-height: 1.3;
}

/* Same weight as the primary line above it. A tile's two lines are one sentence broken in half - "Your
   Membership is:" / "Gold" - so setting only the first of them bold read as an accident rather than a
   hierarchy. The size and colour still separate them. */
.summary-tile-secondary {
    color: #6c757d;
    font-size: 0.9rem;
    font-weight: 600;
    line-height: 1.3;
}

.summary-tile-btn {
    min-width: 130px;
    border-radius: 999px;
}

.summary-icon-profile {
    background: #0d6efd;
}

.summary-icon-bookings {
    background: #20c997;
}

.summary-icon-events {
    background: #fd7e14;
}

.summary-icon-news {
    background: #6f42c1;
}

.summary-icon-membership {
    background: #d63384;
}

.summary-icon-balance {
    background: #198754;
}

/* --- Summary: List format (compact rows) --- */
.summary-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    max-width: 520px;
    margin: 0 auto;
}

.summary-row {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding: 0.7rem 0.9rem;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.07);
    cursor: pointer;
    transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.summary-row:hover {
    transform: translateX(3px);
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}

.summary-row-icon {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    font-size: 1.15rem;
    color: #fff;
    border-radius: 50%;
}

.summary-row-text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.summary-row-primary {
    font-weight: 600;
    line-height: 1.25;
}

/* As .summary-tile-secondary: the same sentence split the same way, so it takes the same weight. */
.summary-row-secondary {
    color: #6c757d;
    font-size: 0.88rem;
    font-weight: 600;
    line-height: 1.25;
}

.summary-row-chevron {
    flex: 0 0 auto;
    color: #b0b7c0;
    font-size: 0.85rem;
}

/* --- Summary: Compact format (icon chips) --- */
/* The chips get the same balanced grid as the tiles, for the same reason and with the same machinery -
   only the item width, and so the thresholds, differ. Wrapping flex gave six chips as 4 + 2, or 5 + 1 at
   some widths; a grid of three gives 3 + 3.

   Equal-width tracks are also the answer to pills of different widths looking ragged when they stack: each
   chip fills its track, so they all match. The track is capped rather than 1fr, because stretching six
   pills across a wide page would not be compact. */
.summary-compact {
    display: grid;
    grid-template-columns: var(--sc-tpl, repeat(auto-fit, minmax(0, 170px)));
    justify-content: center;
    gap: 0.6rem;
}

@container (max-width: 1069.98px) {
    .summary-compact { grid-template-columns: var(--sc-b1070, var(--sc-tpl, repeat(auto-fit, minmax(0, 170px)))); }
}

@container (max-width: 889.98px) {
    .summary-compact { grid-template-columns: var(--sc-b890, var(--sc-tpl, repeat(auto-fit, minmax(0, 170px)))); }
}

@container (max-width: 709.98px) {
    .summary-compact { grid-template-columns: var(--sc-b710, var(--sc-tpl, repeat(auto-fit, minmax(0, 170px)))); }
}

@container (max-width: 529.98px) {
    .summary-compact { grid-template-columns: var(--sc-b530, var(--sc-tpl, repeat(auto-fit, minmax(0, 170px)))); }
}

@container (max-width: 349.98px) {
    .summary-compact { grid-template-columns: var(--sc-b350, var(--sc-tpl, repeat(auto-fit, minmax(0, 170px)))); }
}

/* A phone gets the LIST, whatever the club chose
   A tile carries a heading, a description and a button, and at phone width the tiles are already one per
   line - so a summary of six becomes six full-width cards and pushes everything below it off the screen.
   The list says the same thing in the same order in a fraction of the height.

   The list and not the chips: a list is already a phone-shaped layout, and the chips drop the description
   line. Only CARDS fall back - a club that chose list or compact keeps it, since both already read well on
   a phone.

   480px is the widest a phone's page column gets, and comfortably above the 396px at which the tiles go to
   one per line, so the swap happens before that arrangement is ever seen rather than after.

   Measured on .summary-dashboard, which is the query container (it has to be - an element cannot query
   itself). A summary in a narrow COLUMN on a desktop therefore switches too, which is right for the same
   reason. */
.summary-list.summary-narrow {
    display: none;
}

@container (max-width: 479.98px) {
    .summary-wide {
        display: none;
    }

    .summary-list.summary-narrow {
        display: flex;
    }
}

.summary-chip {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.8rem 0.4rem 0.4rem;
    background: #fff;
    border-radius: 999px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    cursor: pointer;
    transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.summary-chip:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 14px rgba(0, 0, 0, 0.13);
}

.summary-chip-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    font-size: 0.95rem;
    color: #fff;
    border-radius: 50%;
}

.summary-chip-label {
    font-weight: 600;
    font-size: 0.9rem;
}

.summary-chip-value {
    background: var(--bs-primary, #0d6efd);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
    min-width: 1.4rem;
    text-align: center;
    padding: 0.05rem 0.45rem;
    border-radius: 999px;
}

@media (prefers-color-scheme: dark) {
    .summary-row,
    .summary-chip { background: #2b3035; }
}

/* Divider / Spacer widget. The container reserves the widget's vertical space (min-height set inline
   from the Height field) and centres the optional rule; when the line is off it is simply whitespace.
   The rule itself is drawn with a top border so LineStyle maps straight to CSS (solid/dashed/dotted). */
.loc-divider {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
}

.loc-divider-line {
    margin: 0;
    opacity: 1;
    box-sizing: border-box;
}

/* Themed section divider: a row shows this below itself via "Show divider". Its look comes entirely from the
   Club Theme (emitted as --loc-divider-* by /ClubTheme.css), so one theme change restyles every divider on
   every page. It sits between rows in the .website flex column, centered at the theme's chosen width. */
.loc-row-divider {
    display: flex;
    justify-content: center;
    width: 100%;
    /* The .website flex gap already puts a normal row gap ABOVE the line (so it sits where the row would
       normally end). The space AFTER the line is the theme's Space-after: we adjust the bottom margin by the
       difference from the row gap so the total below the line equals Space-after (can be tighter or wider). */
    margin-bottom: calc(var(--loc-divider-space-after, 1rem) - var(--loc-row-gap-eff, 1rem));
}

.loc-row-divider .loc-row-divider-line {
    width: var(--loc-divider-width, 100%);
    border-top: var(--loc-divider-thickness, 1px) var(--loc-divider-style, solid) var(--loc-divider-color, var(--bs-border-color, #dee2e6));
}

/* Full-width SVG shape (wave/slant/curve/zigzag/triangle). Block so it fills the divider's width/height. */
.loc-divider-shape {
    display: block;
}

/* Line with a centered ornament: two flexible line segments either side of the ornament, all vertically
   centered. The ornament inherits the line colour via currentColor. */
.loc-divider-accentline {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.loc-divider-accentline .loc-divider-seg {
    flex: 1 1 auto;
    height: 0;
}

.loc-divider-ornament {
    flex: 0 0 auto;
    color: currentColor;
}

/* Diamond: a small rotated square. */
.loc-divider-ornament-diamond {
    width: 0.55rem;
    height: 0.55rem;
    background: currentColor;
    transform: rotate(45deg);
}

/* Circle: a small filled dot. */
.loc-divider-ornament-circle {
    width: 0.6rem;
    height: 0.6rem;
    background: currentColor;
    border-radius: 50%;
}

/* Dots: three small filled dots. The element is the middle dot; box-shadow paints the outer two, and the
   side margins reserve room for them so they don't overlap the line segments. */
.loc-divider-ornament-dots {
    width: 0.35rem;
    height: 0.35rem;
    background: currentColor;
    border-radius: 50%;
    margin: 0 0.65rem;
    box-shadow: -0.65rem 0 0 currentColor, 0.65rem 0 0 currentColor;
}

/* Content List widget: the accordion / tabs markup is Bootstrap's (styled by the theme); these rules
   cover the widget's own chrome - the title, the visitor format toggle, the empty state, and the plain
   "list" format which is not a Bootstrap component. */
.loc-contentlist {
    width: 100%;
}

.loc-cl-title {
    margin-bottom: 0.75rem;
}

.loc-cl-empty {
    padding: 1rem;
    text-align: center;
    color: #888;
    font-style: italic;
}

.loc-cl-toggle {
    display: flex;
    justify-content: flex-end;
    gap: 0;
    margin-bottom: 0.5rem;
}

.loc-cl-toggle-btn {
    border: 1px solid var(--bs-border-color, #dee2e6);
    background: transparent;
    color: inherit;
}

.loc-cl-toggle-btn.active {
    background: var(--bs-primary, #0d6efd);
    border-color: var(--bs-primary, #0d6efd);
    color: #fff;
}

.loc-cl-tab-body {
    padding: 1rem 0.25rem;
}

.loc-cl-list-item {
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

.loc-cl-list-item:last-child {
    border-bottom: 0;
}

.loc-cl-list-header {
    margin-bottom: 0.35rem;
}

/* Custom HTML widget: the block renders in a sandboxed iframe. In the builder a transparent overlay sits
   over the frame so the widget stays selectable/draggable; published pages have no .columncontent ancestor,
   so the overlay is hidden and the iframe is interactive. */
.loc-customhtml {
    position: relative;
    width: 100%;
}

.loc-customhtml-frame {
    width: 100%;
    border: 0;
    display: block;
}

.loc-customhtml-overlay {
    display: none;
}

.columncontent .loc-customhtml-overlay {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 2;
    cursor: move;
}

.loc-customhtml-empty {
    padding: 1rem;
    text-align: center;
    color: #888;
    font-style: italic;
}

/* Live preview in the Custom HTML Block modal. The stage is the bordered panel; the frame inside it is
   built by customhtmlblock.js and carries the block's markup in srcdoc, exactly as the published page
   does. The stage scrolls rather than the modal, so an auto-height block that reports 4000px shows what
   it will do without pushing Save off the bottom of the dialog. */
.loc-chb-preview {
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 6px;
    background: #fff;
    overflow: auto;
    min-height: 120px;
    max-height: 62vh;
}

/* The generator panel above the markup editor. Tinted and boxed so it reads as a tool that ACTS ON the
   editor below rather than as one more field to fill in - the distinction matters on a form where every
   other control is saved and this one is not. */
.loc-chb-ai {
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 6px;
    background: var(--bs-tertiary-bg, #f8f9fa);
    padding: 0.75rem;
}

/* White, not the theme's page colour: the frame is a document of its own with none of the club's CSS in
   it, and a preview on a tinted panel would credit the block with a background it does not have. */
.loc-chb-preview-frame {
    display: block;
    width: 100%;
    border: 0;
    background: #fff;
}

/* Structured image widget: image + caption arrangement is driven by CSS classes (imgw-pos-*), composed
   server-side (ShowImage.cshtml). Responsive - side-by-side positions stack on narrow screens. */
.imgw-img {
    max-width: 100%;
    height: auto;
    display: block;
}

/* "Rounded" shape: a clearly-visible radius - Bootstrap's .rounded (~6px) is too subtle on a large image. */
.imgw-rounded {
    border-radius: 1rem;
}

/* Builder-only: empty editable regions (image title/caption, card header/body/footer) are otherwise
   invisible and near-impossible to click into. Frame them with a dashed border + a "click to add..."
   placeholder so the region's bounds are obvious and easy to target. Never shown on the published site
   (there is no .website-builder there) or once the region has content (:empty stops matching). */
.website-builder .editable-content:empty {
    min-height: 2rem;
    border: 1px dashed #b0b7c0;
    border-radius: 4px;
    background: rgba(0, 0, 0, 0.015);
}

.website-builder .editable-content:empty::before {
    content: "Click to edit";
    color: #9aa2ac;
    font-size: 0.85rem;
    font-style: italic;
    pointer-events: none;
}

.website-builder .editable-content.title:empty::before { content: "Click to add a title"; }
.website-builder .editable-content.text:empty::before { content: "Click to add text"; }
.website-builder .editable-content.description:empty::before { content: "Click to add a description"; }
.website-builder .editable-content[name="Header"]:empty::before { content: "Click to add a header"; }
.website-builder .editable-content[name="Body"]:empty::before { content: "Click to add body content"; }
.website-builder .editable-content[name="Footer"]:empty::before { content: "Click to add a footer"; }

/* Builder-only: the overlay text block on a background/hero image sits on top of the photo, so when its
   colour is close to the image (e.g. white text on a light photo) it's invisible and the user can't tell
   where to double-click to edit it. Outline the editable block even when it already has text, and give it a
   targetable min-size so an empty one is still easy to hit. Scoped to .website-builder - published pages
   show nothing. */
.website-builder .imgw-pos-bg .imgw-textblock {
    outline: 2px dashed rgba(13, 110, 253, 0.85);
    outline-offset: 2px;
    min-width: 2.5rem;
    min-height: 1.4rem;
}

/* The way back out of a shared section - see #section-return-bar in WebsiteBuilder.cshtml.
   Purple, matching the badge on a placement, so the two read as the same idea; and deliberately the most
   prominent thing in the toolbar while it is showing, because everything below it is about a page and none
   of it applies to a section. */
.section-return-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin: 0.5rem 0.25rem;
    padding: 0.5rem 0.75rem;
    border-radius: var(--bs-border-radius, 0.375rem);
    font-size: 0.85rem;
    color: #fff;
    background: rgba(111, 66, 193, 0.95);
}

.section-return-what {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
}

/* Publish and the way out, kept together at the end of the bar. They wrap onto their own line in a 285px
   toolbar rather than squeezing the section's name to nothing. */
.section-return-actions {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-left: auto;
}

/* EDITING A SHARED SECTION, as opposed to a page. Set on <body> by WebsiteBuilder.EditSection and taken
   off again the moment a page is opened.

   A section is not a page: it has no nav item, no audience, no URL and no history of its own. So while one
   is open the site's chrome comes off the canvas - the navigation bar's every entry leads somewhere this
   canvas cannot follow, and the top bar is itself a section, which made "which one am I editing?" a fair
   question - and the toolbar's page group goes with it, because every control in it is keyed on a nav item
   and would have acted on whatever page was last open. The section header stands in for all of it. */
body.loc-editing-section .website-builder .loc-topbar,
body.loc-editing-section #website-nav,
body.loc-editing-section #builder-page-group {
    display: none;
}

/* Purple, like the return bar in the toolbar and the badge on a placement, so all three read as one idea:
   this is shared content. Square-cornered and full width because it stands where the navigation bar stood,
   and the bar is the shape the eye is expecting there. */
.section-canvas-header {
    display: none;
    align-items: center;
    gap: 0.6rem;
    padding: 0.6rem 1rem;
    color: #fff;
    background: rgba(111, 66, 193, 0.95);
}

body.loc-editing-section .section-canvas-header {
    display: flex;
}

.section-canvas-what {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem;
    min-width: 0;
}

.section-canvas-title {
    font-weight: 600;
}

.section-canvas-note {
    font-size: 0.8rem;
    opacity: 0.85;
}

/* SHARED SECTIONS
   A section placement renders another page's rows, so it adds NO padding of its own - the widgets inside it
   already carry the theme's, and a second layer would inset shared content differently from the same
   content on the page that owns it. That is also why it is absent from the theme text-padding list. */
.loc-section-widget {
    width: 100%;
}

/* Builder only: shared content has to be recognisable as shared, or somebody edits the footer on the home
   page and is surprised it changed on every other one. */
.website-builder .loc-section-shared {
    box-shadow: inset 0 0 0 2px rgba(111, 66, 193, 0.35);
}

.loc-section-badge {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.6rem;
    font-size: 0.75rem;
    font-weight: 600;
    color: #fff;
    background: rgba(111, 66, 193, 0.9);
}

.loc-section-badge-note {
    font-weight: 400;
    opacity: 0.85;
}

/* The badge is builder chrome, not content - a published page shows the section, not a label about it. */
.website:not(.website-builder .website) .loc-section-badge {
    display: none;
}

/* JOIN WIDGET
   The one call to action a signed-out visitor gets. Centred and roomy because it is usually the only thing
   in its row - the club puts it on a row with Show For Members off, so members never see it at all. */
.loc-join-widget {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
    text-align: center;
}

/* Rich text, edited in place. Width-capped rather than styled: whatever the club writes should look like
   the rest of their site, so this only stops a paragraph running the full width of a wide row. */
.loc-join-body {
    width: 100%;
    max-width: 44rem;
    margin: 0;
}

.loc-join-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
}

/* EVENTS WIDGET
   The query container for the card grid below - same arrangement as the news and summary widgets, and for
   the same reason: an element cannot query its own container. */
.loc-event-widget {
    container-type: inline-size;
}

/* CARDS: the poster look. Balanced lines, so four events in a space that holds three go 2 + 2 and never
   leave one card on a line of its own. Templates computed in CardVars (ShowEventWidget.cshtml). */
.loc-event-cards {
    display: grid;
    grid-template-columns: var(--ec-tpl, repeat(auto-fit, minmax(0, 1fr)));
    gap: 1rem;
}

@container (max-width: 1463.98px) {
    .loc-event-cards { grid-template-columns: var(--ec-b1464, var(--ec-tpl, repeat(auto-fit, minmax(0, 1fr)))); }
}

@container (max-width: 1167.98px) {
    .loc-event-cards { grid-template-columns: var(--ec-b1168, var(--ec-tpl, repeat(auto-fit, minmax(0, 1fr)))); }
}

@container (max-width: 871.98px) {
    .loc-event-cards { grid-template-columns: var(--ec-b872, var(--ec-tpl, repeat(auto-fit, minmax(0, 1fr)))); }
}

@container (max-width: 575.98px) {
    .loc-event-cards { grid-template-columns: var(--ec-b576, var(--ec-tpl, repeat(auto-fit, minmax(0, 1fr)))); }
}

.loc-event-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: #fff;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: var(--bs-border-radius, 0.375rem);
}

/* The image, with the date block sitting on it. Fixed aspect rather than fixed height, so cards line up
   whatever the photo's proportions - which they will not otherwise, since clubs upload anything. */
.loc-event-media {
    position: relative;
    aspect-ratio: 16 / 9;
    background: var(--bs-tertiary-bg, #f1f3f5);
}

.loc-event-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* No photo: the date block stops being an overlay and becomes the whole panel, centred. An event with no
   image is the common case for a small club, so this is a first-class look rather than a fallback. */
.loc-event-media-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 7;
    background: color-mix(in srgb, var(--bs-primary, #0d6efd) 8%, var(--bs-body-bg, #fff));
}

.loc-event-media img + .loc-event-date {
    position: absolute;
    top: 0.6rem;
    left: 0.6rem;
    background: rgba(255, 255, 255, 0.94);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
}

/* THE DATE BLOCK
   Three lines rather than a formatted string: the day number is what the eye lands on, and the month and
   weekday are context around it. Shared by all three formats. */
.loc-event-date {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    min-width: 3.4rem;
    padding: 0.35rem 0.5rem;
    border-radius: var(--bs-border-radius, 0.375rem);
    line-height: 1.05;
}

.loc-event-month {
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: var(--bs-primary, #0d6efd);
}

.loc-event-day {
    font-size: 1.5rem;
    font-weight: 700;
}

.loc-event-weekday {
    font-size: 0.68rem;
    color: var(--bs-secondary-color, #6c757d);
}

.loc-event-body {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    gap: 0.35rem;
    padding: 0.85rem;
}

.loc-event-name {
    text-decoration: none;
}

/* Pinned to the bottom so the buttons line up across cards whose descriptions run to different lengths. */
.loc-event-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-top: auto;
    padding-top: 0.5rem;
}

/* LIST: the date block beside the text, for a sidebar. */
.loc-event-list {
    display: flex;
    flex-direction: column;
}

.loc-event-row {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

.loc-event-row:last-child {
    border-bottom: 0;
}

.loc-event-row:first-child {
    padding-top: 0;
}

.loc-event-row .loc-event-body {
    padding: 0;
}

/* COMPACT: a coming-up strip. The name may run out of room rather than wrap - two lines would defeat the
   point of choosing it. */
.loc-event-compact {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.loc-event-line {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    min-width: 0;
}

.loc-event-when {
    flex-shrink: 0;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: var(--bs-primary, #0d6efd);
}

.loc-event-line .loc-event-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
}

/* NEWS WIDGET
   The query container for the card grid below - the same arrangement as .summary-dashboard, and for the
   same reason: an element cannot query its own container. */
.loc-news-widget {
    container-type: inline-size;
}

/* LIST: the arrangement this widget has always drawn.
   The items used to be spaced with utility classes (border-bottom pb-2 mb-2), which put half a rem under
   the text and nothing above it - so each article sat hard against the rule above it while the widget's
   own padding held the text off the edges. Read beside My Membership, whose blocks breathe, the list
   looked cramped rather than dense.

   Padding rather than margin, and on both sides of the text, so a rule between two articles has the same
   space above and below it. Horizontal padding stays with the widget, exactly as it does for My
   Membership - the theme's text padding is what insets the content in both. */
.loc-news-list .loc-news-item {
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

/* No rule under the last article: it would read as a line under the whole widget rather than between two
   of anything, and the widget's own bottom padding already closes the list. */
.loc-news-list .loc-news-item:last-child {
    border-bottom: 0;
}

/* The first article has the widget's own top padding above it already. */
.loc-news-list .loc-news-item:first-child {
    padding-top: 0;
}

/* CARDS: one card per article, in a balanced grid.
   Same rule as the summary tiles - only counts that leave no card stranded on a line of its own - with its
   own width, and so its own thresholds, because a news card carries a headline, a date line and an excerpt.
   The templates are computed in CardVars (ShowNewsWidget.cshtml) and arrive as variables. */
.loc-news-cards {
    display: grid;
    grid-template-columns: var(--nc-tpl, repeat(auto-fit, minmax(0, 1fr)));
    gap: 1rem;
}

@container (max-width: 1363.98px) {
    .loc-news-cards { grid-template-columns: var(--nc-b1364, var(--nc-tpl, repeat(auto-fit, minmax(0, 1fr)))); }
}

@container (max-width: 1087.98px) {
    .loc-news-cards { grid-template-columns: var(--nc-b1088, var(--nc-tpl, repeat(auto-fit, minmax(0, 1fr)))); }
}

@container (max-width: 811.98px) {
    .loc-news-cards { grid-template-columns: var(--nc-b812, var(--nc-tpl, repeat(auto-fit, minmax(0, 1fr)))); }
}

@container (max-width: 535.98px) {
    .loc-news-cards { grid-template-columns: var(--nc-b536, var(--nc-tpl, repeat(auto-fit, minmax(0, 1fr)))); }
}

/* A column, so the actions can be pinned to the bottom and line up across cards whose excerpts run to
   different lengths - the same problem the summary tiles solve by stretching. */
.loc-news-card {
    display: flex;
    flex-direction: column;
    padding: 0.85rem;
    background: #fff;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: var(--bs-border-radius, 0.375rem);
}

.loc-news-card .loc-news-actions {
    margin-top: auto;
    padding-top: 0.5rem;
}

/* COMPACT: a headline strip. One line per article, and the title is allowed to run out of room rather than
   wrap - two lines would defeat the point of choosing it. */
.loc-news-compact {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.loc-news-line {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    min-width: 0;
}

.loc-news-line .loc-news-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* THEME TEXT PADDING, AND WHAT "DEFAULT" MEANS
   A padding side left on Default emits no utility class at all, which is what lets this rule apply. Set any
   side to 0-5 and its p*-N utility beats this, because Bootstrap's utilities are !important - so Default
   follows the theme, 0 means none, and a number means that number. All three are honest.

   WHICH WIDGETS. Every widget whose content is TEXT, which is the whole point of the setting: the text and
   custom-HTML widgets, the news and content lists, and the member widgets, which all render words that need
   room from their edge. A card is here too but through its regions below, since it has three of them.

   NOT media: image, banner, video, gallery, map, divider, CTA button. Padding there insets the thing
   itself rather than giving words space - and on a banner it would undo the full-bleed layout.

   SOCIAL ICONS ARE HERE AND NOT THERE, which is a change: they were grouped with media on the reasoning
   that padding would inset the icons. It does not. An image is a media object whose edge IS the content,
   so insetting it changes the thing; the social widget renders an optional HEADING above a cluster of
   fixed-size icon chips, so it is words and content that need room from the edge - the very definition
   this list is drawn on. Flush against the top of its box is what it looked like, and every club was
   fixing it by hand.

   Listed per type rather than "everything except", so a new widget starts unpadded and joins this list only
   when someone decides it holds text. The first block is the published class, the second the builder's. */
.website .loc-widget-html,
.website .loc-widget-customhtml,
.website .loc-widget-contentlist,
.website .loc-widget-newswidget,
.website .loc-widget-eventwidget,
.website .loc-widget-joinwidget,
.website .loc-widget-searchwidget,
.website .loc-widget-summary,
.website .loc-widget-memberbookings,
.website .loc-widget-tabbalance,
.website .loc-widget-membershipwidget,
.website .loc-widget-socialmedia,
.website .loc-widget-weather,
.website .columncontent.text-widget,
.website .columncontent.custom-html-widget,
.website .columncontent.contentlist-widget,
.website .columncontent.newswidget-widget,
.website .columncontent.eventwidget-widget,
.website .columncontent.joinwidget-widget,
.website .columncontent.searchwidget-widget,
.website .columncontent.summary-widget,
.website .columncontent.memberbookings-widget,
.website .columncontent.tabbalance-widget,
.website .columncontent.membership-widget,
.website .columncontent.socialmedia-widget,
.website .columncontent.weather-widget {
    padding: var(--text-pad-y, 1rem) var(--text-pad-x, 1rem);
}

.website .card-header { padding: var(--loc-card-hdr-pad, var(--text-pad-y, 1rem) var(--text-pad-x, 1rem)); }
.website .card-body   { padding: var(--loc-card-body-pad, var(--text-pad-y, 1rem) var(--text-pad-x, 1rem)); }
.website .card-footer { padding: var(--loc-card-ftr-pad, var(--text-pad-y, 1rem) var(--text-pad-x, 1rem)); }

/* Card widgets: rounding and shadow come from the SAME site-wide Club Theme tokens as buttons, inputs and
   the rest of the UI (--bs-border-radius / --bs-box-shadow, set by /ClubTheme.css from the theme's Corner
   Rounding + Shadow Depth), so the whole website shares one consistent look. Only the border on/off and the
   lift-on-hover remain card-specific (--loc-card-* from _PageSpacingVars). Scoped to .website so admin/member
   dashboard cards are unaffected. overflow:hidden clips the header/footer corners to the card radius so
   rounding looks right without touching each card's markup; the shadow sits outside, so it isn't clipped. */
.website .card {
    /* A card fills its widget wrapper: that wrapper is .d-flex.w-100 in the builder (so the card is a flex
       item, which would otherwise shrink to its content) and a plain block when published - width:100% is
       right in both. What must go is the SIDE margins: cards are stored as raw HTML per card and the old
       template wrote inline "margin:10px; width:100%", and because margins sit outside the border box that
       pair is always 20px wider than the column. The card ran past the page's right edge, and every widget
       on the page then read as inset-left / clipped-right with the page scrollable by the overflow. Only
       the margins need !important, to beat that inline shorthand; the 10px top/bottom is kept. */
    width: 100%;
    margin-left: 0 !important;
    margin-right: 0 !important;
    box-shadow: var(--bs-box-shadow, none);
    /* No border by default. This used to read a theme token whose default was "off"; with the token gone
       the var's own 1px fallback would have handed every card a border nobody asked for. A card that wants
       one gets it from the Border control on the element or its Element Style. */
    border-width: 0;
    border-radius: var(--bs-border-radius, 0.375rem);
    overflow: hidden;
    transition: box-shadow 0.15s ease, transform 0.15s ease;
}

/* No hover behaviour of its own. "Does this card lift" is a per-card question, so it belongs to the element
   or its Element Style - the Hover preset (lift / glow) - rather than to one switch covering every card on
   the site. The transition above stays, so a style's hover animates rather than snapping. */

/* CARD REGIONS AND HEADER COLOUR, driven from the widget's BoxStyle
   All of this used to be written into the card's own HTML - "display:none" inline for a hidden region, a
   bg-* class for the header colour. That put presentation into the field search will index, so it now lives
   in BoxStyle and arrives as a class on the widget WRAPPER. !important on the region rules so a card still
   carrying the legacy inline display is overridden rather than fighting it. */
.website .loc-card-nohdr .card-header { display: none !important; }
.website .loc-card-noftr .card-footer { display: none !important; }

.website [class*="loc-cardhdr-"] .card-header { color: #fff; }
.website .loc-cardhdr-primary   .card-header { background-color: var(--bs-primary, #0d6efd); }
.website .loc-cardhdr-secondary .card-header { background-color: var(--bs-secondary, #6c757d); }
.website .loc-cardhdr-success   .card-header { background-color: var(--bs-success, #198754); }
.website .loc-cardhdr-danger    .card-header { background-color: var(--bs-danger, #dc3545); }
.website .loc-cardhdr-dark      .card-header { background-color: var(--bs-dark, #212529); }

/* Light-family presets need dark text to stay readable, mirroring Bootstrap's own .text-bg-* contrast. */
.website .loc-cardhdr-info    .card-header { background-color: var(--bs-info, #0dcaf0); color: var(--bs-body-color, #212529); }
.website .loc-cardhdr-warning .card-header { background-color: var(--bs-warning, #ffc107); color: var(--bs-body-color, #212529); }
.website .loc-cardhdr-light   .card-header { background-color: var(--bs-light, #f8f9fa); color: var(--bs-body-color, #212529); }

/* Split card footer: the common footer shape is something on the left and an action on the right - a date
   and a "Read more" button. The footer is one editable region, so the two are just the first and last blocks
   the user typed; space-between puts them at the ends and anything in between spreads evenly. Wraps on a
   narrow column rather than overflowing, and the blocks lose their paragraph bottom margin so a one-line
   footer keeps its normal height. Toggled from the card's Options panel and stored as BoxStyle.SplitFooter,
   which emits this class on the widget WRAPPER - the card's own HTML stays free of styling so it indexes
   cleanly for search. */
.website .loc-card-split .card-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

.website .loc-card-split .card-footer > * {
    margin-bottom: 0;
}

/* Card header text contrast: when a header uses a colour preset, give its text a readable colour so it's
   visible on dark backgrounds (e.g. a dark/black theme primary) without the user hand-setting it - matching
   how Bootstrap's .text-bg-* utilities contrast the default palette. Dark-family presets get white text,
   light-family get dark text. A colour set explicitly in the editor still wins. */
.website .card-header.bg-primary,
.website .card-header.bg-secondary,
.website .card-header.bg-success,
.website .card-header.bg-danger,
.website .card-header.bg-dark {
    color: #fff;
}

.website .card-header.bg-info,
.website .card-header.bg-warning,
.website .card-header.bg-light {
    color: #212529;
}

/* =================================================================================================
   WIDGET STYLE PRESETS (BoxStyle.Accent / Surface / EdgeAccent / Hover)

   A small vocabulary of looks the box editor offers as dropdowns, so the common modern treatments -
   an accent rule along an edge, a tinted panel, a card that lifts on hover - are one click rather
   than something a club has to write CSS for. The administrator dashboard's cards are the reference:
   an accent, a rule along the top edge, and tints derived from that same accent.

   THE ACCENT IS DATA, THE PRESET IS A LOOK. Colour arrives as --loc-accent (set inline per element by
   BoxStyle.PresetCssVars, from a palette role or a custom hex) and every rule here reads it. The
   alternative - loc-s-edge-top-primary, loc-s-edge-top-danger, ... - is a class per preset x colour
   pair and still cannot express a colour outside the palette.

   Tints are color-mix() and NOT rgba(var(--bs-*-rgb), a) as the dashboard does: an rgb triple only
   exists for the eight palette roles, so the triple approach silently produces nothing for a custom
   hex. color-mix treats a role and a hex identically.

   PRECEDENCE. Everything here is a class, and the box editor's own Border/Background/Shadow are
   emitted inline, so an explicitly set border or background always beats the preset. That is the
   right way round: the preset is the starting point, the control is the override.

   Scoped to .website so admin and member screens are unaffected. These names are a support contract
   in the same sense as WidgetStyleHooks - a club may target them from their theme's Custom CSS, so
   renaming one breaks live sites.
   ================================================================================================= */

/* The accent every preset reads, resolved once. A preset used without choosing a colour falls back to
   the theme's primary, so it still looks like the club rather than like nothing. */
.website [class*="loc-s-"] {
    --loc-a: var(--loc-accent, var(--bs-primary, #0d6efd));
}

/* --- Surface ------------------------------------------------------------------------------------
   A surface preset paints the WIDGET BOX. On a card widget the .card inside would otherwise draw its
   own border and shadow inside that one, reading as a frame within a frame - so the card stands down
   and lets the wrapper be the single surface. Its regions are untouched: header/footer colours, the
   region paddings and the split footer all still work. Listed for :hover as well so it also beats
   .website .card:hover, which would otherwise put the card's shadow and lift back on hover. */
.website .loc-s-surface {
    border-radius: var(--bs-border-radius, 0.375rem);
    overflow: hidden;
}

.website .loc-s-surface .card,
.website .loc-s-surface .card:hover {
    border: 0;
    background: transparent;
    box-shadow: none;
    transform: none;
}

.website .loc-s-elevated {
    background: var(--bs-body-bg, #fff);
    border: 1px solid var(--bs-border-color, #dee2e6);
    box-shadow: var(--bs-box-shadow, 0 0.5rem 1rem rgba(0, 0, 0, 0.15));
}

.website .loc-s-outline {
    background: transparent;
    border: 1px solid color-mix(in srgb, var(--loc-a) 35%, transparent);
}

.website .loc-s-tint {
    background: color-mix(in srgb, var(--loc-a) 8%, var(--bs-body-bg, #fff));
    border: 1px solid color-mix(in srgb, var(--loc-a) 18%, transparent);
}

.website .loc-s-gradient {
    background-image: linear-gradient(135deg, color-mix(in srgb, var(--loc-a) 16%, var(--bs-body-bg, #fff)), var(--bs-body-bg, #fff) 70%);
    border: 1px solid color-mix(in srgb, var(--loc-a) 15%, transparent);
}

/* Glass needs something behind it to blur, so it only reads as glass over a row background or image -
   over the plain page background it degrades to a plain translucent panel, which is still fine. */
.website .loc-s-glass {
    background: color-mix(in srgb, var(--bs-body-bg, #fff) 60%, transparent);
    border: 1px solid rgba(255, 255, 255, 0.35);
    box-shadow: var(--bs-box-shadow-sm, 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075));
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}

/* --- Surface presets on a full-width row ---------------------------------------------------------
   A row with "full-width background band" paints its background on a ::before layer that bleeds past the
   page padding; the row itself stays at content width. A Surface preset knows nothing about that and paints
   the ROW, which produced a section that behaved like a band - edge-to-edge layer, butting against the next
   band - while looking like a boxed panel. Two corrections:

   1. overflow. .loc-s-surface sets overflow:hidden to clip its corners, which also clips the ::before and
      stops the band bleeding at all. A band has nothing to clip, so it opts out.
   2. where the colour goes. The surface hands its fill to the band layer and stops painting the row, so the
      colour reaches the page edges instead of stopping at the content width.

   Only the surfaces that make sense edge-to-edge are given a band fill. Outline and glass are about the
   edge of a contained thing, so on a band they simply do nothing rather than being faked. */
.website .loc-s-surface.loc-row-band,
.website .loc-s-surface.loc-row-fullbleed {
    overflow: visible;
    border-radius: 0;
}

.website .loc-s-surface.loc-row-band {
    background: none;
    border: 0;
}

.website .loc-s-tint.loc-row-band::before {
    background-color: color-mix(in srgb, var(--loc-a) 8%, var(--bs-body-bg, #fff));
}

.website .loc-s-elevated.loc-row-band::before {
    background-color: var(--bs-body-bg, #fff);
}

.website .loc-s-gradient.loc-row-band::before {
    background-image: linear-gradient(135deg, color-mix(in srgb, var(--loc-a) 16%, var(--bs-body-bg, #fff)), var(--bs-body-bg, #fff) 70%);
}

/* --- Edge accent --------------------------------------------------------------------------------
   The dashboard draws its rule as a ::before in normal flow. Here it has to be an absolutely
   positioned ::after instead, for two reasons: ::before on a widget box is already the builder's
   widget label, and a card widget's box is display:flex in the builder, where a flow pseudo-element
   would become a flex ITEM and lay out as a bar down the side rather than across the top.

   z-index stays below the builder label's 4 so the label is never covered, and pointer-events:none
   keeps the bar from swallowing clicks meant for the widget underneath. */
.website .loc-s-edge {
    position: relative;
}

.website .loc-s-edge::after {
    content: "";
    position: absolute;
    z-index: 3;
    background: var(--loc-a);
    pointer-events: none;
}

.website .loc-s-edge-top::after {
    top: 0;
    left: 0;
    right: 0;
    height: var(--loc-edge-w, 3px);
}

.website .loc-s-edge-bottom::after {
    bottom: 0;
    left: 0;
    right: 0;
    height: var(--loc-edge-w, 3px);
}

.website .loc-s-edge-start::after {
    top: 0;
    bottom: 0;
    left: 0;
    width: var(--loc-edge-w, 3px);
}

.website .loc-s-edge-end::after {
    top: 0;
    bottom: 0;
    right: 0;
    width: var(--loc-edge-w, 3px);
}

/* --- Hover --------------------------------------------------------------------------------------
   Applied on the edit canvas as well as the published page, as .website .card:hover already is - the
   builder is a preview, and a hover effect that only exists once published is one the user cannot
   check. Both effects stand down under a reduced-motion preference. */
.website .loc-s-hover-lift,
.website .loc-s-hover-glow {
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.website .loc-s-hover-lift:hover {
    transform: translateY(-3px);
    box-shadow: var(--bs-box-shadow-lg, 0 1rem 3rem rgba(0, 0, 0, 0.175));
}

.website .loc-s-hover-glow:hover {
    box-shadow: 0 0 0 0.2rem color-mix(in srgb, var(--loc-a) 25%, transparent);
}

@media (prefers-reduced-motion: reduce) {
    .website .loc-s-hover-lift,
    .website .loc-s-hover-glow {
        transition: none;
    }

    .website .loc-s-hover-lift:hover {
        transform: none;
    }
}

/* --- Per-widget presets --------------------------------------------------------------------------
   The presets above apply to any element. These name treatments that only mean something on one kind
   of widget - the opening of a block of text, the frame around an image, a card's header. The editor
   offers each only for the widget it fits; the rules are written so that a class landing anywhere else
   matches nothing rather than doing damage.

   THE TWO-SELECTOR PATTERN. A widget's typed content sits at a different depth in the two contexts:
   published, ShowContent renders it straight into the widget box, while the builder wraps it in a
   .widget-wrapper first. So any rule reaching for "the first thing the user typed" is listed for both
   shapes. Rules that can use a descendant selector (p:first-of-type, .card-header, .imgw-img) work at
   any depth and need only one. In the builder the bare "> :first-child" arm lands on the hidden
   .name-overlay rather than the content, which is why the .widget-wrapper arm has to be there too. */

/* Eyebrow: the opening line becomes a small uppercase kicker above the heading it introduces. */
.website .loc-s-text-eyebrow > :first-child,
.website .loc-s-text-eyebrow > .widget-wrapper > :first-child {
    margin-bottom: 0.35rem;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--loc-a);
}

/* Lead: a larger, lighter opening paragraph. first-of-type rather than first-child so it still finds
   the opening paragraph when a heading comes before it, which is the usual shape. */
.website .loc-s-text-lead p:first-of-type {
    font-size: 1.15rem;
    line-height: 1.6;
    font-weight: 400;
}

.website .loc-s-text-dropcap p:first-of-type::first-letter {
    float: left;
    margin: 0.05em 0.08em 0 0;
    font-size: 3.2em;
    font-weight: 700;
    line-height: 0.82;
    color: var(--loc-a);
}

/* Reading measure. Applied to the CONTENT and not to the widget box, so a tinted or elevated panel
   keeps its full width while the words inside it stay short enough to read comfortably. In the builder
   the first arm caps .widget-wrapper, which then caps the content inside it - same result. */
.website .loc-s-measure-comfortable > *,
.website .loc-s-measure-comfortable > .widget-wrapper > * {
    max-width: 65ch;
}

.website .loc-s-measure-narrow > *,
.website .loc-s-measure-narrow > .widget-wrapper > * {
    max-width: 45ch;
}

/* Image frames. Both are box-shadow rather than a border or a pseudo-element: a shadow follows the
   image's own border-radius, so these compose with the Shape option (square / rounded / circle) in the
   image widget's Options panel instead of fighting it, and neither changes the image's layout size.

   The offset frame is drawn outside the image, so it is clipped if the widget also carries a Surface
   preset (those set overflow:hidden to keep the corners clean). Use one or the other. */
.website .loc-s-frame-ring .imgw-img {
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--loc-a) 30%, transparent);
}

.website .loc-s-frame-offset .imgw-img {
    box-shadow: 12px 12px 0 0 var(--loc-a);
}

/* Card header tinted with the accent instead of filled with a solid colour - the quieter, more current
   treatment. !important throughout because this has to beat both .loc-cardhdr-* (equal specificity,
   and it may legitimately still be set) and any legacy bg-* utility written into a card's stored HTML,
   which Bootstrap marks !important. */
.website .loc-s-cardhdr-tint .card-header {
    background-color: color-mix(in srgb, var(--loc-a) 10%, transparent) !important;
    color: var(--bs-body-color, #212529) !important;
    border-bottom-color: color-mix(in srgb, var(--loc-a) 22%, transparent);
}

/* An icon placed in the card's header, rendered as the tinted rounded chip the dashboard cards use.
   Matches an <i> (Font Awesome) or an <svg>, and matches nothing at all when the header holds no icon -
   so switching this on can never break a card that has none. */
.website .loc-s-cardicon .card-header i,
.website .loc-s-cardicon .card-header svg {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    margin-right: 0.55rem;
    vertical-align: middle;
    border-radius: 10px;
    background: color-mix(in srgb, var(--loc-a) 14%, transparent);
    color: var(--loc-a);
}

/* Statistic layout: a small uppercase label in the header, one large figure, a caption beneath it.
   This is the administrator dashboard's card interior as a preset - the piece that previously needed
   hand-written Custom CSS to reproduce.

   tabular-nums matters more than it looks: without it, a figure that updates re-flows as the digit
   widths change, and a column of figures never lines up. */
.website .loc-s-figure .card-header {
    padding-bottom: 0;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--bs-secondary-color, #6c757d);
    background: none;
    border-bottom: 0;
}

.website .loc-s-figure .card-body > :first-child {
    margin-bottom: 0.15rem;
    font-size: 1.95rem;
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}

.website .loc-s-figure .card-body > :first-child + * {
    margin-bottom: 0;
    font-size: 0.82rem;
    color: var(--bs-secondary-color, #6c757d);
}



/* Toolbar size editors (margin/padding, border radius, box shadow): a tidy 2-column grid of labelled px
   inputs with consistent widths and top-aligned labels. Replaces the old unstyled floating-label markup
   (.dynamic-form-group had a JS handler but no CSS), where the inline label text pushed each input to a
   different size and left the labels misaligned. */
.loc-size-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}

.loc-size-field {
    min-width: 0;
}

.loc-size-field > label {
    display: block;
    margin-bottom: 0.15rem;
    font-size: 0.72rem;
    color: #6c757d;
    white-space: nowrap;
}

.loc-size-field .input-group-text {
    font-size: 0.72rem;
    padding: 0.15rem 0.4rem;
}

/* Drop the leading/trailing margins that CKEditor's <p>/<h2> add inside the title & caption, so the
   Gap control (not stray content margins) is what sets the space between the image and the text. */
.imgw .editable-content > :first-child {
    margin-top: 0;
}

.imgw .editable-content > :last-child {
    margin-bottom: 0;
}

/* Above / Below - stacked, image centered */
.imgw-pos-above .imgw-media,
.imgw-pos-below .imgw-media {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

.imgw-pos-below .imgw-media {
    flex-direction: column-reverse;
}

/* The image is centered to its own width, but the caption should fill the widget width instead of
   shrinking to its content - so there's room to read it, and to use CKEditor when editing it. */
.imgw-pos-above .imgw-caption,
.imgw-pos-below .imgw-caption {
    align-self: stretch;
}

/* Left / Right - image beside text (wraps/stacks when narrow) */
.imgw-pos-left .imgw-media,
.imgw-pos-right .imgw-media {
    display: flex;
    flex-wrap: wrap;
}

.imgw-pos-left .imgw-media {
    flex-direction: row;
}

.imgw-pos-right .imgw-media {
    flex-direction: row-reverse;
}

.imgw-pos-left .imgw-img,
.imgw-pos-right .imgw-img {
    flex: 0 0 auto;
}

.imgw-pos-left .imgw-caption,
.imgw-pos-right .imgw-caption {
    flex: 1 1 0;
    min-width: 0;
}

/* Vertical alignment of the image row (Left/Right) and of overlay text (Background) */
.imgw-valign-start .imgw-media {
    align-items: flex-start;
}

.imgw-valign-center .imgw-media {
    align-items: center;
}

.imgw-valign-end .imgw-media {
    align-items: flex-end;
}

/* Gap 0-5 between image and text */
.imgw-gap-1 .imgw-media { gap: 0.25rem; }
.imgw-gap-2 .imgw-media { gap: 0.5rem; }
.imgw-gap-3 .imgw-media { gap: 1rem; }
.imgw-gap-4 .imgw-media { gap: 1.5rem; }
.imgw-gap-5 .imgw-media { gap: 3rem; }

/* Background - image fills, caption overlaid */
.imgw-pos-bg .imgw-media {
    position: relative;
}

.imgw-pos-bg .imgw-img {
    width: 100% !important;
}

.imgw-pos-bg .imgw-caption {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    padding: 1rem;
}

.imgw-pos-bg.imgw-valign-start .imgw-caption { justify-content: flex-start; }
.imgw-pos-bg.imgw-valign-center .imgw-caption { justify-content: center; }
.imgw-pos-bg.imgw-valign-end .imgw-caption { justify-content: flex-end; }
.imgw-pos-bg.imgw-halign-start .imgw-caption { text-align: left; align-items: flex-start; }
.imgw-pos-bg.imgw-halign-center .imgw-caption { text-align: center; align-items: center; }
.imgw-pos-bg.imgw-halign-end .imgw-caption { text-align: right; align-items: flex-end; }

/* Optional dark overlay behind background text for legibility */
.imgw-pos-bg.imgw-overlay .imgw-caption::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    background: rgba(0, 0, 0, 0.4);
}

.imgw-pos-bg .imgw-caption > * {
    position: relative;
    z-index: 1;
}

/* Optional semi-transparent plate behind the caption text, so it stays legible over any image without
   darkening the whole picture the way the full overlay does. The plate is on the single inner text block
   (imgw-textblock), which the flex alignment shrinks to its content - so the WHOLE caption, however many
   lines, sits on ONE rounded panel rather than one highlight per line. Dark plate pairs with light text;
   light plate pairs with dark text. */
.imgw-pos-bg .imgw-textblock.imgw-textplate {
    padding: 0.5em 0.85em;
    border-radius: var(--bs-border-radius, 0.375rem);
}

.imgw-pos-bg .imgw-textblock.imgw-textplate-dark {
    background: rgba(0, 0, 0, 0.5);
}

.imgw-pos-bg .imgw-textblock.imgw-textplate-light {
    background: rgba(255, 255, 255, 0.6);
}

/* Edge inset: space between the overlay caption and the image edges. This is the caption's padding (it
   overrides the default 1rem above); the flex alignment then positions the text block within what's left. */
.imgw-pos-bg .imgw-caption.imgw-inset-none { padding: 0; }
.imgw-pos-bg .imgw-caption.imgw-inset-small { padding: 0.75rem; }
.imgw-pos-bg .imgw-caption.imgw-inset-medium { padding: 1.5rem; }
.imgw-pos-bg .imgw-caption.imgw-inset-large { padding: 3rem; }

/* Max text width: cap the caption block so a long line wraps into a tidy column instead of stretching the
   whole banner. The block still shrinks below the cap for short text, and below the cap on narrow phones
   (max-width never forces it wider than the space available), so it stays responsive. Full = no cap. */
.imgw-pos-bg .imgw-textblock.imgw-tw-wide { max-width: 48rem; }
.imgw-pos-bg .imgw-textblock.imgw-tw-medium { max-width: 32rem; }
.imgw-pos-bg .imgw-textblock.imgw-tw-narrow { max-width: 20rem; }

/* Fixed aspect ratio: the height scales with the width at every screen size (responsive), and the image
   fills it via object-fit:cover. For a background/banner the media box holds the ratio (so the caption can
   overlay it); for other positions the image itself is cropped to the ratio. The focal point below chooses
   which part of the photo stays in frame. */
.imgw-ar-wide { --imgw-ar: 21 / 9; }
.imgw-ar-standard { --imgw-ar: 16 / 9; }
.imgw-ar-banner { --imgw-ar: 3 / 1; }
.imgw-ar-classic { --imgw-ar: 4 / 3; }
.imgw-ar-square { --imgw-ar: 1 / 1; }

.imgw-ar.imgw-pos-bg .imgw-media {
    aspect-ratio: var(--imgw-ar);
}

.imgw-ar.imgw-pos-bg .imgw-img {
    width: 100% !important;
    height: 100% !important;
    object-fit: cover;
}

.imgw-ar:not(.imgw-pos-bg) .imgw-img {
    aspect-ratio: var(--imgw-ar);
    height: auto;
    object-fit: cover;
}

/* Focal point - which part of a cropped (object-fit:cover) image stays visible. */
.imgw-focal-top .imgw-img { object-position: top; }
.imgw-focal-bottom .imgw-img { object-position: bottom; }
.imgw-focal-left .imgw-img { object-position: left; }
.imgw-focal-right .imgw-img { object-position: right; }

/* Full-bleed banner: the image simply fills its row - the ROW is what breaks out of the page frame (see
   .rowcontent:has(.imgw-fullbleed) below). Every breakout on this page cancels the frame's own side padding
   rather than reaching for 100vw. That is what makes the builder canvas and the published page agree (100vw
   overshoots the inset canvas, so the builder had to switch the bleed off, leaving the banner narrower than
   the preview navbar), and what makes a max-width site stop the banner where the navbar stops instead of
   spilling to the viewport edge. */
.imgw-fullbleed {
    width: 100%;
    max-width: 100%;
    margin-left: 0;
    margin-right: 0;
}

/* Breakouts cancel the page padding exactly, so they should not overflow at all - this is a safety net for
   sub-pixel rounding on fractional zoom levels, kept because a stray horizontal scrollbar on a club's public
   site is worse than the risk of masking a layout bug here. x-axis only (clip, not hidden) so vertical page
   scroll and a behind-navbar pull-up still work. */
body:has(.imgw-fullbleed, .loc-row-band, .loc-row-fullbleed) {
    overflow-x: clip;
}

/* Full-width section band: the row stays boxed (content keeps the page's content width), but its background
   is painted edge-to-edge by a ::before layer behind the content. The colour/image arrive as CSS vars so the
   boxed row carries no background of its own. The pseudo is absolutely positioned, so it is NOT a flex item
   and never disturbs the row's columns. */
.rowcontent.loc-row-band {
    position: relative;
}

.rowcontent.loc-row-band::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(-1 * var(--loc-page-pad-side-eff, 1rem));
    right: calc(-1 * var(--loc-page-pad-side-eff, 1rem));
    background-color: var(--loc-rowbg-color, transparent);
    background-image: var(--loc-rowbg-image, none);
    background-size: cover;
    background-position: center;
    z-index: 0;
}

/* The band's content is lifted above the ::before layer that paints the background. Not the builder's
   row handle, though: it is absolutely positioned and already carries its own z-index, and this rule is
   specific enough (0,2,0) to override that - which turned it into an in-flow flex item that wrapped below
   the columns, so a banded row's handle appeared at its bottom edge instead of its top-right corner. */
.rowcontent.loc-row-band > *:not(.loc-chrome-handle) {
    position: relative;
    z-index: 1;
}

/* Full-width band with "stretch content": the whole row goes edge-to-edge - background inline on the row and
   the columns fill the viewport width.

   Every margin below is !important on purpose. BoxStyle emits ms-0/me-0/mt-0/mb-0 on EVERY row (0 means
   "no spacing", not "unset", so the box editor can always dial a value back to nothing) and Bootstrap's
   utilities are !important, so without this they would silently win. These are not spacing defaults the
   user is overriding - they are the layout mechanics of a full-bleed row, which cannot be expressed on a
   positive 0-5 scale at all. A row that bleeds edge-to-edge has no user margin by definition. */
.rowcontent.loc-row-fullbleed {
    margin-left: calc(-1 * var(--loc-page-pad-side-eff, 1rem)) !important;
    margin-right: calc(-1 * var(--loc-page-pad-side-eff, 1rem)) !important;
    background-size: cover;
    background-position: center;
}

/* Two full-width bands in a row butt together, so a background change reads as a real section break instead
   of showing a strip of body background between them. The negative top margin cancels the .website row gap
   for band-after-band only; a band next to a normal row keeps its normal gap. */
:is(.loc-row-band, .loc-row-fullbleed) + :is(.loc-row-band, .loc-row-fullbleed) {
    margin-top: calc(-1 * var(--loc-row-gap-eff, 1rem)) !important;
}

/* A full-bleed banner spans the whole page frame, so the frame must not inset it on any side.

   Sides: cancel the page's side padding, so the banner starts and ends exactly where the navbar does.
   Top/bottom: cancel the flex row gap, so no strip of body background shows above or below. The gap is
   between rows only - the first row has none above it and the last none below, so those two edges are the
   page padding instead, handled by the :first-child / :last-child rules below (both more specific, so they
   win on the side they set while inheriting the sides from here). */
.website > .rowcontent:has(.imgw-fullbleed) {
    margin-left: calc(-1 * var(--loc-page-pad-side-eff, 1rem)) !important;
    margin-right: calc(-1 * var(--loc-page-pad-side-eff, 1rem)) !important;
    margin-top: calc(-1 * var(--loc-row-gap-eff, 1rem)) !important;
    margin-bottom: calc(-1 * var(--loc-row-gap-eff, 1rem)) !important;
}

/* A full-width banner as the page's LAST row sits flush against the bottom of the content area. */
.website > .rowcontent:last-child:has(.imgw-fullbleed) {
    margin-bottom: calc(-1 * var(--loc-page-pad-bottom-eff, 1rem)) !important;
}

/* A full-width banner as the page's FIRST row sits flush against the top of the content area (right under
   the navbar) - the page's top padding above it is removed so there's no gap. :has scopes it to the first
   row only, so a full-width band lower down keeps its normal spacing. */
.website > .rowcontent:first-child:has(.imgw-fullbleed) {
    margin-top: calc(-1 * var(--loc-page-pad-top-eff, 1rem)) !important;
}

/* "Behind the navbar": the first-row banner extends up UNDER the navbar (pull past the navbar clearance
   too), and its text is padded down so it clears the bar. Best paired with a transparent navbar - a solid
   bar would simply cover the top of the banner. --loc-navbar-h is published on <body> by each layout. */
.website > .rowcontent:first-child:has(.imgw-fullbleed.imgw-behind-nav) {
    margin-top: calc(-1 * (var(--loc-page-pad-top-eff, 1rem) + var(--loc-navbar-h, 3.25rem))) !important;
}

.imgw-behind-nav.imgw-pos-bg .imgw-caption {
    padding-top: calc(var(--loc-navbar-h, 3.25rem) + 1rem);
}

/* Keep the builder's inline preview navbar above a banner that's pulled up flush/behind it. On the live
   site the real navbar is fixed-top (z-index 1030) so it already wins; in the builder the preview navbar is
   a normal-flow sibling, so without a stacking context the later banner would paint over (hide) it. */
#website-nav {
    position: relative;
    z-index: 3;
}

/* Hover effects */
.imgw-hover-zoom {
    transition: transform 0.25s ease;
}

.imgw-hover-zoom:hover {
    transform: scale(1.05);
}

.imgw-hover-fade {
    transition: opacity 0.25s ease;
}

.imgw-hover-fade:hover {
    opacity: 0.8;
}

/* Click behaviors */
.imgw-lightbox {
    cursor: zoom-in;
}

.imgw-link {
    display: inline-block;
    max-width: 100%;
}

/* Lightbox overlay */
.imgw-lightbox-overlay {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.85);
    cursor: zoom-out;
}

.imgw-lightbox-overlay img {
    max-width: 92%;
    max-height: 92%;
    box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
}

/* Stack image above text on phones for the side-by-side positions */
@media (max-width: 576px) {
    .imgw-pos-left .imgw-media,
    .imgw-pos-right .imgw-media {
        flex-direction: column;
    }

    .imgw-pos-left .imgw-img,
    .imgw-pos-right .imgw-img {
        width: 100% !important;
    }
}

/* Website builder frame: the content pane (.website-builder) reserves a 295px column on its right
   (margin-right: 295px) for the settings toolbar. The toolbar must be taken out of the flex flow and
   pinned into that reserved slot; otherwise it sits as a flex sibling and steals width from the pane,
   which prevents "Max" from filling the available space. */
/* Builder frame: a full-width flex row filling the viewport below the fixed navbar (height set in JS,
   Website.SizeBuilderFrame). The canvas area centers the (max-width-constrained) page canvas in the
   space between the left sidebar and the right toolbar; the toolbar is pinned to the right and can be
   collapsed (Website.ToggleToolBar). Canvas and toolbar scroll independently so both stay usable on a
   tall page. */
#website-builder-wrapper {
    position: relative;
}

/* Fills the gap between the sidebar and toolbar and centers the canvas within it. min-width: 0 lets it
   shrink so the canvas scrolls internally instead of widening the page. */
.builder-canvas-area {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    justify-content: center;
    overflow: hidden;
}

/* The page canvas; max-width is set per responsive preview button (Max/1280/768/400). Scrolls on its
   own (minus its 2px top margin) so the toolbar stays put while editing a tall page. */
.website-builder {
    height: calc(100% - 2px);
    overflow-y: auto;
}

/* Right-hand settings toolbar: fixed-width column pinned to the right, with its own scroll. */
#website-builder-toolbar {
    flex: 0 0 auto;
    max-height: 100%;
    overflow-y: auto;
    overflow-x: hidden; /* fixed-width settings column: never show a horizontal scrollbar */
}

.toolbar-expanded {
    width: 285px;
}

.toolbar-collapsed {
    width: 40px;
}

/* When the bar is collapsed to the 40px rail, only the collapse toggle remains; hide the action icons. */
/* The builder toolbar's two section frames - the website, and the page being edited. Grouping rather than
   decoration: the icons alone gave no clue that one set republishes the whole site and the other changes a
   single page.

   Collapsed, everything inside the site frame is hidden except the collapse toggle, so the border comes off
   with it - a box drawn around one button reads as a mistake rather than a group. */
.toolbar-collapsed .toolbar-frame {
    border: 0 !important;
    padding: 0 !important;
    margin: 0 !important;
}

.toolbar-collapsed .toolbar-action {
    display: none;
}

/* -----------------------------------------
   SECTION HEADING
   Replaces nested "cards within a card". Introduces a sub-section inside a single card with
   a bold title and a brand-coloured divider. It's a flex row, so it can also carry inline
   controls (filter buttons, totals) to the right of the title. Everything is driven by
   --bs-* variables, so it tracks the user's theme and light/dark automatically. Swap the
   border colour to var(--bs-border-color) if you'd prefer a neutral divider over the accent. */
.section-heading {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 1.25rem 0 0.75rem;
    padding-bottom: 0.4rem;
    font-weight: 600;
    color: var(--bs-emphasis-color);
    border-bottom: 2px solid var(--bs-primary);
}

/* A section heading that opens a card sits flush under the card header. */
.card-body > .section-heading:first-child {
    margin-top: 0;
}

/* -----------------------------------------
   SELECT2 -> match the themed Bootstrap 5 .form-control
   Select2 4.x ships a Bootstrap-3-era look (28px tall, square corners). These rules make the
   selection box, focus ring, dropdown and search field track .form-control via the theme's
   own --bs-* variables (rounded corners, same border/height/colours). Targets the default
   Select2 theme, so no JS `theme:` option or extra dependency is needed. */
.select2-container--default .select2-selection--single {
    height: calc(1.5em + 0.75rem + 2px); /* match .form-control */
    padding: 0.375rem 0.75rem;
    display: flex;
    align-items: center;
    color: var(--bs-body-color);
    background-color: var(--bs-body-bg);
    border: var(--bs-border-width, 1px) solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
    padding: 0;
    line-height: 1.5;
    color: var(--bs-body-color);
}
.select2-container--default .select2-selection--single .select2-selection__placeholder {
    color: var(--bs-secondary-color);
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
    top: 0;
    right: 0.5rem;
    height: 100%;
}

/* focus / open state — mirror .form-control:focus */
.select2-container--default.select2-container--focus .select2-selection--single,
.select2-container--default.select2-container--open .select2-selection--single,
.select2-container--default.select2-container--focus .select2-selection--multiple {
    border-color: var(--bs-primary);
    box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25);
    outline: 0;
}

/* multi-select box (tags) */
.select2-container--default .select2-selection--multiple {
    /* Grow to fit the tag chips instead of being clamped to the fixed 35px height that the
       generic .select2-selection rule sets for single-selects - otherwise wrapped chips
       overflow the box border. min-height keeps it aligned with a normal input when empty. */
    height: auto;
    min-height: calc(1.5em + 0.75rem + 2px);
    padding: 0.1875rem 0.5rem;
    color: var(--bs-body-color);
    background-color: var(--bs-body-bg);
    border: var(--bs-border-width, 1px) solid var(--bs-border-color);
    border-radius: var(--bs-border-radius);
}
.select2-container--default .select2-selection--multiple .select2-selection__choice {
    border-radius: var(--bs-border-radius-sm);
}

/* dropdown panel, search box and options */
.select2-dropdown {
    color: var(--bs-body-color);
    background-color: var(--bs-body-bg);
    border-color: var(--bs-border-color);
    border-radius: var(--bs-border-radius);
}
.select2-container--default .select2-search--dropdown .select2-search__field {
    color: var(--bs-body-color);
    background-color: var(--bs-body-bg);
    border: var(--bs-border-width, 1px) solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-sm);
}
.select2-container--default .select2-results__option--highlighted[aria-selected] {
    background-color: var(--bs-primary);
    color: #fff;
}

/* -----------------------------------------
   CHECKBOX FIELD (label-on-top)
   The label sits ABOVE the control like every other field, so checkbox fields line up
   with text/number fields in adjacent columns. The checkbox itself lives inside a
   bordered .form-control box of the same height:

       <div class="col-12">
           <label class="control-label">...</label>
           <div class="form-control checkbox-control">
               <input type="checkbox" class="checkbox" />
           </div>
           <p class="form-text">optional helper text</p>
       </div>
   ----------------------------------------- */
/* Compound .form-control.checkbox-control selector (specificity 0,2,0) so display:flex
   wins over Bootstrap/theme's single-class .form-control { display: block }. */
.form-control.checkbox-control {
    display: flex;
    align-items: center;
    justify-content: flex-end; /* checkbox sits at the right-hand end of the box */
    min-height: calc(1.5em + 0.75rem + 2px); /* match .form-control height */
}

/* The checkbox itself: a normal, themed control sitting inside the bordered box.
   (It no longer carries .form-control, so nothing forces appearance:none on it.) */
input[type="checkbox"].checkbox {
    flex: 0 0 auto;
    width: 1.25em;
    height: 1.25em;
    min-width: auto;
    min-height: auto;
    margin: 0;
    padding: 0;
    /* .form-control sets appearance:none, which suppresses the native checkbox glyph and left
       it invisible. Restore native rendering and theme it with the primary colour. */
    -webkit-appearance: auto;
    -moz-appearance: auto;
    appearance: auto;
    box-shadow: none;
    accent-color: var(--bs-primary);
    cursor: pointer;
}

/* -----------------------------------------
   BUTTONS (no global width override)
   ----------------------------------------- */

.btn-fixed {
    max-width: 400px;
}

.btn-full {
    width: 100%;
}

.btn-inline-control {
    margin-top: 25px;
}

/* -----------------------------------------
   BOOKING BUTTONS
   ----------------------------------------- */

.btn-member,
.btn-guest {
    max-width: 300px;
    min-width: 200px;
    margin-top: 2px;
}

@media (max-width: 500px) {
    .btn-member,
    .btn-guest {
        width: 100%;
        max-width: 100%;
    }
}

@media (min-width: 500px) {
    .btn-member,
    .btn-guest {
        max-width: 48%;
        min-width: 48%;
    }
}

/* -----------------------------------------
   SELECT2 (kept, cleaned)
   ----------------------------------------- */

.select2 {
    width: 100%;
    min-width: 200px;
}

.select2-selection {
    height: 35px;
}

.select2-selection__rendered {
    line-height: 32px;
    text-align: left;
}

.select2-selection__placeholder {
    color: var(--bs-secondary-color) !important;
}

/* -----------------------------------------
   CARDS & LAYOUT
   ----------------------------------------- */

.card {
    border-radius: var(--bs-border-radius);
}

.box-shadow {
    box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.05);
}

.card-header-primary {
    background-color: var(--bs-primary) !important;
    color: var(--bs-body-color);
}

/* -----------------------------------------
   TABLES
   ----------------------------------------- */

.table-striped tbody tr:nth-of-type(odd) {
    background-color: var(--bs-secondary-bg);
}

/* -----------------------------------------
   BADGES
   ----------------------------------------- */

.badge {
    background-color: var(--bs-primary);
    color: var(--bs-white);
}

/* -----------------------------------------
   SIDEBAR
   ----------------------------------------- */
/* Expanded */
.sidebar-expanded {
    width: 250px;
    min-width: 250px;
    max-width: 250px;
}

/* Collapsed */
.sidebar-collapsed {
    width: 50px;
    min-width: 50px;
    max-width: 50px;
}

/* ---- Syncfusion EJ2 grid integration ---- */

/* The SPA content column (.main-col) is a flex item; flex items won't shrink below their
   content width by default (min-width: auto), so a wide grid widens the whole page.
   min-width: 0 lets it shrink so the grid scrolls horizontally inside itself. */
.main-col {
    min-width: 0;
}

/* EJ2 pager: give the number buttons and the nav arrows a uniform height. The theme
   balances them with padding tuned to a specific font size; the app's base font size
   throws that off, making the number buttons taller than the arrows. */
.e-grid .e-pager .e-numericitem,
.e-grid .e-pager .e-currentitem,
.e-grid .e-pager .e-numericitem.e-currentitem,
.e-grid .e-pager .e-pagercontainer .e-firstpage,
.e-grid .e-pager .e-pagercontainer .e-prevpage,
.e-grid .e-pager .e-pagercontainer .e-nextpage,
.e-grid .e-pager .e-pagercontainer .e-lastpage,
.e-grid .e-pager .e-pagercontainer .e-firstpagedisabled,
.e-grid .e-pager .e-pagercontainer .e-prevpagedisabled,
.e-grid .e-pager .e-pagercontainer .e-nextpagedisabled,
.e-grid .e-pager .e-pagercontainer .e-lastpagedisabled {
    height: 36px;
    line-height: 36px;
    padding-top: 0;
    padding-bottom: 0;
    box-sizing: border-box;
}

/* More compact grid rows (the theme's default cell padding is a bit tall). The td/th
   element selectors give this higher specificity than the theme so it wins regardless
   of stylesheet load order. */
.e-grid td.e-rowcell,
.e-grid th.e-headercell {
    padding-top: 3px;
    padding-bottom: 3px;
}

/* Active | All scope toggle (createListGrid activeToggle) sits in the grid toolbar next to
   New; keep it vertically centred and give it a little breathing room. */
.e-grid .e-toolbar .loc-active-toggle {
    margin: 0 6px;
    vertical-align: middle;
}

/* Quick filter (createListGrid quickFilter) - the single-select that replaced the strip of
   one-button-per-category above these grids. Sized to its content rather than the toolbar item,
   because .form-select is width:100% and would otherwise stretch across the toolbar; capped so a
   long club-authored name cannot push the search box off the end. */
.e-grid .e-toolbar .loc-quick-filter {
    display: inline-block;
    width: auto;
    max-width: 14rem;
    margin: 0 6px;
    vertical-align: middle;
}

/* Selected | All scope toggle (createPickerGrid); same treatment as the Active | All one. */
.e-grid .e-toolbar .loc-pick-scope {
    margin: 0 6px;
    vertical-align: middle;
}

/* Picker grid (createPickerGrid): the chosen rows. The tick in the first column is the primary
   indicator; this tints the whole row so the selection reads at a glance from across the grid.
   .e-gridcontent is in the selector to outrank the theme's own .e-grid .e-altrow .e-rowcell,
   which is otherwise more specific and leaves every second chosen row untinted. */
.e-grid .e-gridcontent tr.loc-picked td.e-rowcell {
    background-color: rgba(13, 110, 253, 0.09);
}

/* -----------------------------------------
   ADMIN SHELL - FIXED NAVBAR OFFSET
   The Admin top navbar is position:fixed (out of flow), so the content column must reserve
   space for it. The offset lives in this CSS variable rather than being applied as an inline
   margin from window.onload (the old approach kept the page hidden until the load event and
   produced a visible jump). The default below (~52px: 40px brand logo + the navbar's vertical
   padding) is correct for the common single-row navbar; the layout's DOM-ready script refines
   --loc-navbar-h from the real height before revealing the page, so content still clears the
   bar if the menu wraps to two rows on narrow desktops. Scoped to .loc-admin-shell so it
   doesn't affect the other layouts (which still set their own offset in JS for now).
   ----------------------------------------- */
.loc-admin-shell {
    --loc-navbar-h: 3.25rem;
}

.loc-admin-shell main {
    padding-top: var(--loc-navbar-h);
}

/* The website builder renders its own in-flow navbar inside .website-builder, so its <main> must NOT also
   take the admin shell's fixed-navbar offset above - that added a ~navbar-height gap between the builder's
   nav and the first row. Scoped higher than the rule above so it wins regardless of stylesheet order. */
.loc-admin-shell .website-builder main {
    padding-top: 0;
}

/* -----------------------------------------
   WEB SHELL (MEMBER / VISITOR) - FIXED NAVBAR OFFSET
   These layouts use a website-themed position:fixed navbar whose height varies at runtime
   (Website.NavbarAutoCollapse toggles the expand state to fit the brand + items). The content
   container reserves space for it via --loc-navbar-h, which each layout's DOM-ready script
   sets from the real navbar height and updates on resize. The fallback below (~52px) keeps
   the layout sane before the script runs or if it fails.
   ----------------------------------------- */
.loc-shell-main {
    padding-top: var(--loc-navbar-h, 3.25rem);
}

/* -----------------------------------------
   SITE CHROME: THE STRIP ABOVE THE NAVBAR
   -----------------------------------------
   .loc-chrome wraps the top bar and the navigation bar so the two can be fixed to the viewport as ONE
   element. It carries fixed-top only when there is a strip to show; with no strip the bar keeps fixed-top
   itself and this wrapper is an inert div, so a site without a top bar is positioned exactly as before.

   No styling of its own beyond that on purpose - the strip's own look is a Section, built from rows and
   columns like any other content, so its background and spacing come from the club's theme and whatever
   Element Style the row carries. Anything painted here would be a second opinion about that. */
.loc-chrome.fixed-top {
    /* Bootstrap's .fixed-top already pins it; this only settles the stacking against the strip's own
       content, which can carry shadows and dropdowns of its own. */
    z-index: 1030;
}

/* A UTILITY BAR IS DESKTOP FURNITURE.
   Hidden below Bootstrap's md breakpoint - the same width at which the navbar collapses to a burger. On a
   phone this strip is competing for the space the site can least afford, and what it holds (social links,
   a phone number, opening hours) is already reachable from the menu and the footer.

   display: none and not visibility/height: the rows inside it are a grid and would still claim their
   tracks, and --loc-navbar-h is measured from the bar's rendered position - a strip that is merely
   invisible would still push the navigation bar down the screen by its full height. */
@media (max-width: 767.98px) {
    .loc-topbar {
        display: none;
    }
}

/* A DEFAULT GROUND, and the one thing painted here. The strip is fixed to the viewport, so with no
   background of its own the page would scroll visibly underneath it. The Section paints over this the
   moment a row carries a background or a style, so it costs a club nothing to override and saves every
   club that has not thought about it yet. */
.loc-topbar {
    background: var(--bs-body-bg, #fff);
}

/* The strip's rows sit against the top of the viewport, where a page's rows never do. Page Padding is a
   page-level inset and this is not a page, so the vertical padding that ShowContent's wrapper would
   otherwise inherit is dropped and the strip is sized by its own row spacing. */
.loc-topbar .website {
    padding-top: 0;
    padding-bottom: 0;
}

/* -----------------------------------------
   MODALS
   ----------------------------------------- */
/* Overlay modals opened on top of the base #modal need a higher z-index than Bootstrap's
   default modal (1055) so they stack above it rather than appearing behind. */
.loc-modal-top {
    z-index: 10000;
}

/* Let the modal scroll when its content is taller than the viewport. */
.loc-modal-scroll {
    overflow: auto;
}

/* Modals default to modal-dialog-scrollable (set in the layouts): the header/footer stay pinned and only
   the body scrolls, so Save/Cancel are always reachable. Safety net: modal-dialog-scrollable clips content
   via .modal-content { overflow: hidden }, which would hide overflow in any modal that has NO .modal-body
   to scroll. For those, let the content itself scroll instead of being clipped. (:has is ignored by older
   browsers, which simply fall back to Bootstrap's behaviour.) */
.modal-dialog-scrollable > .modal-content:not(:has(.modal-body)) {
    overflow-y: auto;
}

/* The other way a modal can defeat that layout: wrapping its header, body and footer in a <form>.
   SimpleBookingModal does, so the whole modal can be serialised in one call, and it is the only view that
   does - but the effect was severe enough to be worth a rule.

   modal-dialog-scrollable is a FLEX CHAIN: .modal-content is the flex column, and .modal-body is the
   flexible child that takes the leftover height and scrolls. A plain block in between breaks the chain -
   the body is never given a bounded height, so it never scrolls, and .modal-content's own overflow:hidden
   then CLIPS whatever did not fit. On a phone that put the footer, with Book and Close in it, below the
   bottom of the screen and unreachable: nothing scrolled, so there was no way down to them.

   Making the form a flex column of its own restores the chain. min-height:0 is the part that actually
   matters - a flex item will not shrink below its content's intrinsic height without it, so the body would
   still refuse to scroll however the rest was set. */
.modal-dialog-scrollable > .modal-content > form {
    display: flex;
    flex-direction: column;
    min-height: 0;
    max-height: 100%;
    width: 100%;
}

.modal-dialog-scrollable > .modal-content > form > .modal-body {
    min-height: 0;
    overflow-y: auto;
}

/* The booking modal's Type tab carried an inline min-height: 400px, so the dialog would not resize when
   switching between Type and Players. That is worth having on a desktop dialog sized to its content; on a
   phone the modal is already modal-fullscreen and a fixed height floor, so the 400px bought nothing and
   cost exactly the room the footer needed. Kept from sm up, dropped below it. */
@media (min-width: 576px) {
    .loc-booking-tab {
        min-height: 400px;
    }
}

/* A news story beside its image gallery. Capped in rem rather than the old fixed 415px, which
   clipped: the markup asked for a "scrolling-div", a class defined nowhere in this stylesheet,
   so nothing ever scrolled.

   The cap applies only from xl, where the story and the slider are side by side and one column
   running long would leave the other stranded beside white space. Below xl the columns stack and
   the story simply flows - a scroll region inside a page that already scrolls is miserable on a
   phone, and phones now get this view rather than a hand-written mobile one. */
@media (min-width: 1200px) {
    .news-story {
        max-height: 26rem;
        overflow-y: auto;
    }
}

/* Nothing under a story should be able to push the page wider than its column - news bodies are
   authored in CKEditor and routinely carry pasted images and tables with intrinsic widths. */
.news-story img,
.news-story video,
.news-story iframe,
.news-story table {
    max-width: 100%;
}

/* ============================================================================
   Image galleries — replaces the old Unite Gallery (jQuery, 2017) with plain
   CSS layouts + a small home-grown lightbox. Three layouts share one base:
   grid (uniform tiles), masonry (natural aspect ratios), slider (scroll-snap).
   Column count and gap come from CSS vars set inline by the renderer.
   ============================================================================ */
.loc-gallery {
    --loc-gallery-cols: 3;
    --loc-gallery-gap: 0.5rem;
    --loc-gallery-ratio: 4 / 3;
}

.loc-gallery-item {
    position: relative;
    overflow: hidden;
    margin: 0;
    border-radius: var(--bs-border-radius, 0.375rem);
}

.loc-gallery--clickable .loc-gallery-item {
    cursor: pointer;
}

/* The second selector is the LINKED tile: an <a> sits between the figure and the image (see
   CreateGalleryElement), so a child combinator that stopped at the figure would drop the sizing and every
   linked tile would render at the image's natural size. Stated rather than loosened to a descendant
   selector, which would also catch the lightbox's own img. */
.loc-gallery-item > img,
.loc-gallery-item > .loc-gallery-link > img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* The anchor is a pass-through: it fills the tile so the image below it still has a box to fill, and takes
   no colour, underline or focus ring of its own - the thing being clicked is the picture. It keeps the
   keyboard focus outline, which is the one piece of anchor chrome that earns its place. */
.loc-gallery-item > .loc-gallery-link {
    display: block;
    width: 100%;
    height: 100%;
    color: inherit;
    text-decoration: none;
}

.loc-gallery-item > video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    background: #000;
}

.loc-gallery--masonry .loc-gallery-item > video {
    height: auto;
}

/* Play badge on a video tile. A video shows its poster frame until it is played, so without this a video
   is indistinguishable from a photo - and stays that way if the browser cannot play the file at all.
   pointer-events are off so the badge never steals a click from the player's own controls, and it fades
   out once playback starts. */
.loc-gallery-play {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 54px;
    height: 54px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    font-size: 22px;
    line-height: 54px;
    text-align: center;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

.loc-gallery-item--video:hover .loc-gallery-play {
    background: rgba(0, 0, 0, 0.75);
}

/* Once the member starts the clip the badge is just in the way. Driven by the play / pause / ended
   listeners in CreateGalleryElement - playback state is a DOM property, not an attribute, so there is no
   selector for it. */
.loc-gallery-item--video.is-playing .loc-gallery-play {
    opacity: 0;
}

/* Caption ribbon — hidden until hover, or pinned on when "always visible". */
.loc-gallery-caption {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0;
    padding: 0.4rem 0.6rem;
    font-size: 0.85rem;
    line-height: 1.25;
    color: #fff;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0));
    opacity: 0;
    transition: opacity 0.2s ease;
    pointer-events: none;
}

.loc-gallery-item:hover .loc-gallery-caption,
.loc-gallery--captions-on .loc-gallery-caption {
    opacity: 1;
}

/* Subtle zoom on hover for clickable tiles. */
.loc-gallery--clickable .loc-gallery-item > img {
    transition: transform 0.25s ease;
}

.loc-gallery--clickable .loc-gallery-item:hover > img {
    transform: scale(1.04);
}

/* The same invitation for a LINKED tile, but only on the tiles that actually carry a link. A gallery set to
   Link is usually part sponsors and part filler, and the point of keying off .loc-gallery-link rather off
   the container is that a tile with nowhere to go says so by not responding. */
.loc-gallery--linked .loc-gallery-link {
    cursor: pointer;
}

.loc-gallery--linked .loc-gallery-link > img {
    transition: transform 0.25s ease;
}

.loc-gallery--linked .loc-gallery-link:hover > img {
    transform: scale(1.04);
}

/* --- Uniform grid ------------------------------------------------------- */
.loc-gallery--grid {
    display: grid;
    grid-template-columns: repeat(var(--loc-gallery-cols), minmax(0, 1fr));
    gap: var(--loc-gallery-gap);
}

.loc-gallery--grid .loc-gallery-item {
    aspect-ratio: var(--loc-gallery-ratio);
}

/* --- Masonry (natural aspect ratios, CSS columns) ----------------------- */
.loc-gallery--masonry {
    column-count: var(--loc-gallery-cols);
    column-gap: var(--loc-gallery-gap);
}

.loc-gallery--masonry .loc-gallery-item {
    break-inside: avoid;
    margin-bottom: var(--loc-gallery-gap);
}

.loc-gallery--masonry .loc-gallery-item > img,
.loc-gallery--masonry .loc-gallery-item > .loc-gallery-link > img {
    height: auto;
}

.loc-gallery--masonry .loc-gallery-item > .loc-gallery-link {
    height: auto;
}

/* --- Slider (native scroll-snap; JS only drives buttons/dots/autoplay) --- */
.loc-gallery-slider {
    position: relative;
}

.loc-gallery--slider {
    display: flex;
    gap: var(--loc-gallery-gap);
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none; /* Firefox */
}

.loc-gallery--slider::-webkit-scrollbar {
    display: none;
}

.loc-gallery--slider .loc-gallery-item {
    flex: 0 0 calc((100% - (var(--loc-gallery-cols) - 1) * var(--loc-gallery-gap)) / var(--loc-gallery-cols));
    scroll-snap-align: center;
    aspect-ratio: var(--loc-gallery-ratio);
}

.loc-gallery-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.4rem;
    height: 2.4rem;
    padding: 0;
    border: none;
    border-radius: 50%;
    color: #fff;
    background: rgba(0, 0, 0, 0.45);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    transition: background 0.2s ease;
}

.loc-gallery-nav:hover {
    background: rgba(0, 0, 0, 0.7);
}

.loc-gallery-prev {
    left: 0.5rem;
}

.loc-gallery-next {
    right: 0.5rem;
}

.loc-gallery-dots {
    display: flex;
    justify-content: center;
    gap: 0.35rem;
    margin-top: 0.5rem;
}

.loc-gallery-dot {
    width: 0.55rem;
    height: 0.55rem;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.25);
    cursor: pointer;
    transition: background 0.2s ease;
}

.loc-gallery-dot.is-active {
    background: var(--bs-primary, #0d6efd);
}

/* On phones, grid/masonry collapse toward fewer columns and the slider shows
   one image at a time regardless of the configured count. */
@media (max-width: 575.98px) {
    .loc-gallery--grid,
    .loc-gallery--masonry {
        --loc-gallery-cols: 2;
    }

    .loc-gallery--slider {
        --loc-gallery-cols: 1;
    }
}

/* --- Lightbox (home-grown; opened on click when the gallery is clickable) -- */
.loc-lightbox {
    position: fixed;
    inset: 0;
    z-index: 20000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.9);
    opacity: 0;
    transition: opacity 0.2s ease;
    touch-action: pan-y;
}

.loc-lightbox.is-open {
    opacity: 1;
}

.loc-lightbox-figure {
    margin: 0;
    max-width: 92vw;
    max-height: 92vh;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.loc-lightbox-figure img {
    max-width: 92vw;
    max-height: 84vh;
    object-fit: contain;
    border-radius: 0.25rem;
    box-shadow: 0 0.5rem 2rem rgba(0, 0, 0, 0.5);
}

.loc-lightbox-caption {
    margin-top: 0.6rem;
    max-width: 92vw;
    color: #fff;
    text-align: center;
    font-size: 0.9rem;
}

.loc-lightbox-btn {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    color: #fff;
    background: rgba(255, 255, 255, 0.12);
    cursor: pointer;
    transition: background 0.2s ease;
}

.loc-lightbox-btn:hover {
    background: rgba(255, 255, 255, 0.28);
}

.loc-lightbox-nav {
    top: 50%;
    transform: translateY(-50%);
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    font-size: 2rem;
    line-height: 1;
}

.loc-lightbox-prev {
    left: 1rem;
}

.loc-lightbox-next {
    right: 1rem;
}

.loc-lightbox-close {
    top: 1rem;
    right: 1rem;
    width: 2.6rem;
    height: 2.6rem;
    border-radius: 50%;
    font-size: 1.75rem;
    line-height: 1;
}

.loc-lightbox-counter {
    position: absolute;
    top: 1.2rem;
    left: 1.2rem;
    color: rgba(255, 255, 255, 0.75);
    font-size: 0.85rem;
}

@media (max-width: 575.98px) {
    .loc-lightbox-nav {
        width: 2.4rem;
        height: 2.4rem;
        font-size: 1.5rem;
    }

    .loc-lightbox-prev {
        left: 0.35rem;
    }

    .loc-lightbox-next {
        right: 0.35rem;
    }
}

/* --- Admin gallery management grid (replaces Unite Gallery in image.js) --- */
.loc-admin-gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 0.5rem;
    margin-top: 1rem;
}

.loc-admin-gallery .loc-gallery-item {
    aspect-ratio: 1 / 1;
    cursor: pointer;
    border: 1px solid var(--bs-border-color, #dee2e6);
}

.loc-admin-gallery .loc-gallery-item:hover {
    outline: 2px solid var(--bs-primary, #0d6efd);
    outline-offset: -2px;
}

/* --- Weather widget (Open-Meteo) ------------------------------------------ */
.loc-weather {
    width: 100%;
}

.weather-message {
    text-align: center;
    margin: 0.25rem 0 0.5rem;
}

.weather-message-text {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--bs-body-color, #333);
}

.weather-message-date {
    margin: 0.1rem 0 0;
    font-size: 0.72rem;
    color: #888;
}

.weather-body {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: stretch;
}

.weather-body > .weather-forecast {
    flex: 1 1 280px;
    min-width: 0;
}

.weather-body > .weather-webcam {
    flex: 1 1 280px;
    min-width: 0;
}

.weather-card {
    border-radius: var(--bs-border-radius, 0.75rem);
    overflow: hidden;
    box-shadow: 0 0.4rem 1.2rem rgba(0, 0, 0, 0.12);
    background: var(--bs-body-bg, #fff);
    height: 100%;
}

.weather-loading,
.weather-empty {
    padding: 1.5rem;
    text-align: center;
    color: #6c757d;
    border: 1px dashed #ced4da;
    border-radius: 0.5rem;
}

/* Hero: current conditions on a condition-based gradient. */
.weather-hero {
    padding: 1.25rem 1rem 1.4rem;
    color: #fff;
    text-align: center;
    background: linear-gradient(135deg, #6b7b8c, #9aa7b4);
}

.weather-hero-location {
    font-size: 0.85rem;
    font-weight: 600;
    opacity: 0.9;
    margin-bottom: 0.25rem;
}

.weather-hero-main {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
}

.weather-hero-icon {
    font-size: 3.25rem;
    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
}

.weather-hero-temp {
    font-size: 3.5rem;
    font-weight: 700;
    line-height: 1;
}

.weather-hero-label {
    font-size: 1rem;
    font-weight: 600;
    margin-top: 0.15rem;
}

.weather-hero-meta {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-top: 0.7rem;
}

.weather-hero-meta span {
    background: rgba(255, 255, 255, 0.18);
    border-radius: 999px;
    padding: 0.15rem 0.6rem;
    font-size: 0.8rem;
    white-space: nowrap;
}

.weather-hero--clear  { background: linear-gradient(135deg, #2f80ed, #56ccf2); }
.weather-hero--partly { background: linear-gradient(135deg, #4a90d9, #8fc3e8); }
.weather-hero--clouds { background: linear-gradient(135deg, #5c6b7a, #93a2b0); }
.weather-hero--rain   { background: linear-gradient(135deg, #3a6073, #16222a); }
.weather-hero--snow   { background: linear-gradient(135deg, #5b8bb0, #a9cbe3); }
.weather-hero--storm  { background: linear-gradient(135deg, #3d3a5c, #29253f); }

/* Visitor format toggle (segmented pill control). */
.weather-toggle {
    display: flex;
    justify-content: center;
    gap: 0.25rem;
    padding: 0.6rem 0.75rem 0;
}

.weather-toggle-btn {
    border: 1px solid var(--bs-border-color, #dee2e6);
    background: transparent;
    color: #6c757d;
    font-size: 0.75rem;
    font-weight: 600;
    padding: 0.2rem 0.7rem;
    border-radius: 999px;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.weather-toggle-btn:hover {
    background: rgba(0, 0, 0, 0.04);
}

.weather-toggle-btn.is-active {
    background: var(--bs-primary, #0d6efd);
    border-color: var(--bs-primary, #0d6efd);
    color: #fff;
}

/* Week format: each day is a section with its daytime (6am-10pm) hourly breakdown. */
.weather-week {
    padding: 0.25rem 0.5rem 0.5rem;
}

.weather-whday {
    padding: 0.35rem 0;
    border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}

.weather-whday:last-child {
    border-bottom: none;
}

.weather-whday-head {
    font-size: 0.85rem;
    font-weight: 700;
    padding: 0.15rem 0.25rem 0.1rem;
}

.weather-whday-date {
    font-weight: 400;
    color: #8a94a0;
    font-size: 0.75rem;
    margin-left: 0.25rem;
}

/* In the Week view each day's hourly row also fills the full width (equal columns),
   inheriting the base .weather-hour flex sizing. */
.weather-whday .weather-hourly {
    padding: 0.4rem 0.25rem 0.25rem;
}

.weather-icon--clear  { color: #f5a623; }
.weather-icon--partly { color: #5a9bd4; }
.weather-icon--clouds { color: #8a94a0; }
.weather-icon--rain   { color: #4a90d9; }
.weather-icon--snow   { color: #7cc0e8; }
.weather-icon--storm  { color: #7b6fb0; }

/* Hourly format: equal-width columns filling the full width. */
.weather-hourly {
    display: flex;
    gap: 0.35rem;
    padding: 0.6rem 0.75rem 0.75rem;
}

.weather-hour {
    flex: 1 1 0;
    min-width: 0;
    text-align: center;
    padding: 0.35rem 0.15rem;
    border-radius: 0.5rem;
}

.weather-hour:hover {
    background: rgba(0, 0, 0, 0.04);
}

.weather-hour-time {
    font-size: 0.72rem;
    color: #6c757d;
    font-weight: 600;
}

.weather-hour-icon {
    font-size: 1.15rem;
    margin: 0.25rem 0;
}

.weather-hour-temp {
    font-size: 0.95rem;
    font-weight: 700;
}

.weather-hour-wind {
    font-size: 0.68rem;
    color: #7fa8c9;
    margin-top: 0.12rem;
    white-space: nowrap;
}

.weather-hour-wind i {
    font-size: 0.58rem;
}

.weather-hour-rain {
    font-size: 0.7rem;
    color: #8a94a0;
    margin-top: 0.15rem;
    white-space: nowrap;
}

.weather-hour-rain i {
    font-size: 0.6rem;
}

.weather-hour-rain.is-wet {
    color: #4a90d9;
    font-weight: 600;
}

/* Chart format: SVG temperature curve + hourly rain-probability bars. */
.weather-chart-wrap {
    padding: 0.5rem 0.75rem 0.75rem;
}

.weather-chart-legend {
    display: flex;
    gap: 1rem;
    justify-content: center;
    font-size: 0.72rem;
    color: #6c757d;
    margin-bottom: 0.15rem;
}

.weather-chart-legend .temp i {
    color: #f2994a;
}

.weather-chart-legend .rain i {
    color: #4a90d9;
}

.weather-chart {
    width: 100%;
    height: auto;
    display: block;
    overflow: visible;
}

.weather-chart-area {
    fill: rgba(242, 153, 74, 0.18);
}

.weather-chart-line {
    stroke: #f2994a;
    stroke-width: 2.5;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.weather-chart-rain {
    fill: rgba(74, 144, 217, 0.6);
}

.weather-chart-baseline {
    stroke: rgba(0, 0, 0, 0.15);
    stroke-width: 1;
}

.weather-chart-rainlabel {
    fill: #4a90d9;
    font-size: 11px;
    font-weight: 600;
    text-anchor: middle;
}

.weather-chart-xlabel {
    fill: #8a94a0;
    font-size: 13px;
    text-anchor: middle;
}

.weather-chart-tlabel {
    fill: #495057;
    font-size: 13px;
    font-weight: 600;
    text-anchor: middle;
}

/* Webcam cross-fade cycler: all images stacked in one grid cell, opacity fades. */
.weather-webcam-stage {
    display: grid;
    border-radius: var(--bs-border-radius, 0.75rem);
    overflow: hidden;
    box-shadow: 0 0.4rem 1.2rem rgba(0, 0, 0, 0.12);
    height: 100%;
}

.weather-webcam-stage img {
    grid-area: 1 / 1;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 1s ease;
}

.weather-webcam-stage img.is-active {
    opacity: 1;
}

@media (prefers-color-scheme: dark) {
    .weather-whday { border-bottom-color: rgba(255, 255, 255, 0.08); }
    .weather-hour:hover { background: rgba(255, 255, 255, 0.06); }
    .weather-toggle-btn:hover { background: rgba(255, 255, 255, 0.06); }
    .weather-chart-baseline { stroke: rgba(255, 255, 255, 0.18); }
    .weather-chart-xlabel { fill: #adb5bd; }
    .weather-chart-tlabel { fill: #dee2e6; }
}

/* --- Video widget --------------------------------------------------------- */
.vidw-video {
    display: block;
    width: 100%;
    height: auto;
    background: #000;
    border-radius: var(--bs-border-radius, 0.375rem);
}

.vidw-empty {
    padding: 2rem;
    text-align: center;
    color: #6c757d;
    background: #f8f9fa;
    border: 1px dashed #ced4da;
    border-radius: 0.375rem;
}

/* --- Flat selectable image grid ------------------------------------------
   Replaces Unite Gallery on the remaining admin/member/picker screens whose
   markup is just a container of bare <img> children (logo/video pickers,
   Google/YAY image search, member images, copy-move image selection). Direct
   <img> children become responsive square tiles; .loc-selected marks a picked
   tile in multi-select pickers. */
.loc-image-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 0.5rem;
    margin: 0.5rem 0;
}

.loc-image-grid img {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    cursor: pointer;
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: 0.375rem;
}

.loc-image-grid img:hover {
    outline: 2px solid var(--bs-primary, #0d6efd);
    outline-offset: -2px;
}

.loc-image-grid img.loc-selected {
    outline: 3px solid var(--bs-primary, #0d6efd);
    outline-offset: -3px;
}

/* Sticky action footer: keeps a form's Save/Cancel bar pinned to the bottom of the viewport on long
   forms, so the buttons stay reachable without scrolling. Add the "sticky-actions" class to a .card-footer.
   (If the footer doesn't stick, an ancestor with overflow other than visible is clipping it.) */
.card-footer.sticky-actions {
    position: sticky;
    bottom: 0;
    z-index: 5;
    background-color: var(--bs-card-bg, #fff);
    border-top: 1px solid var(--bs-border-color, #dee2e6);
}

/* Bootbox renders its close button with Bootstrap 4's `.close` class, which Bootstrap 5 dropped - so the
   button gets no positioning. Give it a subtle look and place it top-right in BOTH layouts bootbox produces:
   inside the flex .modal-header when the dialog has a title, and prepended as the first child of .modal-body
   (far left, in front of the message) when it does not - which is the case for bootbox.confirm. */
.bootbox .bootbox-close-button {
    padding: 0;
    border: 0;
    background: transparent;
    font-size: 1.5rem;
    line-height: 1;
    opacity: 0.5;
    cursor: pointer;
}

.bootbox .bootbox-close-button:hover {
    opacity: 0.9;
}

/* Titled dialog: the button is in the flex header - push it to the far right. */
.bootbox .modal-header .bootbox-close-button {
    margin-left: auto;
}

/* Untitled dialog (e.g. bootbox.confirm): the button is the first child of the body. Pin it to the
   top-right of the dialog (the .modal-content is the positioned ancestor) instead of the far left. */
.bootbox .modal-body > .bootbox-close-button {
    position: absolute;
    top: 0.5rem;
    right: 0.75rem;
    z-index: 1;
}

/* Reserve space so a longer, wrapping message never runs under the pinned close button. Targets only the
   message body that directly follows the close button, i.e. the untitled (bootbox.confirm) layout. */
.bootbox .bootbox-close-button + .bootbox-body {
    padding-right: 1.5rem;
}

/* ---------------------------------------------------------------------------------------------
   Contextual help panel

   Slides in from the right over the current screen rather than navigating away. The reader keeps
   the screen they were confused about in view, which is the entire reason this is a panel and not
   a page - somebody who has to leave the screen to find the answer has already half given up.

   Off-canvas by transform rather than by display, so it animates and so its content keeps its
   layout while hidden. prefers-reduced-motion drops the animation and not the behaviour.
   --------------------------------------------------------------------------------------------- */

#support-help-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: 380px;
    max-width: 90vw;
    z-index: 1045; /* above the page, below Bootstrap's modal (1050) so an article can open over it */
    background: var(--bs-body-bg, #fff);
    border-left: 1px solid var(--bs-border-color, #dee2e6);
    box-shadow: -2px 0 12px rgba(0, 0, 0, 0.12);
    padding: 1rem;
    overflow-y: auto;
    transform: translateX(100%);
    transition: transform 0.2s ease-out;
    visibility: hidden;
}

#support-help-panel.open {
    transform: translateX(0);
    visibility: visible;
}

@media (prefers-reduced-motion: reduce) {
    #support-help-panel {
        transition: none;
    }
}

/* The "?" in the page header. Quiet until hovered - it should be findable, not loud. */
.support-help-button {
    opacity: 0.55;
}

.support-help-button:hover,
.support-help-button:focus {
    opacity: 1;
}

/* Article bodies come from CKEditor, so give the common elements sane spacing. */
.support-article-body img {
    max-width: 100%;
    height: auto;
}

.support-article-body h2,
.support-article-body h3,
.support-article-body h4 {
    margin-top: 1rem;
}

/* ---------------------------------------------------------------------------------------------
   Service incident banner

   Fixed above the fixed navbar, and ONLY inside the admin shell. #navbar is fixed-top at z-index
   1030, so a banner in normal document flow sits underneath it and is invisible - which is
   exactly what happened. Fixing the banner and pushing the navbar down by its height (see
   setNavbarOffset in _LayoutAdmin) puts it where an outage notice has to be: above everything,
   and still there after the page is scrolled.

   Not applied on the sign-in page or the member portal: neither has a fixed navbar, so the banner
   already renders correctly in normal flow there, and fixing it would only overlap their content.
   --------------------------------------------------------------------------------------------- */

.loc-admin-shell #service-incident-banner {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1032; /* above the navbar's 1030, below Bootstrap's modal at 1050 */
}

/* ---------------------------------------------------------------------------------------------
   Administrator dashboard (Views/Staff/Dashboard)

   Every colour, radius and shadow here is a Bootstrap custom property, which is what /ClubTheme.css
   rewrites per club (ThemeController). Hard-coding hex - as the member summary widget above does -
   would leave the dashboard as the one admin screen that ignores the club theme an administrator
   picked.

   Each card sets its own accent through --loc-dash-accent (plus the matching -rgb triple, which is
   what lets the tints below be translucent shades of the SAME colour rather than six more
   variables).
   --------------------------------------------------------------------------------------------- */

/* The admin shell's <main> is a .container-fluid, so the side padding is already there; only the top and
   bottom are missing, and without them the first card butts into the navbar and the last into the window. */
.loc-dash {
    --loc-dash-accent: var(--bs-primary);
    --loc-dash-accent-rgb: var(--bs-primary-rgb);
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 0.5rem 0 1.5rem;
}

/* Accent presets. Set on the card or chip, so everything inside it - rule, icon, meter, footer link
   - picks the colour up without repeating it. */
.loc-dash-accent-primary {
    --loc-dash-accent: var(--bs-primary);
    --loc-dash-accent-rgb: var(--bs-primary-rgb);
}

.loc-dash-accent-info {
    --loc-dash-accent: var(--bs-info);
    --loc-dash-accent-rgb: var(--bs-info-rgb);
}

.loc-dash-accent-success {
    --loc-dash-accent: var(--bs-success);
    --loc-dash-accent-rgb: var(--bs-success-rgb);
}

.loc-dash-accent-warning {
    --loc-dash-accent: var(--bs-warning);
    --loc-dash-accent-rgb: var(--bs-warning-rgb);
}

.loc-dash-accent-danger {
    --loc-dash-accent: var(--bs-danger);
    --loc-dash-accent-rgb: var(--bs-danger-rgb);
}

.loc-dash-accent-secondary {
    --loc-dash-accent: var(--bs-secondary);
    --loc-dash-accent-rgb: var(--bs-secondary-rgb);
}

/* --- Page heading ------------------------------------------------------ */

.loc-dash-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem 1rem;
}

.loc-dash-title {
    margin: 0;
    font-size: 1.4rem;
    font-weight: 600;
    letter-spacing: -0.01em;
}

.loc-dash-asof {
    color: var(--bs-secondary-color);
    font-size: 0.85rem;
}

/* Accent shaded towards black until it is readable as text on the card. Used for glyphs and the footer
   link; fills keep the accent at full strength. The plain-accent line before it is the fallback for any
   browser without color-mix - a slightly weak colour rather than no colour at all. */

/* --- "Needs attention" chips ------------------------------------------- */

/* A wrapping row rather than a grid: how many chips appear depends on what is actually outstanding,
   and a grid would leave holes on the days when most of them are clear. */
/* The default accent lives on the CONTAINER, not on .loc-dash-chip: a chip carrying its own
   .loc-dash-accent-* class has equal specificity, so a default set on the chip itself would win on
   source order and every chip would come out the same colour. */
.loc-dash-attention {
    --loc-dash-accent: var(--bs-warning);
    --loc-dash-accent-rgb: var(--bs-warning-rgb);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
}

.loc-dash-chip {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.45rem 0.9rem 0.45rem 0.5rem;
    border: 1px solid rgba(var(--loc-dash-accent-rgb), 0.35);
    border-radius: 999px;
    background: rgba(var(--loc-dash-accent-rgb), 0.08);
    color: inherit;
    text-decoration: none;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}

a.loc-dash-chip:hover,
a.loc-dash-chip:focus-visible {
    background: rgba(var(--loc-dash-accent-rgb), 0.18);
    border-color: rgba(var(--loc-dash-accent-rgb), 0.6);
    color: inherit;
}

.loc-dash-chip-icon {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: rgba(var(--loc-dash-accent-rgb), 0.18);
    color: var(--loc-dash-accent);
    color: color-mix(in srgb, var(--loc-dash-accent) 45%, #000);
    font-size: 0.8rem;
}

.loc-dash-chip-count {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.loc-dash-chip-label {
    font-size: 0.875rem;
    color: var(--bs-secondary-color);
}

/* --- Card grid --------------------------------------------------------- */

/* CSS grid rather than .row/.col: under BS5 a .row forces width:100% on any child without a column
   class, and equal-height cards then need d-flex plumbing on every wrapper. Grid gives both for
   free. Two columns from tablet up, a third only on a really wide screen, so the six cards stay
   2x3 or 3x2 and never leave a lone card stranded on a row of its own. */
.loc-dash-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
}

@media (min-width: 768px) {
    .loc-dash-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1600px) {
    .loc-dash-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

.loc-dash-card {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--bs-body-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius-lg);
    box-shadow: var(--bs-box-shadow-sm);
    transition: box-shadow 0.15s ease;
}

.loc-dash-card:hover {
    box-shadow: var(--bs-box-shadow);
}

/* The accent rule along the top edge - the only place a card states its colour at full strength. */
.loc-dash-card::before {
    content: "";
    display: block;
    height: 3px;
    background: var(--loc-dash-accent);
}

.loc-dash-card-head {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.9rem 1.15rem 0;
}

.loc-dash-card-icon {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 10px;
    background: rgba(var(--loc-dash-accent-rgb), 0.14);
    color: var(--loc-dash-accent);
    color: color-mix(in srgb, var(--loc-dash-accent) 45%, #000);
}

.loc-dash-card-title {
    margin: 0;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--bs-secondary-color);
}

/* --- Hero figure ------------------------------------------------------- */

.loc-dash-hero {
    padding: 0.55rem 1.15rem 0.85rem;
}

.loc-dash-hero-value {
    font-size: 1.95rem;
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}

.loc-dash-hero-label {
    font-size: 0.82rem;
    color: var(--bs-secondary-color);
}

/* Zero is good news on some cards and simply nothing to report on others; either way it should not
   shout as loudly as a real figure. */
.loc-dash-hero-value.is-zero {
    color: var(--bs-secondary-color);
}

/* --- Meters and stacked breakdown bars --------------------------------- */

.loc-dash-meter {
    height: 8px;
    margin-top: 0.6rem;
    border-radius: 999px;
    background: var(--bs-secondary-bg);
    overflow: hidden;
}

.loc-dash-meter-fill {
    height: 100%;
    border-radius: 999px;
    background: var(--loc-dash-accent);
}

/* The composition bar: one row of segments summing to 100%. Segments step DOWN in opacity so the
   series reads as one quantity split several ways rather than several unrelated colours. */
.loc-dash-split {
    display: flex;
    height: 8px;
    margin-top: 0.6rem;
    border-radius: 999px;
    background: var(--bs-secondary-bg);
    overflow: hidden;
}

.loc-dash-seg {
    height: 100%;
    background: rgba(var(--loc-dash-accent-rgb), var(--loc-dash-seg-alpha, 1));
}

.loc-dash-seg-1 {
    --loc-dash-seg-alpha: 1;
}

.loc-dash-seg-2 {
    --loc-dash-seg-alpha: 0.75;
}

.loc-dash-seg-3 {
    --loc-dash-seg-alpha: 0.52;
}

.loc-dash-seg-4 {
    --loc-dash-seg-alpha: 0.32;
}

.loc-dash-seg-5 {
    --loc-dash-seg-alpha: 0.22;
}

.loc-dash-seg-6 {
    --loc-dash-seg-alpha: 0.12;
}

/* The dot in front of a breakdown row, matching its segment in the bar above. */
.loc-dash-dot {
    display: inline-block;
    flex: 0 0 auto;
    width: 9px;
    height: 9px;
    margin-right: 0.5rem;
    border-radius: 50%;
    background: rgba(var(--loc-dash-accent-rgb), var(--loc-dash-seg-alpha, 1));
}

/* --- Stat rows --------------------------------------------------------- */

.loc-dash-stats {
    list-style: none;
    margin: 0;
    padding: 0 1.15rem 0.55rem;
}

.loc-dash-stat {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.45rem 0;
    border-top: 1px solid var(--bs-border-color);
}

.loc-dash-stat:first-child {
    border-top: 0;
}

.loc-dash-stat-label {
    display: flex;
    align-items: center;
    font-size: 0.875rem;
    color: var(--bs-secondary-color);
}

.loc-dash-stat-value {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* A figure somebody has to do something about. */
.loc-dash-stat.is-attention .loc-dash-stat-value {
    color: var(--bs-danger);
}

.loc-dash-stat-note {
    margin-left: 0.4rem;
    font-weight: 400;
    font-size: 0.8rem;
    color: var(--bs-secondary-color);
}

/* --- Card footer link -------------------------------------------------- */

/* margin-top:auto pins the footer to the bottom edge whatever the card above it holds, which is what
   keeps a row of cards looking like a set. */
.loc-dash-foot {
    margin-top: auto;
    border-top: 1px solid var(--bs-border-color);
}

.loc-dash-foot-link,
.loc-dash-foot-text {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.7rem 1.15rem;
    font-size: 0.875rem;
    font-weight: 500;
    text-decoration: none;
}

.loc-dash-foot-link {
    color: var(--loc-dash-accent);
    color: color-mix(in srgb, var(--loc-dash-accent) 45%, #000);
    cursor: pointer;
}

.loc-dash-foot-link:hover,
.loc-dash-foot-link:focus-visible {
    background: rgba(var(--loc-dash-accent-rgb), 0.08);
    color: var(--loc-dash-accent);
    color: color-mix(in srgb, var(--loc-dash-accent) 45%, #000);
    text-decoration: none;
}

.loc-dash-foot-arrow {
    transition: transform 0.15s ease;
}

.loc-dash-foot-link:hover .loc-dash-foot-arrow {
    transform: translateX(3px);
}

/* Shown instead of the link when the signed-in user does not hold the function behind the screen. */
.loc-dash-foot-text {
    color: var(--bs-secondary-color);
}

@media (prefers-reduced-motion: reduce) {

    .loc-dash-card,
    .loc-dash-chip,
    .loc-dash-foot-arrow {
        transition: none;
    }

    .loc-dash-foot-link:hover .loc-dash-foot-arrow {
        transform: none;
    }
}

/* WEBSITE MENUS - the read-only audience columns (Members / Guests / Visitors)
   These ticks carried form-control, which is the class for a text input: on Bootstrap 5 that gives a
   checkbox display:block, width:100% and an input's height, so each one rendered as an empty rounded box
   filling its cell with no visible tick. They use .checkbox now, like every other checkbox in the app -
   see the rule above, which restores native rendering precisely because .form-control had suppressed it.
   Disabled on purpose: this list reports the setting, the pencil is where it is changed. */
.navgroup-item-list td .checkbox {
    cursor: default;
}


/* ============================================================================
   SCROLLING DIV
   Fourteen views across the application ask for a "scrolling-div" and pair it with a height or a
   max-height: waivers, news stories, the terms and privacy documents, the gallery and content pickers.
   The class was never DEFINED - see the note by .news-story, written when the same thing clipped an
   article - so every one of them capped its height and then simply cut the content off, with no
   scrollbar and no way to reach the end. The member startup wizard is where that mattered most: the
   waiver you are being asked to accept was unreadable past 325px.

   Only the overflow belongs here. The height stays with the caller, because it is the caller that knows
   how much room the thing has.
   ============================================================================ */
.scrolling-div {
    overflow-y: auto;
    /* A scroll region inside a page that also scrolls: stop the page from taking over the moment this
       one hits its end, which on a touch screen reads as the document jumping away under your finger. */
    overscroll-behavior-y: contain;
}

/* ============================================================================
   CONFIRM BUTTONS
   The footer button row, used by 171 views and - like .scrolling-div - never defined, so the buttons
   were laid out by whatever whitespace happened to sit between the tags. A flex row with a real gap
   keeps them evenly spaced, and keeps them from jamming together when they wrap on a phone.

   Deliberately no justification: most of these sit inside a .modal-footer, which is already a flex row
   that pushes its content right, and taking a position here would move all 171 of them.
   ============================================================================ */
.confirm-btns {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

/* ============================================================================
   MEMBER STARTUP WIZARD
   The first screen a new member ever sees: profile, preferences, waivers to accept and must-read news.

   Its tab strip is HIDDEN by member.js, deliberately - the tabs are how Bootstrap switches panes, but
   letting somebody click straight to the last one would walk them past a waiver they never accepted.
   That left the wizard with no sense of progress at all beyond a line of text, which is what the
   stepper and the bar below put back.
   ============================================================================ */
/* The tab strip is the MECHANISM, never a control: Bootstrap switches the panes on it, but a member who
   clicked one would land past a waiver they had not accepted. member.js used to hide it on startup, which
   meant a full row of tabs flashed up on every load before the script ran. It is structural to this screen,
   so it belongs here. */
.loc-wizard #member-startup-tabs {
    display: none;
}

.loc-wizard .card-title {
    margin-bottom: 0;
}

.loc-wizard-step-of {
    margin-top: 0.15rem;
    font-size: 0.875rem;
    color: var(--bs-secondary-color);
}

.loc-wizard-intro {
    max-width: 60ch;
    color: var(--bs-secondary-color);
}

/* The stepper. A row of numbered markers joined by a rule, each with the name of what it asks for, so
   the member can see both how far along they are and what is still coming. */
.loc-wizard-steps {
    display: flex;
    margin: 0 0 0.75rem;
    padding: 0;
    list-style: none;
}

.loc-wizard-step {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    padding: 0 0.25rem;
    text-align: center;
    color: var(--bs-secondary-color);
}

/* The connector runs BEHIND the markers rather than between them, so it does not have to know how wide
   they are. It starts at the middle of this step and ends at the middle of the next, which is why the
   last step has none. */
.loc-wizard-step::before {
    position: absolute;
    top: 0.9rem;
    left: 50%;
    z-index: 0;
    width: 100%;
    height: 2px;
    content: "";
    background-color: var(--bs-border-color);
}

.loc-wizard-step:last-child::before {
    content: none;
}

.loc-wizard-step.is-done::before {
    background-color: var(--bs-primary);
}

.loc-wizard-marker {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.8rem;
    height: 1.8rem;
    margin: 0 auto 0.35rem;
    font-size: 0.8rem;
    font-weight: 600;
    line-height: 1;
    color: var(--bs-secondary-color);
    background-color: var(--bs-body-bg);
    border: 2px solid var(--bs-border-color);
    border-radius: 50%;
}

.loc-wizard-step.is-current .loc-wizard-marker {
    color: var(--bs-primary);
    border-color: var(--bs-primary);
    box-shadow: 0 0 0 0.2rem rgba(var(--bs-primary-rgb), 0.2);
}

.loc-wizard-step.is-current {
    font-weight: 600;
    color: var(--bs-emphasis-color);
}

/* Done steps swap the number for a tick - the number has served its purpose once you are past it, and a
   tick says "nothing left here" in a way a dimmed 2 does not. */
.loc-wizard-step.is-done .loc-wizard-marker {
    color: var(--bs-primary-bg-subtle);
    background-color: var(--bs-primary);
    border-color: var(--bs-primary);
    font-size: 0;
}

.loc-wizard-step.is-done .loc-wizard-marker::after {
    font-size: 0.85rem;
    content: "\2713";
}

.loc-wizard-label {
    display: block;
    overflow: hidden;
    font-size: 0.8rem;
    line-height: 1.2;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* On a phone the labels cannot all fit, and truncating four of them to three letters each says nothing.
   The markers still show the shape of the journey, and the "Step 2 of 4: Preferences" line under the
   title names the one you are actually on. */
@media (max-width: 575.98px) {
    .loc-wizard-label {
        display: none;
    }
}

.loc-wizard-progress {
    height: 0.5rem;
    margin-bottom: 1.25rem;
}

/* A waiver or a news story, in a box that is visibly a box - bordered and inset, so it reads as a
   document handed to you rather than as more of the page. Capped in vh as well as rem: the wizard is a
   full page, and a fixed 325px was both too short on a desktop and too tall on a phone in landscape. */
.loc-wizard-document {
    max-height: min(45vh, 22rem);
    padding: 0.75rem 1rem;
    background-color: var(--bs-tertiary-bg);
    border: 1px solid var(--bs-border-color);
    border-radius: var(--bs-border-radius, 0.375rem);
}

/* Waivers and news bodies are authored in CKEditor and routinely carry pasted images and tables with
   intrinsic widths - nothing in one may push the wizard wider than its column. */
.loc-wizard-document img,
.loc-wizard-document video,
.loc-wizard-document iframe,
.loc-wizard-document table {
    max-width: 100%;
}

/* The one control the step exists for. It was a bare checkbox floated left with an inline margin, which
   put the single required action of the screen in the plainest thing on it.

   Compound .loc-wizard-accept.form-check (specificity 0,2,0) for the same reason .form-control.checkbox-control
   above is compound: loc-theme-default.css - the Bootstrap build - is linked AFTER this stylesheet, so a
   plain .loc-wizard-accept loses padding-left to .form-check at equal specificity. It did: .form-check's
   1.5em, against the -1.5em margin Bootstrap puts on the input, sat the checkbox and its prompt hard against
   the panel border. The left padding has to clear that margin AND leave a gap, hence 2.25rem - the checkbox
   lands 0.75rem in, matching the other three sides. */
.loc-wizard-accept.form-check {
    padding: 0.75rem 0.75rem 0.75rem 2.25rem;
    margin-top: 1rem;
    background-color: var(--bs-primary-bg-subtle);
    border: 1px solid var(--bs-primary-border-subtle);
    border-radius: var(--bs-border-radius, 0.375rem);
}

.loc-wizard-accept .form-check-input {
    margin-top: 0.15rem;
    cursor: pointer;
}

.loc-wizard-accept .form-check-label {
    font-weight: 600;
    cursor: pointer;
}

/* Previous on one side, the way forward on the other - by pushing the forward group right rather than by
   justifying the row, because on a one-step startup there IS no Previous and space-between would leave
   Finish stranded on the left, away from where every other footer in the application puts it. */
.loc-wizard-btns-forward {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-left: auto;
}

/* ==========================================================================================
   Site template preview bar
   ------------------------------------------------------------------------------------------
   Shown only while a club is previewing a prebuilt site template (WebsiteController.
   TemplatePreview). Fixed to the BOTTOM on purpose: the top belongs to the previewed site's
   own navbar, and pushing a real site's header down changes the very thing being judged.

   Its own colours rather than the club theme's - it is our chrome around somebody else's
   design, and it has to stay legible whatever palette the template uses.
   ========================================================================================== */
.loc-template-preview-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1080; /* above a fixed navbar (1030) and sticky content, below modals (1055+) */
    padding: 0.6rem 1rem;
    background: #1f2937;
    color: #f9fafb;
    font-size: 0.9rem;
    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.25);
}

.loc-template-preview-bar a.btn-light {
    --bs-btn-color: #1f2937;
}

/* Keep the bar off the last of the page's own content. */
body:has(.loc-template-preview-bar) {
    padding-bottom: 4.5rem;
}

/* Copy-in-progress banner while a site template is being applied. A full-screen overlay would be wrong -
   nothing else is safe to click, but the club should still see the builder it is about to get back. */
.loc-template-progress {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 2000; /* above modals: it outlives the gallery that started it */
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    padding: .75rem 1rem;
    background: #1f2937;
    color: #f9fafb;
    font-size: .95rem;
}

/* ============================================================================================
   SEARCH WIDGET AND SEARCH RESULTS

   Almost nothing here is colour. The box is a Bootstrap input group and the button is a
   .btn-primary, so both already take the club's theme - its radius, its shadow, its primary
   colour - without this file naming any of them. What IS here is the part Bootstrap has no
   opinion about: a dropdown that has to sit over the page, and a list of results.

   See [app-css-loads-before-bootstrap]: this file is linked BEFORE the theme, so anything
   overriding a Bootstrap class would need a compound selector to win. The rules below are all
   on loc-* classes of our own, which is why none of them do.
   ============================================================================================ */

.loc-search-form {
    /* The suggestion list is absolutely positioned against this. */
    position: relative;
}

/* The dropdown sits OVER the page rather than pushing it down: a list that reflowed the page
   under it would move whatever the visitor was reading every time they typed a letter. */
.loc-search-suggestions {
    position: absolute;
    z-index: 1030;
    left: 0;
    right: 0;
    top: 100%;
    margin-top: 2px;
    background: var(--bs-body-bg, #fff);
    border: 1px solid var(--bs-border-color, #dee2e6);
    border-radius: var(--bs-border-radius, .375rem);
    box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15);
    overflow: hidden;
}

.loc-search-suggestion {
    display: block;
    padding: .5rem .75rem;
    color: var(--bs-body-color, #212529);
    text-decoration: none;

    /* A page title is one line. Wrapping a long one to three lines makes six suggestions taller
       than the page being searched. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Hover and keyboard highlight look identical on purpose: arrowing down the list and moving the
   mouse over it are the same act, and two different highlights in one list read as two lists. */
.loc-search-suggestion:hover,
.loc-search-suggestion.active {
    background: var(--bs-secondary-bg, #e9ecef);
    color: var(--bs-body-color, #212529);
}

/* The button's caption folds away on a phone, leaving the magnifier. A search button is the one
   control that needs no label - and on a narrow screen the words cost the box its width. */
@media (max-width: 575.98px) {
    .loc-search-button-text {
        display: none;
    }
}

/* ---- The results page ---- */

.loc-search-results {
    max-width: 44rem;
    margin: 0 auto;
    padding: 1.5rem 1rem 3rem;
}

.loc-search-results-heading {
    margin-bottom: 1rem;
}

.loc-search-count,
.loc-search-empty {
    margin: 1.5rem 0 .5rem;
}

.loc-search-hint {
    color: var(--bs-secondary-color, #6c757d);
}

.loc-search-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.loc-search-result {
    padding: 1rem 0;
    border-top: 1px solid var(--bs-border-color, #dee2e6);
}

.loc-search-result-title {
    font-weight: 600;
    font-size: 1.1rem;
}

.loc-search-result-snippet {
    margin: .25rem 0 0;
    color: var(--bs-secondary-color, #6c757d);
}

/* The matched words. Not the browser default yellow, which fights every club theme it lands in -
   the club's own primary, faintly, so the highlight belongs to the site it is on. */
.loc-search-result-snippet mark {
    padding: 0;
    background: color-mix(in srgb, var(--bs-primary, #0d6efd) 18%, transparent);
    color: inherit;
}
