/* =========================================================================
   TransitLive Web — App shell
   Ports RootTabView (top app bar + status badge), NetworkModePicker and
   FloatingTabBar.CustomGlassTabBar. Everything resolves through tokens.css.

   Layout model mirrors the app: a fixed top bar, a full-bleed pane stack
   underneath it, and a floating tab bar over the top. All four panes stay
   mounted and swap by opacity so the map keeps its pan/zoom state.
   ========================================================================= */

*, *::before, *::after { box-sizing: border-box; }

/* Form controls do not inherit the page font by default, which quietly breaks
   anything sized in `em` inside them — the app-bar gear rendered at 16.7px
   against everything else's 20px for exactly this reason. */
button, input, select, textarea { font: inherit; }

html, body {
    height: 100%;
    margin: 0;
    overscroll-behavior: none;
}

.app-body {
    background: var(--ds-bg-app);
    color: var(--ds-label-primary);
    font-family: var(--ds-font-sans);
    font-size: var(--ds-fs-15);
    line-height: var(--ds-lh-body);
    -webkit-font-smoothing: antialiased;
    -webkit-tap-highlight-color: transparent;
    display: flex;
    flex-direction: column;
}

.hidden { display: none !important; }

.icon { width: 1.25em; height: 1.25em; display: block; }
.icon--sm { width: 1em; height: 1em; }

/* Shared glass recipe — the .ultraThinMaterial port. Never stack these. */
.glass {
    background: var(--ds-glass-fill-material);
    -webkit-backdrop-filter: var(--ds-glass-blur);
    backdrop-filter: var(--ds-glass-blur);
    border: var(--ds-stroke-hairline) solid var(--ds-glass-border);
    box-shadow: var(--ds-shadow-glass);
}

/* =========================================================================
   TOP APP BAR
   ========================================================================= */
.app-bar {
    position: relative;
    z-index: 30;
    flex: 0 0 auto;
    padding: calc(var(--ds-safe-top) + var(--ds-space-8)) var(--ds-gutter-header) var(--ds-space-12);
    background: var(--ds-bg-app);
}

.app-bar__row {
    display: flex;
    align-items: flex-end;
    gap: var(--ds-space-8);
    min-height: 44px;
}

/* Brand mark. Transparent — no tile — so the mark itself carries the colour.
   It lives INSIDE the h1 and is sized in `em`, so it scales with the title's
   clamp() and stays optically centred on the wordmark at every viewport width.
   Sizing it in px as a sibling left it overshooting the cap height. The art is
   1000x850, so the box keeps that ratio or the mark is letterboxed. */
.app-bar__logo {
    flex: 0 0 auto;
    height: 1.16em;
    width: calc(1.16em * 1000 / 850);
    display: block;
    /* The mark's own art has more headroom than the wordmark's cap height;
       this nudge lines the two optical centres up. */
    margin-top: -0.06em;
    --logo-body: var(--ds-logo-body);
    --logo-wings: var(--ds-logo-body);
    --logo-halo: var(--ds-logo-halo);
}

.app-bar__title {
    display: flex;
    align-items: center;
    gap: 0.34em;
    margin: 0;
    /* The app uses .minimumScaleFactor(0.5) so the title shrinks rather than
       truncating on narrow devices; clamp is the web equivalent. */
    font-size: clamp(1.5rem, 7.4vw, var(--ds-fs-34));
    font-weight: var(--ds-fw-bold);
    letter-spacing: var(--ds-tracking-title);
    line-height: 1.05;
    color: var(--ds-label-primary);
    flex: 1 1 auto;
    min-width: 0;
}

.app-bar__wordmark {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.app-bar__actions {
    display: flex;
    align-items: center;
    gap: var(--ds-space-8);
    flex: 0 0 auto;
}

/* --- Status badge (alert count / previewing) --------------------------- */
.status-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-6);
    padding: 5px var(--ds-space-10);
    border-radius: var(--ds-radius-pill);
    font-size: var(--ds-fs-12);
    font-weight: var(--ds-fw-semibold);
    font-variant-numeric: var(--ds-numeric);
    white-space: nowrap;
}

