/* ==========================================================================
   UAE Bronco Club -- application shell
   Loaded after Bootstrap from the CDN. No build step: edit this file directly.

   Design tokens follow docs/UI-BRIEF.md. Colour carries meaning only: black
   for structure and active state, red and green for status, greys for
   everything secondary. There is no accent colour.
   ========================================================================== */

:root {
    /* ---- Palette (UI-BRIEF) ------------------------------------------- */
    --c-white: #FFFFFF;
    --c-surface: #F5F5F4;
    --c-black: #231F20;
    --c-ink-2: #6B6868;
    --c-line: #E3E1E1;

    /* Meaning only. Never decoration. */
    --c-red: #D7202A;
    --c-green: #1B7A3D;

    /* Tints, for filled pills and soft backgrounds. */
    --c-red-soft: #FCEDEE;
    --c-green-soft: #E8F3EC;
    --c-black-soft: #34302F;

    /* Older rules and a few views still use these names. */
    --ubc-black: var(--c-black);
    --ubc-black-soft: var(--c-black-soft);
    --ubc-bg: var(--c-surface);
    --ubc-border: var(--c-line);
    --ubc-muted: var(--c-ink-2);

    /* ---- Type --------------------------------------------------------- */
    /* Michroma for headings, titles, date bars and big numbers. It has one
       weight and wide letterforms, so it is never used for body copy. */
    --ubc-font-display: "Michroma", "Segoe UI", system-ui, sans-serif;
    --ubc-font-body: "Inter", "IBM Plex Sans Arabic", system-ui, -apple-system,
        "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

    --ubc-fs-xs: 13px;
    --ubc-fs-sm: 15px;
    --ubc-fs-md: 17px;
    --ubc-fs-lg: 20px;
    --ubc-fs-xl: 26px;
    --ubc-fs-xxl: 34px;

    /* ---- Radius ------------------------------------------------------- */
    --r-sheet: 16px;
    --r-card: 12px;
    --r-control: 10px;

    /* ---- Chrome ------------------------------------------------------- */
    --ubc-topbar-h: 56px;
    --ubc-bottomnav-h: 60px;

    /* The shell fills the viewport everywhere below the desktop breakpoint.
       It used to be capped at 600px and centred, which left an obvious margin
       down both sides of anything between 600px and the 768px tablet rule --
       a dead zone covering small tablets and split-screen windows.

       The reading measure only needs protecting from 992px up, where the
       sidebar appears and a window can be genuinely wide; that is what the
       >=992px block resets this to. */
    --ubc-shell-max: 100%;
    --ubc-sidebar-w: 240px;

    /* Home indicator / notch. Falls back to 0 off iOS. */
    --ubc-safe-bottom: env(safe-area-inset-bottom, 0px);
    --ubc-safe-top: env(safe-area-inset-top, 0px);
}

/* --------------------------------------------------------------------------
   Bootstrap theming
   Bootstrap's default accent is #0d6efd, which shows up on every link, form
   switch and focus ring. The brand has no accent colour, so all of it maps
   onto black; red and green stay reserved for status.
   -------------------------------------------------------------------------- */

:root {
    --bs-primary: var(--c-black);
    --bs-primary-rgb: 35, 31, 32;

    --bs-link-color: var(--c-black);
    --bs-link-color-rgb: 35, 31, 32;
    --bs-link-hover-color: var(--c-ink-2);
    --bs-link-hover-color-rgb: 107, 104, 104;

    --bs-border-color: var(--c-line);
    --bs-body-color: var(--c-black);
    --bs-secondary-color: var(--c-ink-2);

    --bs-danger: var(--c-red);
    --bs-danger-rgb: 215, 32, 42;
    --bs-success: var(--c-green);
    --bs-success-rgb: 27, 122, 61;

    --bs-focus-ring-color: transparent;
    --bs-focus-ring-width: 0;
}

/* Links are black, so they need the underline to read as links at all. */
.ubc-main a:not(.btn):not(.nav-link):not(.ubc-card):not(.ubc-row):not(.dropdown-item) {
    text-decoration: underline;
    text-underline-offset: .18em;
}

/* Switches, checkboxes and radios when on. */
.form-check-input:checked {
    background-color: var(--c-black);
    border-color: var(--c-black);
}

/* --------------------------------------------------------------------------
   Focus
   One rule for everything: a black 2px outline with an offset, so it is
   visible on white, on surface grey and on a black bar alike. Bootstrap's
   box-shadow rings are switched off above.
   -------------------------------------------------------------------------- */

:focus-visible {
    outline: 2px solid var(--c-black);
    outline-offset: 2px;
}

.form-control:focus,
.form-select:focus,
.form-check-input:focus {
    border-color: var(--c-black);
    box-shadow: none;
    outline: 2px solid var(--c-black);
    outline-offset: 2px;
}

/* On the black bars an all-black ring would be invisible. */
.ubc-topbar :focus-visible,
.ubc-drawer :focus-visible,
.ubc-auth :focus-visible,
.ubc-admin-sidebar :focus-visible,
.ubc-date-bar :focus-visible,
.btn-ubc:focus-visible {
    outline-color: var(--c-white);
}

html,
body {
    overscroll-behavior-y: none;
}

body {
    background-color: var(--c-surface);
    color: var(--c-black);
    font-family: var(--ubc-font-body);
    font-size: var(--ubc-fs-sm);
    line-height: 1.5;
    /* Stop iOS zooming the page when an input is focused. */
    -webkit-text-size-adjust: 100%;
}

