/* Application shell. Deliberately hand-written and small: the UI this MVP needs is a
   full-bleed map with panels over it, which a component framework's grid would fight
   rather than help. The template's Bootstrap-specific rules were removed with it. */

:root {
    color-scheme: light dark;

    --surface: #ffffff;
    --surface-raised: #f4f6f8;
    --surface-sunken: #e7ebef;
    --ink: #16202a;
    --ink-muted: #5b6b7a;
    --line: #d3dae1;
    --accent: #0f6fbe;
    --accent-ink: #ffffff;
    --danger: #b32121;
    --ok: #26b050;

    --header-height: 3.5rem;
}

@media (prefers-color-scheme: dark) {
    :root {
        --surface: #101820;
        --surface-raised: #17222c;
        --surface-sunken: #0b1219;
        --ink: #e6edf3;
        --ink-muted: #9aabb9;
        --line: #2a3946;
        --accent: #4da3e8;
        --accent-ink: #06121c;
    }
}

* { box-sizing: border-box; }

html, body {
    height: 100%;
    margin: 0;
}

body {
    background: var(--surface);
    color: var(--ink);
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    font-size: 1rem;
    line-height: 1.5;
}

h1 { font-size: 1.35rem; margin: 0 0 0.35rem; }
h1:focus { outline: none; }

a { color: var(--accent); }

.lede { color: var(--ink-muted); margin: 0 0 1rem; max-width: 60ch; }

/* Visible only once focused: the first Tab on the page should offer a way past the
   header, and the map that lands here is a large keyboard trap otherwise. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 1100;
    padding: 0.5rem 0.75rem;
    background: var(--accent);
    color: var(--accent-ink);
}

.skip-link:focus {
    left: 0;
}

/* --- Shell ------------------------------------------------------------------ */

.app-shell {
    display: grid;
    grid-template-rows: var(--header-height) 1fr auto;
    height: 100dvh;
}

.app-header {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    padding: 0 1rem;
    background: var(--surface-raised);
    border-bottom: 1px solid var(--line);
}

.app-brand {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    min-width: 0;
}

.app-brand-name {
    font-weight: 650;
    letter-spacing: -0.01em;
}

.app-brand-tagline {
    color: var(--ink-muted);
    font-size: 0.8125rem;
    /* The tagline is the first thing to go on a narrow viewport; the product name and
       the language switcher are not negotiable. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-nav {
    display: flex;
    gap: 0.75rem;
    margin-inline-start: auto;
}

.app-nav a {
    color: var(--ink-muted);
    text-decoration: none;
    padding: 0.25rem 0.5rem;
    border-radius: 0.25rem;
}

.app-nav a:hover { color: var(--ink); background: var(--surface-sunken); }

.app-nav a.active {
    color: var(--ink);
    background: var(--surface-sunken);
    font-weight: 600;
}

.app-main {
    /* min-height:0 so a map filling this row scrolls itself rather than the page. */
    min-height: 0;
    overflow: auto;
    padding: 1rem;
}

.app-footer {
    padding: 0.4rem 1rem;
    background: var(--surface-raised);
    border-top: 1px solid var(--line);
    color: var(--ink-muted);
}

/* --- Language switcher ------------------------------------------------------ */

.culture-selector {
    display: flex;
    gap: 0.25rem;
    margin: 0;
}

.culture-option {
    font: inherit;
    font-size: 0.8125rem;
    cursor: pointer;
    padding: 0.2rem 0.55rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: transparent;
    color: var(--ink-muted);
}

.culture-option:hover { color: var(--ink); background: var(--surface-sunken); }

.culture-option.is-current {
    background: var(--surface-sunken);
    border-color: var(--ink-muted);
    color: var(--ink);
    font-weight: 600;
}

/* --- Forms and validation --------------------------------------------------- */

.valid.modified:not([type=checkbox]) { outline: 1px solid var(--ok); }
.invalid { outline: 1px solid var(--danger); }
.validation-message { color: var(--danger); }

/* An ErrorBoundary must supply its own localized ErrorContent — CSS generated content
   cannot be translated, so this carries the styling and none of the words. */
.blazor-error-boundary {
    background: var(--danger);
    color: #ffffff;
    padding: 1rem;
    border-radius: 0.375rem;
}

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

/* The map fills its region and positions its own overlays. The shell's main row is
   already min-height:0, so this grows to the viewport and no further. */
.map-view {
    position: relative;
    height: 100%;
    min-height: 20rem;
    border: 1px solid var(--line);
    border-radius: 0.375rem;
    overflow: hidden;
}

.map-canvas {
    position: absolute;
    inset: 0;
}

.map-overlay {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 1.5rem;
    text-align: center;
    background: var(--surface-sunken);
}

.map-overlay-quiet { color: var(--ink-muted); }

.map-overlay-error {
    color: var(--ink);
    max-width: 40ch;
    margin: 0 auto;
    inset: 0;
}

/* MapLibre's controls default to a light palette; keep them legible on a dark shell
   without restyling the library. */
.map-view .maplibregl-ctrl-attrib {
    color-scheme: light only;
    font-size: 0.6875rem;
}

/* Present for structure and for assistive technology, but taking no space. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* --- Map rails -------------------------------------------------------------- */

/* Two columns over the map, one down each side, and every floating panel is a child of
   one of them. Before this the panels were each positioned against the map's corners
   independently — the AOI/analysis column from the top, the scene detail panel from the
   bottom — so a tall column and a tall detail panel claimed the same pixels and the
   later one in the DOM simply painted over the other. A visitor who ran a measurement
   found the numbers and the download link underneath the scene panel with no way to
   reach them.

   A rail makes that unreachable state impossible rather than unlikely: the panels are
   flex items in one column, so the layout itself guarantees they sit one after another,
   and there is no combination of open panels that can put one on top of another. */
.map-rail {
    position: absolute;
    top: 0;
    bottom: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    width: 22rem;
    max-width: 100%;
    /* The rail spans the map's full height so its panels can be laid out against it,
       but it is a layout box and not a surface: the pointer passes through the empty
       space under the last panel, so the map stays draggable there. Each panel takes
       the pointer back. */
    pointer-events: none;
}

.map-rail-left { left: 0; }

/* zoom-control-fix: MapLibre's own NavigationControl sits top-right too, and this
   rail's first item otherwise starts at the same 0.75rem every rail does — found by
   browser-test-infra's Playwright suite, which had to zoom with the mouse wheel
   because a real click on the button landed on the rail instead. Moving the control
   would not remove the collision, only relocate it: every corner is the top of some
   rail's own stack (.map-rail-left's first icon starts at top-left the same way), so
   the rail gets the clearance instead.

   rail-icon-strip-right-dock-fix: 4.5rem cleared the two zoom buttons this was
   originally measured against, but NavigationControl is constructed with its default
   showCompass (map-interop.js), a third stacked button — real measured height 97px,
   not 68px. Once the DOM-order fix made the icon strip (rather than the search panel)
   the rail's true top-right-most element, that extra ~25px of undercount became a
   real, visible overlap. 7rem clears the full three-button control plus the same
   0.75rem gap every other rail spacing in this file uses. */
.map-rail-right { right: 0; }

/* Two 22rem rails plus their margins need about 46rem of map. Narrower than that they
   would meet in the middle, which is the same overlap in the other axis, so they split
   what there is instead. */
@media (max-width: 48rem) {
    .map-rail { width: 50%; }
}

/* Every panel in a rail. `flex: 0 1 auto` lets a panel take the height its content
   wants and no more, and shrink when the rail runs out; `min-height: 0` is what makes
   that shrink produce a scrollbar instead of an overflow, because a flex item's default
   minimum is its content size and a panel at that minimum would push its sibling out of
   the rail — which is the overlap, arrived at the long way round. */
.map-rail > * {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    pointer-events: auto;
}

/* A dismiss control in a panel's top-right. An icon rather than a word because it sits
   in a heading row beside a title that is already the panel's name, and it is given an
   accessible name in full. */
.panel-close {
    flex: none;
    align-self: flex-start;
    font: inherit;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.4rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface-raised);
    color: var(--ink-muted);
}

.panel-close:hover {
    background: var(--surface-sunken);
    color: var(--ink);
}

/* --- AOI editor ------------------------------------------------------------- */

/* A panel over the map rather than beside it: the map is the page, and taking a
   column from it to hold three buttons is a poor trade. Positioned by the left rail
   rather than against the map's corner, so the scene detail panel below it in the same
   rail can never cover it. */
.map-panel {
    padding: 0.75rem;
    border: 1px solid var(--line);
    border-radius: 0.375rem;
    background: var(--surface);
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
}

/* --- Icon-strip rail layout --------------------------------------------------- */

/* One rail-icon-strip and the currently open panel's content, side by side — the
   one new flex item inside .map-rail's own column (MapPanelLayoutTests holds that
   column property, unchanged). SceneDetails sits below this row, not inside it,
   because it is never part of the icon-strip system (see Home.razor's remarks).

   scroll-fix: being a direct child of .map-rail, this also inherits .map-rail > *'s
   `flex: 0 1 auto; overflow-y: auto` — meant for a panel sitting directly in the
   rail, which this now sits in front of instead. Left as `0 1 auto`, this row only
   shrank on demand rather than actively filling the rail's remaining space, so
   .rail-panel-area's own height was never bounded by anything and grew to match its
   content — a long results list could be thousands of pixels tall with nothing to
   stop it. The *row's own* box stayed correctly sized regardless (flex-shrink still
   capped it against .map-rail), so its inherited overflow-y: auto did activate, but
   over the row's own content, icon strip included — scrolling it dragged the strip
   off screen with the panel. `flex: 1 1 auto` here makes the row actively claim the
   rail's real remaining space rather than merely tolerate being shrunk into it, and
   `overflow-y: visible` stops the row itself from ever being the scrolling element,
   pushing that role down to .rail-panel-area/.map-panel below, where it belongs. */
