/* --- Design tokens ---------------------------------------------------- */
/* Centralise the app's accent colors here. If a button ever needs a tweak,
   edit these vars — not 10 rules scattered through the file. */
:root {
    --color-primary: #2563eb;
    --color-primary-hover: #1d4ed8;
    --color-danger: #dc2626;
    --color-danger-hover: #b91c1c;
}

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}
/* iOS safe-area insets. Exposed as custom properties so the topbar, app
   shell and floating action bars can factor them in without repeating
   env() calls. Fallbacks are 0 on non-notched devices / browsers. */
:root {
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left: env(safe-area-inset-left, 0px);
    --safe-right: env(safe-area-inset-right, 0px);
    --topbar-height: 48px;
}

/* --- Login overlay ---------------------------------------------------- */

.login-screen[hidden] { display: none; }
/* Full-bleed container. The screenshot lives behind it as ambient backdrop —
   heavily blurred and darkened so it reads as atmosphere, not content.
   A brand-tinted mesh overlay adds colour depth. */
.login-screen {
    position: fixed;
    inset: 0;
    z-index: 5000;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    background: #0b0b0d;  /* fallback before image paints */
    isolation: isolate;
}
.login-screen::before {
    content: "";
    position: absolute;
    /* Slight overscan + scale so the blurred feather edge never shows. */
    inset: -2%;
    background-image: url('/img/login-background.png');
    background-size: cover;
    background-position: center;
    /* Light blur keeps the screenshot recognisable as "the app" while
       still reading as backdrop. Brightness trimmed only slightly so
       colour stays rich behind the overlay. */
    filter: blur(6px) saturate(1.1) brightness(0.85);
    transform: scale(1.03);
    z-index: -2;
}
/* Aurora-ish colour wash: two radial blooms (blue + magenta) on a soft
   dark vignette. Lighter now that the image is visible — we want to
   accent, not drown it. */
.login-screen::after {
    content: "";
    position: absolute;
    inset: 0;
    background:
        radial-gradient(ellipse 60% 45% at 20% 15%, rgba(37, 99, 235, 0.3), transparent 70%),
        radial-gradient(ellipse 55% 40% at 85% 85%, rgba(168, 85, 247, 0.28), transparent 70%),
        linear-gradient(180deg, rgba(10, 10, 14, 0.25) 0%, rgba(10, 10, 14, 0.5) 100%);
    z-index: -1;
    pointer-events: none;
}

/* Frosted-glass card. backdrop-filter blurs the busy backdrop behind the
   card so the copy stays readable without needing a fully opaque surface.
   Falls back to a near-solid white when the browser doesn't support it. */