/* Sentence case, never all-caps: Michroma is already wide enough to shout. */
h1, h2, h3, h4, h5, h6,
.ubc-display {
    font-family: var(--ubc-font-display);
    font-weight: 400;
    letter-spacing: -0.01em;
    line-height: 1.25;
    text-transform: none;
}

h1, .h1 { font-size: var(--ubc-fs-xl); }
h2, .h2 { font-size: var(--ubc-fs-lg); }
h3, .h3 { font-size: var(--ubc-fs-md); }
h4, .h4,
h5, .h5 { font-size: var(--ubc-fs-md); }
h6, .h6 { font-size: var(--ubc-fs-sm); }

/* Numeric values.
 *
 * DEVIATION from docs/UI-BRIEF.md, which asks for Michroma on big numbers.
 * Michroma draws zero as a wide rounded rectangle that reads as a missing
 * glyph at every size we use, and zero is our most common value -- a new
 * member's whole stats grid and every unbooked trip's gauge. Inter semibold
 * with tabular figures keeps the numbers aligned and legible. Michroma still
 * carries the headings, titles, date bars and the club name. */
.ubc-num {
    font-family: var(--ubc-font-body);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
}

/* A screen's own title (a trip, a member), which grows on a larger screen
   where there is room for it. Michroma is wide, so it tops out well below
   the scale's 34px. */
.ubc-page-title {
    font-family: var(--ubc-font-display);
    font-weight: 400;
    font-size: clamp(var(--ubc-fs-lg), 4.5vw, var(--ubc-fs-xl));
    line-height: 1.2;
    margin: 0;
}

/* Long-form copy stays under ~70 characters a line. */
.ubc-prose {
    max-width: 70ch;
}

/* --------------------------------------------------------------------------
   Rich text
   Admin-written HTML from Summernote, sanitised by Purifier on save. Sets the
   rhythm for the tags it produces; without this a rules page rendered as one
   undifferentiated block at caption size.
   -------------------------------------------------------------------------- */

.ubc-richtext {
    font-size: var(--ubc-fs-sm);
    line-height: 1.65;
}

.ubc-richtext > :first-child {
    margin-top: 0;
}

.ubc-richtext > :last-child {
    margin-bottom: 0;
}

.ubc-richtext p,
.ubc-richtext ul,
.ubc-richtext ol,
.ubc-richtext blockquote,
.ubc-richtext table {
    margin-bottom: 1rem;
}

.ubc-richtext h1,
.ubc-richtext h2,
.ubc-richtext h3,
.ubc-richtext h4,
.ubc-richtext h5,
.ubc-richtext h6 {
    margin-top: 1.75rem;
    margin-bottom: .6rem;
}

/* Michroma is wide; a heading inside body copy stays close to body size so a
   long one does not wrap three times on a phone. */
.ubc-richtext h1,
.ubc-richtext h2 {
    font-size: var(--ubc-fs-lg);
}

.ubc-richtext h3,
.ubc-richtext h4,
.ubc-richtext h5,
.ubc-richtext h6 {
    font-size: var(--ubc-fs-md);
}

.ubc-richtext ul,
.ubc-richtext ol {
    padding-left: 1.35rem;
}

.ubc-richtext li {
    margin-bottom: .35rem;
}

.ubc-richtext li:last-child {
    margin-bottom: 0;
}

.ubc-richtext blockquote {
    border-left: 3px solid var(--c-line);
    padding-left: .9rem;
    color: var(--c-ink-2);
}

.ubc-richtext img {
    max-width: 100%;
    height: auto;
    border-radius: var(--r-control);
}

.ubc-richtext hr {
    border-color: var(--c-line);
    opacity: 1;
    margin: 1.5rem 0;
}

.ubc-richtext table {
    width: 100%;
    border-collapse: collapse;
}

.ubc-richtext th,
.ubc-richtext td {
    border: 1px solid var(--c-line);
    padding: .4rem .6rem;
    text-align: left;
}

/* --------------------------------------------------------------------------
   Note
   A neutral inline notice: why something is limited, locked or unavailable.
   Deliberately not red or amber -- "add a vehicle before you can book" is
   information, not an alarm, and under UI-BRIEF red carries status meaning.
   -------------------------------------------------------------------------- */

.ubc-note {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    background-color: var(--c-surface);
    border: 1px solid var(--c-line);
    border-left: 3px solid var(--c-black);
    border-radius: var(--r-control);
    padding: .65rem .8rem;
    font-size: var(--ubc-fs-xs);
    line-height: 1.5;
}

.ubc-note__body {
    flex: 1 1 auto;
    min-width: 0;
}