.rail-row {
    display: flex;
    align-items: stretch;
    gap: 0;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: visible;
}

/* The right rail's icon strip stays on the rail's own outer edge — mirrored, not
   moved: the strip is always the edge-most element, the panel always opens toward
   the map. */
.rail-row-reverse {
    flex-direction: row-reverse;
}

/* Holds every panel for this rail, one .map-panel per icon, all but the open one
   hidden. A flex column of its own so the visible panel gets the same shrink/scroll
   behaviour .map-rail > * gave a panel directly, one nesting level up from where
   that rule can reach it.

   scroll-fix: `align-self: stretch` rather than changing .rail-row's own
   `align-items` — this alone should stretch to .rail-row's real, bounded height
   (now that the row claims it via flex-grow above), without also stretching
   .rail-icon-strip into a tall empty bar it was never meant to be. Without a real
   height here, .map-panel's own overflow-y: auto below has nothing to constrain
   against and stays inert, matching the failure this whole rule set fixes. */
.rail-panel-area {
    flex: 1 1 auto;
    align-self: stretch;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.rail-panel-area > .map-panel {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* Deliberately not one of the app's own light/dark surface tokens: a persistent
   dark toolbar against the map, whichever theme the rest of the page is in — the
   same reason MapLibre's own NavigationControl reads as a fixed dark chrome rather
   than adapting to the page around it. */
.rail-icon-strip {
    display: flex;
    flex-direction: column;
    flex: none;
    gap: 0.25rem;
    padding: 0.5rem 0.375rem;
    border-radius: 0;
    background: #1a222b;
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.25);
}

.rail-icon-button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border: none;
    border-radius: 0.25rem;
    background: transparent;
    color: #b8c4cf;
    cursor: pointer;
}

.rail-icon-button:hover {
    background: rgba(255, 255, 255, 0.08);
    color: #ffffff;
}

.rail-icon-button.is-active {
    background: var(--accent);
    color: var(--accent-ink);
}

/* --- Admin boundary search --------------------------------------------------- */

/* Sits above the draw/upload controls in the same map-panel card, rather than its own
   floating panel: it feeds the same AOI editor state and a visitor reads the three
   input methods (search, draw, upload) as one group. */
.admin-boundary-search {
    margin-bottom: 0.6rem;
    padding-bottom: 0.5rem;
    border-bottom: 1px solid var(--line);
}

.admin-boundary-label {
    display: block;
    font-size: 0.8125rem;
    font-weight: 600;
    margin-bottom: 0.25rem;
}

.admin-boundary-input {
    width: 100%;
    box-sizing: border-box;
    font: inherit;
    font-size: 0.8125rem;
    padding: 0.3rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface-raised);
    color: var(--ink);
}

.admin-boundary-results {
    list-style: none;
    margin: 0.35rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    /* An autocomplete list under a text field, not a growing archive like the saved-
       AOI list below it — capped by the endpoint at ten rows, so this is a ceiling for
       an unusually tall one rather than something normally reached. */
    max-height: 10rem;
    overflow-y: auto;
}

.admin-boundary-result {
    width: 100%;
    text-align: left;
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
    font: inherit;
    font-size: 0.8125rem;
    cursor: pointer;
    padding: 0.3rem 0.5rem;
    border: 1px solid transparent;
    border-radius: 0.25rem;
    background: none;
    color: var(--ink);
}

.admin-boundary-result:hover {
    background: var(--surface-sunken);
    border-color: var(--line);
}

.admin-boundary-province {
    color: var(--ink-muted);
    font-size: 0.75rem;
}

.aoi-actions {
    display: flex;
    gap: 0.4rem;
    margin-bottom: 0.5rem;
}

.aoi-button {
    font: inherit;
    font-size: 0.875rem;
    cursor: pointer;
    padding: 0.3rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface-raised);
    color: var(--ink);
}

.aoi-button:hover { background: var(--surface-sunken); }

.aoi-button-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-ink);
}

.aoi-button-primary:hover { filter: brightness(1.08); }

.aoi-hint {
    margin: 0;
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

.aoi-summary {
    display: flex;
    gap: 0.6rem;
    align-items: baseline;
    margin: 0.5rem 0 0;
    font-size: 0.875rem;
}

.aoi-vertices { color: var(--ink-muted); font-size: 0.8125rem; }

.aoi-error {
    margin: 0.5rem 0 0;
    font-size: 0.8125rem;
    color: var(--danger);
}

.aoi-upload {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin-bottom: 0.5rem;
}

.aoi-upload-label {
    font-size: 0.8125rem;
    font-weight: 600;
}

.aoi-upload-input {
    font: inherit;
    font-size: 0.8125rem;
    max-width: 100%;
}

.aoi-upload-input:disabled { opacity: 0.6; }

/* --- Saved AOIs --------------------------------------------------------------- */

/* Its own block within the same map-panel card, rather than a separate floating panel:
   it acts on the AOI editor's own state (backlog item 3, Stage 1) and a visitor reads
   the two together. */
.saved-aoi {
    margin-top: 0.75rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--line);
}

.saved-aoi-title {
    font-size: 0.875rem;
    margin: 0 0 0.4rem;
}

.saved-aoi-save {
    display: flex;
    gap: 0.4rem;
    margin-bottom: 0.4rem;
}

.saved-aoi-name {
    flex: 1;
    font: inherit;
    font-size: 0.8125rem;
    padding: 0.3rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface-raised);
    color: var(--ink);
    min-width: 0;
}

.saved-aoi-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    /* This is the one list on the left rail with no upper bound (every saved area
       stays until deleted), so it scrolls internally rather than pushing the map's
       other controls off screen — the same reasoning the search results list follows. */
    max-height: 12rem;
    overflow-y: auto;
}

.saved-aoi-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.3rem 0;
    border-bottom: 1px solid var(--line);
}

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

.saved-aoi-info {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.saved-aoi-name-label {
    font-size: 0.8125rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.saved-aoi-date {
    font-size: 0.75rem;
    color: var(--ink-muted);
}

.saved-aoi-actions {
    display: flex;
    gap: 0.3rem;
    flex: none;
}

/* --- Search panel ----------------------------------------------------------- */

/* Opposite rail from the AOI editor. Its own scroll comes from the rail, which every
   panel in one gets: the results list is the one thing on this page with no upper bound
   on its length, and letting it push the panel past the viewport would take the map
   with it. */
.search-panel {
    padding: 0.75rem;
    border: 1px solid var(--line);
    border-radius: 0.375rem;
    background: var(--surface);
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
}

.search-title {
    font-size: 1rem;
    margin: 0 0 0.6rem;
}

.search-field {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    margin-bottom: 0.55rem;
    font-size: 0.8125rem;
}

.search-field > label {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.5rem;
    font-weight: 600;
}

.search-value {
    font-weight: 400;
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

.search-input {
    font: inherit;
    font-size: 0.875rem;
    padding: 0.25rem 0.4rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface-raised);
    color: var(--ink);
}

.search-range {
    width: 100%;
    accent-color: var(--accent);
}

.search-input:disabled,
.search-range:disabled { opacity: 0.6; }

.search-actions {
    display: flex;
    gap: 0.4rem;
    margin: 0.6rem 0;
}

.search-hint {
    margin: 0;
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

.search-status {
    margin: 0.5rem 0;
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

.search-error {
    margin: 0.5rem 0;
    font-size: 0.8125rem;
    color: var(--danger);
}

.search-warning {
    margin: 0.5rem 0;
    font-size: 0.8125rem;
    color: var(--ink);
}

.search-results {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

/* A button, not a div with a click handler: a result is chosen with the keyboard as
   often as with a pointer, and aria-pressed says which one is chosen. */
.search-result {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    width: 100%;
    text-align: start;
    font: inherit;
    font-size: 0.8125rem;
    cursor: pointer;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface-raised);
    color: var(--ink);
}

.search-result:hover { background: var(--surface-sunken); }

.search-result-selected {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
}

.search-result-date {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.search-result-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    color: var(--ink-muted);
}

/* Wraps rather than truncates: a product id is what a visitor quotes when asking
   someone else about this scene, and half of one is no use. */
.search-result-id {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.6875rem;
    color: var(--ink-muted);
    overflow-wrap: anywhere;
}

/* --- Search tabs and commercial results ------------------------------------- */

/* Inside SearchSourceTabs, which carries the frame and the heading. */
.search-panel-embedded {
    padding: 0;
    border: 0;
    box-shadow: none;
}

.demo-chip {
    display: inline-block;
    padding: 0 0.3rem;
    border-radius: 0.2rem;
    background: #8a5a00;
    color: #ffffff;
    font-size: 0.625rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    line-height: 1.4;
    vertical-align: middle;
    white-space: nowrap;
    flex: none;
}

.search-aoi-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.5rem;
    font-size: 0.8125rem;
}

.search-aoi-area {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    margin-inline-end: auto;
}

.search-aoi-summary .aoi-button {
    padding: 0.15rem 0.5rem;
    font-size: 0.8125rem;
}

.search-tabs {
    display: flex;
    gap: 0.25rem;
    margin-bottom: 0.6rem;
    border-bottom: 1px solid var(--line);
}

.search-tab {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font: inherit;
    font-size: 0.875rem;
    padding: 0.35rem 0.6rem;
    border: 0;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    background: transparent;
    color: var(--ink-muted);
    cursor: pointer;
}

.search-tab.is-active {
    border-bottom-color: var(--accent);
    color: var(--ink);
    font-weight: 600;
}

.search-tab:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.commercial-demo-notice {
    display: flex;
    gap: 0.4rem;
    align-items: baseline;
    margin: 0 0 0.5rem;
    font-size: 0.75rem;
    color: var(--ink-muted);
}

.commercial-empty,
.commercial-no-results {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.5rem;
}

.commercial-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.5rem;
    font-size: 0.8125rem;
}

.commercial-count {
    font-weight: 600;
    margin-inline-end: auto;
    font-variant-numeric: tabular-nums;
}

.commercial-sort {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}

.commercial-filters {
    margin-bottom: 0.6rem;
    padding: 0.5rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface-raised);
}

.commercial-results {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.commercial-card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface-raised);
    font-size: 0.8125rem;
}