.status-badge--alerts {
    color: var(--ds-text-red);
    background: rgba(239, 68, 68, 0.14);
}

.status-badge--alerts::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--ds-alert-red);
    box-shadow: var(--ds-glow-alert-dot);
}

.status-badge--clear {
    color: var(--ds-text-green);
    background: rgba(48, 209, 88, 0.14);
}

.status-badge--preview {
    color: var(--ds-alert-orange);
    background: rgba(255, 159, 28, 0.12);
    font-weight: var(--ds-fw-bold);
}

.status-badge--preview .icon {
    animation: badge-pulse var(--ds-dur-pulse-badge) var(--ds-ease-in-out) infinite alternate;
}

@keyframes badge-pulse { from { opacity: 1; } to { opacity: 0.3; } }

/* --- Circular glass button (settings / sheet actions) ------------------ */
.glass-icon-btn {
    width: 36px;
    height: 36px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    border: var(--ds-stroke-hairline) solid var(--ds-hairline);
    background: var(--ds-glass-fill-material);
    -webkit-backdrop-filter: var(--ds-glass-blur);
    backdrop-filter: var(--ds-glass-blur);
    color: var(--ds-label-primary);
    cursor: pointer;
    padding: 0;
    transition: transform var(--ds-dur-fast) var(--ds-ease-smooth),
                background var(--ds-dur-fast) var(--ds-ease-smooth);
}

.glass-icon-btn:active { transform: scale(0.92); }

/* Explicit, so this matches the tab bar and the map FABs rather than tracking
   whatever font-size happens to apply. */
.glass-icon-btn .icon { width: 20px; height: 20px; }

/* =========================================================================
   NETWORK MODE PICKER  (TTC / GO)
   Port of NetworkModePicker: a glass capsule with a sliding thumb.
   ========================================================================= */
/* Three columns so the picker stays optically centred while the badge sits
   at the right edge — a plain flex row with the badge appended would shove
   the picker left by the badge's width. */
.app-bar__picker {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: var(--ds-space-8);
    margin-top: var(--ds-space-10);
}

.app-bar__picker .status-badge { justify-self: end; }

/* Below ~360px the badge and picker cannot both fit on one line; the badge is
   the one that can wrap without breaking navigation. */
@media (max-width: 359px) {
    .app-bar__picker { grid-template-columns: 1fr; justify-items: center; }
    .app-bar__picker-spacer { display: none; }
    .app-bar__picker .status-badge { justify-self: center; }
}

.mode-picker {
    position: relative;
    display: grid;
    grid-template-columns: 1fr 1fr;
    /* NetworkModePicker is .frame(width: 160) in the app — the full-width
       version read as a slab and washed out against the map behind it. */
    width: var(--ds-modepicker-w);
    padding: 3px;
    border-radius: var(--ds-radius-pill);
    background: var(--ds-picker-track);
    -webkit-backdrop-filter: var(--ds-glass-blur);
    backdrop-filter: var(--ds-glass-blur);
    border: var(--ds-stroke-hairline) solid var(--ds-hairline);
    isolation: isolate;
}

.mode-picker__thumb {
    position: absolute;
    z-index: 0;
    top: 3px;
    bottom: 3px;
    left: 3px;
    width: calc(50% - 3px);
    border-radius: var(--ds-radius-pill);
    background: var(--ds-picker-thumb);
    box-shadow: var(--ds-shadow-pill);
    transition: transform var(--ds-dur-select) var(--ds-ease-smooth);
}

.mode-picker[data-network="go"] .mode-picker__thumb { transform: translateX(100%); }

.mode-picker__seg {
    position: relative;
    z-index: 1;
    appearance: none;
    border: 0;
    background: transparent;
    padding: var(--ds-space-6) 0;
    font-family: inherit;
    font-size: var(--ds-fs-15);
    font-weight: var(--ds-fw-bold);
    color: var(--ds-label-primary);
    opacity: var(--ds-opacity-mode-unselected);
    cursor: pointer;
    transition: opacity var(--ds-dur-select) var(--ds-ease-smooth);
}

