/* ============================================================
   solver-map.css  --  Map infrastructure styles for SolverTech.MapLibre.Blazor
   Extracted from timepoint-analysis app.css.
   ============================================================ */

/* Custom properties are all --solver-map-* prefixed so that linking this
   stylesheet can never clobber a consumer's same-named variables. They stay on
   :root rather than on .solver-map-wrapper because some library-owned elements
   live outside the wrapper: .valhalla-minimap is appended to document.body by
   solver-map.js, so a wrapper-scoped variable would not reach it.
   This stylesheet deliberately sets NO html/body rules -- page font, margin and
   height are the host app's responsibility. */

/* HOST-EMITTED vs LIBRARY-EMITTED SELECTORS -- read before adding a rule.
   `.solver-map-wrapper` and `.solver-map-container` are rendered by Map.razor, i.e. by ONE host.
   A WebView2 bootstrap (webview-host/map.html) or a hand-rolled page emits neither. So a visual
   rule keyed only on those classes silently does nothing outside Blazor -- that is the shape of
   every bug in this family so far (attribution offset, layers panel, zoom readout, font).
   Rule of thumb: a visual declaration must hang off an element solver-map.js itself creates
   (.solver-map-controls, .solver-map-zoom-row, .solver-map-*-btn, .valhalla-*, .vt-*) or off a
   `.maplibregl-map:has(> .solver-map-…)` twin. Keep the `.solver-map-wrapper` selectors only as
   back-compat alternatives alongside such a twin, or for genuinely host-sizing concerns. */
