:root {
    --bg: #0a0e1a;
    --panel-bg: #131a2b;
    --border: #283252;
    --text: #e6edf3;
    --text-muted: #7c8aa5;
    --accent-teal: #4fd1c5;
    --accent-amber: #f0a83c;
    --accent-amber-dark: #0a0e1a;
    --accent-red: #e8837a;
    --accent-yellow: #f2c94c;
    --accent-grey: #8a94a6;
    --accent-purple: #9b7fd4;
    --accent-blue: #5ea8e0;
    --accent-green: #3fae5a;
    --warning-bg: #2e2110;
    --warning-border: #6b4a1a;
    --warning-text: #f0a83c;
    --input-bg: #dcdfe6;
    --input-text: #2b303b;
    --input-placeholder: #7b818f;
}

body {
    font-family: Segoe UI, Arial, sans-serif;
    color: var(--text);
    background-color: var(--bg);
    margin: 0;
    display: flex;
    justify-content: center;
}

.page {
    width: 100%;
    max-width: 1100px;
    padding: 20px;
    box-sizing: border-box;
}

/* body is display:flex (row, centered) for laying out its one normal
   child (.page) -- position:fixed takes this banner fully out of that
   flow instead of trying to make a flex-row child span edge-to-edge,
   which .page's own centering would otherwise fight. body.hasExerciseBanner
   reserves the matching space at the top so fixed positioning doesn't
   cover the first row of real content. Print reports don't go through
   this layout at all (Layout = null) -- see print.css for their own
   copy of this same banner. */
.exerciseBanner {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    padding: 7px 12px;
    text-align: center;
    background: repeating-linear-gradient(
        45deg,
        var(--accent-amber),
        var(--accent-amber) 22px,
        #171205 22px,
        #171205 44px
    );
}

.exerciseBanner span {
    display: inline-block;
    background: rgba(10, 14, 26, 0.88);
    color: var(--accent-amber);
    border: 1px solid var(--accent-amber);
    border-radius: 4px;
    padding: 3px 16px;
    font-weight: 800;
    font-size: 0.82em;
    letter-spacing: 0.06em;
}

body.hasExerciseBanner {
    padding-top: 38px;
    box-sizing: border-box;
}

/* Small-scale counterpart to .exerciseBanner -- for Viewer's net lists,
   which show several nets at once (a full banner doesn't fit "some of
   these are drills"). Not hidden/filtered -- a hidden drill someone
   forgot about is its own kind of confusing. */
.exerciseBadge {
    display: inline-block;
    background: var(--warning-bg);
    color: var(--accent-amber);
    border: 1px solid var(--accent-amber);
    border-radius: 3px;
    padding: 1px 6px;
    font-size: 0.7em;
    font-weight: 800;
    letter-spacing: 0.05em;
    vertical-align: middle;
}

header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 10px;
}

header h1 {
    font-size: 1.5em;
    margin: 0;
    color: var(--text);
}

/* Links the title back to the landing page ("/") with zero visual change
   from plain text -- no color shift, no underline, not even on hover. */
.siteTitleLink,
.siteTitleLink:hover,
.siteTitleLink:visited {
    color: inherit;
    text-decoration: none;
}

header nav a {
    color: var(--accent-teal);
    text-decoration: none;
    margin-left: 16px;
    font-size: 0.9em;
}

header nav a:hover {
    text-decoration: underline;
}

.navDropdownWrap {
    position: relative;
    display: inline-block;
}

/* Overrides the generic button {} rule's boxed chrome (dark background,
   border, padding) so this reads as one of its plain-link siblings (New
   Net/Viewer) rather than a NetControl-style button — it's sitting among
   plain <a> links here, not in a toolbar of buttons. The dropdown items
   themselves (<a> inside .dropdownMenu) already get correct styling from
   the existing .dropdownMenu a rule, which out-specificities header nav a's
   teal/margin so they read as plain menu rows, not nav links. */
.navDropdownButton {
    background: none;
    border: none;
    color: var(--accent-teal);
    font-size: 0.9em;
    font-family: inherit;
    margin-left: 16px;
    margin-right: 0;
    padding: 0;
    cursor: pointer;
}

.navDropdownButton:hover {
    text-decoration: underline;
}

section {
    margin-bottom: 20px;
}

h2 {
    color: var(--accent-teal);
    font-size: 1em;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-top: 0;
}

#netSetupPanel h2,
#checkinPanel h2,
#mapSection h2,
#rosterSection h2,
.landingCard h2 {
    color: var(--accent-amber);
}

#landingIntro {
    text-align: center;
    color: var(--text-muted);
    margin-bottom: 24px;
}

.landingSection {
    max-width: 800px;
    margin: 40px auto 0;
}

.landingSection:first-of-type {
    margin-top: 0;
}

/* Uses the site-wide default h2 (teal, uppercase, letter-spaced) rather
   than .landingCard h2's amber -- a section label reads as a different
   kind of heading than a card title, not a competing one. Left-justified,
   with the "horizontal line" as an underline directly beneath the title
   rather than a full divider between sections. */
.landingSectionHeading {
    margin: 0 0 16px;
    padding-bottom: 10px;
    border-bottom: 1px solid var(--border);
    text-align: left;
}

.landingChoices {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 20px;
}

/* Plain single-line links, deliberately not another row of .landingCard
   tiles -- this list is a quick admin-only index, not a set of
   destinations that need their own description text. */
.landingMaintenanceList {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.landingMaintenanceList a {
    color: var(--accent-teal);
    text-decoration: none;
}

.landingMaintenanceList a:hover {
    text-decoration: underline;
}

.landingCard {
    display: block;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 28px 24px;
    background-color: var(--panel-bg);
    text-decoration: none;
    color: var(--text);
    transition: border-color .15s ease, transform .05s ease;
}

.landingCard:hover {
    border-color: var(--accent-teal);
}

.landingCard:active {
    transform: translateY(1px);
}

.landingCard p {
    margin: 0;
    color: var(--text-muted);
    font-size: 0.9em;
    line-height: 1.5;
}

#netSetupPanel,
#checkinPanel {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
}

#topBar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 12px;
}

#resourcesLink,
#commandOverviewMapLink {
    display: inline-block;
    background-color: #1b2439;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 6px 14px;
    text-decoration: none;
    cursor: pointer;
}

#resourcesLink:hover,
#commandOverviewMapLink:hover {
    border-color: var(--accent-teal);
}

#printMenuWrap,
#setupMenuWrap,
#helpMenuWrap {
    position: relative;
    display: inline-block;
    margin-right: 6px;
}

/* Real 3-lamp stoplight, internet reachability -- see
   Services/NetworkStatusService.cs. Small and quiet by design (this is a
   background signal, not an alert tile), sitting right of the Help
   button as its own #topBar item. Exactly one lamp is ever "lit"
   (.networkStoplightLamp-lit, toggled by refreshNetworkStatus in
   netcontrol.js) -- full color plus a soft glow -- while the other two
   stay dim versions of their own hue, so the housing always reads as a
   genuine 3-light stoplight rather than a single on/off dot. */
.networkStoplight {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    margin-left: 8px;
    padding: 4px 3px;
    background-color: #1a1a1a;
    border: 1px solid var(--border);
    border-radius: 5px;
    vertical-align: middle;
}

.networkStoplightLamp {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    transition: background-color 0.2s ease, box-shadow 0.2s ease;
}

/* Dim/off state -- a muted, dark version of each lamp's own hue, not a
   flat grey, so you can still tell red/amber/green apart even unlit. */
.networkStoplightLamp-red { background-color: #4a201c; }
.networkStoplightLamp-amber { background-color: #4a3a14; }
.networkStoplightLamp-green { background-color: #1c3a22; }

.networkStoplightLamp-red.networkStoplightLamp-lit {
    background-color: var(--accent-red);
    box-shadow: 0 0 5px 1px var(--accent-red);
}

.networkStoplightLamp-amber.networkStoplightLamp-lit {
    background-color: var(--accent-amber);
    box-shadow: 0 0 5px 1px var(--accent-amber);
}

.networkStoplightLamp-green.networkStoplightLamp-lit {
    background-color: var(--accent-green);
    box-shadow: 0 0 5px 1px var(--accent-green);
}

.dropdownMenu {
    display: none;
    flex-direction: column;
    position: absolute;
    right: 0;
    top: 100%;
    margin-top: 4px;
    background-color: var(--panel-bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 4px;
    min-width: 130px;
    z-index: 20;
}

.dropdownMenu.visible {
    display: flex;
}

.dropdownMenu button,
.dropdownMenu a {
    text-align: left;
    background: none;
    border: 1px solid transparent;
    margin: 0;
    padding: 6px 10px;
    white-space: nowrap;
}

.dropdownMenu a {
    display: block;
    color: var(--text);
    text-decoration: none;
    border-radius: 4px;
    cursor: pointer;
}

.dropdownMenu button:hover,
.dropdownMenu a:hover {
    background-color: #1b2439;
    border-color: transparent;
}

/* Positioned at the cursor via JS (left/top set on rightclick), not
   relative to a menu-button ancestor like the rest of .dropdownMenu --
   needs its own top/right/z-index so it doesn't inherit the "hang below
   a button, flush right" defaults. z-index is higher than .modalOverlay's
   1000 -- this family (#resourceStatusPopup in particular, opened by
   clicking a tile inside #resourceStatusFullModal) needs to paint above an
   already-open modal, not fall back to DOM/reparent order against it
   (real fullscreen reparenting appends every FULLSCREEN_REPARENT_IDS
   element, this family included, before every .modalOverlay, which left
   #resourceStatusPopup rendering underneath #resourceStatusFullModal with
   only a sliver poking out below it -- confirmed live, not just assumed). */
.mapContextMenu {
    position: fixed;
    top: 0;
    right: auto;
    z-index: 1001;
}

/* Fullscreen-only "More" menu (see #fullscreenMenuWrap in NetControl.cshtml)
   -- hidden by default, shown only while the map is in true fullscreen
   (handleMapFullscreenChange in netcontrol.js toggles .visible). Top-left
   corner, opposite the weather widget's own top-right default. left is
   199px (10px + ~50mm, tuned up from an initial ~35mm that still slightly
   overlapped it) rather than flush against the edge -- Google's own
   Map/Satellite type control sits there too. position:fixed (not the usual
   position:relative every other *MenuWrap uses) since this floats over the
   fullscreened map rather than sitting in normal page flow -- it still
   works as #fullscreenMenu's containing block the same way. */
#fullscreenMenuWrap {
    display: none;
    position: fixed;
    top: 10px;
    left: 199px;
    z-index: 1000;
}

#fullscreenMenuWrap.visible {
    display: block;
}

/* Overrides .dropdownMenu's default right:0 (which assumes a toolbar
   button near the right edge, like Print/Setup/Help) -- anchored top-left
   instead, so it opens toward the middle of the screen rather than further
   off the left edge. Same z-index as .mapContextMenu/.modalOverlay so it
   never renders underneath either once reparented into the same fullscreen
   element. */
/* overflow-x explicit, not left to default -- setting only overflow-y
   otherwise computes overflow-x to "auto" too (same real CSS Overflow
   Module gotcha #matchDetailModal's own comment documents elsewhere in
   this file), which rendered an unwanted horizontal scrollbar. */
#fullscreenMenu {
    left: 0;
    right: auto;
    z-index: 1000;
    max-height: 80vh;
    overflow-y: auto;
    overflow-x: hidden;
    min-width: 220px;
}

.fullscreenMenuDivider {
    border-top: 1px solid var(--border);
    margin: 4px 0;
}

/* Print flyout (see #fullscreenPrintSubmenu in NetControl.cshtml) -- a
   sibling popup positioned by JS (openFullscreenPrintSubmenu in
   netcontrol.js), same shape as #reassignPositionMenu, not a
   position:absolute descendant of #fullscreenMenu -- that had inflated
   #fullscreenMenu's own scrollable area the instant it opened (phantom
   scrollbars that only appeared while hovering and vanished the moment the
   cursor left "Print"). .mapContextMenu (see above) already supplies
   position:fixed/z-index; only sizing is needed here. */
#fullscreenPrintSubmenu {
    max-height: 80vh;
    overflow-y: auto;
    overflow-x: hidden;
    min-width: 220px;
}

#resumeNetPanel {
    margin-bottom: 12px;
}

#resumeNetToggle {
    background: none;
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-muted);
    font-size: 0.85em;
    padding: 4px 10px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

#resumeNetToggle:hover {
    border-color: var(--accent-teal);
    color: var(--text);
}

#resumeNetIcon {
    display: inline-block;
    transition: transform 0.15s ease;
}

#resumeNetPanel.expanded #resumeNetIcon {
    transform: rotate(90deg);
}

#resumeNetList {
    display: none;
}

#resumeNetPanel.expanded #resumeNetList {
    display: block;
    margin-top: 8px;
    padding: 10px 14px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background-color: var(--panel-bg);
    max-width: 500px;
}

#resumeNetList ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

#resumeNetList li {
    padding: 5px 0;
    border-bottom: 1px solid var(--border);
}

#resumeNetList li:last-child {
    border-bottom: none;
}

#resumeNetList a {
    color: var(--accent-teal);
    text-decoration: none;
}

#resumeNetList a:hover {
    text-decoration: underline;
}

#resumeNetList li.endedNetItem a {
    color: var(--text-muted);
}

.mutedNote {
    color: var(--text-muted);
    font-size: 0.9em;
    margin: 0;
}

input[type="text"],
input[type="password"],
input[type="date"],
input[type="url"],
input[type="tel"],
input[type="number"],
select {
    background-color: var(--input-bg);
    border: 1px solid var(--border);
    color: var(--input-text);
    border-radius: 4px;
    padding: 6px;
    font-size: 1em;
    margin-right: 6px;
    font-family: inherit;
    box-sizing: border-box;
}

input[type="text"]::placeholder,
input[type="url"]::placeholder {
    color: var(--input-placeholder);
}

/* The native date picker's calendar-icon button otherwise renders with a
   light UA-default background, standing out against the dark input it
   sits inside. */
input[type="date"]::-webkit-calendar-picker-indicator {
    filter: invert(0.8);
}

/* Double the browser's default ~20-character width — this field now also
   accepts typed command tokens (callsign + .commands) after it, not just
   a bare callsign, so it needs room to show more at once. */
#callsignInput {
    width: 40ch;
    box-sizing: border-box;
}

button {
    background-color: #1b2439;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 6px 14px;
    margin-right: 6px;
    cursor: pointer;
    /* <button> doesn't inherit the body's font-family/font-size by default
       (browsers apply their own UA form-control font/size), which was
       making Print/Setup/Help (all <button>) render smaller and in a
       different font than a plain <a> like Crew Maintenance or the Scribe
       Log link, which do inherit from body. */
    font-family: inherit;
    font-size: inherit;
}

button:hover {
    border-color: var(--accent-teal);
}

button:disabled {
    opacity: 0.5;
    cursor: default;
}

/* The one shared "primary action" slot in #netSetupHeaderRow -- Start Net,
   End Net, and Submit all get the same amber treatment as #checkinButton
   (this app's other "the one obvious next step" action), so whichever of
   the three is currently visible reads as the same kind of button
   regardless of which net stage put it there. */
#startNetButton,
#endNetButton,
#submitNetButton,
#checkinButton,
#saveNetChangesButton {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
    border-color: var(--accent-amber);
    font-weight: 600;
}

#netSetupPanel label {
    margin-left: 6px;
    color: var(--text-muted);
}

#netSetupPanel label:first-of-type {
    margin-left: 0;
}

.formGrid2col {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px 16px;
    margin-top: 12px;
}

.formField {
    display: flex;
    flex-direction: column;
}

.formField label {
    margin-bottom: 4px;
    color: var(--text-muted);
    font-size: 0.85em;
}

.formField input[type="text"],
.formField input[type="date"],
.formField input[type="url"],
.formField input[type="tel"] {
    width: 100%;
    box-sizing: border-box;
    margin-right: 0;
}

/* Any "how many minutes" prompt (BA bottle duration, PAR interval, and any
   future one) -- 4 digits is already more than anyone would ever enter,
   so this stays a short, fixed-width field regardless of which container
   it sits in, rather than stretching to fill a .formField column's full
   width (that column-flex default is what made these balloon out to the
   row's entire width before this rule existed). 64px, not a literal 4ch --
   a plain 4ch left no room for the browser's own up/down spinner buttons
   on a number input, which ate the box and hid every digit behind them. */
.minutesInput {
    width: 64px;
    flex-shrink: 0;
}

.formFieldCheckbox {
    justify-content: center;
}

.formFieldCheckbox label {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 6px;
    margin-bottom: 0;
}

.formFieldCheckbox input[type="checkbox"] {
    width: auto;
    flex-shrink: 0;
}

#netStatusRow,
#setupStatusRow {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
    align-items: center;
    gap: 12px;
    margin-top: 4mm;
}

/* A real grid instead of a flex-wrap row — letting the browser decide
   wrap points put items in different spots depending on window width and
   made it impossible to keep e.g. Assigned Position directly under EOC
   status. grid-template-columns: auto sizes each column to its own widest
   cell, so column 1 (which holds every "checkbox + a second field"
   pairing — EOC Status/Assigned Position's template dropdowns, BA
   Control's duration field, Critical Alerts' Manage Contacts button) ends
   up meaningfully wider than columns 2/3 (single plain checkboxes) — by
   design, not a bug. A grid also never wraps a cell's content into a new
   row on its own the way flex-wrap did.

   Every item's cell is set explicitly below (grid-row/grid-column) rather
   than relying on DOM order + auto-placement — auto-placement fills a
   fixed column count strictly in source order, so removing/reordering any
   one item silently shifts every item after it into the wrong cell (this
   is exactly what had left BA Control's duration field in column 2 and
   Critical Alerts' whole row stuck in narrow column 3, both landing there
   purely as accidents of DOM order rather than deliberate placement).
   Explicit placement can't drift like that regardless of source order.

   Row 1: EOC status / Signal Reports / Viewing.
   Row 2: Assigned Position / Checked In column / Grid column.
   Row 3: Messaging / Resource Status / Show Repeaters map layer.
   Row 4: BA Control (+ duration) / Operational Period / Command Staff.
   Row 5: Critical Alerts (+ Manage Contacts) / Communications Plan / Task Board.
   Row 6: PAR interval / Mutual-Aid Requests / Keep editing private.
   Row 7: Skip Callsign Lookup, alone in column 1.
   Column 1 is reserved for every "checkbox + companion field/button" row
   (its own width accommodates that); columns 2/3 hold single plain
   checkboxes. This keeps Assigned Position directly under EOC status,
   Signal Reports directly above Checked In column, and every column-1 row
   sharing the same left edge. */
#netEditableStatusRow {
    display: grid;
    grid-template-columns: repeat(3, auto);
    align-items: center;
    gap: 12px;
    /* Without this, the template dropdown's focus ring touches the entry
       fields row directly above it — there was no vertical gap between the
       two rows at all before. Keeps the gap above it (from #netSetupForm)
       and below it (to #netStatusRow) visually equal. */
    margin-top: 4mm;
}

#netEocStatusRow { grid-row: 1; grid-column: 1; }
#netSignalReportsRow { grid-row: 1; grid-column: 2; }
#netViewingRow { grid-row: 1; grid-column: 3; }
#netAssignedPositionRow { grid-row: 2; grid-column: 1; }
#netCheckedInColumnRow { grid-row: 2; grid-column: 2; }
#netGridColumnRow { grid-row: 2; grid-column: 3; }
#netMessagingRow { grid-row: 3; grid-column: 1; }
#netResourceStatusRow { grid-row: 3; grid-column: 2; }
#netFrequencyStatusRow { grid-row: 3; grid-column: 3; }

/* Row 4 on: every one of these used to rely on grid auto-placement (no
   rule here at all) rather than an explicit cell, which is exactly the
   "silently shifts everything after it" drift this file's own comment
   warns about -- confirmed real when BA Control's paired duration field
   and Critical Alerts' paired button both ended up off in column 2/3
   instead of column 1 alongside their own checkbox, purely as a side
   effect of DOM order. Explicit placement for the rest of the grid too,
   not just rows 1-3, so nothing here can drift like that again. BA
   Control's duration field is folded into #netBaControlRow itself (one
   flex row, same shape as EOC Status/Assigned Position above) so it
   travels with its own checkbox into column 1 instead of landing
   elsewhere on its own. PAR interval was briefly folded into #netAlertsRow
   alongside its button too, on the theory that both were "checkbox +
   companion" rows that belonged in column 1 the same way -- reverted:
   crowding a checkbox + a second label/input + a button onto one row
   needed column 1 wider than columns 2/3 could spare without pushing
   several of *their* labels into wrapping that never happened before, and
   PAR has no real connection to Critical Alerts to begin with. PAR interval
   keeps its own plain, unpaired row instead (like #netMessagingRow), and
   #netAlertsRow just holds its own checkbox + button, matching BA
   Control's own "checkbox + one companion" shape. */
#netBaControlRow { grid-row: 4; grid-column: 1; }
#netOperationalPeriodRow { grid-row: 4; grid-column: 2; }
#netCommandStaffRow { grid-row: 4; grid-column: 3; }
#netAlertsRow { grid-row: 5; grid-column: 1; }
#netCommunicationsPlanRow { grid-row: 5; grid-column: 2; }
#netTaskAssignmentsRow { grid-row: 5; grid-column: 3; }
#netParIntervalMinutesRow { grid-row: 6; grid-column: 1; }
#netMutualAidRequestsRow { grid-row: 6; grid-column: 2; }
#netIsPrivateEditingRow { grid-row: 6; grid-column: 3; }
#netSkipQrzLookupRow { grid-row: 7; grid-column: 1; }

/* Unlike #netViewingRow/#netMessagingRow (a single label wrapping one
   checkbox), these rows hold a checkbox label PLUS a second label+dropdown
   (or, for BA Control/Critical Alerts, a label+number-input and/or a
   button) pair as siblings — needs its own flex context to sit them
   inline instead of stacking. #netParIntervalMinutesRow has no checkbox at
   all (still class="formField" in the markup, a plain label+input pair --
   this ID-scoped rule overrides that class's own column-flex direction,
   same "ID always outranks class regardless of source order" precedence
   already relied on elsewhere in this file), but gets the same flex-row
   treatment so its label+input sit inline like the others instead of
   stacked. */
#netEocStatusRow,
#netAssignedPositionRow,
#netBaControlRow,
#netAlertsRow,
#netParIntervalMinutesRow,
#defaultEocStatusRow {
    display: flex;
    /* Every other row in this group uses class="formFieldCheckbox", which
       never sets flex-direction, so it defaults to row on its own.
       #netParIntervalMinutesRow is still class="formField", which
       explicitly sets flex-direction:column -- display:flex above doesn't
       touch that property at all, so without this line that column
       direction kept winning regardless (the cascade resolves each CSS
       property independently; a higher-specificity rule only overrides a
       property it actually sets), stacking the label above the input
       instead of sitting them side by side like the others. */
    flex-direction: row;
    align-items: center;
    gap: 6px;
}