/* Keeps list rows an even height when the text behind them varies. */
.ubc-clamp-2 {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.small, small {
    font-size: var(--ubc-fs-xs);
}

.text-muted {
    color: var(--c-ink-2) !important;
}

/* Kill the tap highlight so the app does not feel like a web page. */
a,
button,
.nav-link {
    -webkit-tap-highlight-color: transparent;
}

/* Every interactive target clears 44px, even where the label is small. */
.ubc-bottomnav a,
.ubc-row,
.btn,
.form-control,
.form-select {
    min-height: 44px;
}

.btn-sm {
    min-height: 36px;
}

/* --------------------------------------------------------------------------
   Brand helpers
   -------------------------------------------------------------------------- */

.bg-ubc {
    background-color: var(--c-black) !important;
}

.btn {
    border-radius: var(--r-control);
    font-family: var(--ubc-font-body);
    font-weight: 500;
}

.form-control,
.form-select {
    border-radius: var(--r-control);
}

.btn-ubc {
    background-color: var(--c-black);
    border-color: var(--c-black);
    color: var(--c-white);
    font-weight: 600;
}

.btn-ubc:hover,
.btn-ubc:focus,
.btn-ubc:active {
    background-color: var(--c-black-soft);
    border-color: var(--c-black-soft);
    color: var(--c-white);
}

.btn-ubc-outline {
    background-color: transparent;
    border-color: var(--c-black);
    color: var(--c-black);
    font-weight: 500;
}

.btn-ubc-outline:hover,
.btn-ubc-outline:focus {
    background-color: var(--c-black);
    color: var(--c-white);
}

/* --------------------------------------------------------------------------
   Logo chip
   The badge is drawn for a light page: its outer ring includes a black
   quarter, and white outline strokes that are invisible on white. Dropped
   straight onto a black background the black quarter disappears and the white
   strokes read as a halo.

   Rather than recolour the artwork, dark surfaces put it on a white disc --
   the conventional way to place a logo on a background it was not drawn for.
   -------------------------------------------------------------------------- */

.ubc-logo-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background-color: var(--c-white);
    border-radius: 50%;
    padding: 6px;
    line-height: 0;
}

.ubc-logo-chip--sm {
    padding: 4px;
}

/* --------------------------------------------------------------------------
   Brand ring
   A single stroke that runs red -> green -> black. UI-BRIEF allows it in
   exactly three places: under the logo on login/register, at the top of the
   drawer, and on the offline screen. Nowhere else.
   -------------------------------------------------------------------------- */

.ubc-brand-ring {
    display: block;
    width: 120px;
    height: 3px;
    margin: .75rem auto 0;
    border-radius: 2px;
    background: linear-gradient(90deg,
        var(--c-red) 0%,
        var(--c-red) 33%,
        var(--c-green) 33%,
        var(--c-green) 66%,
        var(--c-black) 66%,
        var(--c-black) 100%);
}

/* On a black background the black third would vanish. */
.ubc-auth .ubc-brand-ring,
.ubc-drawer .ubc-brand-ring {
    background: linear-gradient(90deg,
        var(--c-red) 0%,
        var(--c-red) 33%,
        var(--c-green) 33%,
        var(--c-green) 66%,
        var(--c-white) 66%,
        var(--c-white) 100%);
}

.ubc-brand-ring--start {
    margin-inline: 0;
}

/* --------------------------------------------------------------------------
   Status pills
   Colour carries meaning, and never on its own -- the word is always there.
   -------------------------------------------------------------------------- */

.ubc-status {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    border-radius: 999px;
    padding: .2rem .6rem;
    font-size: var(--ubc-fs-xs);
    font-weight: 600;
    line-height: 1.3;
    white-space: nowrap;
    border: 1px solid transparent;
}

.ubc-status--open {
    color: var(--c-green);
    background-color: var(--c-green-soft);
    border-color: var(--c-green);
}

.ubc-status--booked {
    color: var(--c-white);
    background-color: var(--c-green);
    border-color: var(--c-green);
}

.ubc-status--full,
.ubc-status--closed,
.ubc-status--cancelled {
    color: var(--c-red);
    background-color: var(--c-red-soft);
    border-color: var(--c-red);
}

.ubc-status--pending {
    color: var(--c-black);
    background-color: transparent;
    border-color: var(--c-black);
}

/* Filled black. Structural labels only -- a rank, a level, "you" -- never a
   status, so it can never be confused with one. */
.ubc-status--dark {
    color: var(--c-white);
    background-color: var(--c-black);
    border-color: var(--c-black);
}

.ubc-status--completed,
.ubc-status--neutral {
    color: var(--c-ink-2);
    background-color: var(--c-surface);
    border-color: var(--c-line);
}

/* Level requirement, e.g. "Intermediate+". Outline, never filled: a level is
   not a status and must not compete with one. */