.mode-picker__seg.is-active { opacity: 1; }

/* =========================================================================
   PANES
   All mounted; inactive ones lose opacity and pointer events rather than
   being unmounted, so map transform state survives a tab switch.
   ========================================================================= */
.app-panes {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
}

.pane {
    position: absolute;
    inset: 0;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--ds-dur-select) var(--ds-ease-smooth);
}

.pane.is-active {
    opacity: 1;
    pointer-events: auto;
}

/* `hidden` is only used before a pane's first activation, so a pane that has
   never been shown costs nothing. Once mounted it stays in the layer stack. */
.pane[hidden] { display: none; }

.pane--list {
    display: flex;
    flex-direction: column;
    background: var(--ds-bg-grouped);
}

.pane__scroll {
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0 var(--ds-gutter-page);
    /* Clear the floating tab bar. */
    padding-bottom: calc(var(--ds-safe-bottom) + 96px);
    scrollbar-width: none;
}

.pane__scroll::-webkit-scrollbar { display: none; }

.card-list {
    display: flex;
    flex-direction: column;
    gap: var(--ds-space-12);
    max-width: var(--ds-content-max);
    margin: 0 auto;
    padding-top: var(--ds-space-4);
}

/* =========================================================================
   FILTER RAIL  (All / Line 1 / Line 2 …)
   ========================================================================= */
.filter-rail {
    flex: 0 0 auto;
    display: flex;
    gap: var(--ds-space-10);
    padding: var(--ds-space-4) var(--ds-gutter-page) var(--ds-space-14);
    overflow-x: auto;
    scrollbar-width: none;
    background: var(--ds-bg-grouped);
}

.filter-rail::-webkit-scrollbar { display: none; }

.filter-chip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-8);
    padding: var(--ds-space-10) var(--ds-space-16);
    border-radius: var(--ds-radius-pill);
    border: var(--ds-stroke-hairline) solid var(--ds-hairline);
    background: var(--ds-fill-subtle);
    color: var(--ds-label-secondary);
    font-size: var(--ds-fs-15);
    font-weight: var(--ds-fw-semibold);
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--ds-dur-chip) var(--ds-ease-smooth),
                color var(--ds-dur-chip) var(--ds-ease-smooth);
}

.filter-chip.is-active {
    background: var(--ds-fill-active);
    color: var(--ds-label-primary);
}

.filter-chip__dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--chip-color, var(--ds-label-tertiary));
    flex: 0 0 auto;
}

.filter-chip__count {
    font-size: var(--ds-fs-13);
    font-weight: var(--ds-fw-semibold);
    color: var(--ds-label-tertiary);
    font-variant-numeric: var(--ds-numeric);
}

.filter-chip.is-active .filter-chip__count { color: var(--ds-label-secondary); }

/* =========================================================================
   FLOATING TAB BAR
   ========================================================================= */
.tab-bar {
    position: fixed;
    z-index: 40;
    left: 50%;
    bottom: calc(var(--ds-safe-bottom) + var(--ds-space-12));
    transform: translateX(-50%);
    width: calc(100% - var(--ds-space-32));
    max-width: var(--ds-tabbar-max);
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    padding: var(--ds-space-6);
    border-radius: var(--ds-radius-pill);
    background: var(--ds-glass-tint-bar);
    box-shadow: var(--ds-shadow-tabbar);
    isolation: isolate;
    transition: opacity var(--ds-dur-select) var(--ds-ease-smooth),
                transform var(--ds-dur-select) var(--ds-ease-smooth);
}

/* On Buses the bar lives inside the sheet, exactly like SurfaceMapTab —
   in NORMAL FLOW between the search row and the scrolling list. Positioning
   it absolutely inside the panel made it overlap the search field and the
   route chips the moment the panel was dragged open. */