/* .formFieldCheckbox's shared justify-content:center is fine elsewhere
   (e.g. Setup modal rows), but here it centers each row's checkbox+label
   within its grid column's full stretched width -- since EOC's and
   Position's own content widths differ (Position's longer label + the
   20px gap above), centering gave them two different left offsets,
   throwing off the checkbox alignment column-1 rows are supposed to
   share with #netMessagingRow (a plain block, always left-aligned). Same
   reasoning applies to BA Control's duration field and Critical Alerts'
   button alongside their own checkboxes. #netParIntervalMinutesRow has no
   checkbox to left-anchor against, but margin-left:auto below still does
   the real positioning work regardless of this row's own justify-content
   -- included anyway for consistency with the rest of this group. */
#netEocStatusRow,
#netAssignedPositionRow,
#netBaControlRow,
#netAlertsRow,
#netParIntervalMinutesRow {
    justify-content: flex-start;
}

/* "Collect EOC status" and "Collect Assigned Position" (or whatever
   terminology preset relabels the latter — see AssignedPositionTerm)
   are different lengths, so left to their own natural width the two
   rows' "Template" labels wouldn't start at the same X even after the
   margin-left:auto fix below lines up the dropdowns' *right* edges.
   Giving both checkbox labels the same fixed width — sized to fit the
   longer of the two current default strings on one line — makes
   "Template" start at an identical position on both rows instead. Both
   rows share the same 6px gap now too (Position's old 20px override is
   gone) since that gap difference would otherwise reintroduce the same
   misalignment on its own. */
label[for="netEocStatusEnabledInput"],
label[for="netAssignedPositionEnabledInput"] {
    width: 205px;
    flex-shrink: 0;
}

/* Same "shared fixed width sized to the longer string" trick as EOC/
   Position's own labels just above, for the same reason: "BA full-bottle
   duration (minutes)" (~229px measured) and "PAR interval (minutes,
   0 = off)" (~210px) are different lengths, and BA Control's row has a
   checkbox pushing its own label's start position around while PAR's row
   has none -- without a shared width, the two labels' left edges (and
   therefore "PAR interval..." indent relative to "BA full-bottle...")
   wouldn't land in the same place even with both right-aligned via
   margin-left:auto below. 230px (a few px of headroom over the longer
   measured string) makes both labels start at an identical X regardless
   of which row has a checkbox before it, since margin-left:auto computes
   each row's leftover space the same way once the trailing label+input's
   own combined width is fixed and identical on both rows. */
label[for="netBaFullDurationMinutesInput"],
label[for="netParIntervalMinutesInput"] {
    width: 230px;
    flex-shrink: 0;
}

#netEocTemplateSelect,
#defaultEocTemplateSelect,
#netPositionTemplateSelect {
    max-width: 50mm;
    box-sizing: border-box;
}

/* Pushes each row's trailing label + control (or, for Critical Alerts,
   its Manage Alert Contacts button) to the right edge of its grid cell --
   Template dropdowns, BA Control's duration field, and PAR interval's own
   field this way, and #manageAlertContactsButton the same way just below,
   all landing flush with the same right edge. Same reasoning/pattern as
   label[for="defaultEocTemplateSelect"] below for the Setup modal's
   equivalent row. Scoped under #netSetupPanel (rather than the bare
   attribute selector) so its specificity (1,1,1) beats #netSetupPanel
   label's own margin-left:6px above (1,0,1) -- an ID selector always
   outranks an attribute selector regardless of source order, so the bare
   form of this rule was silently losing that fight. */
#netSetupPanel label[for="netEocTemplateSelect"],
#netSetupPanel label[for="netPositionTemplateSelect"],
#netSetupPanel label[for="netBaFullDurationMinutesInput"],
#netSetupPanel label[for="netParIntervalMinutesInput"] {
    margin-left: auto;
}

/* Critical Alerts has no second label+field, just its own button -- pushed
   to the same right edge directly, same effect as the label-based rule
   above achieves for the rows that do have one. */
#netAlertsRow #manageAlertContactsButton {
    margin-left: auto;
}

/* Stretches to consume whatever width the other two checkbox rows leave
   unused on this line, so the margin-left:auto below has real space to
   push against instead of just hugging its own checkbox text. */
#defaultEocStatusRow {
    flex: 1;
}

/* Pushes the EOC Template label + dropdown to the right edge of the
   Setup row, leaving the checkbox on the left. */
label[for="defaultEocTemplateSelect"] {
    margin-left: auto;
}

#startNetStatus {
    margin: 0;
}

/* #startNetStatus is the first flex child of #netStatusRow — even empty
   (no validation error to show), it still ate one "gap" of space before
   #netViewingRow, pushing "Allow others to view net" out of alignment with
   "Collect EOC status" directly above it in #netEditableStatusRow. :empty
   drops it out of flex flow entirely until JS actually puts a message in
   it, at which point it's no longer :empty and reappears automatically. */
#startNetStatus:empty {
    display: none;
}

#netMessagingRow label {
    margin-left: 0;
}

#importFileRow {
    margin-top: 12px;
}

#importFileRow label {
    color: var(--text-muted);
    font-size: 0.85em;
    margin-bottom: 4px;
    display: block;
}

.statusBadge-imported {
    background-color: var(--accent-blue);
}

#repeaterOrFrequencyInput,
#secondaryFrequencyInput,
#controllerCallsignInput {
    width: 90px;
}

/* Fixed mm-based gaps (an earlier attempt at this row) pushed the total
   content past the panel's width and forced a wrap — space-between only
   ever distributes whatever room is actually left over after the content's
   natural width, so it spreads the fields evenly across the row while
   guaranteeing everything still fits on one line. flex-wrap stays on purely
   as a safety net for an unusually narrow window; it won't engage at the
   panel's normal width. */
/* Now just a vertical stack of two panels (Mode, then Details) -- the
   flex-wrap field-row layout that used to live directly on this element
   moved to #netDetailsPanel below, once the Mode picker got pulled out
   into its own visually distinct panel. */
#netSetupForm {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 8px;
}

/* The primary choice, visually set apart from #netDetailsPanel below it --
   a subtle tint of this app's own accent color (not a new arbitrary hue)
   so it reads as "decide this first," especially once Details expands to
   show every individual checkbox for Custom. */
/* A second, separate "decide this first" above #netOperatingModePanel --
   amber rather than that panel's teal, so it doesn't read as part of the
   same choice. Deliberately not styled via .formFieldCheckbox (that class
   expects a wrapper div around a nested label, not a label carrying the
   class itself, same pattern Register.cshtml's own radio buttons use) --
   this is small enough to just style directly. */
#netExerciseFields {
    background-color: rgba(240, 168, 60, 0.1);
    border: 1px solid rgba(240, 168, 60, 0.4);
    border-radius: 8px;
    padding: 10px 14px;
    margin-bottom: 10px;
}

#netExerciseFields label {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--accent-amber);
    font-weight: 600;
    cursor: pointer;
}

#netOperatingModePanel {
    display: flex;
    align-items: center;
    gap: 8px;
    background-color: rgba(79, 209, 197, 0.08);
    border: 1px solid rgba(79, 209, 197, 0.3);
    border-radius: 8px;
    padding: 10px 14px;
}

#netOperatingModePanel select {
    flex: 1 1 auto;
    max-width: 320px;
}

/* Pushes the toggle to the panel's far right regardless of how much slack
   the capped-width select leaves behind — same margin-left:auto approach
   used elsewhere in this file to flush content to a row's right edge. */
#netModeExpandToggle {
    margin-left: auto;
    background: none;
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-muted);
    font-size: 1em;
    line-height: 1;
    padding: 6px 10px;
    cursor: pointer;
    transition: transform 0.15s ease;
}

#netModeExpandToggle:hover {
    border-color: var(--accent-teal);
    color: var(--text);
}

/* Same rotate-in-place expand/collapse language as #resumeNetIcon. */
#netModeExpandToggle[aria-expanded="true"] {
    transform: rotate(90deg);
}

/* Heading + Start Net on one row -- right-justified across from "Net
   Setup", freeing #netDetailsPanel below of its widest item so the
   remaining fields can shift right (see the label min-width rule below)
   to line up under #netOperatingModePanel's own fields. */
#netSetupHeaderRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

/* The field-row layout #netSetupForm itself used to have, now scoped to
   just this second panel since the Mode picker moved out into its own. */
#netDetailsPanel {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    /* Matches #netOperatingModePanel's own left inner padding (see below)
       so "Net Name" starts at the same X as "Operating Mode" above it. */
    padding-left: 14px;
    box-sizing: border-box;
}

#netDetailsPanel label {
    margin-left: 0;
    margin-right: 0;
    flex-shrink: 0;
    white-space: nowrap;
}

/* "Operating Mode" (the widest label in #netOperatingModePanel, itself
   right above this row) is longer than "Net Name" -- without this, Net
   Name's own shorter label would let its input start further left than
   the Operating Mode dropdown above it despite the matching padding just
   above. Widening only the first label to match reserves the same column
   width regardless of which field text happens to be shorter. */
/* Its own nested flex context (not just a plain inline span) so the label
   inside actually participates as a flex item -- min-width on a bare
   inline element like <label> is otherwise ignored entirely per the CSS
   spec; it only takes effect once flex layout blockifies the element. */
#netNameFields {
    display: inline-flex;
    align-items: center;
}

label[for="netNameInput"] {
    min-width: 126.25px;
    flex-shrink: 0;
}

#netDetailsPanel input[type="text"] {
    margin-right: 0;
    flex-shrink: 0;
}

#startNetButton {
    /* No longer an auto-margin push to the right — that would swallow all
       the row's free space before justify-content ever saw it, bunching
       every other field together with no gap at all. */
    margin-left: 0;
    margin-right: 0;
    flex-shrink: 0;
}

#activeNetRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

#activeNetInfo {
    margin: 0;
}

.statusBadge {
    display: inline-block;
    margin-left: 8px;
    padding: 2px 10px;
    border-radius: 12px;
    font-size: 0.75em;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--accent-amber-dark);
}

.statusBadge-active {
    background-color: var(--accent-teal);
}

.statusBadge-pending {
    background-color: var(--accent-yellow);
}

.statusBadge-finished {
    background-color: var(--accent-grey);
}

.statusBadge-paused {
    background-color: var(--accent-amber);
}

.statusBadge-ended {
    background-color: var(--accent-purple);
}

#netLifecycleButtons {
    display: flex;
    gap: 6px;
    flex-shrink: 0;
}

#editNetButton,
#endNetButton,
#submitNetButton,
#saveNetChangesButton {
    display: none;
    white-space: nowrap;
    margin-right: 0;
}

#rosterTable input[readonly] {
    opacity: 0.7;
    cursor: default;
}

/* position:relative here (not on #map) is the positioning anchor for
   #weatherConditionsWidget: the widget is a DOM sibling of #map, not a
   child (Google Maps owns #map's own subtree), so #map's own position
   can't establish a containing block for it. #mapRow's top-left corner
   always coincides with #map's (map is its first flex child, no padding,
   align-items:flex-start) even when #sideColumn is visible and widens
   the row, so left/top values computed relative to #map's own rect
   (see setupWeatherConditionsWidgetDrag/restoreWeatherConditionsWidgetPosition
   in netcontrol.js) still land in the right place. */
#mapRow {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 12px;
}

/* Fills whatever room #sideColumn's fixed 220px leaves it. #sideColumn's
   status tiles are always present now (calm "OK" state included, see
   .statusTile), so this width is stable rather than reflowing as panels
   used to appear/disappear. */
#map {
    flex: 1 1 auto;
    min-width: 0;
    height: 500px;
    border: 1px solid var(--border);
    border-radius: 8px;
}

#sideColumn {
    display: flex;
    flex-direction: column;
    gap: 12px;
    width: 220px;
    flex-shrink: 0;
}

/* Collapses once every tile inside is hidden (see updateSideColumnVisibility
   in netcontrol.js), so #map's flex:1 can reclaim the width instead of an
   empty column sitting there. */
#sideColumn.empty {
    display: none;
}

/* Below ~768px (phones, and narrow tablets in portrait), #map + #sideColumn
   side by side leaves too little width for either -- #sideColumn's fixed
   220px never shrinks (flex-shrink:0), squeezing the map into an unusable
   sliver and forcing an awkward nested horizontal scrollbar (inside
   #mapRow) instead of the page just reflowing normally. Stack them
   vertically instead: full-width map, full-width side panel(s) below it.
   align-items:flex-start on #mapRow means the cross-axis (width, once
   stacked into a column) doesn't auto-stretch, so both children need an
   explicit 100% override here. */
@media (max-width: 768px) {
    #mapRow {
        flex-direction: column;
    }

    #map,
    #sideColumn {
        width: 100%;
    }
}

/* Uniform-height status tile -- title + one urgency-colored summary line,
   click/Enter opens the matching detail modal (see wireStatusTileClick).
   Color modifiers reuse the same site-wide accent tokens the old
   per-panel colors already used, just applied consistently instead of
   each panel picking its own. Hidden entirely until there's something to
   flag (each render function sets its own tile's `hidden` attribute) --
   an empty tile still costs map width for nothing, and a tile that only
   appears when it matters doubles as its own attention-getter. */
.statusTile {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 2px;
    height: 60px;
    box-sizing: border-box;
    border-radius: 8px;
    padding: 8px 14px;
    cursor: pointer;
}

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

/* Explicit -- otherwise [hidden]'s UA-stylesheet display:none and this
   class's own display:flex are equal specificity, so source order alone
   would decide which one wins. */
.statusTile[hidden] {
    display: none;
}

.statusTileTitle {
    font-size: 0.75em;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    opacity: 0.85;
}

.statusTileSummary {
    font-weight: 600;
}

.statusTile--ok {
    border: 1px solid var(--accent-green);
    background-color: #16281c;
    color: var(--accent-green);
}

.statusTile--warn {
    border: 1px solid var(--accent-amber);
    background-color: #2a2013;
    color: var(--accent-amber);
}

.statusTile--alert {
    border: 1px solid var(--accent-red);
    background-color: #2a1613;
    color: var(--accent-red);
}

#parNoContactList {
    color: var(--accent-amber);
    /* No bullet/indent -- reclaims horizontal space for the entry text
       itself, which can already run long (asset name + crew count). */
    list-style: none;
    padding-left: 0;
    margin: 0;
}

#unknownLocationsModalTitle {
    color: var(--accent-red);
}

#parNoContactModalTitle {
    color: var(--accent-amber);
}

/* Teal rather than green/amber/red -- an incoming message isn't inherently
   good or bad news the way the other three tiles' conditions are, so it
   doesn't fit the ok/warn/alert vocabulary. Used whenever there's at
   least one message; .statusTile--ok covers the empty state. */
.statusTile--info {
    border: 1px solid var(--accent-teal);
    background-color: #0f2422;
    color: var(--accent-teal);
}

#messagesModalTitle {
    color: var(--accent-teal);
}

#messagesList {
    max-height: 244px;
    overflow-y: auto;
}

.messageLine {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 6px;
    padding: 4px 0;
    border-bottom: 1px solid var(--border);
    font-size: 0.85em;
}

.messageLine:last-child {
    border-bottom: none;
}

.messageContent {
    flex: 1 1 auto;
    min-width: 0;
    word-break: break-word;
}

/* Small checkmark at the right edge of each message's own line --
   acknowledges just that one message (acknowledgeMessage in netcontrol.js),
   never a bulk/whole-panel action. The line itself stays in the list even
   after acknowledging (see .messageLine--acknowledged below) -- the modal
   always shows full history, only the Messages tile's own count changes. */
.messageAckButton {
    flex: 0 0 auto;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0 2px;
    font-size: 1em;
    line-height: 1.4;
    color: var(--text-muted);
}

.messageAckButton:hover {
    color: var(--accent-green);
}

/* Already acknowledged -- dimmed, and its own checkmark turns solid green
   and inert (disabled, see buildMessageLine) rather than a clickable muted
   one, so a glance down the full-history list tells handled apart from
   still-pending. */
.messageLine--acknowledged {
    opacity: 0.55;
}

.messageAckButton:disabled {
    color: var(--accent-green);
    cursor: default;
}

/* "Unacknowledged"/"Acknowledged" section divider (buildMessageGroupLabel
   in netcontrol.js) -- Unacknowledged group always renders first/on top,
   each group newest-first within itself. Omitted entirely when a group is
   empty (see renderMessages), so there's never a label sitting over
   nothing. */
.messageGroupLabel {
    font-size: 0.75em;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-muted);
    padding: 6px 0 2px;
}

.messageGroupLabel:first-child {
    padding-top: 0;
}

.messageTime {
    color: var(--text-muted);
    margin-right: 6px;
}

.messageSender {
    color: var(--accent-teal);
    font-weight: 600;
}

table {
    border-collapse: collapse;
    width: 100%;
    max-width: 600px;
}

/* Scopes any horizontal overflow to just the table itself instead of the
   whole page -- #rosterTable's content (multiple action buttons, signal/
   status columns, etc.) can be wider than a phone screen even though the
   table is set to width:100%, since table columns still size to their
   content's natural minimum. On a desktop-width screen this wrapper is
   completely inert (nothing to scroll, no visible change at all); on a
   narrow screen it turns an awkward whole-page sideways scroll into an
   expected, contained swipe-to-scroll on just the table. */
.rosterTableScroll {
    overflow-x: auto;
}

#rosterTable {
    width: 100%;
    min-width: 100%;
    max-width: none;
}

th, td {
    border: 1px solid var(--border);
    padding: 6px 10px;
    text-align: left;
}

#unknownList {
    color: var(--accent-red);
}

#rosterTable button {
    padding: 2px 6px;
    margin-right: 4px;
    cursor: pointer;
}

#rosterTable .rosterCallsignInput {
    width: 90px;
    padding: 4px;
    font-size: 1em;
}

#rosterTable .rosterNameInput {
    width: 140px;
    padding: 4px;
    font-size: 1em;
}

/* Operational Period panel -- objectives textarea matches the width of
   its own panel rather than the browser's small default; role/person
   inputs fill their table cell so the two columns actually line up. */
#operationalPeriodObjectivesInput {
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: 1em;
    resize: vertical;
}

/* table-layout: fixed is what actually makes the inputs' own width:100%
   below take effect -- without it, auto layout sizes each column to fit
   its content's intrinsic width (roughly the placeholder text), which is
   exactly what was overflowing the modal even after widening it. The
   last (delete-button) column gets a small fixed width instead of an
   equal fixed-layout share, so Role/Person keep most of the room. */
#operationalPeriodRolesTable {
    width: 100%;
    table-layout: fixed;
}

#operationalPeriodRolesTable th:last-child,
#operationalPeriodRolesTable td:last-child {
    width: 32px;
}

#operationalPeriodRolesTable .operationalPeriodRoleNameInput,
#operationalPeriodRolesTable .operationalPeriodPersonNameInput,
#communicationsPlanTable .commsPlanFunctionInput,
#communicationsPlanTable .commsPlanSourceSelect,
#communicationsPlanTable .commsPlanTacticalSelect,
#communicationsPlanTable .commsPlanGmrsSelect,
#communicationsPlanTable .commsPlanOtherInput,
#communicationsPlanTable .commsPlanRepeaterSearch input,
#communicationsPlanTable .commsPlanNotesInput {
    width: 100%;
    box-sizing: border-box;
    padding: 4px;
    font-size: 1em;
}

/* Other/Simplex's free-text field needs to stay usable even when the
   table's other columns squeeze this one -- floor it at roughly 10
   characters wide regardless. */
#communicationsPlanTable .commsPlanOtherInput {
    min-width: 10ch;
}

/* The real cause of the modal's "wasted" right-side space: the base
   `table` rule below caps every table at max-width:600px, which was
   silently shrinking this table well inside the modal regardless of
   column widths. This table gets its own modal-sized cap instead --
   800px, matching #communicationsPlanModal .modalBox's own width
   exactly (that width is a content-box width, i.e. already excludes
   the modal's own padding/border, so the table's available space is
   the full 800px, not 800 minus the modal's padding -- an earlier
   752px cap here undercounted by that padding and was most of the
   ~20mm gap this table's rows were left with on the right).

   table-layout:fixed + percentage column widths (rather than px, and
   rather than the default auto layout) -- auto layout was redistributing
   any surplus width across columns unpredictably (Source/Channel kept
   inflating past their requested px width while Function shrank to a
   few clipped characters, even after widening the modal). Percentages
   scale consistently with the table's actual rendered width whether
   that's the full modal or a narrower viewport's 90vw-clamped one. */
#communicationsPlanTable {
    table-layout: fixed;
    max-width: 800px;
}

#communicationsPlanTable th:nth-child(1),
#communicationsPlanTable td:nth-child(1) {
    width: 23%;
}

/* Widened -- "Other/Simplex", the longest option, was clipping its last
   character at the previous 20%. */
#communicationsPlanTable th.commsPlanSourceHeader,
#communicationsPlanTable .commsPlanSourceCell {
    width: 22%;
}

#communicationsPlanTable th.commsPlanChannelHeader,
#communicationsPlanTable .commsPlanChannelCell {
    width: 34%;
}

#communicationsPlanTable th:nth-child(4),
#communicationsPlanTable td:nth-child(4) {
    width: 14%;
}

/* Just the delete "X" button lives here -- the previous 10% left it
   floating in a lot of empty space that was really the missing width
   from the max-width bug above; slimmed down now that Function/Source
   have somewhere real to grow into instead. */
#communicationsPlanTable th:nth-child(5),
#communicationsPlanTable td:nth-child(5) {
    width: 7%;
}

/* "Selected: VA7AHR — Sorrento, 147.14" + a small pencil-icon Change
   button, shown instead of the search box once a repeater is picked. */
.commsPlanRepeaterSelected {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 4px 0;
}

.commsPlanRepeaterSelected span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.commsPlanRepeaterChangeBtn {
    flex-shrink: 0;
    padding: 2px 6px;
    line-height: 1;
}

.commsPlanRepeaterSearch {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

/* Explicit -- otherwise [hidden]'s UA-stylesheet display:none and these
   classes' own display:flex are equal specificity, so source order alone
   would decide which one wins (same bug as .statusTile[hidden] and
   .coordinatesModalFormatOverrideRow[hidden] elsewhere in this file) --
   without this, picking a repeater set repeaterSearchWrap.hidden = true
   in netcontrol.js but the search box stayed visually open regardless. */
.commsPlanRepeaterSelected[hidden],
.commsPlanRepeaterSearch[hidden] {
    display: none;
}

/* Reuses .addNetResourceResults' own list styling (max-height/overflow-y
   scroll, flex layout) -- this trims that shared list down to fit inside
   a table cell instead of a full modal section. #communicationsPlanTable-
   scoped (not just the bare class) so it reliably out-specificities
   .addNetResourceResults' own max-height regardless of which rule
   happens to come later in this file. */
#communicationsPlanTable .commsPlanRepeaterResults {
    max-height: 20vh;
}