.ubc-level-pill {
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    border: 1px solid var(--c-black);
    border-radius: 999px;
    padding: .1rem .55rem;
    font-size: var(--ubc-fs-xs);
    font-weight: 500;
    line-height: 1.4;
    color: var(--c-black);
    white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Date bar
   Full-width black bar between date groups on the trips list.
   -------------------------------------------------------------------------- */

.ubc-date-bar {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .75rem;
    background-color: var(--c-black);
    color: var(--c-white);
    border-radius: var(--r-control);
    padding: .65rem .85rem;
    margin: 1.25rem 0 .75rem;
}

.ubc-date-bar:first-child {
    margin-top: 0;
}

/* Michroma is wide, so this is the largest the date can be before a long one
   ("Wednesday, 30 September") wraps at 390px. */
.ubc-date-bar__date {
    font-family: var(--ubc-font-display);
    font-size: var(--ubc-fs-sm);
    letter-spacing: -0.02em;
}

.ubc-date-bar__count {
    font-size: var(--ubc-fs-xs);
    color: rgba(255, 255, 255, .7);
    white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Rows
   "Not everything is a card." Participants, sponsors, vehicles, settings and
   menus are rows separated by a hairline, inside one bordered container.
   -------------------------------------------------------------------------- */

.ubc-rows {
    background-color: var(--c-white);
    border: 1px solid var(--c-line);
    border-radius: var(--r-card);
    overflow: hidden;
}

.ubc-row {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .75rem;
    border-top: 1px solid var(--c-line);
    color: inherit;
    text-decoration: none;
    background-color: var(--c-white);
}

.ubc-row:first-child {
    border-top: 0;
}

a.ubc-row:hover,
a.ubc-row:focus {
    background-color: var(--c-surface);
    color: inherit;
}

.ubc-row__body {
    flex: 1 1 auto;
    min-width: 0;
}

.ubc-row__title {
    font-weight: 600;
    font-size: var(--ubc-fs-sm);
    line-height: 1.3;
}

.ubc-row__meta {
    font-size: var(--ubc-fs-xs);
    color: var(--c-ink-2);
}

.ubc-row__end {
    flex-shrink: 0;
    margin-left: auto;
    text-align: right;
}

/* A chevron on rows that navigate. */
.ubc-row__chevron {
    color: var(--c-ink-2);
    font-size: var(--ubc-fs-sm);
    flex-shrink: 0;
}

/* Trip detail: the gauge and the level/leader facts side by side in one row,
   rather than a card of mostly whitespace followed by more rows. */
.ubc-trip-facts {
    align-items: center;
    gap: .5rem;
}

.ubc-trip-facts__gauge {
    flex: 0 0 50%;
    min-width: 0;
}

.ubc-trip-facts__gauge .ubc-gauge {
    max-width: 100%;
}

.ubc-trip-facts__side {
    flex: 1 1 auto;
    min-width: 0;
}

/* A row that carries controls of its own (attendance radios, a remove form)
   wraps them onto their own full-width line rather than squeezing them in. */
.ubc-row--stacked {
    flex-wrap: wrap;
    align-items: flex-start;
}

.ubc-row__full {
    flex: 0 0 100%;
    margin-top: .5rem;
}

/* --------------------------------------------------------------------------
   Stat tiles
   The per-level attendance breakdown on a profile. A grid, not a list: the
   levels are a fixed small set and the numbers are meant to be compared.
   -------------------------------------------------------------------------- */

.ubc-stat-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
    gap: .5rem;
}

.ubc-stat-tile {
    border: 1px solid var(--c-line);
    border-radius: var(--r-control);
    padding: .5rem .4rem;
    text-align: center;
}

.ubc-stat-tile__label {
    font-size: var(--ubc-fs-xs);
    color: var(--c-ink-2);
    line-height: 1.3;
}

.ubc-stat-tile__value {
    font-size: var(--ubc-fs-xl);
    line-height: 1.2;
}

/* The total is the summary of the row, so it is the one that is filled. */
.ubc-stat-tile--total {
    background-color: var(--c-black);
    border-color: var(--c-black);
    color: var(--c-white);
}

.ubc-stat-tile--total .ubc-stat-tile__label {
    color: rgba(255, 255, 255, .7);
}

/* Leading icon on a row, sized so a column of them lines up. */
.ubc-row__icon {
    flex: 0 0 1.25rem;
    text-align: center;
    font-size: var(--ubc-fs-md);
    color: var(--c-ink-2);
}

/* --------------------------------------------------------------------------
   Member offer
   The one black block on a sponsor's page. It is the reason a member opened
   it, so it gets the strongest treatment on the screen.
   -------------------------------------------------------------------------- */

.ubc-offer {
    background-color: var(--c-black);
    color: var(--c-white);
    border-radius: var(--r-card);
    padding: .85rem 1rem;
}

.ubc-offer__label {
    font-size: var(--ubc-fs-xs);
    text-transform: uppercase;
    letter-spacing: .06em;
    color: rgba(255, 255, 255, .7);
    margin-bottom: .15rem;
}

.ubc-offer__body {
    font-size: var(--ubc-fs-md);
    font-weight: 500;
    line-height: 1.35;
}

/* Group heading above a set of rows. */
.ubc-rows-label {
    font-size: var(--ubc-fs-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--c-ink-2);
    margin: 1.25rem 0 .4rem;
}

/* --------------------------------------------------------------------------
   Capacity gauge
   Semicircular tick gauge on trip detail; a segmented bar on cards. Both are
   inline SVG so they need no images and inherit currentColor.
   -------------------------------------------------------------------------- */

.ubc-gauge {
    display: block;
    width: 100%;
    max-width: 220px;
    margin-inline: auto;
}

.ubc-gauge__tick {
    stroke: var(--c-line);
    stroke-width: 7;
    stroke-linecap: round;
}

.ubc-gauge__tick--filled {
    stroke: var(--c-black);
}

.ubc-gauge__tick--full {
    stroke: var(--c-red);
}

/* The member's own slot, so they can see themselves in the count. */
.ubc-gauge__tick--mine {
    stroke: var(--c-green);
}

/* Same reason as .ubc-num above: "0 / 6" is the gauge's most common reading. */
.ubc-gauge__value {
    font-family: var(--ubc-font-body);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    font-size: 20px;
    fill: var(--c-black);
}

.ubc-gauge__caption {
    font-family: var(--ubc-font-body);
    font-size: 12px;
    fill: var(--c-ink-2);
}

/* Compact segmented bar for cards. */
.ubc-capacity {
    display: flex;
    gap: 2px;
    height: 6px;
}

.ubc-capacity__seg {
    flex: 1 1 0;
    border-radius: 2px;
    background-color: var(--c-line);
}

.ubc-capacity__seg--filled {
    background-color: var(--c-black);
}

.ubc-capacity__seg--full {
    background-color: var(--c-red);
}

.ubc-capacity__seg--mine {
    background-color: var(--c-green);
}

/* --------------------------------------------------------------------------
   Member shell: top bar + scrollable content + bottom nav
   -------------------------------------------------------------------------- */

.ubc-app {
    min-height: 100dvh;
    display: flex;
    flex-direction: column;

    /* The centred column. Fixed-position chrome cannot inherit this, so the
       top bar, bottom nav and sticky action bar each repeat it below. */
    max-width: var(--ubc-shell-max);
    margin-inline: auto;
    width: 100%;
}

.ubc-topbar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1030;

    /* Match the shell rather than the viewport. */
    max-width: var(--ubc-shell-max);
    margin-inline: auto;
    height: calc(var(--ubc-topbar-h) + var(--ubc-safe-top));
    padding-top: var(--ubc-safe-top);
    background-color: var(--c-black);
    color: var(--c-white);
    display: flex;
    align-items: center;
    gap: .5rem;
    padding-inline: .75rem;
}