.login-card {
    position: relative;
    z-index: 1;
    width: 400px;
    max-width: calc(100vw - 32px);
    padding: 40px 40px 36px;
    border-radius: 20px;
    background: rgba(255, 255, 255, 0.88);
    backdrop-filter: blur(24px) saturate(1.4);
    -webkit-backdrop-filter: blur(24px) saturate(1.4);
    border: 1px solid rgba(255, 255, 255, 0.6);
    box-shadow:
        0 30px 80px rgba(0, 0, 0, 0.45),
        inset 0 0 0 1px rgba(255, 255, 255, 0.2);
    text-align: center;
    animation: login-card-in 0.5s cubic-bezier(0.2, 0.9, 0.3, 1);
}
@keyframes login-card-in {
    from { opacity: 0; transform: translateY(12px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* Brand logo above the title. */
.login-logo {
    display: block;
    width: 140px;
    height: auto;
    margin: 0 auto 14px;
}

.login-card h1 {
    margin: 0 0 6px;
    font-size: 26px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: #0f0f12;
}
.login-card p {
    margin: 0 0 24px;
    color: #52525b;
    font-size: 14px;
    line-height: 1.5;
}
.login-card .primary {
    width: 100%;
    padding: 11px 18px;
    font-size: 14px;
    font-weight: 600;
    border: none;
    border-radius: 10px;
    background: linear-gradient(135deg, #2563eb 0%, #7c3aed 100%);
    color: #fff;
    cursor: pointer;
    transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
    box-shadow: 0 6px 18px rgba(37, 99, 235, 0.35);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
}
.login-card .primary:hover {
    transform: translateY(-1px);
    box-shadow: 0 10px 24px rgba(37, 99, 235, 0.45);
    filter: brightness(1.08);
}
.login-card .primary:active { transform: translateY(0); filter: brightness(0.95); }

.login-foot {
    margin-top: 18px !important;
    font-size: 11px !important;
    color: #a1a1aa !important;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.login-error {
    color: #dc2626 !important;
    font-size: 12px !important;
    margin: 12px 0 0 !important;
}

/* --- Top bar --------------------------------------------------------- */

.topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: calc(var(--topbar-height) + var(--safe-top));
    padding: var(--safe-top) calc(16px + var(--safe-right)) 0 calc(16px + var(--safe-left));
    background: #18181b;
    color: #fafafa;
    border-bottom: 1px solid #27272a;
    font-size: 13px;
    position: relative;
    z-index: 1500;
}
.topbar-left,
.topbar-right {
    display: flex;
    align-items: center;
    gap: 12px;
}
.topbar-center {
    flex: 1;
    display: flex;
    justify-content: center;
    padding: 0 16px;
    min-width: 0;
}
.topbar-logo {
    width: 24px;
    height: 24px;
    display: block;
}
.topbar-name {
    font-weight: 700;
    font-size: 16px;
    letter-spacing: 0.01em;
    color: #fafafa;
    white-space: nowrap;
}
/* Hamburger button: hidden on desktop, revealed in the mobile media query. */
.mobile-menu-btn,
.mobile-sidebar-btn {
    display: none;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    padding: 0;
    background: transparent;
    color: #fafafa;
    border: 1px solid #3f3f46;
    border-radius: 6px;
    cursor: pointer;
}
.mobile-menu-btn:hover,
.mobile-sidebar-btn:hover { background: #27272a; }
.mobile-menu-btn:active,
.mobile-sidebar-btn:active { background: #3f3f46; }
/* Sidebar toggle — a half-pill tab stuck to the sidebar's right edge
   (when open) or the viewport's left edge (when closed). Flat face sits
   flush against the edge, rounded half pokes into the map. Chevron
   points the direction a click would move the sidebar. Vertical center
   keeps it clear of Leaflet's zoom / draw controls at the map corner. */
.sidebar-toggle {
    position: absolute;
    top: 50%;
    /* 340 (sidebar width) = the seam; the tab sits just past it. */
    left: 340px;
    width: 20px;
    height: 56px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #18181b;
    color: #fafafa;
    /* Border only on the curved side; the flat face hugs the sidebar
       border (no double-line). */
    border: 1px solid #27272a;
    border-left: none;
    border-radius: 0 12px 12px 0;
    cursor: pointer;
    box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.25);
    z-index: 1400;  /* above sidebar content (1), under modals */
    transform: translateY(-50%);
    transition: left 0.2s ease, box-shadow 0.15s ease,
                background 0.12s ease, color 0.12s ease;
}
.sidebar-toggle:hover {
    background: #2563eb;
    border-color: #2563eb;
    box-shadow: 2px 6px 16px rgba(37, 99, 235, 0.35);
}
.sidebar-toggle svg { transition: transform 0.2s ease; }
/* When the sidebar is hidden the tab slides to the viewport's left
   edge and the chevron flips so it points where a click would take the
   user. */
#app.sidebar-hidden .sidebar-toggle { left: 0; }
#app.sidebar-hidden .sidebar-toggle svg { transform: rotate(180deg); }
.topbar-title {
    font-size: 15px;
    font-weight: 600;
    letter-spacing: 0.02em;
}
.topbar-locale {
    background: transparent;
    color: #fafafa;
    border: 1px solid #3f3f46;
    border-radius: 4px;
    padding: 2px 6px;
    font-size: 12px;
    font-family: inherit;
    cursor: pointer;
}
.topbar-locale option { color: #18181b; background: #fff; }

.topbar-account {
    color: #a1a1aa;
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Plan badge: small pill next to account info. Free is amber (an intentional
   nudge to upgrade), paid tiers use neutral/premium colors. Unknown plan names
   fall back to the generic gray style from the base rule. */
.plan-badge {
    display: inline-flex;
    align-items: center;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 2px 8px;
    border-radius: 999px;
    background: #3f3f46;
    color: #fafafa;
    border: 1px solid transparent;
    cursor: pointer;
}
.plan-badge:hover { filter: brightness(1.1); }

/* --- Plan info / upgrade dialog -------------------------------------- */

.plan-dialog {
    /* Override the default dialog padding so the header gradient can bleed to edges. */
    padding: 0;
    overflow: hidden;
    width: 400px;
    gap: 0;
}
.plan-dialog-header {
    position: relative;
    padding: 28px 24px 22px;
    background: linear-gradient(135deg, #7c3aed 0%, #db2777 55%, #f59e0b 100%);
    color: #fff;
    text-align: center;
    overflow: hidden;
}
.plan-dialog-header::before {
    /* Soft radial highlight — keeps the gradient from feeling flat. */
    content: "";
    position: absolute;
    top: -50%;
    right: -20%;
    width: 260px;
    height: 260px;
    background: radial-gradient(closest-side, rgba(255, 255, 255, 0.35), transparent 70%);
    pointer-events: none;
}
.plan-dialog-icon {
    width: 44px;
    height: 44px;
    position: relative;
    filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.25));
}
.plan-dialog-title {
    position: relative;
    font-size: 20px;
    font-weight: 700;
    margin-top: 10px;
    letter-spacing: 0.01em;
}
.plan-dialog-current {
    position: relative;
    font-size: 12px;
    color: rgba(255, 255, 255, 0.85);
    margin-top: 6px;
    letter-spacing: 0.02em;
}
.plan-dialog-current-name {
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    background: rgba(255, 255, 255, 0.2);
    border: 1px solid rgba(255, 255, 255, 0.35);
    padding: 2px 8px;
    border-radius: 999px;
    margin-left: 4px;
}

.plan-dialog-body {
    padding: 20px 24px 20px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}
.plan-dialog-message {
    margin: 0;
    font-size: 13px;
    color: #3f3f46;
    line-height: 1.5;
    text-align: center;
}
.plan-dialog-cta {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    border-radius: 10px;
    background: #18181b;
    color: #fafafa;
    font-size: 14px;
    font-weight: 600;
    text-decoration: none;
    box-shadow: 0 4px 14px rgba(124, 58, 237, 0.25);
    transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.plan-dialog-cta:hover {
    background: #27272a;
    transform: translateY(-1px);
    box-shadow: 0 6px 18px rgba(124, 58, 237, 0.32);
}
.plan-dialog-cta svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    color: #f59e0b;
}
.plan-dialog-cta span:nth-of-type(1) {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.plan-dialog-cta-arrow {
    opacity: 0.7;
    transition: transform 0.15s ease, opacity 0.15s ease;
}
.plan-dialog-cta:hover .plan-dialog-cta-arrow {
    opacity: 1;
    transform: translateX(3px);
}

.plan-dialog-body .modal-actions {
    justify-content: center;
    margin-top: 2px;
}
.plan-badge[hidden] { display: none; }
.plan-badge.plan-free {
    background: #f59e0b;
    color: #18181b;
    border-color: #d97706;
    box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.4);
}
.plan-badge.plan-standard {
    background: #2563eb;
    color: #fafafa;
    border-color: #1d4ed8;
}
.plan-badge.plan-ultra {
    background: linear-gradient(135deg, #7c3aed, #db2777);
    color: #fafafa;
    border-color: #6d28d9;
}
.topbar-btn,
.topbar-btn-link {
    background: transparent;
    color: #fafafa;
    border: 1px solid transparent;
    padding: 4px 10px;
    font-size: 13px;
    border-radius: 4px;
    cursor: pointer;
}
.topbar-btn:hover,
.topbar-btn-link:hover {
    background: #27272a;
}
.topbar-btn[aria-expanded="true"] {
    background: #27272a;
    border-color: #3f3f46;
}

/* Admin dropdown */
.topbar-menu { position: relative; }
.topbar-dropdown[hidden] { display: none; }
.topbar-dropdown {
    list-style: none;
    margin: 0;
    padding: 4px 0;
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    min-width: 220px;
    background: #fff;
    color: #18181b;
    border: 1px solid #d4d4d8;
    border-radius: 6px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}
.topbar-dropdown li button {
    display: block;
    width: 100%;
    text-align: left;
    padding: 8px 12px;
    background: transparent;
    border: none;
    font-size: 13px;
    cursor: pointer;
    color: inherit;
    white-space: nowrap;
}
.topbar-dropdown li button:hover { background: #f4f4f5; }
/* Feature-gated menu item: grey it out and append a lock glyph. Click still
   works — JS routes it to the plan-upgrade prompt. */
.topbar-dropdown li button.feature-locked {
    color: #a1a1aa;
    display: flex;
    align-items: center;
    justify-content: space-between;
}
.topbar-dropdown li button.feature-locked::after {
    content: '🔒';
    font-size: 12px;
    opacity: 0.85;
    margin-left: 10px;
    filter: grayscale(0.2);
}
.topbar-dropdown li button.feature-locked:hover {
    background: #fef3c7;
    color: #92400e;
}

/* Custom map-tool controls (Route, Import-roads, Import-buildings). Flex-
   centred icon and a lock badge that matches the canonical
   `.leaflet-draw.draw-locked::after` style below: big red circle, top-right,
   identical across every map tool. */
.route-control,
.import-roads-control,
.import-buildings-control { position: relative; }
.route-control-btn,
.import-roads-control-btn,
.import-buildings-control-btn,
.snap-control-btn {
    display: flex !important;
    align-items: center;
    justify-content: center;
}
.route-control.feature-locked,
.import-roads-control.feature-locked,
.import-buildings-control.feature-locked {
    opacity: 0.55;
    filter: grayscale(0.6);
    cursor: not-allowed;
}
.route-control.feature-locked::after,
.import-roads-control.feature-locked::after,
.import-buildings-control.feature-locked::after {
    content: '🔒';
    position: absolute;
    top: -8px;
    right: -8px;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    background: #dc2626;
    border-radius: 50%;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
    pointer-events: none;
}

/* Shared "feature locked" styling for any button gated behind a plan.
   Stays clickable (unlike :disabled) so the click handler can open the
   upgrade dialog. Topbar dropdown has its own override above (flex layout,
   amber hover) — this rule covers +Add buttons everywhere else. */
button.feature-locked {
    color: #a1a1aa;
    background: #fafafa;
    border-color: #e4e4e7;
}
button.feature-locked:hover {
    background: #fef3c7;
    color: #92400e;
    border-color: #fde68a;
}
button.feature-locked::after {
    content: ' 🔒';
    margin-left: 4px;
    font-size: 11px;
    filter: grayscale(0.2);
}

#app {
    display: grid;
    grid-template-columns: 340px 1fr;
    /* Fill everything below the (safe-area-inflated) topbar. */
    height: calc(100vh - var(--topbar-height) - var(--safe-top));
    transition: grid-template-columns 0.2s ease;
    position: relative;   /* anchor for the floating sidebar toggle */
}
#app.sidebar-hidden {
    grid-template-columns: 0 1fr;
}
#app.sidebar-hidden #sidebar {
    /* Hide contents but keep the column animated smoothly. overflow:hidden
       prevents the sidebar's content from poking into the map during the
       shrink animation. */
    overflow: hidden;
    padding-left: 0;
    padding-right: 0;
    border-right: none;
}

/* --- Mobile layout -------------------------------------------------- */
/* Phase 1: don't fall over on small screens. Sidebar becomes an overlay
   drawer (Notion-style), map fills the remaining width. Drawing tools
   still work mouse-first, touch UX comes in a later phase. */
@media (max-width: 768px) {
    /* Belt-and-braces: kill horizontal page scroll on mobile. Dialogs
       and tables are the usual culprits (long email strings, wide
       selects). Individual containers still own their own width logic;
       this is the outermost guardrail so a rogue overflow can never
       produce a horizontal page scrollbar. */
    html, body { overflow-x: hidden; }
    /* Both #app and #app.sidebar-hidden need the override: otherwise the
       desktop rule `#app.sidebar-hidden { grid-template-columns: 0 1fr }`
       wins on specificity and #map lands in a zero-width column. Row
       height is equally critical - the fixed-positioned sidebar
       contributes nothing to an auto row, so without 1fr the map's
       `height: 100%` collapses to 0 and Leaflet tiles never paint. */
    #app, #app.sidebar-hidden {
        grid-template-columns: 1fr;
        grid-template-rows: 1fr;
        transition: none;
    }
    #sidebar {
        position: fixed;
        /* Clear the topbar - which itself absorbs the iOS notch via
           safe-area-inset-top. */
        top: calc(var(--topbar-height) + var(--safe-top));
        left: 0;
        bottom: 0;
        width: 340px;
        max-width: 85vw;
        z-index: 1200;
        transform: translateX(-100%);
        transition: transform 0.25s ease;
        box-shadow: 4px 0 20px rgba(0, 0, 0, 0.25);
        border-right: none;
        /* Reserve space for the home indicator at the bottom. */
        padding-bottom: calc(16px + var(--safe-bottom));
    }
    #app:not(.sidebar-hidden) #sidebar { transform: translateX(0); }
    /* Override the desktop "collapsed column" look - on mobile the sidebar
       is a drawer, not a grid cell, so zeroing padding / hiding overflow
       would just cut off its content when open. */
    #app.sidebar-hidden #sidebar {
        overflow: auto;
        padding: 16px;
        border-right: none;
    }
    /* Dim the map behind the open drawer so the focus is clear. */
    #app:not(.sidebar-hidden)::after {
        content: "";
        position: fixed;
        top: calc(var(--topbar-height) + var(--safe-top));
        left: 0; right: 0; bottom: 0;
        background: rgba(0, 0, 0, 0.4);
        z-index: 1150;
        animation: sidebar-backdrop-in 0.25s ease;
    }

    /* The desktop floating sidebar toggle competes with Leaflet controls
       on the map's left edge and overlaps layer-row action icons when the
       drawer is open. On mobile the `#mobileSidebarBtn` in the topbar
       takes over, so hide the floating pill entirely. */
    .sidebar-toggle { display: none; }
    .mobile-sidebar-btn { display: inline-flex; }

    /* Topbar: logo + geocode search + hamburger. The right-side menu group
       collapses into a dropdown panel revealed by the hamburger. Preserve
       safe-area insets (notch, left/right) added by the base .topbar rule. */
    .topbar {
        padding: var(--safe-top) calc(8px + var(--safe-right)) 0 calc(8px + var(--safe-left));
        gap: 6px;
    }
    .topbar-left { gap: 6px; }
    .topbar-center { padding: 0 8px; min-width: 0; }
    .geocode-search { width: 100%; max-width: 100%; }
    .mobile-menu-btn { display: inline-flex; }
    /* Collapsed state: hide the right-side group entirely. When the
       hamburger is open (.mobile-menu-open), re-show it as a full-width
       dropdown panel positioned under the topbar. */
    .topbar-right {
        display: none;
    }
    .topbar.mobile-menu-open .topbar-right {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        gap: 4px;
        position: absolute;
        /* Anchor below the (safe-area-inflated) topbar. */
        top: calc(var(--topbar-height) + var(--safe-top));
        left: 0;
        right: 0;
        padding: 10px calc(12px + var(--safe-right)) 12px calc(12px + var(--safe-left));
        background: #18181b;
        border-bottom: 1px solid #27272a;
        box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
        z-index: 1250;
    }
    /* Inside the mobile panel: make menu roots, the Tips button, the
       locale select and sign-out look like a stacked list of tall tap
       targets. The existing dropdowns (Import / Admin submenus) already
       render below their trigger and reflow naturally. */
    .topbar.mobile-menu-open .topbar-menu { position: static; }
    .topbar.mobile-menu-open .topbar-btn,
    .topbar.mobile-menu-open .topbar-btn-link,
    .topbar.mobile-menu-open #openTips {
        width: 100%;
        justify-content: flex-start;
        text-align: left;
        padding: 10px 12px;
        font-size: 14px;
        border-radius: 6px;
    }
    .topbar.mobile-menu-open .topbar-dropdown {
        position: static;
        width: 100%;
        min-width: 0;
        margin-top: 4px;
    }
    .topbar.mobile-menu-open .topbar-locale {
        padding: 8px 10px;
        font-size: 13px;
        width: 100%;
    }
    .topbar.mobile-menu-open .topbar-account {
        max-width: none;
        padding: 6px 12px;
        font-size: 12px;
    }

    /* Shift the modal container down by the topbar height so the
       centered dialog never overlaps the topbar. Put the scroll on
       `.modal` itself (not on the dialog) so the dialog's header
       bleed (negative margins) has nothing to be clipped against.
       The dialog stays a natural-height block and the whole thing
       scrolls as one if content exceeds the viewport. */
    body .modal {
        padding: calc(var(--topbar-height) + var(--safe-top) + 8px) 0 8px;
        align-items: flex-start;
        overflow-y: auto;
        overflow-x: hidden;
    }
    /* Dialog no longer owns the scroll. Max-height is removed so it
       can grow naturally; the `.modal` wrapper scrolls instead. */
    body .modal-dialog {
        width: calc(100vw - 24px) !important;
        max-width: calc(100vw - 24px) !important;
        max-height: none !important;
        padding: 20px 18px;
        overflow: visible;
    }
    body .modal-dialog.modal-wide { width: calc(100vw - 24px) !important; }
    .import-dialog { padding: 0 !important; }
    .import-body { padding: 16px 18px 16px !important; }
    /* Right padding reserves space for the 36x36 `.modal-close` pill
       (right: 12px + 36px button + 8px gap). Without this the title
       overlaps the red close circle. */
    .import-header { padding: 14px 56px 14px 18px !important; }
    /* Import target picker: stack the two cards on mobile instead of
       forcing them into a 2-column grid that truncates the "Vytvořit
       novou…" title with an ellipsis. */
    .target-options { grid-template-columns: 1fr !important; }

    /* Action bars at the bottom: wrap into a rounded-rect card so every
       control is visible at once. The desktop pill shape (border-radius
       999px) doesn't play with multi-row content, so we soften it here.
       `body` prefix bumps specificity above the base `.action-bar` rule,
       which is *later* in this file and would otherwise win on source
       order. */
    body .action-bar {
        flex-wrap: wrap;
        justify-content: center;
        row-gap: 4px;
        column-gap: 6px;
        padding: 6px 10px;
        border-radius: 12px;
        font-size: 12px;
    }
    /* Label spans the full row so the action buttons below line up on
       their own line, instead of the label squeezing itself between
       wrapped buttons at odd offsets. */
    body .action-bar .action-bar-label {
        flex: 0 0 100%;
        text-align: center;
    }
    /* Selection metrics get their own row too - the side borders that
       visually separate the metrics from the label on desktop become
       stray lines when the pill wraps, so we drop them here. */
    body .action-bar .selection-stats {
        flex: 0 0 100%;
        justify-content: center;
        border-left: none;
        border-right: none;
        padding: 0;
    }
    /* Sidebar is an overlay drawer on mobile, not a grid cell, so the
       desktop calc `340px + (100vw-340px)/2` lands the stack off-screen.
       Pin the stack to the map's horizontal center and cap the bars so
       they never exceed the viewport width. The `body` prefix bumps
       specificity above the base .action-bar-stack rule, which sits
       *later* in this file and would otherwise win on source order. */
    body .action-bar-stack {
        left: 0;
        right: 0;
        transform: none;
        align-items: center;
        padding: 0 calc(12px + var(--safe-right)) 0 calc(12px + var(--safe-left));
    }
    body .action-bar-stack > .action-bar {
        width: 100%;
        max-width: calc(100vw - 24px - var(--safe-left) - var(--safe-right));
    }

    /* Touch-target sizing: bump small icon buttons so they're easier to
       hit with a thumb. iOS HIG recommends 44x44, Android Material 48x48 -
       we compromise at 40px for sidebar items (space-constrained), 44px
       for topbar / modal actions. Visual glyphs stay small; padding
       grows the hit area. */
    /* Stretch-to-sibling pattern: sidebar icon buttons adopt the row
       height set by their bigger flex/grid sibling (project dropdown,
       "All layers" checkbox label, layer row content). Keeps a
       minimum touch width without letting any button tower over its
       row. */
    .project-toolbar button.icon,
    .project-toolbar button#addProject,
    .layer-toolbar button,
    #layerList .layer-vis,
    #layerList button {
        align-self: stretch;
        min-width: 32px;
        min-height: 0;
    }
    .shape-filter-clear {
        width: 32px;
        height: 32px;
    }
    .topbar-btn, .topbar-btn-link, #openTips, .mobile-menu-btn {
        min-height: 40px;
    }
    .mobile-menu-btn {
        width: 40px;
        height: 40px;
    }
    .sidebar-toggle {
        width: 40px;
        height: 40px;
    }
    /* Modal close buttons - the `×` glyph in the top-right corner.
       Base 26x26 is too small for a thumb; 36x36 is the tight
       compromise between tap comfort and not eating the title bar.
       Header gets just enough right padding to clear the pill. */
    .modal-dialog .modal-close {
        width: 36px;
        height: 36px;
        font-size: 22px;
    }
    /* Match the header's negative margins to our mobile dialog
       padding (18px) so the dark header still bleeds flush with the
       dialog's rounded edge. Extra right padding reserves room for
       the 36x36 close button absolutely-positioned inside. */
    body .modal-dialog > h3:first-child {
        margin: -20px -18px 4px;
        padding: 12px 56px 12px 18px;
    }
    /* Action-bar buttons: the floating toolbar below the map. Kept
       compact - the pill is already a tap target; full 40px buttons on
       every row bloat it to half the screen.
       `touch-action: manipulation` removes the 300ms tap-to-click
       delay some mobile browsers apply to double-tap-zoom, so Cancel
       etc. react on the first tap. */
    .action-bar .action-btn,
    .action-bar button {
        min-height: 32px;
        padding: 6px 10px;
        font-size: 12px;
        touch-action: manipulation;
    }

    /* Users table -> stacked cards. Each <tr> becomes a bordered block;
       each <td> a label + value pair. The ::before pulls the header
       name from `data-label` which renderUsers() sets from the i18n
       strings, so the cards stay localized without duplicating headers
       in HTML.
       All selectors are prefixed with `#userManagement` so they beat
       the base `.users-table td { padding: 8px 10px }` rule which lives
       later in the file. Without that specificity bump the base padding
       / border / nowrap rules leak through and the card content can
       push past the modal's edge. */
    #userManagement .users-table thead { display: none; }
    /* box-sizing: border-box is critical. Without it `width: 100%`
       means content-box = 100%, so the card's own padding + border
       ADD to its total width, pushing the card past the dialog and
       clipping buttons on the right. */
    #userManagement .users-table,
    #userManagement .users-table tbody,
    #userManagement .users-table tr,
    #userManagement .users-table td {
        display: block;
        width: 100%;
        box-sizing: border-box;
    }
    /* Critical: base `.users-table { overflow-y: auto }` causes the spec
       to implicitly promote overflow-x from `visible` to `auto` too.
       That auto-horizontal scroll is exactly the bar we're trying to
       kill, so pin overflow-x to hidden explicitly. */
    #userManagement .users-table {
        overflow-x: hidden;
    }
    #userManagement .users-table tr {
        border: 1px solid #e4e4e7;
        border-radius: 8px;
        padding: 8px 10px;
        margin-bottom: 10px;
        background: #fff;
    }
    /* Grid instead of flex: `minmax(0, 1fr)` is the idiomatic escape
       hatch for "let this column shrink below its content's min-content"
       - crucial when the cell holds a long unbreakable string like an
       email address, which a flex item's auto min-width won't shrink
       below and would otherwise push the card past the modal's edge. */
    #userManagement .users-table td {
        border: none;
        padding: 5px 0;
        display: grid;
        grid-template-columns: 56px minmax(0, 1fr);
        align-items: center;
        gap: 8px;
        white-space: normal;
    }
    #userManagement .users-table td::before {
        content: attr(data-label);
        font-weight: 600;
        color: #52525b;
        font-size: 11px;
        text-transform: uppercase;
        letter-spacing: 0.04em;
    }
    /* Long email / display-name strings still need explicit wrap rules
       so the text reflows inside the now-shrinkable grid track. */
    #userManagement .users-table td:nth-child(1),
    #userManagement .users-table td:nth-child(2) {
        word-break: break-word;
        overflow-wrap: anywhere;
        font-size: 13px;
    }
    #userManagement .users-table td select {
        width: 100%;
        min-width: 0;
        min-height: 34px;
    }
    /* Action row: Save + Delete buttons side-by-side, full width.
       Overrides the grid template to a pure button row. */
    #userManagement .users-table td.users-table-actions {
        display: flex;
        gap: 8px;
        padding-top: 8px;
        border-top: 1px solid #f4f4f5;
        margin-top: 4px;
    }
    #userManagement .users-table td.users-table-actions::before { display: none; }
    #userManagement .users-table td.users-table-actions button {
        flex: 1;
        min-height: 36px;
        padding: 6px 10px;
        font-size: 13px;
        margin: 0 !important;
    }
    /* "(you)" italic suffix that normally trails the email cell must still
       appear inline inside the flex row, not as a full-width block. */
    #userManagement .users-table tr.self td:first-child::after {
        display: inline;
    }

    /* Subscriptions plan editor: base uses `1fr auto` so the feature
       label and the input+unlimited cluster sit in one row. On narrow
       screens the number input + "Unlimited" checkbox label don't fit
       alongside the feature name, so stack them: label on top, input
       row below. */
    #subscriptionsDialog .subscriptions-features .feature-row {
        grid-template-columns: 1fr;
        row-gap: 4px;
    }
    #subscriptionsDialog .subscriptions-features .feature-input {
        justify-self: stretch;
        gap: 10px;
    }
    /* Let the number input share the row without being pinned to 100px
       if space is tight. */
    #subscriptionsDialog .subscriptions-features .feature-input input[type="number"] {
        width: auto;
        flex: 1;
        min-width: 0;
    }
    #subscriptionsDialog .subscriptions-features .feature-input label.unlimited {
        font-size: 13px;
    }
}
@keyframes sidebar-backdrop-in { from { opacity: 0; } to { opacity: 1; } }

