/* ============================================================================
   OLYMPUS — design tokens.  See crystalTi.md for the reasoning behind every
   value here; this file is the implementation, that file is the argument.

   Light neutral, system-native, restrained.  --brand (indigo) is the action
   colour; --ink is text, not action (rev. 08-22b — black-as-action was what
   made the whole app read as one grey).  --live is cyan and means
   presence/proximity, nothing else (one sanctioned exception: the
   :focus-visible outline), and it is a MARK colour — text takes --live-text.
   Every text and glyph token clears WCAG AA 4.5:1 in both themes.

   :root carries LIGHT.  Dark is the [data-theme="dark"] override — the
   reverse of the pre-2026-08-21 file.  theme.js always writes an explicit
   data-theme on init, so nothing depends on which one lives on :root.
   ============================================================================ */

:root {
    /* colour */
    --ink: #0b0d0e;       /* 19.6:1 on --surface */
    --ink-2: #5e646b;     /* 6.0:1  */
    --ink-3: #676d74;     /* 5.2:1 — raised from #9da3a9; the AA failure is no longer accepted */
    --on-ink: #ffffff;

    /* The primary action colour. Deep low-chroma indigo (~232deg): saturated
       enough to read as "press this", cool enough to stay clear of the
       category hues, and 45deg off --live so the two never read alike. */
    --brand: #2f43b8;     /* 8.0:1 on --surface */
    --brand-soft: #e6e9fb;
    --on-brand: #ffffff;  /* 8.0:1 on --brand */

    --line: #cfd3d8;      /* raised from #e6e8ea so hairlines actually separate rows */
    --fill: #f1f2f4;
    --fill-hover: #e6e8eb;
    --surface: #ffffff;
    --canvas: #f2f3f5;
    --live: #00a9be;      /* 2.8:1 — a MARK colour (dots/rings), never set as text colour */
    --live-soft: #e2f6f9;
    --live-text: #01707e; /* 5.8:1 — what live TEXT takes */
    --danger: #c62d22;    /* 5.5:1 */

    /* Category tier (rev. 08-22b) — a FIRST-CLASS tier, still not part of the neutral system.
       Valid anywhere the category is NAMED and always paired with its glyph or label: the pin
       ring, the filter rail, the "Sharing as" picker, .my-status, pin callouts, feed check-in
       badges. Never a general accent, never state, never presence, never errors.

       Three tokens each: --cat-N is the ring/glyph/rail-fill, --cat-N-soft a chip background,
       --cat-N-text the text and glyph that sit on soft (and on --surface). The light --cat-N
       values are as dark as they are because a filled rail button carries a white glyph.

       Every hue avoids ~187deg by at least 40deg, so none can be mistaken for --live on the map.

       KEYED BY THE LocationCategory ENUM VALUE — a persisted/wire identity — NOT by display
       order (LocationCategories.Ordered is Looking, Driving, Hosting, Meeting, Group, which is
       a different sequence). crystalTi.md's §Categories table numbers its rows by display order
       by mistake; the hue-to-NAME pairing there is what's authoritative, and it is what's below.
       Getting this backwards paints Hosting pins amber and makes the ring contradict the glyph. */
    --cat-1: #1e7a4c;      /* Hosting — 150deg green  */
    --cat-1-soft: #dcf3e6;
    --cat-1-text: #176540;
    --cat-2: #6b3fc9;      /* Driving — 262deg violet */
    --cat-2-soft: #eee7ff;
    --cat-2-text: #5b32b5;
    --cat-3: #c0255b;      /* Meeting — 339deg rose   */
    --cat-3-soft: #fce3ec;
    --cat-3-text: #a81f50;
    --cat-4: #b04a1a;      /* Group   — 19deg terracotta */
    --cat-4-soft: #fbe6db;
    --cat-4-text: #9a3f14;
    --cat-5: #9a6200;      /* Looking — 38deg amber   */
    --cat-5-soft: #fff1d1;
    --cat-5-text: #7a4d00;

    /* elevation — always soft, never coloured */
    --e1: 0 1px 2px rgba(11,13,14,0.06);
    --e2: 0 4px 16px rgba(11,13,14,0.08);
    --e3: 0 -6px 28px rgba(11,13,14,0.10);

    /* Modal/lightbox backdrop. Deliberately NOT derived from --ink: a scrim has to darken
       what's behind it in both themes, and --ink inverts to near-white in dark. Consumers:
       ReconnectModal's ::backdrop, ChatThreadView's lightbox. */
    --scrim: rgba(11,13,14,0.45);

    /* basemap. explore-map.js's THEME_PALETTES is a hand-kept mirror of these —
       update both together. Roads are pure white and separate by stroke width,
       not tone; that flat printed look is deliberate. */
    --map-bg: #e9ebed;
    --map-landcover: #e2eae1;
    --map-landuse: #eceef0;
    --map-water: #d5e3ea;
    --map-water-line: #b9cdd6;
    --map-road: #ffffff;
    --map-road-major: #ffffff;
    --map-building: #e2e4e7;
    --map-boundary: rgba(11,13,14,0.14);
    --map-label: #5e646b;
    --map-label-halo: #ffffff;

    /* ---- theme-independent below this line ---- */

    /* Identity fills. There is no avatar image anywhere in this app —
       OlympusUser has no such column and MapPinDto.AvatarUrl is always null —
       so initials ARE the avatar. Pick one per user by a stable hash of their
       id so the same person is the same colour everywhere. Identical in both
       themes on purpose: these are mid-tones that clear white and near-black
       alike, and a person's colour changing with the theme would undo the
       point of hashing it. */
    --ava-1: #3c4a57;
    --ava-2: #7a6250;
    --ava-3: #4e6357;
    --ava-4: #5b5470;
    --ava-5: #8a5b5b;
    --ava-6: #41606e;
    --on-ava: #ffffff;

    /* type. Inter FIRST but never fetched — devices that have it render the
       reference's exact face at zero network cost, everything else paints
       immediately in the platform face. See perf rule 1. The product UI has
       no monospace surface, so there is no --mono. */
    --ui: 'Inter', 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif;

    /* space — 4pt base */
    --s1: 4px;
    --s2: 8px;
    --s3: 12px;
    --s4: 16px;
    --s5: 20px;
    --s6: 24px;
    --s8: 32px;

    /* radius */
    --r-ctl: 10px;
    --r-card: 14px;
    --r-sheet: 20px;
    --r-pill: 999px;
}

