/*
 * Liftoff Marina Web — brand stylesheet.
 *
 * Mirrors the iOS/Android Brand object:
 *   primary    #1A1A1A  (near-black — buttons, headers)
 *   secondary  #FF8800  (orange CTA — accent + toggles)
 *   sand       #F7F4EE  (page background)
 *   surface    #FFFFFF  (cards)
 *   navy       #0E3A66  (map markers)
 *   danger     #D93333  (hazard zones, errors)
 *   success    #33A666  (freshness fresh, charging)
 *   warning    #E69E1A  (freshness stale)
 */

:root {
    /*
     * Brand palette — these tokens stay literal across both themes
     * (the rocket emblem is orange in dark mode AND light mode, etc).
     * `--brand-primary` is the brand "ink" — used as a background/
     * stroke color in dark/contrasty contexts (toasts, switch tracks).
     * For TEXT use --text-default below so dark mode actually flips.
     */
    --brand-primary:    #1A1A1A;
    --brand-secondary:  #FF8800;
    --brand-sand:       #F7F4EE;
    --brand-surface:    #FFFFFF;
    --brand-navy:       #0E3A66;
    --brand-danger:     #D93333;
    --brand-success:    #33A666;
    --brand-warning:    #E69E1A;
    --brand-on-primary: #FFFFFF;
    --brand-on-secondary:#FFFFFF;

    /*
     * Semantic text tokens — these are the ones that should be used
     * for any human-readable text, because they actually theme-adapt.
     * `--text-default` is the workhorse: paragraph copy, button labels,
     * input value text, anything that needs "the right text color for
     * whatever surface I'm on right now."
     */
    --text-default:     #1A1A1A;
    --text-muted:       rgba(26, 26, 26, 0.6);
    --text-faint:       rgba(26, 26, 26, 0.45);
    --hairline:         rgba(26, 26, 26, 0.12);
    --shadow-card:      0 2px 8px rgba(0, 0, 0, 0.06);
    --radius-card:      14px;
    --radius-button:    999px;
    --pad-screen:       20px;

    /*
     * Tell the browser the page supports both color schemes so native
     * form controls (focus rings, scroll bars, autofill yellow, etc.)
     * pick the matching dark variant in dark mode. Without this, Safari
     * paints autofilled fields with a stubborn light yellow background
     * regardless of our CSS.
     */
    color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
    :root {
        --brand-sand:       #14110D;
        --brand-surface:    #1F1B16;
        --text-default:     #FFFFFF;
        --text-muted:       rgba(255, 255, 255, 0.62);
        --text-faint:       rgba(255, 255, 255, 0.50);
        --hairline:         rgba(255, 255, 255, 0.14);
        --shadow-card:      0 2px 8px rgba(0, 0, 0, 0.4);
    }
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    background: var(--brand-sand);
    /* Use the semantic --text-default so the body text inverts cleanly
       in dark mode without us re-overriding every descendant individually. */
    color: var(--text-default);
    font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI",
                 Roboto, Helvetica, Arial, sans-serif;
    font-size: 16px;
    line-height: 1.4;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    /* Lock orientation/viewport on iOS Safari so address-bar collapse
       doesn't reshuffle layout mid-tap. */
    overflow-x: hidden;
}

#app {
    min-height: 100vh;
    min-height: 100dvh;       /* dynamic viewport on iOS 26 + Safari 18 */
}

.spa-boot {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100vh;
    color: var(--text-muted);
}

/* ---- Screen layout primitives --------------------------------------
 *
 * Mobile is the base layout (max-width 480px, single column). Tablet
 * and desktop breakpoints below widen the content area progressively
 * — but only the .screen container, NOT the tab bar (which stays
 * centered + capped to keep its visual character on big screens).
 *
 * Breakpoints:
 *   <600px           mobile        — base styles below
 *   600px - 1023px   tablet        — wider single column
 *   1024px+          desktop       — multi-column where it helps
 */
.screen {
    max-width: 480px;
    margin: 0 auto;
    padding: var(--pad-screen);
    padding-bottom: 100px;    /* leave room above the bottom nav */
}
@media (min-width: 600px) {
    .screen { max-width: 640px; }
}
@media (min-width: 1024px) {
    .screen { max-width: 880px; }
}

/* The .screen.full class opts a screen OUT of the max-width cap —
 * used for the map screen, which deserves the whole viewport on
 * desktop instead of being shrunk into a column. */