:root {
    /* The library's own typography, in one place. Every library-emitted root repeats it rather
       than relying on inheritance from a host wrapper, so a literal stack would be duplicated
       across a dozen rules. */
    --solver-map-font: system-ui, -apple-system, sans-serif;
    --solver-map-z-panel: 1000;
    --solver-map-z-panel-above: 1001;
    --solver-map-z-overlay: 2000;
    --solver-map-z-minimap: 3000;
    --solver-map-color-dark-text: #1e293b;
    --solver-map-color-muted-text: #475569;
    --solver-map-color-label: #64748b;
    --solver-map-color-faint: #94a3b8;
    --solver-map-color-surface: rgba(255, 255, 255, 0.95);
    --solver-map-color-border: #e2e8f0;
    --solver-map-color-border-strong: #cbd5e1;
    --solver-map-panel-radius: 8px;
    --solver-map-panel-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

/* ---- Map wrapper ---- */

/* Fills whatever the host element is, instead of assuming it owns the viewport,
   so several maps can sit side by side or next to a sidebar. The host must give
   the parent a resolved height (e.g. height:100vh, or a flex/grid track);
   min-height is only a safety net so a map dropped into an auto-height parent
   still renders something visible instead of collapsing to 0px.
   font-family is declared here rather than on html/body so the library's own
   controls keep their designed typography without restyling the host. Everything
   inside this element is library-owned (Map.razor renders ChildContent OUTSIDE
   the wrapper), and MapLibre popups mount into map.getContainer(), which is the
   .solver-map-container child -- so the locate popup inherits it too.
   Kept for back-compat: since 1.2 the font and the sizing safety net are ALSO declared on the
   library-emitted roots below, so they hold in a host that never renders this element. */
.solver-map-wrapper {
    position: relative;
    width: 100%;
    height: 100%;
    min-height: 300px;
    overflow: hidden;
    font-family: var(--solver-map-font);
}

/* ---- Map container ---- */

.solver-map-container {
    width: 100%;
    height: 100%;
}

/* The library font, for the map subtree MapLibre owns rather than solver-map.js: the locate
   popup's chrome, the compact attribution, MapLibre's own controls. maplibre-gl.css declares
   `font: 12px/20px Helvetica Neue, Arial, …` on .maplibregl-map, which beats any inheritance from
   a host wrapper, so the only way to reach those is a rule on the map element itself. Keyed off
   the two roots solver-map.js appends into map.getContainer() -- the same `:has()` twin technique
   the attribution offset uses -- so it holds in every host by construction and stays inert for a
   plain MapLibre map that merely links this stylesheet.

   Sizing safety net (was .solver-map-wrapper-only until 1.2): min-height keeps a map dropped into
   an auto-height container from collapsing to 0px, and overflow:hidden clips the top-left/top-right
   styles flyout to the map instead of letting it spill onto the host page. Blazor is unaffected --
   .solver-map-wrapper already imposes both and .solver-map-container is height:100% inside it. */
.maplibregl-map:has(> .solver-map-controls),
.maplibregl-map:has(> .solver-map-zoom-row) {
    font-family: var(--solver-map-font);
    min-height: 300px;
    overflow: hidden;
}

/* ---- Zoom row (buttons + live zoom readout) ---- */

/* One vertical column, top-to-bottom `+`, the readout, `−`, anchored bottom-right directly above the
   compact ⓘ attribution and directly below the layers control button, so the whole bottom-right
   corner reads as ONE continuous stack: ⓘ, −, readout, +, layers. solver-map.js appends it straight
   into map.getContainer(), so this rule is keyed off nothing the host renders and holds in every
   host — Blazor wrapper, WebView2 bootstrap or a hand-rolled page — by construction.

   right:10px lines its right edge up with the layers button's edge (both at right:10px); bottom:40px
   = the ⓘ's 10px margin + its 24px collapsed height + a 6px gap, matching the gap the controls stack
   uses internally. The column itself is exactly as wide as one icon button (32px, see
   .solver-map-icon-btn) so every element in the stack shares one width. pointer-events are off on
   the column and back on for its children, so dragging the map across the readout still pans instead
   of dead-zoning a strip above the attribution. */
.solver-map-zoom-row {
    position: absolute;
    bottom: 40px;
    right: 10px;
    width: 32px;
    font-family: var(--solver-map-font);
    z-index: var(--solver-map-z-panel);
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    pointer-events: none;
}

.solver-map-zoom-row .solver-map-icon-btn {
    pointer-events: auto;
    flex-shrink: 0;
    font-size: 18px;
    font-weight: 600;
    line-height: 1;
    color: var(--solver-map-color-dark-text);
}

/* Same box as the icon buttons above/below it (32x32, see .solver-map-icon-btn) so the column has
   one uniform width and the readout looks like a third button in the stack rather than a wider bar
   wedged between two narrower ones. Tabular figures keep the digits from jittering horizontally as
   the value changes (9.0 -> 14.5); the font is a step down from the row's default 13px and the text
   drops its space ('Z10.0' not 'Z 10.0') because at 32px wide there is no room for either. */
.solver-map-zoom-value {
    box-sizing: border-box;
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    color: var(--solver-map-color-dark-text);
    background: rgba(255, 255, 255, 0.92);
    border-radius: 4px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}

/* ---- Layer controls (anchored to one of four corners) ---- */

.solver-map-controls {
    position: absolute;
    font-family: var(--solver-map-font);
    z-index: var(--solver-map-z-panel);
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* Corner anchors — default is bottom-right to match the library's original behavior. */
.solver-map-controls--bottom-right {
    bottom: 36px;
    right: 10px;
    align-items: flex-end;
}

.solver-map-controls--bottom-left {
    bottom: 36px;
    left: 10px;
    align-items: flex-start;
}

/* The bottom-right stack shares its column with the zoom row, so it has to start above it whenever
   one is present: 40px (the row's own offset) + 108px (the column's own height — three 32px
   elements plus two 6px gaps between them) + 6px gap = 154px, the same gap the stack uses
   everywhere else, so the layers button sits directly on top of the `+` button. Keyed off the row
   element solver-map.js appends into the map container, so a host that opts the row out with
   config.navigationControl === false keeps the tighter 36px offset with no extra config. */
.maplibregl-map:has(> .solver-map-zoom-row) > .solver-map-controls--bottom-right {
    bottom: 154px;
}

.solver-map-controls--top-right {
    top: 10px;
    right: 10px;
    align-items: flex-end;
}

.solver-map-controls--top-left {
    top: 10px;
    left: 10px;
    align-items: flex-start;
}

.solver-map-layers-wrap {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.solver-map-controls--bottom-right .solver-map-layers-wrap,
.solver-map-controls--top-right .solver-map-layers-wrap {
    align-items: flex-end;
}

.solver-map-controls--bottom-left .solver-map-layers-wrap,
.solver-map-controls--top-left .solver-map-layers-wrap {
    align-items: flex-start;
}

/* Expanded panel: vertical anchor flips with the corner so it always grows away from the map edge. */
.solver-map-layers-panel {
    position: absolute;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.solver-map-controls--bottom-right .solver-map-layers-panel {
    bottom: calc(100% + 6px);
    right: 0;
}

.solver-map-controls--bottom-left .solver-map-layers-panel {
    bottom: calc(100% + 6px);
    left: 0;
}

.solver-map-controls--top-right .solver-map-layers-panel {
    top: calc(100% + 6px);
    right: 0;
}

.solver-map-controls--top-left .solver-map-layers-panel {
    top: calc(100% + 6px);
    left: 0;
}

/* font-family is explicit, not inherited: a <button> does not inherit font-family from its
   ancestors, it takes the UA form-control default (Arial), so without this the icon fallback text
   rendered in Arial next to system-ui labels in EVERY host, wrapper or not. */
.solver-map-icon-btn {
    width: 32px;
    height: 32px;
    padding: 4px;
    font-family: var(--solver-map-font);
    background: #fff;
    border: 1px solid rgba(0, 0, 0, 0.08);
    border-radius: 4px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    outline: none;
    transition: background 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}

.solver-map-icon-btn:hover {
    background: #f5f7fa;
}

.solver-map-icon-btn:focus:focus-visible {
    box-shadow: 0 0 2px 2px #0096ff;
}

.solver-map-icon-btn--active {
    background: #e7f1ff;
    border-color: #0096ff;
    box-shadow: 0 0 2px 2px #0096ff;
}

.solver-map-icon-btn--active img,
.solver-map-icon-btn--active svg {
    filter: drop-shadow(0 0 1px rgba(0, 150, 255, 0.8));
}

/* Constrain control icons to the button box. The icons are rendered as
   <img src="*.svg"> and the source SVGs carry a viewBox but no intrinsic
   width/height. Safari (WebKit) then sizes the <img> to the viewBox
   dimensions (e.g. layers.svg -> 122.88x111.96px), overflowing the 32px
   button; Chrome/Firefox clamp it. width/height + object-fit pin the icon
   in every engine, so consumers no longer need a per-app workaround. */
.solver-map-icon-btn img,
.solver-map-icon-btn svg {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* Text fallback inside a custom layer button that has no icon (MapLayerDescriptor.Text, or the
   first letter of its title). It has to fill the same box the icons do, otherwise a one-character
   label renders at whatever size the host page's font stack happens to give it - the class was
   emitted by the old Blazor panel too but never had a rule of its own. */
.solver-map-icon-btn-fallback {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: 600;
    line-height: 1;
    color: var(--solver-map-color-dark-text);
    text-transform: uppercase;
    overflow: hidden;
}

/* ---- MapLibre compact attribution button ---- */

/* solver-map.js adds the AttributionControl bottom-right, right below the layers stack.
   MapLibre's collapsed compact button is 24px wide with a 10px margin while the layers
   button above it is 32px wide at right:10px, so the ⓘ sat 4px off the layers button's
   axis. 14px = 10px + (32 - 24) / 2 puts both centers on the same vertical line. The
   margin is kept when the attribution is expanded so the box doesn't jump on toggle;
   the bottom-left mirror covers a consumer that re-adds the control on the left.

   The rule must not hang off .solver-map-wrapper alone: that class is emitted by the Blazor
   package's Map.razor, so a host that mounts solver-map.js on its own page - the WebView2
   bootstrap in webview-host/map.html, or any native host doing the same - never matched it and
   its ⓘ kept MapLibre's default 10px margin, landing 4px right of the layers button's axis
   (measured: layers centre 1238, ⓘ centre 1242). The alignment is a property of the map that
   owns the control, not of the page around it, so it is also keyed off the control root that
   solver-map.js appends into map.getContainer() - present in every host, by construction.
   The .solver-map-wrapper rules stay for the Blazor case with no layers control, where they
   have applied since 1.2 and there is no button to align against either way. */
.solver-map-wrapper .maplibregl-ctrl-bottom-right .maplibregl-ctrl-attrib.maplibregl-compact,
.maplibregl-map:has(> .solver-map-controls--bottom-right) .maplibregl-ctrl-bottom-right .maplibregl-ctrl-attrib.maplibregl-compact,
.maplibregl-map:has(> .solver-map-zoom-row) .maplibregl-ctrl-bottom-right .maplibregl-ctrl-attrib.maplibregl-compact {
    margin: 0 14px 10px 0;
}

.solver-map-wrapper .maplibregl-ctrl-bottom-left .maplibregl-ctrl-attrib.maplibregl-compact,
.maplibregl-map:has(> .solver-map-controls--bottom-left) .maplibregl-ctrl-bottom-left .maplibregl-ctrl-attrib.maplibregl-compact {
    margin: 0 0 10px 14px;
}

/* ---- Style picker flyout (popup card anchored to the style button) ---- */

.solver-map-style-wrap {
    position: relative;
    display: flex;
}

.solver-map-styles-flyout {
    position: absolute;
    top: 0;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 2px;
    padding: 4px 6px;
    background: var(--solver-map-color-surface);
    border: 1px solid var(--solver-map-color-border);
    border-radius: var(--solver-map-panel-radius);
    box-shadow: var(--solver-map-panel-shadow);
    backdrop-filter: blur(4px);
    animation: solver-map-flyout-in 0.12s ease-out;
    white-space: nowrap;
}

/* On right-anchored corners the flyout opens to the left of the button. */
.solver-map-controls--bottom-right .solver-map-styles-flyout,
.solver-map-controls--top-right .solver-map-styles-flyout {
    right: calc(100% + 6px);
}

/* On left-anchored corners it opens to the right so it stays on-screen. */
.solver-map-controls--bottom-left .solver-map-styles-flyout,
.solver-map-controls--top-left .solver-map-styles-flyout {
    left: calc(100% + 6px);
}

@keyframes solver-map-flyout-in {
    from { opacity: 0; transform: translateX(4px); }
    to { opacity: 1; transform: translateX(0); }
}

.solver-map-controls--bottom-left .solver-map-styles-flyout,
.solver-map-controls--top-left .solver-map-styles-flyout {
    animation-name: solver-map-flyout-in-left;
}

@keyframes solver-map-flyout-in-left {
    from { opacity: 0; transform: translateX(-4px); }
    to { opacity: 1; transform: translateX(0); }
}

/* font-family for the same reason as .solver-map-icon-btn: <button> does not inherit it. */
.solver-map-style-btn {
    padding: 4px 8px;
    font-family: var(--solver-map-font);
    background: transparent;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 12px;
    font-weight: 600;
    color: var(--solver-map-color-dark-text);
    transition: text-shadow 0.15s ease, color 0.15s ease;
    white-space: nowrap;
    line-height: 1;
}

.solver-map-style-btn:hover {
    text-shadow: 0 0 4px rgba(0, 150, 255, 0.9), 0 0 1px rgba(0, 150, 255, 0.9);
}

.solver-map-style-btn--active {
    color: #0066b3;
    text-shadow: 0 0 4px rgba(0, 150, 255, 0.9), 0 0 1px rgba(0, 150, 255, 0.9);
}

.solver-map-style-btn:focus-visible {
    outline: none;
    text-shadow: 0 0 4px rgba(0, 150, 255, 0.9), 0 0 1px rgba(0, 150, 255, 0.9);
}

/* ---- Toggle ---- */

.solver-map-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--solver-map-color-muted-text);
    cursor: pointer;
    padding: 1px 0;
}

/* ---- Color dot ---- */

.solver-map-color {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    flex-shrink: 0;
}

/* ---- Divider ---- */

.solver-map-divider {
    border-top: 1px solid var(--solver-map-color-border);
    margin: 4px 0;
}

/* ---- Subtitle ---- */

.solver-map-subtitle {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--solver-map-color-faint);
    margin: 4px 0 4px 0;
}

/* ---- Valhalla split popup ---- */

/* The popup root solver-map.js sets as the popup HTML. It re-declares the font for the same reason
   .vt-tree and .valhalla-minimap do: the popup mounts inside .maplibregl-map, whose vendored
   `font:` shorthand wins over any inheritance, and .valhalla-split-hdr sits outside .vt-tree -- so
   without this the two columns of one popup rendered in two different fonts. */
.valhalla-split {
    display: flex;
    gap: 0;
    font-family: var(--solver-map-font);
    font-size: 12px;
    color: var(--solver-map-color-dark-text);
    min-width: 500px;
}

.valhalla-split-col {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.valhalla-split-hdr {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--solver-map-color-label);
    padding-bottom: 6px;
    border-bottom: 1px solid var(--solver-map-color-border);
    margin-bottom: 6px;
}

.valhalla-split-body {
    max-height: 350px;
    overflow-y: auto;
    padding-right: 6px;
}

.valhalla-split-divider {
    width: 1px;
    background: var(--solver-map-color-border);
    margin: 0 10px;
    flex-shrink: 0;
}

/* ---- Tree viewer ---- */

.vt-tree {
    font-size: 11px;
    font-family: var(--solver-map-font);
    line-height: 1.6;
}

.vt-row {
    white-space: nowrap;
}

.vt-key {
    color: var(--solver-map-color-label);
    font-weight: 600;
}

.vt-val {
    color: var(--solver-map-color-dark-text);
}

.vt-null {
    color: var(--solver-map-color-faint);
    font-style: italic;
}

.vt-toggle {
    cursor: pointer;
    user-select: none;
}

.vt-toggle:hover {
    background: rgba(0, 0, 0, 0.04);
    border-radius: 3px;
}

.vt-btn {
    display: inline-block;
    width: 14px;
    height: 14px;
    line-height: 14px;
    text-align: center;
    font-size: 11px;
    font-weight: 700;
    color: var(--solver-map-color-muted-text);
    background: #f1f5f9;
    border: 1px solid var(--solver-map-color-border-strong);
    border-radius: 3px;
    margin-right: 4px;
    vertical-align: middle;
}

.vt-children {
    padding-left: 16px;
    border-left: 1px solid var(--solver-map-color-border);
    margin-left: 6px;
}

.vt-node {
    margin: 1px 0;
}

.vt-edge-wrap {
    border-radius: 4px;
    transition: background 0.15s;
}

.vt-edge-wrap:hover {
    background: rgba(37, 99, 235, 0.06);
}

/* ---- Minimap overlay ---- */

/* Repeats font-family because solver-map.js appends this element to document.body,
   outside .solver-map-wrapper's inheritance chain. */
.valhalla-minimap {
    display: none;
    position: fixed;
    font-family: var(--solver-map-font);
    top: 50%;
    right: 16px;
    transform: translateY(-50%);
    width: 400px;
    height: 400px;
    z-index: var(--solver-map-z-minimap);
    border-radius: var(--solver-map-panel-radius);
    border: 2px solid #2563eb;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
    background: #f8fafc;
    pointer-events: none;
}

/* ---- Ctrl-lasso menu --------------------------------------------------------------------- */
/* Positioned by solver-map.js at the drag's release point, inside the map container so it clips
   with the map. Same surface, border, radius and font as the layers panel. */
.solver-map-lasso-menu {
    position: absolute;
    z-index: var(--solver-map-z-panel-above);
    min-width: 200px;
    max-width: 280px;
    padding: 6px;
    background: var(--solver-map-color-surface);
    border: 1px solid var(--solver-map-color-border);
    border-radius: var(--solver-map-panel-radius);
    box-shadow: var(--solver-map-panel-shadow);
    font-family: var(--solver-map-font);
    font-size: 13px;
    color: var(--solver-map-color-dark-text);
}

.solver-map-override-tooltip {
    position: absolute;
    display: none;
    z-index: var(--solver-map-z-panel-above);
    max-width: 260px;
    padding: 5px 8px;
    background: var(--solver-map-color-surface);
    border: 1px solid var(--solver-map-color-border);
    border-radius: var(--solver-map-panel-radius);
    box-shadow: var(--solver-map-panel-shadow);
    font-family: var(--solver-map-font);
    font-size: 12px;
    line-height: 1.35;
    color: var(--solver-map-color-dark-text);
    pointer-events: none;
    white-space: nowrap;
}

.solver-map-override-tooltip-title {
    font-weight: 600;
    color: var(--solver-map-color-muted-text);
    margin-bottom: 2px;
}

.solver-map-override-tooltip-profile {
    padding-left: 6px;
}

.solver-map-lasso-title {
    padding: 4px 8px 6px;
    font-weight: 600;
    color: var(--solver-map-color-muted-text);
}

.solver-map-lasso-item,
.solver-map-lasso-confirm,
.solver-map-lasso-cancel {
    display: block;
    width: 100%;
    padding: 6px 8px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.solver-map-lasso-item:hover,
.solver-map-lasso-cancel:hover {
    background: var(--solver-map-color-border);
}

.solver-map-lasso-profiles {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 0 4px 6px;
}

.solver-map-lasso-profile {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px;
    cursor: pointer;
}

.solver-map-lasso-direction {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 6px 4px 6px;
    border-top: 1px solid var(--solver-map-color-border);
    margin-top: 2px;
}

.solver-map-lasso-actions {
    display: flex;
    gap: 6px;
}

.solver-map-lasso-confirm {
    background: var(--solver-map-color-dark-text);
    color: #fff;
    text-align: center;
}

.solver-map-lasso-cancel {
    text-align: center;
}

.solver-map-lasso-confirm:disabled {
    opacity: 0.45;
    cursor: default;
}