html[data-theme="dark"] {
    /* The ramp flips: --ink stays "maximum contrast against the canvas" and
       --on-ink stays "the tone that sits on it", so one set of component
       rules serves both themes. Primary buttons and outgoing bubbles are
       near-white here, not near-black. */
    --ink: #f4f6f7;       /* 17.3:1 on --surface */
    --ink-2: #a9afb5;     /* 8.0:1  */
    --ink-3: #9aa0a6;     /* 6.7:1  */
    --on-ink: #0b0d0e;

    /* The brand lightens to a periwinkle and its on-colour flips to ink, so a
       light-theme button is white-on-indigo and a dark one is ink-on-periwinkle
       — the same invariant --ink/--on-ink carries. */
    --brand: #9aa6ff;     /* 7.9:1 on --surface */
    --brand-soft: #1b2448;
    --on-brand: #0b0d0e;  /* 8.6:1 on --brand */

    --line: #3a4045;
    --fill: #1e2225;
    --fill-hover: #262a2e;
    --surface: #16181b;
    --canvas: #0b0d0e;
    --live: #2cc7da;      /* same hue (~187deg), raised luminance to clear near-black */
    --live-soft: #0e2a2f;
    --live-text: #7fe0ec;
    --danger: #ff6b5e;

    /* Same hues, lifted for a near-black canvas. Pastel enough that a filled
       rail button carries an --on-ink (near-black) glyph, not a white one. */
    --cat-1: #4bcb8a;      /* Hosting */
    --cat-1-soft: #0e2e1c;
    --cat-1-text: #7fddaa;
    --cat-2: #b79cff;      /* Driving */
    --cat-2-soft: #2a1c4a;
    --cat-2-text: #c7b3ff;
    --cat-3: #ff7fa6;      /* Meeting */
    --cat-3-soft: #3b0e1e;
    --cat-3-text: #ff9dbb;
    --cat-4: #ff9a6a;      /* Group   */
    --cat-4-soft: #3a1a0c;
    --cat-4-text: #ffb08a;
    --cat-5: #f0b53d;      /* Looking */
    --cat-5-soft: #3a2a0a;
    --cat-5-text: #f5c96a;

    /* Shadows barely read against near-black, so hairlines do proportionally
       more of the separating work here. That's expected — don't compensate
       with heavier shadows. */
    --e1: 0 1px 2px rgba(0,0,0,0.4);
    --e2: 0 4px 16px rgba(0,0,0,0.45);
    --e3: 0 -6px 28px rgba(0,0,0,0.5);

    --scrim: rgba(0,0,0,0.65);

    --map-bg: #0b0d0e;
    --map-landcover: #101315;
    --map-landuse: #121517;
    --map-water: #131a1e;
    --map-water-line: #24333a;
    --map-road: #24282b;
    --map-road-major: #2e3337;
    --map-building: #16191c;
    --map-boundary: rgba(244,246,247,0.16);
    --map-label: #a9afb5;
    --map-label-halo: #0b0d0e;
}