/* Notes button reflects whether this row already has a note (bold +
   accent color) vs. still empty (plain), so it's scannable at a glance
   which rows have something written without opening every one. */
.commsPlanNotesBtn--set {
    font-weight: 700;
    color: var(--accent-teal);
}

/* Single-line notes editor expands directly below its row (see
   toggleCommsPlanNotesEditor in netcontrol.js), spanning only the
   Function/Source/Channel columns -- not the Notes button or delete
   column -- so it reads as an extension of those three fields. */
.commsPlanNotesExpandRow td {
    /* Horizontal padding matches the base th/td rule (10px) so the notes
       input's left edge lines up exactly with the Function field above
       it -- only vertical padding differs (8px vs 6px) for a bit more
       breathing room around the expanded input. No background-color
       here (that was previously making the whole cell's light area
       start at the column edge, well left of where Function's own
       input box starts) -- the input itself already gets its light
       background from the shared input[type="text"] rule, same as
       every other field in this table. */
    padding: 8px 10px;
}

/* This input opens already-focused (see toggleCommsPlanNotesEditor), and
   the browser's default focus ring looked like a stray black outline
   sitting inside the field -- suppressed here only, not on inputs
   generally, since this is the one field in the app that auto-focuses
   itself on open rather than from a deliberate click/tab. */
.commsPlanNotesInput:focus {
    outline: none;
}

#operationalPeriodActions {
    display: flex;
    gap: 8px;
    margin-top: 8px;
}

#aprsModalActions {
    display: flex;
    gap: 8px;
    margin-top: 20px;
}

/* Wider than the default 340px .modalBox -- the UTM row's value ("31N
   303189mE 5787193mN") was wrapping onto two lines past the label/copy-
   button chrome at the default width. */
#coordinatesModal .modalBox {
    width: 440px;
}

.coordinatesModalRows {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 12px;
}

.coordinatesModalRow {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 8px;
    border: 1px solid var(--border);
    border-radius: 6px;
}

.coordinatesModalLabel {
    width: 90px;
    flex-shrink: 0;
    color: var(--text-muted);
}

.coordinatesModalValue {
    flex-grow: 1;
    font-family: monospace;
    font-size: 1.05em;
}

.coordinatesModalCopyBtn {
    flex-shrink: 0;
}

.coordinatesModalEntryRow {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* Overrides .formField input[type="text"]'s own width:100% (higher
   specificity otherwise wins) so the input shares the row with Parse
   instead of pushing it onto its own line. */
.coordinatesModalEntryRow input[type="text"] {
    width: auto;
    flex: 1;
}

.coordinatesModalEntryRow button {
    flex-shrink: 0;
}

.coordinatesModalDetectedFormat {
    display: block;
    margin-top: 6px;
    color: var(--text-muted);
    font-size: 0.9em;
}

.coordinatesModalEntryError {
    color: var(--accent-red);
    font-size: 0.9em;
    margin: 6px 0 0;
}

.coordinatesModalFormatOverrideRow {
    display: flex;
    gap: 6px;
    margin-top: 4px;
}

/* Needed alongside the rule above -- a class selector and the browser's
   own [hidden]{display:none} UA rule have equal specificity, and this
   author stylesheet loads after the UA one, so display:flex was silently
   winning over the hidden attribute regardless of the JS toggling it.
   This [hidden] variant has higher specificity (class + attribute) so it
   correctly wins instead. */
.coordinatesModalFormatOverrideRow[hidden] {
    display: none;
}

.coordinatesModalFormatOverrideBtn {
    font-size: 0.85em;
    padding: 4px 8px;
}

/* Roster row's single collapsed-actions trigger -- replaces the 4-5
   always-visible icon buttons (Clear Location/Set on Map/APRS/Delete)
   that used to crowd this cell; see toggleRosterRowActionsMenu.
   #rosterTable-prefixed (not just .rosterActionsToggleBtn) since
   #rosterTable button's own padding rule otherwise wins on specificity
   (ID beats a bare class). */
#rosterTable .rosterActionsToggleBtn {
    font-weight: 700;
    padding: 2px 10px;
}

.recheckBadge {
    display: inline-block;
    margin-left: 4px;
    padding: 1px 6px;
    border-radius: 8px;
    font-size: 0.75em;
    font-weight: 700;
    background-color: var(--accent-purple);
    color: var(--bg);
    vertical-align: middle;
}

.rosterDragCell {
    width: 24px;
    padding: 6px 4px;
    text-align: center;
}

.rosterDragHandle {
    cursor: grab;
    color: var(--text-muted);
    font-size: 1.1em;
    user-select: none;
}

.rosterDragHandle[draggable="false"] {
    cursor: default;
    opacity: 0.3;
}

/* The most recent brand-new check-in (not a re-check) — lets a sighted
   operator spot it without hunting through the roster. */
.rosterDragHandle.lastAdded {
    background-color: var(--accent-blue);
    color: var(--bg);
    border-radius: 4px;
    padding: 2px 4px;
}

/* Marks the row currently armed via F1/F2/F3/Shift+F3 — the same "what's
   selected" cue a screen reader gets from the command field's own text,
   surfaced visually for sighted operators too. Declared after .lastAdded
   so it wins if a row is ever both (equal specificity, later wins). */
.rosterDragHandle.armed {
    background-color: var(--accent-purple);
    color: var(--bg);
    border-radius: 4px;
    padding: 2px 4px;
}

#rosterTable tr.dragging {
    opacity: 0.4;
}

/* Static, not blinking -- matches #unknownPanel's own "color cue, no
   animation" precedent. Reflects only the most recently closed PAR (see
   OnGetCheckInsAsync); clears itself automatically once that same person
   is reached and marked in a later roll call. */
#rosterTable tr.parNoContactRow {
    border-left: 3px solid var(--accent-amber);
    background-color: rgba(240, 168, 60, 0.1);
}

/* Marks the 🎯 button currently waiting for a map click to place that
   station's location — stays lit for the whole click-pin-then-click-map
   flow (not just while it has native focus) so a poll-driven roster
   refresh landing in between can't make it ambiguous which station is
   being relocated. Cleared on Esc or once the location is set. */
/* Set on Map is now triggered from the roster row's "⋯" flyout rather
   than its own always-visible button, so this highlights the flyout
   trigger itself while a placement is armed (see startPlacingLocation's
   buttonEl in netcontrol.js). */
.rosterActionsToggleBtn.placingActive {
    border: 2px solid var(--accent-green);
    box-shadow: 0 0 0 2px var(--accent-green);
}

/* Sighted-only, drag-to-reorder major page panels (see #panelsContainer) —
   same visual language as the roster's own row drag handle. */
.panelHeader {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}

.panelDragHandle {
    cursor: grab;
    color: var(--text-muted);
    font-size: 1.2em;
    user-select: none;
}

.panelTitle {
    margin: 0;
}

#panelsContainer > section.draggingPanel {
    opacity: 0.4;
}

/* Keeps the Map/Roster/Resource panels out of a new user's way until a net
   actually exists — CSS-only (not @if) so #map/#rosterTable etc. stay in
   the DOM for netcontrol.js's init code, just visually absent. Applied per-
   section (see NetControl.cshtml) rather than once on #panelsContainer, so
   #checkinPanel can share that container without inheriting the hiding. */
.hiddenUntilNetStarted {
    display: none;
}

.signalReportBtn {
    font-size: 0.8em;
    padding: 3px 8px;
    white-space: nowrap;
    border-color: transparent;
}

.signalReportBtn-untested {
    background-color: #3a4258;
    color: var(--text);
}

.signalReportBtn-noContact {
    background-color: var(--accent-red);
    color: var(--bg);
    font-weight: 600;
}

.signalReportBtn-reported {
    background-color: var(--accent-green);
    color: var(--bg);
    font-weight: 600;
}

.eocStatusBtn {
    font-size: 0.9em;
    white-space: nowrap;
    border-color: transparent;
    background-color: #3a4258;
}

/* #rosterTable button (site.css elsewhere) sets padding: 2px 6px with
   higher specificity than the plain .eocStatusBtn class, which would
   otherwise win by source order — pinned explicitly here so the button's
   left edge lines up with the header's, with zero horizontal padding of
   its own left over: all spacing between/before glyphs comes from .eocSlot's
   fixed width instead, which is what actually needs to match the header. */
#rosterTable button.eocStatusBtn {
    padding: 2px 0;
}

/* Shared by both the per-row status button (each glyph in its own .eocSlot)
   and the column header's letter legend (see renderEocStatusHeaderLetters)
   — giving every slot the exact same fixed width, regardless of font
   metrics for any particular emoji or letter, is what actually guarantees
   the header letters line up with their matching row glyphs. Deliberately
   a fixed px value, not em — the header text renders at a smaller
   font-size than the button, so "the same" em width would actually resolve
   to a smaller absolute pixel width there, drifting the two further apart
   with every subsequent slot (caught via direct measurement, not assumed). */
.eocSlot {
    display: inline-block;
    width: 20px;
    text-align: center;
    /* The header letters are plain <span>s (no click handler, just a
       title tooltip naming the question) — cursor:pointer is purely a
       hover affordance so hovering signals "there's more text here",
       matching every other icon with a tooltip in this app. Harmless on
       the row button's own glyphs too, which already show a pointer
       cursor as a <button>. */
    cursor: pointer;
}

.eocStatusHeaderLetters {
    display: flex;
    margin-top: 2px;
    font-size: 0.75em;
    font-weight: 700;
    color: var(--text-muted);
}

/* A Choice category's answered-state glyph -- a circle (colored per the
   admin's own per-option choice, see EOC_CHOICE_COLOR_OPTIONS) with that
   option's abbreviation's first letter, since arbitrary admin-defined
   choice text has no natural single emoji the way Color/YesNo answers do.
   .eocSlot has no explicit height (it just relies on its emoji glyphs' own
   natural line height), so height is set explicitly here to match its 20px
   width and keep this badge visually flush with the neighboring emoji slots. */
.eocSlot-choice-teal,
.eocSlot-choice-amber,
.eocSlot-choice-red,
.eocSlot-choice-yellow,
.eocSlot-choice-green,
.eocSlot-choice-purple,
.eocSlot-choice-blue,
.eocSlot-choice-grey {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 20px;
    border-radius: 50%;
    color: var(--bg);
    font-weight: 700;
    font-size: 0.75em;
}

.eocSlot-choice-teal { background-color: var(--accent-teal); }
.eocSlot-choice-amber { background-color: var(--accent-amber); }
.eocSlot-choice-red { background-color: var(--accent-red); }
.eocSlot-choice-yellow { background-color: var(--accent-yellow); }
.eocSlot-choice-green { background-color: var(--accent-green); }
.eocSlot-choice-purple { background-color: var(--accent-purple); }
.eocSlot-choice-blue { background-color: var(--accent-blue); }
.eocSlot-choice-grey { background-color: var(--accent-grey); }

.modalOverlay {
    display: none;
    position: fixed;
    inset: 0;
    background-color: rgba(0, 0, 0, 0.6);
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

/* Higher than every other .modalOverlay AND higher than .mapContextMenu's
   own 1001 (see that rule's comment) -- #customConfirmModal can open on
   top of an already-open modal (Take PAR's submit confirmation appears
   while #parModal is still open underneath it), and same-z-index stacking
   otherwise falls back to DOM order, which would have painted it behind
   #parModal (defined later in the page). Always topmost, matching how a
   native confirm() always sits above everything else. */
#customConfirmModal {
    z-index: 1002;
}

.modalOverlay.visible {
    display: flex;
}

.modalBox {
    background-color: var(--panel-bg);
    /* Every popup gets this same white border -- was previously only
       applied to a hand-maintained list of "settings-style" modals
       (Setup/Import Roster/Signal Report/EOC Status/Help/EOC Template),
       which silently left out Position Maintenance, Add Operator, Drop
       Waypoint, the Missing Persons match popups, and the three modules'
       Notes popups. Living on the base rule means any future modal gets
       it automatically, no allowlist to remember to update. */
    border: 2px solid #ffffff;
    border-radius: 10px;
    padding: 20px 24px;
    width: 340px;
    max-width: 90vw;
}

#importRosterModal .modalBox {
    width: 480px;
}

/* Default .modalBox width (340px) + ~50mm -- messages tend to run long
   enough (sender + text) that the default width wrapped most lines
   awkwardly. */
#messagesModal .modalBox {
    width: 529px;
}

#setupModal .modalBox {
    width: 600px;
}

#eocStatusModal .modalBox {
    width: 420px;
}

#parModal .modalBox {
    width: 480px;
    max-height: 80vh;
    overflow-y: auto;
}

#eocTemplateModal .modalBox {
    width: 520px;
    max-height: 80vh;
    overflow-y: auto;
}

/* Both hold a Role/Person table (two text inputs + a delete button per
   row) -- the default 340px .modalBox truncates the inputs and cramps
   the delete column. max-height/overflow-y matches every other
   multi-row-table modal (e.g. #eocTemplateModal above) since the roles
   list can grow past one screen's height via repeated "+ Add Role". */
#operationalPeriodModal .modalBox,
#communicationsPlanModal .modalBox,
#netTaskAssignmentsModal .modalBox,
#netTaskEntryModal .modalBox,
#netMutualAidModal .modalBox,
#netMutualAidEntryModal .modalBox,
#mutualAidEntryModal .modalBox {
    max-height: 80vh;
    overflow-y: auto;
}

/* Same 4-status-button width need as the standalone page's own copy. */
#netTaskEntryModal .modalBox {
    width: 760px;
}

/* Widened 20mm (~76px) from its original 720px so the table below has
   more room to give the Task column, per user request. */
#netTaskAssignmentsModal .modalBox {
    width: 796px;
}

/* Matches #rosterTable/#taskTable's own override -- without this, the
   base `table { max-width: 600px }` rule caps this table well inside the
   modal regardless of the modal's own (wider) width, same "wasted space"
   bug already found and fixed on Communications Plan and the standalone
   Task Assignments page. table-layout:fixed + percentage columns for the
   same reason those two needed it: auto layout redistributes surplus
   width unpredictably instead of respecting each column's intended
   share. Task gets the largest share (extra space from both the 20mm
   widening and the Actions column shrinking down to two icon buttons
   goes entirely to it). */
#netTaskAssignmentsTable {
    width: 100%;
    min-width: 100%;
    max-width: none;
    table-layout: fixed;
}

#netTaskAssignmentsTable th, #netTaskAssignmentsTable td {
    overflow-wrap: anywhere;
}

#netTaskAssignmentsTable th:nth-child(1), #netTaskAssignmentsTable td:nth-child(1) { width: 10%; }
#netTaskAssignmentsTable th:nth-child(2), #netTaskAssignmentsTable td:nth-child(2) { width: 14%; }
#netTaskAssignmentsTable th:nth-child(3), #netTaskAssignmentsTable td:nth-child(3) { width: 37%; }
#netTaskAssignmentsTable th:nth-child(4), #netTaskAssignmentsTable td:nth-child(4) { width: 14%; }
#netTaskAssignmentsTable th:nth-child(5), #netTaskAssignmentsTable td:nth-child(5) { width: 16%; }
#netTaskAssignmentsTable th:nth-child(6), #netTaskAssignmentsTable td:nth-child(6) { width: 9%; }

#netTaskAssignmentsTable td:nth-child(6) {
    white-space: nowrap;
}

/* The base `button` rule's own padding:6px 14px/margin-right:6px is sized
   for text-labeled buttons -- these are single-icon (pencil/✕), so it was
   both making the Edit pencil look oversized/unevenly spaced next to
   Delete (the pencil+variation-selector glyph already renders wider than
   a plain "✕", and the generic 14px side padding exaggerated that further)
   and, combined, wide enough that Delete overflowed past the column/table
   border. Same compact override #rosterTable's own icon-style buttons
   already use. Trailing margin dropped on the last button in the cell --
   nothing follows it, so it was just adding unused width. */
#netTaskAssignmentsTable td:nth-child(6) button {
    padding: 2px 6px;
    margin-right: 4px;
}

#netTaskAssignmentsTable td:nth-child(6) button:last-child {
    margin-right: 0;
}

#netTaskAssignmentsTable .taskStatusSelect {
    width: 100%;
    box-sizing: border-box;
    padding: 4px;
    font-size: 1em;
}

/* Mutual-Aid Requests -- same modal/table width standard as the
   Task/Assignment Board immediately above (both entry modals need room for
   a side-by-side field row; both list modals need room for a wide table
   without triggering the shared 600px table cap). */
#netMutualAidEntryModal .modalBox {
    width: 760px;
}

/* Widened 15mm (~57px) from its original 796px -- the Requested time
   column was wrapping ("13:52:4" / "3") and the Status quick-select was
   getting clipped at its old 14% share, per user report. */
#netMutualAidModal .modalBox {
    width: 853px;
}

#netMutualAidTable {
    width: 100%;
    min-width: 100%;
    max-width: none;
    table-layout: fixed;
}

#netMutualAidTable th, #netMutualAidTable td {
    overflow-wrap: anywhere;
}

#netMutualAidTable .mutualAidNumberHeader, #netMutualAidTable td:nth-child(1) { width: 5%; }
/* Widened from 10% to 12% (the extra room from the modal widening above,
   plus a point each borrowed from Resource/ETA below) so "hh:mm:ss" never
   wraps onto a second line. */
#netMutualAidTable .mutualAidTimeHeader, #netMutualAidTable td:nth-child(2) { width: 12%; }
/* Resource gets the largest share -- it's the one field net control
   actually reads at a glance, everything else here is supporting metadata,
   same reasoning as the Task Description column above. */
#netMutualAidTable .mutualAidResourceHeader, #netMutualAidTable td:nth-child(3) { width: 23%; }
#netMutualAidTable .mutualAidPriorityHeader, #netMutualAidTable td:nth-child(4) { width: 10%; }
#netMutualAidTable .mutualAidFromHeader, #netMutualAidTable td:nth-child(5) { width: 15%; }
/* Widened from 14% to 16% so the widest option in the quick-status
   <select> ("En Route"/"Cancelled") stops getting clipped. */
#netMutualAidTable .mutualAidStatusHeader, #netMutualAidTable td:nth-child(6) { width: 16%; }
#netMutualAidTable .mutualAidEtaHeader, #netMutualAidTable td:nth-child(7) { width: 10%; }
#netMutualAidTable .mutualAidActionsHeader, #netMutualAidTable td:nth-child(8) { width: 9%; }

#netMutualAidTable td:nth-child(8) {
    white-space: nowrap;
}

#netMutualAidTable td:nth-child(8) button {
    padding: 2px 6px;
    margin-right: 4px;
}

#netMutualAidTable td:nth-child(8) button:last-child {
    margin-right: 0;
}

#netMutualAidTable .taskStatusSelect {
    width: 100%;
    box-sizing: border-box;
    padding: 4px;
    font-size: 1em;
}

/* Same 6-color-tier convention as .taskStatusBadge above, one color per
   Mutual-Aid status. Cancelled reuses the grey "closed, ignore" tone
   (matches Requested's neutral starting tone rather than Denied's red, to
   keep "someone said no" visually distinct from "we withdrew it"). */
.mutualAidStatusBadge {
    display: inline-block;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.85em;
    font-weight: 600;
    white-space: nowrap;
}
.mutualAidStatusBadge-requested { background-color: var(--accent-grey); color: var(--bg); }
.mutualAidStatusBadge-approved { background-color: var(--accent-blue); color: var(--bg); }
.mutualAidStatusBadge-enroute { background-color: var(--accent-amber); color: var(--bg); }
.mutualAidStatusBadge-arrived { background-color: var(--accent-green); color: var(--bg); }
.mutualAidStatusBadge-denied { background-color: var(--accent-red); color: var(--bg); }
.mutualAidStatusBadge-cancelled { background-color: var(--accent-grey); color: var(--bg); }

/* Wider than Command Staff's 480px -- four data columns (Function/Source/
   Channel/Notes) plus the delete column need more room than a plain
   Role/Person pair. Widened again alongside the Source/Channel column
   widths below -- without the extra room here, Function was left with
   only ~86px and its placeholder text was clipping down to a few
   characters. */
#communicationsPlanModal .modalBox {
    width: 800px;
}

/* Wider still than Communications Plan -- the Phone column alone packs an
   input, a "Send code" button, a code input, and a "Confirm" button on one
   nowrap line (see .repeaterMaintenanceTable's own white-space:nowrap),
   which overflowed the default 340px modalBox entirely (spilling out over
   the page behind it, not just clipping) before this override existed. */
#criticalAlertsModal .modalBox {
    width: 860px;
    max-height: 85vh;
    overflow-y: auto;
}

/* Wider than the old flat-table version -- a position's header row can
   carry up to 3 buttons (Co-Lead/Sub-Position/Remove) alongside its type
   input, and each assignment row carries a name input, an agency
   <select>, a free-text fallback, and a remove button. Sized up again
   40mm/50mm (~151px/189px at 96dpi) past that first pass per direct
   request -- own max-height rule (not the shared 80vh block above) so
   only this modal grows taller, not every other one in that list. */
#commandStaffModal .modalBox {
    width: 791px;
    max-height: calc(80vh + 189px);
    overflow-y: auto;
}

#commandStaffPositionTypeModal .modalBox {
    width: 420px;
}

/* Title on the left, Print top-right -- same 🖨 icon/label as the site's
   main Print button, just scoped to this one modal (prints ICS-207
   directly, no need to close the modal and go find it in the main Print
   menu). */
.commandStaffModalHeader {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 4px;
}

.commandStaffModalHeader .panelTitle {
    margin: 0;
}

#commandStaffTree {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 10px 0;
}

.commandStaffPositionNode {
    border-left: 2px solid var(--border);
    padding-left: 10px;
}

.commandStaffPositionHeader {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: nowrap;
    margin-bottom: 4px;
}

.commandStaffPositionTypeInput {
    font-weight: 600;
    flex: 1 1 160px;
    min-width: 90px;
}

/* Buttons never wrap (their own text, or onto a second line) -- the type
   input shrinks first instead, same "widen/shrink the layout, don't let a
   button wrap" rule this app applies everywhere else. */
.commandStaffPositionHeader button,
.commandStaffAssignmentRow button {
    flex-shrink: 0;
    white-space: nowrap;
}

.commandStaffAssignmentsList {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin: 4px 0 8px 0;
}

.commandStaffAssignmentRow {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: nowrap;
}