.screen.full {
    max-width: none;
    padding-left: 24px;
    padding-right: 24px;
}
@media (min-width: 1024px) {
    .screen.full { padding-left: 40px; padding-right: 40px; }
}

.screen-h1 {
    font-size: 28px;
    font-weight: 600;
    margin: 12px 0 6px;
}
@media (min-width: 1024px) {
    .screen-h1 { font-size: 32px; }
}
.screen-sub {
    color: var(--text-muted);
    margin: 0 0 24px;
}

/* ---- Cards --------------------------------------------------------- */
.card {
    background: var(--brand-surface);
    border-radius: var(--radius-card);
    padding: 16px;
    margin: 0 0 16px;
    box-shadow: var(--shadow-card);
}
.card-title {
    font-weight: 600;
    margin: 0 0 4px;
}
.card-sub {
    color: var(--text-muted);
    font-size: 14px;
    margin: 0;
}

/* ---- Buttons ------------------------------------------------------- */
button.btn,
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 14px 22px;
    font-size: 16px;
    font-weight: 600;
    border-radius: var(--radius-button);
    border: none;
    cursor: pointer;
    transition: transform 0.06s ease, opacity 0.15s ease;
    min-height: 48px;
}
.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.45; cursor: default; }
.btn-primary {
    background: var(--brand-secondary);
    color: var(--brand-on-secondary);
}
.btn-secondary {
    background: transparent;
    /* --text-default, not --brand-primary — the latter is fixed near-
       black across themes and would render invisible-on-dark in dark
       mode. */
    color: var(--text-default);
    border: 1.5px solid var(--hairline);
}
.btn-danger {
    background: transparent;
    color: var(--brand-danger);
    border: 1.5px solid var(--brand-danger);
}
.btn-block { width: 100%; }

/* ---- Inputs -------------------------------------------------------- */
.input {
    width: 100%;
    padding: 14px 16px;
    font-size: 17px;
    border-radius: 10px;
    border: 1.5px solid var(--hairline);
    background: var(--brand-surface);
    /* Explicit — `color: inherit` was insufficient in some browsers
       where form-control UA stylesheets win the cascade. The text-fill
       belt-and-suspenders handles WebKit's autofill stubbornness. */
    color: var(--text-default);
    -webkit-text-fill-color: var(--text-default);
    caret-color: var(--brand-secondary);
    font-family: inherit;
}
.input:focus {
    outline: none;
    border-color: var(--brand-secondary);
}
.input::placeholder {
    color: var(--text-faint);
    opacity: 1;   /* Firefox dims placeholders to 0.54 by default — undo it. */
}
/*
 * WebKit autofill paints its own background ("autofill yellow" on light,
 * a navy blue on dark) and forces its own text color. The standard hack
 * is to push the background out with a huge inset shadow and pin the
 * text-fill explicitly. Without this, an autofilled phone field looks
 * like nothing was typed even though the value is there.
 */
.input:-webkit-autofill,
.input:-webkit-autofill:focus,
.input:-webkit-autofill:hover {
    -webkit-box-shadow: 0 0 0 1000px var(--brand-surface) inset;
    -webkit-text-fill-color: var(--text-default);
    caret-color: var(--brand-secondary);
}
.field-label {
    font-size: 13px;
    color: var(--text-muted);
    margin: 0 0 6px 4px;
    font-weight: 500;
}

/* ---- Welcome / hero -------------------------------------------------- */
/*
 * Welcome is the one screen that stays "branded light" regardless of
 * OS theme — matches the iOS app's behaviour. The cards are always
 * translucent-white over the sand backdrop; the text inside is always
 * dark so it reads cleanly. Without the explicit color overrides the
 * body's dark-mode `color: white` would inherit through and make the
 * h1 + subtitle invisible on the white cards.
 */
