﻿/* The report control vocabulary, and the report chart's own palette.

   GLOBAL, AND THAT IS THE FIX
   ---------------------------
   These rules style markup ReportGroupPanel authors: the filter chips, their popovers,
   the options inside them, the dismissal backdrop, the buttons, the chart, the legend
   and the results table. They used to live in ReportBuilder.razor.css behind ::deep,
   which compiles to a descendant selector under ReportBuilder's own scope attribute -
   so they applied only while the panel sat inside an element the Builder itself had
   authored.

   That was a hosting assumption disguised as a stylesheet. When the Builder moved into
   the shared ReportsShell its own <main class="rb-main"> stopped existing, no ancestor
   carried the Builder's scope any more, and all 71 rules silently stopped applying: the
   chips rendered as unstyled white blocks and their SVGs fell back to 300x150 filled
   black. The same assumption meant Report View - which never had a Builder ancestor -
   had been drawing every non-water-balance chart without its series colours.

   A vocabulary shared by two authoring components cannot live inside one of them.
   Global is the correct scope for it, and it is what lets a third host render a report
   correctly without knowing any of this.

   Loaded after caelowx-tokens.css and before the scoped bundle, so a component may
   still override a rule here from its own .razor.css. Every colour resolves to a
   platform token; nothing below names a colour of its own. */


/* ----------------------------------------------------------- the chart palette

   Series colours come from the existing indicator tokens rather than a palette of
   their own. They are already tuned for small filled shapes on this background, which
   is exactly what a line, a legend key and a donut slice are.

   Declared on .weather-shell rather than on the Builder's old page element: the panel
   needs them wherever it is hosted, and every Reports surface renders a shell. This is
   by *position* and not by measurement kind - two temperature series have to be told
   apart - which is why it is not the semantic measurement namespace. */
.weather-shell {
    /* Ordered so consecutive series are told apart at a glance. --accent and --rain
       are both blues and sat next to each other in the first ordering, which made a
       wind line and a rainfall column read as the same measurement. */
    --rb-series-1: var(--warning-indicator);
    --rb-series-2: var(--rain);
    --rb-series-3: var(--success-indicator);
    --rb-series-4: var(--danger-indicator);
    --rb-series-5: var(--accent);
}


.rb-note {
    color: var(--muted);
    font-size: 0.68rem;
    margin: 0;
}

.rb-note-error {
    color: var(--danger);
}

.rb-btn {
    align-items: center;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 9px;
    color: inherit;
    cursor: pointer;
    display: inline-flex;
    font-family: inherit;
    font-size: 0.69rem;
    font-weight: 500;
    gap: 7px;
    padding: 9px 15px;
}

.rb-btn svg {
    fill: none;
    height: 14px;
    stroke: currentColor;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 1.6;
    width: 14px;
}

.rb-btn:hover:not(:disabled) {
    border-color: var(--accent);
}

.rb-btn:disabled {
    cursor: not-allowed;
    opacity: 0.4;
}

.rb-btn-icon {
    padding: 9px 10px;
}

.rb-btn-small {
    font-size: 0.64rem;
    padding: 6px 11px;
}

.rb-btn-small svg,
.rb-btn-icon svg {
    height: 13px;
    width: 13px;
}

.rb-btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--bg);
    font-weight: 600;
}

.rb-btn-primary svg {
    fill: currentColor;
    stroke: none;
}

.rb-btn-primary:hover:not(:disabled) {
    filter: brightness(1.08);
}

.rb-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.rb-btn-icon.is-open {
    border-color: var(--accent);
    color: var(--text);
}











/* ---------------------------------------------------------------------- tabs */

/* The Library / Templates / Builder switch lives in ReportTabs, which owns its own
   rules. The copy that used to be here is gone rather than left as a dormant
   duplicate: three surfaces share one control now, and two stylesheets describing
   it is how they came to differ in the first place. */

/* Covers the viewport while any filter is open. This is what gives an open popover the
   "the reader has moved on" signal that Blazor has no document-level click for, and it is
   deliberately one element for all seven filters rather than a rule repeated per popover.

   Transparent: it dismisses, it does not dim. A shaded overlay would say the report behind
   it is unavailable, and the whole point of the composer is that the report stays legible
   while it is being changed. */
.rb-backdrop {
    background: transparent;
    inset: 0;
    position: fixed;
    z-index: var(--cx-z-popover-backdrop);
}

/* Anchors its own popover. */
.rb-filter {
    position: relative;
}

/* ------------------------------------------------------------------- the pill */

/* Label above value, as ref_3 shows: a pill states what it controls and what it
   is set to, so a closed row still reads as the report's definition. */
/* A dropdown trigger, not a badge - it keeps its own anatomy because it opens a
   popover and a badge does not. What it takes from the system is the shape: the same
   radius, the same internal padding, the same gap and the same transition as every
   other control, so the composer row reads as part of the application rather than as
   a vocabulary of its own. */
.rb-pill {
    align-items: center;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--cx-radius-md);
    color: inherit;
    cursor: pointer;
    display: inline-flex;
    font-family: inherit;
    gap: var(--cx-space-3);
    min-width: 132px;
    min-height: var(--cx-control-height);
    padding: var(--cx-space-2) var(--cx-control-padding-x);
    text-align: left;
    transition: border-color var(--cx-transition);
}

