/* 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.75rem;
    bottom: 0.75rem;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
    width: 22rem;
    max-width: calc(100% - 1.5rem);
    /* 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.75rem; }
.map-rail-right { right: 0.75rem; }

/* 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: calc(50% - 1.125rem); }
}

/* 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);
}

/* --- 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;
}

/* --- 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);
}

/* 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);
}

/* --- 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;
}