.welcome {
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 24px;
    gap: 20px;
}
.welcome .logo-card,
.welcome .cta-card {
    /* Cap at 380px on desktop so a 2K monitor doesn't leave the cards
       looking lost; on phones min() picks the 100% branch and we
       fill the available width minus the screen padding. */
    width: min(380px, 100%);
    box-shadow: var(--shadow-card);
}
.welcome .logo-card {
    background: rgba(255, 255, 255, 0.92);
    border-radius: 24px;
    padding: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.welcome .logo-card img {
    width: 96px;
    height: 96px;
    object-fit: contain;
}
.welcome .cta-card {
    background: rgba(255, 255, 255, 0.95);
    border-radius: 20px;
    padding: 22px;
    text-align: center;
}
.welcome .cta-card h1 {
    margin: 0 0 8px;
    font-size: 24px;
    font-weight: 600;
    /* Force dark — body's dark-mode rule would otherwise paint this
       white-on-white. */
    color: #1A1A1A;
}
.welcome .cta-card p {
    margin: 0 0 16px;
    color: rgba(26, 26, 26, 0.66);
}
.version-badge {
    margin-top: 16px;
    /* On the sand background this needs more contrast in BOTH modes
       — text-faint @ 45% was too washed out on the dark backdrop. */
    color: rgba(255, 255, 255, 0.55);
    font-size: 12px;
}
@media (prefers-color-scheme: light) {
    .version-badge { color: rgba(26, 26, 26, 0.5); }
}

/* ---- OTP slots ----------------------------------------------------- */
.otp-row {
    display: flex;
    gap: 8px;
    margin: 24px 0 4px;
}
.otp-slot {
    flex: 1;
    height: 56px;
    background: var(--brand-surface);
    border: 2px solid var(--hairline);
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    font-weight: 600;
    transition: border-color 0.2s, transform 0.2s;
}
.otp-row.otp-fail .otp-slot { border-color: var(--brand-danger); transform: scale(0.97); }
.otp-row.otp-ok   .otp-slot { border-color: var(--brand-success); transform: scale(1.04); }
.otp-input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
    height: 0;
    width: 0;
}
.otp-host {
    position: relative;
    cursor: text;
}

/* ---- Family list / band rows --------------------------------------- */
.band-row {
    display: flex;
    align-items: center;
    gap: 6px;
    background: var(--brand-surface);
    border-radius: var(--radius-card);
    margin-bottom: 10px;
    box-shadow: var(--shadow-card);
    overflow: hidden;
}
.band-info-link {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    text-decoration: none;
    color: inherit;
    cursor: pointer;
}
.band-detail-arrow {
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    color: var(--text-faint);
    font-size: 22px;
    text-decoration: none;
    border-left: 1px solid var(--hairline);
}
.band-detail-arrow:hover { color: var(--text-default); }
.band-avatar {
    width: 44px;
    height: 44px;
    border-radius: 22px;
    color: white;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.band-info { flex: 1; min-width: 0; }
.band-name {
    font-weight: 600;
    font-size: 16px;
    margin: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.band-meta {
    color: var(--text-muted);
    font-size: 13px;
    margin: 2px 0 0;
}
.band-pill {
    font-size: 12px;
    font-weight: 600;
    padding: 4px 10px;
    border-radius: 999px;
}

/* Freshness color tokens. Map across all surfaces. */
.fresh-fresh    { background: rgba(51, 166, 102, 0.16); color: var(--brand-success); }
.fresh-stale    { background: rgba(230, 158, 26, 0.18); color: var(--brand-warning); }
.fresh-offline  { background: rgba(26, 26, 26, 0.10);   color: var(--text-muted); }
.fresh-paused   { background: rgba(26, 26, 26, 0.10);   color: var(--text-muted); }
.fresh-never    { background: rgba(26, 26, 26, 0.10);   color: var(--text-muted); }

/* ---- Toggle switch (brand orange) ---------------------------------- */
.switch {
    position: relative;
    display: inline-block;
    width: 50px;
    height: 30px;
    flex-shrink: 0;
}
.switch input { display: none; }
.switch .slider {
    position: absolute;
    inset: 0;
    background: rgba(26, 26, 26, 0.18);
    border-radius: 999px;
    transition: background 0.18s;
}
.switch .slider::before {
    content: "";
    position: absolute;
    height: 24px; width: 24px;
    left: 3px; top: 3px;
    background: white;
    border-radius: 50%;
    transition: transform 0.18s;
    box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.switch input:checked + .slider { background: var(--brand-secondary); }
.switch input:checked + .slider::before { transform: translateX(20px); }
.switch input:disabled + .slider { opacity: 0.5; }

/* ---- Bottom navigation -------------------------------------------- */
/*
 * Phone-first chrome — cap the tab bar's width to the same 480px the
 * screen content uses, then translate it into the horizontal center.
 * On a phone the cap is wider than the viewport so it occupies the
 * full bottom edge; on desktop it sits as a small floating bar.
 *
 * z-index 100 keeps it above the Leaflet map (which uses ~400 for
 * popups but ~200 for its own controls — we want to draw OVER its
 * controls, so we also bump the map's panes inside our CSS where
 * needed; in practice the tab bar at 100 doesn't conflict with the
 * map controls that matter).
 */
.tab-bar {
    position: fixed;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 100%;
    max-width: 480px;
    display: flex;
    background: var(--brand-surface);
    border-top: 1px solid var(--hairline);
    padding: 8px 0 calc(8px + env(safe-area-inset-bottom));
    z-index: 1000;
}
@media (min-width: 520px) {
    /* On a comfortable viewport, give the floating bar real shape so
       it doesn't look like a stripe glued to the screen edge. */
    .tab-bar {
        border-radius: 18px 18px 0 0;
        box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.12);
        border-top: 0;
    }
}
.tab-bar .tab {
    flex: 1;
    text-align: center;
    color: var(--text-muted);
    text-decoration: none;
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    user-select: none;
}
.tab-bar .tab .tab-icon {
    font-size: 22px;
    line-height: 1;
    display: block;
    margin: 0 auto 2px;
}
.tab-bar .tab.active { color: var(--brand-secondary); }

/* ---- Map ---------------------------------------------------------- */
#mapHostWrap {
    position: relative;
    width: 100%;
    /* On mobile, leave room for the tab bar at the bottom. On desktop
       the tab bar is centered + capped at 480px and floats above the
       map, but the map itself can use almost the entire viewport. */
    height: calc(100dvh - 160px);
}
@media (min-width: 1024px) {
    #mapHostWrap {
        height: calc(100dvh - 180px);
    }
}