.rb-pill:hover:not(:disabled),
.rb-pill.is-open {
    border-color: var(--accent);
}

.rb-pill:disabled {
    cursor: not-allowed;
    opacity: 0.45;
}

.rb-pill:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* A filter carrying a non-default choice reads as set without shouting. */
.rb-pill.is-set {
    border-color: var(--accent);
}

/* A pill sized for a card header rather than a composer row.

   SHARED RATHER THAN CARD-LOCAL, because the constraint is not Media Stream's: any
   control that has to live inside a dashboard card meets the same two facts - the
   card is as narrow as three grid columns, and the header band is 34px. So the
   minimum width goes (the pill shrinks to its text and ellipsises), the height comes
   down one step, and nothing else about the pill changes: same border, same radius,
   same hover, focus, disabled and open treatment, same caret, same tokens.

   IT ADDS A CLASS AND CHANGES NO EXISTING RULE, so every pill already on the report
   composer renders exactly as it did. */
.rb-pill-compact {
    gap: var(--cx-space-2);
    max-width: 100%;
    min-height: 28px;
    min-width: 0;
    padding: 2px var(--cx-space-2);
}

/* The label is the only text on a compact pill, so it carries the truncation. `16ch`
   from the full pill is a composer measurement; here the container decides. */
.rb-pill-compact .rb-pill-copy strong {
    font-size: var(--cx-text-sm);
    max-width: none;
}

.rb-pill-compact .rb-pill-caret svg {
    height: 11px;
    width: 11px;
}

.rb-pill-icon svg {
    fill: none;
    height: 16px;
    stroke: var(--muted);
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 1.5;
    width: 16px;
}

.rb-pill-copy {
    display: flex;
    flex-direction: column;
    line-height: 1.3;
    min-width: 0;
}

.rb-pill-copy small {
    color: var(--muted);
    font-size: 0.55rem;
}