.ubc-topbar .ubc-topbar-title {
    font-family: var(--ubc-font-display);
    font-size: var(--ubc-fs-sm);
    font-weight: 400;
    letter-spacing: -0.01em;
    margin: 0;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ubc-topbar .btn {
    color: var(--c-white);
    border: 0;
    padding: .35rem .5rem;
    font-size: 1.25rem;
    line-height: 1;
    background: transparent;
}

.ubc-main {
    flex: 1 1 auto;
    padding-top: calc(var(--ubc-topbar-h) + var(--ubc-safe-top));
    padding-bottom: calc(var(--ubc-bottomnav-h) + var(--ubc-safe-bottom) + 1rem);
}

/* Pages with a sticky action bar (trip detail) add this class to .ubc-main. */
.ubc-main--sticky-action {
    padding-bottom: calc(var(--ubc-bottomnav-h) + var(--ubc-safe-bottom) + 5rem);
}

.ubc-bottomnav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1030;

    max-width: var(--ubc-shell-max);
    margin-inline: auto;

    background-color: var(--c-white);
    border-top: 1px solid var(--c-line);
    display: flex;
    /* The padding is what clears the iOS home indicator. */
    padding-bottom: var(--ubc-safe-bottom);
}

.ubc-bottomnav a {
    position: relative;
    flex: 1 1 0;
    height: var(--ubc-bottomnav-h);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    text-decoration: none;
    color: var(--c-ink-2);
    font-size: var(--ubc-fs-xs);
    font-weight: 500;
}

.ubc-bottomnav a i {
    font-size: 1.3rem;
    line-height: 1;
}

/* Active is black. The thin red bar above it is the only decorative use of
   red in the app, and it is doubled by the colour change, so red is never
   the sole signal. */
.ubc-bottomnav a.active {
    color: var(--c-black);
    font-weight: 600;
}

.ubc-bottomnav a.active::before {
    content: "";
    position: absolute;
    top: 0;
    left: 22%;
    right: 22%;
    height: 2px;
    border-radius: 0 0 2px 2px;
    background-color: var(--c-red);
}

/* --------------------------------------------------------------------------
   Floating action button
   The primary "add" on a list screen. Bottom-right, because the top-right
   corner of a 390px phone is the hardest place to reach one-handed. Flat
   black, no shadow (docs/UI-BRIEF.md).
   -------------------------------------------------------------------------- */

.ubc-fab {
    position: fixed;
    right: 1rem;
    bottom: calc(var(--ubc-bottomnav-h) + var(--ubc-safe-bottom) + 1rem);
    z-index: 1029;

    width: 56px;
    height: 56px;
    border-radius: 50%;
    border: 0;

    display: flex;
    align-items: center;
    justify-content: center;

    background-color: var(--c-black);
    color: var(--c-white);
    font-size: 1.5rem;
    line-height: 1;
    text-decoration: none;
}

.ubc-fab:hover,
.ubc-fab:focus {
    background-color: var(--c-black-soft);
    color: var(--c-white);
}

/* Keeps the button clear of the last row in the list behind it. */
.ubc-main--fab {
    padding-bottom: calc(var(--ubc-bottomnav-h) + var(--ubc-safe-bottom) + 5rem);
}

@media (min-width: 992px) {
    /* The bottom nav is a left sidebar up here, so the button returns to the
       bottom of the content column. */
    .ubc-fab {
        bottom: 1.5rem;
        right: max(1.5rem, calc(50vw - var(--ubc-shell-max) / 2));
    }

    .ubc-main--fab {
        padding-bottom: 6rem;
    }
}

/* Sticky per-page action bar, sits directly above the bottom nav. */
.ubc-sticky-action {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(var(--ubc-bottomnav-h) + var(--ubc-safe-bottom));
    z-index: 1029;

    max-width: var(--ubc-shell-max);
    margin-inline: auto;

    background-color: var(--c-white);
    border-top: 1px solid var(--c-line);
    padding: .65rem .75rem;
}

/* --------------------------------------------------------------------------
   Phone and tablet

   Nothing to do: the shell fills the viewport from --ubc-shell-max above, and
   the only inset is the standard 16px page gutter on the content container.

   There used to be a 768px block here that capped the shell at 600px and
   framed it on a darker page, meant to read as a deliberate app column. On an
   820px tablet that looked like a phone emulator parked mid-screen, and
   because it only started at 768px it also left everything from 600px to
   767px boxed with no way out. Capping the text column at 760px instead was
   no better -- still a margin down both sides of a device with no sidebar to
   balance it.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Desktop (>=992px)
   The bottom nav becomes a left sidebar, the content column widens to 760px,
   and trip lists go two-up. The DOM does not change -- only the layout.
   -------------------------------------------------------------------------- */