/* ============================================================================
   BASE
   ============================================================================ */

html, body {
    font-family: var(--ui);
    font-size: 15px;
    line-height: 1.5;
    background: var(--canvas);
    color: var(--ink);
    -webkit-font-smoothing: antialiased;
    transition: background-color 0.25s ease, color 0.25s ease;
}

/* rev. 08-22b: links are an action, so they take --brand. */
a, .btn-link {
    color: var(--brand);
}

/* The one sanctioned use of --live outside presence. :focus-visible only, so
   it never appears on a touch or mouse press — a pointer user on the map will
   not see a cyan ring and cannot confuse it with a presence marker. Do not
   extend to :hover, :active, or any resting outline. */
:focus-visible {
    outline: 2px solid var(--live);
    outline-offset: 2px;
    border-radius: 4px;
}

/* Bootstrap loads before this file and puts a box-shadow ring on focused
   controls; the outline above is the app's focus affordance instead. */
.btn:focus, .btn:focus-visible,
.form-control:focus, .form-check-input:focus {
    box-shadow: none;
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        transition: none !important;
        animation: none !important;
    }
}

/* ============================================================================
   TYPE SCALE

   Utility classes rather than custom properties: letter-spacing can't ride in
   a `font` shorthand, so a token would have to be split three ways and named
   six times over. Six discrete steps, applied in markup. Sentence case
   everywhere except .t-label, the only uppercase in the app.
   ============================================================================ */

.t-title   { font-size: 28px; font-weight: 600; letter-spacing: -0.022em; }
.t-section { font-size: 20px; font-weight: 600; letter-spacing: -0.018em; }
.t-row     { font-size: 16px; font-weight: 600; letter-spacing: -0.012em; }
.t-body    { font-size: 15px; font-weight: 400; }
.t-caption { font-size: 13px; font-weight: 400; color: var(--ink-2); }

.t-label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-3);
}

/* ============================================================================
   BUTTONS — one primary per screen, maximum.
   ============================================================================ */

.btn {
    height: 48px;
    padding: 0 var(--s5);
    border-radius: var(--r-ctl);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s2);
    font-size: 16px;
    font-weight: 600;
    letter-spacing: 0;
    border: 1px solid transparent;
    cursor: pointer;
    transition: background-color 0.18s ease, color 0.18s ease, border-color 0.18s ease;
}

/* rev. 08-22b: was --ink. --brand is the action colour now; --ink is text. */
.btn-primary {
    background: var(--brand);
    color: var(--on-brand);
    border-color: var(--brand);
}

/* Hover darkens/lightens the brand rather than sliding to a neutral, which is
   what the old --ink-2 hover did. color-mix keeps one source of truth: there is
   no --brand-hover token to drift out of step with --brand. */
.btn-primary:hover {
    background: color-mix(in srgb, var(--brand) 86%, var(--ink));
    border-color: color-mix(in srgb, var(--brand) 86%, var(--ink));
}