.rb-pill-copy strong {
    font-size: 0.7rem;
    font-weight: 600;
    max-width: 16ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rb-pill-caret {
    margin-left: auto;
}

.rb-pill-caret svg {
    fill: none;
    height: 13px;
    stroke: var(--muted);
    stroke-linecap: round;
    stroke-width: 1.7;
    width: 13px;
}

.rb-pill.is-open .rb-pill-caret svg {
    transform: rotate(180deg);
}

/* ---------------------------------------------------------------- the popover */

/* Anchored to its own filter and holding only that filter's controls. A shared
   panel is what made the previous version read as a form. */
.rb-pop {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 11px;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
    left: 0;
    max-height: 340px;
    min-width: 216px;
    overflow-y: auto;
    padding: 8px;
    position: absolute;
    top: calc(100% + 6px);
    z-index: 40;
}

/* The chart's popovers hang from controls at the right edge of the panel, so they
   open leftwards instead of off the side of the page.

   Declared *after* .rb-pop deliberately. Both are single-class selectors, so they
   have equal specificity and the later one wins - this rule previously sat 1200
   lines earlier and lost its `left` to .rb-pop every time, which is why the menus
   ran off the right edge. Keep it below. */
.rb-pop-right {
    left: auto;
    right: 0;
}

.rb-pop-wide {
    min-width: 258px;
}

.rb-pop-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.rb-opt {
    align-items: center;
    background: transparent;
    border: 0;
    border-radius: var(--cx-radius-sm);
    color: inherit;
    cursor: pointer;
    display: flex;
    font-family: inherit;
    font-size: var(--cx-text-md);
    gap: var(--cx-space-3);
    padding: var(--cx-space-2) var(--cx-space-3);
    text-align: left;
    width: 100%;
}

.rb-opt:hover {
    background: var(--surface-3);
}

.rb-opt:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.rb-opt-all {
    border-bottom: 1px solid var(--border);
    border-radius: 0;
    margin-bottom: 4px;
    padding-bottom: 9px;
}

/* Checkbox and radio marks drawn rather than native, so they follow the theme. */
.rb-mark {
    border: 1px solid var(--border);
    border-radius: 4px;
    flex: none;
    height: 14px;
    position: relative;
    width: 14px;
}

.rb-mark.is-on {
    background: var(--accent);
    border-color: var(--accent);
}

.rb-mark.is-on::after {
    border: solid var(--bg);
    border-width: 0 2px 2px 0;
    content: "";
    height: 7px;
    left: 4px;
    position: absolute;
    top: 1px;
    transform: rotate(45deg);
    width: 3px;
}

.rb-mark-radio {
    border-radius: 50%;
}

.rb-opt.is-on .rb-mark-radio {
    background: var(--accent);
    border-color: var(--accent);
    box-shadow: inset 0 0 0 3px var(--surface-2);
}

.rb-mark-plus {
    align-items: center;
    border-style: dashed;
    color: var(--muted);
    display: inline-flex;
    font-size: 0.7rem;
    justify-content: center;
    line-height: 1;
}

/* A derived calculation is labelled as one in the picker it is chosen from, not
   only on the card it produces. */
.rb-opt-tag {
    background: var(--surface-3);
    border-radius: 5px;
    color: var(--muted);
    font-size: 0.5rem;
    letter-spacing: 0.05em;
    margin-left: auto;
    padding: 2px 5px;
    text-transform: uppercase;
}

.rb-pop-error {
    color: var(--danger-indicator);
}

/* The platform's own message style, inside a popover. Margins only - the colours
   and the shape come from design-system.css, so Reports does not carry a second
   notification style of its own. */
.rb-pop-message {
    font-size: 0.66rem;
    margin: 8px 0 0;
    padding: 7px 9px;
}

.rb-pop-search {
    align-items: center;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    display: flex;
    gap: 7px;
    margin-bottom: 6px;
    padding: 0 9px;
}

.rb-pop-search svg {
    fill: none;
    flex: none;
    height: 14px;
    stroke: var(--muted);
    stroke-linecap: round;
    stroke-width: 1.6;
    width: 14px;
}

.rb-pop-search input {
    background: transparent;
    border: 0;
    color: inherit;
    font-family: inherit;
    font-size: 0.68rem;
    padding: 7px 0;
    width: 100%;
}

.rb-pop-search input:focus-visible {
    outline: none;
}

/* The count and Clear pair from ref_3 - a multi-select that says how many are on
   without having to be reopened. */
.rb-pop-foot {
    align-items: center;
    border-top: 1px solid var(--border);
    color: var(--muted);
    display: flex;
    font-size: 0.63rem;
    justify-content: space-between;
    margin-top: 6px;
    padding-top: 8px;
}

.rb-pop-foot button {
    background: transparent;
    border: 0;
    color: var(--accent);
    cursor: pointer;
    font-family: inherit;
    font-size: 0.63rem;
    padding: 0;
}

.rb-pop-note {
    color: var(--muted);
    font-size: 0.6rem;
    margin: 6px 2px 2px;
}

.rb-pop-pair {
    display: grid;
    gap: 8px;
    grid-template-columns: 1fr 1fr;
    margin-top: 8px;
}

.rb-pop-pair label {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.rb-pop-pair small {
    color: var(--muted);
    font-size: 0.57rem;
}

.rb-pop-pair input {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 7px;
    color: inherit;
    font-family: inherit;
    font-size: 0.67rem;
    padding: 6px 8px;
}

/* One row per series in the chart settings panel: what it is on the left, how it
   is drawn on the right. */
.rb-pop-row {
    align-items: center;
    display: flex;
    gap: 9px;
    justify-content: space-between;
    padding: 4px 2px;
}

.rb-pop-row-name {
    font-size: 0.66rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The per-series style pickers in the chart settings panel. Compact rather than a
   drawer: one row per series, the name on the left and its visualisation on the
   right, so the whole configuration reads at a glance. */
.rb-pop-style {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 7px;
    color: inherit;
    cursor: pointer;
    flex: none;
    font-family: inherit;
    font-size: 0.63rem;
    padding: 4px 6px;
}

.rb-pop-style:hover {
    border-color: var(--accent);
}

/* One group per selected measurement, each listing only the calculations its own
   capability entry permits - which is why the groups are not the same length. */
.rb-pop-group + .rb-pop-group {
    border-top: 1px solid var(--border);
    margin-top: 6px;
    padding-top: 6px;
}

.rb-pop-group-name {
    color: var(--muted);
    font-size: 0.6rem;
    letter-spacing: 0.04em;
    margin: 2px 2px 4px;
    overflow: hidden;
    text-overflow: ellipsis;
    text-transform: uppercase;
    white-space: nowrap;
}

.rb-opt:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

.rb-opt:disabled:hover {
    background: transparent;
}

/* An optional filter can be taken off the row. Core filters - station, date range,
   measurements - have no such control, because a report without one of them cannot
   be run at all.

   Sits beside the pill rather than inside it: a button nested in a button is
   invalid, and the inner one never receives the click. */
.rb-pill-remove {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-left: 0;
    border-radius: 0 9px 9px 0;
    color: var(--muted);
    cursor: pointer;
    font-family: inherit;
    font-size: 0.85rem;
    line-height: 1;
    margin-left: -1px;
    padding: 9px 8px;
}

.rb-pill-remove:hover {
    border-color: var(--danger);
    color: var(--danger);
}

.rb-pill-remove:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* The pill and its remove button read as one control. */
.rb-filter:has(.rb-pill-remove) .rb-pill {
    border-radius: 9px 0 0 9px;
}

.rb-filter {
    align-items: stretch;
    display: inline-flex;
}

/* One rule split in two when the group became a component: the page's half here, the
   group's half in ReportGroupPanel.razor.css. A media query cannot straddle a component
   boundary, and neither half is the other's to own. */
@media (max-width: 640px) {
    .rb-btn {
        flex: 1;
        justify-content: center;
    }
}


/* ============================================================ the Export menu

   Report View's one Export control and the panel it owns.

   Global rather than scoped to ReportView, deliberately. The markup is authored in
   ReportView but rendered inside ReportsShell's PrimaryAction slot, so a rule scoped
   to either component reaches it only while that nesting holds - which is exactly the
   fragility that put 71 ::deep rules in this file already. A control whose popover
   silently loses its positioning does not look broken; it looks placed. */

/* Anchors its own panel, the way .rb-filter does for the composer's pills. */
.rexport {
    display: inline-flex;
    position: relative;
}

.rexport-caret {
    display: inline-flex;
    margin-left: 2px;
}

.rexport-caret svg {
    fill: none;
    height: 14px;
    stroke: currentColor;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 2;
    width: 14px;
}

/* The open control keeps the pressed look while its menu is up, so the two read as one
   thing rather than as a button with a floating panel near it. */
.rexport .app-button.is-open {
    border-color: var(--cx-accent);
}

/* Right-aligned, because this control sits at the right edge of the row and a panel
   hanging leftwards from it is the only one that stays on the page. */
.rexport-menu {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 11px;
    box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
    display: grid;
    gap: 2px;
    min-width: 232px;
    padding: 6px;
    position: absolute;
    right: 0;
    top: calc(100% + 6px);
    z-index: var(--cx-z-popover);
}

.rexport-item {
    background: none;
    border: 0;
    border-radius: 8px;
    color: var(--text);
    cursor: pointer;
    display: grid;
    gap: 1px;
    padding: 7px 9px;
    text-align: left;
    width: 100%;
}

.rexport-item:hover,
.rexport-item:focus-visible {
    background: var(--surface-3);
}

.rexport-item-name {
    font-size: var(--cx-text-sm);
    font-weight: 600;
}

/* What the file actually is. "PDF" alone does not tell a reader that one of these is
   the whole report and another is only its chart. */
.rexport-item-note {
    color: var(--muted);
    font-size: var(--cx-text-xs);
}

/* Behind the menu, in front of the report: a click anywhere else dismisses it. Matches
   .rb-backdrop's arrangement, one layer below the menu itself. */
.rexport-backdrop {
    background: transparent;
    inset: 0;
    position: fixed;
    z-index: var(--cx-z-popover-backdrop);
}


/* ==========================================================================
   The Reports main region, moved out of ReportsShell.razor.css

   <main class="dashboard-main rbrowse-main"> is AppShell's element now - the shared
   application frame, the same one the Dashboard uses - so it carries AppShell's scope
   attribute and not ReportsShell's. Scoped rules anchored on .rbrowse-main stopped
   matching it the moment the frame was shared.

   These are those rules, in their original order, with ::deep dropped because it is a
   scoped-CSS construct and means nothing in a global sheet. The descendants they reach
   (.rbrowse-board, .rbrowse-toolbar, the card grid) are rendered by the Reports pages
   as content, exactly as before.

   This file loads after app.css, so .rbrowse-main still overrides .dashboard-main's
   padding on equal specificity. It loads *before* the scoped bundle, which is the one
   thing to keep in mind when adding a rule here.
   ========================================================================== */

.rbrowse-main {
    display: flex;
    flex-direction: column;

    /* The Dashboard's own rhythm, so moving between the two does not shift the
       content in from the edges. Full width, deliberately: Templates was leaving
       most of a desktop empty. */
    gap: var(--cx-space-5);
    padding: var(--cx-page-pad-top) var(--cx-page-pad-x) var(--cx-page-pad-bottom);
}



/* ============================================== the browsing surface, shared */

/* One board on both surfaces. Library had one and Templates did not, which is most
   of why they read as unrelated pages. */
.rbrowse-main .rbrowse-board {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--cx-radius-lg);
    display: flex;
    flex-direction: column;
    gap: var(--cx-space-5);
    padding: var(--cx-space-6);
}


/* ---------------------------------------------------------------- the toolbar */

.rbrowse-main .rbrowse-toolbar {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--cx-space-3);
}


/* Every control in the row is the same height, radius and border weight. The search
   box is allowed to be wider and nothing else about it differs - it was a different
   shape as well as a different width, which is what made the row read as four
   unrelated controls. */
.rbrowse-main .rbrowse-control {
    align-items: center;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--cx-radius-md);
    color: var(--text);
    display: flex;
    gap: var(--cx-space-3);
    height: var(--cx-control-height);
    padding: 0 var(--cx-control-padding-x);
    transition: border-color var(--cx-transition);
}