.commercial-card-selected {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
}

.commercial-card-main {
    display: flex;
    gap: 0.5rem;
    width: 100%;
    padding: 0.4rem 0.5rem;
    border: 0;
    background: transparent;
    color: var(--ink);
    font: inherit;
    text-align: start;
    cursor: pointer;
}

.commercial-card-main:hover { background: var(--surface-sunken); }

.commercial-card-thumb {
    flex: none;
    width: 4rem;
    height: 4rem;
    border-radius: 0.2rem;
    object-fit: cover;
    background: var(--surface-sunken);
}

.commercial-card-summary {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
}

.commercial-card-title {
    display: flex;
    gap: 0.3rem;
    align-items: center;
    font-weight: 600;
    overflow-wrap: anywhere;
}

.commercial-card-time,
.commercial-card-facts {
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

.commercial-card-facts {
    display: flex;
    flex-wrap: wrap;
    column-gap: 0.6rem;
}

.commercial-card-toggle {
    align-self: flex-start;
    margin: 0 0.5rem;
    padding: 0.1rem 0;
    border: 0;
    background: transparent;
    color: var(--accent);
    font: inherit;
    cursor: pointer;
}

.commercial-card-details {
    padding: 0.25rem 0.5rem 0.4rem;
}

.commercial-card-list {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.1rem 0.6rem;
    margin: 0 0 0.4rem;
}

.commercial-card-list dt { color: var(--ink-muted); }
.commercial-card-list dd { margin: 0; overflow-wrap: anywhere; }

.commercial-card-id {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.6875rem;
}

.commercial-card-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.commercial-card-licence-text {
    margin: 0.4rem 0 0;
    color: var(--ink-muted);
}

.commercial-card-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.15rem 0.6rem;
    padding: 0.4rem 0.5rem;
    border-top: 1px solid var(--line);
    color: var(--ink-muted);
    font-variant-numeric: tabular-nums;
}

.commercial-card-minimum { flex-basis: 100%; }

.commercial-card-total {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35rem;
    margin-inline-start: auto;
}

.commercial-card-price {
    color: var(--ink);
    font-size: 0.9375rem;
}

.commercial-card-noprice {
    display: flex;
    flex-direction: column;
    flex-basis: 100%;
    color: var(--danger);
}

.commercial-card-noprice-reason { color: var(--ink-muted); }

.commercial-skeleton {
    flex-direction: row;
    gap: 0.5rem;
    padding: 0.4rem 0.5rem;
}

.commercial-skeleton-thumb,
.commercial-skeleton-lines > span {
    display: block;
    border-radius: 0.2rem;
    background: var(--surface-sunken);
    animation: commercial-pulse 1.2s ease-in-out infinite;
}

.commercial-skeleton-thumb {
    flex: none;
    width: 4rem;
    height: 4rem;
}

.commercial-skeleton-lines {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    flex: 1;
    padding-top: 0.2rem;
}

.commercial-skeleton-lines > span { height: 0.6rem; }
.commercial-skeleton-lines > span:nth-child(2) { width: 70%; }
.commercial-skeleton-lines > span:nth-child(3) { width: 45%; }

@keyframes commercial-pulse {
    50% { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
    .commercial-skeleton-thumb,
    .commercial-skeleton-lines > span { animation: none; }
}

/* The label on the map for the hovered or selected footprint; never in the way of a click. */
.commercial-map-chip {
    padding: 0.1rem 0.4rem;
    border-radius: 0.75rem;
    background: #9c3d00;
    color: #ffffff;
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    pointer-events: none;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

/* --- Scene details ---------------------------------------------------------- */

/* Last in the left rail, under the AOI editor: the two are read in that order — choose
   an area, then inspect what was found over it. It used to be anchored to the map's
   bottom-left corner independently of the column above it, which is how it came to
   cover the analysis panel's results. */
.scene-details {
    padding: 0.75rem;
    border: 1px solid var(--line);
    border-radius: 0.375rem;
    background: var(--surface);
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
}

.scene-details-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
}

.scene-details-title {
    font-size: 1rem;
    margin: 0;
}

.scene-details-list {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.15rem 0.75rem;
    margin: 0;
    font-size: 0.8125rem;
}

.scene-details-list dt {
    color: var(--ink-muted);
}

.scene-details-list dd {
    margin: 0;
    overflow-wrap: anywhere;
}

.scene-details-id {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.6875rem;
}

/* The top-of-atmosphere caveat. Set apart from the metadata list rather than shown as
   another row, because it qualifies a number the visitor will read elsewhere — an index
   value — rather than describing the scene. Muted and un-alarming on purpose: it is a
   condition on a comparison, not a warning that something is wrong. */
.scene-details-caveat {
    margin: 0.6rem 0 0;
    padding: 0.5rem 0.6rem;
    border-inline-start: 3px solid var(--accent);
    background: var(--surface-raised);
    border-radius: 0 0.25rem 0.25rem 0;
    color: var(--ink-muted);
    font-size: 0.75rem;
    line-height: 1.45;
}

.scene-quicklook {
    display: block;
    width: 100%;
    margin-top: 0.6rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
}

/* --- Imagery layer ---------------------------------------------------------- */

/* Below the AOI controls in the same panel: what this governs is the map, and what it
   costs is decided by where the map is looking. */
.imagery-layer {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--line);
}

.imagery-title {
    margin: 0 0 0.4rem;
    font-size: 0.875rem;
    font-weight: 600;
}

.imagery-toggle {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.875rem;
    cursor: pointer;
}

.imagery-opacity {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-top: 0.5rem;
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

.imagery-opacity-value { font-variant-numeric: tabular-nums; }

.imagery-opacity-input {
    width: 100%;
    margin: 0.15rem 0 0;
    accent-color: var(--accent);
}

.imagery-opacity-input:disabled { opacity: 0.5; }

.imagery-hint {
    margin: 0;
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

/* A notice, not an error: rendering has stopped or is about to, and the map still
   works — cached tiles keep appearing. */
.imagery-budget {
    margin: 0.5rem 0 0;
    padding: 0.4rem 0.5rem;
    border-radius: 0.25rem;
    font-size: 0.8125rem;
    border: 1px solid var(--line);
    background: var(--surface-raised);
}

.imagery-budget-warning { border-color: var(--accent); }

.imagery-budget-stopped {
    border-color: var(--danger);
    color: var(--danger);
}

.imagery-budget-details {
    margin-top: 0.35rem;
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

.imagery-budget-details summary { cursor: pointer; }

.imagery-budget-details p { margin: 0.25rem 0 0; }

/* Source checkboxes in the search panel. A fieldset because these are one question with
   several answers, which is what a fieldset and legend are for, and what a screen reader
   announces correctly. The browser's default fieldset border and padding are removed
   rather than styled: the panel already groups them visually. */
.search-sources {
    border: 0;
    margin: 0 0 0.75rem;
    padding: 0;
}

.search-sources > legend {
    padding: 0;
    margin-bottom: 0.35rem;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--ink-muted, inherit);
}

.search-source {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.875rem;
    line-height: 1.6;
    cursor: pointer;
}

/* The NASA layer switcher, separated from the imagery controls above it by the same
   rule the imagery panel uses, because it is a distinct control rather than more of
   the same one. */
.nasa-layers {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--line);
}

.nasa-layers-title {
    margin: 0 0 0.4rem;
    font-size: 0.875rem;
    font-weight: 600;
}

.nasa-layer-toggle {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.4rem;
    font-size: 0.875rem;
    cursor: pointer;
}

/* Small and quiet: a credit should be legible and never compete with the controls. */
.nasa-layers-attribution {
    margin: 0.5rem 0 0;
    font-size: 0.75rem;
    line-height: 1.4;
    color: var(--ink-muted, inherit);
}

/* The acquisition timeline. It replaced the flat results list rather than sitting beside
   it, so it inherits .search-result and .search-results and adds only what grouping and
   filtering need. */
.timeline-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.6rem;
    margin: 0.5rem 0;
    padding: 0.5rem;
    background: var(--surface-sunken, rgba(0, 0, 0, 0.03));
    border-radius: 0.25rem;
}

.timeline-filters-label {
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--ink-muted, inherit);
}

.timeline-chip,
.timeline-cloud {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.8125rem;
    cursor: pointer;
}

.timeline-cloud input[type="range"] {
    width: 6rem;
}

.timeline-hint {
    flex-basis: 100%;
    font-size: 0.75rem;
    color: var(--ink-muted, inherit);
}

.timeline-clear {
    padding: 0.15rem 0.5rem;
    font-size: 0.75rem;
}

/* An ordered list of months, each holding its own list of acquisitions. The markers are
   removed because the dates are the ordering a reader sees. */