.tab-bar.is-docked {
    /* relative, not static: .tab-bar__pill is absolutely positioned against
       the bar, and a static bar would hand it .bus-panel as the containing
       block — the pill then stretched the full height of the panel. */
    position: relative;
    transform: none;
    left: auto;
    bottom: auto;
    flex: 0 0 auto;
    margin: 0 auto var(--ds-space-12);
    box-shadow: none;
    background: transparent;
    border-color: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

.tab-bar__pill {
    position: absolute;
    z-index: 0;
    top: var(--ds-space-6);
    bottom: var(--ds-space-6);
    left: var(--ds-space-6);
    width: calc((100% - var(--ds-space-12)) / 4);
    border-radius: var(--ds-radius-pill);
    background: var(--ds-picker-thumb);
    box-shadow: var(--ds-shadow-pill);
    transition: transform var(--ds-dur-select) var(--ds-ease-smooth);
}

.tab-bar__tab {
    position: relative;
    z-index: 1;
    appearance: none;
    border: 0;
    background: transparent;
    padding: var(--ds-space-12) 0;
    display: grid;
    place-items: center;
    color: var(--ds-label-primary);
    opacity: var(--ds-opacity-tab-unselected);
    cursor: pointer;
    transition: opacity var(--ds-dur-select) var(--ds-ease-smooth);
}

.tab-bar__tab.is-active { opacity: 1; }
.tab-bar__tab .icon { width: 20px; height: 20px; }

/* =========================================================================
   TOASTS  (AlertClearedBanner port)
   ========================================================================= */
.toast-host {
    position: fixed;
    z-index: 60;
    top: calc(var(--ds-safe-top) + 100px);
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--ds-space-8);
    pointer-events: none;
    padding: 0 var(--ds-space-32);
}

.toast {
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-10);
    padding: var(--ds-space-12) var(--ds-space-16);
    border-radius: var(--ds-radius-pill);
    background: var(--ds-glass-fill-material);
    -webkit-backdrop-filter: var(--ds-glass-blur);
    backdrop-filter: var(--ds-glass-blur);
    border: var(--ds-stroke-hairline) solid var(--ds-hairline);
    box-shadow: var(--ds-shadow-toast);
    font-size: var(--ds-fs-14);
    font-weight: var(--ds-fw-semibold);
    animation: toast-in var(--ds-dur-list) var(--ds-ease-spring-soft);
}

.toast--ok { color: var(--ds-label-primary); }
.toast--ok .icon { color: var(--ds-system-green); }
.toast--err .icon { color: var(--ds-alert-red); }

.toast.is-leaving { animation: toast-out var(--ds-dur-scene) var(--ds-ease-in-out) forwards; }