.rbrowse-main .rbrowse-control:hover {
    border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}


.rbrowse-main .rbrowse-control:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}


.rbrowse-main .rbrowse-search {
    flex: 1 1 220px;
    max-width: 320px;
}


.rbrowse-main .rbrowse-search input {
    background: transparent;
    border: 0;
    color: inherit;
    font-family: inherit;
    font-size: var(--cx-text-md);
    height: 100%;
    min-width: 0;
    padding: 0;
    width: 100%;
}


.rbrowse-main .rbrowse-search input:focus-visible {
    outline: none;
}


.rbrowse-main .rbrowse-search .cx-icon,
.rbrowse-main .rbrowse-search .cx-icon {
    color: var(--muted);
}


/* A select is the same box with a label inside it, so a filter and the search read
   as one family. The label is inside the control rather than above it because a
   row of controls with labels above them is a form, and this is a toolbar. */
.rbrowse-main .rbrowse-filter {
    gap: var(--cx-space-2);
    min-width: 132px;
}


.rbrowse-main .rbrowse-filter label {
    color: var(--muted);
    flex: none;
    font-size: var(--cx-text-2xs);
    font-weight: var(--cx-weight-semibold);
    letter-spacing: .02em;
    text-transform: uppercase;
}


.rbrowse-main .rbrowse-filter select {
    background: transparent;
    border: 0;
    color: inherit;
    font-family: inherit;
    font-size: var(--cx-text-md);
    font-weight: var(--cx-weight-medium);
    min-width: 0;
    padding: 0;
    width: 100%;
}