/* Pale yellow, distinct from both the plain --input-bg every other text
   field uses and the Position Type field right above it -- a real point
   of confusion earlier (a position's name typed into what was thought to
   be a person-name field, and vice versa) is now visible at a glance:
   yellow always means "this is an assigned person's name," nothing else
   in this modal is styled that way. */
input.commandStaffAssignmentNameInput {
    flex: 1 1 120px;
    min-width: 100px;
    background-color: #fdf6b2;
    color: var(--input-text);
}

.commandStaffAssignmentAgencySelect,
.commandStaffAssignmentAgencyFreeTextInput {
    flex: 1 1 120px;
    min-width: 100px;
}

.commandStaffPositionTypeRow {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 0;
    border-bottom: 1px solid var(--border);
}

.commandStaffPositionTypeRow input {
    flex: 1 1 auto;
}

/* One row per HandoverChecklistItem in the Operational Period modal --
   checkbox, item text, an optional "Checked by X at HH:mm" stamp, and a
   Notes button (opens #handoverChecklistItemNotesModal). Buttons never
   wrap here either -- the text label shrinks first instead. */
.handoverChecklistItemRow {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: nowrap;
    padding: 4px 0;
    border-bottom: 1px solid var(--border);
}

.handoverChecklistItemText {
    flex: 1 1 160px;
}

/* Pending/Confirmed status word -- Confirmed's own title attribute
   carries the "Checked by X at HH:mm" detail as a hover tooltip instead
   of spelling it out inline (see renderHandoverChecklist in
   netcontrol.js). */
.handoverChecklistItemStatusPending,
.handoverChecklistItemStatusConfirmed {
    flex-shrink: 0;
    white-space: nowrap;
}

.handoverChecklistItemStatusPending {
    color: var(--accent-amber);
}

.handoverChecklistItemStatusConfirmed {
    color: var(--accent-teal);
    text-decoration: underline;
    cursor: help;
}

.handoverChecklistItemRow button {
    flex-shrink: 0;
    white-space: nowrap;
}

/* Small popup for one checklist item's Notes -- same "edited in a popup,
   not an inline input" reasoning as #scribeNotesModal. Widened ~40mm
   past the original ~60mm x 40mm ask (a paragraph of text needs more
   room than that) to 4in x 2in -- this app's own established "Notes
   popup" size, already used by #scribeNotesTextarea. */
#handoverChecklistItemNotesModal .modalBox {
    width: auto;
}

#handoverChecklistItemNotesTextarea {
    display: block;
    width: 4in;
    height: 2in;
    box-sizing: border-box;
    font-family: inherit;
    font-size: 1em;
    padding: 8px;
    margin: 12px 0;
    resize: vertical;
    background-color: var(--input-bg);
    color: var(--input-text);
    border: 1px solid var(--border);
    border-radius: 4px;
}

/* Title row's right-hand button cluster (Manage Checklist Items + Print)
   -- kept as one flex child of .commandStaffModalHeader (itself
   space-between) so the title stays pinned left and both buttons sit
   together on the right instead of getting spread apart individually. */
.operationalPeriodHeaderButtons {
    display: flex;
    align-items: center;
    gap: 8px;
}

.operationalPeriodHeaderButtons button {
    flex-shrink: 0;
    white-space: nowrap;
}

/* "Command Staff / Org Chart" heading + "+ Add Role" on the same row,
   button pinned right -- same space-between shape as
   .commandStaffModalHeader, kept separate since .panelHeader (used
   elsewhere for adjacent-left title+button pairs) intentionally doesn't
   space-between. */
.operationalPeriodSectionHeader {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 4px;
}

.operationalPeriodSectionHeader h3 {
    margin: 0;
}

/* Bottom action row (.modalActions) -- "Start New Period" pinned far
   left, Save+Close clustered on the right via margin-left:auto on the
   wrapper (robust to the guest case, where the left button is absent
   entirely: a lone right-hand group with margin-left:auto still lands at
   the far right instead of hitting flexbox's single-child
   space-between edge case). */
#operationalPeriodModal .modalActions {
    justify-content: flex-start;
}

.operationalPeriodModalActionsRight {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
}

#operationalPeriodModal .modalActions button {
    flex-shrink: 0;
    white-space: nowrap;
}

/* Wider than Command Staff -- this modal's header/section rows can carry
   2-3 buttons across, which wrapped their own text at 480px, which this
   app's own convention treats as a bug (buttons never wrap; widen the
   container instead when there's room to). */
#operationalPeriodModal .modalBox,
#situationReportModal .modalBox,
#situationReportViewModal .modalBox {
    width: 600px;
}

#situationReportModal .modalBox,
#situationReportViewModal .modalBox {
    max-height: 80vh;
    overflow-y: auto;
}

.eocTemplateRow,
.eocTemplateCategoryRow,
.eocTemplateCategoryEditRow {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 6px;
}

/* The edit row's own inputs otherwise take the browser's default text-input
   width (~20 characters, ~209px each) regardless of what they actually
   need to hold — Category name + Answer type + Shortcode + Save + Cancel
   together came to 774px against a 520px-wide modal, pushing Save/Cancel
   fully off-screen with no way to reach them except scrolling the whole
   page right. flex-wrap is the safety net (guarantees Save/Cancel are
   always reachable no matter how narrow the modal gets); the explicit
   widths below are what actually make one line fit in the common case. */
.eocTemplateCategoryEditRow {
    flex-wrap: wrap;
}

.eocTemplateCategoryEditRow input[aria-label="Category name"] {
    /* flex-basis is explicit (not "auto") so the wrap decision uses our
       120px, not the browser's default text-input intrinsic width (~190px)
       — otherwise this field claims extra space on the wrap line before
       Save/Cancel are even considered, pushing Cancel onto its own line. */
    flex: 1 1 120px;
    min-width: 120px;
}

.eocTemplateCategoryEditRow input[aria-label="Shortcode"] {
    width: 55px;
    flex-shrink: 0;
}

/* Answer Type as two small icon buttons instead of a <select> — the select's
   full option text ("Color (Green/Yellow/Red)") needed ~174px, which was the
   last thing keeping Cancel from fitting on the same line as everything
   else. */
.eocAnswerTypeGroup {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
}

.eocAnswerTypeBtn {
    font-size: 1em;
    line-height: 1;
    padding: 4px 8px;
    background-color: #1b2439;
    opacity: 0.6;
}

.eocAnswerTypeBtn.selected {
    opacity: 1;
    background-color: var(--accent-teal);
}

/* Only shown when Choice is the selected answer type (toggled in JS) --
   modeled on .eocTemplateCategoryEditRow's own flex/wrap pattern rather
   than a new layout convention, just nested one level deeper (this is the
   first Template -> Category -> sub-item editor in the app). */
.eocChoicesEditor {
    flex-basis: 100%;
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 4px;
}

.eocChoiceRow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

.eocChoiceRow .eocChoiceInput {
    flex: 1 1 120px;
    min-width: 120px;
}

.eocChoiceRow .eocChoiceAbbrevInput {
    flex: 0 1 70px;
    min-width: 55px;
}

.eocChoiceRow .eocChoiceColorSelect {
    flex-shrink: 0;
}

.eocChoiceRow button {
    flex-shrink: 0;
    padding: 4px 8px;
}

.eocTemplateCategoryRow span {
    flex: 1;
    color: var(--text);
    font-size: 0.9em;
}

.eocTemplateSelectButton {
    flex: 1;
    text-align: left;
    background-color: #1b2439;
}

.eocTemplateSelectButton.selected {
    background-color: var(--accent-teal);
    color: var(--bg);
    font-weight: 600;
}

.eocTemplateRetiredBadge {
    font-size: 0.8em;
    opacity: 0.7;
    font-style: italic;
}

#positionTemplateModal .modalBox {
    width: 520px;
    max-height: 80vh;
    overflow-y: auto;
}

/* Unlike #eocTemplateModal's New Template/Close (space-between, spread
   apart), both buttons here sit flush against the right edge together. */
#positionTemplateModal .modalActions {
    justify-content: flex-end;
}

/* Wider than the default .modalBox -- 18 fields (5 text + 9 checkboxes +
   4 terminology selects) stacked one-per-row in a 340px modal pushed Save
   entirely below the visible viewport with no way to scroll to it.
   max-height/overflow-y is the same backstop #eocTemplateModal/
   #positionTemplateModal already use, kept here too in case a very short
   window still can't fit two columns of this many rows. */
#operatingModeModal .modalBox {
    width: 760px;
    max-height: 85vh;
    overflow-y: auto;
}

/* Two columns instead of one long stack -- Name spans both (it's the
   primary identifier); the status-row/terminology-row containers also
   span both so their own children (below) can lay out in their own
   two-column grid rather than being squeezed into a single half-width
   column. */
#operatingModeForm {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px 20px;
    margin-top: 4mm;
}

#operatingModeForm > .formField:first-child,
#modeEditableStatusRow,
#modeTerminologyRow {
    grid-column: 1 / -1;
}

#modeEditableStatusRow,
#modeTerminologyRow {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: center;
    gap: 10px 20px;
}

/* Same reasoning as #netEditableStatusRow's own justify-content override
   just above -- centers each checkbox+label at its own content width
   instead of the row's full stretched grid-column width. */
#modeEditableStatusRow .formFieldCheckbox {
    justify-content: flex-start;
}

.positionTemplateRow,
.positionTemplateItemRow,
.positionTemplateItemEditRow {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 6px;
}

.positionTemplateItemEditRow {
    flex-wrap: wrap;
}

.positionTemplateItemEditRow input[aria-label="Position label"] {
    flex: 1 1 120px;
    min-width: 120px;
}

.positionTemplateItemEditRow select[aria-label="Marker pin color"] {
    flex-shrink: 0;
}

.positionTemplateItemRow span {
    flex: 1;
    color: var(--text);
    font-size: 0.9em;
}

/* flex: 1 makes this button eat the row's remaining space like a label,
   which is what pushes Rename/Retire flush right against it -- same
   pattern as .eocTemplateSelectButton above. */
.positionTemplateSelectButton {
    flex: 1;
    text-align: left;
    background-color: #1b2439;
}

.positionTemplateSelectButton.selected {
    background-color: var(--accent-teal);
    color: var(--bg);
    font-weight: 600;
}

.positionTemplateRetiredBadge {
    font-size: 0.8em;
    opacity: 0.7;
    font-style: italic;
}

#helpModal .modalBox {
    width: 480px;
    max-height: 80vh;
    overflow-y: auto;
}

#helpModalBody p {
    margin: 0 0 6px;
    font-size: 0.9em;
    color: var(--text);
}

.modalBox h2 {
    margin-bottom: 16px;
    color: var(--accent-teal);
}

.signalBarsGroup {
    margin-bottom: 18px;
}

.signalBarsGroup label {
    display: block;
    margin-bottom: 8px;
    color: var(--text-muted);
    font-size: 0.85em;
}

.signalBars {
    display: flex;
    align-items: flex-end;
    gap: 4px;
    height: 60px;
}

.signalBar {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 3px;
    background-color: var(--border);
    color: var(--bg);
    font-size: 0.75em;
    font-weight: 700;
    cursor: pointer;
}

.signalBars-quality .signalBar.filled {
    background-color: var(--accent-blue);
}

.signalBars-strength .signalBar.filled {
    background-color: var(--accent-amber);
}

.eocCategoryRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
}

.eocCategoryRow label {
    color: var(--text);
    font-size: 0.9em;
}

/* flex-wrap is a real fix, not polish -- Color/YesNo's 2-3 buttons always
   fit the 420px popup on one line, but a Choice category's full-text
   buttons (e.g. "Out of Control"/"Contained"/"Controlled"/"Extinguished")
   can genuinely overflow it without this. */
.eocStatusPicker {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.eocStatusOption {
    font-size: 0.8em;
    padding: 4px 10px;
    margin-right: 0;
    background-color: #1b2439;
    opacity: 0.6;
}

.eocStatusOption.selected {
    opacity: 1;
    font-weight: 600;
}

.eocStatusOption-green.selected {
    background-color: var(--accent-green);
    color: var(--bg);
}

.eocStatusOption-yellow.selected {
    background-color: var(--accent-yellow);
    color: var(--bg);
}

.eocStatusOption-red.selected {
    background-color: var(--accent-red);
    color: var(--bg);
}

.eocStatusOption-yes.selected {
    background-color: var(--accent-green);
    color: var(--bg);
}

.eocStatusOption-no.selected {
    background-color: var(--accent-red);
    color: var(--bg);
}

/* Each Choice option carries its own admin-assigned color (unlike Color/
   YesNo's fixed per-value colors, this is a picker -- see
   EOC_CHOICE_COLOR_OPTIONS) -- one small rule per available palette entry. */
.eocStatusOption-choice-teal.selected { background-color: var(--accent-teal); color: var(--bg); }
.eocStatusOption-choice-amber.selected { background-color: var(--accent-amber); color: var(--bg); }
.eocStatusOption-choice-red.selected { background-color: var(--accent-red); color: var(--bg); }
.eocStatusOption-choice-yellow.selected { background-color: var(--accent-yellow); color: var(--bg); }
.eocStatusOption-choice-green.selected { background-color: var(--accent-green); color: var(--bg); }
.eocStatusOption-choice-purple.selected { background-color: var(--accent-purple); color: var(--bg); }
.eocStatusOption-choice-blue.selected { background-color: var(--accent-blue); color: var(--bg); }
.eocStatusOption-choice-grey.selected { background-color: var(--accent-grey); color: var(--bg); }

/* Outline style (no fill), distinct from every other option's solid color —
   deliberately reads as "no answer" rather than a fourth color choice. */
.eocStatusOption-clear {
    background-color: transparent;
    border: 1px dashed var(--text-muted);
    color: var(--text-muted);
}

.eocStatusOption-clear.selected {
    background-color: transparent;
    border: 2px solid var(--text-muted);
    color: var(--text);
}

/* Every popup's primary action (Save/Done/Submit/etc.) sits on the far
   right, so the whole group is pushed to the right edge by default --
   previously only a hand-maintained set of modals (Setup/Import Roster/
   EOC Status/Position Template) got this via their own ID-specific
   override, which silently left every other modal (Add Operator, Drop
   Waypoint, both Notes popups, and Missing Persons' Edit/match popups)
   packed flush left instead. Every modal's own button markup already
   lists its primary action last -- this only needed the shared alignment
   rule, not per-modal reordering, with the sole exception (see
   editRecordModal below). Modals that need a different arrangement
   (space-between for a modal with an unrelated left-side action, or the
   signal report modal's own 3-column grid) still override this per ID,
   same as before. */
.modalActions {
    display: flex;
    gap: 8px;
    margin-top: 6px;
    justify-content: flex-end;
}

/* Screen-reader-only content — visually hidden but still announced.
   Used for the EOC popup's save/error confirmations, which need to be
   spoken even though the modal that held the visible status text is
   about to close. */
.visuallyHidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

#setupModal .modalActions,
#eocTemplateModal .modalActions,
#importRosterModal .modalActions {
    justify-content: space-between;
}

#signalReportModal .modalActions {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    align-items: center;
}

#signalReportModal .modalActions button {
    margin-right: 0;
}

#cancelSignalReportButton {
    justify-self: start;
}

#noContactButton {
    justify-self: center;
}

#saveSignalReportButton {
    justify-self: end;
    background-color: var(--accent-green);
    color: var(--bg);
    border-color: var(--accent-green);
    font-weight: 600;
}

#noContactButton {
    background-color: var(--accent-red);
    border-color: var(--accent-red);
    color: var(--bg);
}

#eocStatusModal .modalActions {
    display: flex;
    justify-content: space-between;
}

#saveEocStatusButton {
    background-color: var(--accent-green);
    color: var(--bg);
    border-color: var(--accent-green);
    font-weight: 600;
}

#recenterButton,
#toggleSignalReachButton,
#toggleHistoricalReachButton {
    margin-bottom: 8px;
    margin-right: 6px;
}

#activeNetsPanel {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
}

#activeNetsPanel h2,
#submittedNetsPanel h2 {
    color: var(--accent-amber);
}

#activeNetsGrid {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    margin-top: 10px;
}

.netCard {
    display: block;
    width: 220px;
    padding: 14px 16px;
    border-radius: 10px;
    text-decoration: none;
    color: var(--bg);
}

.netCard:hover {
    filter: brightness(1.1);
}

.netCardName {
    font-weight: 700;
    font-size: 1.1em;
    margin-bottom: 6px;
}

.netCardDetail {
    font-size: 0.85em;
    margin-bottom: 3px;
    opacity: 0.85;
}

.netCardCount {
    margin-top: 10px;
    font-weight: 700;
    font-size: 0.9em;
}

.netCard-color-0 { background-color: var(--accent-teal); }
.netCard-color-1 { background-color: var(--accent-amber); }
.netCard-color-2 { background-color: var(--accent-purple); }
.netCard-color-3 { background-color: var(--accent-yellow); }
.netCard-color-4 { background-color: var(--accent-red); }
.netCard-color-5 { background-color: var(--accent-blue); }

/* Deliberately plain/muted (unlike the colorful live #activeNetsGrid cards)
   — these are archival records, not something demanding attention. */
#submittedNetsPanel {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    margin-top: 16px;
    background-color: var(--panel-bg);
}

/* No width:100% here on purpose — sized purely by its (nowrap) content
   instead, so the table is exactly as wide as it needs to be for every
   column to stay on one line, rather than being squeezed to fit a
   container that isn't actually wide enough for that. */
#submittedNetsTable {
    border-collapse: collapse;
    margin-top: 10px;
    white-space: nowrap;
}

#submittedNetsTable th {
    text-align: left;
    font-size: 0.85em;
    opacity: 0.7;
    padding: 6px 10px;
    border-bottom: 1px solid var(--border);
}

#submittedNetsTable td {
    padding: 6px 10px;
    font-size: 0.9em;
    border-bottom: 1px solid var(--border);
}

#submittedNetsTable tr:last-child td {
    border-bottom: none;
}

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

/* Matches every other page's boxed-amber-title header convention
   (#pressHeader/#commandOverviewHeader/etc.) -- previously had no CSS rule
   at all, so the title rendered flush against the page background in the
   default teal instead of amber. */
#viewerNetInfo {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
}

#viewerNetInfo h2 {
    color: var(--accent-amber);
}

#controllerRow {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
}

#controllerInfo {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

#controllerLine {
    margin: 0;
    color: var(--text-muted);
}

#controllerStartedLine {
    margin: 0;
    font-size: 0.9em;
    color: var(--text-muted);
}

#messageComposer {
    display: none;
    gap: 8px;
    margin-top: 10px;
}

#messageComposer.visible {
    display: flex;
}

#senderCallsignInput {
    flex: 0 0 140px;
}

#messageInput {
    flex: 1;
    margin-right: 0;
}

#sendMsgButton {
    min-width: 90px;
    text-align: center;
}

.sendMessageButton-sent {
    background-color: var(--accent-green);
    color: var(--bg);
    border-color: var(--accent-green);
    font-weight: 600;
}

.markerLabel {
    white-space: pre-line !important;
    text-align: center;
    line-height: 1.15;
}

/* Rollover hint popup (Google's InfoWindow) — override its default white
   bubble, which otherwise inherits this page's light theme text color and
   renders unreadable (near-white on white). Hover in/out already controls
   when it shows, so the built-in close "X" is just removed outright — it
   sits at the edge of the marker's hover area, so reaching for it closes
   the popup before it can be clicked. */
.gm-style-iw-c {
    background-color: #3a4258 !important;
    padding: 8px 12px !important;
}

.gm-style-iw-d {
    color: var(--text) !important;
    overflow: visible !important;
}

.gm-style-iw-t::after {
    background: #3a4258 !important;
}

/* The close button itself was already hidden below, but its parent header
   row (Google's own markup, not ours) stayed in the layout empty — this is
   what left a blank ~17px strip above the content. */
.gm-style-iw-chr {
    display: none !important;
}

.gm-ui-hover-effect {
    display: none !important;
}

/* --- Net Cleanup (admin) --- */

#netCleanupLogin {
    max-width: 320px;
    margin: 60px auto 0;
    text-align: center;
}

/* Same position/behavior as NetControl's #topBar (Print/Setup/Help), which
   this page doesn't have -- Log Out sits where Print would appear if this
   page had one, for consistency across every password-gated admin page. */
#netCleanupTopBar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 12px;
}

.netCleanupError {
    color: var(--accent-red);
}

.netCleanupWarning {
    border: 1px solid var(--accent-red);
    border-radius: 6px;
    padding: 10px 14px;
    color: var(--accent-red);
    max-width: 700px;
}

#netCleanupResult {
    color: var(--accent-green);
    font-weight: 600;
}

/* Boxed-panel treatment matching CommandOverview's equivalent sections --
   both password-gated admin pages sharing the same login/top-bar shape
   (see the comment above #commandOverviewLogin), but this page's own
   sections never got the same panel box. */
#netCleanupHeader,
#netCleanupOpen,
#netCleanupEnded,
#netCleanupSubmitted {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
    margin-bottom: 16px;
}

#netCleanupHeader {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
}

#netCleanupHeader h2 {
    color: var(--accent-amber);
}

#netCleanupOpen table,
#netCleanupEnded table,
#netCleanupSubmitted table {
    max-width: none;
    margin-bottom: 8px;
}

/* --- Command Overview (admin) — same login-page/top-bar shape as
   Net Cleanup above (both password-gated admin pages), plus a picker
   section matching every other net-scoped module's own picker. --- */
#commandOverviewLogin {
    max-width: 320px;
    margin: 60px auto 0;
    text-align: center;
}

.commandOverviewError {
    color: var(--accent-red);
}

/* flex-end, not space-between -- matches NetControl's own #topBar
   (Print/Setup/Help, all right-aligned) rather than spreading these link(s)
   to the far left. */
#commandOverviewTopBar {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 12px;
    margin-bottom: 12px;
}

#commandOverviewTopBarLinks {
    display: flex;
    gap: 8px;
}

#commandOverviewPicker,
#commandOverviewNotFound,
#commandOverviewHeader,
#commandOverviewTiles,
#commandOverviewTimeline {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
    margin-bottom: 16px;
}

#commandOverviewPicker h2,
#commandOverviewHeader h2 {
    color: var(--accent-amber);
}

#commandOverviewTitleRow {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 12px;
}

#commandOverviewTitleText h2 {
    margin: 0 0 2px 0;
}

/* Static (non-draggable, non-absolute) sibling of .weatherConditionsWidget
   -- there's no map on this page to float over or drag across, so this is
   just a plain box docked in the title row via flex, not position:absolute. */
.commandOverviewWeatherWidget {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 6px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    font-size: 0.9em;
    min-width: 90px;
    flex-shrink: 0;
    cursor: pointer;
}

/* Weather Alerts is the one status tile that opens something on click
   (the Current Weather Conditions detail modal) -- same cursor/title
   affordance as the header widget above. */
.commandOverviewTile--clickable {
    cursor: pointer;
}