.timeline-months,
.timeline-entries {
    list-style: none;
    margin: 0;
    padding: 0;
}

.timeline-month + .timeline-month {
    margin-top: 0.75rem;
}

/* Sticky so the month a visitor is scrolling through stays named. It is the one piece of
   context that makes a long list navigable. */
.timeline-month-label {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin: 0 0 0.25rem;
    padding: 0.2rem 0;
    background: var(--surface, #fff);
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--ink-muted, inherit);
}

.timeline-month-count {
    font-weight: 400;
}

/* The mark beside an acquisition whose provider can be rendered. Quiet: it is a hint
   that imagery exists, not a call to action. */
.timeline-renderable {
    opacity: 0.7;
}

/* The before/after buttons on a timeline row. Small and secondary: the row's own button
   is the primary action, and these are a second, deliberate choice. */
.timeline-compare {
    display: flex;
    gap: 0.25rem;
    margin: 0.15rem 0 0.4rem;
}

.timeline-compare-button {
    padding: 0.1rem 0.4rem;
    font-size: 0.6875rem;
    font-family: inherit;
    color: var(--ink-muted, inherit);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: 0.2rem;
    cursor: pointer;
}

.timeline-compare-button:hover {
    background: var(--surface-sunken, rgba(0, 0, 0, 0.04));
}

/* A chosen side reads as chosen without relying on colour alone: it is also the only
   one of the pair that is filled. */
.timeline-compare-set {
    font-weight: 600;
    color: var(--surface, #fff);
    background: var(--accent, #2f6f4f);
    border-color: var(--accent, #2f6f4f);
}

/* --- Search history ----------------------------------------------------------- */

/* Its own card between the search panel and the comparison panel, the same chrome as
   both. Only rendered once a session has a first entry — an empty history panel taking
   space above the comparison panel every time would be a fixture, not information. */
.search-history {
    padding: 0.75rem;
    border: 1px solid var(--line);
    border-radius: 0.375rem;
    background: var(--surface);
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
}

.search-history-title {
    font-size: 1rem;
    margin: 0 0 0.5rem;
}

.search-history-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    /* Bounded upstream by SearchHistoryRepository.MaxEntriesPerSession rather than by
       this — the scroll is a courtesy for a full list, not the mechanism that caps it. */
    max-height: 14rem;
    overflow-y: auto;
}

.search-history-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.3rem 0;
    border-bottom: 1px solid var(--line);
}

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

.search-history-info {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.search-history-dates {
    font-size: 0.8125rem;
}

.search-history-criteria {
    font-size: 0.75rem;
    color: var(--ink-muted);
}

.search-history-actions {
    display: flex;
    gap: 0.3rem;
    flex: none;
}

/* Second in the right rail, under the search panel. It was written as a section nested
   inside another panel — a top border and nothing else — but it is rendered as a
   sibling of the search panel, so before the rails it had no surface of its own and lay
   in the map's normal flow. Given the same chrome as the panels it sits with. */
.comparison {
    padding: 0.75rem;
    border: 1px solid var(--line);
    border-radius: 0.375rem;
    background: var(--surface);
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
}

.comparison-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.5rem;
}

.comparison-title {
    margin: 0 0 0.4rem;
    font-size: 0.875rem;
    font-weight: 600;
}

.comparison-sides {
    margin: 0;
}

.comparison-side-label {
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--ink-muted, inherit);
}

.comparison-side {
    margin: 0 0 0.5rem;
    font-size: 0.8125rem;
}

.comparison-side-date {
    display: block;
    font-weight: 600;
}

/* Source, sensor and resolution on both sides always, not only when they differ: a
   visitor comparing two Sentinel-2 scenes should not have to notice the absence of a
   label to know the pair matches. */
.comparison-side-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.15rem 0.5rem;
    color: var(--ink-muted, inherit);
}

.comparison-side-clear {
    margin-top: 0.2rem;
    padding: 0.1rem 0.4rem;
    font-size: 0.6875rem;
}

/* The map blend. One map with two stacked layers, so position and zoom are shared by
   construction — there is nothing to keep in step because there is only one view. */
.compare-layers {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--line);
}

.compare-layers-title {
    margin: 0 0 0.4rem;
    font-size: 0.875rem;
    font-weight: 600;
}

.compare-slider-label {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 0.8125rem;
}

.compare-slider-value {
    font-variant-numeric: tabular-nums;
    color: var(--ink-muted, inherit);
}

.compare-slider {
    width: 100%;
    margin: 0.25rem 0;
}

/* The two ends and the midpoint, reachable without dragging. A comparison is usually
   read by flicking between the extremes rather than by resting in between. */
.compare-slider-ends {
    display: flex;
    justify-content: space-between;
    gap: 0.25rem;
}

.compare-end {
    flex: 1;
    padding: 0.15rem 0.3rem;
    font-family: inherit;
    font-size: 0.6875rem;
    color: var(--ink-muted, inherit);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: 0.2rem;
    cursor: pointer;
}

.compare-end:hover {
    background: var(--surface-sunken, rgba(0, 0, 0, 0.04));
}

/* Which acquisition is actually on screen. A slider position is not self-explanatory,
   and a visitor reading a difference needs to know what they are looking at. */
.compare-showing {
    margin: 0.4rem 0 0;
    font-size: 0.75rem;
    color: var(--ink-muted, inherit);
}

.compare-mode-toggle {
    display: flex;
    gap: 0.25rem;
    margin-bottom: 0.5rem;
}

.compare-mode-option {
    flex: 1;
    padding: 0.25rem 0.4rem;
    font-family: inherit;
    font-size: 0.75rem;
    color: var(--ink-muted, inherit);
    background: transparent;
    border: 1px solid var(--line);
    border-radius: 0.2rem;
    cursor: pointer;
}

.compare-mode-option.is-current {
    color: var(--ink);
    background: var(--surface-sunken, rgba(0, 0, 0, 0.04));
    font-weight: 600;
}

/* swipe-compare: a sibling of .map-canvas (SwipeCompareLayer, not a rail panel — see
   its own remarks), so this positions against .map-view exactly the way .map-canvas
   already does, rather than against whichever rail happened to be the nearest
   positioned ancestor. */
.swipe-overlay {
    position: absolute;
    inset: 0;
    z-index: 1;
    /* Only the divider itself takes the pointer; everywhere else the primary map
       underneath must stay draggable, the same reasoning .map-rail's own empty space
       is pointer-events: none for. */
    pointer-events: none;
}

/* The slave map's own container. clip-path is set by initSwipeDivider in
   map-interop.js, entirely in the browser — nothing here crosses to .NET. */
.swipe-slave {
    position: absolute;
    inset: 0;
}

.swipe-divider {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 2px;
    margin-left: -1px;
    background: var(--surface-raised, #fff);
    box-shadow: 0 0 0 1px var(--line);
    cursor: col-resize;
    pointer-events: auto;
    touch-action: none;
}

/* A wider hit target than the visible 2px bar — a visitor should not have to land a
   pointer on a hairline to start dragging it. */
.swipe-divider::before {
    content: "";
    position: absolute;
    inset: 0 -0.5rem;
}

/* --- NDVI layer and legend --------------------------------------------------- */

/* Its own block below the imagery controls, separated the same way the NASA switcher
   is: this is a distinct layer over the same scene, not more of the same control. */
.ndvi-layer {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--line);
}

.ndvi-title {
    margin: 0 0 0.4rem;
    font-size: 0.875rem;
    font-weight: 600;
}

.ndvi-legend {
    margin-top: 0.6rem;
}

/* The ramp itself. The gradient is written inline by the component from the same seven
   stops the evalscript paints with, so it is not repeated here — a colour in this file
   would be a second place for the ramp to drift. */
.ndvi-legend-bar {
    height: 0.7rem;
    border: 1px solid var(--line);
    border-radius: 0.15rem;
}

/* -1, 0, +1 under the bar. The middle label sits at the middle because zero is the
   middle of a -1..1 scale, which is also where the ramp diverges. */
.ndvi-legend-scale {
    display: flex;
    justify-content: space-between;
    margin-top: 0.2rem;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: var(--ink-muted);
}

/* What the three parts of the scale mean, as a description list: one subject with named
   parts, which is what dl is for and what a screen reader announces correctly. */
.ndvi-legend-meaning {
    margin: 0.5rem 0 0;
    font-size: 0.75rem;
    line-height: 1.5;
}

.ndvi-legend-meaning dt {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.ndvi-legend-meaning dd {
    margin: 0 0 0.35rem;
    color: var(--ink-muted);
}

.ndvi-legend-meaning dd:last-child { margin-bottom: 0; }

/* The top-of-atmosphere caveat, where the index is actually read. Bordered rather than
   coloured as an error: it is a limit on what the number means, not a failure. */
.ndvi-legend-caveat {
    margin: 0.5rem 0 0;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: 0.25rem;
    font-size: 0.75rem;
    line-height: 1.5;
    background: var(--surface-raised);
}

/* --- NDVI statistics --------------------------------------------------------- */

/* Below the NDVI layer control, because the numbers and the colours are two readings of
   the same measurement and belong together. */
.ndvi-stats {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--line);
}

/* Title and dismiss on one row, the control at the end of it. */
.ndvi-stats-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
}

.ndvi-stats-title {
    margin: 0 0 0.4rem;
    font-size: 0.875rem;
    font-weight: 600;
}

.ndvi-stats-cost {
    margin-top: 0.35rem;
    font-size: 0.75rem;
}

/* A description list: one subject with named properties, which reads correctly to a
   screen reader and does not pretend three figures are a table. */