.rbrowse-main .rbrowse-filter select:focus-visible {
    outline: none;
}


/* The view switch. Square, and exactly as tall as everything beside it. */
.rbrowse-main .rbrowse-views {
    display: flex;
    gap: var(--cx-space-1);
    margin-left: auto;
}


.rbrowse-main .rbrowse-view {
    align-items: center;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--cx-radius-md);
    color: var(--muted);
    cursor: pointer;
    display: inline-flex;
    height: var(--cx-control-height);
    justify-content: center;
    transition: border-color var(--cx-transition), color var(--cx-transition), background var(--cx-transition);
    width: var(--cx-control-height);
}


.rbrowse-main .rbrowse-view:hover {
    border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
    color: var(--text);
}


.rbrowse-main .rbrowse-view.is-active {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent);
}


.rbrowse-main .rbrowse-view:focus-visible {
    outline: var(--cx-focus-width) solid var(--accent);
    outline-offset: var(--cx-focus-offset);
}


/* --------------------------------------------------------------- the count line */

.rbrowse-main .rbrowse-count {
    color: var(--muted);
    font-size: var(--cx-text-xs);
    margin: 0;
}


/* ------------------------------------------------------------------ a section

   Templates groups its shelf by category and the Library does not. That is the one
   structural difference between them and it is content, not layout - so a section
   heading is part of the shared vocabulary rather than something Templates draws
   for itself. */

.rbrowse-main .rbrowse-section {
    display: flex;
    flex-direction: column;
    gap: var(--cx-space-4);
}


.rbrowse-main .rbrowse-section + .rbrowse-section {
    margin-top: var(--cx-space-3);
}


.rbrowse-main .rbrowse-section-name {
    align-items: center;
    color: var(--muted);
    display: flex;
    font-size: var(--cx-text-eyebrow);
    font-weight: var(--cx-weight-heavy);
    gap: var(--cx-space-2);
    letter-spacing: var(--cx-tracking-eyebrow);
    margin: 0;
    text-transform: uppercase;
}


/* --------------------------------------------------------------- the card list

   Two densities of one system. The list is content-driven and uses the width; the
   grid is a catalogue and every cell is the same size. Both draw the same card. */

.rbrowse-main .rbrowse-list {
    display: flex;
    flex-direction: column;
    gap: var(--cx-space-4);
    list-style: none;
    margin: 0;
    padding: 0;
}


.rbrowse-main .rbrowse-list.is-grid {
    display: grid;

    /* 300px is where a card still holds a title, a sentence and a meta row without
       the sentence becoming three words per line. auto-fill rather than auto-fit so
       a shelf of two cards on a wide desktop keeps card-sized cards instead of
       stretching two of them across the viewport. */
    gap: var(--cx-space-4);
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}


/* Uniform cells. The grid is a deliberate catalogue: a long title or a third
   category chip must not make one card taller than the one beside it. */
.rbrowse-main .rbrowse-list.is-grid > li {
    display: flex;
    min-width: 0;
}

/* ------------------------------------------------------- phone, the main region */
@media (max-width: 720px) {


/* ==================================================================== phone */

    .rbrowse-main {
        gap: var(--cx-space-4);

        /* No top gutter: the compact header AppTopBar draws at this width sits flush
           against the top of the region, exactly as it does on the Dashboard. */
        padding: 0 var(--cx-page-pad-x-phone) var(--cx-page-pad-bottom-phone);
    }


    .rbrowse-main .rbrowse-board {
        gap: var(--cx-space-4);
        padding: var(--cx-space-5);
    }


    /* THE SEARCH BOX WAS 220px TALL HERE.

       The toolbar used flex-direction: column, and the search box carries
       flex: 1 1 220px from the row layout above. In a column, flex-basis is the
       *height* - so a single-line input reserved 220px of vertical space, against
       38px at every other width. It was the largest piece of empty space on the page,
       and a unit mix-up rather than a design.

       A two-column grid replaces the column of full-width rows. Search and the
       density toggle take the full width; the filters pair up, which takes Library's
       Station / Measures / Sort from three stacked rows to two cells and a half.
       Every filter is still present and still labelled - the footprint shrinks, the
       capability does not. */
    .rbrowse-main .rbrowse-toolbar {
        align-items: center;
        display: grid;
        gap: var(--cx-space-3);
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }


    .rbrowse-main .rbrowse-search {
        /* Its natural height, and the full width of the row. */
        flex: 0 0 auto;
        grid-column: 1 / -1;
        max-width: none;
    }


    .rbrowse-main .rbrowse-filter {
        /* The 132px floor is for filters sharing a wide toolbar; in a grid cell it
           only forces overflow on a narrow phone. */
        min-width: 0;
    }


    /* THE LIST/GRID TOGGLE IS GONE AT THIS WIDTH.

       Below 720 the grid density already collapses to a single column - see
       .rbrowse-list.is-grid further down - so the two buttons switched between one
       column and one column. They cost a toolbar row and bought nothing.

       display: none rather than visibility or opacity, so the grid row it occupied
       collapses with it and the count and the first card move up. The choice is
       untouched above the breakpoint, and a reader's selection survives the width
       change because nothing here writes to it. */
    .rbrowse-main .rbrowse-views {
        display: none;
    }


    /* One column at phone width whichever density is selected. */
    .rbrowse-main .rbrowse-list.is-grid {
        grid-template-columns: 1fr;
    }
}