/* Welfare Check Inquiries is the one Command Overview tile that's hidden
   until there's actually something pending (see commandoverview.js's own
   comment) -- same red urgency treatment as NetControl's own
   .statusTile--alert, so it reads as needing attention rather than just
   another calm module summary. */
.commandOverviewTile--urgent,
.commandOverviewTile--urgent h4,
.commandOverviewTile--urgent p {
    border-color: var(--accent-red);
    color: var(--accent-red);
}

.commandOverviewTile--urgent {
    border: 1px solid var(--accent-red);
    background-color: #2a1613;
}

#commandOverviewAfterAction h3 {
    margin: 12px 0 4px 0;
    color: var(--accent-amber);
}

#commandOverviewAfterAction p {
    white-space: pre-wrap;
    margin: 0;
}

#commandOverviewPicker ul {
    padding-left: 1.2em;
    margin-bottom: 20px;
}

#commandOverviewTileGrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 12px;
    margin-top: 12px;
}

.commandOverviewTile {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 10px 12px;
    background-color: var(--bg);
}

.commandOverviewTile h4 {
    margin: 0 0 6px;
    color: var(--accent-teal);
    font-size: 0.95em;
}

.commandOverviewTile p {
    margin: 0 0 2px;
    font-size: 0.9em;
    color: var(--text);
}

/* Safety net for a long function name or "Other" free-text channel
   description that's still too wide for the tile -- collapses to an
   ellipsis rather than wrapping/overflowing; the full text is always
   available via this element's own title tooltip. */
.commandOverviewCommsPlanLine {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: default;
}

/* The full org chart now only ever renders inside #commandStaffOrgChartModal
   (the Command Overview tile itself is a compact summary, see
   buildCommandStaffSummaryTile) -- a wider modalBox than the default,
   since a chart with several branches needs the room; still scrolls
   horizontally on its own (.commandStaffOrgChart) if it doesn't fit. */
#commandStaffOrgChartModal .modalBox {
    width: 700px;
    max-width: 90vw;
}

.commandStaffOrgChart {
    display: flex;
    gap: 16px;
    overflow-x: auto;
    padding-top: 4px;
}

.commandStaffOrgBox {
    border: 1px solid var(--accent-teal);
    border-radius: 6px;
    padding: 6px 10px;
    background-color: var(--panel-bg);
    min-width: 140px;
}

.commandStaffOrgBoxLabel {
    font-weight: 600;
    color: var(--accent-teal);
    font-size: 0.9em;
    margin-bottom: 2px;
}

.commandStaffOrgBoxPerson {
    font-size: 0.85em;
    color: var(--text);
}

/* Simple CSS-only connector -- a top border on the children wrapper
   plus a short vertical stub above it reads as "reports down to" without
   needing an SVG/canvas line-drawing layer at this scale. */
.commandStaffOrgChildren {
    display: flex;
    gap: 12px;
    margin-top: 14px;
    padding-top: 10px;
    border-top: 1px solid var(--border);
    position: relative;
}

.commandStaffOrgChildren::before {
    content: "";
    position: absolute;
    top: -14px;
    left: 50%;
    width: 1px;
    height: 14px;
    background-color: var(--border);
}

#commandOverviewTimelineList {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 12px;
    max-height: 60vh;
    overflow-y: auto;
}

.commandOverviewTimelineRow {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 6px 8px;
    border: 1px solid var(--border);
    border-radius: 4px;
}

.commandOverviewTimelineTime {
    flex-shrink: 0;
    font-size: 0.85em;
    white-space: nowrap;
}

.commandOverviewTimelineBadge {
    flex-shrink: 0;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: 0.75em;
    font-weight: 600;
    text-transform: uppercase;
}

/* Same source colors used elsewhere for these same three roles — teal for
   Scribe (matches its own dir badges), amber for Press (matches Media
   Relations' own header accent), grey for Missing Persons (the same
   deliberately-non-alarming muted tone used for Deceased/Archived
   elsewhere, since this badge can represent a Deceased-status update
   without ever spelling the word out). */
.commandOverviewTimelineBadge-scribe {
    background-color: var(--accent-teal);
    color: var(--bg);
}

.commandOverviewTimelineBadge-press {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

.commandOverviewTimelineBadge-missing {
    background-color: var(--accent-grey);
    color: var(--bg);
}

/* Waypoint/Damage/EOC added later, once those 3 sources got their own
   permanent per-event tables to feed this timeline from. Damage reuses red
   (hazard/danger, same association as its map icon); EOC reuses purple,
   already the app's own fallback color for an EOC Choice category with no
   admin-assigned color (see EOC_CHOICE_COLOR_OPTIONS/choiceByLabel in
   netcontrol.js); Waypoint has no existing single color to match (map pins
   use whatever color the category implies), so it just gets blue. */
.commandOverviewTimelineBadge-waypoint {
    background-color: var(--accent-blue);
    color: var(--bg);
}

.commandOverviewTimelineBadge-damage {
    background-color: var(--accent-red);
    color: var(--bg);
}

.commandOverviewTimelineBadge-eoc {
    background-color: var(--accent-purple);
    color: var(--bg);
}

.commandOverviewTimelineBadge-frequency {
    background-color: var(--accent-teal);
    color: var(--bg);
}

.commandOverviewTimelineBadge-bacontrol {
    background-color: var(--accent-amber);
    color: var(--bg);
}

.commandOverviewTimelineBadge-par {
    background-color: var(--accent-green);
    color: var(--bg);
}

.commandOverviewTimelineBadge-taskassignment {
    background-color: var(--accent-blue);
    color: var(--bg);
}

.commandOverviewTimelineText {
    flex: 1;
}

/* NetScribe's content sections boxed the same way as NetControl's
   #netSetupPanel/#checkinPanel, for visual consistency between the two
   pages — same border/radius/padding/background, h2 titles in the same
   amber accent. #scribeTopBar (the Print link) is deliberately excluded,
   matching how NetControl's own #topBar isn't boxed either. */
#scribePicker,
#scribeNotFound,
#scribeIdentity,
#scribeHeader,
#scribeEntryRow,
#scribeLog {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
}

/* Formal Message Log (radiogram.cshtml) never got the boxed-panel
   treatment every sibling module page (Scribe/Press/Missing/Damage)
   shares for these same picker/header/filters/log sections. */
#formalMessagePicker,
#formalMessageNotFound,
#formalMessageHeader,
#formalMessageFilters,
#formalMessageLog {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
}

#scribePicker h2,
#scribeIdentity h2,
#scribeHeader h2 {
    color: var(--accent-amber);
}

/* Fixed column widths (via the <colgroup> in NetScribe.cshtml) so that
   entering edit mode — which needs room for text inputs, a 2-button
   Direction group, and Save/Cancel — never changes any column's width.
   Without this, table-layout:auto let the Dir/Actions cells' edit-mode
   content push those columns wider, which squeezed Time/Callsign narrow
   enough to wrap their text onto a second line and made the whole row
   visibly taller ("spread into 2 rows"). Widths measured directly against
   this table's real font/padding, not guessed — Callsign in particular is
   sized 10mm narrower than the edit-mode <input>'s old default width.
   Message has no fixed width — it absorbs whatever's left and is the one
   column still allowed to wrap onto multiple lines for a long entry. */
#scribeLogTable {
    max-width: none;
    table-layout: fixed;
}

/* Caps the log at a fixed viewport-relative height instead of letting a
   long net keep pushing the whole page taller -- newest entries are now
   sorted to the top (see NetScribe.cshtml.cs), so this scrolls the older
   ones out of view rather than the ones just typed. */
.scribeLogScroll {
    max-height: 60vh;
    overflow-y: auto;
}

/* Widths measured live against real rendered cells, not estimated — the
   Callsign input's own padding/border stacks on top of the <td>'s padding,
   so 12 characters needs more room than the raw text width alone suggests.
   Dir/Actions/Time all had real slack above their true minimums (verified
   the same way) to give back to Callsign without risking their own wrap. */
.scribeColTime { width: 45mm; }
.scribeColCallsign { width: 43mm; }
.scribeColDir { width: 24mm; }
.scribeColNotes { width: 17mm; }
.scribeColActions { width: 33mm; }

#scribeLogTable th:nth-child(1), #scribeLogTable td:nth-child(1),
#scribeLogTable th:nth-child(2), #scribeLogTable td:nth-child(2),
#scribeLogTable th:nth-child(3), #scribeLogTable td:nth-child(3),
#scribeLogTable th:nth-child(6), #scribeLogTable td:nth-child(6) {
    white-space: nowrap;
    overflow: hidden;
}

/* Smaller than this app's default button padding — the roster table
   (#rosterTable) uses this same compact style for the same reason: fitting
   two buttons in one table cell without needing a wider column. */
#scribeLogTable td button {
    padding: 2px 6px;
    margin-right: 4px;
}

#scribeLogTable td input[type="text"] {
    width: 100%;
    box-sizing: border-box;
    margin-right: 0;
}

.scribeNotesIcon {
    cursor: help;
}

/* Notes is edited in a popup rather than an inline table input — the
   column's too narrow for a real paragraph, and a full note is more
   comfortable to write in a proper textarea anyway. */
#scribeNotesModal .modalBox {
    width: auto;
}

#scribeNotesTextarea {
    display: block;
    width: 4in;
    height: 2in;
    box-sizing: border-box;
    font-family: inherit;
    font-size: 1em;
    padding: 8px;
    margin: 12px 0;
    resize: vertical;
    background-color: var(--input-bg);
    color: var(--input-text);
    border: 1px solid var(--border);
    border-radius: 4px;
}

.scribeLockedNotice {
    color: var(--accent-red);
}

#scribePicker ul {
    padding-left: 1.2em;
    margin-bottom: 20px;
}

/* Same position/behavior as NetControl's #topBar (Print/Setup/Help), for
   consistency between the two pages. */
#scribeTopBar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 12px;
}

/* Mirrors NetControl's #netSetupForm: label/input pairs on one row, spread
   across the full width, with the action button (Start Scribing here,
   Start Net there) landing at the right end once space-between distributes
   the row's leftover space. */
#scribeIdentityForm {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    margin-top: 8px;
}

#scribeIdentityForm label {
    margin-left: 0;
    margin-right: 0;
    flex-shrink: 0;
    white-space: nowrap;
}

#scribeIdentityForm input[type="text"] {
    margin-right: 0;
    flex-shrink: 0;
}

#startScribingButton {
    margin-left: 0;
    margin-right: 0;
    flex-shrink: 0;
}

#scribePrintLink {
    display: inline-block;
    background-color: #1b2439;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 6px 14px;
    text-decoration: none;
    cursor: pointer;
}

#scribePrintLink:hover {
    border-color: var(--accent-teal);
}

/* --- Scribe entry composer: one row, directly above the log table --- */

#scribeEntryRow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-bottom: 4px;
}

#entryCallsignInput {
    width: 12ch;
}

#entryMessageInput {
    flex: 1;
    min-width: 220px;
}

#entryNotesInput {
    flex: 1;
    min-width: 160px;
}

.scribeDirGroup {
    display: flex;
    gap: 4px;
}

.scribeDirBtn {
    padding: 6px 14px;
    opacity: 0.6;
}

.scribeDirBtn-in {
    border-color: var(--accent-teal);
    color: var(--accent-teal);
}

.scribeDirBtn-out {
    border-color: var(--accent-amber);
    color: var(--accent-amber);
}

.scribeDirBtn.selected {
    opacity: 1;
    font-weight: 600;
}

.scribeDirBtn-in.selected {
    background-color: var(--accent-teal);
    color: var(--bg);
}

.scribeDirBtn-out.selected {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

/* Log table Dir-column chip — same colors as the composer's In/Out
   buttons, so the two visually read as the same thing. Text stays ("In"/
   "Out"), not just the color, so this reads the same either way for a
   screen reader. */
.scribeDirBadge {
    display: inline-block;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.85em;
    font-weight: 600;
}

.scribeDirBadge-in {
    background-color: var(--accent-teal);
    color: var(--bg);
}

.scribeDirBadge-out {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

/* Quick Entry's own input is sized/positioned via JS (alignQuickEntryField
   in netscribe.js), not fixed CSS -- see that function's comment for why
   a static width/margin can't line it up with the composer row below. */
#scribeHelpModal .modalBox {
    width: 480px;
}

.scribeHelpList {
    padding-left: 1.2em;
    margin: 6px 0 12px;
}

.scribeHelpList li {
    margin-bottom: 4px;
}

/* Shared by any wide log/roster table (Missing/Deceased today) that isn't
   worth compressing into fixed mm column widths the way Scribe's simpler
   table is — same idea as .rosterTableScroll, just under a page-agnostic
   name since more than one page's table needs it. */
.moduleTableScroll {
    overflow-x: auto;
}

/* --- Media Relations (NetPress) — a small roster of releases grouped by
   Status, not a running log. Panel-box styling matches Scribe/Missing
   Persons; the status-grouped listing reuses .missingStatusPanel* from
   Missing Persons (see #pressStatusPanels in NetPress.cshtml), and the
   Add/Edit popup reuses .missingFormRow/.formField from the same page. --- */
#pressPicker,
#pressNotFound,
#pressHeader,
#pressLog,
#mediaViewerPicker,
#mediaViewerNotFound,
#mediaViewerHeader,
#mediaViewerLog {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
}

#pressPicker h2,
#pressHeader h2,
#mediaViewerPicker h2,
#mediaViewerHeader h2 {
    color: var(--accent-amber);
}

#pressPicker ul,
#mediaViewerPicker ul {
    padding-left: 1.2em;
    margin-bottom: 20px;
}

#pressTopBar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 12px;
}

#pressPrintLink {
    display: inline-block;
    background-color: #1b2439;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 6px 14px;
    text-decoration: none;
    cursor: pointer;
}

#pressPrintLink:hover {
    border-color: var(--accent-teal);
}

.pressLockedNotice {
    color: var(--accent-red);
}

#addPressEntryButton {
    margin-top: 8px;
}

#pressEntryModal .modalBox {
    width: 580px;
}

/* The first row's three fields don't need an even flex:1 split (the
   .missingFormRow default) -- Released By only ever holds a short name,
   Release Date needs enough room for the native picker's text + calendar
   icon (too narrow and Chrome clips the icon/truncates the text), and the
   Status group needs enough width to fit all three buttons
   (Draft/Released/Archived) on one line without the last one overflowing
   the modal's edge. The modal itself is widened (520px -> 580px) rather
   than squeezing these further, since 520px didn't leave enough room for
   all three once Release Date got wide enough to render properly. */
#pressEntryModal .missingFormRow:first-of-type .formField:nth-child(1) {
    flex: 0 1 100px;
    min-width: 90px;
}

#pressEntryModal .missingFormRow:first-of-type .formField:nth-child(2) {
    flex: 0 1 150px;
    min-width: 140px;
}

#pressEntryModal .missingFormRow:first-of-type .formField:nth-child(3) {
    flex: 1 1 260px;
    min-width: 260px;
}

/* Chrome renders a native date input's content box a couple pixels taller
   than a plain text input even with identical padding/border/font-size
   (the calendar-icon control forces its own minimum), so Released By and
   Release Date visibly sat at different heights on the same row. Pinning
   an explicit height matching the text input's own computed height fixes
   it deterministically instead of chasing UA-dependent auto-sizing. */
#pressReleaseDateInput {
    height: 35px;
}

#pressStoryTextInput {
    display: block;
    width: 100%;
    box-sizing: border-box;
    font-family: inherit;
    font-size: 1em;
    padding: 8px;
    resize: vertical;
    background-color: var(--input-bg);
    color: var(--input-text);
    border: 1px solid var(--border);
    border-radius: 4px;
}

/* Same semantic-color convention as Missing Persons' status buttons
   (.missingStatusBtn-missing etc.) — amber for work in progress, green for
   live/current, grey for put-away-but-not-urgent. */
.pressStatusBtn-draft {
    border-color: var(--accent-amber);
    color: var(--accent-amber);
}

.pressStatusBtn-released {
    border-color: var(--accent-green);
    color: var(--accent-green);
}

.pressStatusBtn-archived {
    border-color: var(--accent-grey);
    color: var(--accent-grey);
}

.pressStatusBtn-draft.selected {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

.pressStatusBtn-released.selected {
    background-color: var(--accent-green);
    color: var(--bg);
}

.pressStatusBtn-archived.selected {
    background-color: var(--accent-grey);
    color: var(--bg);
}

/* Indents the whole listing (rows and cards alike) relative to the
   Draft/Released/Archived toggle above it, so the panel header and its
   contents read as a clear parent/child relationship rather than sitting
   flush with the same left edge. */
.pressStatusPanel .missingStatusPanelBody {
    padding-left: 24px;
}

.pressDraftRows {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.pressDraftRow {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 8px;
    border: 1px solid var(--border);
    border-radius: 4px;
}

.pressDraftRowTitle {
    font-weight: 600;
    flex: 1;
}

/* First ~20-25 characters of the story text, so a draft's actual content
   is scannable from the row without opening it — a distinct color from
   both the bold Title and the muted Released By keeps the three pieces
   of text easy to tell apart at a glance. */
.pressDraftRowPreview {
    color: var(--accent-teal);
    font-style: italic;
    white-space: nowrap;
    flex-shrink: 0;
}

.pressDraftRowActions {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
}

.pressReleaseCardGrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(3in, 1fr));
    gap: 12px;
}

/* Literal physical sizing per the user's own spec (3in x 2in) — this
   codebase already leans on physical CSS units for compact layouts
   (print.css's mm columns, the old Notes textarea's 4in width), so an
   on-screen "in"-sized card isn't without precedent here. */
.pressReleaseCard {
    position: relative;
    width: 3in;
    height: 2in;
    box-sizing: border-box;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: 6px;
    background-color: var(--panel-bg);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.pressReleaseCardTitle {
    margin: 0 0 4px;
    font-size: 1em;
    color: var(--accent-teal);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pressReleaseCardMeta {
    margin: 0 0 6px;
    font-size: 0.85em;
}

.pressReleaseCardPreview {
    margin: 0;
    font-size: 0.85em;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
}

.pressReleaseCardIcons {
    position: absolute;
    top: 6px;
    right: 8px;
    display: flex;
    gap: 4px;
}

.pressCardIcon {
    background: none;
    border: none;
    padding: 2px;
    margin: 0;
    cursor: pointer;
    font-size: 0.95em;
    line-height: 1;
    text-decoration: none;
}

/* Media Viewer's cards (see #mediaViewerCardGrid in MediaViewer.cshtml) --
   no Edit/Delete/link-icon row at all, since the whole card itself is the
   click target when a release has a URL. Its title attribute (set in
   mediaviewer.js) is the full link, shown as the browser's own native
   tooltip on hover -- the "rollover help" the user asked for, no custom
   tooltip widget needed. Cards with no URL stay non-interactive (default
   cursor, no hover highlight). */
.pressReleaseCard-clickable {
    cursor: pointer;
}

.pressReleaseCard-clickable:hover {
    border-color: var(--accent-teal);
}

/* --- Missing/Deceased Log (NetMissingPersons) — same panel-box styling as
   Scribe/Press, but the composer is a multi-row .formField grid (too many
   fields for one line) and the table is wide enough to need horizontal
   scroll (.moduleTableScroll) instead of fixed mm columns. --- */
#missingPicker,
#missingNotFound,
#missingIdentity,
#missingHeader,
#missingEntryForm,
#missingLog,
#damagePicker,
#damageNotFound,
#damageHeader,
#damageLog,
#damageFilters {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
}

#missingPicker h2,
#missingIdentity h2,
#missingHeader h2,
#damagePicker h2,
#damageHeader h2 {
    color: var(--accent-amber);
}

/* Formal Message Log (radiogram.cshtml) never got this page's title-color
   treatment -- every sibling module page's picker/header h2 is amber. */
#formalMessagePicker h2,
#formalMessageHeader h2 {
    color: var(--accent-amber);
}

#missingPicker ul,
#damagePicker ul {
    padding-left: 1.2em;
    margin-bottom: 20px;
}

#missingTopBar,
#damageTopBar,
#formalMessageTopBar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 12px;
}

#missingPrintLink,
#damagePrintLink,
#formalMessagePrintLink {
    display: inline-block;
    background-color: #1b2439;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 6px 14px;
    text-decoration: none;
    cursor: pointer;
}

#missingPrintLink:hover,
#damagePrintLink:hover,
#formalMessagePrintLink:hover {
    border-color: var(--accent-teal);
}

#damageTableActions {
    margin-bottom: 12px;
}

/* Same 600px global <table> cap that #rosterTable/#missingLogTable already
   opt out of -- this one was just missed. Found while fixing the identical
   width issue on the Formal Message Log table. */
#damageTable {
    min-width: 100%;
    max-width: none;
}

#damageFilters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-bottom: 12px;
}

#damageFilters label {
    margin-left: 0;
}

#missingIdentityForm {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    margin-top: 8px;
}

#missingIdentityForm label {
    margin-left: 0;
    margin-right: 0;
    flex-shrink: 0;
    white-space: nowrap;
}

#missingIdentityForm input[type="text"] {
    margin-right: 0;
    flex-shrink: 0;
}

#startMissingLogButton {
    margin-left: 0;
    margin-right: 0;
    flex-shrink: 0;
}

.missingLockedNotice {
    color: var(--accent-red);
}

/* Each row of the composer holds several .formField label/input pairs
   (shared class, defined earlier for NetControl's own modals) plus,
   on the last row, the submit button. flex:1 lets fields share the row's
   width evenly; min-width keeps a field from getting squeezed illegibly
   narrow before wrapping to its own line. */
.missingFormRow {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    margin-bottom: 10px;
}

.missingFormRow:last-child {
    margin-bottom: 4px;
}

.missingFormRow .formField {
    flex: 1;
    min-width: 140px;
}

.missingFormRow button {
    flex-shrink: 0;
}

.missingStatusGroup {
    display: flex;
    gap: 4px;
}

.missingStatusBtn {
    padding: 6px 14px;
    opacity: 0.6;
    white-space: nowrap;
}

/* Same semantic colors as EOC status elsewhere in this app: amber for an
   active concern, green for a positive resolution. Deceased deliberately
   avoids red (already means "alarm/error" throughout this app) in favor of
   a muted grey — final/closed, not urgent. */
.missingStatusBtn-missing {
    border-color: var(--accent-amber);
    color: var(--accent-amber);
}

.missingStatusBtn-locatedsafe {
    border-color: var(--accent-green);
    color: var(--accent-green);
}

.missingStatusBtn-deceased {
    border-color: var(--accent-grey);
    color: var(--accent-grey);
}

.missingStatusBtn.selected {
    opacity: 1;
    font-weight: 600;
}

