/* =========================================================
   DESIGN TOKENS
   ========================================================= */

:root {
    /* ── Layout ── */
    --nav-height-mobile:  4.5rem;   /* 72px — bottom nav */
    --nav-height-desktop: 3.5rem;   /* 56px — top nav */
    --max-content-width:  72rem;

    /* ── Spacing scale ── */
    --space-xs: 0.25rem;   /*  4px */
    --space-sm: 0.5rem;    /*  8px */
    --space-md: 1rem;      /* 16px */
    --space-lg: 1.5rem;    /* 24px */
    --space-xl: 2rem;      /* 32px */
    --space-xxl: 4rem;      /* 32px */

    /* ── Border radius ── */
    --border-radius-sm:   4px;
    --border-radius:      8px;
    --border-radius-lg:   12px;
    --border-radius-full: 9999px;

    /* ── Transitions ── */
    --transition-fast: 150ms ease;
    --transition-base: 250ms ease;
    --transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1);
    /* Light↔dark repaint. js/theme.js must outlive this — see TRANSITION_MS. */
    --transition-theme: 320ms ease;

    /* ── Shadows ── */
    /* --shadow / --shadow-soft / --shadow-deep are the palette's, so they
       already follow the theme. These are just the names the components
       grew up with — do NOT redeclare --shadow here or the light values
       would win in dark mode too. */
    --shadow-sm: var(--shadow-soft);
    --shadow-lg: var(--shadow-deep);

    /* Smooth height animations */
    interpolate-size: allow-keywords;
}

/* =========================================================
   MODERN CSS RESET
   Adapted from Josh W. Comeau & Andy Bell
   ========================================================= */

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

html {
    -moz-text-size-adjust: none;
    -webkit-text-size-adjust: none;
    text-size-adjust: none;
    scroll-behavior: smooth;
}

body {
    min-height: 100svh;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    background-color: var(--color-background);
    color: var(--color-on-surface);
}

p, h1, h2, h3, h4, h5, h6 {
    overflow-wrap: break-word;
}

h1, h2, h3, h4, h5, h6 {
    text-wrap: balance;
    line-height: 1.2;
}

p, li {
    text-wrap: pretty;
}

img, picture, video, canvas, svg {
    display: block;
    max-width: 100%;
}

input, button, textarea, select {
    font: inherit;
}

ul, ol {
    list-style: none;
}

button {
    background: none;
    border: none;
    cursor: pointer;
    color: inherit;
}

:focus {
    outline: none;
}

:focus-visible {
    outline: 2px solid var(--color-focus-indicator);
    outline-offset: 3px;
    border-radius: var(--border-radius-sm);
}

::selection {
    background-color: var(--color-selection-bg);
    color: var(--color-selection-fg);
}


/* =========================================================
   TYPOGRAPHY ENHANCEMENTS
   ========================================================= */

h1, h2, h3, h4, h5 {
    margin-bottom: var(--space-md);
    color: var(--color-on-surface);
}

p {
    margin-bottom: var(--space-md);
}

p:last-child {
    margin-bottom: 0;
}

small {
    color: var(--color-outline-variant);
}


/* =========================================================
   LINKS
   ========================================================= */

a {
    color: var(--color-link);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-thickness: 1px;
    transition: color var(--transition-fast);
}

a:hover {
    color: var(--color-link);
    text-decoration-thickness: 2px;
}


/* =========================================================
   SCROLLBAR
   ========================================================= */

::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}

::-webkit-scrollbar-track {
    background: var(--color-surface-dim);
}

::-webkit-scrollbar-thumb {
    background: var(--color-outline-strong);
    border-radius: var(--border-radius-full);
}

::-webkit-scrollbar-thumb:hover {
    background: var(--color-primary);
}


/* =========================================================
   REDUCED MOTION
   ========================================================= */

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}


/* =========================================================
   BLAZOR DEFAULTS
   ========================================================= */

#blazor-error-ui {
    background: var(--color-secondary-container);
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: var(--space-sm) var(--space-md);
    position: fixed;
    width: 100%;
    z-index: 1000;
    color: var(--color-on-secondary-container);
    font-size: 0.875rem;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: var(--space-md);
    top: var(--space-sm);
}