#sidebar {
    background: #f4f4f5;
    border-right: 1px solid #d4d4d8;
    padding: 16px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

#sidebar h1 { font-size: 20px; margin: 0 0 8px; }
#sidebar h2 {
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #52525b;
    margin-top: 24px;
    margin-bottom: 8px;
}
/* First section heading sits right under the topbar - no extra top margin
   so the gap between the black bar and "Projekt" matches the sidebar's
   own padding (16px) instead of padding + margin (40px). */
#sidebar h2:first-child { margin-top: 0; }
#sidebar .hint { font-size: 13px; color: #52525b; line-height: 1.5; }

#map { height: 100%; }
/* Leaflet sets tabindex=0 on the map container for keyboard panning. On
   :focus-visible (after any keypress) browsers draw a focus outline that
   shows as a blue rectangle around the canvas — distracting for a map. */
.leaflet-container:focus,
.leaflet-container:focus-visible { outline: none; }

/* --- Project panel --------------------------------------------------- */

/* Shared minimum row height so the Project and Layers toolbars have
   the same vertical footprint. Without this the Layers row collapses
   to its "All layers" checkbox label height and its + button ends up
   visibly shorter than the Project row buttons above it. */
.project-toolbar,
.layer-toolbar {
    min-height: 30px;
}
.project-toolbar {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 8px;
}
.project-toolbar #projectSelect {
    flex: 1;
    min-width: 0;
    padding: 4px 6px;
    font: inherit;
    font-size: 13px;
    border: 1px solid #d4d4d8;
    border-radius: 6px;
    background: #fff;
    cursor: pointer;
}
.project-toolbar button {
    padding: 4px 8px;
    font-size: 12px;
    /* Match the dropdown's height (the tallest sibling in the flex
       row) so the edit / delete / + buttons line up with the select
       instead of sitting a couple of pixels shorter. */
    align-self: stretch;
}
.project-toolbar button.icon {
    background: transparent;
    color: #18181b;
    border-color: #d4d4d8;
    padding: 4px 6px;
    line-height: 1;
}
.project-toolbar button.icon.danger-icon { color: #dc2626; }

/* --- Layers panel ----------------------------------------------------- */

.layer-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}
.layer-toolbar .all-layers {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    cursor: pointer;
    user-select: none;
}
.layer-toolbar button {
    padding: 4px 8px;
    font-size: 12px;
    align-self: stretch;
}