.ndvi-stats-list {
    margin: 0.6rem 0 0;
    font-size: 0.8125rem;
}

.ndvi-stats-list dt {
    font-weight: 600;
    color: var(--ink-muted);
}

.ndvi-stats-list dd {
    margin: 0 0 0.4rem;
    font-variant-numeric: tabular-nums;
}

/* Beside the mean, not under the list: it says how much of the area the mean is about,
   and a mean read without it is read as a statement about the whole area. */
.ndvi-stats-valid {
    margin-left: 0.4rem;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: var(--ink-muted);
}

/* Not an error: nothing failed, there was simply nothing valid to measure. */
.ndvi-stats-empty {
    margin: 0.4rem 0 0;
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--ink-muted);
}

/* The same treatment as the legend's caveat, because it is the same caveat said where
   it matters most — beside a number a visitor is about to subtract from another. */
.ndvi-stats-caveat {
    margin: 0.5rem 0 0;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: 0.25rem;
    font-size: 0.75rem;
    line-height: 1.5;
    background: var(--surface-raised);
}

.ndvi-stats-download {
    margin: 0.6rem 0 0.25rem;
}

/* A link styled as a button: the browser follows it directly, so the file never crosses
   the circuit, but it should read as the action it is. */
.ndvi-stats-download .aoi-button {
    display: inline-block;
    text-decoration: none;
}

.ndvi-stats-error {
    margin: 0.5rem 0 0;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--danger);
    border-radius: 0.25rem;
    font-size: 0.8125rem;
    line-height: 1.5;
    color: var(--danger);
}

/* --- Change detection --------------------------------------------------------- */

/* Below the NDVI controls, because it is the same index read a different way, and it is
   about the pair rather than the single selection. */
.change-panel {
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--line);
}

.change-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
}

.change-title {
    margin: 0 0 0.4rem;
    font-size: 0.875rem;
    font-weight: 600;
}

.change-cost-hint,
.change-threshold-hint {
    margin: 0.3rem 0 0;
}

.change-run {
    margin-top: 0.4rem;
}

/* The caveat block, and it opens the result rather than closing it. A visitor who has
   read "28.7% changed" before reaching a cloud figure has already formed the belief this
   block exists to prevent, so it is first, bordered, and not collapsible. */
.change-caveat {
    margin: 0.6rem 0;
    padding: 0.5rem 0.6rem;
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: 0.25rem;
    background: var(--surface-raised);
}

.change-caveat-title {
    margin: 0 0 0.35rem;
    font-size: 0.8125rem;
    font-weight: 600;
}

.change-caveat-text {
    margin: 0.4rem 0 0;
    font-size: 0.75rem;
    line-height: 1.5;
}

/* Both scenes' cloud cover, side by side, above the sentence that says why it matters. */
.change-cloud {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.15rem 0.6rem;
    margin: 0;
    font-size: 0.75rem;
}

.change-cloud dt {
    font-weight: 600;
}

.change-cloud dd {
    margin: 0;
}

.change-cloud-value {
    font-variant-numeric: tabular-nums;
}

/* A genuinely cloudy scene is not a footnote. */
.change-cloud-high {
    font-weight: 700;
    color: var(--danger);
}

.change-caveat-strong {
    margin: 0.45rem 0 0;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--danger);
    border-radius: 0.25rem;
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--danger);
}

.change-threshold-label {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
    margin-top: 0.6rem;
    font-size: 0.8125rem;
}

.change-threshold-value {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.change-threshold-input {
    width: 100%;
    margin: 0.15rem 0 0;
}

.change-figures {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.25rem 0.75rem;
    margin: 0.6rem 0 0;
    font-size: 0.8125rem;
}

.change-figures dt {
    color: var(--ink-muted);
}

.change-figures dd {
    margin: 0;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* The share of the whole area, under the share of what was measured. Two numbers on one
   line would read as one number with a decoration; the gap between them is the point. */
.change-of-area,
.change-elapsed,
.change-free {
    display: block;
    font-size: 0.75rem;
    color: var(--ink-muted);
}

.change-empty,
.change-refusal {
    margin: 0.5rem 0 0;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: 0.25rem;
    font-size: 0.75rem;
    line-height: 1.5;
    background: var(--surface-raised);
}

.change-error {
    margin: 0.5rem 0 0;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--danger);
    border-radius: 0.25rem;
    font-size: 0.8125rem;
    line-height: 1.5;
    color: var(--danger);
}

.change-legend {
    margin-top: 0.6rem;
}

/* The diverging bar. The gradient is written inline by the component from
   ChangeColourRamp, which is the only statement of the ramp there is — a colour here
   would be a second one, and the PNG is painted from the first. */
.change-legend-bar {
    height: 0.7rem;
    border: 1px solid var(--line);
    border-radius: 0.15rem;
}

.change-legend-scale {
    display: flex;
    justify-content: space-between;
    margin-top: 0.2rem;
    font-size: 0.75rem;
    font-variant-numeric: tabular-nums;
    color: var(--ink-muted);
}

.change-legend-meaning {
    margin: 0.5rem 0 0;
    font-size: 0.75rem;
    line-height: 1.5;
}

.change-legend-meaning dt {
    font-weight: 600;
}

.change-legend-meaning dd {
    margin: 0 0 0.35rem;
    color: var(--ink-muted);
}

.change-legend-meaning dd:last-child { margin-bottom: 0; }

.change-legend-note {
    margin: 0.4rem 0 0;
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--ink-muted);
}

.change-download {
    margin: 0.6rem 0 0.25rem;
}

/* A link styled as a button: the browser follows it directly, so the file never crosses
   the circuit, but it should read as the action it is. */
.change-download .aoi-button {
    display: inline-block;
    text-decoration: none;
}

/* --- Account ------------------------------------------------------------------ */

.account-form {
    max-width: 22rem;
    margin: 2rem auto;
}

.account-form-fields {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.account-form-fields input {
    font: inherit;
    font-size: 0.8125rem;
    padding: 0.3rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface-raised);
    color: var(--ink);
}

.account-status {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.8125rem;
}

.account-status form {
    display: contents;
}

/* --- Account plans -------------------------------------------------------------- */

/* Wider than .account-form: a single narrow column suits a login field, not three
   tiers meant to be compared side by side. */
.plans-page {
    max-width: 54rem;
    margin: 2rem auto;
}

.plans-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 1rem;
}

/* Same card treatment .search-panel and every other panel in the app already use —
   border/radius/background/shadow repeated rather than shared, which is this
   stylesheet's existing convention. */
.plan-card {
    flex: 1 1 15rem;
    padding: 0.75rem;
    border: 1px solid var(--line);
    border-radius: 0.375rem;
    background: var(--surface);
    box-shadow: 0 1px 6px rgba(0, 0, 0, 0.18);
}

.plan-card h2 {
    font-size: 1rem;
    margin: 0 0 0.5rem;
}

.plan-card p {
    margin: 0 0 0.4rem;
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

.plan-current {
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--accent);
}

.plan-until {
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

/* --- Plan status modal ------------------------------------------------------------ */

/* Opened from within Home's own circuit rather than by navigating to the standalone
   page above — see PlanStatusModal's own remarks for why. Full-viewport and outside
   the rail system entirely, the same reason SwipeCompareLayer's divider sits outside
   it: a rail-nested overlay would size against the rail's own box, not the viewport. */
.plan-status-backdrop {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: rgba(0, 0, 0, 0.45);
    z-index: 20;
}

.plan-status-modal {
    width: 100%;
    max-width: 54rem;
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    padding: 1rem;
    border: 1px solid var(--line);
    border-radius: 0.375rem;
    background: var(--surface);
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
}

.plan-status-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem;
}

.plan-status-title {
    margin: 0;
}

.plan-status-manage {
    margin-top: 1rem;
}

/* A tier card nested in the modal needs its own heading level below the modal's own
   h2, unlike the standalone page's h1-then-h2 hierarchy above. */
.plan-card h3 {
    font-size: 1rem;
    margin: 0 0 0.5rem;
}

/* A button that reads as inline text, for a refusal message's own "view plans" link —
   now a button rather than an anchor, since it opens the modal above in place of a
   real navigation. */
.link-button {
    padding: 0;
    border: none;
    background: none;
    font: inherit;
    color: var(--accent);
    text-decoration: underline;
    cursor: pointer;
}

.link-button:hover {
    text-decoration: none;
}

/* Admin area (/admin): plain server-rendered tables and definition lists. */
.admin-search {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
}

.admin-search input[type="search"] {
    min-width: 18rem;
}

.admin-table {
    width: 100%;
    border-collapse: collapse;
    margin: 0.75rem 0;
}

.admin-table th,
.admin-table td {
    padding: 0.4rem 0.6rem;
    border-bottom: 1px solid var(--line);
    text-align: left;
}

.admin-table th {
    color: var(--ink-muted);
    font-weight: 600;
}

.admin-pager {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-basis: 100%;
}

.admin-facts {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.35rem 1rem;
}

.admin-facts dt {
    color: var(--ink-muted);
}

.admin-warning {
    color: var(--danger);
}

.admin-note {
    color: var(--ink-muted);
    font-size: 0.9em;
}

.admin-result {
    margin: 0.5rem 0 1rem;
    padding: 0.6rem 0.9rem;
    border-left: 4px solid;
    border-radius: 0.25rem;
}

.admin-result-success {
    border-color: var(--ok);
    background: color-mix(in srgb, var(--ok) 12%, var(--surface));
    color: var(--ink);
}