.btn-primary:active { background: var(--brand); border-color: var(--brand); }

.btn-secondary {
    background: var(--fill);
    color: var(--ink);
    border-color: var(--fill);
}

.btn-secondary:hover { background: var(--fill-hover); border-color: var(--fill-hover); }

.btn-ghost {
    background: var(--surface);
    color: var(--ink);
    border-color: var(--line);
}

.btn-ghost:hover { background: var(--fill); }

/* In-row only — the "Ping" affordance. Never a page action. */
.btn-sm {
    height: 34px;
    padding: 0 var(--s3);
    border-radius: var(--r-pill);
    font-size: 14px;
}

.btn:disabled, .btn[disabled] {
    opacity: 0.45;
    cursor: default;
}

/* Icon buttons. 44px is the HIG minimum tap target (doc rule 9), up from the
   36px the metallic system used. */
.icon-btn {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--r-ctl);
    background: var(--fill);
    border: 1px solid transparent;
    color: var(--ink-2);
    cursor: pointer;
    /* Also used on <a> (Profile's settings gear), where the default underline would show. */
    text-decoration: none;
    transition: background-color 0.18s ease, color 0.18s ease;
}

.icon-btn:hover {
    background: var(--fill-hover);
    color: var(--ink);
}

/* ============================================================================
   CHIPS
   ============================================================================ */

.chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 32px;
    padding: 0 var(--s3);
    border-radius: var(--r-pill);
    background: var(--fill);
    color: var(--ink);
    font-size: 13px;
    font-weight: 500;
    border: 0;
    cursor: pointer;
}

/* rev. 08-22b: was --ink. */
.chip.on {
    background: var(--brand);
    color: var(--on-brand);
}

/* Presence/proximity labels only — "Live · 140 m", "Open to meet". */
.chip.live {
    background: var(--live-soft);
    color: var(--live-text);
}

/* Category labels only, and only where the category is NAMED beside the colour
   (§Categories): the "Sharing as" picker, .my-status, pin callouts, feed
   check-in badges. The glyph inherits currentColor, so it takes --cat-N-text
   with the label rather than the louder --cat-N. Numbered by LocationCategory
   ENUM value — see the token block for why that is not display order. */
.chip.cat-1 { background: var(--cat-1-soft); color: var(--cat-1-text); }  /* Hosting */
.chip.cat-2 { background: var(--cat-2-soft); color: var(--cat-2-text); }  /* Driving */
.chip.cat-3 { background: var(--cat-3-soft); color: var(--cat-3-text); }  /* Meeting */
.chip.cat-4 { background: var(--cat-4-soft); color: var(--cat-4-text); }  /* Group   */
.chip.cat-5 { background: var(--cat-5-soft); color: var(--cat-5-text); }  /* Looking */

/* A bare numeric pill, deliberately NOT .chip: it has to stay circular and
   legible at two digits inside a 44px nav icon. The reference bans red/orange
   unread badges; an ink count is compatible, and a bare ink dot is the right
   form wherever the number isn't needed. */
.count-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--r-pill);
    background: var(--ink);
    color: var(--on-ink);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0;
}

/* ============================================================================
   SURFACES
   ============================================================================ */

/* The standard panel surface. Was two radial shines over a linear gradient
   with an inset top edge; that material is exactly what the reference's
   never-list rules out. The class keeps its name — redefined in place rather
   than renamed to .panel — so this stayed a stylesheet edit and didn't touch
   the four consumers' markup. The name is now a historical label for "the
   standard panel surface", not a description of a texture. Don't read it as
   licence to reintroduce shine. */
.crystal-panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-card);
}

/* The same surface as a padded content block, plus the label/row primitives that go inside it.
   Promoted out of Profile.razor.css when Settings became the second consumer — rule 3, the same
   call that promoted .crystal-panel and the auth-form set. Use .crystal-panel when you want the
   bare surface and will supply your own spacing (the Explore placeholders do); use .panel when
   you want the standard padded section. */
.panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-card);
    padding: 26px var(--s8);
    margin-bottom: var(--s5);
}