#layerList { list-style: none; padding: 0; margin: 0 0 8px; }
#layerList li {
    padding: 6px 8px;
    border: 1px solid #d4d4d8;
    border-radius: 6px;
    background: #fff;
    margin-bottom: 6px;
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    cursor: pointer;
    user-select: none;
    transition: background-color 0.1s ease-in, border-color 0.1s ease-in;
}
#layerList li:hover { background: #fafafa; border-color: #a1a1aa; }
#layerList li.active {
    border-color: #18181b;
    box-shadow: 0 0 0 1px #18181b inset;
    background: #fff;
}
#layerList .swatch {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 3px;
    border: 1px solid rgba(0, 0, 0, 0.2);
}
#layerList .name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
#layerList .name.active { font-weight: 600; }
#layerList input[type="radio"],
#layerList input[type="checkbox"] {
    margin: 0;
    cursor: pointer;
}
#layerList .layer-vis {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    padding: 0;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 6px;
    cursor: pointer;
    color: #52525b;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
    /* Stretch to row height so the eye icon lines up with edit/delete
       and the rest of the row content. */
    align-self: stretch;
}
#layerList .layer-vis:hover { background: #f4f4f5; color: #18181b; border-color: #d4d4d8; }
#layerList .layer-vis.off { color: #a1a1aa; }
#layerList .layer-vis.on { color: #2563eb; }

#layerList .edit,
#layerList .delete {
    background: transparent;
    border-color: transparent;
    padding: 2px 4px;
    font-size: 13px;
    line-height: 1;
    align-self: stretch;
}
#layerList .edit { color: #2563eb; }
#layerList .delete { color: #dc2626; }
#layerList .empty {
    grid-template-columns: 1fr;
    justify-items: center;
    color: #71717a;
    font-style: italic;
    cursor: default;
}

/* --- Shape list ------------------------------------------------------- */

/* Filter input above the Saved-shapes list. Small gap below separates it
   from the list visually. The × button is shown only when there's text —
   clicking it clears the filter and returns the full list. */
.shape-filter-wrap {
    position: relative;
    margin: 0 0 12px 0;
}
.shape-filter {
    width: 100%;
    box-sizing: border-box;
    padding: 6px 28px 6px 8px;
    font-size: 13px;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    background: #fff;
}
.shape-filter:focus {
    outline: none;
    border-color: #2563eb;
    box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.15);
}
/* Hide browsers' native clear so the custom button is the only one shown. */
.shape-filter::-webkit-search-cancel-button { display: none; }
.shape-filter-clear {
    position: absolute;
    right: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 20px;
    height: 20px;
    line-height: 1;
    padding: 0;
    border: none;
    background: transparent;
    color: #71717a;
    cursor: pointer;
    font-size: 16px;
    border-radius: 50%;
}
.shape-filter-clear:hover {
    color: #18181b;
    background: #f4f4f5;
}

/* Filter stats strip: "Found N · reason1 x · reason2 y". Shown only when
   the filter input has text. Compact, wraps on narrow sidebars. */
.shape-filter-stats {
    margin: -6px 0 10px 0;
    font-size: 11px;
    color: #71717a;
    display: flex;
    flex-wrap: wrap;
    gap: 4px 6px;
    align-items: center;
}
.filter-stats-summary {
    font-weight: 600;
    color: #3f3f46;
}
.filter-stats-chip {
    padding: 1px 7px;
    background: #f4f4f5;
    border: 1px solid #e4e4e7;
    border-radius: 10px;
    font-size: 10px;
    color: #52525b;
    white-space: nowrap;
    cursor: pointer;
    font-family: inherit;
    line-height: 1.4;
}
.filter-stats-chip:hover { background: #e4e4e7; border-color: #d4d4d8; }
.filter-stats-chip.active {
    background: #2563eb;
    border-color: #2563eb;
    color: #fff;
}
.filter-stats-chip.active:hover { background: #1d4ed8; }

.filter-stats-select {
    margin-left: auto;   /* push to the right of the chip row */
    padding: 2px 10px;
    background: #2563eb;
    border: 1px solid #2563eb;
    border-radius: 10px;
    font-size: 10px;
    font-weight: 600;
    color: #fff;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
    cursor: pointer;
    font-family: inherit;
    line-height: 1.5;
}
.filter-stats-select:hover { background: #1d4ed8; border-color: #1d4ed8; }

/* Scroll-to-filter pill: sticky to the bottom of the sidebar's scroll
   context, hidden when the filter input is on screen, revealed via a
   .visible class driven by an IntersectionObserver. */
.scroll-to-filter {
    position: sticky;
    bottom: 12px;
    align-self: flex-end;
    margin-top: auto;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: #18181b;
    color: #fafafa;
    border: none;
    font-size: 20px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
    display: none;
    align-items: center;
    justify-content: center;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
    z-index: 10;
}
.scroll-to-filter.visible { display: flex; }
.scroll-to-filter:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35); }

/* Applied to marker icon DOM when filter is active and this marker didn't
   match — makes matched shapes pop without hiding the non-matched context. */
.leaflet-marker-icon.filter-dimmed { opacity: 0.18; }
#shapeList { list-style: none; padding: 0; margin: 0; flex: 1; }
#shapeList li {
    padding: 8px 10px;
    border: 1px solid #d4d4d8;
    border-radius: 6px;
    background: #fff;
    margin-bottom: 6px;
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 10px;
    font-size: 13px;
    cursor: pointer;
    user-select: none;
    transition: background-color 0.1s ease-in, border-color 0.1s ease-in;
}
#shapeList li:hover { background: #fafafa; border-color: #a1a1aa; }
#shapeList li.selected { background: #e0edff; border-color: #3b82f6; }