.admin-result-refused {
    border-color: var(--danger);
    background: color-mix(in srgb, var(--danger) 10%, var(--surface));
    color: var(--ink);
}

.admin-plan-note {
    white-space: pre-wrap;
}

.admin-plan-form {
    display: grid;
    grid-template-columns: max-content minmax(12rem, 28rem);
    gap: 0.5rem 1rem;
    align-items: center;
}

.admin-plan-form .admin-note,
.admin-plan-form .admin-warning,
.admin-plan-form button {
    grid-column: 2;
    margin: 0;
}

/* --- Assistant (chat) panel --------------------------------------------------- */

.chat-panel {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.chat-title {
    margin: 0;
    font-size: 1rem;
}

.chat-messages {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    max-height: 26rem;
    overflow-y: auto;
}

.chat-intro,
.chat-unavailable,
.chat-notice,
.chat-privacy,
.chat-quota,
.chat-stopped,
.chat-shortened,
.chat-thinking {
    margin: 0;
    font-size: 0.875rem;
    color: var(--ink-muted);
}

.chat-message {
    padding: 0.5rem 0.625rem;
    border-radius: 0.375rem;
    font-size: 0.875rem;
    line-height: 1.45;
    overflow-wrap: anywhere;
}

.chat-message-user {
    align-self: flex-end;
    max-width: 85%;
    background: var(--accent);
    color: var(--accent-ink);
    white-space: pre-wrap;
}

.chat-message-assistant {
    align-self: stretch;
    background: var(--surface-raised);
    border: 1px solid var(--line);
}

.chat-paragraph,
.chat-list {
    margin: 0 0 0.4rem;
}

.chat-paragraph:last-child,
.chat-list:last-child {
    margin-bottom: 0;
}

.chat-list {
    padding-left: 1.1rem;
}

.chat-failed,
.chat-refused {
    margin: 0 0 0.4rem;
    color: var(--danger);
}

.chat-signin-required {
    margin: 0 0 0.4rem;
}

.chat-signin-links {
    display: flex;
    gap: 1rem;
    margin: 0;
}

.chat-form {
    display: flex;
    gap: 0.5rem;
    align-items: flex-end;
}

.chat-input {
    flex: 1 1 auto;
    min-width: 0;
    resize: vertical;
    font: inherit;
    font-size: 0.875rem;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface);
    color: var(--ink);
}

.chat-send,
.chat-stop,
.chat-retry {
    font: inherit;
    font-size: 0.875rem;
    cursor: pointer;
    padding: 0.3rem 0.7rem;
    border: 1px solid var(--line);
    border-radius: 0.25rem;
    background: var(--surface-raised);
    color: var(--ink);
}

.chat-send:disabled {
    cursor: default;
    opacity: 0.6;
}

/* Consent checkbox on the subscribe form, and the draft legal pages. */
.consent {
    display: flex;
    gap: 0.5rem;
    align-items: flex-start;
    margin: 0.75rem 0;
    font-size: 0.875rem;
}

.legal-page {
    max-width: 48rem;
    margin: 0 auto;
}

.legal-draft-banner {
    text-align: center;
}

.app-footer-legal {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 0.25rem;
}

/* Wyvern demo: spectral signatures. Colours are fixed per curve slot and read on both themes. */
:root {
    --spec-0: #d1495b;
    --spec-1: #00798c;
    --spec-2: #edae49;
}

.spec-panel {
    margin-top: 0.75rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--line);
}