.panel-title {
    font-size: 15px;
    font-weight: 600;
    margin: 0 0 18px;
    color: var(--ink);
    display: flex;
    align-items: center;
    gap: var(--s2);
}

.panel-title::before {
    content: "";
    width: 3px;
    height: 16px;
    border-radius: 2px;
    background: var(--ink);
}

.field-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--s4);
    padding: var(--s3) 0;
    border-bottom: 1px solid var(--line);
    font-size: 14px;
}

.field-row:last-child {
    border-bottom: none;
}

/* Scoped under .panel so it can't collide with .auth-card's own .field-label / .field-error
   set, which is a different control at a different size. */
.panel .field-label {
    color: var(--ink-2);
}

.panel .field-value {
    color: var(--ink);
    font-weight: 500;
}

/* ============================================================================
   AVATARS

   .chat-avatar keeps its name for the same reason .crystal-panel does: the
   consumers' markup stays put. Note Profile.razor.css already scopes its own
   .avatar for the 92px hero disc, so `.avatar` is not available as a global.

   The --a1..--a6 modifiers are defined here ahead of the markup that will
   emit them; until a display-name hash is wired up, every avatar renders
   --ava-1.
   ============================================================================ */

.chat-avatar {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--ava-1);
    color: var(--on-ava);
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -0.01em;
    user-select: none;
}

.chat-avatar--56 { width: 56px; height: 56px; font-size: 19px; }
.chat-avatar--40 { width: 40px; height: 40px; font-size: 14px; }
.chat-avatar--32,
.chat-avatar--sm { width: 32px; height: 32px; font-size: 12px; }

.chat-avatar--a1 { background: var(--ava-1); }
.chat-avatar--a2 { background: var(--ava-2); }
.chat-avatar--a3 { background: var(--ava-3); }
.chat-avatar--a4 { background: var(--ava-4); }
.chat-avatar--a5 { background: var(--ava-5); }
.chat-avatar--a6 { background: var(--ava-6); }

/* A real photo (OlympusUser.AvatarObjectKey) draws over the initials fill rather than replacing
   the element: markup renders either the <img> or the initials text, never both, so the fill
   colour only shows through where there is no photo — border-radius is still needed here because
   the image is a rectangle until clipped. */
.chat-avatar img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
}

/* Presence ring — 13px --live disc, 2.5px surface border, bottom-right. */
.chat-avatar--live::after {
    content: "";
    position: absolute;
    right: -1px;
    bottom: -1px;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    background: var(--live);
    border: 2.5px solid var(--surface);
}

/* Standalone presence dot, for rows that don't hang it off an avatar.
   --live, not a category token: cyan means presence at every call site now. */
.presence-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--ink-3);
    border: 2px solid var(--surface);
}

.presence-dot--online {
    background: var(--live);
}

/* ============================================================================
   THEME TOGGLE
   ============================================================================ */

.theme-toggle {
    display: flex;
    align-items: center;
    gap: var(--s2);
    cursor: pointer;
    user-select: none;
    background: none;
    border: none;
    padding: 0;
}

.theme-toggle-track {
    position: relative;
    width: 44px;
    height: 24px;
    border-radius: var(--r-pill);
    background: var(--fill);
    border: 1px solid var(--line);
    transition: background-color 0.18s ease;
}

.theme-toggle-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--surface);
    box-shadow: var(--e1);
    transition: left 0.18s ease;
}

.theme-toggle.is-light .theme-toggle-track { background: var(--ink); border-color: var(--ink); }
.theme-toggle.is-light .theme-toggle-thumb { left: 22px; }

.theme-toggle-label {
    font-size: 12px;
    color: var(--ink-2);
    min-width: 34px;
    text-align: left;
}

/* ============================================================================
   AUTH FORMS — shared by Register and Login. Promoted out of Register.razor.css
   once a second page needed the identical card: two scoped copies of the same
   100 lines is exactly what rule 3 exists to prevent. Any third auth-shaped
   page (password reset, email confirmation) reuses these.
   ============================================================================ */

.auth-page {
    min-height: calc(100vh - 3.5rem);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--s8) var(--s5) 3rem;
}