@media (min-width: 992px) {
    :root {
        --ubc-shell-max: 760px;

        /* Wider than the phone nav was: this now holds the full menu. */
        --ubc-sidebar-w: 280px;

        /* Left edge of the content column, in viewport coordinates. It is
           centred on the viewport -- not on the space beside the sidebar --
           so the page does not read as pushed to the right, and it never
           slides under the sidebar on a narrow desktop. */
        --ubc-content-left: max(var(--ubc-sidebar-w), calc(50vw - var(--ubc-shell-max) / 2));
    }

    body {
        background-color: var(--c-surface);
    }

    /* width:auto, not the inherited 100%. With `width: 100%` the element was
       a full viewport wide AND pushed over by margin-left, overflowing by
       exactly the sidebar width and putting a horizontal scrollbar on every
       desktop page. */
    .ubc-app {
        width: auto;
        max-width: none;
        margin-left: var(--ubc-sidebar-w);
        background-color: transparent;
        box-shadow: none;
    }

    .ubc-topbar {
        left: var(--ubc-sidebar-w);
        max-width: none;
        margin-inline: 0;
        padding-inline: 1.25rem;
    }

    .ubc-bottomnav {
        display: none;
    }

    /* .ubc-app is already offset by the sidebar, so subtract it back out. */
    .ubc-main > .container-fluid,
    .ubc-main > .ubc-hero {
        max-width: var(--ubc-shell-max);
        margin-left: calc(var(--ubc-content-left) - var(--ubc-sidebar-w));
        margin-right: auto;
    }

    .ubc-main {
        padding-bottom: 2rem;
    }

    .ubc-main--sticky-action {
        padding-bottom: 6rem;
    }

    /* Fixed, so it is positioned against the viewport and lines up with the
       content column rather than with .ubc-app. */
    .ubc-sticky-action {
        left: var(--ubc-content-left);
        right: auto;
        bottom: 0;
        width: var(--ubc-shell-max);
        max-width: calc(100vw - var(--ubc-sidebar-w));
        margin-inline: 0;
        border: 1px solid var(--c-line);
        border-bottom: 0;
        border-radius: var(--r-sheet) var(--r-sheet) 0 0;
    }

    /* Trip lists go two-up. auto-fit rather than a fixed pair of tracks: a
       date with a single trip gets one full-width card instead of a card
       floating in half the row. */
    .ubc-trip-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
        gap: 1rem;
    }

    .ubc-trip-grid > .ubc-card {
        margin-bottom: 0 !important;
    }

    /* An 88px thumbnail looks lost on a card that can be 725px wide when a
       date has only one trip. */
    .ubc-trip-card__media {
        flex-basis: 132px;
        width: 132px;
    }
}

/* --------------------------------------------------------------------------
   Drawer
   -------------------------------------------------------------------------- */

.ubc-drawer {
    background-color: var(--c-black);
    color: var(--c-white);
    width: 82vw;
    max-width: 320px;
}

.ubc-drawer .offcanvas-header {
    padding-top: calc(1rem + var(--ubc-safe-top));
    border-bottom: 1px solid rgba(255, 255, 255, .12);
}

.ubc-drawer .offcanvas-title,
.ubc-drawer .ubc-drawer-brand {
    font-family: var(--ubc-font-display);
    font-size: var(--ubc-fs-sm);
    font-weight: 400;
    color: var(--c-white);
}

.ubc-drawer .nav-link {
    color: rgba(255, 255, 255, .85);
    padding: .6rem .25rem;
    min-height: 44px;
    display: flex;
    align-items: center;
    gap: .75rem;
    font-size: var(--ubc-fs-sm);
}

.ubc-drawer .nav-link:hover,
.ubc-drawer .nav-link.active {
    color: var(--c-white);
    font-weight: 600;
}

.ubc-drawer .nav-link i {
    width: 1.25rem;
    text-align: center;
    font-size: 1.05rem;
}

.ubc-drawer hr {
    border-color: rgba(255, 255, 255, .15);
    opacity: 1;
}

/* Identity row under the drawer header. */
.ubc-drawer-identity {
    display: flex;
    align-items: center;
    gap: .65rem;
    padding: .75rem 1rem;
    min-height: 44px;
    color: var(--c-white);
    text-decoration: none;
    border-bottom: 1px solid rgba(255, 255, 255, .12);
}

.ubc-drawer-identity:hover,
.ubc-drawer-identity:focus {
    background-color: rgba(255, 255, 255, .06);
    color: var(--c-white);
}

.ubc-drawer-identity > .bi {
    font-size: 2.1rem;
    line-height: 1;
    color: rgba(255, 255, 255, .6);
}

.ubc-drawer-identity__body {
    flex: 1 1 auto;
    min-width: 0;
}