/* ==========================================================================
   The browsing filters: Station, Measures, Sort

   These were native <select>s. The closed control was styled and looked right; the
   open list could not be styled at all, because a native select's popup is drawn by
   the operating system. On a dark application, tapping a filter produced a white
   system menu - not a CSS problem, a control problem.

   ReportsFilterMenu uses the popover the Builder's filters already use, so what is
   below is mostly the closed control. The panel itself is .rb-pop and the rows are
   .rb-opt, both defined above and unchanged.
   ========================================================================== */

.rbrowse-main .rbrowse-menu {
    /* Positioning context only, so the panel hangs off this control.

       NOT isolation. It was here, with a comment claiming it put the panel above the
       shelf, and the reasoning was backwards: isolation *confines* a subtree, so the
       panel's --cx-z-popover became a number meaningful only inside this filter, while
       the filter itself sat at auto in the toolbar. The cards below paint later, so on
       Templates the Measures panel opened behind them.

       Without it the panel's z-index competes where it needs to - against the board -
       and wins, with no card raised to meet it. */
    position: relative;
}

.rbrowse-main .rbrowse-filter-label {
    color: var(--muted);
    flex: none;
    font-size: var(--cx-text-2xs);
    font-weight: var(--cx-weight-medium);
    letter-spacing: .04em;
    text-transform: uppercase;
}

.rbrowse-main .rbrowse-filter-button {
    background: transparent;
    border: 0;
    color: inherit;
    cursor: pointer;
    display: flex;
    align-items: center;
    flex: 1;
    font-family: inherit;
    font-size: var(--cx-text-sm);
    gap: var(--cx-space-2);
    justify-content: space-between;
    min-width: 0;
    padding: 0;
    text-align: left;
}

.rbrowse-main .rbrowse-filter-value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rbrowse-main .rbrowse-filter-caret {
    color: var(--muted);
    display: inline-flex;
    flex: none;
}

.rbrowse-main .rbrowse-filter-caret svg {
    fill: none;
    height: 15px;
    stroke: currentColor;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 1.8;
    width: 15px;
}

.rbrowse-main .rbrowse-menu.is-open {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Dismissal, the same element-not-a-listener approach the Builder uses. */
.rbrowse-main .rbrowse-menu-backdrop {
    inset: 0;
    position: fixed;
    z-index: var(--cx-z-popover-backdrop);
}

.rbrowse-main .rbrowse-menu-list {
    /* Above its own backdrop, and never wider than the screen it opens on. */
    left: 0;
    list-style: none;
    margin: 0;
    max-width: calc(100vw - (var(--cx-page-pad-x-phone) * 2));
    right: auto;
    z-index: var(--cx-z-popover);
}

/* Touch-sized rows. The Builder's .rb-opt is already comfortable on a pointer; this
   is the floor a finger needs. */
.rbrowse-main .rbrowse-menu-list .rb-opt {
    min-height: 40px;
}

@media (max-width: 720px) {
    /* Sized to its own control rather than to a minimum width.

       The filters share a two-column grid row here, and .rb-pop carries a 216px floor for
       the Builder's wide toolbar. A panel opening from the right-hand cell started at
       x=203 and ran 216px wide, so it left the screen. Anchoring it to the other edge only
       moves the problem to the left-hand cell, and CSS cannot know which column a control
       is in - so neither anchor is the answer. Matching the control is: it is on-screen by
       construction, from either cell, at any width. */
    .rbrowse-main .rbrowse-menu-list {
        min-width: 0;
        width: 100%;
    }
}

/* ===========================================================================
   SHARED BROWSE PRIMITIVES

   Unscoped and in this file on purpose. The Account page's system lives in
   Account.razor.css, which is Blazor scoped CSS - every selector there gains a
   per-component attribute, so no other page can consume it however shared the
   class names look. Anything two pages must share has to live in a global
   sheet, and this is the one the browse pages already load.

   A badge, a segmented control and a pager. Each is the pattern Account
   established, expressed where History and any later browse page can use it.
   =========================================================================== */

/* ---------------------------------------------------------------- badge

   A ROUNDED RECTANGLE, NOT A CAPSULE, matching the radius the platform's
   buttons and inputs use. Semantic through a variant class rather than through
   a colour chosen at the call site:

     is-active    blue     active / current / in progress
     is-success   green    completed / resolved
     is-info      purple   informational / event / source / storm
     is-caution   amber    caution / moderate / advisory
     is-concern   orange   high concern / significant
     is-severe    red      severe
     is-critical  deep red critical / highest-priority threat
     is-rain      teal     rain / precipitation
     (default)    neutral  ended / closed / dismissed / inactive

   is-rain IS THE FIRST VARIANT THAT NAMES A KIND OF WEATHER rather than a
   severity or a lifecycle state, and it is deliberately here rather than in
   History's own stylesheet. A page-local rain colour is how two surfaces come
   to disagree about what rain looks like, and the Weather Journal will grow
   more of these - wind, heat, freeze - each wanting the same treatment.

   ------------------------------------------------------------------------
   is-concern AND is-critical CLOSE A REAL GAP, AND is-severe WAS ORANGE.

   docs/CaeloWX_Semantic_Color_Matrix.md defines a four-step progression -
   amber for caution, ORANGE for high concern, RED for severe, and the
   strongest red for a critical threat. The stylesheet had only amber and one
   more step, and that step was #ff7b22, which is orange. So a Tornado Warning
   and a Heat Advisory sat two shades apart on a scale the product says should
   span four, and the most dangerous product the National Weather Service
   issues rendered orange.

   is-severe is therefore now RED, which is what the matrix always said it was
   and what every consumer of the token already assumed. The orange it used to
   carry is not lost - it becomes is-concern, where it belongs, because the
   high-concern tier is a real rung and a Flood Warning is not a Tornado
   Warning. This changes how existing severe alert badges render, deliberately:
   the previous colour was under-stating a severity the data already carried.  */

.wp-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 7px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background: var(--surface-2, var(--bg));
    color: var(--muted);
    font-size: .68rem;
    font-weight: 700;
    line-height: 1.45;
    white-space: nowrap;
}