.auth-card {
    width: 100%;
    max-width: 400px;
    padding: var(--s8) var(--s6);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-card);
    box-shadow: var(--e1);
}

.auth-title {
    font-size: 22px;
    font-weight: 600;
    letter-spacing: -0.018em;
    margin: 0 0 6px;
    color: var(--ink);
}

.auth-subtitle {
    font-size: 14px;
    line-height: 1.6;
    color: var(--ink-2);
    margin: 0 0 var(--s6);
}

.auth-form {
    display: flex;
    flex-direction: column;
    gap: var(--s4);
}

.auth-form .field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.auth-card .field-label {
    font-size: 13px;
    font-weight: 500;
    color: var(--ink-2);
}

.auth-card .field-hint {
    font-size: 12px;
    color: var(--ink-3);
}

/* Error text sits BELOW the field; the field itself only takes a border. */
.auth-card .field-error {
    font-size: 12.5px;
    color: var(--danger);
}

.auth-card .input {
    height: 40px;
    padding: 0 var(--s3);
    background: var(--fill);
    color: var(--ink);
    border: 1.5px solid transparent;
    border-radius: var(--r-ctl);
    font-size: 15px;
    font-family: inherit;
}

.auth-card .input::placeholder {
    color: var(--ink-3);
}

.auth-card .input:focus {
    background: var(--surface);
    border-color: var(--ink);
}

.auth-card .input.invalid {
    background: var(--surface);
    border-color: var(--danger);
}

.auth-submit {
    margin-top: var(--s2);
    width: 100%;
}

/* Cross-link between Register and Login. */
.auth-alt {
    margin: 18px 0 0;
    font-size: 13px;
    text-align: center;
    color: var(--ink-2);
}

.auth-alt a {
    color: var(--brand);
    font-weight: 500;
    text-decoration: none;
}

.auth-alt a:hover {
    text-decoration: underline;
}

.auth-success {
    text-align: center;
}

.auth-success__icon {
    width: 48px;
    height: 48px;
    margin: 0 auto var(--s4);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    background: var(--ink);
    color: var(--on-ink);
}

/* ============================================================================
   LAYOUT / THIRD-PARTY OVERRIDES
   ============================================================================ */

.content {
    height: 100%;
    padding-top: 1.1rem;
}

/* MapLibre's own attribution control is appended straight into the map
   container by JS, not rendered by any .razor component, so Blazor's CSS
   isolation can't reach it — it has to be overridden here, globally. Nudge it
   clear of the app-nav (4.5rem bottom bar on mobile, 240px left sidebar on
   desktop) the same way the custom map controls in MapView.razor.css are. */
.maplibregl-ctrl-bottom-right {
    bottom: 4.5rem !important;
}

/* MapLibre's own stylesheet sets `.maplibregl-ctrl { pointer-events: auto }`, which overrides the
   inherited `pointer-events: none` PersistentMapHost uses to disable the hidden map — leaving the
   attribution control clickable on top of every other route. (Its sibling escape,
   `.maplibregl-compact-show { visibility: visible }`, is handled by the non-inherited `opacity: 0`
   over there; this one has no such trick available, so it needs an explicit override.)
   It lives here rather than in PersistentMapHost.razor.css because MapLibre injects these
   elements via JS and CSS isolation can never reach them. */
.map-host:not(.map-host--active) .maplibregl-ctrl,
.map-host:not(.map-host--active) .maplibregl-ctrl-attrib {
    pointer-events: none !important;
}

@media (min-width: 768px) {
    .maplibregl-ctrl-bottom-right {
        bottom: 0 !important;
    }
}

h1:focus {
    outline: none;
}

/* Blazor validation. The framework's default green "valid" outline is dropped
   deliberately: the reference's input spec has no success state, and a field
   turning green the moment it's touched is noise. Invalid keeps a border only,
   with the message below — same shape as .auth-card .field-error. */
.invalid {
    outline: 1px solid var(--danger);
}

.validation-message {
    color: var(--danger);
    font-size: 12.5px;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, var(--danger);
    padding: 1rem 1rem 1rem 3.7rem;
    color: #fff;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: var(--line);
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--ink-3);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}