.ubc-drawer-identity__name {
    display: block;
    font-size: var(--ubc-fs-sm);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ubc-drawer-identity__meta {
    display: block;
    font-size: var(--ubc-fs-xs);
    color: rgba(255, 255, 255, .6);
}

/* The level pill is black-on-white by default, which disappears on the black
   drawer. Same shape, inverted. */
.ubc-level-pill--ondark {
    border-color: rgba(255, 255, 255, .6);
    color: var(--c-white);
}

.ubc-drawer-section {
    font-size: var(--ubc-fs-xs);
    text-transform: uppercase;
    letter-spacing: .08em;
    color: rgba(255, 255, 255, .45);
    margin: 1rem 0 .35rem;
}

/* --------------------------------------------------------------------------
   The drawer becomes the desktop sidebar (>=992px).

   It used to be the bottom nav that turned into a sidebar, which left two
   menus on screen at once: four items in the sidebar, and the same four plus
   Sponsors, Marshals and the content pages behind the hamburger. The drawer
   is the complete menu, so it is the one that stays; the bottom nav and the
   hamburger both hide. Same arrangement as the admin shell.

   This block sits after the base .ubc-drawer rules on purpose: a media query
   adds no specificity, so declaring it earlier let `width: 82vw; max-width:
   320px` win and the sidebar came out 320px wide, clipping the top bar.
   -------------------------------------------------------------------------- */

@media (min-width: 992px) {
    .ubc-drawer {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        width: var(--ubc-sidebar-w);
        max-width: none;
        z-index: 1035;

        display: flex;
        flex-direction: column;
        overflow-y: auto;

        /* Bootstrap sets transparent !important on a responsive offcanvas
           above its breakpoint, which strips the background while leaving the
           white text -- the same trap the admin sidebar hit. */
        background-color: var(--c-black) !important;
        transform: none !important;
        visibility: visible !important;
        border-right: 1px solid rgba(255, 255, 255, .12);
    }

    /* Bootstrap hides the header of a responsive offcanvas above the
       breakpoint. Here it is the brand block, so it has to come back. */
    .ubc-drawer .offcanvas-header {
        display: flex !important;
        padding-top: 1rem;
    }

    .ubc-drawer .offcanvas-body {
        flex-grow: 1 !important;
        padding: 1rem !important;
        overflow-y: visible;
        background-color: transparent !important;
    }
}

/* --------------------------------------------------------------------------
   Admin shell
   -------------------------------------------------------------------------- */

.ubc-admin {
    min-height: 100dvh;
}

.ubc-admin h1,
.ubc-admin h2,
.ubc-admin h3 {
    font-family: var(--ubc-font-display);
}

.ubc-admin-sidebar {
    background-color: var(--c-black);
    color: var(--c-white);
    width: 250px;
}

.ubc-admin-sidebar .nav-link {
    color: rgba(255, 255, 255, .8);
    border-radius: var(--r-control);
    padding: .55rem .75rem;
    min-height: 44px;
    display: flex;
    align-items: center;
    gap: .65rem;
    font-size: var(--ubc-fs-sm);
}

.ubc-admin-sidebar .nav-link:hover {
    background-color: rgba(255, 255, 255, .08);
    color: var(--c-white);
}

/* Active is an inverted chip -- black on white -- with a red leading edge,
   matching the member nav. No accent colour. */
.ubc-admin-sidebar .nav-link.active {
    position: relative;
    background-color: var(--c-white);
    color: var(--c-black);
    font-weight: 600;
}

.ubc-admin-sidebar .nav-link.active::before {
    content: "";
    position: absolute;
    top: .3rem;
    bottom: .3rem;
    left: 0;
    width: 3px;
    border-radius: 0 2px 2px 0;
    background-color: var(--c-red);
}

.ubc-admin-sidebar .ubc-admin-brand {
    font-family: var(--ubc-font-display);
    font-size: var(--ubc-fs-sm);
    color: var(--c-white);
}

@media (min-width: 992px) {
    .ubc-admin-content {
        margin-left: 250px;
    }

    .ubc-admin-sidebar {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        overflow-y: auto;

        /* Bootstrap sets `background-color: transparent !important` on a
           responsive offcanvas above its breakpoint, which stripped the dark
           background while leaving the white text -- making every sidebar
           link invisible on desktop. Only !important can win that back. */
        background-color: var(--c-black) !important;
    }
}

/* Admin tables scroll sideways on small screens rather than squashing.
   The wrapper alone was not enough: Bootstrap's .table is width:100%, so the
   table shrank to fit and wrapped titles one word per line instead of
   overflowing. The min-width is what makes it scroll. */
.table-responsive-card {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

.table-responsive-card > .table {
    min-width: 560px;
}

.ubc-admin .table {
    --bs-table-border-color: var(--c-line);
    font-size: var(--ubc-fs-sm);
}

.ubc-admin .table > thead th {
    font-size: var(--ubc-fs-xs);
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--c-ink-2);
    font-weight: 600;
}

/* --------------------------------------------------------------------------
   Auth shell
   -------------------------------------------------------------------------- */

.ubc-auth {
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: calc(1.5rem + var(--ubc-safe-top)) 1rem calc(1.5rem + var(--ubc-safe-bottom));
    background-color: var(--c-black);
}

.ubc-auth-card {
    width: 100%;
    max-width: 420px;
    margin-inline: auto;
    background-color: var(--c-white);
    border-radius: var(--r-sheet);
    padding: 1.5rem;
}

.ubc-auth-logo {
    text-align: center;
    color: var(--c-white);
    margin-bottom: 1.25rem;
}

.ubc-auth-logo .ubc-auth-title {
    font-family: var(--ubc-font-display);
    font-size: var(--ubc-fs-lg);
    font-weight: 400;
    margin: 0;
}

/* --------------------------------------------------------------------------
   Bits used across screens
   -------------------------------------------------------------------------- */

.ubc-card {
    background-color: var(--c-white);
    border: 1px solid var(--c-line);
    border-radius: var(--r-card);
    overflow: hidden;
}

/* --------------------------------------------------------------------------
   Featured sponsor
   A large card above the sponsor list, where everything else is a row. The
   club's own entry uses this. Logo on a white panel, because sponsor logos
   are stored "contain" on white and several will be dark artwork.
   -------------------------------------------------------------------------- */

.ubc-featured {
    display: block;
    text-decoration: none;
    color: inherit;
}

.ubc-featured:hover,
.ubc-featured:focus {
    color: inherit;
}

.ubc-featured__media {
    background-color: var(--c-white);
    border-bottom: 1px solid var(--c-line);
    padding: 1.25rem;
    min-height: 132px;

    display: flex;
    align-items: center;
    justify-content: center;

    color: var(--c-ink-2);
    font-size: 2.5rem;
}

.ubc-featured__media img {
    max-width: 180px;
    max-height: 132px;
    width: auto;
    height: auto;
    object-fit: contain;
    display: block;
}

.ubc-featured__body {
    padding: .9rem 1rem 1rem;
}

.ubc-featured__name {
    font-family: var(--ubc-font-display);
    font-weight: 400;
    font-size: var(--ubc-fs-md);
    line-height: 1.3;
}

/* --------------------------------------------------------------------------
   Trip card
   Thumbnail left, content right. A full-width 16:9 cover put barely two
   trips on a phone screen; this puts four or five there, and the detail page
   is where the cover gets to be big.
   -------------------------------------------------------------------------- */

.ubc-trip-card {
    display: flex;
    align-items: stretch;
    gap: 0;
}

.ubc-trip-card__media {
    flex: 0 0 88px;
    width: 88px;
    background-color: var(--c-surface);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--c-ink-2);
    font-size: 1.6rem;
    border-right: 1px solid var(--c-line);
}