.loading-progress {
    position: relative;
    display: block;
    width: 8rem;
    height: 8rem;
    margin: 20vh auto 1rem;
}

.loading-progress circle {
    fill: none;
    stroke: var(--color-primary);
    stroke-width: 0.6rem;
    transform-origin: 50% 50%;
    transform: rotate(-90deg);
}

.loading-progress circle:last-child {
    stroke: var(--color-surface-dim);
    stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
    transition: stroke-dasharray 0.05s ease-in-out;
}

.loading-progress-text {
    position: absolute;
    text-align: center;
    font-weight: 500;
    inset: calc(20vh + 8rem + 1rem) 0 auto;
    color: var(--color-outline-variant);
}

.loading-progress-text:after {
    content: var(--blazor-load-percentage-text, "Loading");
}


/* =========================================================
   MAP (OPENLAYERS)
   OL builds its DOM at runtime, so none of this can live in a
   scoped .razor.css. ol.css is linked vendor-first in
   index.html so these token hooks win the cascade.
   ========================================================= */

/* OL's control chrome (zoom buttons, attribution) runs entirely on its
   --ol-* variables — pointing them at the theme tokens themes it in both
   modes with no selector overrides. */
:root {
    --ol-background-color: var(--color-surface);
    --ol-accent-background-color: var(--color-surface-dim);
    --ol-subtle-background-color: color-mix(in srgb, var(--color-surface) 25%, transparent);
    --ol-partial-background-color: color-mix(in srgb, var(--color-surface) 75%, transparent);
    --ol-foreground-color: var(--color-on-surface);
    --ol-subtle-foreground-color: var(--color-on-surface-variant);
    --ol-brand-color: var(--color-primary-accent);
}

/* The ground behind still-loading tiles follows the theme. */
.ol-viewport {
    background-color: var(--color-background);
}

/* ── Dark basemap ──
   Night streets are the same OSM rasters (and the same browse cache) as
   light, inverted at display time — no second tile provider, no licence
   question, no cache fork. .iris-basemap is the tile layer's own canvas
   (className in core.js initMap), so markers are untouched. Satellite is
   photography: never inverted, only dimmed against glare (core.js keeps
   .iris-basemap--satellite on the map container). Guarded both ways,
   mirroring colours.css: stamped dark, and system-dark before a stamp. */
:root[data-theme="dark"] .iris-basemap {
    filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(0.9) saturate(0.5);
}

:root[data-theme="dark"] .iris-basemap--satellite .iris-basemap {
    filter: brightness(0.85);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .iris-basemap {
        filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(0.9) saturate(0.5);
    }

    :root:not([data-theme="light"]) .iris-basemap--satellite .iris-basemap {
        filter: brightness(0.85);

/* =========================================================
   THEME SWITCH

   Custom properties are not animatable, so flipping [data-theme] repaints
   every token at once. What CAN animate is the properties that READ them, so
   js/theme.js parks .theme-transition on <html> for the length of the swap
   and this blanket lends a colour transition to everything that has not
   declared one of its own.

   Deliberately no !important: at (0,1,0) these rules lose to every scoped
   component rule, so a component that already tunes its own colour transition
   keeps its timing, and — more importantly — a component transitioning
   `transform` (the sun/moon glyph, the side panel) does not have that
   transition shorthand-reset out from under it mid-swap.

   Deliberately temporary: a standing universal transition would tax every
   hover, every render and every list virtualisation for the sake of one
   button press.
   ========================================================= */

.theme-transition,
.theme-transition *,
.theme-transition *::before,
.theme-transition *::after {
    transition: background-color var(--transition-theme),
                border-color var(--transition-theme),
                outline-color var(--transition-theme),
                color var(--transition-theme),
                fill var(--transition-theme),
                stroke var(--transition-theme),
                box-shadow var(--transition-theme),
                /* The dark basemap is a filter, not a colour (see the map
                   section above). Without this the largest surface on the
                   page snaps while all the chrome around it fades. */
                filter var(--transition-theme);
}

@media (prefers-reduced-motion: reduce) {
    .theme-transition,
    .theme-transition *,
    .theme-transition *::before,
    .theme-transition *::after {
        transition: none;
    }
}