@keyframes toast-in { from { opacity: 0; transform: translateY(-16px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(-12px); } }

/* =========================================================================
   ADAPTIVE DESKTOP
   Same design language, reflowed: the map fills the viewport, the list
   panes become a left rail beside it, and the tab bar keeps its capsule
   but anchors bottom-left rather than centre.
   ========================================================================= */
@media (min-width: 900px) {
    .app-body { --ds-content-max: 460px; }

    .app-bar {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        display: flex;
        align-items: center;
        gap: var(--ds-space-24);
        padding: var(--ds-space-12) var(--ds-space-24);
        background: linear-gradient(to bottom, var(--ds-bg-app) 55%, transparent);
        pointer-events: none;
    }

    .app-bar > * { pointer-events: auto; }
    .app-bar__row { flex: 0 0 auto; min-height: 0; }
    .app-bar__title { font-size: var(--ds-fs-28); }
    .app-bar__picker {
        margin-top: 0;
        flex: 0 0 auto;
        /* No centring column needed here — the bar itself is a row, so the
           picker and badge simply sit side by side after the title. */
        grid-template-columns: auto auto;
        gap: var(--ds-space-12);
    }
    .app-bar__picker-spacer { display: none; }
    .app-bar__picker .status-badge { justify-self: start; }
    .mode-picker { width: var(--ds-modepicker-w); }


    .app-bar__actions { order: 3; margin-left: auto; }

    .app-panes { position: fixed; inset: 0; }

    /* Map is always the ground layer on desktop; list panes float over it
       as a rail so you never lose sight of the network. */
    .pane--map { opacity: 1 !important; pointer-events: auto !important; }

    .pane--list {
        left: auto;
        width: min(440px, 38vw);
        right: var(--ds-space-24);
        top: 88px;
        bottom: var(--ds-space-24);
        background: var(--ds-glass-fill-material);
        -webkit-backdrop-filter: var(--ds-glass-blur-heavy);
        backdrop-filter: var(--ds-glass-blur-heavy);
        border: var(--ds-stroke-hairline) solid var(--ds-hairline);
        border-radius: var(--ds-radius-28);
        box-shadow: var(--ds-shadow-peek);
        overflow: hidden;
        transform: translateX(12px);
        transition: opacity var(--ds-dur-select) var(--ds-ease-smooth),
                    transform var(--ds-dur-select) var(--ds-ease-smooth);
    }

    .pane--list.is-active { transform: none; }
    .pane__scroll { padding-bottom: var(--ds-space-24); }
    .filter-rail { background: transparent; padding-top: var(--ds-space-16); }
    .pane--list, .filter-rail { background-color: transparent; }
    .pane--list { background: var(--ds-glass-fill-material); }

    .tab-bar {
        left: var(--ds-space-24);
        transform: none;
        bottom: var(--ds-space-24);
        width: var(--ds-tabbar-max);
    }

    .toast-host { top: 88px; }
}

/* =========================================================================
   SPLASH  (port of SplashScreenView)
   The mark alone on the app ground, covering the gap before the first data
   arrives. It fades out and is then removed from the DOM entirely, so it can
   never sit invisibly over the app eating pointer events.
   ========================================================================= */
.splash {
    position: fixed;
    inset: 0;
    z-index: 90;
    display: grid;
    place-items: center;
    background: var(--ds-bg-app);
    transition: opacity var(--ds-dur-scene) var(--ds-ease-smooth);
}

.splash.is-leaving { opacity: 0; }

.splash__logo {
    width: min(38vw, 168px);
    height: auto;
    aspect-ratio: 1000 / 850;
    --logo-body: var(--ds-logo-body);
    --logo-wings: var(--ds-logo-body);
    --logo-halo: var(--ds-logo-halo);
    animation: splash-breathe 1.8s var(--ds-ease-in-out) infinite alternate;
}

@keyframes splash-breathe {
    from { opacity: 0.72; transform: scale(0.985); }
    to   { opacity: 1;    transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .splash__logo { animation: none; }
}

/* =========================================================================
   KO-FI LINK
   Sits in the picker row's left column — the slot that was previously an empty
   spacer keeping the picker optically centred, so adding this costs no layout.
   ========================================================================= */
.kofi-link {
    justify-self: start;
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-6);
    padding: var(--ds-space-5) var(--ds-space-10);
    border-radius: var(--ds-radius-pill);
    background: var(--ds-fill-subtle);
    border: var(--ds-stroke-hairline) solid var(--ds-hairline);
    color: var(--ds-label-secondary);
    font-size: var(--ds-fs-12);
    font-weight: var(--ds-fw-semibold);
    text-decoration: none;
    white-space: nowrap;
    transition: color var(--ds-dur-fast) var(--ds-ease-smooth),
                background var(--ds-dur-fast) var(--ds-ease-smooth);
}

.kofi-link:hover,
.kofi-link:focus-visible {
    color: var(--ds-label-primary);
    background: var(--ds-fill-neutral);
}

/* Ko-fi's own cup mark, the same asset their widget_2 button uses — kept so
   the link still reads as Ko-fi rather than a generic coffee glyph. */
.kofi-link__cup {
    width: 17px;
    height: 17px;
    flex: 0 0 auto;
    display: block;
}

/* On a narrow phone the label is the first thing to go — the cup still reads,
   and the picker keeps its full width. */
@media (max-width: 419px) {
    .kofi-link__label { display: none; }
    .kofi-link { padding: var(--ds-space-5) var(--ds-space-8); }
}

@media (max-width: 359px) {
    .app-bar__picker .kofi-link { justify-self: center; }
}

/* Visually hidden but present for screen readers and crawlers (used by the
   crawlable site summary in app.html). The clip pattern, not display:none,
   so the text stays in the accessibility tree and the indexed DOM. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