.ubc-trip-card__media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.ubc-trip-card__body {
    flex: 1 1 auto;
    min-width: 0;
    padding: .7rem .75rem;
}

/* Michroma is wide, so a long trip title is clamped rather than allowed to
   push the card to three lines of heading. */
.ubc-trip-card__title {
    font-family: var(--ubc-font-display);
    font-weight: 400;
    font-size: var(--ubc-fs-sm);
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.modal-content,
.offcanvas.offcanvas-bottom {
    border-radius: var(--r-sheet);
}

.ubc-avatar {
    border-radius: 50%;
    object-fit: cover;
    background-color: var(--c-line);
}

/* --------------------------------------------------------------------------
   Banner carousel

   An earlier attempt let the next slide peek in at the trailing edge, copying
   the reference app. It cannot work with Bootstrap's carousel: inactive items
   are `display: none`, so no amount of `overflow: visible` reveals them. The
   indicators carry the "there is more" signal instead.
   -------------------------------------------------------------------------- */

/* The hero sits directly under the fixed top bar, which gives it no breathing
   room at all -- the carousel was flush against the bar on every screen. */
.ubc-hero {
    padding-top: .75rem;
}

.ubc-banners {
    padding-inline: .75rem;
}

.ubc-banners .carousel-inner {
    border-radius: var(--r-card);
}

/* The indicators sit below the carousel, not on top of it.

   Overlaid they were white dots on the image, which vanish completely on a
   light banner -- the club's own slide is white. Below the image they are on
   the app's own background, so they read the same whatever the banner is, and
   they get natural space above them instead of sitting flush on the artwork. */
.ubc-banners .carousel-indicators {
    position: static;
    margin: .6rem 0 0;
    padding: 0;
}

.ubc-banners .carousel-indicators [data-bs-target] {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    margin-inline: 3px;

    background-color: var(--c-black);
    opacity: .22;

    /* Bootstrap gives these transparent top and bottom borders to enlarge the
       tap target while background-clip keeps the dot small. Keep that, just
       less of it, or the row of dots is taller than the gap it sits in. */
    border-top-width: 9px;
    border-bottom-width: 9px;
}

.ubc-banners .carousel-indicators .active {
    opacity: 1;
}

/* Toast stack clears the top bar. */
.ubc-toasts {
    position: fixed;
    top: calc(var(--ubc-topbar-h) + var(--ubc-safe-top) + .5rem);
    left: 50%;
    transform: translateX(-50%);
    z-index: 1090;
    width: min(92vw, 420px);
}

.ubc-toasts .toast {
    border-radius: var(--r-control);
}

/* Inputs at 16px or larger, otherwise iOS Safari zooms on focus. */
.form-control,
.form-select {
    font-size: 16px;
}

/* --------------------------------------------------------------------------
   File picker
   The real <input type="file"> is visually hidden and driven by its <label>,
   so it keeps native keyboard and screen-reader behaviour while looking like
   the rest of the form.
   -------------------------------------------------------------------------- */

.ubc-filepick {
    display: flex;
    align-items: center;
    gap: .75rem;
}

.ubc-filepick__preview {
    width: 72px;
    height: 72px;
    flex-shrink: 0;
    object-fit: cover;
    border-radius: var(--r-control);
    background-color: var(--c-surface);
}

.ubc-filepick__body {
    min-width: 0;
}

/* The hidden input still takes focus, so the label has to show the ring. */
.ubc-filepick__body .btn:focus-visible,
.visually-hidden:focus-visible + .ubc-filepick .btn,
input[data-filepick-input]:focus-visible ~ .ubc-filepick .btn {
    outline: 2px solid var(--c-black);
    outline-offset: 2px;
}

/* intl-tel-input needs to fill the Bootstrap input group. */
.iti {
    width: 100%;
}