/* Highlight selected markers on the map */
.marker-selected { filter: brightness(1.3) drop-shadow(0 0 6px rgba(59, 130, 246, 0.8)); }
#shapeList li.empty {
    cursor: default;
    grid-template-columns: 1fr;
    justify-items: center;
    color: #71717a;
    font-style: italic;
}
#shapeList .dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 1px solid rgba(0, 0, 0, 0.25);
}
#shapeList .meta { display: flex; flex-direction: column; min-width: 0; }
#shapeList .title {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
#shapeList .edit {
    background: transparent;
    border-color: transparent;
    color: #2563eb;
    padding: 2px 4px;
    font-size: 13px;
    line-height: 1;
}
#shapeList .title.placeholder {
    font-weight: 400;
    font-style: italic;
    color: #a1a1aa;
}
#shapeList .sub {
    color: #71717a;
    font-size: 11px;
    text-transform: capitalize;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

button {
    padding: 6px 10px;
    border: 1px solid #18181b;
    background: #18181b;
    color: #fff;
    border-radius: 6px;
    cursor: pointer;
    font-size: 13px;
}
button.danger { background: var(--color-danger); border-color: var(--color-danger); }
button.danger:hover { background: var(--color-danger-hover); border-color: var(--color-danger-hover); }
button.link {
    background: transparent;
    color: #dc2626;
    border-color: transparent;
    padding: 2px 6px;
}
/* Compact cross-icon variant used for delete buttons in the shape list —
   matches the ✎ edit icon sitting next to it. */
button.link.icon-x {
    font-size: 18px;
    line-height: 1;
    padding: 0 6px;
    font-weight: 400;
}
button.link.icon-x:hover {
    background: #fee2e2;
    border-radius: 4px;
}
.toolbar { margin-top: 4px; margin-bottom: 8px; }