/* The empty-state overlay sits absolutely-positioned over the map
   when no zones are configured. The map underneath still renders
   (so the operator can pan around) but the card tells the parent
   why they're not seeing markers. */
.map-empty-overlay {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(20, 17, 13, 0.55);
    z-index: 500;       /* above Leaflet's panes (200-400) but below FABs (1000) */
    pointer-events: none;  /* clicks pass through to the map */
}
.map-empty-overlay .card {
    pointer-events: auto;
}
#mapHost {
    width: 100%;
    height: 100%;
    border-radius: var(--radius-card);
    overflow: hidden;
    box-shadow: var(--shadow-card);
}
/* In fullscreen mode the wrapper takes the whole screen and the
   rounded-corner / shadow inside looks weird — flatten it. */
#mapHostWrap:fullscreen,
#mapHostWrap:-webkit-full-screen {
    height: 100vh;
    background: black;
}
#mapHostWrap:fullscreen #mapHost,
#mapHostWrap:-webkit-full-screen #mapHost {
    border-radius: 0;
    box-shadow: none;
}

.map-fab {
    position: absolute;
    width: 44px;
    height: 44px;
    border-radius: 22px;
    background: var(--brand-surface);
    /* Theme-adaptive — needs to be white-on-charcoal in dark mode,
       black-on-white in light. --brand-primary would have stayed
       black in both, making the FAB icon invisible on the dark
       surface. */
    color: var(--text-default);
    border: none;
    box-shadow: 0 2px 8px rgba(0,0,0,0.18);
    font-size: 20px;
    cursor: pointer;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
}
.map-fab:active { transform: scale(0.94); }
/* Top-LEFT so it doesn't collide with Leaflet's built-in layer-control
   widget, which lives in the top-right corner. Matches the iOS app's
   fullscreen FAB placement too — close/expand buttons go top-leading,
   recenter goes bottom-trailing. */
.map-fab-top    { top: 12px;    left: 12px; }
.map-fab-bottom { bottom: 16px; right: 16px; }

.user-marker {
    background: var(--brand-navy);
    color: white;
    width: 28px; height: 28px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: 16px;
    border: 2px solid white;
    box-shadow: 0 1px 4px rgba(0,0,0,0.4);
}
/* Each map marker is an icon (circle, top) plus a name label
   (white pill, just below). The icon's CENTER pins to the lat/lon
   via Leaflet iconAnchor — the label sits underneath but doesn't
   affect positioning. White pill + dark text + drop-shadow makes
   the labels legible against both satellite and street tiles. */