.missingStatusBtn-missing.selected {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

.missingStatusBtn-locatedsafe.selected {
    background-color: var(--accent-green);
    color: var(--bg);
}

.missingStatusBtn-deceased.selected {
    background-color: var(--accent-grey);
    color: var(--bg);
}

/* Weather Conditions modal's US/Metric units toggle -- reuses
   .missingStatusGroup/.missingStatusBtn's generic radiogroup layout, just
   a neutral accent (not a status severity, so no semantic color needed). */
.weatherUnitsBtn.selected {
    background-color: var(--accent-teal);
    color: var(--bg);
}

.missingStatusBadge {
    display: inline-block;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.85em;
    font-weight: 600;
    white-space: nowrap;
}

.missingStatusBadge-missing {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

.missingStatusBadge-locatedsafe {
    background-color: var(--accent-green);
    color: var(--bg);
}

.missingStatusBadge-deceased {
    background-color: var(--accent-grey);
    color: var(--bg);
}

/* Public /WelfareCheck's own result banner -- shown once after a search
   submits. Colored by outcome (safe/missing/no-match/contact-directly),
   same left-border notice-box shape as .weatherAlertItem, just larger
   since this holds a full sentence rather than a compact row. */
.welfareCheckResult {
    margin-top: 12px;
    padding: 10px 14px;
    border-left: 4px solid var(--accent-grey);
}

.welfareCheckResult-safe {
    border-left-color: var(--accent-green);
}

.welfareCheckResult-missing {
    border-left-color: var(--accent-amber);
}

.welfareCheckResult-no-match,
.welfareCheckResult-contact-directly {
    border-left-color: var(--accent-grey);
}

/* Damage Assessment severity buttons/badges -- green -> yellow -> orange ->
   red progression, matching DAMAGE_SEVERITY_COLORS' marker-dot mapping in
   netcontrol.js/viewer.js, so the map pin and the table read consistently. */
.damageSeverityBtn {
    padding: 6px 14px;
    opacity: 0.6;
    white-space: nowrap;
}

.damageSeverityBtn-affected {
    border-color: var(--accent-green);
    color: var(--accent-green);
}

.damageSeverityBtn-minor {
    border-color: var(--accent-yellow);
    color: var(--accent-yellow);
}

.damageSeverityBtn-major {
    border-color: var(--accent-amber);
    color: var(--accent-amber);
}

.damageSeverityBtn-destroyed {
    border-color: var(--accent-red);
    color: var(--accent-red);
}

.damageSeverityBtn.selected {
    opacity: 1;
    font-weight: 600;
}

.damageSeverityBtn-affected.selected {
    background-color: var(--accent-green);
    color: var(--bg);
}

.damageSeverityBtn-minor.selected {
    background-color: var(--accent-yellow);
    color: var(--accent-amber-dark);
}

.damageSeverityBtn-major.selected {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

.damageSeverityBtn-destroyed.selected {
    background-color: var(--accent-red);
    color: var(--bg);
}

/* Workflow status -- a separate semantic axis from Severity above (Reported
   = neutral/new, Verified = confirmed, Resolved = done), even though both
   live in the same row of buttons. */
.damageWorkflowBtn {
    padding: 6px 14px;
    opacity: 0.6;
    white-space: nowrap;
}

.damageWorkflowBtn-reported {
    border-color: var(--accent-grey);
    color: var(--accent-grey);
}

.damageWorkflowBtn-verified {
    border-color: var(--accent-blue);
    color: var(--accent-blue);
}

.damageWorkflowBtn-resolved {
    border-color: var(--accent-green);
    color: var(--accent-green);
}

.damageWorkflowBtn.selected {
    opacity: 1;
    font-weight: 600;
}

.damageWorkflowBtn-reported.selected {
    background-color: var(--accent-grey);
    color: var(--bg);
}

.damageWorkflowBtn-verified.selected {
    background-color: var(--accent-blue);
    color: var(--bg);
}

.damageWorkflowBtn-resolved.selected {
    background-color: var(--accent-green);
    color: var(--bg);
}

.damageSeverityBadge,
.damageStatusBadge {
    display: inline-block;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.85em;
    font-weight: 600;
    white-space: nowrap;
}

/* Task/Assignment Board -- same grey/blue/green/red escalation as
   .damageWorkflowBtn above, just a 4th (Cancelled) state instead of that
   feature's own 3. */
.taskStatusBtn {
    padding: 6px 14px;
    opacity: 0.6;
    white-space: nowrap;
}

.taskStatusBtn-assigned {
    border-color: var(--accent-grey);
    color: var(--accent-grey);
}

.taskStatusBtn-inprogress {
    border-color: var(--accent-blue);
    color: var(--accent-blue);
}

.taskStatusBtn-complete {
    border-color: var(--accent-green);
    color: var(--accent-green);
}

.taskStatusBtn-cancelled {
    border-color: var(--accent-red);
    color: var(--accent-red);
}

.taskStatusBtn.selected {
    opacity: 1;
    font-weight: 600;
}

.taskStatusBtn-assigned.selected {
    background-color: var(--accent-grey);
    color: var(--bg);
}

.taskStatusBtn-inprogress.selected {
    background-color: var(--accent-blue);
    color: var(--bg);
}

.taskStatusBtn-complete.selected {
    background-color: var(--accent-green);
    color: var(--bg);
}

.taskStatusBtn-cancelled.selected {
    background-color: var(--accent-red);
    color: var(--bg);
}

.taskStatusBadge {
    display: inline-block;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.85em;
    font-weight: 600;
    white-space: nowrap;
}

.taskStatusBadge-assigned { background-color: var(--accent-grey); color: var(--bg); }
.taskStatusBadge-inprogress { background-color: var(--accent-blue); color: var(--bg); }
.taskStatusBadge-complete { background-color: var(--accent-green); color: var(--bg); }
.taskStatusBadge-cancelled { background-color: var(--accent-red); color: var(--bg); }

.taskLockedNotice {
    color: var(--accent-red);
}

#taskDisabledNotice {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
    margin-bottom: 16px;
}

/* Matches #rosterTable's own override on NetControl -- without this, the
   base `table { max-width: 600px }` rule caps this table well inside its
   .moduleTableScroll wrapper regardless of the wrapper's own (much wider)
   width, the same "wasted space" bug already found and fixed on the
   Communications Plan table. table-layout:fixed + percentage columns
   (rather than auto layout) for the same reason that table needed it too:
   auto layout redistributes surplus width unpredictably instead of
   respecting each column's intended share. */
#taskTable {
    width: 100%;
    min-width: 100%;
    max-width: none;
    table-layout: fixed;
}

/* table-layout:fixed clips overflow at each column's boundary rather than
   growing the column, so a string with no spaces to wrap at (the
   "Branch/Division/Group" header, in particular -- "/" isn't a wrap
   opportunity on its own) would otherwise render as one unbroken line
   that visually bleeds into the next column instead of wrapping. */
#taskTable th, #taskTable td {
    overflow-wrap: anywhere;
}

#taskTable .taskTimeHeader, #taskTable td:nth-child(1) { width: 10%; }
#taskTable .taskBranchHeader, #taskTable td:nth-child(2) { width: 14%; }
/* The Task Description column gets the largest share -- it's the one
   field net control actually reads at a glance, everything else here is
   just supporting metadata. */
#taskTable .taskDescriptionHeader, #taskTable td:nth-child(3) { width: 32%; }
#taskTable .taskStatusHeader, #taskTable td:nth-child(4) { width: 12%; }
#taskTable .taskResourcesHeader, #taskTable td:nth-child(5) { width: 16%; }
#taskTable .taskActionsHeader, #taskTable td:nth-child(6) { width: 16%; }

/* Edit/Delete must stay on one line regardless of how narrow the Actions
   column's 16% share ends up being on a smaller window -- wrapping them
   onto two lines reads as broken, same "buttons never wrap" convention
   already followed everywhere else in this app. */
#taskTable td:nth-child(6) {
    white-space: nowrap;
}

#taskTable td:nth-child(6) button {
    margin-right: 4px;
}

#taskTable .taskStatusSelect {
    width: 100%;
    box-sizing: border-box;
    padding: 4px;
    font-size: 1em;
}

/* Same "wasted space" fix as #taskTable above -- without this, the base
   `table { max-width: 600px }` rule caps this table well inside its
   .moduleTableScroll wrapper regardless of the wrapper's own (much wider)
   width. table-layout:fixed + percentage columns, same reasoning. */
#mutualAidTable {
    width: 100%;
    min-width: 100%;
    max-width: none;
    table-layout: fixed;
}

#mutualAidTable th, #mutualAidTable td {
    overflow-wrap: anywhere;
}

#mutualAidTable .mutualAidNumberHeader, #mutualAidTable td:nth-child(1) { width: 5%; }
/* Same rebalancing as #netMutualAidTable's own copy -- Requested/Status get
   more room, taken from Resource/ETA, so the time never wraps and the
   quick-status <select> never clips its widest option. */
#mutualAidTable .mutualAidTimeHeader, #mutualAidTable td:nth-child(2) { width: 12%; }
#mutualAidTable .mutualAidResourceHeader, #mutualAidTable td:nth-child(3) { width: 23%; }
#mutualAidTable .mutualAidPriorityHeader, #mutualAidTable td:nth-child(4) { width: 10%; }
#mutualAidTable .mutualAidFromHeader, #mutualAidTable td:nth-child(5) { width: 15%; }
#mutualAidTable .mutualAidStatusHeader, #mutualAidTable td:nth-child(6) { width: 16%; }
#mutualAidTable .mutualAidEtaHeader, #mutualAidTable td:nth-child(7) { width: 10%; }
#mutualAidTable .mutualAidActionsHeader, #mutualAidTable td:nth-child(8) { width: 9%; }

#mutualAidTable td:nth-child(8) {
    white-space: nowrap;
}

#mutualAidTable td:nth-child(8) button {
    margin-right: 4px;
}

#taskEntryResourcesList {
    list-style: none;
    padding: 0;
    margin: 0 0 8px;
}

#taskEntryResourcesList li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 4px 0;
}

.damageSeverityBadge-affected {
    background-color: var(--accent-green);
    color: var(--bg);
}

.damageSeverityBadge-minor {
    background-color: var(--accent-yellow);
    color: var(--accent-amber-dark);
}

.damageSeverityBadge-major {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

.damageSeverityBadge-destroyed {
    background-color: var(--accent-red);
    color: var(--bg);
}

.damageStatusBadge-reported {
    background-color: var(--accent-grey);
    color: var(--bg);
}

.damageStatusBadge-verified {
    background-color: var(--accent-blue);
    color: var(--bg);
}

.damageStatusBadge-resolved {
    background-color: var(--accent-green);
    color: var(--bg);
}

.damageSortable {
    cursor: pointer;
    user-select: none;
}

.damageLockedNotice {
    color: var(--accent-red);
}

/* Formal Message Log -- Precedence buttons/badges (grey -> blue -> amber ->
   red escalation, same "selected" pattern as damageSeverityBtn/Badge) and
   the independent delivery-status badge (Filed/Sent/Delivered/
   Undeliverable), a one-way funnel rather than a free-form radiogroup, so
   only a badge + quick-action buttons exist for it, no button-group. */
.formalMessageSortable {
    cursor: pointer;
    user-select: none;
}

.formalMessageLockedNotice {
    color: var(--accent-red);
}

#formalMessageTable {
    min-width: 100%;
    max-width: none;
}

/* Wider than the default 340px .modalBox -- at the default width the 4
   Precedence buttons wrap, and this user doesn't want wrapping on a form
   (reserved for actual paragraph text). Sized like #pressEntryModal/
   #editRecordModal's own overrides; the extra room is a bonus for the
   From/To Station/Name and Subject/Handling/Place-of-Origin field rows too. */
#formalMessageEntryModal .modalBox {
    width: 560px;
}

/* Wider than the default 340px .modalBox -- Severity (4 buttons) and
   Status (3 buttons) sit side-by-side in one .missingFormRow, and at the
   default width their button rows were actually overlapping each other's
   text, not just wrapping. Same modal markup exists on both NetControl's
   own map-pin version (#damageReportModal) and NetDamageAssessment's list
   page (#damageEntryModal) -- both need the same width. */
#damageReportModal .modalBox,
#damageEntryModal .modalBox {
    width: 720px;
}

/* Wide enough for the 4 Status buttons (Assigned/In Progress/Complete/
   Cancelled) to sit on one row without wrapping -- "In Progress" is
   longer than any of Damage Assessment's own severity/status labels,
   which is why this needs to be wider than that modal's 720px. */
#taskEntryModal .modalBox {
    width: 760px;
}

/* Same width standard as #taskEntryModal above -- Priority is a plain
   select here (not a 4-button group like Task's Status), but the ETA/
   Requested By and Contact Name/Phone field rows need the same extra room. */
#mutualAidEntryModal .modalBox {
    width: 760px;
}

.formalMessagePreviewCell {
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.formalMessagePrecedenceBtn {
    padding: 6px 14px;
    opacity: 0.6;
    white-space: nowrap;
}

.formalMessagePrecedenceBtn-routine {
    border-color: var(--accent-grey);
    color: var(--accent-grey);
}

.formalMessagePrecedenceBtn-priority {
    border-color: var(--accent-blue);
    color: var(--accent-blue);
}

.formalMessagePrecedenceBtn-immediate {
    border-color: var(--accent-amber);
    color: var(--accent-amber);
}

.formalMessagePrecedenceBtn-emergency {
    border-color: var(--accent-red);
    color: var(--accent-red);
}

.formalMessagePrecedenceBtn.selected {
    opacity: 1;
    font-weight: 600;
}

.formalMessagePrecedenceBtn-routine.selected {
    background-color: var(--accent-grey);
    color: var(--bg);
}

.formalMessagePrecedenceBtn-priority.selected {
    background-color: var(--accent-blue);
    color: var(--bg);
}

.formalMessagePrecedenceBtn-immediate.selected {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

.formalMessagePrecedenceBtn-emergency.selected {
    background-color: var(--accent-red);
    color: var(--bg);
}

.formalMessagePrecedenceBadge,
.formalMessageStatusBadge {
    display: inline-block;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.85em;
    font-weight: 600;
    white-space: nowrap;
}

.formalMessagePrecedenceBadge-routine {
    background-color: var(--accent-grey);
    color: var(--bg);
}

.formalMessagePrecedenceBadge-priority {
    background-color: var(--accent-blue);
    color: var(--bg);
}

.formalMessagePrecedenceBadge-immediate {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

.formalMessagePrecedenceBadge-emergency {
    background-color: var(--accent-red);
    color: var(--bg);
}

.formalMessageStatusBadge-filed {
    background-color: var(--accent-grey);
    color: var(--bg);
}

.formalMessageStatusBadge-sent {
    background-color: var(--accent-blue);
    color: var(--bg);
}

.formalMessageStatusBadge-delivered {
    background-color: var(--accent-green);
    color: var(--bg);
}

.formalMessageStatusBadge-undeliverable {
    background-color: var(--accent-red);
    color: var(--bg);
}

.formalMessageStatusActionBtn {
    margin-left: 6px;
    padding: 2px 8px;
    font-size: 0.85em;
}

/* Weather/Hazard Alert Feed banner (NetControl + ViewerNet) -- severity
   colors match WEATHER_ALERT_SEVERITY_COLORS in netcontrol.js/viewer.js,
   which drive the map polygons, so the banner and the map agree. */
.weatherAlertBanner {
    margin-bottom: 8px;
    padding: 8px 12px;
    border: 1px solid var(--accent-amber);
    border-radius: 4px;
    background: rgba(240, 168, 60, 0.08);
}

.weatherAlertBannerHeader {
    display: flex;
    align-items: center;
    gap: 6px;
    font-weight: 600;
}

.weatherAlertBannerList {
    list-style: none;
    margin: 6px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.weatherAlertItem {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 2px 6px;
    border-left: 3px solid var(--accent-grey);
    font-size: 0.9em;
}

.weatherAlertItemDot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
}

.weatherAlertItem-severity-extreme  { border-left-color: var(--accent-red); }
.weatherAlertItem-severity-severe   { border-left-color: var(--accent-amber); }
.weatherAlertItem-severity-moderate { border-left-color: var(--accent-yellow); }
.weatherAlertItem-severity-minor    { border-left-color: var(--accent-teal); }
.weatherAlertItem-severity-unknown  { border-left-color: var(--accent-grey); }

.weatherAlertItemEvent {
    font-weight: 600;
}

/* Current Weather Conditions widget -- position:absolute anchored to #map
   (not position:fixed like #mapContextMenu, which is viewport-relative and
   deliberately one-shot-positioned per right-click, never draggable).
   z-index sits above map tiles/markers but below every .modalOverlay
   (1000), so the modal always wins visually if somehow both are showing. */
.weatherConditionsWidget {
    position: absolute;
    z-index: 50;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 6px 10px;
    background-color: var(--panel-bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
    cursor: grab;
    user-select: none;
    touch-action: none; /* required for pointer events to receive move deltas on touchscreens instead of the browser panning */
    font-size: 0.9em;
    min-width: 90px;
}

.weatherConditionsWidget.dragging {
    cursor: grabbing;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.55);
}

.weatherConditionsWidgetTemp {
    font-size: 1.3em;
    font-weight: 700;
}

.weatherConditionsWidgetWind {
    color: var(--text-muted);
    font-size: 0.85em;
}

/* Small wind-direction arrow shown inline before the wind text, both in the
   compact widget and the detail modal -- rotated per-reading via inline
   transform (see setWindArrow in netcontrol.js/viewer.js). Base glyph
   points up/north at 0deg. Sized in rem (not em) so it stays legible
   inside the widget's small 0.85em wind text instead of shrinking with it. */
.windArrow {
    display: inline-block;
    margin-right: 4px;
    font-size: 1.2rem;
    font-weight: 700;
    vertical-align: middle;
}

.weatherConditionsDetailList {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 12px 0;
}

.weatherConditionsDetailRow {
    display: flex;
    justify-content: space-between;
    gap: 12px;
}

.weatherConditionsDetailRow dt {
    color: var(--text-muted);
    font-size: 0.85em;
}

.weatherConditionsDetailRow dd {
    margin: 0;
    font-weight: 600;
    text-align: right;
}

#missingLogTable {
    min-width: 100%;
    max-width: none;
}

#missingLogTable td button {
    padding: 2px 6px;
    margin-right: 4px;
}

#missingLogTable td input[type="text"] {
    width: 100%;
    box-sizing: border-box;
    margin-right: 0;
}

#missingNotesModal .modalBox {
    width: auto;
}

#missingNotesTextarea {
    display: block;
    width: 4in;
    height: 2in;
    box-sizing: border-box;
    font-family: inherit;
    font-size: 1em;
    padding: 8px;
    margin: 12px 0;
    resize: vertical;
    background-color: var(--input-bg);
    color: var(--input-text);
    border: 1px solid var(--border);
    border-radius: 4px;
}

/* --- Possible-matches popups (Missing Persons "Log Report" duplicate
   check) --- wider than the default .modalBox since these show real
   record content, not just a couple of fields. */
#matchListModal .modalBox,
#matchDetailModal .modalBox {
    width: 480px;
    max-height: 80vh;
    overflow-y: auto;
    overflow-x: hidden;
}

/* overflow-x: hidden is explicit, not left to default -- setting only
   overflow-y here otherwise computes overflow-x to "auto" too (a real CSS
   Overflow Module rule: when one axis is non-visible and the other isn't
   set, the unset one becomes auto), which was rendering an unwanted
   horizontal scrollbar even though nothing here actually needs one. */
#matchListBody {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 12px 0;
    max-height: 50vh;
    overflow-y: auto;
    overflow-x: hidden;
}

/* Each candidate is a full-width button (not a table row) so the whole
   card is clickable, keyboard-focusable, and doesn't need a separate
   "View" link. */
.matchListRow {
    display: block;
    width: 100%;
    box-sizing: border-box;
    text-align: left;
    padding: 10px 12px;
    background-color: var(--bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    cursor: pointer;
}

.matchListRow:hover {
    border-color: var(--accent-teal);
}

.matchListRowName {
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 8px;
}

.matchListRowNet,
.matchListRowSummary {
    font-size: 0.85em;
    margin-top: 2px;
}

.matchDetailBackButton {
    background: none;
    border: none;
    color: var(--accent-teal);
    padding: 0;
    margin-bottom: 12px;
    cursor: pointer;
}

.matchDetailBackButton:hover {
    text-decoration: underline;
}

.matchDetailField {
    margin: 0 0 6px;
}

#matchDetailStatusLabel {
    display: block;
    margin: 12px 0 6px;
}

/* Missing Persons listing, grouped by Status into collapsed-by-default
   panels (see #missingLog in NetMissingPersons.cshtml) -- same expand/
   collapse mechanics as NetControl's own #resumeNetPanel (rotating
   triangle icon, display:none/block toggle), just full-width and
   class-based since there are three of these rather than one singleton. */
#missingStatusPanels {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.missingStatusPanelToggle {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    box-sizing: border-box;
    background: none;
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 10px 14px;
    color: var(--text);
    font-size: 1em;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

.missingStatusPanelToggle:hover {
    border-color: var(--accent-teal);
}

.missingStatusPanelIcon {
    display: inline-block;
    transition: transform 0.15s ease;
}

.missingStatusPanel.expanded .missingStatusPanelIcon {
    transform: rotate(90deg);
}

.missingStatusPanelCount {
    color: var(--text-muted);
}

.missingStatusPanelBody {
    display: none;
}

.missingStatusPanel.expanded .missingStatusPanelBody {
    display: block;
    margin-top: 8px;
}

/* Welfare Check Inquiries triage tab -- own class set rather than reusing
   .missingStatusPanel* (same reasoning as the Resources Agency panel
   comment above: this page's own generic .missingStatusPanelToggle
   wiring loop assumes every such toggle's closest .missingStatusPanel
   ancestor exists, which this one-off collapsible section doesn't have).
   Visually identical to .missingStatusPanelToggle/-Icon. */
.welfareCheckPanelToggle {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    box-sizing: border-box;
    background: none;
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 10px 14px;
    color: var(--text);
    font-size: 1em;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

.welfareCheckPanelToggle:hover {
    border-color: var(--accent-teal);
}

.welfareCheckPanelIcon {
    display: inline-block;
    transition: transform 0.15s ease;
}

.welfareCheckPanelIcon.expanded {
    transform: rotate(90deg);
}

.welfareCheckInquiryCard {
    display: flex;
    flex-direction: column;
    gap: 4px;
    border: 1px solid var(--border);
    border-left: 4px solid var(--accent-grey);
    border-radius: 6px;
    padding: 10px 14px;
    margin-top: 8px;
}

.welfareCheckInquiryCard-needsreview {
    border-left-color: var(--accent-red);
}

.welfareCheckInquiryCard-nomatch {
    border-left-color: var(--accent-amber);
}

.welfareCheckInquiryCard-autoresolved {
    border-left-color: var(--accent-green);
}

.welfareCheckInquiryActions {
    display: flex;
    gap: 8px;
    margin-top: 6px;
}

.missingGroupTable {
    min-width: 100%;
    max-width: none;
}

.missingGroupTable td button {
    padding: 2px 6px;
    margin-right: 4px;
}

.missingGroupTable td input[type="text"] {
    width: 100%;
    box-sizing: border-box;
    margin-right: 0;
}

/* Edit modal -- wider than the default .modalBox to comfortably fit the
   same multi-row .formField layout the composer uses. */
#editRecordModal .modalBox {
    width: 520px;
}