.api-status {
    margin-top: 16px;
    font-size: 11px;
    color: #71717a;
    border-top: 1px solid #d4d4d8;
    padding-top: 8px;
}
.api-status.ok { color: #15803d; }
.api-status.err { color: #dc2626; }

/* Leaflet-draw toolbar gets dimmed and click-blocked when there is no active,
   visible layer to draw into — see updateDrawAvailability() in app.js. */
.leaflet-draw.draw-disabled {
    opacity: 0.4;
    pointer-events: none;
    filter: grayscale(1);
}

/* Plan-gated: user has hit the account's object budget. Stays clickable
   (unlike draw-disabled) so our capture-phase handler can route the click
   to the upgrade dialog. Dimmed + 🔒 badge in the top-right corner. */
.leaflet-draw.draw-locked {
    opacity: 0.55;
    filter: grayscale(0.6);
    position: relative;
    cursor: not-allowed;
}
.leaflet-draw.draw-locked::after {
    content: '🔒';
    position: absolute;
    top: -8px;
    right: -8px;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    background: #dc2626;
    border-radius: 50%;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
    pointer-events: none;
}

/* Hide Leaflet.Draw's own contextual action toolbar (Finish / Delete last
   point / Cancel) — we render those actions in #drawToolbar instead, in the
   shared bottom action-bar. The main draw-tool palette stays visible. */
.leaflet-draw-actions { display: none !important; }

/* Active draw tool: tinted blue bg + bold blue outline. Can't recolour
   the sprite icon to white (it's a background-image and filter: invert
   would flip the bg too, giving an orange tone), so we keep the icon
   dark and contrast it against a light-blue pill instead.
   Leaflet.Draw itself adds `leaflet-draw-toolbar-button-enabled` to
   whichever tool is currently enabled - no JS wiring needed on our
   side. Covering :hover/:focus too so the button stays visibly blue
   even while the cursor is still on it right after the click. */
.leaflet-draw-toolbar a.leaflet-draw-toolbar-button-enabled,
.leaflet-draw-toolbar a.leaflet-draw-toolbar-button-enabled:hover,
.leaflet-draw-toolbar a.leaflet-draw-toolbar-button-enabled:focus {
    background-color: #dbeafe !important;
    box-shadow: inset 0 0 0 2px #2563eb !important;
}

/* Leaflet.Draw's floating instruction / distance tooltip is pinned to
   the pointer. On the first click of a tool the pointer is still over
   the toolbar button at the top-left of the map, so the tooltip pops
   up right there - partly behind our topbar (z:1500) because the
   tooltip lives inside map-pane's own stacking context and can't
   outrank a root-level element from within.
   Hide the tooltip until the pointer enters the map; once the user
   moves the cursor we remove `.draw-tip-warmup` from the container
   (see DRAWSTART in app.js) and the tooltip behaves normally,
   including showing the running distance between vertices. */
.leaflet-container.draw-tip-warmup .leaflet-draw-tooltip {
    display: none !important;
}


/* iOS-hover-bug guard: on touch devices, tapping an element that has a
   :hover style registers only as hover on the first tap - the click
   event fires on the SECOND tap. Disable hover styling for all Leaflet
   controls so draw / zoom / select buttons respond to the first tap. */
@media (hover: none) {
    .leaflet-bar a:hover,
    .leaflet-draw-toolbar a:hover,
    .leaflet-control-layers label:hover {
        background-color: inherit;
    }
}

/* Permanent center label drawn over each named shape on the map. */
.leaflet-tooltip.shape-label {
    background: transparent;
    border: none;
    box-shadow: none;
    padding: 0;
    color: #18181b;
    font-weight: 600;
    font-size: 12px;
    text-shadow:
        -1px -1px 0 #fff,  1px -1px 0 #fff,
        -1px  1px 0 #fff,  1px  1px 0 #fff,
         0   -1px 0 #fff,  0    1px 0 #fff,
        -1px  0   0 #fff,  1px  0   0 #fff;
    pointer-events: none;
    white-space: nowrap;
}
.leaflet-tooltip.shape-label::before { display: none; }

/* Colored marker icon (used by L.divIcon — see colorIcon() in app.js). */
.colored-marker { background: transparent !important; border: 0 !important; }
.colored-marker .marker-pin {
    display: block;
    width: 16px;
    height: 16px;
    margin: 2px 0 0 2px;
    border-radius: 50% 50% 50% 0;
    transform: rotate(-45deg);
    border: 2px solid #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
}

/* --- Layer editor modal --------------------------------------------- */

.modal[hidden] { display: none; }
.modal {
    position: fixed;
    inset: 0;
    z-index: 3000;
    display: flex;
    align-items: center;
    justify-content: center;
}
.modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
}
.modal-dialog {
    position: relative;
    background: #fff;
    border-radius: 8px;
    padding: 20px 22px;
    width: 320px;
    max-width: calc(100vw - 32px);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.modal-dialog.modal-wide {
    width: 640px;
    max-height: calc(100vh - 64px);
}
.modal-dialog.modal-med {
    width: 400px;
}

/* User management table */
.users-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
    overflow-y: auto;
}
.users-table th,
.users-table td {
    padding: 8px 10px;
    text-align: left;
    border-bottom: 1px solid #e4e4e7;
    vertical-align: middle;
}
.users-table th {
    font-weight: 600;
    color: #52525b;
    background: #fafafa;
    position: sticky;
    top: 0;
}
.users-table td select {
    padding: 4px 6px;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    font: inherit;
    background: #fff;
}
.users-table td button {
    padding: 4px 10px;
    font-size: 12px;
}
/* Last column holds Save + Delete side-by-side. Flex keeps them on one
   line even when the table column is tight; nowrap stops any accidental
   wrapping if the dialog gets narrow. */
.users-table td:last-child {
    white-space: nowrap;
}
.users-table td:last-child button {
    display: inline-block;
    vertical-align: middle;
    margin: 0;
}
.users-table td:last-child button + button { margin-left: 6px; }
.users-table tr.self td:first-child::after {
    content: " (you)";
    color: #71717a;
    font-style: italic;
}

/* Subscriptions editor: feature rows */
.subscriptions-features {
    display: flex;
    flex-direction: column;
    gap: 6px;
    border: 1px solid #e4e4e7;
    border-radius: 6px;
    padding: 10px 12px;
    background: #fafafa;
}
.subscriptions-features .feature-row {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 8px;
    align-items: center;
    font-size: 13px;
}
.subscriptions-features .feature-input {
    display: flex;
    align-items: center;
    gap: 8px;
}
.subscriptions-features .feature-input input[type="number"] {
    width: 100px;
}
.subscriptions-features .feature-input label.unlimited {
    font-size: 12px;
    color: #52525b;
    display: flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
}
.subscriptions-features .feature-key { color: #18181b; }
.subscriptions-features .feature-type {
    color: #71717a;
    font-size: 11px;
    margin-left: 4px;
}
.subscriptions-features input[type="number"],
.subscriptions-features input[type="checkbox"] {
    font: inherit;
    padding: 4px 6px;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
}
.subscriptions-features input[type="checkbox"] {
    width: 18px;
    height: 18px;
    margin: 0;
    justify-self: start;
}

/* --- Selection tool (rubber-band) ------------------------------------ */

/* Control button sits in the leaflet-bar stack — already styled by leaflet;
   we just colour-swap when active so the user sees selection mode is on. */
.select-control-btn {
    display: flex !important;
    align-items: center;
    justify-content: center;
    color: #18181b;
}
.select-control-btn.active,
.route-control-btn.active,
.import-roads-control-btn.active,
.import-buildings-control-btn.active,
.snap-control-btn.active {
    background: #2563eb !important;
    color: #fff !important;
}
/* Merged-bar lock badge — applies to the <a> button directly because
   mergeToolBars reparents the button out of its original container (where
   the .feature-locked class lives on the now-hidden parent). */
.route-control-btn.feature-locked,
.import-roads-control-btn.feature-locked,
.import-buildings-control-btn.feature-locked {
    position: relative;
    opacity: 0.55;
    filter: grayscale(0.6);
    cursor: not-allowed;
}
.route-control-btn.feature-locked::after,
.import-roads-control-btn.feature-locked::after,
.import-buildings-control-btn.feature-locked::after {
    content: '🔒';
    position: absolute;
    top: -8px;
    right: -8px;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    background: #dc2626;
    border-radius: 50%;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
    pointer-events: none;
}


/* Cursor hint while selection mode is on. */
#map.select-mode { cursor: crosshair; }
#map.select-mode .leaflet-interactive { cursor: crosshair; }

/* Rubber-band overlay — just a dashed outline, no fill. `mix-blend-mode:
   difference` inverts the border pixels against whatever is below, so the
   frame stays visible on any basemap (OSM, Esri imagery, snow, forest…).
   Transparent interior means the map inside the selection stays readable.
   `.crossing` class is still toggled by JS for semantic parity. */
.rubber-band,
.rubber-band.crossing {
    position: absolute;
    pointer-events: none;
    z-index: 450;
    background: transparent;
    border: 2px dashed #fff;
    mix-blend-mode: difference;
}

/* Floating action-bar component — shared style for multi-selection, geometry
   editing, and any future contextual toolbars. Bars stack at the bottom of
   the map area; tweak colors/spacing here and every bar updates. */
.action-bar-stack {
    position: fixed;
    /* Clear the iOS home indicator on notched devices. */
    bottom: calc(24px + var(--safe-bottom));
    /* Span the full map area: sidebar edge on the left, viewport edge
       on the right. `align-items: center` centers the pills inside.
       Using `right: 0` (not `transform: translateX(-50%)` on a single
       `left` point) is important because shrink-to-fit would otherwise
       clamp the stack's natural width to the space between `left` and
       the viewport edge, forcing flex items to wrap mid-word when the
       devtools panel shrinks the viewport. */
    left: 340px;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    z-index: 1400;
    pointer-events: none;
}
/* Desktop-only: let the stack span the full viewport when the sidebar
   collapses. Scoped so the mobile override (which always pins it to
   left: 0) can't get outranked by this rule's specificity. */
@media (min-width: 769px) {
    #app.sidebar-hidden ~ .action-bar-stack {
        left: 0;
    }
}
.action-bar[hidden] { display: none; }
.action-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 14px;
    background: #18181b;
    color: #fafafa;
    border-radius: 999px;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
    pointer-events: auto;
}
.action-bar-label {
    font-size: 13px;
    font-weight: 600;
    padding: 0 6px;
}
.action-btn {
    font-size: 12px;
    padding: 5px 12px;
    border-radius: 999px;
    border: 1px solid #3f3f46;
    background: #27272a;
    color: #fafafa;
    cursor: pointer;
}
.action-btn:hover { background: #3f3f46; }
.action-btn.primary {
    background: #2563eb;
    border-color: #2563eb;
}
.action-btn.primary:hover { background: #1d4ed8; border-color: #1d4ed8; }
.action-btn.danger {
    background: #dc2626;
    border-color: #b91c1c;
}
.action-btn.danger:hover { background: #b91c1c; }
.action-btn.ghost {
    background: transparent;
    border-color: #52525b;
    color: #a1a1aa;
}
.action-btn.ghost:hover {
    background: #27272a;
    color: #fafafa;
}
.action-btn.active {
    background: #2563eb;
    border-color: #2563eb;
    color: #fff;
}
.action-btn.active:hover { background: #1d4ed8; border-color: #1d4ed8; }
/* Touch devices: reset the :hover variants back to their non-hover
   background. Without this, iOS/Android treat the first tap as hover
   (styles change but click does NOT fire); the second tap fires click.
   Keeping the hover rules above unconditional means desktop mouse
   behaviour is exactly what it was before - this block only affects
   devices that report no hover capability. */
@media (hover: none) {
    .action-btn:hover           { background: #27272a; }
    .action-btn.primary:hover   { background: #2563eb; border-color: #2563eb; }
    .action-btn.danger:hover    { background: #dc2626; border-color: #b91c1c; }
    .action-btn.ghost:hover     { background: transparent; color: #a1a1aa; }
    .action-btn.active:hover    { background: #2563eb; border-color: #2563eb; }
}

/* Snap toggle — amber when OFF (warning cue: drawing without snap is
   easier to make imprecise), green when ON (confirmation). Both override
   the generic .action-btn colours. */
#drawToolbarSnap:not(.active) {
    background: #f59e0b;
    border-color: #f59e0b;
    color: #18181b;
}
#drawToolbarSnap:not(.active):hover {
    background: #d97706;
    border-color: #d97706;
}
#drawToolbarSnap.active {
    background: #16a34a;
    border-color: #16a34a;
    color: #fff;
}
#drawToolbarSnap.active:hover {
    background: #15803d;
    border-color: #15803d;
}

/* Aggregate stats in the selection bar — compact chips on the dark pill
   background showing Σ area / perimeter / length across a multi-select. */
.selection-stats[hidden] { display: none; }
.selection-stats {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 0 4px;
    border-left: 1px solid #3f3f46;
    border-right: 1px solid #3f3f46;
    margin: 0 2px;
}
.selection-stat {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    background: rgba(255, 255, 255, 0.06);
    border-radius: 999px;
    font-size: 11px;
}
.selection-stat-label {
    color: #a1a1aa;
    letter-spacing: 0.04em;
    font-weight: 600;
}
.selection-stat-value {
    color: #fafafa;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.selection-stat-value.locked {
    font-size: 12px;
    line-height: 1;
    filter: grayscale(0.2);
}
.selection-stat.locked {
    cursor: pointer;
    transition: background 0.15s;
}
.selection-stat.locked:hover {
    background: rgba(220, 38, 38, 0.25);
}

/* Shape editor: dynamic key/value attributes list */
.attrs-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
.attrs-add {
    font-size: 12px;
    padding: 2px 10px;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    background: #fff;
    color: #18181b;
    cursor: pointer;
}
.attrs-add:hover { background: #f4f4f5; }
.attrs-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 180px;
    overflow-y: auto;
}
.attrs-list:empty::before {
    content: "No attributes yet.";
    color: #a1a1aa;
    font-size: 12px;
    font-style: italic;
    padding: 4px 2px;
}
.attr-row {
    display: grid;
    /* Fixed column for the remove button so its width doesn't shift with row
       state (hover border etc). Explicit gaps keep × well away from the value. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) 28px;
    column-gap: 12px;
    align-items: center;
}
.attr-row input[type="text"] {
    width: 100%;
    box-sizing: border-box; /* otherwise width:100% + padding overflows the cell */
    padding: 5px 8px;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    font-size: 12px;
    background: #fff;
}
.attr-row input[type="text"]:focus {
    outline: none;
    border-color: #2563eb;
    box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.15);
}
.attr-row .attr-remove {
    width: 26px;
    height: 26px;
    border: 1px solid #e4e4e7;
    background: #fff;
    border-radius: 4px;
    color: #71717a;
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    padding: 0;
}
.attr-row .attr-remove:hover {
    background: #fee2e2;
    border-color: #fca5a5;
    color: #b91c1c;
}

/* --- Import dialog (sexier version) ---------------------------------- */

.import-dialog {
    padding: 0;
    overflow: hidden;
    width: 520px;
    gap: 0;
}
.import-header {
    position: relative;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 20px 24px;
    background: linear-gradient(135deg, #0f172a 0%, #1e3a8a 60%, #2563eb 100%);
    color: #fff;
}
.import-header-icon {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    padding: 6px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.15);
    border: 1px solid rgba(255, 255, 255, 0.25);
}
.import-header-title {
    font-size: 17px;
    font-weight: 700;
}
.import-header-sub {
    font-size: 12px;
    color: rgba(255, 255, 255, 0.75);
    margin-top: 2px;
}

.import-body {
    padding: 20px 24px 20px;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Custom file drop zone — hides the native input and shows a big dashed
   target instead. JS toggles .has-file / .dragging classes. */
.file-drop {
    display: block;
    border: 2px dashed #d4d4d8;
    border-radius: 10px;
    background: #fafafa;
    padding: 20px 16px;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.file-drop:hover {
    border-color: #2563eb;
    background: #eff6ff;
}
.file-drop.dragging {
    border-color: #2563eb;
    background: #dbeafe;
    transform: scale(1.01);
}
.file-drop.has-file {
    border-style: solid;
    border-color: #16a34a;
    background: #f0fdf4;
}
.file-drop-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    text-align: center;
    color: #52525b;
    pointer-events: none;
}
.file-drop-inner svg {
    width: 28px;
    height: 28px;
    color: #71717a;
}
.file-drop.has-file .file-drop-inner svg { color: #16a34a; }
.file-drop-text { font-size: 13px; }
.file-drop-text strong { color: #18181b; }
.file-drop-text code {
    background: #e4e4e7;
    padding: 1px 6px;
    border-radius: 4px;
    font-size: 12px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.file-drop.has-file .file-drop-text strong { color: #15803d; }
.file-drop-meta {
    font-size: 11px;
    color: #71717a;
    margin-top: 2px;
}

/* Target layer picker as two side-by-side cards */
.target-group { display: flex; flex-direction: column; gap: 8px; }
.target-label {
    font-size: 12px;
    color: #52525b;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-weight: 600;
}
.target-options {
    display: grid;
    /* minmax(0, 1fr) forces truly equal columns — otherwise the card with the
       wider inner controls (new-layer name + color) inflates its 1fr track and
       squeezes the other title onto two lines. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 10px;
}
.target-card {
    display: flex;
    gap: 10px;
    padding: 12px 12px;
    border: 1px solid #e4e4e7;
    border-radius: 8px;
    cursor: pointer;
    background: #fff;
    transition: border-color 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
}
.target-card:hover { border-color: #a1a1aa; }
.target-card:has(input[type="radio"]:checked) {
    border-color: #2563eb;
    background: #eff6ff;
    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.12);
}
.target-card input[type="radio"] {
    margin: 2px 0 0 0;
    accent-color: #2563eb;
}
.target-card-body { flex: 1; min-width: 0; }
.target-card-title {
    font-size: 13px;
    font-weight: 600;
    color: #18181b;
    margin-bottom: 6px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* `.import-dialog` prefix bumps specificity over `.modal-dialog select` which
   uses `font: inherit` and would otherwise reset our font-size. */
.import-dialog .target-card select {
    width: 100%;
    padding: 4px 6px;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    font-family: inherit;
    font-size: 12px;
    background: #fff;
}
.target-card:has(input[type="radio"]:not(:checked)) select { pointer-events: none; opacity: 0.55; }

.import-new-fields {
    display: flex;
    gap: 6px;
}
.import-new-fields[hidden] { display: none; }
.import-dialog .import-new-fields input[type="text"] {
    flex: 1;
    min-width: 0;
    padding: 4px 6px;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    font-family: inherit;
    font-size: 12px;
}
.import-new-fields input[type="color"] {
    width: 32px;
    height: 28px;
    padding: 0;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    cursor: pointer;
    flex-shrink: 0;
}

.country-results {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 220px;
    overflow-y: auto;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    background: #fff;
}
.country-results-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 10px;
    cursor: pointer;
    font-size: 12px;
    border-bottom: 1px solid #f1f1f4;
}
.country-results-item:last-child { border-bottom: none; }

/* --- Locate-me control + pin --- */
.locate-control-btn {
    display: flex !important;
    align-items: center;
    justify-content: center;
    color: #18181b;
}
.locate-control-btn.active { background: #2563eb !important; color: #fff !important; }
.locate-control-btn.loading {
    animation: locate-spin 0.9s linear infinite;
    color: #2563eb;
}
@keyframes locate-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

.locate-pin {
    position: relative;
    pointer-events: none;
}
.locate-pin-dot {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: #2563eb;
    border: 2px solid #fff;
    box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.4), 0 2px 6px rgba(0, 0, 0, 0.25);
    z-index: 2;
}
.locate-pin-pulse {
    position: absolute;
    top: 0;
    left: 0;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: rgba(37, 99, 235, 0.45);
    animation: locate-pulse 1.8s ease-out infinite;
}
@keyframes locate-pulse {
    0%   { transform: scale(0.8); opacity: 0.7; }
    100% { transform: scale(3);   opacity: 0;   }
}

/* --- Address search (topbar center slot) --- */
.geocode-search {
    position: relative;  /* dropdown positions against this */
    width: 420px;
    max-width: 100%;
    font-family: inherit;
}
.geocode-input {
    width: 100%;
    padding: 6px 10px;
    border: 1px solid #d4d4d8;
    border-radius: 6px;
    font: inherit;
    font-size: 13px;
    background: #fff;
    color: #18181b;
    outline: none;
}
.geocode-input::placeholder { color: #71717a; }
.geocode-input:focus {
    border-color: #2563eb;
    box-shadow: 0 0 0 2px rgba(37,99,235,0.35);
}
.geocode-results {
    list-style: none;
    margin: 0;
    padding: 0;
    position: absolute;  /* drop beneath the input, on top of the map */
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    max-height: 320px;
    overflow-y: auto;
    border: 1px solid #d4d4d8;
    border-radius: 6px;
    background: #fff;
    color: #18181b;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
    z-index: 1600;  /* above topbar (1500) so dropdown overlaps map */
}
.geocode-results[hidden] { display: none; }
.geocode-results li {
    padding: 7px 10px;
    font-size: 12px;
    cursor: pointer;
    border-bottom: 1px solid #f1f1f4;
    display: flex;
    align-items: flex-start;
    gap: 8px;
}
.geocode-result-icon {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    color: #52525b;
    margin-top: 1px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.geocode-results li:last-child { border-bottom: none; }
.geocode-results li:hover,
.geocode-results li.active { background: #eff6ff; }
.geocode-result-label {
    flex: 1;
    min-width: 0;
    line-height: 1.35;
    /* Full addresses can be long (street, postcode, city, region, country)
       - let them wrap instead of truncating so the user can see the whole
       match. The dropdown already scrolls when rows overflow vertically. */
    word-break: break-word;
}
.geocode-result-source {
    font-size: 10px;
    color: #71717a;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    flex-shrink: 0;
}
.geocode-empty {
    padding: 7px 10px;
    font-size: 12px;
    color: #71717a;
    font-style: italic;
    background: #fff;
    border: 1px solid #d4d4d8;
    border-radius: 6px;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
    margin-top: 4px;
}

/* --- Tips & tricks modal --- */
.tips-dialog { max-height: 80vh; overflow-y: auto; }
.tips-section { margin-top: 14px; }
.tips-section:first-of-type { margin-top: 8px; }
.tips-section h4 {
    margin: 0 0 6px;
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #52525b;
}
.tips-section p { margin: 0 0 4px; font-size: 13px; line-height: 1.5; color: #27272a; }
.tips-section ul {
    list-style: disc;
    padding-left: 20px;
    margin: 0;
}
.tips-section ul li {
    font-size: 13px;
    line-height: 1.5;
    color: #27272a;
    margin-bottom: 4px;
}
.tips-shortcuts { list-style: none !important; padding-left: 0 !important; }
.tips-shortcuts li { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.tips-shortcuts kbd {
    display: inline-block;
    padding: 2px 6px;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    background: #f4f4f5;
    font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
    font-size: 11px;
    color: #18181b;
    box-shadow: 0 1px 0 #d4d4d8;
}
.country-results-item:hover { background: #f5f5f7; }
.country-results-item.selected {
    background: #dbeafe;
    color: #1e3a8a;
    font-weight: 600;
}
.country-results-code {
    font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
    font-size: 11px;
    color: #71717a;
    margin-left: 8px;
}
.country-results-item.selected .country-results-code { color: inherit; }
.modal-dialog h3 { margin: 0 0 4px; font-size: 16px; }
/* Colored header shared by all standard modal dialogs. Bleeds past the
   dialog's default padding via negative margins, rounds only the top. */
.modal-dialog > h3:first-child {
    position: relative;
    margin: -20px -22px 4px;
    padding: 14px 22px;
    background: linear-gradient(135deg, #09090b 0%, #18181b 55%, #27272a 100%);
    color: #fafafa;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: 0.01em;
    border-radius: 8px 8px 0 0;
    box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.06);
    overflow: hidden;
}
.modal-dialog > h3:first-child::before {
    /* Cool indigo glow in the top-right — adds depth and a subtle pop of
       color against the graphite base. */
    content: "";
    position: absolute;
    top: -80%;
    right: -20%;
    width: 220px;
    height: 220px;
    background: radial-gradient(closest-side, rgba(129, 140, 248, 0.32), transparent 70%);
    pointer-events: none;
}
/* Close (×) button in the modal header — replaces Cancel in editing dialogs.
   Reuses data-close so existing modal close listeners catch it automatically. */
.modal-close {
    position: absolute;
    top: 50%;
    right: 12px;
    transform: translateY(-50%);
    width: 26px;
    height: 26px;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Extra bottom padding compensates for the "×" glyph sitting slightly
       above its line-box center — flex would otherwise render it low. */
    box-sizing: border-box;
    padding: 0 0 3px 0;
    margin: 0;
    border: 0;
    background: #dc2626;
    color: #fff;
    border-radius: 50%;
    font-size: 20px;
    font-weight: 400;
    line-height: 1;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
    z-index: 1;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.modal-close:hover {
    background: #b91c1c;
    color: #fff;
}
/* Plan dialog header is tall (icon + title + current plan line); pin the
   close button to the top-right corner instead of vertical-center. */
.plan-dialog-header .modal-close {
    top: 12px;
    right: 12px;
    transform: none;
}
.modal-dialog .field { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
.modal-dialog .field span { color: #52525b; }
.modal-dialog .field.field-inline {
    flex-direction: row;
    align-items: center;
    gap: 8px;
}
.modal-dialog .field.field-inline span { color: inherit; }
.modal-dialog input[type="range"] {
    width: 100%;
}
.modal-dialog input[type="text"],
.modal-dialog select {
    padding: 6px 8px;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    font: inherit;
    background: #fff;
}
.modal-dialog input[type="color"] {
    width: 48px;
    height: 28px;
    padding: 0;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    background: #fff;
    cursor: pointer;
}
/* Paired "swatch + hex text" input for the layer editor. The native color
   control stays small; the text field picks up the remaining width so the
   user can paste a hex string from Figma / a brand guide. */
.layer-color-pick {
    display: flex;
    align-items: center;
    gap: 8px;
}
.layer-color-pick input[type="color"] {
    width: 44px;
    height: 34px;
    flex-shrink: 0;
}
.layer-color-hex {
    flex: 1;
    min-width: 0;
    padding: 6px 8px;
    border: 1px solid #d4d4d8;
    border-radius: 4px;
    font: inherit;
    font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
    font-size: 13px;
    text-transform: uppercase;
    background: #fff;
}
.layer-color-hex:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 2px rgba(37,99,235,0.2); }
.layer-color-hex.invalid { border-color: #dc2626; background: #fef2f2; }
.modal-dialog .palette {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.modal-dialog .palette button {
    width: 22px;
    height: 22px;
    border-radius: 4px;
    border: 1px solid rgba(0, 0, 0, 0.2);
    padding: 0;
    cursor: pointer;
}
.modal-dialog .palette button.selected {
    outline: 2px solid #18181b;
    outline-offset: 1px;
}
.modal-dialog .modal-meta {
    margin: 0;
    font-size: 11px;
    color: #71717a;
    text-transform: capitalize;
}
.modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 4px;
}
.modal-actions button { font-size: 13px; }
.modal-actions button.primary {
    background: var(--color-primary);
    border-color: var(--color-primary);
}
.modal-actions button.primary:hover {
    background: var(--color-primary-hover);
    border-color: var(--color-primary-hover);
}
/* Explicit danger rule — must beat the generic ":not(.primary)" fallback
   below, which would otherwise paint destructive buttons white. */
.modal-actions button.danger {
    background: var(--color-danger);
    border-color: var(--color-danger);
    color: #fff;
}
.modal-actions button.danger:hover {
    background: var(--color-danger-hover);
    border-color: var(--color-danger-hover);
}
.modal-actions button:not(.primary):not(.danger) {
    background: #fff;
    color: #18181b;
    border-color: #d4d4d8;
}

/* Primary confirm-dialog message — bigger and readable, not the tiny grey
   meta line we use for secondary hints elsewhere. */
.confirm-message {
    margin: 0;
    font-size: 14px;
    line-height: 1.5;
    color: #18181b;
}

/* Shape metrics chips shown in the shape editor (just below the meta line). */
.shape-metrics[hidden] { display: none; }
.shape-metrics {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: -4px;
}
.shape-metric {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    background: #f4f4f5;
    border: 1px solid #e4e4e7;
    border-radius: 999px;
    font-size: 11px;
}
.shape-metric-label {
    color: #71717a;
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.04em;
}
.shape-metric-value {
    color: #18181b;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.shape-metric-value.locked {
    /* Match label metrics so the lock sits on the same baseline as AREA /
       PERIMETER / LENGTH — otherwise an emoji at a bigger font size bumps
       the chip's text off-center. */
    font-size: 11px;
    line-height: 1;
    filter: grayscale(0.3);
}
/* Keep both label and value vertically baseline-aligned in the chip. */
.shape-metric .shape-metric-label,
.shape-metric .shape-metric-value {
    line-height: 1;
}
.shape-metric.locked {
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
}
.shape-metric.locked:hover {
    background: #ede9fe;
    border-color: #c4b5fd;
}
/* Hover-panel lock: slightly larger emoji so it reads on dark bg. */
.shape-hover-value.locked {
    font-size: 13px;
    filter: grayscale(0.2) brightness(1.1);
}

/* --- Shape hover info panel (area / perimeter / length) -------------- */

.shape-hover-info[hidden] { display: none; }
.shape-hover-info {
    position: fixed;
    z-index: 3500;
    padding: 8px 12px;
    background: #18181b;
    color: #fafafa;
    border-radius: 8px;
    box-shadow: 0 8px 22px rgba(0, 0, 0, 0.28);
    font-size: 12px;
    line-height: 1.3;
    pointer-events: none;
    white-space: nowrap;
}
.shape-hover-name {
    font-weight: 700;
    font-size: 12px;
    color: #fafafa;
    padding-bottom: 4px;
    margin-bottom: 4px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}
.shape-hover-name.placeholder {
    font-weight: 400;
    font-style: italic;
    color: #a1a1aa;
}
.shape-hover-name:last-child {
    /* Marker with a name only: no metrics → drop the divider + spacing. */
    padding-bottom: 0;
    margin-bottom: 0;
    border-bottom: 0;
}
.shape-hover-row {
    display: flex;
    justify-content: space-between;
    gap: 14px;
}
.shape-hover-row + .shape-hover-row { margin-top: 2px; }
.shape-hover-label {
    color: #a1a1aa;
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.04em;
    align-self: center;
}
.shape-hover-value {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}
.shape-hover-sep {
    height: 1px;
    background: rgba(255, 255, 255, 0.15);
    margin: 6px 0;
}
/* Attribute rows: keep the user's original key casing (no uppercase / letter
   spacing) so they read naturally against the metric labels above. */
.shape-hover-attr .shape-hover-label {
    text-transform: none;
    letter-spacing: 0;
    font-size: 11px;
    color: #d4d4d8;
}

/* --- Custom context menu -------------------------------------------- */

.context-menu[hidden] { display: none; }
.context-menu {
    position: fixed;
    z-index: 4000;
    list-style: none;
    margin: 0;
    padding: 4px 0;
    min-width: 160px;
    background: #fff;
    border: 1px solid #d4d4d8;
    border-radius: 6px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
    font-size: 13px;
    color: #18181b;
}
.context-menu li {
    padding: 6px 14px;
    cursor: pointer;
    user-select: none;
}
.context-menu li:hover { background: #f4f4f5; }
/* Feature-gated menu item: grey it out and append a lock glyph. Click still
   fires — the action normally routes to the upgrade dialog. Mirrors the
   topbar dropdown's feature-locked styling for UI consistency. */
.context-menu li.feature-locked {
    color: #a1a1aa;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}
.context-menu li.feature-locked::after {
    content: '🔒';
    font-size: 12px;
    opacity: 0.85;
    filter: grayscale(0.2);
}
.context-menu li.feature-locked:hover {
    background: #fef3c7;
    color: #92400e;
}

/* Toast for transient info + error messages. Pinned just below the 48px
   topbar so it sits cleanly inside the map area — doesn't clash with the
   bottom-docked action bars either. */
#toast {
    position: fixed;
    top: 64px;
    left: 50%;
    transform: translate(-50%, -8px);
    background: #18181b;
    color: #fff;
    padding: 10px 16px;
    border-radius: 6px;
    font-size: 13px;
    max-width: min(420px, calc(100vw - 32px));
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
    z-index: 2000;
}
#toast.show {
    opacity: 1;
    transform: translate(-50%, 0);
}
#toast.error {
    background: #dc2626;
    box-shadow: 0 6px 20px rgba(220, 38, 38, 0.35);
}
#toast.warning {
    background: #f59e0b;
    color: #18181b;
    box-shadow: 0 6px 20px rgba(245, 158, 11, 0.35);
}
#toast.success {
    background: #16a34a;
    box-shadow: 0 6px 20px rgba(22, 163, 74, 0.35);
}

/* Spinner used in the import action bar while the backend is waiting on
   Overpass. Sits inline-left of the label, same height as the text. The
   [hidden] rule must win over .spinner's display:inline-block, so it uses
   the same specificity-plus-attribute trick. */
.spinner {
    display: inline-block;
    width: 14px;
    height: 14px;
    border: 2px solid rgba(255, 255, 255, 0.25);
    border-top-color: #fff;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    margin-right: 8px;
    vertical-align: middle;
    flex-shrink: 0;
}
.spinner[hidden] { display: none; }
@keyframes spin { to { transform: rotate(360deg); } }

/* Dimensions dialog: SVG-based technical drawing of the shape with edge
   lengths labelled at midpoints (rotated to match the edge), vertex dots
   numbered V1…Vn, and a summary line below. Scroll-to-zoom + drag-to-pan,
   double-click resets view (see enableSvgZoomPan).
   The content is a flex column so the SVG fills the available modal height
   without introducing a scrollbar (which previously clipped part of the
   SVG and cut the summary in half). */
#dimensionsDialog .modal-dialog {
    resize: both;
    overflow: auto;
    min-width: 420px;
    min-height: 380px;
    max-width: calc(100vw - 32px);
    max-height: calc(100vh - 32px);
}
.dimensions-content {
    display: flex;
    flex-direction: column;
    gap: 8px;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}
.dim-svg {
    flex: 1 1 auto;
    min-height: 320px;
    width: 100%;
    background: #fafafa;
    border: 1px solid #e4e4e7;
    border-radius: 4px;
    display: block;
    overflow: hidden;
    touch-action: none;
}
.dim-vertex-label {
    font-size: 10px;
    font-family: ui-sans-serif, system-ui, sans-serif;
    fill: #71717a;
}
.dim-edge-label {
    font-size: 11px;
    font-family: ui-sans-serif, system-ui, sans-serif;
    fill: #18181b;
    font-weight: 600;
    paint-order: stroke;
    stroke: #fafafa;
    stroke-width: 3px;
    stroke-linejoin: round;
}
.dim-summary {
    margin: 10px 0 0 0;
    text-align: center;
    font-size: 13px;
    color: #3f3f46;
    font-weight: 600;
}