.wp-badge.is-active {
    border-color: color-mix(in srgb, var(--border) 45%, #5ba9f3 55%);
    background: color-mix(in srgb, transparent 86%, #5ba9f3 14%);
    color: #5ba9f3;
}

.wp-badge.is-success {
    border-color: color-mix(in srgb, var(--border) 45%, #55b27c 55%);
    background: color-mix(in srgb, transparent 86%, #55b27c 14%);
    color: #55b27c;
}

.wp-badge.is-info {
    border-color: color-mix(in srgb, var(--border) 45%, #a66df2 55%);
    background: color-mix(in srgb, transparent 86%, #a66df2 14%);
    color: #a66df2;
}

.wp-badge.is-caution {
    border-color: color-mix(in srgb, var(--border) 45%, #f59f17 55%);
    background: color-mix(in srgb, transparent 88%, #f59f17 12%);
    color: #f59f17;
}

/* HIGH CONCERN. The orange is-severe used to carry, now correctly named. */
.wp-badge.is-concern {
    border-color: color-mix(in srgb, var(--border) 45%, #ff7b22 55%);
    background: color-mix(in srgb, transparent 88%, #ff7b22 12%);
    color: #ff7b22;
}

.wp-badge.is-severe {
    border-color: color-mix(in srgb, var(--border) 45%, #e5484d 55%);
    background: color-mix(in srgb, transparent 88%, #e5484d 12%);
    color: #e5484d;
}

/* THE STRONGEST TREATMENT IN THE PRODUCT, and the only SOLID badge - a Tornado
   Warning should not have to be read to be noticed. Nothing below critical
   earns this, which is what keeps it meaning something.

   SOLID RATHER THAN TINTED, because a tint has to pick a text colour that suits
   one theme. The first attempt used #ff8a97, which reads well on the dark ground
   and washes out on the light one - a badge legible in only one of the two
   themes the product ships. White on this red is unambiguous in both.         */
/* is-recovered IS THE ONLY VARIANT THAT REPORTS GOOD NEWS.

   A platform alert is living state, upserted in place, so a cleared sensor outage still carries
   the Amber it was raised at - and "Sensors Reporting Again" was therefore drawn in the colour of
   a problem. Green says the condition is over. It is reachable only from a resolved state, never
   from a severity, so nothing can be promoted INTO it.                                        */
.wp-badge.is-recovered {
    border-color: #2f9e5f;
    color: #2f9e5f;
    background: color-mix(in srgb, transparent 86%, #2f9e5f 14%);
}

.wp-badge.is-critical {
    border-color: #c2334a;
    background: #c2334a;
    color: #fff;
    font-weight: 800;
}

.wp-badge.is-rain {
    border-color: color-mix(in srgb, var(--border) 45%, #2fb3ad 55%);
    background: color-mix(in srgb, transparent 86%, #2fb3ad 14%);
    color: #2fb3ad;
}

/* ---------------------------------------------------------------- segmented control */

.wp-segmented {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg);
}

.wp-segmented button {
    min-height: 30px;
    padding: 0 14px;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--muted);
    font: inherit;
    font-size: .74rem;
    font-weight: 700;
    cursor: pointer;
}

.wp-segmented button:hover {
    color: var(--text);
}

.wp-segmented button.is-selected {
    background: var(--accent);
    color: var(--on-accent, #fff);
}

.wp-segmented button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

/* ---------------------------------------------------------------- pager */

.wp-pager {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
}

.wp-pager-count {
    color: var(--muted);
    font-size: .74rem;
    font-variant-numeric: tabular-nums;
}

.wp-pager-nav {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.wp-pager-nav button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 32px;
    min-height: 32px;
    padding: 0 8px;
    border: 1px solid var(--border);
    border-radius: 7px;
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: .74rem;
    font-weight: 700;
    cursor: pointer;
}

.wp-pager-nav button:hover:not(:disabled) {
    border-color: var(--accent);
    color: var(--accent);
}

.wp-pager-nav button:disabled {
    opacity: .45;
    cursor: default;
}

.wp-pager-size {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--muted);
    font-size: .74rem;
}

.wp-pager-size select {
    min-height: 32px;
    padding: 0 8px;
    border: 1px solid var(--border);
    border-radius: 7px;
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: .74rem;
}

/* ---------------------------------------------------------------- toolbar micro-labels

   THE LABEL GOES, THE ACCESSIBLE NAME STAYS. "SHOW All" read as two words fighting each other;
   the selected value alone says what is chosen. The element is still rendered and is still what
   the control's `aria-labelledby` points at, so a screen reader hears "Show, All" exactly as
   before - this hides it visually and nothing more.

   Here rather than in History.razor.css because the label belongs to ReportsFilterMenu, and a
   rule in another component's scoped stylesheet can never reach it. */
.hist-toolbar .rbrowse-filter-label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ===========================================================================
   HISTORY AS A WORKSPACE, NOT A DOCUMENT

   ONE RULE HERE, AND ONLY BECAUSE IT HAS TO BE. `.dashboard-main` is rendered by
   AppShell, so a rule for it in History.razor.css - which is scoped CSS - would
   gain History's component attribute and never match. Everything INSIDE the
   board is History's own markup and is styled in History's own stylesheet.

   It is gated on `.hist-main`, a class only the History page passes, so no other
   surface changes: Reports keeps `.rbrowse-main` and keeps scrolling the page.

   WHY THE HEIGHT IS NEEDED AT ALL. An inner pane can only own the scroll if
   every ancestor between it and the viewport is height-constrained. Today the
   document scrolls, measured: `scrollingElement` is HTML at 1999px against a
   900px viewport. Constraining main is what hands that job downwards.
   =========================================================================== */

.dashboard-main.hist-main {
    display: flex;
    flex-direction: column;

    /* dvh, not vh: on mobile browsers vh is the tallest the viewport ever gets,
       so the bottom of the pane would sit under the address bar. */
    height: 100dvh;
}

/* Everything holds its natural size... */
.dashboard-main.hist-main > * {
    flex: 0 0 auto;
}

/* ...except the board, which takes the rest. `min-height: 0` is the part that is
   easy to omit and breaks the whole thing: a flex item's default minimum is its
   content, so without it the board refuses to be shorter than its rows and the
   overflow moves back up to the document. */
.dashboard-main.hist-main > .rbrowse-board {
    flex: 1 1 auto;
    min-height: 0;
}

@media (max-width: 640px) {
    /* The phone reserves room for AppBottomBar with a padding on the shell. Main
       has to come off the same measurement or the page scrolls by exactly that
       clearance. The token is the existing one - no new number. */
    .dashboard-main.hist-main {
        height: calc(100dvh - var(--cx-bottom-bar-clearance));
    }
}

/* ===========================================================================
   .wp-scroll - THE APPLICATION'S INTERNAL SCROLL PANE

   PROMOTED, NOT INVENTED. The Alerts card already had this treatment - thin,
   rounded, drawn from `--muted` and `--text` with `color-mix` so it follows the
   theme - but as a rule bound to `.card-alerts .alerts-content`, which nothing
   else could use. The forecast strips have a second version that hard-codes
   rgba(120, 128, 140), which is the same idea with the tokens spelled out.

   This is the first one, given a name. No new colour: every value resolves from
   the shell's own tokens, so light and dark follow without a second rule.

   ONE DELIBERATE DIFFERENCE FROM THE CARD. The Alerts version is fully
   transparent at rest and appears on hover, which suits a small card where the
   bar would be clutter. A results pane is the thing the reader is scrolling, so
   the bar stays visible - muted at rest, stronger on hover, strongest while
   dragging.

   `scrollbar-gutter: stable` reserves the track's width whether or not the bar
   is showing, so content does not shift sideways the moment a pane becomes
   scrollable.
   =========================================================================== */

.wp-scroll {
    scrollbar-gutter: stable;

    /* Firefox and the standard property. */
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--muted) 42%, transparent) transparent;
}

.wp-scroll:hover,
.wp-scroll:focus-within {
    scrollbar-color: color-mix(in srgb, var(--muted) 68%, transparent) transparent;
}

/* WebKit. The 3px transparent border with `background-clip: padding-box` is what
   makes a 10px track carry a 4px thumb - thin without being hard to grab. */
.wp-scroll::-webkit-scrollbar {
    width: 10px;
}

.wp-scroll::-webkit-scrollbar-track {
    background: transparent;
}

.wp-scroll::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--muted) 42%, transparent);
    background-clip: padding-box;
    border: 3px solid transparent;
    border-radius: 999px;
    min-height: 36px;
}

.wp-scroll:hover::-webkit-scrollbar-thumb,
.wp-scroll:focus-within::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--muted) 68%, transparent);
    background-clip: padding-box;
}

.wp-scroll::-webkit-scrollbar-thumb:hover,
.wp-scroll::-webkit-scrollbar-thumb:active {
    background: color-mix(in srgb, var(--text) 55%, transparent);
    background-clip: padding-box;
}