#editNotesInput {
    display: block;
    width: 100%;
    min-height: 3em;
    box-sizing: border-box;
    font-family: inherit;
    font-size: 1em;
    padding: 8px;
    resize: vertical;
    background-color: var(--input-bg);
    color: var(--input-text);
    border: 1px solid var(--border);
    border-radius: 4px;
}

.netCleanupActions {
    display: flex;
    gap: 8px;
    margin-bottom: 24px;
}

.dangerButton {
    background-color: var(--accent-red);
    border-color: var(--accent-red);
    color: var(--bg);
    font-weight: 600;
}

/* --- Resources Lookup — NOT net-scoped, unlike everything else in this
   app. Agency panels reuse .missingStatusPanel and .missingStatusPanelBody
   from Missing Persons verbatim (same expand/collapse mechanics),
   independent toggles like Media Relations' own panels rather than
   Missing Persons' own accordion (there's no privacy reason to close one
   agency when opening another here). --- */
#resourcesHeader {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
    margin-bottom: 16px;
}

#resourcesHeader h2 {
    color: var(--accent-amber);
}

#resourcesToolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-top: 10px;
}

#resourcesSearchInput {
    flex: 1;
    min-width: 240px;
}

#resourcesAgencyAdd {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 16px;
    background-color: var(--panel-bg);
    margin-bottom: 16px;
}

#addAgencyRow {
    display: flex;
    align-items: center;
    gap: 8px;
}

#resourcesAgencyList {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.resourcesAgencyPanelHeader {
    display: flex;
    align-items: center;
    gap: 8px;
}

.resourcesAgencyPanelHeader .missingStatusPanelToggle {
    flex: 1;
}

.resourcesAgencyActions {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
    padding-right: 8px;
}

.resourcesTypeGroup {
    margin-bottom: 12px;
    /* Indents the whole Category group (heading + its resource rows) from
       the Agency row above it -- the resource rows' own indent under the
       category heading is untouched, since this shifts the group as a
       whole rather than the rows individually. */
    margin-left: 20mm;
}

/* Smaller/quieter than the Agency-level toggle it's nested under --
   collapsed by default, same independent-multi-open behavior as the
   Agency panels themselves (see expandedTypeGroups in resources.js).
   Own class set rather than reusing .missingStatusPanel/.missingStatusPanelBody
   a second time nested inside itself -- that class's rules use a
   descendant selector, so two nesting levels of it made the outer
   Agency panel's "expanded" state reveal every inner body regardless of
   its own Contact Type toggle. */
.resourcesTypeGroupToggle {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    box-sizing: border-box;
    background: none;
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 6px 10px;
    color: var(--accent-teal);
    font-size: 0.9em;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}

.resourcesTypeGroupToggle:hover {
    border-color: var(--accent-teal);
}

.resourcesTypeGroupIcon {
    display: inline-block;
    transition: transform 0.15s ease;
}

.resourcesTypeGroup.expanded .resourcesTypeGroupIcon {
    transform: rotate(90deg);
}

.resourcesTypeGroupCount {
    color: var(--text-muted);
}

.resourcesTypeGroupBody {
    display: none;
}

.resourcesTypeGroup.expanded .resourcesTypeGroupBody {
    display: block;
    margin-top: 8px;
}

.resourcesResourceRow {
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 8px 10px;
    margin-bottom: 6px;
}

.resourcesResourceMain {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}

.resourcesResourceName {
    font-weight: 600;
    margin-right: 4px;
}

.resourcesContactLink {
    color: var(--accent-teal);
    text-decoration: none;
    font-size: 0.9em;
    white-space: nowrap;
}

.resourcesContactLink:hover {
    text-decoration: underline;
}

.resourcesUnverifiedFlag {
    font-size: 0.72em;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--warning-text);
    background: var(--warning-bg);
    border: 1px solid var(--warning-border);
    border-radius: 3px;
    padding: 1px 5px;
    white-space: nowrap;
    cursor: help;
}

.resourcesResourceComment {
    margin: 6px 0 0;
}

.resourcesResourceActions {
    display: inline-flex;
    gap: 4px;
    margin-left: auto;
}

#resourceModal .modalBox {
    width: 520px;
}

#addCrewMemberModal .modalBox {
    width: 420px;
}

/* Crew Maintenance -- same header/toolbar/row shape as Resources above,
   just flat (no Agency nesting) since a crew roster has only one level. */
#crewHeader {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
    margin-bottom: 16px;
}

#crewHeader h2 {
    color: var(--accent-amber);
}

#crewToolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-top: 10px;
}

#crewSearchInput {
    flex: 1;
    min-width: 240px;
}

/* Grouped by default/home Asset into collapsed-by-default panels -- same
   accordion shape as Resources' own Agency grouping, see
   crewmaintenance.js's buildAssetGroupPanel. */
#crewMemberList {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.crewMemberRow {
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 8px 10px;
    margin-bottom: 6px;
}

.crewMemberRetired {
    opacity: 0.6;
}

.crewMemberMain {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}

.crewMemberName {
    font-weight: 600;
    margin-right: 4px;
}

.crewMemberActions {
    display: inline-flex;
    gap: 4px;
    margin-left: auto;
}

#resourceCommentInput {
    display: block;
    width: 100%;
    min-height: 3em;
    box-sizing: border-box;
    font-family: inherit;
    font-size: 1em;
    padding: 8px;
    resize: vertical;
    background-color: var(--input-bg);
    color: var(--input-text);
    border: 1px solid var(--border);
    border-radius: 4px;
}

#resourcePhoneRows,
#resourceEmailRows {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 6px;
}

.resourcesContactRow {
    display: flex;
    gap: 6px;
}

/* #resourcePhoneRows/#resourceEmailRows (ID) scoping is required here, not
   just the .resourcesContact*Input classes alone -- .formField input[type=
   "text"] (used for the Media Relations/date-field fixes earlier this
   session) has higher specificity than a single class, and was silently
   winning here: both inputs rendered at width:100%, squeezing the value
   field down to nothing since the label claimed the full row first. */
#resourcePhoneRows .resourcesContactLabelInput,
#resourceEmailRows .resourcesContactLabelInput {
    width: 100px;
    flex-shrink: 0;
}

#resourcePhoneRows .resourcesContactValueInput,
#resourceEmailRows .resourcesContactValueInput {
    flex: 1 1 50%;
    width: auto;
    min-width: 0;
}

#resourceTypeModal .modalBox {
    width: 360px;
}

#resourceTypeListRows {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 10px;
    max-height: 40vh;
    overflow-y: auto;
}

.resourceTypeRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 4px 8px;
}

#resourceTypeAddRow {
    display: flex;
    gap: 6px;
}

/* Resource Status Board (NetControl) -- the ICS-211/219 "T-Card" panel.
   See Models/NetResourceStatus.cs; #resourceStatusSection follows the same
   .panelHeader/.panelDragHandle/.panelTitle shape as #mapSection/
   #rosterSection. Redesigned from 3 status columns of full-size cards to
   one wrapping grid of compact colored tiles -- color alone conveys
   status now, so a busy net with a dozen-plus Assets doesn't need to
   scroll through tall cards to scan them all. */
#addNetResourceButton {
    margin-left: auto;
}

/* Pushes the countdown + button together as one right-flush group --
   #parCountdown always renders immediately before #takeParButton (see
   NetControl.cshtml), so giving the auto margin to the first of the pair
   keeps them adjacent (gap: 8px from .panelHeader) rather than pushing
   just the button and leaving the countdown stranded next to the
   heading. */
#parCountdown {
    margin-left: auto;
}

/* Each row: Callsign — Name on the left, the three-way status button
   group flush right. Rows aren't tables -- flex is enough for two
   columns and keeps the button group from wrapping oddly on a narrow
   modal. */
.parRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 4px;
    border-bottom: 1px solid var(--border);
}

.parRow:last-child {
    border-bottom: none;
}

.parRowLabel {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.parButtonGroup {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
}

.parStatusBtn {
    font-size: 0.85em;
    padding: 3px 8px;
}

.parStatusBtn.selected {
    font-weight: 600;
}

/* No blink/pulse treatment here (unlike BA Control's always-visible
   dashboard tiles) -- this modal is actively worked through in one
   sitting, and an unmarked row simply hasn't been given any selected
   color yet, which already stands out against its two marked
   neighbors. */
.parStatusBtn-accounted.selected { background-color: var(--accent-green); color: var(--bg); border-color: var(--accent-green); }
.parStatusBtn-nocontact.selected { background-color: var(--accent-red); color: var(--bg); border-color: var(--accent-red); }
.parStatusBtn-notapplicable.selected { background-color: var(--accent-grey); color: var(--bg); border-color: var(--accent-grey); }

/* No "PAR due" text label -- the blinking border alone is the due
   signal. Same hard on/off shape as BA Control's critical-tier blink
   (baControlCriticalBlink), just amber instead of red. */
.takeParButton-due {
    border-width: 2px;
    animation: takeParButtonDueBlink 0.9s steps(1, end) infinite;
}

@media (prefers-reduced-motion: reduce) {
    .takeParButton-due {
        animation: none;
        border-color: var(--accent-amber);
    }
}

@keyframes takeParButtonDueBlink {
    0%, 49% { border-color: var(--accent-amber); box-shadow: 0 0 0 2px rgba(240, 168, 60, 0.45); }
    50%, 100% { border-color: var(--border); box-shadow: none; }
}

.resourceStatusBoard {
    display: flex;
    flex-wrap: wrap;
    gap: 2mm;
}

/* mm units match this file's existing precedent (.resourcesTypeGroup). */
.resourceStatusPanel {
    width: 30mm;
    height: 13mm;
    box-sizing: border-box;
    padding: 1mm 2mm;
    border-radius: 4px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: hidden;
    position: relative;
}

/* Green (Assigned, actively deployed) and red (Out of Service, a genuine
   operational warning) are a deliberate departure from this app's usual
   "green = ready/good" convention -- confirmed intentional: green here
   reads as "in active use," not "available." Available itself gets amber
   -- sitting in reserve, neither a problem nor actively tasked. Staged
   (pre-positioned but not yet formally assigned) gets teal, an existing
   accent color not otherwise used for status, sitting between Available
   and Assigned. */
.resourceStatusPanel-assigned {
    background-color: var(--accent-green);
    color: var(--bg);
}

.resourceStatusPanel-outofservice {
    background-color: var(--accent-red);
    color: var(--bg);
}

.resourceStatusPanel-available {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

.resourceStatusPanel-staged {
    background-color: var(--accent-teal);
    color: var(--bg);
}

.resourceStatusPanel.draggingResourcePanel {
    opacity: 0.4;
}

.resourceStatusPanelName,
.resourceStatusPanelSubtitle {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.resourceStatusPanelName {
    font-weight: 600;
    font-size: 0.75em;
}

.resourceStatusPanelSubtitle {
    font-size: 0.65em;
    opacity: 0.85;
}

/* Restricted to logged-in operators, same as the drag handle -- see
   buildResourceStatusPanel's canManage guard. */
.resourceStatusPanelCrewRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 0.65em;
    margin-top: 0.5mm;
}

.resourceStatusPanelCrewButton {
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    font-size: 1.1em;
    line-height: 1;
    cursor: pointer;
    color: inherit;
}

.resourceStatusPanelAprsButton {
    background: none;
    border: none;
    padding: 0;
    margin: 0 0 0 6px;
    font-size: 1.1em;
    line-height: 1;
    cursor: pointer;
    color: inherit;
}

/* Green = tagged and refreshed within the last 15 minutes; amber =
   tagged but stale (never refreshed, or older than that); no class at
   all = no APRS callsign tagged, stays the button's normal default
   color. Same modifier classes on both the roster row's button and the
   Resource Status Board tile's own (see updateAprsButtonState). */
.aprsButton--fresh,
.resourceStatusPanelAprsButton.aprsButton--fresh {
    color: var(--accent-green);
}

.aprsButton--stale,
.resourceStatusPanelAprsButton.aprsButton--stale {
    color: var(--accent-amber);
}

.aprsButton--very-stale,
.resourceStatusPanelAprsButton.aprsButton--very-stale {
    color: var(--accent-red);
}

/* Dedicated drag handle, not the tile body -- keeps a plain click (opens
   the popup) unambiguous from a drag gesture (reorders), same pattern as
   .panelDragHandle/the roster's own row handles. */
.resourceStatusPanelDragHandle {
    position: absolute;
    top: 0;
    right: 1mm;
    font-size: 0.7em;
    opacity: 0.7;
    cursor: grab;
}

/* Same vertical-strip shape as #mapContextMenu -- .resourceStatusPopup
   just sets the width, every button inside inherits .dropdownMenu's
   full-width/left-aligned/hover-highlight button styling. */
.resourceStatusPopup {
    width: 180px;
}

/* Plain text (inherits .dropdownMenu's default near-white) like every
   other item in this strip and in #mapContextMenu -- color only shows up
   as a filled background on whichever status is currently selected, not
   as a permanent tint on the others. */
.resourceStatusPopupStatusBtn {
    display: block;
    width: 100%;
    box-sizing: border-box;
}

.resourceStatusPopupStatusBtn.selected {
    font-weight: 600;
}

.resourceStatusPopupStatusBtn-assigned.selected { background-color: var(--accent-green); color: var(--bg); }
.resourceStatusPopupStatusBtn-outofservice.selected { background-color: var(--accent-red); color: var(--bg); }
.resourceStatusPopupStatusBtn-available.selected { background-color: var(--accent-amber); color: var(--accent-amber-dark); }
.resourceStatusPopupStatusBtn-staged.selected { background-color: var(--accent-teal); color: var(--bg); }

/* Repeater/Frequency Status Board's own popup. Widened from the original
   .resourceStatusPopup's 180px strip -- a shared-location click can stack
   several repeaters' worth of rows (see buildFrequencyPopupSection), and
   each row's status buttons run horizontally rather than one-per-line, so
   the popup needs real width to hold them. Capped height + scroll as a
   backstop for an unusually crowded site; actual on-screen placement is
   clamped in JS (see openFrequencyStatusPopup) so it can't run off any
   edge regardless of where the click landed. */
.frequencyStatusPopup {
    width: 320px;
    max-height: 80vh;
    overflow-y: auto;
}

.frequencyStatusPopupStatusBtn-primary.selected { background-color: var(--accent-green); color: var(--bg); }
.frequencyStatusPopupStatusBtn-backup.selected { background-color: var(--accent-teal); color: var(--bg); }
.frequencyStatusPopupStatusBtn-degraded.selected { background-color: var(--accent-amber); color: var(--accent-amber-dark); }
.frequencyStatusPopupStatusBtn-down.selected { background-color: var(--accent-red); color: var(--bg); }

/* One row per repeater -- info line on top, its own status/clear/notes
   buttons as a horizontal wrapping strip underneath, instead of the
   original one-button-per-line stack (which is what ran a 3+ repeater
   popup off the bottom of the screen). */
.frequencyStatusPopupRow {
    padding: 6px 10px;
}

.frequencyStatusPopupHeader {
    font-size: 0.8em;
    color: var(--text-muted);
    line-height: 1.3;
    margin-bottom: 4px;
}

.frequencyStatusPopupRowButtons {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

/* Overrides .dropdownMenu button's block/full-width default -- only inside
   this row-buttons strip, so the Resource Status Board's own popup (same
   .resourceStatusPopupStatusBtn/.resourceStatusPopupDeleteBtn classes,
   still one-per-line there) is untouched. */
.frequencyStatusPopupRowButtons button {
    display: inline-flex;
    width: auto;
    flex: 1 1 auto;
    white-space: nowrap;
    padding: 4px 8px;
    font-size: 0.85em;
    justify-content: center;
}

.frequencyStatusPopupRowNotes {
    margin-top: 4px;
}

.frequencyStatusPopupDivider {
    border-top: 1px solid var(--border);
    margin: 4px 0;
}

.resourceStatusPopupDeleteBtn {
    display: block;
    width: 100%;
    box-sizing: border-box;
}

/* Collapsed by default -- "Add notes" reveals this right below itself
   (see openResourceStatusPopup), rather than an always-visible field
   there's no room for on a 10mm-tall tile. */
.resourceStatusPopupNotesSection {
    display: none;
    padding: 6px 10px;
}

.resourceStatusPopupNotesSection.visible {
    display: block;
}

.resourceStatusPopupNotesInput {
    width: 100%;
    box-sizing: border-box;
    font-size: 0.85em;
    margin-bottom: 6px;
}

/* Fills #mapContextEditCallsignPopup's own .dropdownMenu padding instead of
   the browser's default ~20-character input width -- same reasoning as
   .resourceStatusPopupNotesInput just above. */
#mapContextEditCallsignInput {
    width: 100%;
    box-sizing: border-box;
    margin-right: 0;
}

.resourceStatusPopupNotesActions {
    display: flex;
    gap: 6px;
}

.resourceStatusPopupNotesActions button {
    flex: 1;
    text-align: center;
}

/* Hover-history tooltip -- a custom element (not the native title
   attribute) since it needs a real multi-line list; pointer-events:none so
   it can never itself become a mouseleave target and get stuck open. */
.resourceStatusHistoryTooltip {
    display: none;
    position: fixed;
    background-color: var(--panel-bg);
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 6px 10px;
    font-size: 0.8em;
    z-index: 1000;
    pointer-events: none;
    max-width: 240px;
}

.resourceStatusHistoryTooltip.visible {
    display: block;
}

.resourceStatusHistoryTooltipSubtitle {
    color: var(--text-muted);
    margin-bottom: 4px;
}

/* --- BA Control -- boxed panels + amber title, same convention as every
   other module page (Scribe/Press/Missing/Damage). --- */
#baControlPicker,
#baControlNotFound,
#baControlHeader,
#baControlBoard,
#baControlDisabledNotice {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
    margin-bottom: 16px;
}

#baControlPicker h2,
#baControlHeader h2 {
    color: var(--accent-amber);
}

.baControlLockedNotice {
    color: var(--accent-red);
}

#baControlTopBar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 12px;
}

#baControlPrintLink {
    display: inline-block;
    background-color: #1b2439;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 6px 14px;
    text-decoration: none;
    cursor: pointer;
}

#baControlPrintLink:hover {
    border-color: var(--accent-teal);
}

/* #baControlHeader's h2/button share Resource Status Board's own
   .panelHeader row shape -- the add button pins to the far right of the
   title line instead of sitting in its own row, matching every other
   board-style panel's "+ Add ..." placement. */
#addBaControlEntryButton {
    margin-left: auto;
}

.baControlGroupHeading {
    color: var(--text-muted);
    font-size: 0.85em;
    margin: 12px 0 6px;
}

.baControlGroupRow {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 8px;
}

.baControlTile {
    width: 170px;
    padding: 10px 12px;
    border-radius: 8px;
    background-color: var(--bg);
    border: 1px solid var(--border);
    cursor: pointer;
}

.baControlTile-green { border-color: var(--accent-green); }
.baControlTile-amber { border-color: var(--accent-amber); }
.baControlTile-red { border-color: var(--accent-red); }

.baControlTile-emergency {
    border-color: var(--accent-red);
    background-color: rgba(232, 131, 122, 0.15);
    animation: baControlEmergencyPulse 1.4s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
    .baControlTile-emergency {
        animation: none;
    }
}

@keyframes baControlEmergencyPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(232, 131, 122, 0.5); }
    50% { box-shadow: 0 0 0 6px rgba(232, 131, 122, 0); }
}

.baControlTile-exited {
    opacity: 0.6;
}

/* A more urgent tier than the static red border alone -- 20%-or-less
   remaining blinks the outline on/off (a hard toggle, not the Emergency
   state's smooth pulse) so a nearly-out member stands out from a merely
   low one at a glance. */
.baControlTile-critical {
    border-width: 2px;
    animation: baControlCriticalBlink 0.9s steps(1, end) infinite;
}

@media (prefers-reduced-motion: reduce) {
    .baControlTile-critical {
        animation: none;
        border-color: var(--accent-red);
    }
}

@keyframes baControlCriticalBlink {
    0%, 49% { border-color: var(--accent-red); box-shadow: 0 0 0 2px rgba(232, 131, 122, 0.45); }
    50%, 100% { border-color: var(--border); box-shadow: none; }
}

.baControlTileName {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 6px;
    font-weight: 600;
    margin-bottom: 6px;
}

/* Right-justified via the .baControlTileName flex row above -- a sharp
   on/off flash (not the softer critical-band blink) so a Mayday-flagged
   member is unmistakable even at a glance across the room. */
.baControlTileSOS {
    color: #fff;
    font-size: 0.75em;
    font-weight: 700;
    letter-spacing: 0.05em;
    animation: baControlSOSFlash 0.6s steps(1, end) infinite;
}

@media (prefers-reduced-motion: reduce) {
    .baControlTileSOS {
        animation: none;
    }
}

/* 80% on / 20% off -- reads as "mostly lit, briefly blanking" rather
   than an even 50/50 flash. */
@keyframes baControlSOSFlash {
    0%, 79% { opacity: 1; }
    80%, 100% { opacity: 0; }
}

.baControlTileTag {
    font-size: 0.75em;
    color: var(--text-muted);
    margin-bottom: 4px;
}

.baControlSlider {
    position: relative;
    height: 10px;
    border-radius: 5px;
    background-color: var(--border);
    overflow: hidden;
    margin-bottom: 6px;
}

.baControlSliderFill {
    height: 100%;
    transition: width 1s linear;
}

.baControlSliderFill-green { background-color: var(--accent-green); }
.baControlSliderFill-amber { background-color: var(--accent-amber); }
.baControlSliderFill-red { background-color: var(--accent-red); }
.baControlSliderFill-emergency { background-color: var(--accent-red); }

/* Marks the "Rule of Thirds" 1/3-remaining threshold on the slider. */
.baControlSliderThirdMark {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 33.33%;
    width: 1px;
    background-color: rgba(0, 0, 0, 0.35);
}

.baControlTileReading {
    font-size: 0.8em;
    color: var(--text-muted);
}

.baControlPendingRow {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.9em;
    margin-bottom: 4px;
}

.baControlPendingRowName {
    flex: 1 1 auto;
    white-space: nowrap;
}

.baControlPendingRowTagInput {
    flex: 0 0 auto;
    width: 110px;
}