.spec-points {
    list-style: none;
    margin: 0 0 0.5rem;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.spec-point {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    width: 100%;
    text-align: left;
    border: 1px solid var(--line);
    background: var(--surface-raised);
    color: var(--ink);
    padding: 0.25rem 0.4rem;
    border-radius: 4px;
    font: inherit;
    font-size: 0.8rem;
    cursor: pointer;
}

.spec-point[aria-pressed="true"] {
    border-color: var(--accent);
    background: var(--surface-sunken);
}

.spec-swatch {
    flex: none;
    display: inline-block;
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 50%;
    border: 1px solid var(--line);
}

.spec-swatch-0 { background: var(--spec-0); }
.spec-swatch-1 { background: var(--spec-1); }
.spec-swatch-2 { background: var(--spec-2); }

.spec-detail {
    margin: 0.2rem 0;
    font-size: 0.75rem;
    color: var(--ink-muted);
}

.spec-chart {
    display: block;
    width: 100%;
    height: auto;
    background: var(--surface-sunken);
    border-radius: 4px;
}

.spec-region-blue { fill: #4a7fd4; opacity: 0.2; }
.spec-region-green { fill: #3fa34d; opacity: 0.2; }
.spec-region-red { fill: #d64545; opacity: 0.2; }
.spec-region-rededge { fill: #b3478a; opacity: 0.2; }
.spec-region-nir { fill: #8a8f94; opacity: 0.18; }

.spec-region-label,
.spec-tick,
.spec-axis-title {
    font-size: 7.5px;
    fill: var(--ink-muted);
}

.spec-grid,
.spec-tickmark {
    stroke: var(--line);
    stroke-width: 0.5;
}

.spec-curve { stroke-width: 1.6; }
.spec-spread { fill-opacity: 0.15; stroke: none; }
polyline.spec-curve-0 { stroke: var(--spec-0); }
polyline.spec-curve-1 { stroke: var(--spec-1); }
polyline.spec-curve-2 { stroke: var(--spec-2); }
polygon.spec-curve-0 { fill: var(--spec-0); }
polygon.spec-curve-1 { fill: var(--spec-1); }
polygon.spec-curve-2 { fill: var(--spec-2); }

/* Wyvern demo: pivot analysis. Class colours match the map layer. */
.pivot-panel {
    margin-top: 0.75rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--line);
}

.pivot-disclaimer {
    margin: 0 0 0.35rem;
    padding: 0.3rem 0.45rem;
    border-left: 3px solid #f08c00;
    background: var(--surface-sunken);
    font-size: 0.78rem;
    font-weight: 600;
}

.pivot-total {
    margin: 0.4rem 0 0.2rem;
    font-weight: 600;
}

.pivot-classes {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.8rem;
}

.pivot-classes td {
    padding: 0.15rem 0.2rem;
    border-bottom: 1px solid var(--line);
}

.pivot-num {
    text-align: right;
    white-space: nowrap;
}

.pivot-chip {
    display: inline-block;
    width: 0.7rem;
    height: 0.7rem;
    margin-right: 0.4rem;
    border-radius: 50%;
    border: 1px solid var(--line);
}

.pivot-chip-active { background: #2fb344; }
.pivot-chip-partial { background: #f08c00; }
.pivot-chip-weak { background: #f0c419; }
.pivot-chip-fallow { background: #e5484d; }

.pivot-selected {
    margin-top: 0.5rem;
    padding: 0.4rem 0.5rem;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: var(--surface-raised);
    font-size: 0.8rem;
}

.pivot-selected h4 {
    margin: 0 0 0.2rem;
    font-size: 0.85rem;
}

.pivot-selected dl {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 0.1rem 0.6rem;
    margin: 0;
}

.pivot-selected dt { color: var(--ink-muted); }
.pivot-selected dd { margin: 0; }

/* --- Flush rails, no header (map page) ------------------------------------------------ */

/* The rails touch the left and right edges of the viewport and run its full height; the map is everything between.
   The icon strip is a full-height bar: the logo at its top, the tools below it, account and language at its bottom.
   Panels stay floating cards beside it. */
:root {
    /* A 2.5rem button plus the strip's own padding. */
    --rail-strip: 3.25rem;
    --rail-open: 22rem;
}

@media (max-width: 48rem) {
    :root { --rail-open: 50%; }
}

.map-shell {
    height: 100dvh;
}

.map-main {
    height: 100%;
    overflow: hidden;
}

.map-main .map-view {
    height: 100%;
    min-height: 0;
    border: 0;
    border-radius: 0;
}

/* Only the bar and the cards take the pointer; the empty space between them is the map. */
.map-rail > .rail-row {
    pointer-events: none;
    overflow: visible;
}

.rail-icon-strip {
    pointer-events: auto;
    overflow-y: auto;
    scrollbar-width: none;
}

.rail-panel-area {
    padding: 0.75rem 0.5rem;
    pointer-events: none;
}

.rail-panel-area > .map-panel {
    pointer-events: auto;
}

.map-rail-left > .scene-details {
    margin: 0 0.5rem 0.75rem calc(var(--rail-strip) + 0.5rem);
}

.rail-logo {
    flex: none;
    display: block;
    width: 2.5rem;
    height: 2.5rem;
    padding: 0;
    margin-bottom: 0.5rem;
    border: none;
    border-radius: 0.5rem;
    background: #fbfbf8;
    cursor: pointer;
    overflow: hidden;
}

.rail-logo img {
    display: block;
    width: 100%;
    height: 100%;
}

.rail-logo:focus-visible,
.rail-icon-button:focus-visible {
    outline: 2px solid #ffffff;
    outline-offset: 2px;
}

.rail-icon-group-bottom {
    margin-top: auto;
    padding-top: 0.5rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.25rem;
}

.culture-selector-compact {
    flex-direction: column;
}

.culture-selector-compact .culture-option {
    width: 2.5rem;
    padding: 0.3rem 0;
    font-size: 0.72rem;
    font-weight: 600;
    color: #b8c4cf;
    border-color: rgba(255, 255, 255, 0.18);
}

.culture-selector-compact .culture-option:hover {
    background: rgba(255, 255, 255, 0.08);
    color: #ffffff;
}

.culture-selector-compact .culture-option.is-current {
    background: rgba(255, 255, 255, 0.14);
    border-color: rgba(255, 255, 255, 0.5);
    color: #ffffff;
}

/* MapLibre's own controls keep clear of the rails: beside the icon strip while a rail has no panel open, beside the
   panel while it has. The top-left corner holds no control. */
.map-view .maplibregl-ctrl-top-right,
.map-view .maplibregl-ctrl-bottom-right {
    right: var(--rail-strip);
}

.map-view .maplibregl-ctrl-bottom-left {
    left: var(--rail-strip);
}

.map-view:has(.map-rail-right .rail-panel-area > .map-panel:not([hidden])) .maplibregl-ctrl-top-right,
.map-view:has(.map-rail-right .rail-panel-area > .map-panel:not([hidden])) .maplibregl-ctrl-bottom-right {
    right: calc(var(--rail-open) + 0.25rem);
}

.map-view:has(.map-rail-left .rail-panel-area > .map-panel:not([hidden])) .maplibregl-ctrl-bottom-left {
    left: calc(var(--rail-open) + 0.25rem);
}

/* --- Account panel (right rail) ----------------------------------------------------- */

.account-panel .account-links,
.account-panel nav ul {
    list-style: none;
    margin: 0 0 0.5rem;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-size: 0.875rem;
}

.account-email {
    margin: 0 0 0.4rem;
    font-size: 0.8125rem;
    overflow-wrap: anywhere;
}

.account-heading {
    margin: 0.75rem 0 0.3rem;
    font-size: 0.8125rem;
    color: var(--ink-muted);
}

/* --- FezaAi introduction (native dialog) ------------------------------------------------ */

.intro-dialog {
    position: fixed;
    inset: 0;
    margin: auto;
    padding: 0;
    width: min(36rem, calc(100vw - 2rem));
    max-height: calc(100dvh - 2rem);
    overflow: auto;
    border: none;
    border-radius: 0.75rem;
    background: var(--surface);
    color: var(--ink);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
}

.intro-dialog::backdrop {
    background: rgba(10, 18, 28, 0.6);
}

/* The wordmark's lettering is navy, so it sits on a white tile whatever the theme. */
.intro-logo-tile {
    display: flex;
    justify-content: center;
    padding: 1.25rem 1rem 0.75rem;
    background: #ffffff;
}

.intro-logo-tile img {
    max-width: 100%;
    height: auto;
}

.intro-body {
    padding: 1rem 1.5rem 1.5rem;
}

.intro-body h2 {
    margin: 0 0 0.5rem;
    font-size: 1.25rem;
}

.intro-body h3 {
    margin: 1rem 0 0.4rem;
    font-size: 1rem;
}

.intro-body ul {
    margin: 0;
    padding-inline-start: 1.2rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.intro-sources {
    margin: 1rem 0 0;
    color: var(--ink-muted);
    font-size: 0.8125rem;
}

.intro-legal {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 0.9rem;
    margin-top: 0.4rem;
    font-size: 0.8125rem;
}

.intro-close {
    position: absolute;
    top: 0.5rem;
    inset-inline-end: 0.5rem;
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid #d3dae1;
    border-radius: 50%;
    background: #ffffff;
    color: #16202a;
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
}

.intro-close:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* --- Base map chooser and the Mapbox logo ------------------------------------------------- */

.basemap-choice {
    margin: 0 0 0.75rem;
    padding: 0 0 0.5rem;
    border: none;
    border-bottom: 1px solid var(--line);
}

.basemap-choice legend {
    padding: 0;
}

/* Mapbox's logo, unstyled, at the bottom left of the map beside the rail; the scale moves up out of its way. */
.map-view .mapbox-logo {
    position: absolute;
    z-index: 2;
    bottom: 0.5rem;
    left: calc(var(--rail-strip) + 0.5rem);
    display: block;
    line-height: 0;
}

.map-view:has(.map-rail-left .rail-panel-area > .map-panel:not([hidden])) .mapbox-logo {
    left: calc(var(--rail-open) + 0.5rem);
}

.map-view:has(.mapbox-logo) .maplibregl-ctrl-bottom-left {
    bottom: 1.6rem;
}

/* --- Narrow screens: bottom tab bar and bottom sheet ----------------------------------- */

/* At 48rem and below both icon strips give way to a tab bar along the bottom, and one rail at a time becomes a sheet
   above it: the one Home marks .is-narrow-active, and only while it has a panel open. The map stays visible above the
   sheet. Everything here is inside the media query except hiding the narrow-only parts, so wider screens are unchanged. */
:root {
    --tabbar-height: 3.5rem;
    --sheet-height: 45dvh;
    --touch-target: 2.75rem;
}

.narrow-tabbar,
.sheet-header,
.narrow-only,
.narrow-assistant-button {
    display: none;
}

@media (max-width: 48rem) {
    .rail-icon-strip {
        display: none;
    }

    .map-rail {
        top: auto;
        left: 0;
        right: 0;
        bottom: var(--tabbar-height);
        z-index: 3;
        width: auto;
        height: var(--sheet-height);
        max-height: calc(100% - var(--tabbar-height) - 4rem);
        border-top: 1px solid var(--line);
        border-radius: 0.75rem 0.75rem 0 0;
        background: var(--surface);
        color: var(--ink);
        box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.25);
        pointer-events: auto;
    }

    .map-rail:not(.is-narrow-active),
    .map-rail:not(:has(> .rail-row > .rail-panel-area > .map-panel:not([hidden]))) {
        display: none;
    }

    .map-rail > .rail-row,
    .map-rail .rail-panel-area {
        pointer-events: auto;
    }

    /* The panel area scrolls, not the panel: a scrolling panel inside the sheet was painted a second time, as an empty
       white box over the map, in Chromium. */
    .map-rail .rail-panel-area {
        display: block;
        overflow-y: auto;
        padding: 0 0.75rem 0.75rem;
    }

    .map-rail .rail-panel-area > .map-panel {
        overflow-y: visible;
        padding: 0;
        border: 0;
        border-radius: 0;
        box-shadow: none;
    }

    .map-rail-left > .scene-details {
        margin: 0 0.75rem 0.75rem;
    }

    /* Touch targets: the sheet's own controls, and the buttons and fields of the panels inside it. */
    .map-rail button,
    .map-rail select,
    .map-rail input[type="date"],
    .map-rail input[type="text"],
    .map-rail input[type="search"],
    .map-rail a.aoi-button {
        min-height: var(--touch-target);
    }

    .map-rail a.aoi-button {
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    .sheet-header {
        display: block;
        flex: none;
        overflow: visible;
        padding: 0 0.75rem 0.5rem;
    }

    .sheet-handle {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
        padding: 0;
        border: none;
        background: transparent;
        cursor: grab;
        touch-action: none;
    }

    .sheet-handle-bar {
        width: 2.5rem;
        height: 0.3rem;
        border-radius: 0.15rem;
        background: var(--line);
    }

    .sheet-handle:focus-visible,
    .sheet-title:focus-visible,
    .sheet-close:focus-visible,
    .sheet-switch-button:focus-visible {
        outline: 2px solid var(--accent);
        outline-offset: 2px;
    }

    .sheet-title-row {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 0.5rem;
    }

    .sheet-title {
        margin: 0;
        font-size: 1rem;
    }

    .sheet-close {
        flex: none;
        min-width: var(--touch-target);
        padding: 0;
        border: 1px solid var(--line);
        border-radius: 0.375rem;
        background: var(--surface-raised);
        color: var(--ink-muted);
        font: inherit;
        font-size: 1.25rem;
        line-height: 1;
        cursor: pointer;
    }

    .sheet-switch {
        display: flex;
        gap: 0.375rem;
        margin-top: 0.5rem;
        overflow-x: auto;
        scrollbar-width: none;
    }

    .sheet-switch-button {
        flex: none;
        padding: 0 0.75rem;
        border: 1px solid var(--line);
        border-radius: 999px;
        background: var(--surface-raised);
        color: var(--ink);
        font: inherit;
        font-size: 0.8125rem;
        white-space: nowrap;
        cursor: pointer;
    }

    .sheet-switch-button[aria-pressed="true"] {
        border-color: var(--accent);
        background: var(--accent);
        color: var(--accent-ink);
    }

    .narrow-only {
        display: block;
    }

    .narrow-account-extras {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        gap: 0.5rem;
        margin-top: 0.75rem;
        padding-top: 0.75rem;
        border-top: 1px solid var(--line);
    }

    /* The same dark chrome the icon strips use, for the same reason: a fixed toolbar against the map. */
    .narrow-tabbar {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 4;
        display: flex;
        height: var(--tabbar-height);
        background: #1a222b;
        box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.25);
    }

    .narrow-tab {
        position: relative;
        flex: 1 1 0;
        min-width: var(--touch-target);
        min-height: var(--touch-target);
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 0.15rem;
        padding: 0;
        border: none;
        background: transparent;
        color: #b8c4cf;
        font: inherit;
        font-size: 0.6875rem;
        cursor: pointer;
    }

    .narrow-tab[hidden],
    .narrow-tab-badge[hidden] {
        display: none;
    }

    .narrow-tab[aria-current="true"] {
        background: var(--accent);
        color: var(--accent-ink);
    }

    .narrow-tab:focus-visible {
        outline: 2px solid #ffffff;
        outline-offset: -4px;
    }

    .narrow-tab-badge {
        position: absolute;
        top: 0.25rem;
        left: calc(50% + 0.4rem);
        min-width: 1.1rem;
        padding: 0 0.25rem;
        border-radius: 999px;
        background: var(--danger);
        color: #ffffff;
        font-size: 0.6875rem;
        line-height: 1.1rem;
    }

    /* The assistant's button: bottom right, above the attribution, and so above the sheet or the tab bar; it moves
       with the sheet's height through the same inset the map's controls use. */
    .narrow-assistant-button {
        position: absolute;
        right: 0.75rem;
        bottom: calc(var(--map-bottom-inset) + 2.25rem);
        z-index: 3;
        display: flex;
        align-items: center;
        justify-content: center;
        width: 3rem;
        height: 3rem;
        padding: 0;
        border: none;
        border-radius: 50%;
        background: var(--accent);
        color: var(--accent-ink);
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
        cursor: pointer;
    }

    .narrow-assistant-button[hidden] {
        display: none;
    }

    .narrow-assistant-button:focus-visible {
        outline: 2px solid var(--ink);
        outline-offset: 2px;
    }

    /* MapLibre's controls and the Mapbox logo: against the screen's sides, above the tab bar, and above the sheet while
       one is open. The selectors repeat the wide-screen ones so they win over them. */
    .map-view {
        --map-bottom-inset: var(--tabbar-height);
    }

    .map-view:has(.map-rail.is-narrow-active > .rail-row > .rail-panel-area > .map-panel:not([hidden])) {
        --map-bottom-inset: calc(var(--tabbar-height) + min(var(--sheet-height), 100% - var(--tabbar-height) - 4rem));
    }

    .map-view .maplibregl-ctrl-top-right,
    .map-view .maplibregl-ctrl-bottom-right,
    .map-view:has(.map-rail-right .rail-panel-area > .map-panel:not([hidden])) .maplibregl-ctrl-top-right,
    .map-view:has(.map-rail-right .rail-panel-area > .map-panel:not([hidden])) .maplibregl-ctrl-bottom-right {
        right: 0;
    }

    .map-view .maplibregl-ctrl-bottom-left,
    .map-view:has(.map-rail-left .rail-panel-area > .map-panel:not([hidden])) .maplibregl-ctrl-bottom-left {
        left: 0;
    }

    .map-view .maplibregl-ctrl-bottom-left,
    .map-view .maplibregl-ctrl-bottom-right {
        bottom: var(--map-bottom-inset);
    }

    .map-view:has(.mapbox-logo) .maplibregl-ctrl-bottom-left {
        bottom: calc(var(--map-bottom-inset) + 1.6rem);
    }

    .map-view .mapbox-logo,
    .map-view:has(.map-rail-left .rail-panel-area > .map-panel:not([hidden])) .mapbox-logo {
        left: 0.5rem;
        bottom: calc(var(--map-bottom-inset) + 0.5rem);
    }
}

/* --- Cart ---------------------------------------------------------------------- */

/* The number of lines on the strip's cart icon, like the Sepet tab's badge on a narrow screen. */
.rail-icon-button { position: relative; }

.rail-icon-badge {
    position: absolute;
    top: 0.1rem;
    right: 0.1rem;
    min-width: 1rem;
    padding: 0 0.2rem;
    border-radius: 999px;
    background: var(--danger);
    color: #ffffff;
    font-size: 0.625rem;
    font-weight: 600;
    line-height: 1rem;
    text-align: center;
}

/* The total on the add button: the price is what is pressed. A rail card is narrow, so "KDV dahil" sits above a
   button as wide as the card, with its label at one end and the price at the other. */
.commercial-card-total:has(.commercial-card-cart) {
    flex-basis: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0.15rem;
}

.commercial-card-total:has(.commercial-card-cart) .commercial-card-vat {
    align-self: flex-end;
    font-size: 0.75rem;
}

.commercial-card-cart {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.4rem;
    width: 100%;
    white-space: nowrap;
}

.commercial-card-cart:disabled {
    opacity: 0.6;
    cursor: progress;
}

.commercial-card-cart .commercial-card-price { color: inherit; }

.commercial-card-in-cart {
    border-color: var(--ok);
    color: var(--ink);
    background: color-mix(in srgb, var(--ok) 12%, var(--surface));
}

.commercial-card-cart-notice,
.commercial-card-signin {
    margin: 0;
    padding: 0.4rem 0.5rem;
    border-top: 1px solid var(--line);
}

.commercial-card-signin p { margin: 0 0 0.35rem; }

.commercial-card-signin-links {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.commercial-card-signin-links a { text-decoration: none; }

/* "Sepet · n kalem", the total and "Sepete git", under the results. */
.commercial-cart-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.75rem;
    margin-top: 0.6rem;
    padding: 0.5rem;
    border: 1px solid var(--line);
    border-radius: 0.3rem;
    background: var(--surface-raised);
    font-variant-numeric: tabular-nums;
}

.commercial-cart-go { margin-inline-start: auto; }

.cart-panel { display: flex; flex-direction: column; gap: 0.6rem; }

.cart-lines {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.cart-line {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 0.5rem;
    border: 1px solid var(--line);
    border-radius: 0.3rem;
    background: var(--surface);
}

.cart-line-expired { border-color: var(--danger); }

.cart-line p { margin: 0; }

.cart-line-title {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-weight: 600;
}

.cart-line-facts,
.cart-line-area {
    display: flex;
    flex-wrap: wrap;
    gap: 0.15rem 0.6rem;
    color: var(--ink-muted);
    font-size: 0.8125rem;
}

.cart-line-price-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.6rem;
    font-variant-numeric: tabular-nums;
}

.cart-line-price { font-size: 0.9375rem; }

.cart-line-expired s.cart-line-price { color: var(--ink-muted); }

.cart-line-validity { color: var(--ink-muted); font-size: 0.8125rem; }

.cart-line-expired .cart-line-validity { color: var(--danger); }

.cart-line-changed {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem 0.6rem;
    padding: 0.35rem 0.5rem;
    border-radius: 0.25rem;
    background: var(--surface-raised);
    font-variant-numeric: tabular-nums;
}

.cart-line-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.4rem;
}

.cart-totals {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 0.2rem 0.75rem;
    margin: 0;
    padding-top: 0.5rem;
    border-top: 1px solid var(--line);
    font-variant-numeric: tabular-nums;
}

.cart-totals dt { color: var(--ink-muted); }

.cart-totals dd { margin: 0; text-align: end; }

.cart-checkout {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.cart-checkout-reason { margin: 0; }

/* Checkout (commerce B6): the cart's summary page, an order's result page and the order history in the account panel.
   The summary and result pages reuse the plans page's frame and the cart's line and totals treatment. */
.checkout-page { display: flex; flex-direction: column; gap: 0.75rem; }

.checkout-page h1 { margin: 0; }

.checkout-page p { margin: 0; }

.checkout-lines,
.order-lines,
.order-history-list,
.order-files {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.checkout-line,
.order-summary {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 0.5rem;
    border: 1px solid var(--line);
    border-radius: 0.3rem;
    background: var(--surface);
}

.checkout-line-expired { border-color: var(--danger); }

.checkout-line p,
.order-summary p { margin: 0; }

.checkout-line-title,
.order-line-title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    font-weight: 600;
}

.order-line-price { margin-inline-start: auto; font-variant-numeric: tabular-nums; }

.checkout-line-facts,
.order-line-area,
.order-summary-date,
.order-files-until {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.15rem 0.6rem;
    color: var(--ink-muted);
    font-size: 0.8125rem;
}

.checkout-line-price-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.6rem;
    font-variant-numeric: tabular-nums;
}

.checkout-line-validity { color: var(--ink-muted); font-size: 0.8125rem; }

.checkout-line-expired .checkout-line-validity { color: var(--danger); }

.checkout-form { display: flex; flex-direction: column; gap: 0.6rem; align-items: flex-start; }

.checkout-order-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.order-summary-header {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.25rem 0.6rem;
}

.order-status { font-weight: 600; }

.order-status-delivered { color: var(--ok); }

.order-status-failed,
.order-status-partly-failed,
.order-line-delivery-Failed,
.order-line-delivery-Canceled { color: var(--danger); }

.order-status-expired,
.order-status-canceled { color: var(--ink-muted); }

.order-line { display: flex; flex-direction: column; gap: 0.2rem; }

.order-line-delivery { font-size: 0.8125rem; }

.order-files { flex-direction: row; flex-wrap: wrap; gap: 0.25rem 0.75rem; }

/* Links that are tapped on a phone: as high as every other target in a sheet. */
.order-file { display: inline-flex; align-items: center; min-height: var(--touch-target); }

.order-file-expired { color: var(--ink-muted); font-size: 0.8125rem; }

.order-history { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.75rem; }

.order-history-header { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }

.order-history-header .account-heading { margin: 0; }

.order-totals { font-size: 0.875rem; }

.checkout-page,
.order-summary { overflow-wrap: anywhere; }

/* Statically rendered pages (MainLayout: sign-in, plans, checkout, demo payment, order result) on a phone: the header's
   brand, links, account and language switch wrap onto as many rows as they need instead of widening the page. */
@media (max-width: 48rem) {
    /* The page scrolls as a whole: a fixed-height shell would leave the content a small inner window above the footer. */
    .app-shell {
        grid-template-rows: auto 1fr auto;
        height: auto;
        min-height: 100dvh;
    }

    .app-header {
        flex-wrap: wrap;
        gap: 0.35rem 0.75rem;
        padding: 0.5rem 0.75rem;
    }

    .app-brand-tagline { display: none; }

    .account-status {
        flex-wrap: wrap;
        min-width: 0;
        overflow-wrap: anywhere;
    }

    .app-main {
        overflow: visible;
        padding: 0.75rem;
    }

    .plans-page { margin: 1rem auto; }
}
