/* ==========================================================================
   Staff console. Loaded in addition to app.css, which supplies the tokens.
   ========================================================================== */

/* --------------------------------------------------------------- county map */

/*
   THE RECOLOUR TRICK.

   Every county carries a pre-computed band for all three fill modes
   (data-vband / data-nband / data-zband) and its partisanship for both years
   (data-p24 / data-p20). Which of those the browser paints is decided purely by
   the class on the <svg>.

   So switching fill mode is ONE attribute changing on ONE element. Blazor Server
   sends that as a tiny diff; the alternative — recomputing a fill per county —
   would re-render 159 <path> elements over the wire on every toggle.
*/

.map-wrap {
    position: relative;
}

.county-map {
    display: block;
    width: 100%;
    height: auto;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.county-map path {
    stroke-width: 0.75;
    cursor: pointer;
    transition: fill 0.12s ease;
    /* Default fill, overridden by the mode rules below. A county that somehow
       matches no rule shows as "no data" rather than invisible. */
    fill: #f1ece4;
    stroke: #b9c2cc;
}

.county-map path:hover {
    fill-opacity: 0.82;
}

/*
   A focusable <path> gets the browser's default focus ring, and a browser draws
   that ring around the element's BOUNDING BOX — so a focused county showed a
   black rectangle straddling its neighbours instead of tracing its own border.
   Suppressed on both :focus and :focus-visible (Chrome paints the plain :focus
   ring on SVG), with the focus state carried by the county's own stroke, which
   follows the actual shape.
*/
.county-map path:focus,
.county-map path:focus-visible {
    outline: none;
}

.county-map path:focus-visible {
    stroke: var(--cyan);
    stroke-width: 3;
    /* Paint the focused county's outline over its neighbours' — SVG has no
       z-index, so without this the adjacent shapes overdraw half the ring. */
    paint-order: stroke;
}

/* ---- fill mode: volunteers (green — "how many do we have") ---------------- */
.county-map.mode-volunteers path[data-vband='0'] { fill: #f4f1ec; }
.county-map.mode-volunteers path[data-vband='1'] { fill: #d8ecd6; }
.county-map.mode-volunteers path[data-vband='2'] { fill: #a9d8a4; }
.county-map.mode-volunteers path[data-vband='3'] { fill: #6bbd66; }
.county-map.mode-volunteers path[data-vband='4'] { fill: #2f9e44; }

/* ---- fill mode: need (orange — "where the gap is") ------------------------ */
.county-map.mode-need path[data-nband='0'] { fill: #f4f1ec; }
.county-map.mode-need path[data-nband='1'] { fill: #fde3c8; }
.county-map.mode-need path[data-nband='2'] { fill: #fdbb84; }
.county-map.mode-need path[data-nband='3'] { fill: #f57f3d; }
.county-map.mode-need path[data-nband='4'] { fill: #d94801; }

/* ---- fill mode: voters (blue — "where the people are") -------------------- */
.county-map.mode-voters path[data-zband='0'] { fill: #f4f1ec; }
.county-map.mode-voters path[data-zband='1'] { fill: #dce8f5; }
.county-map.mode-voters path[data-zband='2'] { fill: #a8c8e8; }
.county-map.mode-voters path[data-zband='3'] { fill: #5a9bd4; }
.county-map.mode-voters path[data-zband='4'] { fill: #1c6fd6; }

/* ---- partisan outline, per year ------------------------------------------ */
/*
   Outline colour is INDEPENDENT of fill, which is the point of the two toggles:
   a staffer can ask "where are my volunteers" and "which counties are contested"
   at the same time. D blue / R red / C purple, matching the Apps Script build.
*/
.county-map.year-2024 path[data-p24='D'] { stroke: #1c6fd6; }
.county-map.year-2024 path[data-p24='R'] { stroke: #d13b2a; }
.county-map.year-2024 path[data-p24='C'] { stroke: #8e44ad; }

.county-map.year-2020 path[data-p20='D'] { stroke: #1c6fd6; }
.county-map.year-2020 path[data-p20='R'] { stroke: #d13b2a; }
.county-map.year-2020 path[data-p20='C'] { stroke: #8e44ad; }

/*
   Selection outranks partisanship, and has to be declared AFTER it to do so.
   The partisan rules are (0,3,1) — .county-map + .year-* + [data-p*] — so a
   plain `.county-map path.sel` at (0,2,1) silently lost, leaving a selected
   county tinted by its partisanship and distinguishable only by stroke width.
   The [data-fips] here (every path has one) matches that specificity, and
   source order breaks the tie.

   Selection must never be ambiguous: it drives every send and export.
*/
.county-map path.sel[data-fips] {
    stroke: var(--ink);
    stroke-width: 2.5;
}

.county-label {
    font-family: 'Public Sans', sans-serif;
    font-size: 8.5px;
    font-weight: 600;
    fill: var(--ink);
    text-anchor: middle;
    pointer-events: none;   /* never steal a click from the county underneath */
    paint-order: stroke;
    stroke: rgb(255 255 255 / 75%);
    stroke-width: 2.5px;    /* a halo, so labels stay legible over a dark fill */
}


/* ---- the readout --------------------------------------------------------- */

.county-readout {
    margin: var(--step) 0 0;
    min-height: 1.6em;
    font-size: 0.9375rem;
}

/*
   v1.30.1, carried over. SVG <title> tooltips render on HOVER, which touch
   devices do not have — so on a phone the map could never explain itself. The
   readout is the answer, but inline it lands hundreds of pixels below the county
   being tapped (the map is ~520px tall at 390px wide). On hover-less devices it
   is pinned to the bottom of the viewport instead.
*/
@media (hover: none) {
    .county-readout {
        position: fixed;
        inset: auto 0 0 0;
        z-index: 20;
        margin: 0;
        background: var(--navy-deep);
        color: #fff;
        padding: 0.85rem 1rem;
        box-shadow: var(--shadow-lg);
    }

    .county-readout .hint {
        color: #cfe0ec;
    }
}

/* ---- legend -------------------------------------------------------------- */

.legend {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem 0.75rem;
    margin-top: var(--step);
    font-size: 0.8125rem;
    color: var(--slate);
}

.legend .swatch {
    display: inline-block;
    width: 1rem;
    height: 0.75rem;
    border-radius: 2px;
    border: 1px solid rgb(16 34 47 / 15%);
    margin-right: 0.3rem;
    vertical-align: -1px;
}

/* --------------------------------------------------------------- console UI */

.console {
    max-width: 78rem;
    margin: 0 auto;
    padding: calc(var(--step) * 3) calc(var(--step) * 2) calc(var(--step) * 10);
}

.console-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--step);
    margin-bottom: calc(var(--step) * 3);
}

.panel-grid {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: calc(var(--step) * 3);
    align-items: start;
}

@media (max-width: 60rem) {
    .panel-grid {
        grid-template-columns: 1fr;
    }
}

.stat-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
    gap: calc(var(--step) * 2);
    margin-bottom: calc(var(--step) * 3);
}

.stat {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: calc(var(--step) * 2);
}

.stat .value {
    font-family: Fraunces, serif;
    font-variation-settings: 'SOFT' 20, 'WONK' 1;
    font-size: 1.75rem;
    font-weight: 700;
    line-height: 1.1;
    display: block;
}

.stat .label {
    font-size: 0.8125rem;
    color: var(--slate);
}

/* ---- toggles ------------------------------------------------------------- */

.seg {
    display: inline-flex;
    border-radius: var(--radius);
    overflow: hidden;
    border: 1px solid var(--line-strong);
}

.seg button {
    font: 600 0.8125rem 'Public Sans', sans-serif;
    padding: 0.4rem 0.8rem;
    border: 0;
    border-right: 1px solid var(--line-strong);
    background: var(--card);
    color: var(--navy);
    cursor: pointer;
    min-height: 38px;
}

.seg button:last-child {
    border-right: 0;
}

.seg button.on {
    background: var(--navy);
    color: #fff;
}

.toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--step) calc(var(--step) * 2);
    margin-bottom: calc(var(--step) * 2);
}

.toolbar .label {
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--slate);
    margin-right: 0.25rem;
}

@media (pointer: coarse) {
    .seg button {
        min-height: 44px;
        padding-inline: 1rem;
    }
}

/* ---- staff chrome -------------------------------------------------------- */

.staff-bar {
    background: var(--navy-deep);
    color: #fff;
    padding: 0.6rem calc(var(--step) * 2);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--step);
    font-size: 0.875rem;
}

.staff-bar a {
    color: #cfe0ec;
}

.staff-bar .who {
    color: #cfe0ec;
}

.mode-help {
    margin: var(--step) 0 0;
    font-size: 0.875rem;
    color: var(--slate);
    max-width: 60ch;
}

/* --------------------------------------------------------------- access list */

.access-table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: calc(var(--step) * 2);
}

.access-table th {
    text-align: left;
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--slate);
    padding: 0 0 0.4rem;
    border-bottom: 1px solid var(--line);
}

.access-table td {
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--line);
    overflow-wrap: anywhere;
}

.badge-owner {
    font-size: 0.75rem;
    font-weight: 700;
    background: rgb(37 159 218 / 14%);
    color: var(--navy);
    border-radius: var(--radius-pill);
    padding: 0.1rem 0.6rem;
}

.link-danger {
    all: unset;
    color: var(--danger);
    font-weight: 600;
    font-size: 0.875rem;
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
    min-height: 44px;   /* the only destructive control on the page */
    display: inline-flex;
    align-items: center;
}

.add-row {
    display: flex;
    align-items: end;
    gap: var(--step);
    flex-wrap: wrap;
}

.add-row .field {
    flex: 1 1 18rem;
}

.add-row button {
    font: 700 0.9375rem 'Public Sans', sans-serif;
    color: #fff;
    background: var(--navy);
    border: 0;
    border-radius: var(--radius-pill);
    padding: 0.7rem 1.5rem;
    cursor: pointer;
    min-height: 44px;
}

.add-row button:disabled {
    opacity: 0.5;
    cursor: default;
}

/* --------------------------------------------------------------- moderation */

.moderation-card + .moderation-card {
    margin-top: calc(var(--step) * 2);
}

.moderation-head {
    display: flex;
    align-items: start;
    justify-content: space-between;
    gap: var(--step);
}

.moderation-head h2 {
    font-size: 1.25rem;
    margin-bottom: 0.15rem;
}

.badge-blank {
    flex: none;
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    background: #fff4e0;
    color: #a5710a;
    border-radius: var(--radius-pill);
    padding: 0.15rem 0.6rem;
}

.moderation-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--step);
    margin-top: calc(var(--step) * 2);
}

.btn-approve,
.btn-reject,
.btn-quiet {
    font: 700 0.9375rem 'Public Sans', sans-serif;
    border-radius: var(--radius-pill);
    padding: 0.65rem 1.4rem;
    cursor: pointer;
    min-height: 44px;
    border: 1.5px solid transparent;
}

.btn-approve {
    background: var(--success);
    color: #fff;
}

.btn-reject {
    background: var(--danger);
    color: #fff;
}

/* Reject is deliberately the QUIET option until chosen: approving is the common
   outcome, and a destructive action should not be the loudest thing on screen. */
.btn-quiet {
    background: var(--card);
    color: var(--slate);
    border-color: var(--line-strong);
}

.btn-quiet:hover {
    color: var(--ink);
    border-color: var(--slate);
}

.btn-approve:disabled,
.btn-reject:disabled,
.btn-quiet:disabled {
    opacity: 0.55;
    cursor: default;
}

.reject-box {
    margin-top: calc(var(--step) * 2);
    padding: calc(var(--step) * 2);
    background: var(--danger-bg);
    border: 1.5px solid rgb(179 38 30 / 25%);
    border-radius: var(--radius);
}

/* ------------------------------------------------- queue filter & bulk actions */

.queue-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: calc(var(--step) * 2);
    max-width: 52rem;
    margin-bottom: calc(var(--step) * 2);
}

.field--inline {
    margin: 0;
    min-width: 14rem;
}

.queue-bar-actions {
    display: flex;
    gap: var(--step);
    margin-left: auto;
}

.queue-bar-actions .btn-quiet {
    padding: 0.5rem 1rem;
    font-size: 0.875rem;
    min-height: 40px;
}

/* The reason field needs a doubled class to outweigh app.css's shared field rule, which
   is input + six :not() attribute selectors = (0,6,1). A single class + attribute is
   (0,2,1) and loses, so border/padding/font were silently dead. */

/* Sticky so a long queue never strands the actions off-screen: the whole point of
   selecting 40 events is not having to scroll back to a button. */
.queue-bulk {
    position: sticky;
    top: var(--step);
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--step);
    max-width: 52rem;
    margin-bottom: calc(var(--step) * 2);
    padding: var(--step) calc(var(--step) * 2);
    background: var(--navy);
    color: #fff;
    border-radius: var(--radius-pill);
    box-shadow: 0 6px 20px rgb(17 55 86 / 25%);
}

.queue-bulk .bulk-reason.bulk-reason {
    flex: 1 1 12rem;
    min-height: 40px;
    padding: 0.4rem 0.9rem;
    border: none;
    border-radius: var(--radius-pill);
    font: 400 0.9375rem 'Public Sans', sans-serif;
}

.queue-bulk .btn-approve,
.queue-bulk .btn-reject {
    padding: 0.5rem 1.2rem;
    font-size: 0.875rem;
    min-height: 40px;
}

.pick {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: var(--step);
    font-size: 0.8125rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--slate);
    cursor: pointer;
}

.pick input {
    width: 1.15rem;
    height: 1.15rem;
    accent-color: var(--cyan);
    cursor: pointer;
}

.moderation-card.picked {
    border-color: var(--cyan);
    box-shadow: 0 0 0 3px rgb(37 159 218 / 18%);
}

@media (width <= 640px) {
    .queue-bar-actions {
        margin-left: 0;
    }

    .queue-bulk {
        border-radius: var(--radius);
    }
}

.console-head-actions {
    display: flex;
    align-items: center;
    gap: calc(var(--step) * 2);
}

/* ------------------------------------------------------------- add event form */

/* An <a> styled as a button needs the text centred on the same line box a <button>
   gives it for free. */
.btn-link {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

.check {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    cursor: pointer;
    font-weight: 600;
}

.check input {
    width: 1.15rem;
    height: 1.15rem;
    accent-color: var(--cyan);
    cursor: pointer;
}

.rule {
    border: none;
    border-top: 1.5px solid var(--line);
    margin: 0;
}

/* The recurrence preview is the safety net for the one control on this page whose
   effect is not visible from its inputs. Styled as a quiet aside, not an alert:
   it confirms, it does not warn. */
.preview {
    background: var(--paper);
    border: 1.5px solid var(--line);
    border-left: 4px solid var(--cyan);
    border-radius: var(--radius);
    padding: calc(var(--step) * 2);
}

.preview p {
    margin: 0 0 var(--step);
}

.preview ul {
    margin: 0;
    padding-left: 1.2rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    font-size: 0.9375rem;
}

.preview li.hint {
    list-style: none;
    margin-left: -1.2rem;
    padding-top: 0.2rem;
}

/* ------------------------------------------------------------------ data tables */

/* The wrapper scrolls, not the page: a table with seven columns will not fit a phone,
   and a horizontally scrolling BODY breaks every other layout on the page. */
.table-wrap {
    max-width: 78rem;
    overflow-x: auto;
    border: 1.5px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
}

.hub-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.9375rem;
}

.hub-table th {
    text-align: left;
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--slate);
    padding: 0.75rem 1rem;
    border-bottom: 1.5px solid var(--line);
    white-space: nowrap;
}

.hub-table td {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--line);
    vertical-align: top;
}

.hub-table tr:last-child td {
    border-bottom: none;
}

.hub-table .num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.hub-table .actions {
    white-space: nowrap;
}

.hub-table .actions button {
    padding: 0.35rem 0.8rem;
    font-size: 0.8125rem;
    min-height: 36px;
}

.hub-table tr.unplaced {
    background: #fffaf0;
}

/* ---- selection ring & sign-hub pins -------------------------------------- */

/*
   The selected county's outline, drawn as a stroke-only clone AFTER every county and
   label. SVG has no z-index and the paths are emitted alphabetically — an order with no
   relation to adjacency — so a neighbour drawn later painted its fill over the shared
   border and erased that segment of the ring. Which segment survived was alphabetical
   luck. Re-drawing on top is the whole fix; `paint-order` never helped, because it
   reorders fill and stroke WITHIN one element, not between elements.
*/
.county-map path.sel-ring {
    fill: none;
    stroke: var(--ink);
    stroke-width: 2.5;
    pointer-events: none;   /* must not steal the click from the county underneath */
}

.hub-pin {
    fill: var(--gold);
    stroke: var(--navy-deep);
    stroke-width: 1.25;
    pointer-events: none;   /* a pin sits on top of a county; the county owns the click */
}

/* ---------------------------------------------------------------- slide-in drawer */

/*
   The scrim is always in the DOM and toggles opacity rather than display, so the panel can
   actually animate in. visibility rides along so it cannot be tabbed into while closed —
   an off-screen panel that still takes focus is a keyboard trap.
*/
.drawer-scrim {
    position: fixed;
    inset: 0;
    z-index: 40;
    background: rgb(10 36 56 / 45%);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease, visibility 0.2s;
}

.drawer-scrim.open {
    opacity: 1;
    visibility: visible;
}

.drawer {
    position: fixed;
    inset: 0 0 0 auto;
    z-index: 50;
    width: min(30rem, 100vw);
    padding: calc(var(--step) * 3);
    background: var(--card);
    border-left: 1.5px solid var(--line);
    box-shadow: var(--shadow-lg);
    overflow-y: auto;
    transform: translateX(100%);
    visibility: hidden;
    transition: transform 0.22s cubic-bezier(0.32, 0.72, 0, 1), visibility 0.22s;
}

.drawer.open {
    transform: translateX(0);
    visibility: visible;
}

.drawer-head {
    display: flex;
    align-items: start;
    justify-content: space-between;
    gap: var(--step);
    margin-bottom: calc(var(--step) * 2);
}

.drawer-head h2 {
    margin: 0;
}

.drawer-close {
    flex: none;
    background: none;
    border: none;
    font-size: 1.75rem;
    line-height: 1;
    color: var(--slate);
    cursor: pointer;
    padding: 0 0.25rem;
    min-height: 44px;
    min-width: 44px;
}

.drawer-close:hover {
    color: var(--ink);
}

/* Respect a reduced-motion preference: the panel still appears, it just does not slide. */
@media (prefers-reduced-motion: reduce) {
    .drawer,
    .drawer-scrim {
        transition: none;
    }
}

/* --------------------------------------------------------------- column mapping */

.map-columns {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
    gap: calc(var(--step) * 2);
    margin-bottom: calc(var(--step) * 2);
}

/* The heading being mapped, not a form label: it is data from the pasted file, so it can be
   long and arbitrary and must not stretch the grid. */
.map-columns .field > span:first-child {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ------------------------------------------------------------------ documentation */

/* Prose, not a dashboard: a readable measure matters more than filling the width. */
.doc {
    max-width: 52rem;
}

.doc .lede {
    max-width: 46ch;
}

.doc .steps {
    list-style: none;
    counter-reset: step;
    margin: 0;
    padding: 0;
}

.doc .steps > li {
    counter-increment: step;
    position: relative;
    padding: 0 0 calc(var(--step) * 4) calc(var(--step) * 6);
    border-left: 2px solid var(--line);
    margin-left: 1rem;
}

.doc .steps > li:last-child {
    border-left-color: transparent;
    padding-bottom: 0;
}

/* The number sits ON the rule, so the steps read as one connected sequence. */
.doc .steps > li::before {
    content: counter(step);
    position: absolute;
    left: -1.1rem;
    top: 0;
    width: 2.2rem;
    height: 2.2rem;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--navy);
    color: #fff;
    border-radius: 50%;
    font: 700 0.9375rem 'Public Sans', sans-serif;
}

.doc .steps h2 {
    font-size: 1.375rem;
    margin: 0.2rem 0 var(--step);
}

.doc ul {
    max-width: var(--measure);
}

.doc code {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 4px;
    padding: 0.1rem 0.35rem;
    font-size: 0.9em;
}