.baControlPendingRow button {
    flex: 0 0 auto;
    padding: 2px 6px;
    white-space: nowrap;
}

#baControlEntryPopup {
    min-width: 300px;
    gap: 4px;
    /* .modalBox's own white-outline convention -- this popup isn't a
       .modalBox (it's .dropdownMenu.mapContextMenu, positioned off a
       tile click rather than centered), so it doesn't inherit that base
       rule and needs it pinned here explicitly. */
    border: 2px solid #ffffff;
    border-radius: 10px;
}

.baControlPopupFieldRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 2px 10px;
}

.baControlPopupFieldRow label {
    white-space: nowrap;
}

.baControlPopupFieldRow input {
    width: 100px;
    flex: 0 0 auto;
}

/* Drops the native number-input spin buttons so the % field matches
   Tag/Bottle's plain text box exactly -- same height/width, no stray
   up/down arrows narrowing its usable width. */
#baControlPopupPercentInput {
    -moz-appearance: textfield;
}

#baControlPopupPercentInput::-webkit-outer-spin-button,
#baControlPopupPercentInput::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.baControlPopupActions {
    display: flex;
    gap: 4px;
    padding: 4px 10px;
}

.baControlPopupActions button {
    flex: 1 1 auto;
    padding: 4px 6px;
    font-size: 0.8em;
    white-space: nowrap;
    text-align: center;
}

/* .dropdownMenu button's own background:none/border rule otherwise wins
   on specificity over the bare .dangerButton class -- pinned explicitly
   here so Remove still renders as a red action inside this popup. */
#baControlEntryPopup .dangerButton {
    background-color: var(--accent-red);
    border-color: var(--accent-red);
    color: var(--bg);
}

#baControlPopupName {
    font-weight: 600;
    padding: 4px 10px;
}

#baControlPopupReading {
    font-size: 0.85em;
    color: var(--text-muted);
    padding: 0 10px 6px;
}

.resourceStatusEmpty {
    color: var(--text-muted);
    font-style: italic;
}

/* ViewerNet's read-only Communications Plan list -- same muted-italic
   empty state as .resourceStatusEmpty above, plain list otherwise (no
   special layout needed, just Function: Channel lines). */
.communicationsPlanEmpty {
    color: var(--text-muted);
    font-style: italic;
}

.communicationsPlanList {
    list-style: none;
    padding: 0;
    margin: 0;
}

.communicationsPlanList li {
    padding: 4px 0;
    border-bottom: 1px solid var(--border);
}

.communicationsPlanList li:last-child {
    border-bottom: none;
}

/* Add Resource picker modal */
#addNetResourceSearchInput {
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 8px;
}

.addNetResourceResults {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 40vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.addNetResourceResultRow {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* Checkbox row in the Asset picker -- checking one only stages it
   (stagedAssetIds in netcontrol.js), nothing reaches the board until
   Done. The checkbox is disabled (not hidden) for an asset already on
   this net's board, same "can't re-add, here's why" spirit the old
   disabled-button version had. */
.addNetResourceCheckboxLabel {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    cursor: pointer;
}

.addNetResourceCheckboxLabel input[type="checkbox"] {
    flex-shrink: 0;
}

.addNetResourceCheckboxLabel span {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    min-width: 0;
}

.addNetResourceCheckboxLabel span span {
    font-size: 0.85em;
    color: var(--text-muted);
}

.addNetResourceRowIconBtn {
    background: none;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    padding: 4px 6px;
    flex-shrink: 0;
}

/* flex-wrap is the same safety net as #addNetResourceCreateRow below --
   the Company select's option text (e.g. "Firehall #1 (City of Surrey)")
   can be wide enough that a single unwrapped row pushes Save/Cancel off
   the edge of the modal, forcing a horizontal scrollbar to reach them.
   Wrapping instead of shrinking-to-illegible keeps everything reachable
   at the modal's normal width. */
.addNetResourceRenameForm {
    display: flex;
    flex: 1;
    flex-wrap: wrap;
    gap: 4px;
    align-items: center;
}

.addNetResourceRenameForm input,
.addNetResourceRenameForm select {
    min-width: 0;
    flex: 1 1 100px;
}

.addNetResourceRenameForm button {
    flex-shrink: 0;
}

.addNetResourceEmpty {
    color: var(--text-muted);
    font-style: italic;
    padding: 6px 0;
}

/* Wider than the base 340px .modalBox -- at the default width, the three
   create-row inputs below (Name/Owner/Category) plus the Create button
   were squeezed down to a few characters each, clipping their own
   placeholder text unreadable. */
#addNetResourceModal .modalBox {
    width: 540px;
}

/* Comms Plan's repeater picker -- a real multi-column table needs more
   room than the 540px search-and-list pattern above. Widened a further
   25mm (~95px) from its original 640px so Callsign/Name/Frequency/Band/
   Mode fit without a horizontal scrollbar (Band/Mode both matter for
   picking the right row -- e.g. Fusion vs FM on the same callsign -- so
   neither can be the one dropped to make room; Location was dropped
   instead, see renderCommsPlanRepeaterPickerRows). */
.commsPlanRepeaterPickerModalBox {
    width: 735px;
    max-width: 90vw;
}

.commsPlanRepeaterPickerSearchInput {
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 10px;
}

.commsPlanRepeaterPickerTableScroll {
    max-height: 50vh;
    overflow-y: auto;
    overflow-x: auto;
}

.commsPlanRepeaterPickerTable {
    min-width: 100%;
    border-collapse: collapse;
}

.commsPlanRepeaterPickerTable th,
.commsPlanRepeaterPickerTable td {
    padding: 6px 10px;
    border-bottom: 1px solid var(--border);
    text-align: left;
    white-space: nowrap;
}

/* One click selects and closes (see renderCommsPlanRepeaterPickerRows) --
   the whole row is the click target, not just a button inside it. */
.commsPlanRepeaterPickerRow {
    cursor: pointer;
}

.commsPlanRepeaterPickerRow:hover {
    background-color: #1b2439;
}

/* "Add a new asset" mini-form at the bottom of the picker/manage modal.
   flex-wrap is the safety net (guarantees every field stays readable no
   matter how narrow the modal/viewport gets, same reasoning as
   .eocTemplateCategoryEditRow above); the explicit flex-basis values are
   what actually make one line fit at the modal's normal 480px width,
   rather than the browser's default text-input intrinsic width deciding it. */
#addNetResourceCreateRow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--border);
}

#addNetResourceCreateRow input {
    flex: 1 1 140px;
    min-width: 110px;
}

#addNetResourceCreateRow button {
    flex-shrink: 0;
}

#crewFreeTextAddRow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 4px;
}

#crewFreeTextAddRow input {
    flex: 1 1 140px;
    min-width: 110px;
}

#crewFreeTextAddRow button {
    flex-shrink: 0;
}

/* Company must be picked before any assets show -- see
   onCompanyFilterChanged in netcontrol.js -- so this sits right under the
   "+ New Company" link, full-width like the search box below it. */
#assetCompanyFilterSelect {
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 8px;
}

/* "+ New Company" -- a link rather than a button, since it just opens
   addCompanyModal "on top of" this one rather than doing anything itself.
   Own line, small top margin so it doesn't crowd the subtitle above it. */
.modalInlineLink {
    display: inline-block;
    color: var(--accent-teal);
    text-decoration: none;
    font-size: 0.9em;
    margin: 4px 0 12px;
}

.modalInlineLink:hover {
    text-decoration: underline;
}

#addCompanyModal input,
#addCompanyModal select {
    width: 100%;
    box-sizing: border-box;
    margin-top: 8px;
}

/* Small uppercase label distinguishing the "New Company" mini-form from
   the "New Asset" one right below it -- own full-width flex row so it
   always starts a new line rather than wrapping in among the fields. */
.modalMiniSectionLabel {
    flex: 1 0 100%;
    font-size: 0.75em;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* After-Action Report -- the post-incident review note captured when a net
   ends (see afterActionModal) and displayed once Ended/Finished. */
#afterActionReportInput,
#afterActionReportEditInput {
    width: 100%;
    box-sizing: border-box;
}

.afterActionReportDisplay:empty {
    display: none;
}

.afterActionReportDisplay h3 {
    margin: 12px 0 4px 0;
    color: var(--accent-amber);
}

.afterActionReportText {
    white-space: pre-wrap;
    margin: 0 0 6px 0;
}

/* Personal accounts (Register/Login/Account) -- a slightly wider centered
   panel than NetCleanup/CommandOverview's own login boxes, since these
   carry a real form (username/password/org choice) rather than just one
   password field. */
#accountAuthPanel {
    max-width: 420px;
    margin: 40px auto 0;
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 20px 24px;
    background-color: var(--panel-bg);
}

#accountAuthPanel h2 {
    color: var(--accent-amber);
    margin-top: 0;
}

#orgModeFieldset {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 10px 12px;
    margin: 12px 0;
}

#orgModeFieldset legend {
    padding: 0 6px;
    color: var(--text-muted);
}

/* Log Out sits on the same line as Copy/Regenerate, pushed to the far
   right -- same "primary action sits far-right" convention as this
   app's .modalActions (see [[project_modal_button_alignment]]), applied
   here even though this isn't a modal. */
#accountActionsRow {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

#accountJoinCodeRow {
    display: flex;
    align-items: center;
    gap: 8px;
}

#accountJoinCodeDisplay {
    font-size: 1.1em;
    letter-spacing: 0.05em;
    padding: 4px 8px;
    border: 1px solid var(--border);
    border-radius: 4px;
    background-color: var(--bg);
}

.orgMemberRow {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 6px 0;
    border-bottom: 1px solid var(--border);
}

.orgMemberRow:last-child {
    border-bottom: none;
}

/* Wider than the base 420px card -- that size is right for Login/Register/
   ForgotPassword/ResetPassword/VerifyEmail (which all share #accountAuthPanel
   too), but too cramped once My Account's own Profile/Email/Password/
   Organization sections are added. Compound selector (id+class) so it wins
   over the bare #accountAuthPanel rule regardless of source order. */
#accountAuthPanel.accountAuthPanel-wide {
    max-width: 640px;
}

#accountAuthPanel h3 {
    margin-top: 24px;
    margin-bottom: 4px;
    color: var(--accent-teal);
    font-size: 1em;
    border-top: 1px solid var(--border);
    padding-top: 16px;
}

.emailVerifiedBadge-yes,
.emailVerifiedBadge-no {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 4px;
    font-size: 0.85em;
    font-weight: 600;
}

.emailVerifiedBadge-yes {
    background-color: var(--accent-green);
    color: var(--bg);
}

.emailVerifiedBadge-no {
    background-color: var(--accent-red);
    color: var(--bg);
}

/* Low-key nav status -- reuses header nav a's own teal/margin styling for
   the link itself (no override needed), just a subtle left divider so it
   doesn't visually blend into the New Net/Viewer/Directory Lookup group
   it's not part of. */
.navAccountStatus {
    margin-left: 16px;
    padding-left: 16px;
    border-left: 1px solid var(--border);
}

.navAccountStatus a {
    margin-left: 0 !important;
}

/* Directory Lookup Public/Your-Org badges -- Public reuses the muted
   grey treatment (matches this app's "final/closed, not alarming" use of
   grey elsewhere, e.g. Missing Persons' Deceased); Your Org gets the
   teal accent since it's the operator's own editable data, the same hue
   this app already uses for nav/action links. */
.resourcesOrgBadge {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 10px;
    font-size: 0.7em;
    font-weight: 600;
    vertical-align: middle;
    margin: 0 4px;
}

.resourcesOrgBadge-public {
    background-color: var(--accent-grey);
    color: var(--bg);
}

.resourcesOrgBadge-org {
    background-color: var(--accent-teal);
    color: var(--bg);
}

/* Login/guest gate (NetControl + CommandOverview only, see
   NetControlModel.ShowLoginGate / CommandOverviewModel.ShowLoginGate) --
   Log In/Create Account are plain navigation links but need to read as
   equal-weight actions alongside the real "Continue as Guest" <button>,
   so they're styled to match button's own look rather than nested inside
   one (a <button> inside an <a> is invalid HTML). */
#loginGateActions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 16px;
}

#loginGateActions form {
    display: inline;
}

.loginGateButtonLink {
    display: inline-block;
    background-color: #1b2439;
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 6px 14px;
    text-decoration: none;
    font-size: 1em;
}

.loginGateButtonLink:hover {
    border-color: var(--accent-teal);
}

/* Repeater Directory / Maintenance page -- account-level, not net-scoped,
   same table/badge conventions as Missing Persons and Damage Assessment. */
/* Boxed-panel treatment matching Directory Lookup's #resourcesHeader --
   border/radius/background + an amber h2 title, the same convention every
   other multi-section page in this app uses per named panel. */
#repeaterMaintenanceHeader,
#mySubmissionsSection,
#reviewQueueSection,
#fullCatalogSection {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
    margin-bottom: 16px;
    box-sizing: border-box;
}

#repeaterMaintenanceHeader h2,
#mySubmissionsSection h2 {
    color: var(--accent-amber);
}

/* Tab bar picking which of the 3 boxed panels below is visible -- only
   rendered at all once there's more than one panel to choose from (a
   non-admin user only ever gets "My Submissions", so no tabs there). Same
   underlying pattern as .weatherUnitsBtn's selected/unselected toggle. */
.repeaterMaintenanceTabs {
    display: flex;
    gap: 6px;
    margin-bottom: 12px;
}

.repeaterTabButton {
    background: none;
    border: 1px solid var(--border);
    border-radius: 6px 6px 0 0;
    color: var(--text-muted);
    padding: 8px 16px;
    cursor: pointer;
    white-space: nowrap;
}

.repeaterTabButton:hover {
    color: var(--text);
    border-color: var(--accent-teal);
}

.repeaterTabButton.active {
    color: var(--accent-amber);
    border-color: var(--accent-amber);
    border-bottom-color: transparent;
    font-weight: 600;
}

/* Each search field grows to fill the row; its "List All" button never
   wraps onto its own line -- see [[feedback_no_wrap_on_forms]]. */
.repeaterMaintenanceSearchRow {
    display: flex;
    gap: 8px;
    margin-bottom: 8px;
}

.repeaterMaintenanceSearchRow input {
    flex: 1 1 auto;
    min-width: 0;
}

.repeaterMaintenanceSearchRow button {
    flex: 0 0 auto;
    white-space: nowrap;
}

/* Clickable Pending status in "My Submissions" -- jumps to that same
   record in the Review Queue tab. A plain button styled to look like the
   existing badge (not a link) since it's a same-page tab switch, not
   navigation. */
.repeaterStatusBadgeButton {
    border: none;
    margin: 0;
    cursor: pointer;
    font: inherit;
}

/* Momentary flash on the row a status-click jumped to, so it's findable
   in a long Review Queue instead of just silently being "somewhere in the
   list now visible." Fades out on its own -- no click needed to dismiss. */
@keyframes repeaterRowHighlightFade {
    from { background-color: rgba(240, 168, 60, 0.35); }
    to { background-color: transparent; }
}

.repeaterRowHighlight {
    animation: repeaterRowHighlightFade 2.5s ease-out;
}

#repeaterMaintenanceToolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-top: 10px;
}

.repeaterMaintenanceTableScroll {
    overflow-x: auto;
}

/* min-width, not width -- `width:100%` on a table-layout:auto table is
   only a hint Chrome ignores once content is narrower than the container
   (confirmed by direct measurement: with width:100%, all three tables
   sat at 600px inside 1060px-wide sections, wasting 428px/113mm of
   available room). min-width is a hard floor: the table fills at least
   100% of its container, and table-layout:auto still grows individual
   columns beyond that if content genuinely needs more, so nothing has to
   wrap or scroll under normal content. See [[feedback_no_wrap_on_forms]]. */
.repeaterMaintenanceTable {
    min-width: 100%;
    border-collapse: collapse;
}

.repeaterMaintenanceTable th,
.repeaterMaintenanceTable td {
    padding: 6px 10px;
    border-bottom: 1px solid var(--border);
    text-align: left;
    white-space: nowrap;
}

/* Actions cells (My Submissions/Review Queue/Full Catalog) carry 2-3
   buttons, kept on one line (nowrap, inherited from the base rule above)
   -- buttons must never wrap, see [[feedback_no_wrap_on_forms]]. Now that
   the table actually fills its container's width (min-width:100% above),
   there's always room for this without needing flex-wrap. */
.repeaterActionsCell button {
    display: inline-flex;
    margin: 2px 4px 2px 0;
}

/* Review Queue's Submitted column -- a calendar icon standing in for the
   full date/time, shown on hover/focus via the native title tooltip, to
   save horizontal room for the Actions column's 3 buttons. */
.repeaterDateIcon {
    cursor: help;
}

/* Review Queue's icon-only Edit/Approve/Reject buttons -- same space-
   saving reasoning as the date icon above. title/aria-label carry the
   accessible name since there's no visible text. */
.repeaterIconBtn {
    font-size: 1.1em;
    line-height: 1;
    padding: 4px 8px;
}

.repeaterIconBtn-approve {
    color: var(--accent-green);
}

.repeaterIconBtn-reject {
    color: var(--accent-red);
}

.repeaterStatusBadge {
    display: inline-block;
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.85em;
    font-weight: 600;
    white-space: normal;
}

.repeaterStatusBadge-pending {
    background-color: var(--accent-amber);
    color: var(--accent-amber-dark);
}

.repeaterStatusBadge-approved {
    background-color: var(--accent-green);
    color: var(--bg);
}

.repeaterStatusBadge-rejected {
    background-color: var(--accent-grey);
    color: var(--bg);
}

.repeaterDiscrepancyBadge {
    cursor: help;
}

.repeaterNeedsLocationBadge {
    cursor: help;
    font-size: 0.85em;
    color: var(--accent-amber);
    white-space: nowrap;
}

#fullCatalogSearchInput {
    width: 100%;
    max-width: 400px;
    margin-bottom: 10px;
    box-sizing: border-box;
}

/* Operator Location Cache maintenance -- same password-gated admin-page
   shape as Net Cleanup (login/top-bar/boxed-section rules mirror
   #netCleanupLogin/#netCleanupTopBar/#netCleanupHeader exactly), search/
   table layout reuses Repeater Maintenance's own .repeaterMaintenance*
   classes directly rather than a cloned set, since it's genuinely the same
   list-with-search-and-actions shape. */
#operatorCacheLogin {
    max-width: 320px;
    margin: 60px auto 0;
    text-align: center;
}

#operatorCacheTopBar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 12px;
}

.operatorCacheError {
    color: var(--accent-red);
}

#operatorCacheHeader,
#operatorCacheListSection {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 12px 16px;
    background-color: var(--panel-bg);
    margin-bottom: 16px;
    box-sizing: border-box;
}

#operatorCacheHeader h2 {
    color: var(--accent-amber);
}

#operatorCacheToolbar {
    margin-top: 10px;
}

.mapLookupModalBox {
    width: 700px;
    max-width: 90vw;
}

#mapLookupMap {
    width: 100%;
    height: 420px;
    border-radius: 6px;
    border: 1px solid var(--border);
    margin: 8px 0;
}

/* Net Analysis -- cross-net club-administration reporting. Filter row,
   stat tiles, and chart boxes all follow this app's existing
   standalone-maintenance-page conventions (see #userMaintenanceHeader
   etc.) rather than introducing a new visual language just for this
   one page. */
#netAnalysisFilters {
    display: flex;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: 12px;
    margin: 12px 0;
}

#netAnalysisFilters .formField {
    margin: 0;
}

/* Export/Print pinned to the far right of the filter row -- margin-left:auto
   on the wrapper (not justify-content:space-between on the row itself)
   so it still lands at the right edge even when the org picker isn't
   rendered at all (a plain org-admin has one fewer filter to its left). */
#netAnalysisFilterActions {
    display: flex;
    gap: 8px;
    margin-left: auto;
}

#netAnalysisFilters button {
    flex-shrink: 0;
    white-space: nowrap;
}

/* :not([hidden]) matters here -- a bare #id{display:flex} rule has
   higher specificity than the browser's own [hidden]{display:none} UA
   rule and would silently win, showing this row even while its `hidden`
   attribute is set (the exact same CSS-specificity gotcha hit earlier
   with input[type="text"] vs. a lone class selector). */
#netAnalysisCustomRangeRow:not([hidden]) {
    display: flex;
    align-items: flex-end;
    gap: 8px;
}

/* Same grid shape as #commandOverviewTileGrid/.commandOverviewTile --
   own IDs/classes since this is a different page, but deliberately not
   a new visual pattern. */
#netAnalysisStatTileGrid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 12px;
    margin: 12px 0;
}

.netAnalysisStatTile {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 10px 12px;
    background-color: var(--panel-bg);
}

.netAnalysisStatTile h4 {
    margin: 0 0 6px;
    color: var(--accent-teal);
    font-size: 0.95em;
}

.netAnalysisStatTile p {
    margin: 0;
    font-size: 1.4em;
    font-weight: 600;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}

#netAnalysisChartsSection {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    gap: 16px;
    margin: 16px 0;
}

.netAnalysisChartBox {
    border: 1px solid var(--border);
    border-radius: 6px;
    padding: 12px;
    background-color: var(--panel-bg);
}

.netAnalysisChartBox h3 {
    margin: 0 0 8px;
    color: var(--accent-teal);
    font-size: 1em;
}

.netAnalysisChartBox canvas {
    max-height: 280px;
}

#netAnalysisTablesSection {
    margin: 16px 0;
}

.netAnalysisDataTable {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 20px;
}

.netAnalysisDataTable th,
.netAnalysisDataTable td {
    text-align: left;
    padding: 4px 8px;
    border-bottom: 1px solid var(--border);
}

.netAnalysisDataTable th {
    color: var(--accent-teal);
}

.netAnalysisDataTable td:nth-child(2),
.netAnalysisDataTable td:nth-child(3) {
    font-variant-numeric: tabular-nums;
}

/* Printable summary -- filter controls and buttons don't make sense on
   paper; charts/tiles/tables do (canvases print fine in every modern
   browser). Same "plain window.print(), no dedicated print page" idiom
   used elsewhere for reports that aren't single-net-scoped. */
@media print {
    #netAnalysisFilters,
    #netAnalysisPrintButton,
    #netAnalysisExportButton,
    .modalActions {
        display: none !important;
    }
}

/* Situation Reports -- the 4 Resources Committed count fields sit on one
   row, label immediately before its own number input. Never wraps (this
   app's standing rule for forms) -- the 600px modal width above is
   sized to fit all 4 pairs on one line. */
#situationReportResourcesRow {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 6px 10px;
}

#situationReportResourcesRow label {
    margin: 0;
    font-weight: normal;
    flex-shrink: 0;
    white-space: nowrap;
}

#situationReportResourcesRow input[type="number"] {
    width: 64px;
    flex-shrink: 0;
}