.zone-marker-wrap, .band-marker-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    /* The Leaflet iconSize gives us 120px width to play with. We
       center inside that so even short names look balanced. */
    width: 120px;
    pointer-events: none;       /* clicks pass through to underlying tiles */
}
.zone-marker, .band-marker {
    /* The interactive bit — re-enable pointer events on the icon
       circle itself so a tooltip / future click handler still works. */
    pointer-events: auto;
}
.zone-marker {
    background: var(--brand-navy);
    color: white;
    border-radius: 50%;
    width: 36px; height: 36px;
    display: flex; align-items: center; justify-content: center;
    font-size: 18px;
    box-shadow: 0 1px 4px rgba(0,0,0,0.4);
}
.zone-marker.hazard {
    background: var(--brand-danger);
}
.band-marker {
    background: var(--brand-secondary);
    color: white;
    width: 32px; height: 32px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700;
    border: 2px solid white;
    box-shadow: 0 1px 4px rgba(0,0,0,0.4);
}
.zone-label, .band-label {
    margin-top: 4px;
    padding: 2px 8px;
    background: rgba(255, 255, 255, 0.94);
    color: #1A1A1A;             /* always dark — labels sit on white pill */
    border-radius: 8px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 116px;            /* fits inside the 120px wrap with padding */
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
    pointer-events: none;
}
.band-label {
    /* Band labels get a faint orange left border to visually associate
       them with the brand-orange band-marker above. */
    border-left: 3px solid var(--brand-secondary);
    padding-left: 6px;
}

/* ---- Toasts ------------------------------------------------------- */
#toast-stack {
    position: fixed;
    bottom: 96px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 2000;
    display: flex;
    flex-direction: column-reverse;
    gap: 8px;
    pointer-events: none;
}
.toast {
    background: var(--brand-primary);
    color: var(--brand-on-primary);
    padding: 10px 16px;
    border-radius: 999px;
    font-size: 14px;
    pointer-events: auto;
    opacity: 0;
    transition: opacity 0.18s, transform 0.18s;
    transform: translateY(6px);
    box-shadow: var(--shadow-card);
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast.error { background: var(--brand-danger); }

/* ---- Banner (incursion alert / network error) --------------------- */
.banner {
    border-radius: var(--radius-card);
    padding: 12px 16px;
    margin: 0 0 12px;
    font-size: 14px;
}
.banner.danger {
    background: rgba(217, 51, 51, 0.12);
    color: var(--brand-danger);
}
.banner.info {
    background: rgba(26, 26, 26, 0.08);
    color: var(--text-default);
}

/* ---- QR scanner modal --------------------------------------------- */
/*
 * Full-screen modal that overlays everything else (z-index 3000 sits
 * above toasts at 2000 and the tab bar at 1000). The video element
 * fills the entire viewport; the viewfinder is an absolutely-
 * positioned overlay drawing a square cut-out plus a darkened mask
 * so the user knows where to aim.
 */
.qr-modal {
    position: fixed;
    inset: 0;
    z-index: 3000;
    background: black;
    overflow: hidden;
}
.qr-modal .qr-video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.qr-viewfinder {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}
.qr-viewfinder::before {
    content: '';
    width: min(260px, 70vw);
    height: min(260px, 70vw);
    border: 3px solid white;
    border-radius: 16px;
    /* The big inset shadow darkens the rest of the screen so the
       active viewfinder stands out. Cheap "spotlight" effect. */
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
}
.qr-status {
    position: absolute;
    bottom: calc(40px + env(safe-area-inset-bottom));
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0, 0, 0, 0.7);
    color: white;
    padding: 10px 16px;
    border-radius: 999px;
    font-size: 14px;
    max-width: 80vw;
    text-align: center;
    line-height: 1.3;
}
.qr-close {
    position: absolute;
    top: calc(16px + env(safe-area-inset-top));
    right: 16px;
    width: 44px;
    height: 44px;
    border-radius: 22px;
    background: rgba(0, 0, 0, 0.6);
    color: white;
    border: none;
    font-size: 22px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* ---- Misc utility ------------------------------------------------- */
.hidden { display: none !important; }
.row    { display: flex; align-items: center; }
.row-spaced { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.flex-1 { flex: 1; min-width: 0; }
.gap-2  { gap: 8px; }
.mt-1   { margin-top: 4px; }
.mt-2   { margin-top: 8px; }
.mt-3   { margin-top: 16px; }
.mt-4   { margin-top: 24px; }
.text-muted { color: var(--text-muted); }
.text-danger { color: var(--brand-danger); }
.text-center { text-align: center; }
.font-mono   { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 14px; }

/* ---- Progress dots (multi-step register flow) --------------------- */
.dots { display: flex; justify-content: center; gap: 8px; margin: 12px 0 24px; }
.dot { width: 8px; height: 8px; border-radius: 4px; background: var(--hairline); }
.dot.active { background: var(--brand-secondary); width: 24px; }
