/* ===== BASE: style.css ===== */
@media print {
    .no-print, .no-print * {
        display: none !important;
    }
}

.login_style_text{
    height:30px;
    font-size:16px;
}
.compare_swatch{
    cursor: pointer;
}
.selection_button{
    cursor: pointer;
    border: 1px solid var(--border-strong);
    background-color: var(--gray-6);
    border-radius: var(--radius-sm);
    /*margin:2px;*/
    color: var(--text);
    padding:5px;
    text-decoration: none;
    width: 100%;
    height:40px;
    font-size:16px;
}


.button_style{
    display: inline-block;
    vertical-align: middle!important;
    text-align: center !important;
    cursor: pointer;
    border: 1px solid var(--border-strong);
    background-color: var(--gray-6);
    border-radius: var(--radius-sm);
    margin:5px;
    padding:5px;
    text-decoration: none;
    color: var(--text);
    min-width: 80px;
    font-family: var(--font-legacy);
    font-size: 15px;
    font-style: normal;
    font-variant: normal;
    font-weight: normal;
    text-decoration: none;
    color: var(--text);
    text-align: left;
    white-space: nowrap;
}
.button_style:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.all_menu_button{
    cursor: pointer;
    border: 1px solid var(--border-strong);
    background-color: var(--gray-6);
    border-radius: var(--radius-sm);
    margin:5px;
    color: var(--text);
    padding:5px;
    text-decoration: none;
    width: 300px;
    height:40px;
    font-size:20px;
}
.all_menu_button_thin{
    cursor: pointer;
    border: 1px solid var(--border-strong);
    background-color: var(--gray-6);
    border-radius: var(--radius-sm);
    margin:5px;
    color: var(--text);
    padding:5px;
    text-decoration: none;
    width: 150px;
    height:40px;
    font-size:20px;
}

.svg-icon-button {
    width: 46px;
    height: 46px;
    min-width: 46px;
    min-height: 46px;
    padding: 6px;
    border-radius: 50%;
    border: 2px solid var(--border-strong);
    background-color: var(--gray-6);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
}

.svg-icon-button--white {
    border-color: var(--border-strong) !important;
}

.svg-icon-button > svg {
    display: block;
}
/* SENTRY play/pause/stop/power icon buttons removed - they were backed by
   images/{play,playing,pause,stop,sync,poweron,poweroff}.png and injected by
   jquery/validation.js into containers that no page rendered. pages/devices.php
   uses the inline-SVG .sentry-ctrl buttons instead. */
.menu_button{
    cursor: pointer;
    border: 1px solid var(--border-strong);
    background-color: var(--gray-6);
    border-radius: var(--radius-sm);
    margin:5px;
    color: var(--text);
    padding:5px;
    text-decoration: none;
    min-width: 200px;
    height:40px;
    font-size:20px;
}
.headertitletext{
    font-family: var(--font-legacy);
    font-size: 30px;
    font-style: normal;
    font-variant: normal;
    font-weight: bold;
    color: var(--text);
}

.headingtitletext{
    font-family: var(--font);
    font-size: 1.05rem;
    font-weight: 800;
    letter-spacing: -0.015em;
    color: var(--heading);
}

/* Padding only when used as a .boxed section title row */
tr.boxed-title td.headingtitletext {
    padding: 14px 18px 12px;
}


.headingtitletext a:hover:not(.menu_button),.headingtitletext a:active:not(.menu_button),.headingtitletext a:link:not(.menu_button){
    font-family: var(--font-legacy);
    font-size: 20px;
    font-style: normal;
    font-variant: normal;
    font-weight: bold;
    color: var(--text) !important;
}

.titletext{
    font-family: var(--font-legacy);
    font-size: 15px;
    font-style: normal;
    font-variant: normal;
    font-weight: bold;
    text-decoration: none;
    color: var(--text);
    text-align: left;
}

.payment_text,.payment_text_left{
    font-family: var(--font-legacy);
    font-size: 15px;
    font-style: normal;
    font-variant: normal;
    font-weight: normal;
    text-decoration: none;
    vertical-align: middle;
    color: var(--text);
    text-align: center;
}

.payment_text_left{
    text-align: left;
}

.maintext{
    font-family: var(--font-legacy);
    font-size: 15px;
    font-style: normal;
    font-variant: normal;
    font-weight: normal;
    text-decoration: none;
    color: var(--text);
    text-align: left;
    white-space: nowrap;
}

.maintext a:hover:not(.menu_button),.maintext a:active:not(.menu_button),.maintext a:link:not(.menu_button){
    font-family: var(--font-legacy);
    font-size: 15px;
    font-style: normal;
    font-variant: normal;
    font-weight: normal;
    text-decoration: none;
    color: var(--text);
    text-align: left;
    white-space: nowrap;
}

.boxed{
    background-color: var(--gray-3);
    border-radius: var(--radius-sm);
    box-shadow: 0 1px 3px 0 var(--gray-5);
    /* margin-top: 5px; */
    margin-bottom: 5px;
    padding: 5px;
    transition: all 0.2s linear 0s;
}

/* A card whose title IS the whole message - "No Results Available" and nothing else.
   render_boxed_card() omits .card-body entirely in that case and marks the section, so the only
   thing to undo is the separator: the border divides the head from content, and with no content
   below it there is nothing to divide. The 14px bottom padding STAYS - it is what keeps the title
   off the card's edge, and dropping it left the text sitting on the bottom border. */
.boxed.is-head-only > .card-head {
    border-bottom: 0;
}
/* .card-title h3 carries margin-bottom:2px to sit above the optional <p> description. With no
   description and no body that 2px is the only thing below the text, so it reads as 16px under
   against 14px over - just enough to look off-centre. :only-child scopes this to the case where
   there is genuinely no <p> to make room for. */
.boxed.is-head-only > .card-head .card-title h3:only-child {
    margin-bottom: 0;
}


#progressbar {
    width: 100%;
    height: 20px;
    position: relative;
    background-color: var(--gray-6);
}


#blackprogressmm {
    height: 5px;
    background-color: black !important;
}
#greenprogressmm {
    height: 5px;
    background-color: var(--signal-green) !important;
}
#greyprogressmm {
    height: 5px;
    background-color: var(--gray-6) !important;
}
#amberprogressmm {
    height: 5px;
    background-color: var(--signal-amber);
}
#redprogressmm {
    height: 5px;
    background-color: var(--signal-red);
}

.play_state_button,.power_message_type{
    display: inline-block;
    color: var(--text);
    text-align: center;
    padding: 3px;
    border-width: 2px;
    border-color: #dde3e8;
    border-style: solid;
    float: center;
    border-radius: var(--radius-sm);
    margin-left: 5px;
    font-size: 12px;
}

.play_state_button{ min-width: 90px; }
.power_message_type{ min-width: 65px; }

#percent {
    position: absolute;
    left: 2px;
    top: 1px;
    color: var(--text);
}

#greenprogress {
    height: 20px;
    background-color: var(--signal-green);
}

#amberprogress {
    height: 20px;
    background-color: var(--signal-amber);
}

#redprogress {
    height: 20px;
    background-color: var(--signal-red);
}

.userinput {
    border: 1px solid #000;
    background-color: var(--gray-6);
    border-radius: var(--radius-sm);
    margin:5px;
    color: var(--text);
    padding:5px;
    text-decoration: none;
    width: 400px;
    height:50px;
    font-size:20px;
}

#logindiv{
    opacity: 1 !important;
    position: fixed !important;
    top: 0px !important;
    left: 0px !important;
    height: 100% !important;
    width: 100% !important;
    background: var(--gray-6) !important;
    display: block;
    z-index: 100;
}

#logindiv img{
    padding: 20px;
    margin: 20px;
}

#register{
    width:auto !important;
    margin:20px !important;
    background-color: var(--silver);
    font-family: 'Arial', serif;
    position: fixed !important;
    border: 5px solid #000;
    left: 50% !important;
    top: 50% !important;
    transform: translate(-50%, -50%);
}

#login{
    width:auto !important;
    margin:20px !important;
    background-color: var(--silver);
    font-family: 'Arial', serif;
    position: fixed !important;
    border: 5px solid #000;
    left: 50% !important;
    top: 50% !important;
    transform: translate(-50%, -50%);
    padding: 20px;
}

.graph_button{
    cursor: pointer;
    border: 1px solid var(--border-strong);
    background-color: var(--gray-3);
    border-radius: var(--radius-sm);
    padding:5px;
    text-decoration: none;
    color: var(--text);
    width: 100%;
    height:40px;
    font-size:20px;
}

.graph_button:hover {
    cursor: pointer;
    border: 1px solid var(--border-strong);
    background-color: var(--gray-6);
    border-radius: var(--radius-sm);
    padding:5px;
    text-decoration: none;
    color: var(--text);
    width: 100%;
    height:40px;
    font-size:20px;
}

.graph_button_on {
    cursor: default;
    border: 1px solid var(--border-strong);
    background-color: var(--gray-6);
    border-radius: var(--radius-sm);
    padding:5px;
    text-decoration: none;
    color: var(--text);
    width: 100%;
    height:40px;
    font-size:20px;
}

/* .comments_text / .bookmark_select / .network_select removed - the survey comments,
   bookmark, weather and network-filter controls in index.php now use the shared
   .field-control wrapper (see FIELD section below) instead of three near-identical
   copies of the same eight declarations. */

.network_dropdown {
    position: relative;
    display: inline-block;
    margin: 5px;
}

.network_dropdown_toggle {
    min-width: 240px;
    text-align: left;
}

.network_dropdown_menu {
    display: none;
    position: absolute;
    left: 0;
    top: calc(100% + 6px);
    /* --surface, not --gray-3: this is the same role as a native <select>'s option list, which
       ".field-control select option" below paints with --surface. The two tokens are close in
       dark mode (#2a2a2a vs #333) but nowhere near it in light (#ffffff vs #dde2e7). */
    background-color: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    padding: 6px;
    min-width: 260px;
    max-height: 300px;
    overflow-y: auto;
    box-shadow: 0 6px 14px rgba(0, 0, 0, 0.4);
    z-index: 10000;
}

.network_dropdown.open .network_dropdown_menu {
    display: block;
}

/* The menu is position:absolute, so it gets clipped by the nearest ancestor whose overflow is
   not visible - and .boxed sets overflow:hidden to keep content inside its 14px corners. Opt
   out only for cards that actually contain one of these dropdowns (survey_list.php's Filters
   card; edit_device.php's SENTRY Configuration card for the #steering PLMN picker, which reuses
   .network_dropdown_menu directly without the .network_dropdown toggle wrapper); nothing in such
   a card reaches the rounded corners, so there is nothing left to clip. index.php's Operators
   filter never hit this because it sits in the map header, outside any card. */
.boxed:has(.network_dropdown, .network_dropdown_menu) {
    overflow: visible;
}
/* Same opt-out for the results page's Device card when it carries the previous-software pill, whose
   hover card hangs out of the card. */
.boxed:has(.previous-sw-pill) {
    overflow: visible;
}

/* Survey Schedules quick-assign dropdown (render_schedules_mgmt_row(), includes/functions.php) -
   the same .network_dropdown/.network_dropdown_menu/.network_dropdown_item widget as the Operators
   filter above, just stretched to fill its slot in the .input-group next to the Assign button
   instead of that widget's own standalone inline-block/margin sizing. */
.input-group .network_dropdown.schedule-dropdown { flex: 1 1 auto; min-width: 0; margin: 0; }
.schedule-dropdown-toggle { width: 100%; }
/* .input-group > *:not(:last-child) (INPUT GROUP section below) chamfers the right corners of
   its own direct children - but the toggle isn't one, it's nested a level down inside the
   .schedule-dropdown wrapper, so that rule never reaches it and it kept its full 9px radius
   against the Assign button. Zeroed here directly instead of restructuring the wrapper out. */
.input-group .schedule-dropdown-toggle { border-top-right-radius: 0; border-bottom-right-radius: 0; }
/* .row-action-btn (the base class this button also carries) sets background:transparent, which
   left the closed toggle showing whatever's behind it (the .mgmt-row's own faint tint) instead
   of a solid surface - same var(--surface) the open .network_dropdown_menu below it uses, so the
   closed and open states read as one continuous control in both light and dark mode. Specificity
   bumped past .row-action-btn on purpose, not relying on source order. */
button.schedule-dropdown-toggle { background: var(--surface); }
/* .field-control select is 600 weight (see FIELD section below) - match it exactly rather than
   the 700 .row-action-btn .row-action-label normally uses, so the toggle reads like the plain
   select it replaces instead of a bolder CTA button. Specificity bumped past that rule on
   purpose, not relying on source order. */
button.schedule-dropdown-toggle .schedule-dropdown-label { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.schedule-dropdown-toggle .mdi-chevron-down { flex: 0 0 auto; color: var(--text-secondary); transition: transform .15s ease; }
.schedule-dropdown.open .schedule-dropdown-toggle .mdi-chevron-down { transform: rotate(180deg); }

/* .network_dropdown_item above was written for a <label> (the Operators filter's checkboxes),
   which carries no UA chrome by default - used as a <button> here (Create/Manage nav rows +
   schedule options), it needs its own reset or Chrome's native button face (grey, shrink-to-content)
   shows through instead of a flat, full-width row. */
button.network_dropdown_item { width: 100%; background: transparent; border: 0; text-align: left; cursor: pointer; font-family: inherit; }

/* Matches .field-control select / .network_dropdown_toggle - without these the items fall back
   to the page default and the menu reads in a different font to the native <select> menus and
   to this widget's own toggle button. .network_dropdown_group still overrides to bold below,
   so operator group headings keep standing out from their PLMN children in index.php. */
.network_dropdown_item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 6px;
    color: var(--text);
    font-size: 14px;
    font-weight: 600;
}

.network_dropdown_item:hover {
    background-color: var(--gray-4);
}

.network_dropdown_item input {
    margin: 0;
}

.network_dropdown_group {
    font-weight: bold;
    /* border-bottom: 1px solid var(--gray-5);
    margin-bottom: 4px;
    padding-bottom: 6px; */
}

.filter_select {
    border: 1px solid var(--border-strong);
    background-color: var(--gray-3);
    border-radius: var(--radius-sm);
    padding:5px;
    margin:5px;
    text-decoration: none;
    color: var(--text);
    width: 100%;
    height:30px;
    font-size:14px;
}

.filter_button {
    border: 1px solid var(--border-strong);
    background-color: var(--gray-3);
    border-radius: var(--radius-sm);
    padding:5px;
    margin:5px;
    text-decoration: none;
    color: var(--text);
    width: 200px;
    height:30px;
    font-size:14px;
    cursor: pointer;
}

.helptip, .helptipleft {
    position: relative;
    display: inline-block;
    cursor: help;
    vertical-align: top;
}
.helptip .helptiptext,
.helptipleft .helptiplefttext {
    position: absolute;
    visibility: hidden;
    min-width: 250px;
    background:
        radial-gradient(circle at 20% 15%, rgba(var(--brand-rgb),.14), transparent 60%),
        var(--surface);
    color: var(--text);
    border: 1px solid rgba(var(--accent-soft-rgb),.28);
    border-radius: 12px;
    padding: 12px 14px;
    z-index: 100000;
    top: 50%;
    opacity: 0;
    font-size: 14px;
    line-height: 1.4;
    box-shadow: 0 10px 28px rgba(0,0,0,.35), inset 0 1px 0 rgba(var(--ink-rgb),.06);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    transition: opacity .15s ease, visibility .15s ease;
}
.helptip .helptiptext { text-align: left; margin-left: 14px; transform: translate(0%, -50%); }
.helptipleft .helptiplefttext { text-align: right; right: 100%; margin-right: 14px; transform: translate(0%, -50%); }

.helptiptext::after, .helptiplefttext::after {
    content: "";
    position: absolute;
    top: 50%;
    width: 0;
    height: 0;
    border: solid transparent;
    pointer-events: none;
}
.helptip .helptiptext::after {
    right: 100%;
    border-width: 9px;
    border-right-color: var(--surface);
    margin-top: -9px;
}
.helptipleft .helptiplefttext::after {
    left: 100%;
    border-width: 9px;
    border-left-color: var(--surface);
    margin-top: -9px;
}

.helptiptext, .helptiplefttext {
    min-height: 44px;
    min-width: 200px;
    cursor: default;
}

/* hover only where there is a real hover - a phone's sticky emulated :hover would hold a bubble open after
   it is closed. Touch opens these with .is-tip-open instead (tap handling in jquery/summary-charts.js). */
@media (hover: hover) {
    .helptip:hover .helptiptext,
    .helptipleft:hover .helptiplefttext {
        visibility: visible;
        opacity: 1;
    }
}
.helptip.is-tip-open .helptiptext,
.helptipleft.is-tip-open .helptiplefttext {
    visibility: visible;
    opacity: 1;
}

.helptipdown {
    position: relative;
    display: inline-block;
    cursor: help;
    vertical-align: top;
}
.helptipdown .helptipdowntext {
    position: absolute;
    visibility: hidden;
    min-width: 250px;
    background:
        radial-gradient(circle at 20% 15%, rgba(var(--brand-rgb),.14), transparent 60%),
        var(--surface);
    color: var(--text);
    border: 1px solid rgba(var(--accent-soft-rgb),.28);
    border-radius: 12px;
    padding: 12px 14px;
    z-index: 100000;
    right: 0;
    top: 100%;
    margin-top: 14px;
    opacity: 0;
    font-size: 14px;
    line-height: 1.4;
    text-align: left;
    box-shadow: 0 10px 28px rgba(0,0,0,.35), inset 0 1px 0 rgba(var(--ink-rgb),.06);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    transition: opacity .15s ease, visibility .15s ease;
    min-height: 44px;
    cursor: default;
}
.helptipdown .helptipdowntext::after {
    content: "";
    position: absolute;
    bottom: 100%;
    right: 20px;
    width: 0;
    height: 0;
    border: solid transparent;
    border-width: 9px;
    border-bottom-color: var(--surface);
    margin-right: -9px;
    pointer-events: none;
}
@media (hover: hover) {
    .helptipdown:hover .helptipdowntext {
        visibility: visible;
        opacity: 1;
    }
}
.helptipdown.is-tip-open .helptipdowntext {
    visibility: visible;
    opacity: 1;
}
/* Opens above the trigger instead - for triggers that always sit at the foot of the page (the
   results page's Device card), where a card opening downward runs off the bottom of the screen. */
.helptipdown .helptipdowntext--up { top: auto; bottom: 100%; margin: 0 0 14px; }
.helptipdown .helptipdowntext--up::after { bottom: auto; top: 100%; border-bottom-color: transparent; border-top-color: var(--surface); }

*,*:before,*:after{
    box-sizing:border-box;
    outline:none;
    font-family: var(--font-legacy);
}

.switch{
    display:inline-flex;
    align-items:center;
    gap:8px;
    cursor:pointer;
    user-select:none;
}
.switch:has(.switch-input:disabled){
    cursor:not-allowed;
    opacity:.5;
}
.switch-input{
    position:absolute;
    width:0;
    height:0;
    opacity:0;
    border:none;
    outline:none;
}
.switch-thumb{
    position:relative;
    order:1;
    flex:0 0 auto;
    width:44px;
    height:24px;
    border:1px solid var(--border-strong);
    border-radius:var(--radius-pill);
    background:var(--gray-6);
    transition:background-color .2s ease, border-color .2s ease;
}
.switch-thumb::before{
    content:'';
    position:absolute;
    top:50%;
    left:3px;
    width:16px;
    height:16px;
    border-radius:50%;
    background:#fff;
    box-shadow:0 2px 4px rgba(0,0,0,.3);
    transform:translateY(-50%);
    transition:transform .2s ease;
}
.switch-input:checked + .switch-thumb{
    background:var(--signal-green);
    border-color:var(--signal-green);
}
.switch-input:checked + .switch-thumb::before{
    transform:translate(20px, -50%);
}
.switch-input:focus-visible + .switch-thumb{
    outline:2px solid var(--accent);
    outline-offset:2px;
}
.switch-state{
    order:0;
    min-width:26px;
    font-size:11px;
    font-weight:800;
    line-height:1;
    letter-spacing:.02em;
    text-transform:uppercase;
    color:var(--text-secondary);
    white-space:nowrap;
}
.switch-state::before{ content:attr(data-off); }
.switch-input:checked ~ .switch-state::before{ content:attr(data-on); }
.switch-r .switch-thumb{ order:0; }
.switch-r .switch-state{ order:1; }
.switch--compact .switch-state{ display:none; }
/* tech-matrix column-toggle: some rows on, some off */
.switch-input:indeterminate ~ .switch-state::before{ content:"Mix"; }

.toggle-group{ display:flex; flex-wrap:nowrap; gap:8px; }
.toggle-group input{ position:absolute; opacity:0; width:1px; height:1px; pointer-events:none; }
.toggle-group label{ cursor:pointer; white-space:nowrap; }
.toggle-group label.button-dark{
    background: transparent;
    border-color: var(--border);
    color: var(--text);
    box-shadow: none;
    backdrop-filter: none;
}
.toggle-group label.button-dark:hover{
    background: rgba(var(--ink-rgb),.06);
    border-color: var(--border);
    color: var(--text);
}
.toggle-group input:checked + label{
    background: linear-gradient(#0aa2e6, #0879c6);
    border-color: #0789cf;
    color: var(--text-on-accent);
}

.arrow-up {
    width: 0; 
    height: 0; 
    border-left: 10px solid transparent;
    border-right: 10px solid transparent;
    border-bottom: 10px solid var(--signal-red);
}

.arrow-down {
    width: 0; 
    height: 0; 
    border-left: 10px solid transparent;
    border-right: 10px solid transparent; 
    border-top: 10px solid var(--signal-green);
}

body > .skiptranslate:first-child {
    display: none;
}
body {
    top: 0px !important;
}
.goog-te-gadget-simple {
    display: inline-flex !important;
    align-items: center;
    border: 1px solid var(--border);
    background-color: var(--surface) !important;
    border-radius: 9px;
    padding: 0 10px !important;
    margin: 0;
    text-decoration: none;
    color: var(--text);
    width: 200px;
    height: 38px;
    box-sizing: border-box;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}
.VIpgJd-ZVi9od-xl07Ob-lTBxed span {
    color: var(--text) !important;
}
.VIpgJd-ZVi9od-vH1Gmf {
    background-color: var(--gray-3) !important;
    color: var(--text) !important;
}
.gm-style-iw, .poi-info-window > * {
    min-width: 250px !important;
    /* not --ink-rgb: that triplet flips to near-black under .light-mode, which turned the
       infowindow into a black panel with #111 text. This panel stays light in both themes. */
    background-color: rgba(255,255,255,0.9) !important;
    padding: 12px !important;
    color: #111;
}
/* Map marker tooltips (a render_hover_card() card as the InfoWindow's whole content, from
   index.php's marker builders / jquery/mapping_helpers.js's renderHoverCard() twin): strip the fixed
   light panel above so it doesn't double up with the card's own panel below, same "wrapper drops its
   own inset" convention .floating-chart-tip:has(.hover-card) already uses for the button tooltips. */
.gm-style-iw:has(.hover-card), .poi-info-window > *:has(.hover-card) {
    background-color: transparent !important;
    padding: 0 !important;
    color: inherit;
}
/* .hover-card itself has no background/color of its own - every other place it appears
   (.helptipdowntext, .plan-grid's cards) supplies that via its own wrapper. The InfoWindow gives it
   none, so without this it rendered as bare text floating over the map (see the map screenshot this
   fixed). Same recipe .helptipdowntext/.floating-chart-tip already use, so it matches every other
   hover-card in the app rather than a one-off white. */
.gm-style-iw:has(.hover-card) .hover-card, .map-card-sheet .hover-card {
    width: 380px;
    max-width: min(420px, calc(100vw - 32px));
    background: radial-gradient(circle at 20% 15%, rgba(var(--brand-rgb),.14), transparent 60%), var(--surface);
    color: var(--text);
    border: 1px solid rgba(var(--accent-soft-rgb),.28);
    box-shadow: 0 14px 32px rgba(0,0,0,.28);
}
.gm-style-iw-d {
    overflow: unset !important;
}
/* Google sizes .gm-style with absolute insets, so size containment doesn't change its size. This
   just gives the InfoWindow cqh units to read the map's height (see .hover-card-body--split). */
.gm-style { container-type: size; }
/* Google auto-caps the InfoWindow's height to whatever fits between the marker and the map's edge,
   scrolling .gm-style-iw-d internally when content doesn't fit - the rule above strips that globally,
   fine for the old flat font-tag content (never tall enough to hit the cap), but a hover-card (the
   "S" marker's can genuinely run past a full screen with a long "Cells Located" list) now does. Without
   this, Google still caps the height, just with nothing left to scroll it - the excess renders past
   the cap's edge instead (the map screenshot this fixed). Restore normal scrolling for this case only. */
.gm-style-iw:has(.hover-card) .gm-style-iw-d {
    overflow: auto !important;
}
.gm-style-iw-ch {
    padding: 0;
}
.gm-style-iw-chr button.gm-ui-hover-effect {
    position: absolute !important;
    top: 0;
    right: 0;
}
/* A map card on a phone: jquery/mapping_helpers.js bindMapCardSheet() opens it here instead of in the
   InfoWindow, which can't outgrow the map. MySCAN's dot card (dots/css/style.css) is the pattern:
   12px in from every edge, z-index under .modal's 5000 so confirm boxes still land on top. The sheet
   scrolls; a hover-card fills it and takes its look from the shared rule above. Plain (non-card)
   content gets the sheet's own panel. The JS also sets its width/height to the real screen size,
   since a page wider than the phone stretches these insets past the screen edge. */
.map-card-sheet {
    position: fixed; inset: 12px; z-index: 4500; overflow: auto; overscroll-behavior: contain;
    padding: 12px; border-radius: 12px; background: var(--surface); color: var(--text);
    box-shadow: 0 8px 30px rgba(0,0,0,.35);
}
.map-card-sheet[hidden] { display: none; }
.map-card-sheet:has(.hover-card) { padding: 0; background: transparent; box-shadow: none; }
.map-card-sheet-body { min-height: 100%; display: flex; flex-direction: column; }
.map-card-sheet .hover-card { flex: 1 0 auto; width: auto; max-width: none; box-sizing: border-box; }
.map-card-sheet-close {
    position: sticky; top: 0; float: right; z-index: 2; width: 32px; height: 32px; border: 0; border-radius: 50%;
    background: rgba(var(--ink-rgb),.08); color: var(--text); font-size: 18px; cursor: pointer;
}
/* a render_hover_card() card brings its own close button */
.map-card-sheet:has(.hover-card-close) .map-card-sheet-close { display: none; }

td[th-hidden] {
    /*	width: 0%;*/
    display: none;
}
td[td-toggle][td-visibility="false"] span {
    display: inline-block;
    -moz-transform: scale(-1, 1);
    -webkit-transform: scale(-1, 1);
    -o-transform: scale(-1, 1);
    -ms-transform: scale(-1, 1);
    transform: scale(-1, 1);
}

.menuicon-btn {
    display: none !important;
}
#nav-row .mobile-only {
    display: none;
}
/* Always out of flow (position only - it's a 0x0 box until .nav-back sizes it): an empty static
   #nav-back is still a .portal-nav flex item and costs a 14px gap, so it going fixed on open freed
   that gap and reflowed the header from two rows to one. */
#nav-back { position: fixed; }
.nav-back {
    position: fixed;
    height: 100%;
    width: 100%;
    top: 0;
    left: 0;
    z-index: 2;
}
@media only screen and (max-width: 1800px) {
    .menuicon-btn {
        display: inline-flex !important;
        flex-direction: column;
        gap: 4px !important;
    }
    .menuicon-btn div {
        width: 16px;
        height: 2px;
        background-color: var(--accent-soft);
        border-radius: 2px;
    }
    #nav-row { display: none; }
    .responsive {
        display: flex !important;
        flex-direction: column;
        flex-wrap: nowrap !important;
        justify-content: flex-start !important;
        align-items: stretch !important;
        gap: 6px;
        margin: 0 !important;
        position: fixed;
        right: 0;
        top: 0;
        z-index: 3;
        width: 240px;
        padding: 52px 12px 20px;
        height: 100vh;
        height: 100dvh;
        overflow-y: auto;
        overflow-x: hidden;
        background:
        radial-gradient(circle at 20% 20%, rgba(var(--brand-rgb),0.12), transparent 50%),
        var(--surface);
        border-left: 1px solid rgba(var(--accent-soft-rgb),0.18);
        box-shadow: -8px 0 32px rgba(0,0,0,0.45);
        transition: transform 0.25s ease;
    }
    #nav-row.responsive .mobile-only { display: flex !important; }
    .responsive a { display: block; text-decoration: none; }
    .responsive > button,
    .responsive a > button {
        display: flex !important;
        align-items: center !important;
        width: 100% !important;
        height: auto !important;
        box-sizing: border-box !important;
        margin: 0 !important;
    }
    #nav-row.responsive .mobile-drawer-footer {
        position: sticky;
        bottom: 0;
        margin-top: auto;
        display: flex;
        flex-direction: column;
        gap: 4px;
        padding: 12px;
        border-top: 2px solid rgba(var(--accent-soft-rgb),0.35);
        background: var(--surface);
    }
    #nav-row.responsive .all_menu_button_thin,
    #nav-row.responsive .tablinks,
    #nav-row.responsive .nav-tab,
    #nav-row.responsive .headingtitletext {
        color: var(--text) !important;
    }
    /* stacked, so every chip stretches to the footer's width and they all match */
    #nav-row.responsive .drawer-chip-row {
        display: flex;
        flex-direction: column;
        gap: 6px;
        margin-top: 6px;
    }
    #nav-row.responsive .drawer-chip {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 5px;
        padding: 4px 10px;
        border-radius: var(--radius-pill);
        background: rgba(var(--ink-rgb),0.08);
        border: 1px solid rgba(var(--ink-rgb),0.14);
        color: var(--text-secondary) !important;
        font-size: 0.72rem;
        font-weight: 700;
        white-space: nowrap;
        text-decoration: none;
    }
    #nav-row.responsive .drawer-chip .mdi {
        font-size: 0.9rem;
    }
    #nav-row.responsive a.drawer-chip--action {
        background: rgba(var(--accent-rgb),0.14);
        border-color: rgba(var(--accent-rgb),0.3);
        color: var(--accent) !important;
        cursor: pointer;
    }
}


/* Dynamic Modal */
.modal_content { width: 30vw; }
.modal_content.box-small 	{ width: 30vw; }
.modal_content.box-medium 	{ width: 55vw; }
.modal_content.box-large 	{ width: 80vw; }

.stars { display: flex; align-items: center; justify-content: center; gap: 4px; font-size: 40px; line-height: 45px; user-select: none; margin-bottom: 14px; }
.star { cursor: pointer; color: var(--accent); }
.modal { display: none; position: fixed; z-index: 5000; left: 0; top: 0; width: 100%; height: 100%; overflow: none; background-color: rgba(0,0,0,0.4); }
.modal_content {
    color: var(--text-on-accent);
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
    padding: 20px;
    height: auto;
    border-radius: 14px;
    z-index: 5001;
    background:
        radial-gradient(circle at 20% 20%, rgba(var(--brand-rgb),0.18), transparent 50%),
        linear-gradient(160deg, rgba(255,255,255,0.10), rgba(255,255,255,0.03));
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border: 1px solid rgba(var(--accent-soft-rgb),0.18);
    box-shadow: 0 8px 32px rgba(0,0,0,0.45);
}
span.close { color: var(--text-on-accent); float: right; font-size: 28px; font-weight: bold; }
span.close:hover,
span.close:focus { color: #aaa; text-decoration: none; cursor: pointer; }

/* Card-as-modal variant - for a modal whose content is a single render_boxed_card() (the
   feedback modal below), the card supplies its own border/radius/shadow, so modal_content's
   usual glass-panel chrome would just double it up as a second, misaligned outline around it.
   Strip modal_content down to sizing/centering only and let the card be the visible modal.
   openModal() always rewrites modal_content's class list to "modal_content <width-class>" on
   every open, so this only ever applies when the caller adds it back straight after. */
.modal_content.is-card-modal {
    padding: 0;
    background: none;
    border: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
/* The blank spacer row (createModalElement()'s "<div class='row'><br></div>") - not #modalHTML,
   which also carries class="row" but holds the actual card and must stay. */
.modal_content.is-card-modal > .row:not(#modalHTML) { display: none; }
.modal_content.is-card-modal .boxed { margin-bottom: 0; }
.modal_content.is-card-modal > .close {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 2;
    float: none;
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border-radius: 10px;
    background: rgba(255,255,255,.14);
    color: #fff;
    font-size: 22px;
    font-weight: normal;
    transition: background .15s ease;
}
.modal_content.is-card-modal > .close:hover { background: rgba(255,255,255,.26); color: #fff; }
/* CreateBoxType() Ok/Cancel row: the sitewide .menu_button min-width:200px pushed two buttons
   past a phone-width modal; they share the row while they fit and stack once they don't. */
.modal-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
.modal-actions .menu_button { flex: 1 1 140px; min-width: 0; margin: 0; }

/* Extra small devices (phones, 600px and down) */
@media only screen and (max-width: 600px) {
    .modal_content.box-small	{ width: calc(100vw - 24px); }
    .modal_content.box-medium	{ width: calc(100vw - 24px); }
    .modal_content.box-large	{ width: calc(100vw - 24px); }
}

/* Small devices (portrait tablets and large phones, 600px and up) */
@media only screen and (min-width: 600px) and (max-width: 768px) {
    .modal_content.box-small	{ width: 75vw; }
    .modal_content.box-medium	{ width: 85vw; }
    .modal_content.box-large	{ width: 95vw; }
}

/* Medium devices (landscape tablets, 768px and up) */
@media only screen and (min-width: 768px) and (max-width: 992px) {
    .modal_content.box-small	{ width: 55vw; }
    .modal_content.box-medium	{ width: 75vw; }
    .modal_content.box-large	{ width: 85vw; }
}

/* Large devices (laptops/desktops, 992px and up) */
@media only screen and (min-width: 992px) and (max-width: 1400px) {
    .modal_content.box-small	{ width: 45vw; }
    .modal_content.box-medium	{ width: 65vw; }
    .modal_content.box-large	{ width: 75vw; }
}

/* 1400px+ needs no override: matches the unqualified base rule above (30/55/80vw). */

table.heatmap-table { width: 100%; color: var(--text); min-height: 400px; border-collapse: separate; border-spacing: 0; margin-bottom: 14px; }
table.heatmap-table th, table.heatmap-table td { padding: 8px; text-align: center; }
table.heatmap-table th, table.heatmap-table td { border-bottom: 1px solid var(--border); border-right: 1px solid var(--border); }
table.heatmap-table tbody tr:last-child td { border-bottom: none; }
table.heatmap-table th:last-child, table.heatmap-table td:last-child { border-right: none; }
/* cell text is inline color:#fff from generateHeatmapTable() - readable on the dark theme's mostly-dark
   composited backgrounds, but low-intensity cells go near-white in light mode and swallow white text */
body.light-mode table.heatmap-table td { color: #16233b !important; }

/* margin:0 auto here was always a no-op (no width/max-width was ever set, so auto margins had
   nothing to centre) until .journey-summary-card started sharing .summary-graph-grid's
   > .boxed > .card-body{display:flex;flex-direction:column} rule (below, written for the chart
   cards' own height-matching) - as a flex item, auto margins absorb free space and win over
   align-items:stretch, so .journey-wrapper shrank to whatever width its content (e.g. the
   currently-displayed address string) happened to need instead of filling the row - reported as
   the whole Journey Summary card visibly resizing as journey_timeline.js updated address text
   mid-drag. Plain margin:0 lets it stretch to fill .card-body's width like every other card. */
.journey-wrapper { position: relative; margin: 0; }
/* Circle - bar - circle is ONE flex row (see .journey-grid) so they always touch, at any
   container width, with no manual px/% math to keep in sync - the bar is just flex:1, filling
   exactly whatever space is left between the two fixed-size circles. Green->amber gradient
   (same colours as the circles) blends straight into them, and the pill radius means the bar
   itself has no square corners butting into a round circle either. */
.journey-wrapper .journey-line { flex: 1 1 auto; min-width: 40px; height: 30px; background: linear-gradient(90deg, var(--signal-green), var(--signal-amber)); border-radius: 15px; box-shadow: 0 4px 14px rgba(0,0,0,.35); text-shadow: 0 1px 3px rgba(0,0,0,.45); border-top: 2px dashed var(--border-strong); border-bottom: 2px dashed var(--border-strong); color: var(--text); text-align: center; line-height: 26px; box-sizing: border-box; }
.journey-wrapper .journey-grid { display: flex; align-items: center; position: relative; }
.endpoint-slot { flex: 0 0 auto; }
.journey-wrapper .endpoints { font-size: 30px; width: 100px; height: 100px; background-color: var(--gray-6); border-radius: 50%; color: var(--text); font-weight: bold; display: flex; align-items: center; justify-content: center; border: 2px solid var(--border-strong); }
/* weather glyph inside the endpoint circle - font-size, not width, since the PNGs became MDI */
.journey-wrapper .endpoints .mdi { font-size: 58px; line-height: 1; }
.journey-wrapper .endpoints.start { background-color: var(--signal-green); }
.journey-wrapper .endpoints.end { background-color: var(--signal-amber); }
/* Fixed equal thirds + stretch: the old auto/1fr/auto columns sized each box to its own content,
   so a longer wrapped address made that box both wider and taller than its neighbours. */
.journey-wrapper .info-grid { display: grid; grid-template-columns: repeat(3, 1fr); align-items: stretch; justify-items: stretch; gap: 16px; margin-top: 20px; }
/* Same glass-panel treatment as .boxed/.boxed-alt (render_boxed_card()) instead of a flat grey
   box, so these match the rest of the app's card look. */
.journey-wrapper .info,
.journey-wrapper .middle-info { background: var(--surface-hover); border: 1px solid rgba(var(--ink-rgb),.09); box-shadow: inset 0 1px 0 rgba(var(--ink-rgb),.06), 0 6px 22px rgba(0,0,0,.28); border-radius: 10px; padding: 14px; font-size: 14px; color: var(--text); width: 100%; box-sizing: border-box; }
.journey-wrapper .info { text-align: left; }
.journey-wrapper .middle-info { text-align: center; }
@media (max-width: 900px) {
	.journey-wrapper .info-grid { grid-template-columns: 1fr; gap: 12px; }
	.journey-wrapper .middle-info { order: -1; }
}
@media (max-width: 600px) {
	.journey-wrapper .endpoints { width: 60px; height: 60px; font-size: 20px; }
	.journey-wrapper .endpoints .mdi { font-size: 34px; }
}

/* Interactive Journey Summary timeline (behind $journey_sliding_bool in index.php) - recreated
   from a reference mockup, restyled with this app's own tokens. .timeline-area is the flex child
   that sits between the two .endpoints circles when the slider is on, replacing .journey-line's
   role above (same flex:1, same slot in .journey-grid) - ported from _sandbox_journey_timeline/. */
.journey-wrapper .timeline-area { flex: 1 1 auto; min-width: 0; padding: 0 8px; }

.selected-label { text-align: center; color: var(--accent); font-size: 13px; font-weight: 800; margin-top: 6px; }
.distance-line { text-align: center; font-size: 16px; font-weight: 800; margin-top: 5px; color: var(--text); }
.time-line { display: flex; justify-content: center; align-items: center; gap: 16px; flex-wrap: wrap; color: var(--text-muted); font-size: 13px; margin-top: 8px; }

.slider-shell { position: relative; height: 84px; margin-top: 3px; }

.track { position: absolute; left: 16px; right: 16px; top: 31px; height: 8px; border-radius: 999px; background: var(--gray-6); opacity: .75; }

/* the highlighted selection - also the click-and-hold target that pans both handles together */
.selected-track {
	position: absolute; top: 31px; height: 8px; border-radius: 999px;
	background: var(--accent);
	box-shadow: 0 0 18px rgba(var(--accent-rgb),.4);
	z-index: 2; pointer-events: auto; cursor: grab; touch-action: none;
}
.selected-track:active { cursor: grabbing; }

.marker-row { position: absolute; left: 16px; right: 16px; top: 27px; display: flex; justify-content: space-between; pointer-events: none; z-index: 1; }
.marker-row span { width: 14px; height: 14px; border-radius: 50%; background: var(--border-strong); box-shadow: 0 0 0 2px rgba(var(--ink-rgb),.05); }
.marker-row span.active { background: var(--accent); box-shadow: 0 0 11px rgba(var(--accent-rgb),.8); }

/* two overlaid native range inputs - pointer-events:none on the body lets clicks fall through to
   .selected-track underneath; only the round thumb (cursor:ew-resize) is clickable, same
   "auto on the interactive part only" pattern as .selected-track above. */
.range-input { position: absolute; left: 16px; top: 20px; width: calc(100% - 32px); height: 30px; margin: 0; pointer-events: none; background: transparent; appearance: none; z-index: 5; }
.range-input::-webkit-slider-runnable-track { height: 8px; background: transparent; }
.range-input::-moz-range-track { height: 8px; background: transparent; }
.range-input::-webkit-slider-thumb {
	appearance: none; width: 28px; height: 28px; border-radius: 50%;
	background: var(--surface); border: 4px solid var(--accent);
	box-shadow: 0 0 0 4px rgba(var(--accent-rgb),.18), 0 3px 12px rgba(0,0,0,.45);
	cursor: ew-resize; pointer-events: auto;
	/* WebKit doesn't auto-centre the thumb on the (8px-tall) track like Firefox does - without
	   this it sits flush with the track's top edge instead of straddling its centre. */
	margin-top: -10px;
}
.range-input::-moz-range-thumb {
	width: 28px; height: 28px; border-radius: 50%;
	background: var(--surface); border: 4px solid var(--accent);
	box-shadow: 0 0 0 4px rgba(var(--accent-rgb),.18), 0 3px 12px rgba(0,0,0,.45);
	cursor: ew-resize; pointer-events: auto;
}
@media (pointer: coarse) {
	.range-input::-webkit-slider-thumb { width: 40px; height: 40px; margin-top: -16px; }
	.range-input::-moz-range-thumb { width: 40px; height: 40px; }
}

.slider-meta { display: flex; justify-content: space-between; gap: 24px; margin-top: 4px; font-size: 13px; color: var(--text); font-weight: 700; }
.slider-meta > div { width: 46%; text-align: center; }
.slider-meta small { display: block; color: var(--text-muted); font-weight: 600; margin-top: 3px; white-space: normal; }

/* Account > Settings "Signal Quality Thresholds" - the slider above with a red | amber | green track.
   --lvl-fair / --lvl-good are plain 0-100 numbers set inline and kept in sync by account.php's
   script; named lvl- because --good is already the colour token. */
/* the light theme darkens these for text contrast; here they're only fills (track, thumbs, band
   dots), so keep the bright shades in both themes */
.signal-levels { --good: #22c55e; --ok: #f59e0b; --poor: #ef4444; }
.signal-levels .slider-shell { height: 40px; margin-top: 0; }
.signal-levels .range-input { top: 5px; }
/* the thumb centre travels from 16px + half the thumb to the same from the right, so inset the
   track by that much - then the band edges land exactly under the thumbs */
.signal-levels .track {
	--thumb-half: 14px;
	left: calc(16px + var(--thumb-half)); right: calc(16px + var(--thumb-half)); top: 16px; opacity: 1;
	background: linear-gradient(90deg,
		var(--poor) calc(var(--lvl-fair) * 1%),
		var(--ok)   calc(var(--lvl-fair) * 1%) calc(var(--lvl-good) * 1%),
		var(--good) calc(var(--lvl-good) * 1%));
}
@media (pointer: coarse) {
	.signal-levels .slider-shell { height: 52px; }
	.signal-levels .range-input { top: 11px; }
	.signal-levels .track { --thumb-half: 20px; top: 22px; }
}
/* thumbs take their band colour instead of the accent */
.signal-levels .range-input::-webkit-slider-thumb { box-shadow: 0 3px 12px rgba(0,0,0,.45); }
.signal-levels .range-input::-moz-range-thumb { box-shadow: 0 3px 12px rgba(0,0,0,.45); }
.signal-levels .range-input[data-level="fair"]::-webkit-slider-thumb { border-color: var(--ok); }
.signal-levels .range-input[data-level="fair"]::-moz-range-thumb { border-color: var(--ok); }
.signal-levels .range-input[data-level="good"]::-webkit-slider-thumb { border-color: var(--good); }
.signal-levels .range-input[data-level="good"]::-moz-range-thumb { border-color: var(--good); }
/* whichever handle was last grabbed sits on top */
.signal-levels .range-input.is-top { z-index: 6; }
.signal-levels-inputs { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 18px; padding: 0 16px; }
.signal-band { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; color: var(--text-secondary); }
/* inline-flex above would otherwise beat the [hidden] the script sets at fair 0 */
.signal-band[hidden] { display: none; }
.signal-band::before { content: ""; width: 10px; height: 10px; border-radius: 50%; }
.signal-band.poor::before { background: var(--poor); }
.signal-band.ok::before { background: var(--ok); }
.signal-band.good::before { background: var(--good); }
.signal-band b { color: var(--text); }
.signal-band .field-control input { width: 64px; flex: none; padding: 0 4px 0 10px; }
.signal-levels-reset { height: 36px; padding: 0 12px; gap: 6px; cursor: pointer; color: var(--text-secondary); font: 600 13px var(--font); }

/* "Separate thresholds per technology": the combined slider above plus one row per technology.
   Everything that changes with that toggle is CSS-only, off .signal-levels-group:has(...:checked). */

/* combined slider, per-tech off: full width with a small caps caption over it */
.signal-levels-caption { display: block; padding: 0 16px; font-size: 12px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--text-secondary); }
.signal-levels-caption .when-on,
.signal-levels--combined .signal-levels-unit,
.signal-levels-hint,
.signal-levels-tech { display: none; }
/* what Combined is still for once each technology has its own bands - full width along the bottom
   of the combined row's bubble */
.signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels-hint { display: block; grid-column: 1 / -1; margin: 8px 0 0; padding: 0 16px; font-size: 12px; color: var(--text-secondary); }

/* per-tech on: caption swaps, the technology rows show, the Poor label goes (rows don't have one) */
.signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels-caption { padding: 0; letter-spacing: 0; text-transform: none; }
.signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels-caption .when-off,
.signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels--combined .signal-band.poor { display: none; }
.signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels-caption .when-on { display: flex; }
.signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels--combined .signal-levels-unit { display: block; }
.signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels-tech { display: grid; gap: 12px; }
.signal-levels--combined .tech-dot { background: var(--text-secondary); }

/* one row per technology - and the combined slider too when per-tech is on - name with its readout
   under it | slider | Fair / Good */
:is(.signal-levels--row, .signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels--combined) {
	display: grid; grid-template-columns: 144px minmax(140px, 1fr) auto; align-items: center; column-gap: 10px;
	grid-template-areas: "name slider inputs" "unit slider inputs";
}
:is(.signal-levels--row, .signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels--combined) :is(.technology-name, .signal-levels-caption) {
	grid-area: name; align-self: end; padding-left: 16px;
}
:is(.signal-levels--row, .signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels--combined) .slider-shell { grid-area: slider; }
:is(.signal-levels--row, .signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels--combined) .signal-levels-inputs {
	grid-area: inputs; padding: 0 16px 0 0; flex-wrap: nowrap; gap: 10px;
}
:is(.signal-levels--row, .signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels--combined) .signal-band { white-space: nowrap; }
/* the combined slider and each technology row are their own bubbles, same look as the account
   .detail-rows (search "same look as the device drawer's") - the per-tech toggle above them is one of
   those detail rows. Sliders and inputs already carry their own 16px side inset, so these bubbles
   only pad top and bottom. */
.signal-levels-group { display: grid; gap: 12px; }
:is(.signal-levels--combined, .signal-levels--row) { padding: 12px 0; border: 1px solid rgba(var(--ink-rgb),.08); border-radius: 12px; background: rgba(var(--ink-rgb),.03); }
@media (min-width: 1100px) {
	:is(.signal-levels--combined, .signal-levels--row) { padding: 16px 0; }
}

/* no display here - the combined row's name is shown/hidden by the toggle rules above */
.signal-levels-group .technology-name { align-items: center; gap: 8px; font-size: 13px; font-weight: 700; color: var(--text); }
.signal-levels--row .technology-name { display: flex; }
.signal-levels-unit { grid-area: unit; align-self: start; padding: 0 16px; font-size: 11px; font-weight: 600; color: var(--text-secondary); white-space: nowrap; }
.signal-levels-unit b { color: var(--text); }

/* up to 1600px the Site defaults card is too narrow for name | slider | inputs on one line: name +
   readout on one line, then the slider and the inputs full width under it */
@media (max-width: 1600px) {
	:is(.signal-levels--row, .signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels--combined) {
		grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name unit" "slider slider" "inputs inputs";
	}
	:is(.signal-levels--row, .signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels--combined) :is(.technology-name, .signal-levels-caption, .signal-levels-unit) { align-self: center; }
	/* wrap is the fallback for the narrowest phones - the 480px input trim below keeps one line down to ~380px */
	:is(.signal-levels--row, .signal-levels-group:has(input[name="signal_levels_per_tech"]:checked) .signal-levels--combined) .signal-levels-inputs { padding: 0 16px; flex-wrap: wrap; }
}
@media (max-width: 480px) {
	.signal-band .field-control input { width: 48px; padding: 0 2px 0 8px; }
}

@media (max-width: 900px) {
	.journey-wrapper .journey-grid { flex-wrap: wrap; }
	.journey-wrapper .timeline-area { flex: 1 1 100%; order: -1; padding: 0; }
}

/* brief dim while journey_graphs_ajax_response()'s fetch() is in flight */
.is-refreshing > .card-body { opacity: .45; transition: opacity .15s ease; }

.svg-gauge { display: block; width: 100%; height: 100%; overflow: initial; }
.svg-gauge .needle { /*stroke: #4285f4; stroke-width: 6; stroke-linecap: round;*/ }
.svg-gauge .needle-base { /*fill: #4285f4;*/ }
.svg-gauge .marker { /*stroke-width: 10;*/ }
.svg-gauge .label { font-size: 20px; fill: var(--text); line-height: 20px; }
.svg-gauge .tick-label { font-size: 12px; fill: var(--text); line-height: 12px; }

.recommended-highlight-item { text-align: center; }
.recommended-highlight-circle { background: linear-gradient(135deg, #f39c12, #e67e22); color: var(--text-on-accent); font-weight: normal; font-size: 1rem; border-radius: 50%; margin: 0 auto; display: flex; flex-direction: column; align-items: center; justify-content: center; border: 10px solid white; text-align: center; overflow: hidden; width: var(--size, 400px); height: var(--size, 400px); padding: calc(var(--size, 400px) / 2); }
.recommended-highlight-circle .recommended-highlight-title { user-select: none; font-size: calc(var(--size, 400px) / 22.22) /*1.1rem*/; margin-bottom: 0.3rem; opacity: 0.9; white-space: nowrap; }
.recommended-highlight-circle .recommended-highlight-name { user-select: none; font-size: calc(var(--size, 400px) / 5) /*80px*/; font-weight: bold; margin-bottom: 0.3rem; white-space: nowrap; }
.recommended-highlight-circle .recommended-highlight-desc { user-select: none; font-size: calc(var(--size, 400px) / 22.22) /*1.1rem*/; opacity: 0.85; line-height: 1.2; white-space: nowrap; }

.recommended-highlight-circle.recommended_antenna::after {
    content: 'recommended';
    top: 21%;
    right: 0;
    position: absolute;
    width: 100%;
    height: 100%;
    text-align: center;
    font-size: calc(var(--size, 400px) / 11);
    opacity: 0.5;
    text-transform: uppercase;
}

.recommended-highlight-rect { background: linear-gradient(135deg, #f39c12, #e67e22); color: var(--text-on-accent); font-weight: normal; font-size: 1rem; border-radius: 60px; width: 450px; height: 200px; margin: 0 auto; display: flex; flex-direction: column; align-items: center; justify-content: center; /*padding: 200px;*/ /*box-shadow: 0 0 1px 10px white;*/ border: 10px solid white; text-align: center; overflow: hidden; }
.recommended-highlight-rect .recommended-highlight-title { user-select: none; font-size: 1.1rem; margin-bottom: 0.3rem; opacity: 0.9; white-space: nowrap; }
.recommended-highlight-rect .recommended-highlight-name { user-select: none; font-size: 80px; font-weight: bold; margin-bottom: 0.3rem; white-space: nowrap; }
.recommended-highlight-rect .recommended-highlight-desc { user-select: none; font-size: 1.1rem; opacity: 0.85; line-height: 1.2; white-space: nowrap; }

button.tablinks { white-space: nowrap; }
div.tab_list button:not(.active) { opacity: 0.5; }
tr.tabcontent { display: none; } 
tr.tabcontent { animation: tabContentFadeEffect 1s; }
tr.tabcontent:not(.active) {
    height: 0px;
    width: 100%;
    overflow: hidden;
    /*display: inline-block;*/
    display: block;
    visibility: hidden;
}
tr.tabcontent.active {
    height: auto;
    overflow: auto;
    visibility: visible;
}
/* panels sharing one tr.tabcontent (account.php settings) */
div.tabcontent:not(.active) { display: none; }
div.tabcontent { animation: tabContentFadeEffect 1s; }
@keyframes tabContentFadeEffect { from {opacity: 0;} to {opacity: 1;} }
/* Tab bodies used to get their top/left breathing room for free from an outer <table class="boxed">
   wrapper (border+background made the gap from the tab bar above read fine even at 0 margin) -
   now that every tab wires its own items straight into render_boxed_card()/.metric-card/etc.
   instead of nesting them inside that wrapper, nothing supplies the gap any more. One rule here
   covers every tab uniformly instead of requiring each tab's own heading to carry a margin. */
tr.tabcontent > td { padding-top: 20px; }

.div-grid {
    display: grid;
    gap: 10px;
    grid-template-columns: repeat(4, 1fr);
    /*padding: 10px;*/
}

.google-visualization-tooltip {
    color: #000 !important;
    text-align: left !important;
    border: solid 1px #999 !important;
    padding: 5px !important;
    min-width: 200px !important;
    border-radius: var(--radius-sm) !important;
    background-color: #ddd !important;
    position: absolute !important;
    font-size: 13px !important;
    font-family: var(--font-legacy) !important;
    white-space: nowrap;
}

/* ===== DARK THEME + CHIP SYSTEM ===== */
:root {
    --bg: #1a1a1a;
    --surface: #2a2a2a;
    --surface-hover: #333333;
    --border: rgba(255,255,255,0.08);
    --text: #ffffff;
    --text-secondary: #b8b8b8;
    --good: #22c55e;
    --ok: #f59e0b;
    --poor: #ef4444;
    --radius: 12px;

    /* "white tint on dark" overlay triplet - flips to 0,0,0 in a light theme */
    --ink-rgb: 255,255,255;

    /* text */
    --text-on-accent: #ffffff;         /* text sitting on brand-blue/coloured fills - does not flip */
    --text-muted: #9aaab7;             /* hero / device-card grey */
    --text-soft: #dddddd;              /* legacy secondary text */
    --heading: rgba(220,232,240,0.82); /* section headings */

    /* legacy grey ramp */
    --gray-2: #222;
    --gray-3: #333;
    --gray-4: #444;
    --gray-5: #555;
    --gray-6: #666;
    --gray-8: #888;
    --border-strong: #fff;             /* legacy `border: 1px solid #fff` on grey buttons */

    /* brand / accent */
    --accent: #11bdf3;                 /* hero/login cyan */
    --accent-rgb: 17,189,243;
    --accent-soft: #7dd3fc;            /* light cyan text/icons */
    --accent-soft-rgb: 125,211,252;    /* chip borders */
    --brand-rgb: 0,147,211;            /* CloudSURVEY blue glows/tints */
    --brand-bright-rgb: 0,167,230;     /* chip gradient start */
    --brand-deep-rgb: 0,76,140;        /* chip gradient end */

    --navy-975: #030b17;
    --navy-900: #071a2e;
    --hero-1: #0a4384;
    --hero-2: #08275a;
    --hero-3: #061e47;

    /* status extremes used in progress-fill gradients */
    --good-strong: #16a34a;
    --ok-strong: #d97706;
    --poor-strong: #dc2626;

    /* Survey Weather metric card accent (index.php picks day vs night by survey hour).
       --weather-night has to flip: the original #001f3f is near-black, which reads fine on the
       light card but disappears against the dark one, taking the icon badge with it since
       .metric-icon derives both its colour and its 16% wash from --metric-accent. */
    --weather-day: #0093d0;
    --weather-night: #6f9fd8;

    /* legacy signal colours (progress bars, arrows, journey endpoints) */
    --signal-green: #0B9444;
    --signal-amber: #FF6600;
    --signal-red: #a80b10;
    --record-red: #d51010;

    /* misc legacy */
    --silver: #c0c0c0;                 /* login/register boxes */
    --chart-grid: #585656;             /* Google Charts axis gridlines (read via JS, see performance_chart.js) */

    /* non-colour reused values */
    --radius-sm: 6px;
    --radius-pill: 999px;
    --font: system-ui, sans-serif;
    --font-legacy: Arial;
}

body.light-mode {
    /* the deep navy only works here, where the card behind it is white */
    --weather-night: #001f3f;

    --bg: #f3f5f8;
    --surface: #ffffff;
    --surface-hover: #eef1f5;
    --border: rgba(15,23,32,0.12);
    --text: #15202b;
    --text-secondary: #55636f;
    --ink-rgb: 15,23,32;

    --text-muted: #5b6b79;
    --text-soft: #33414d;
    --heading: rgba(21,32,43,0.82);

    --gray-2: #e8ebef;
    --gray-3: #dde2e7;
    --gray-4: #cdd4db;
    --gray-5: #b3bcc4;
    --gray-6: #93a0ab;
    --gray-8: #66707a;
    --border-strong: #33414d;
    --chart-grid: #c7ccd1;

    /* unchanged from the dark theme otherwise, which measures 2.15-3.76:1 text contrast on
       white (fails WCAG AA's 4.5:1) wherever used as text colour (.metric-good/-warning/-bad).
       These land ~5:1 - see INVESTIGATION_REPORT_2026-08-05_light-mode.md */
    --good: #15803d;
    --ok: #b45309;
    --poor: #dc2626;
}
body {
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    margin: 20px;
}
/* Legacy structural wrappers (index.php's $header opens table.page-shell, which never closes
   its own </table> - it and table.report both stay open row-by-row for the rest of the page,
   see the "in table"/"in cell" insertion-mode comment in pages/devices.php). Both are always a
   single column (one <td> per <tr>, used purely to stack sections), but with the browser default
   table-layout:auto a table sizes to its content's min-content width and ignores width="100%"
   the moment anything inside can't shrink enough - on a narrow viewport this dragged the ENTIRE
   page (nav, hero, cards, everything) out to whatever that min-content happened to be, no matter
   how much flex-wrap/grid responsiveness existed further down. table-layout:fixed makes these two
   actually obey width:100% and hand their real, narrower width down to their content instead.
   On pages where table.report itself gets reopened several times row-by-row (survey reports,
   same unclosed-tag pattern as page-shell), fixed layout's "column width comes from row 1"
   rule stops giving every row the same column width - some rows (the hero's) collapsed to a
   sliver instead of 100%. Forcing each cell's own width closes that gap without touching the
   narrow-viewport fix above. */
table.page-shell, table.report { table-layout: fixed; width: 100%; }
table.report > tbody > tr > td { width: 100%; }
/* CARD — unified container for all section panels across all tabs */
.boxed {
    background: rgba(var(--ink-rgb),.028);
    border: 1px solid rgba(var(--ink-rgb),.09);
    border-radius: 14px;
    padding: 0;
    /* margin-top: 5px; */
    margin-bottom: 20px;
    box-shadow:
    inset 0 1px 0 rgba(var(--ink-rgb),.06),
    0 6px 22px rgba(0,0,0,.28);
    overflow: hidden;
    transition: all 0.2s linear 0s;
}
/* Alt panel - a visually distinct "nested" section within a .boxed layout: class="boxed boxed-alt" */
.boxed-alt {
    background: var(--surface-hover);
}
/* summary-graph-panel no longer adds its own background — absorbed into .boxed */
.summary-graph-panel {}
/* SECTION TITLE */
.section-title {
    margin: 0 0 16px 0;
    font-size: 1.3rem;
    font-weight: 600;
}
/* Links */
.boxed a:not(.menu_button):not(.hover-card-cta) {
    color: inherit;
    text-decoration: none;
}
/* .dot-antenna-row is a whole-card link (MySCAN dot Antenna tab) - its highlight is the hover state */
.boxed a:hover:not(.menu_button):not(.hover-card-cta):not(.dot-antenna-row) {
    color: var(--text);
    text-decoration: underline;
}
.import-panel .table-wrapper { max-height: 70vh; overflow-y: auto; }
@media (max-width: 1600px) {
    .div-grid.import-grid { grid-template-columns: 1fr !important; }
    /* Combined heatmaps carry every band from every technology, too wide for 4-up. Not !important,
       so .div-grid's 1-column rule under 580px still wins. */
    .div-grid.combined-matrix-grid { grid-template-columns: repeat(2, 1fr); }
}
/* Chrome / Edge */
.table-wrapper::-webkit-scrollbar {
    height: 10px;
}
.table-wrapper::-webkit-scrollbar-track {
    background: var(--gray-2);
    border-radius: var(--radius-pill);
}
.table-wrapper::-webkit-scrollbar-thumb {
    background: var(--gray-5);
    border-radius: var(--radius-pill);
}
.table-wrapper::-webkit-scrollbar-thumb:hover {
    background: #777;
}
/* STICKY HEADER */
.data-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--surface);
}
/* GROUP HEADER ROW — for tables with section/group separator rows mixed into tbody */
.data-table tbody tr.row-group-header td,
.data-table tbody tr.row-group-header th {
    background: rgba(var(--brand-rgb),0.08);
    font-weight: 700;
    padding-top: 16px;
}
/* STICKY SECTION HEADER ROW — for grouped survey list tables */
.data-table tbody tr.sticky_header_row > td {
    background: rgba(var(--brand-rgb),0.08);
}
/* COMPACT VIEW (survey list, RMS 3344) — the same table with most columns dropped, so the
   toggle never re-renders or re-queries anything. c-hide leaves in compact, c-only (the info
   icon whose tip carries what c-hide took away) arrives. Marker classes rather than :nth-child
   because the hide-below-* utilities already change which columns are on screen at a given
   width, and the two must not fight. */
.data-table .c-only { display: none; }
.data-table.is-compact .c-hide { display: none; }
.data-table.is-compact .c-only { display: table-cell; }
/* The hide-below-* utilities drop c-hide columns too, and the info card is where their values
   still live - so it arrives at the first of those breakpoints, not only in compact. Both lists
   (survey list, SENTRY history) first hide columns at 1600. */
@media (max-width: 1600px) { .data-table .c-only { display: table-cell; } }
/* Survey list Location: capped and clamped to two lines, so a long address wraps in place
   instead of widening the table. The full text is on the title. */
.data-table .loc-cell {
    min-width: 110px;
    max-width: 220px;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;
}
/* The group headings are what make DOM order != sort order; compact drops them and the JS
   reorders rows by data-order, so the flat list reads in the sort the user actually picked. */
.data-table.is-compact tbody tr.sticky_header_row { display: none; }
/* Rows label their own values inline ("Started:" / "Surveyed:") because the two device types
   word them differently. In compact the <thead> does the labelling, so these go - along with
   the <br> that followed each one, or the value would keep its now-empty first line. */
.data-table.is-compact .row-label,
.data-table.is-compact .row-label + br { display: none; }
/* NETWORK RESULTS GROUP HEADER ROW — the "Identity Group / Detection Group / ..."
   collapsible column-group headers built in index_new.php's per-network table builder */
.network-th-toggle {
    padding: 0 0 5px 5px;
    white-space: nowrap;
    color: var(--text);
    cursor: pointer;
}
.network-th-toggle:hover {
    color: var(--network-color, var(--text));
}
.network-th-label {
    padding: 0 0 5px 5px;
    color: var(--text);
}
.network-th-spacer {
    background-color: var(--gray-3);
    padding: 0 5px;
}
/* Applied by toggleGroups() (index_new.php) in place of a hardcoded bgcolor attribute,
   to highlight a network-results column-group while it's expanded */
.group-expanded-bg {
    background-color: var(--gray-3);
}
.network-td-value {
    white-space: nowrap;
    color: var(--text);
}
/* Survey Results rows picked from the map or a Signal Log legend - mapping_core.js's setMarkerRowState()
   / performance_chart.js. Every selected row gets a gold left bar and every other result row dims; the
   S marker's transmitting cell also gets the gold background. Text colour is left alone - gold vs
   black text was too close to tell apart. */
tr.row-highlighted > td:first-child,
tr.row-transmitting > td:first-child { box-shadow: inset 3px 0 0 #d4af37; }
tr.row-transmitting > td { background-color: rgba(212,175,55,.3); }
body:has(tr.row-highlighted, tr.row-transmitting) tr[network_index]:not(.row-highlighted):not(.row-transmitting) { opacity: .45; }
/* Account page: chart-section header row (Registered Devices / Token History / Network Tally) */
.chart-section-th {
    background-color: var(--gray-3);
    color: var(--text);
}
/* Account page: "SELECTED" / "CANCELLATION SUBMITTED" etc. label - sits on the solid
   #00adef plan-highlight background, which stays fixed regardless of theme */
.plan-selected-label {
    font-size: 30px;
    color: var(--text-on-accent);
}
/* NUMERIC CELLS */
.data-table td:not(:last-child) {
    font-variant-numeric: tabular-nums;
}
/* METRIC COLOURS */
.metric-good {
    color: var(--good);
    font-weight: 600;
}
.metric-warning {
    color: var(--ok);
    font-weight: 600;
}
.metric-bad {
    color: var(--poor);
    font-weight: 600;
}
/* ALERT BANNER — inline warning/info strip above a panel or page section */
.alert-banner {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 16px;
    margin-bottom: 12px;
    border-radius: 14px;
    border: 1px solid rgba(255,102,0,0.35);
    background: rgba(255,102,0,0.10);
    color: var(--text);
    font-size: 0.9rem;
    overflow-wrap: anywhere;
}
.alert-banner .mdi {
    color: var(--signal-amber);
    font-size: 1.2rem;
}
/* Danger variant - failed-login / account-migration notices reuse the same banner shell instead
   of the legacy site's own inline-styled alert-danger markup. */
.alert-banner--danger {
    border-color: rgba(239,68,68,.4);
    background: rgba(239,68,68,.12);
}
.alert-banner--danger .mdi { color: #ef4444; }

.button-group {
    display: flex;
    gap: 8px;
}
/* INPUT GROUP - attaches a button to the right edge of an input, e.g.
   <div class="input-group"><input ...><button class="menu_button input-group-btn">+</button></div> */
.input-group {
    display: flex;
    align-items: stretch;
    width: 100%;
}
.input-group *:not(input) {
    user-select: none;
}
.input-group > *:not(:last-child) {
    border-top-right-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
    border-right-width: 0 !important;
}
.input-group > *:not(:first-child) {
    border-top-left-radius: 0 !important;
    border-bottom-left-radius: 0 !important;
}
.input-group > input {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
}
/* a labelled .field-control in place of the bare input - same shrink, or the input's own size= width
   pushes the attached button out of a narrow container */
.input-group > .field-control {
    flex: 1 1 auto;
    min-width: 0;
}
/* CLOUDSURVEY CALL TO ACTION */
.report-header {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 42px;
    overflow: hidden;
    padding: 24px 30px;
    min-height: 190px;
    background:
    radial-gradient(circle at 16% 35%, rgba(var(--brand-rgb),0.38), transparent 38%),
    radial-gradient(circle at 72% 60%, rgba(var(--brand-deep-rgb),0.22), transparent 36%),
    linear-gradient(135deg, #1e2d3d 0%, #1a2233 50%, #16202e 100%);
    border-radius: var(--radius);
    margin-bottom: 12px;
}
.report-header::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: var(--radius);
    background:
    linear-gradient(90deg, rgba(var(--brand-rgb),0.20), transparent 42%),
    linear-gradient(180deg, rgba(var(--ink-rgb),0.045), transparent 45%);
    opacity: 0.65;
}
.report-header-text {
    position: relative;
    z-index: 1;
    flex: 1 1 auto;
    min-width: 0;
}
.report-header-brand {
    display: flex;
    align-items: center;
    gap: 18px;
    margin-bottom: 18px;
}
.report-header-logo {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border-radius: 16px;
    background:
    radial-gradient(circle at top left, rgba(var(--ink-rgb),0.22), transparent 34%),
    linear-gradient(135deg, #00a7e6 0%, #006cc1 55%, #004c8c 100%);
    box-shadow:
    0 12px 30px rgba(0,0,0,0.36),
    0 0 20px rgba(var(--brand-rgb),0.20),
    inset 0 1px 0 rgba(var(--ink-rgb),0.22);
    color: var(--text-on-accent);
    font-size: 2.05rem;
    font-weight: 950;
    font-style: italic;
    letter-spacing: -0.06em;
}
.report-header-company {
    margin: 0;
    font-size: 1.5rem;
    line-height: 1.08;
    font-weight: 850;
}
.report-header-contact {
    margin: 8px 0 0 0;
    color: rgba(255,255,255,0.82);
    font-size: 1.08rem;
    font-weight: 500;
}
.report-header-contact a {
    color: var(--text-secondary);
    text-decoration: none;
}
.report-header-contact a:hover {
    color: var(--text);
    text-decoration: none;
}
.report-header-title {
    margin: 0;
    font-size: clamp(1.9rem, 2.75vw, 2.45rem);
    line-height: 1.12;
    font-weight: 900;
    letter-spacing: -0.035em;
    text-shadow: 0 3px 14px rgba(0,0,0,0.32);
}
.report-header-title-highlight {
    color: var(--accent-soft);
}
.report-header-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    margin-top: 20px;
}
.report-header-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 12px 22px;
    border: 1px solid rgba(var(--accent-soft-rgb),0.26);
    border-radius: 15px;
    background:
    radial-gradient(circle at top left, rgba(var(--ink-rgb),0.15), transparent 42%),
    linear-gradient(135deg, rgba(var(--brand-bright-rgb),0.24), rgba(var(--brand-deep-rgb),0.18));
    color: var(--text-on-accent);
    font-size: 0.94rem;
    font-weight: 850;
    letter-spacing: 0.018em;
    text-transform: uppercase;
    backdrop-filter: blur(8px);
    box-shadow:
    0 9px 24px rgba(0,0,0,0.26),
    0 0 18px rgba(var(--brand-rgb),0.16),
    inset 0 1px 0 rgba(var(--ink-rgb),0.14);
}
.cloud-cta-wrap {
    position: relative;
    z-index: 2;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    flex: 0 0 auto;
    margin: 0;
}
.cloud-cta-button {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    width: 500px;
    max-width: 500px;
    padding: 24px 28px;
    border: 1px solid rgba(var(--ink-rgb),0.18);
    border-radius: 15px;
    background:
    radial-gradient(circle at top left, rgba(var(--ink-rgb),0.22), transparent 32%),
    linear-gradient(135deg, #00a7e6 0%, #0078bd 48%, #004c8c 100%);
    color: var(--text-on-accent);
    font-family: var(--font);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    box-shadow:
    0 10px 26px rgba(0,0,0,0.34),
    0 0 22px rgba(var(--brand-rgb),0.22),
    inset 0 1px 0 rgba(var(--ink-rgb),0.2);
    transition:
    width 0.2s ease,
    max-width 0.2s ease,
    padding 0.2s ease,
    border-radius 0.2s ease,
    transform 0.16s ease,
    box-shadow 0.16s ease;
}
.cloud-cta-button:hover {
    transform: translateY(-2px);
    box-shadow:
    0 14px 34px rgba(0,0,0,0.42),
    0 0 30px rgba(var(--brand-rgb),0.36),
    inset 0 1px 0 rgba(var(--ink-rgb),0.26);
}
.cloud-cta-button:active {
    transform: translateY(0);
}
.cloud-cta-main {
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.cloud-cta-kicker {
    font-size: 0.9rem;
    font-weight: 850;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    opacity: 0.9;
}
.cloud-cta-title {
    font-size: 1.65rem;
    font-weight: 900;
    line-height: 1.12;
}
.cloud-cta-subtitle {
    color: rgba(255,255,255,0.9);
    font-size: 0.98rem;
    font-weight: 500;
    line-height: 1.32;
}
.cloud-cta-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 64px;
    height: 64px;
    border-radius: var(--radius-pill);
    background: rgba(var(--ink-rgb),0.16);
    border: 1px solid rgba(var(--ink-rgb),0.22);
    font-size: 1.55rem;
    box-shadow: inset 0 1px 0 rgba(var(--ink-rgb),0.18);
}
.cloud-cta-wrap.is-floating {
    position: fixed;
    top: 18px;
    right: 24px;
    z-index: 1000;
    margin: 0;
}
.cloud-cta-wrap.is-floating .cloud-cta-button {
    width: 285px;
    max-width: 285px;
    padding: 11px 14px;
    border-radius: 13px;
}
.cloud-cta-wrap.is-floating .cloud-cta-kicker,
.cloud-cta-wrap.is-floating .cloud-cta-subtitle {
    display: none;
}
.cloud-cta-wrap.is-floating .cloud-cta-title {
    font-size: 0.95rem;
}
.cloud-cta-wrap.is-floating .cloud-cta-icon {
    width: 36px;
    height: 36px;
    font-size: 1.08rem;
}
@media (max-width: 900px) {
    .report-header {
        align-items: stretch;
        gap: 18px;
    }
    .cloud-cta-button {
        width: 430px;
        max-width: 430px;
        padding: 22px 24px;
    }
}
@media (max-width: 760px) {
    .report-header {
        display: block;
    }
    .report-header-brand {
        align-items: flex-start;
    }
    .report-header-meta {
        margin-bottom: 2px;
    }
    .cloud-cta-wrap,
    .cloud-cta-wrap.is-floating {
        position: static;
        margin-top: 16px;
        justify-content: stretch;
    }
    .cloud-cta-button,
    .cloud-cta-wrap.is-floating .cloud-cta-button {
        width: 100%;
        max-width: 100%;
    }
}
.cloud-cta-button,
.cloud-cta-button:visited,
.cloud-cta-button:hover,
.cloud-cta-button:active {
    color: var(--text-on-accent);
    text-decoration: none;
}
.cloud-cta-button {
    -webkit-tap-highlight-color: transparent;
}
.cloud-cta-button,
.cloud-cta-button:link,
.cloud-cta-button:visited,
.cloud-cta-button:hover,
.cloud-cta-button:active,
.cloud-cta-button:focus,
.cloud-cta-button *,
.cloud-cta-button *:hover {
    text-decoration: none;
    color: var(--text-on-accent);
}
/* Graph tooltip canvases should never intercept mouse clicks */
canvas[id^="tip"] {
    pointer-events: none;
}
/* EMBEDDED CANVAS GRAPH REFINEMENTS */
canvas[id^="canvas"] {
    display: block;
    margin-top: 12px;
    border-radius: 12px;
    background:
    radial-gradient(circle at top left, rgba(var(--brand-rgb),0.08), transparent 36%),
    rgba(var(--ink-rgb),0.025);
    box-shadow:
    inset 0 1px 0 rgba(var(--ink-rgb),0.04),
    0 10px 28px rgba(0,0,0,0.18);
}
canvas[id^="canvas"]:hover {
    cursor: crosshair;
}
/* Prevent tiny horizontal scrollbars beneath embedded canvas graphs */
.table-wrapper canvas[id^="canvas"] {
    max-width: 100%;
    box-sizing: border-box;
}
.table-wrapper:has(canvas[id^="canvas"]) {
    overflow-x: hidden;
}
.report-header-chip-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    margin-right: 10px;
    color: var(--accent-soft);
    flex: 0 0 auto;
    filter: drop-shadow(0 0 7px rgba(var(--accent-soft-rgb),0.38));
}
.report-header-chip-icon svg {
    display: block;
    width: 24px;
    height: 24px;
    stroke: currentColor;
    fill: none;
    stroke-width: 2.1;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.cloud-cta-icon {
    position: relative;
}
.cloud-cta-icon::before {
    content: "";
    position: absolute;
    left: -18px;
    top: 8px;
    bottom: 8px;
    width: 1px;
    background: rgba(var(--ink-rgb),0.12);
}
/* Section title layout with visible expand/collapse marker and inline signal legend */
summary.section-title {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0;
    flex-wrap: nowrap;
    list-style: none;
    cursor: pointer;
    padding-left: 30px;
    width: 100%;
    box-sizing: border-box;
}
summary.section-title::-webkit-details-marker {
    display: none;
}
summary.section-title::before {
    content: "▶";
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    color: var(--text);
    font-size: 1rem;
    line-height: 1;
}
details[open] > summary.section-title::before {
    content: "▼";
}
.signal-legend-inline {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    gap: 26px;
    margin-left: auto;
    margin-right: 12px;
    color: var(--text-secondary);
    font-size: 0.85rem;
    font-weight: 650;
    line-height: 1;
    vertical-align: middle;
    flex: 0 0 auto;
}
.signal-legend-inline span {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
}
.signal-dot {
    width: 14px;
    height: 14px;
    border-radius: var(--radius-pill);
    display: inline-block;
}
.signal-dot.good { background: var(--good); }
.signal-dot.fair { background: var(--ok); }
.signal-dot.poor { background: var(--poor); }
@media (max-width: 900px) {
    .signal-legend-inline {
        margin-left: auto;
        margin-right: 8px;
        gap: 14px;
    }
}

/* ===== SPLIT-PANEL LAYOUT (hero left / form right) ===== */
.split-panel { display: grid; grid-template-columns: 1.3fr 1fr; min-height: 100vh; }
.hero-panel {
    position: relative; overflow: hidden; padding: clamp(40px, 4vw, 84px) clamp(36px, 4.6vw, 96px);
    display: flex; flex-direction: column; justify-content: space-between;
    border-right: 1px solid rgba(50,84,105,.35);
    background:
    radial-gradient(circle at 26% 18%, rgba(var(--accent-rgb),.10), transparent 38%),
    radial-gradient(circle at 80% 78%, rgba(22,155,88,.07), transparent 42%),
    linear-gradient(160deg, #0a1620, #071018, #04090e);
}
.hero-wordmark { margin: 0; font-size: clamp(20px, 1.9vw, 38px); letter-spacing: .2em; text-transform: uppercase; font-weight: 900; color: #dff7ff; }
.hero-wordmark span { color: var(--accent); }
.hero-headline { font-size: clamp(32px, 4.3vw, 76px); line-height: 1.18; font-weight: 700; color: #f4fbff; margin: 0 0 16px; }
.hero-headline em { font-style: normal; color: var(--accent); }
.hero-sub { font-size: clamp(15px, 1.6vw, 28px); color: var(--text-muted); line-height: 1.6; margin: 0 0 32px; max-width: 42ch; }
.hero-features { display: flex; flex-direction: column; gap: clamp(16px, 1.6vw, 26px); }
.hero-feature { display: flex; align-items: flex-start; gap: 14px; }
.hero-feature-icon {
    width: clamp(38px, 3.2vw, 58px); height: clamp(38px, 3.2vw, 58px); border-radius: 9px; flex: 0 0 auto;
    border: 1px solid rgba(var(--accent-rgb),.35); background: rgba(var(--accent-rgb),.07);
    display: grid; place-items: center; color: var(--accent);
}
.hero-feature-icon svg { width: clamp(19px, 1.6vw, 29px); height: clamp(19px, 1.6vw, 29px); }
.hero-feature h4 { margin: 0 0 3px; font-size: clamp(16px, 1.45vw, 24px); font-weight: 700; color: #eaf5fa; }
.hero-feature p  { margin: 0; font-size: clamp(14px, 1.25vw, 21px); color: var(--text-muted); line-height: 1.5; }
.hero-stats { display: flex; gap: clamp(28px, 2.6vw, 56px); margin-top: 18px; }
.hero-stats > div { display: flex; flex-direction: column; }
.hero-stats b   { font-size: clamp(22px, 2.2vw, 38px); color: var(--text); letter-spacing: -.02em; }
.hero-stats span { font-size: clamp(12.5px, 1.15vw, 19px); color: var(--text-muted); margin-top: 2px; }
.hero-mobile-brand { display: none; align-items: center; gap: 10px; margin-bottom: 22px; }
.hero-mobile-brand svg { width: 24px; height: 24px; color: #dff7ff; }
.hero-mobile-brand b { letter-spacing: .18em; font-size: 15px; color: #dff7ff; text-transform: uppercase; font-weight: 900; }
.hero-mobile-brand b span { color: var(--accent); }

/* ===== FORM PANEL ===== */
/* No padding: fit_login_card() in landing.php sizes the card to 90% of this section, so the
   remaining 10% is the gutter. Mobile puts real padding back below. */
.form-panel { display: flex; align-items: center; justify-content: center; padding: 0; }

/* ===== CARD — clean bordered container ===== */
.card {
    width: 520px; max-width: 100%;
    border: 1px solid rgba(50,84,105,.48); border-radius: 14px; padding: 44px 42px 36px;
    background:
    radial-gradient(circle at 18% 0%, rgba(16,93,125,.07), transparent 34%),
    linear-gradient(180deg, rgba(11,20,30,.96), rgba(5,10,15,.985));
    box-shadow: inset 0 1px 0 rgba(var(--ink-rgb),.03), 0 24px 60px rgba(0,0,0,.45);
}

/* ===== FIELD — icon-prefix input control =====
   Flippable by default so it can be reused on regular (theme-following) panels like the
   account page. The login/register/2FA card is a deliberate fixed-dark splash screen that
   doesn't flip with the theme, so its own field-controls are pinned back below. */
.field { margin-bottom: 16px; }
.field > label {
    display: block; margin-bottom: 7px; font-size: 12px; font-weight: 800;
    letter-spacing: .04em; color: var(--text-secondary); text-transform: uppercase;
}
.field-control {
    position: relative; display: inline-flex; align-items: center; max-width: 100%;
    border: 1px solid var(--border); border-radius: 9px;
    background: var(--surface); transition: border-color .15s, box-shadow .15s;
}
.field-control:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(var(--accent-rgb),.14); }
button.field-control:hover { border-color: var(--accent); }
.field-icon { width: 38px; flex: 0 0 auto; display: grid; place-items: center; color: var(--text-secondary); }
.field-icon svg { width: 16px; height: 16px; }
.field-icon .mdi { font-size: 16px; line-height: 1; }
/* text equivalent of .field-icon - a plain word/phrase prefixing the input instead of an icon,
   e.g. <div class="field-control"><span class="field-label">Add User Access:</span><input ...></div> */
.field-label { padding-left: 12px; font-size: 14px; font-weight: 700; color: var(--text-secondary); white-space: nowrap; }
.field-control input,
/* font-family: inherit because <select>/<button> do NOT inherit it - without it these sit in the
   UA's form-control font while every label around them is var(--font). Same reason
   .field-control textarea below already carries it. */
/* text-overflow/overflow/white-space: a long selected option (e.g. edit_device.php's "New Survey
   Control") otherwise forces the select wider than its grid cell and overlaps the next field -
   .field-control's own max-width fix above caps the box, this truncates the text inside it
   instead of letting it overflow. */
.field-control select {
    flex: 1; min-width: 0; height: 36px; border: none; outline: none; border-radius: 9px; outline-offset: -2px;
    background: transparent; color: var(--text); font-size: 14px; font-weight: 600; padding: 0 6px 0 12px;
    font-family: inherit; text-overflow: ellipsis; overflow: hidden; white-space: nowrap;
}
.field-control input::placeholder { color: var(--text-secondary); font-weight: 500; }
/* Chrome paints the dropdown popup from the select's own background, which is transparent
   here and so falls back to white - repaint the options from the theme vars instead. */
.field-control select option, .field-control select optgroup { background: var(--surface); color: var(--text); }
.field-control input[type="file"] { display: flex; align-items: center; line-height: 36px; }
.field-control input[type="file"]::file-selector-button { vertical-align: middle; margin-right: 10px; }
.field-control .network_dropdown_toggle {
    flex: 1; min-width: 160px; height: 36px; border: none; outline: none; border-radius: 9px; outline-offset: -2px;
    background: transparent; color: var(--text); font-size: 14px; font-weight: 600; padding: 0 6px 0 12px;
    text-align: left; cursor: pointer; font-family: inherit;
}
/* Disabled when the survey has one operator - same arrow cursor a disabled select gets. */
.field-control .network_dropdown_toggle:disabled { cursor: default; }
/* One dropdown chevron for native selects and the .network_dropdown_toggle button alike. Each
   browser draws its own native arrow (shape, size and inset all differ), so a button can only match
   a select everywhere if both use this one instead. Masked rather than an <img>/data-URI colour so
   it follows the theme; --field-arrow lets a fixed-colour context (the login .card) override it. */
.field-control select { appearance: none; -webkit-appearance: none; }
.field-control select,
.field-control .network_dropdown_toggle { padding-right: 34px; }
.field-control:has(> select, > .network_dropdown_toggle)::after {
    content: ""; position: absolute; right: 10px; top: 50%; width: 18px; height: 18px; margin-top: -9px;
    pointer-events: none; background: var(--field-arrow, var(--text));
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7.41 8.58 12 13.17l4.59-4.59L18 10l-6 6-6-6z'/%3E%3C/svg%3E") center / contain no-repeat;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7.41 8.58 12 13.17l4.59-4.59L18 10l-6 6-6-6z'/%3E%3C/svg%3E") center / contain no-repeat;
}
/* multi-line variant - add field-control--textarea alongside field-control on the wrapper */
.field-control textarea {
    flex: 1; height: auto; min-height: 70px; resize: vertical; border: none; outline: none; border-radius: 9px; outline-offset: -2px;
    background: transparent; color: var(--text); font-size: 14px; font-weight: 600;
    font-family: inherit; line-height: 1.4; padding: 10px 6px 10px 12px;
}
.field-control textarea::placeholder { color: var(--text-secondary); font-weight: 500; }
.field-toggle {
    width: 38px; height: 36px; flex: 0 0 auto; border: none; background: transparent;
    color: var(--text-secondary); display: grid; place-items: center; cursor: pointer;
}
.field-toggle:hover { color: var(--accent); }
.field-toggle svg { width: 16px; height: 16px; }

/* Row-style action button (icon-left, label, optional trailing icon) built on .field-control -
   a clickable row rather than a form input. Border colour/icon tint are left inline per instance
   (accent/danger/etc), same convention .mgmt-row's own --icon-accent already uses. Used by the
   nav account menu (index.php) and the Device Management section's rows (includes/functions.php). */
.row-action-btn { display: flex; align-items: center; height: 40px; padding: 0 14px; gap: 10px; cursor: pointer; background: transparent; font-family: inherit; }
.row-action-btn .row-action-label { flex: 1; text-align: left; font-size: 14px; font-weight: 700; color: var(--text); }

.card .field > label { color: #bcd0db; }
.card .field-control { border-color: rgba(62,91,110,.75); background: #071018; width: 100%; --field-arrow: #eaf4fa; }
.card .field-icon, .card .field-toggle { color: #6f8896; }
.card .field-control input, .card .field-control select, .card .field-control textarea { color: #eaf4fa; }
.card .field-control select option, .card .field-control select optgroup { background: #071018; color: #eaf4fa; }
.card .field-control input::placeholder, .card .field-control textarea::placeholder { color: #5a7282; }

/* .alert-banner's colours are theme-driven (var(--text) etc) - the login card is a fixed dark
   splash that never flips with the site theme, so pin its own copy the same way .field above does. */
.card .alert-banner { color: #eaf4fa; }
.card .alert-banner a { color: #7fdcff; }

/* ===== RESPONSIVE ===== */
/* The hero text's clamp() sizing and fit_login_card()'s JS zoom (landing.php) both scale up
   continuously with viewport size, tuned for very large ("xl") monitors. Below that they make the
   hero text and login card oversized on an ordinary 1600px-or-smaller screen - pin everything
   back to the clamp() minimums (their pre-enlargement sizes) and disable the JS zoom. !important
   beats the JS's inline style.zoom without needing to touch the script itself. */
@media (max-width: 1600px) {
    .card { zoom: 1 !important; }
    .hero-wordmark   { font-size: 20px; }
    .hero-headline   { font-size: 32px; }
    .hero-sub        { font-size: 15px; }
    .hero-feature h4 { font-size: 16px; }
    .hero-feature p  { font-size: 14px; }
    .hero-stats b    { font-size: 22px; }
    .hero-stats span { font-size: 12.5px; }
}
@media (max-width: 980px) {
    .split-panel { grid-template-columns: 1fr; }
    .hero-panel  { display: none; }
    .hero-mobile-brand { display: flex; }
    .form-panel  { min-height: 100vh; padding: 48px 20px; }
    /* The 520px desktop base is what the fit script scales from; here nothing scales it, and
       auto min-sizing up the grid/flex chain lets 520px win over max-width:100% and overflow. */
    .card { width: 100%; }
}
@media (max-width: 420px) { .card { padding: 28px 22px 24px; } }
/* Standard technology Network Signal bars: no full-screen scrollbar, fixed bars only when reduced */
.data-table {
    table-layout: auto;
}
.data-table .network-signal-header,
.data-table .network-signal-cell {
    width: 24%;
    min-width: 24%;
    max-width: 24%;
    box-sizing: border-box;
}
.data-table .network-signal-cell {
    padding-left: 12px;
    padding-right: 12px;
}
.data-table .network-signal-cell .progressbar {
    width: 100%;
    min-width: 0;
    max-width: 100%;
    height: 24px;
    box-sizing: border-box;
}
.data-table .network-signal-cell .progress-label {
    max-width: calc(100% - 18px);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Only when the page is narrowed do the standard tables preserve a fixed signal bar width and scroll. */
@media (max-width: 1500px) {
    .data-table .network-signal-header,
    .data-table .network-signal-cell {
        width: 360px;
        min-width: 360px;
        max-width: 360px;
    }
}
/* Filter bar (index.php survey header, survey_list.php, history.php): equal-width columns that fill
   the card. Three tiers, no breakpoints, driven by the bar's own width rather than the viewport:
     - wide enough for every control at --filter-min: one row, capped at --filter-max per column
     - narrower: two balanced rows, ceil(n/2) columns - (n+1)/2 works without round() because
       auto-fill floors the track count
     - narrower still: as many --filter-min columns as fit, down to one
   The clamp() is the switch: (one-row width - 100%) * 999 is hugely negative when the row fits
   (clamps to the n-column width) and hugely positive when it doesn't (clamps to the half width).
   --n, the control count, is set inline by the page. */
.filter-bar {
    --n: 4;
    --filter-min: 180px;
    --filter-max: 300px;
    --filter-gap: 14px;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, max(var(--filter-min), clamp(
        100% / var(--n) - var(--filter-gap),
        (var(--n) * (var(--filter-min) + var(--filter-gap)) - 100%) * 999,
        100% / ((var(--n) + 1) / 2) - var(--filter-gap)
    ))), 1fr));
    gap: 10px var(--filter-gap);
    align-items: end;
    max-width: calc(var(--n) * (var(--filter-max) + var(--filter-gap)) - var(--filter-gap));
}
.filter-group {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    min-width: 0;
}
/* Every control fills its column; the widths the widgets carry for standalone use would otherwise
   overflow a narrow column (.network_dropdown_toggle alone asks for 240px). */
.filter-bar .field-control,
.filter-bar .network_dropdown { display: flex; width: 100%; margin: 0; }
.filter-bar .network_dropdown_toggle { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filter-bar .network_dropdown_menu { min-width: 100%; }
.filter-group > label { overflow: hidden; text-overflow: ellipsis; }

/* index.php survey header card: survey type + resolved address as the title, then the filter bar and
   the action tabs + Export row. overflow:visible because the action tabs' hover cards and the
   Operators menu hang out of the card. */
.survey-head-card { overflow: visible; }
.survey-head-card .card-title h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 12px; margin: 0; }
.survey-head-label { font-size: .72rem; font-weight: 850; letter-spacing: .08em; text-transform: uppercase; color: var(--accent); white-space: nowrap; }
.survey-head-address { padding-left: 12px; border-left: 1px solid var(--border); font-weight: 800; }
.survey-head-address:empty { display: none; }
/* minmax(0,1fr), not the default auto track: auto sizes to the filter bar's intrinsic width (its
   percentage tracks count as auto there), which pushed the bar out past the card */
.survey-controls { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }
/* the hidden-input form would otherwise take a grid row (and a gap) of its own */
.survey-controls > form { display: contents; }
.survey-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; }
.survey-actions .survey-export { flex: 0 1 300px; min-width: 180px; margin-left: auto; }
/* Direct child only. This styles the group's caption ("Survey Mode Filter", "Technology
   Filter"), but as a descendant selector it also reached every label nested inside the group's
   control - at (0,1,1) it outranks a plain widget class like .network_dropdown_item (0,1,0),
   so the technology dropdown's checkbox labels were being shrunk to 0.78rem/--text-secondary.
   The captions are always direct children, so ">" keeps them all while leaving nested widgets
   to their own styling. */
.filter-group > label {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-secondary, var(--text));
    white-space: nowrap;
}
/* RESPONSIVE VISIBILITY UTILITIES
   Generic, reusable across any table/layout: add the class to a cell/element
   and it disappears once the viewport drops to that breakpoint or below.
   Name = the width it disappears AT (matches the project's existing breakpoints). */
@media (max-width: 1600px) { .hide-below-1600 { display: none !important; } }
@media (max-width: 1150px) { .hide-below-1150 { display: none !important; } }
@media (max-width: 900px)  { .hide-below-900  { display: none !important; } }
@media (max-width: 760px)  { .hide-below-760  { display: none !important; } }
@media (max-width: 600px)  { .hide-below-600  { display: none !important; } }
         /* SUMMARY GRAPH PANELS */
/* summary-graph-head acts as the boxed-title bar for div-based panels */
.summary-graph-head{display:flex;justify-content:space-between;align-items:center;gap:16px;border-bottom:1px solid rgba(var(--ink-rgb),.07);cursor:pointer;}
.summary-graph-title{margin:0;padding:14px 18px 12px;font-size:1.05rem;font-weight:800;letter-spacing:-.015em;color: var(--heading);font-family: var(--font);}
.summary-graph-subtitle{margin:5px 0 0;color:var(--text-secondary);font-size:.88rem;line-height:1.35;}
.summary-graph-chip{display:inline-flex;align-items:center;padding:8px 12px;border:1px solid rgba(var(--accent-soft-rgb),.25);border-radius: var(--radius-pill);background:rgba(var(--brand-rgb),.13);color:#bae6fd;font-size:.82rem;font-weight:800;white-space:nowrap;}
.summary-graph-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px;align-items:stretch;}
.summary-chart-card{display:flex;flex-direction:column;min-width:0;overflow:hidden;padding:12px;border:1px solid var(--border);border-radius:14px;background:rgba(var(--ink-rgb),.035);box-shadow:inset 0 1px 0 rgba(var(--ink-rgb),.04);}
.summary-chart-title{margin:0 0 8px;color: var(--text);font-size:.86rem;font-weight:800;}
.summary-chart-canvas{display:block;width:100%;border-radius:10px;}
@media(max-width:1050px){.summary-graph-grid{grid-template-columns:1fr}}

/* Explicit column count for a .summary-graph-grid row, layered on top rather than changing that
   class (same reasoning as .journey-tab-grid below - it's reused by unrelated tabs). Network Summary
   rows went from 3 cards to 4 when Signal Over Time was added (RMS 3181), and the default
   1fr-1fr-1fr wrapped the 4th onto a row of its own.
   The count comes from PHP ($col_count in index.php) because these rows are not always full - a
   technology with no domination data has 3 cards, SENTRY's reachability row has 1 - and a fixed
   4-track grid would leave visibly empty columns.
   Breakpoints reuse the two this file already established: 1800px is "wider than a 15in laptop"
   (see .journey-tab-grid's comment below) and 1050px is where .summary-graph-grid itself stacks.
   So: >15in gets the full count in one row, a 15in laptop pairs them 2-up, mobile stacks.
   These must sit AFTER the 1050px rule above - equal specificity, so source order decides. */
.graph-cols-1{grid-template-columns:1fr}
.graph-cols-2{grid-template-columns:repeat(2,1fr)}
.graph-cols-3{grid-template-columns:repeat(3,1fr)}
.graph-cols-4{grid-template-columns:repeat(4,1fr)}
/* The narrowing breakpoints apply only on .summary-graph-grid, i.e. rows of full-size chart cards.
   On .div-grid the same classes set the base count and nothing else, because the cards there are
   small donuts that read fine 4-up on a 15in screen - and .div-grid already has its own
   !important collapse rules (2-up under 1100px, 1-up under 580px) further down this file. */
@media(max-width:1799px){
	.summary-graph-grid.graph-cols-3,
	.summary-graph-grid.graph-cols-4{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:1050px){
	.summary-graph-grid.graph-cols-2,
	.summary-graph-grid.graph-cols-3,
	.summary-graph-grid.graph-cols-4{grid-template-columns:1fr}
}
/* .card-actions is flex:1 1 auto globally, so it grows to claim an equal share of .card-head next to
   .card-title rather than shrinking to fit. That's right for a header holding a search form, but in
   a 4-column row it took 240px of a 446px card to hold a 119px dropdown and squeezed "LTE (4G)
   Signal Over Time" into three wrapped lines. Scoped to cards inside this grid only (same scoping
   convention as the .summary-graph-grid > .boxed rules further down) so nothing else changes. */
.summary-graph-grid .card-head > .card-actions{flex:0 1 auto;}

/* Journey Telemetry's 4 top-level cards (Journey Summary + Speed Summary/Coverage Data/Coverage
   Summary) reflow together via .journey-tab-grid - layered onto .summary-graph-grid rather than
   changing that class itself, since it's reused by unrelated tabs (Network Summary etc) that
   shouldn't inherit these breakpoints. Default (laptop-ish widths, matches the pre-existing look):
   Journey Summary spans the full row, the other 3 sit in a row of 3 underneath.

   contain:inline-size - this tab's content sits inside an old-school unclosed <table width='100%'>
   page scaffold (index.php ~line 562/732, no table-layout:fixed - the same class of bug
   .table-wrapper{contain:inline-size} already fixed elsewhere). Without this, journey_timeline.js
   rewriting text/table content on every drag frame (address, date/time, the dynamic tech-totals
   table) lets the browser recompute the outer table's own column width from whatever's currently
   shortest, visibly shrinking/growing this whole region as you drag - reported as the Journey
   Summary card's width "jumping" mid-drag. This isolates the grid's own sizing from its
   descendants so those content changes can't bubble back up into the table's layout pass. */
.journey-tab-grid{contain:inline-size;}
.journey-tab-grid #journey-summary-card{grid-column:1 / -1;}
@media(min-width:1800px){
	/* wider than a 15" laptop: true 2x2 - Journey Summary becomes a normal grid cell like the rest */
	.journey-tab-grid{grid-template-columns:repeat(2,1fr);}
	.journey-tab-grid #journey-summary-card{grid-column:auto;}
}
@media(max-width:1050px){
	/* .summary-graph-grid's own 1050px rule above already collapses this to one column; spanning
	   is meaningless there but reset it anyway so nothing depends on the wider-breakpoint value */
	.journey-tab-grid #journey-summary-card{grid-column:auto;}
}
/* index.php's Network Summary tab wires render_bar_chart()/render_stacked_bar_chart() straight
   into render_boxed_card() (a plain .boxed <section>, not .summary-chart-card) inside this grid.
   .boxed/.card-body are ordinary block elements, so when align-items:stretch above grows a
   shorter card to match a taller sibling in the same row, that extra height just becomes empty
   space below the chart instead of being used - .boxed itself never knows it grew. Scoped to
   .summary-graph-grid specifically (not .boxed generally, which is used all over the site) so
   nothing outside this grid is affected. Horizontal bar charts centre in the leftover space
   (a fixed row height stretched out would just look like oddly spaced-out bars); the vertical/
   stacked column chart grows to fill it instead (.bar-chart-vertical's height:200px is a
   flex-basis here, and flex:1 0 auto keeps it as the minimum - plain flex:1 is a 0 basis, which
   collapsed the chart whenever no taller sibling stretched the row) - taller columns read fine
   since .bar-fill's height is already a %, so it scales up proportionally with the extra room. render_stacked_bar_chart()
   wraps its output in .line-chart-wrap (for the legend), so .bar-chart-vertical isn't a direct
   .card-body child there like it is for render_bar_chart(vertical) - the :has() rule handles
   that nesting without also growing .line-chart-wrap for line/scatter/candle charts, which
   aren't part of this fix. */
.summary-graph-grid > .boxed{display:flex;flex-direction:column;}
.summary-graph-grid > .boxed > .card-body{flex:1;display:flex;flex-direction:column;}
.summary-graph-grid > .boxed > .card-body > .bar-chart:not(.bar-chart-vertical){margin:auto 0;}
.summary-graph-grid > .boxed > .card-body > .bar-chart-vertical,
.summary-graph-grid > .boxed > .card-body > .line-chart-wrap:has(> .bar-chart-vertical){flex:1 0 auto;}
.summary-graph-grid > .boxed > .card-body > .line-chart-wrap:has(> .bar-chart-vertical) > .bar-chart-vertical{flex:1 0 auto;}

/* Performance Monitoring's first .div-grid (index.php) mixes fixed-height gauge cards with the
   "Comparison to All Network Results" grouped-bar-chart card - same empty-space-below-the-chart
   problem as .summary-graph-grid above (default grid align-items:stretch grows .boxed's outer
   height to match the tallest gauge card, but .boxed/.card-body are plain block elements that
   don't use the extra room), scoped to .perf-metric-grid specifically since .div-grid itself is
   a generic layout class reused all over the site. */
.perf-metric-grid > .boxed{display:flex;flex-direction:column;}
.perf-metric-grid > .boxed > .card-body{flex:1;display:flex;flex-direction:column;}
.perf-metric-grid > .boxed > .card-body > .bar-chart-grouped,
.perf-metric-grid > .boxed > .card-body > .line-chart-wrap:has(> .bar-chart-grouped){flex:1;}
.perf-metric-grid > .boxed > .card-body > .line-chart-wrap:has(> .bar-chart-grouped) > .bar-chart-grouped{flex:1;}

/* Survey Results' per-technology Signal Log row (index.php, .signal-log-grid) - same
   empty-space-below problem again: the Signal Log line chart is the tall reference card, the
   "Average Signal by Network" horizontal bar list (render_bar_chart(), no .bar-chart-vertical,
   not wrapped in .line-chart-wrap since it has no legend) is the short one. */
.signal-log-grid > .boxed{display:flex;flex-direction:column;}
.signal-log-grid > .boxed > .card-body{flex:1;display:flex;flex-direction:column;}
.signal-log-grid > .boxed > .card-body > .bar-chart:not(.bar-chart-vertical){margin:auto 0;}
.signal-log-grid > .boxed > .card-body > .line-chart-wrap:has(> .line-chart-plot){flex:1;}
/* Selected-cell panel above the Signal Log chart (performance_chart.js renderSignalLogCell()) -
   the hover-card key/value blocks, but as many columns as the half-width card fits. */
.signal-log-cell{margin-bottom:12px;}
.signal-log-cell .signal-log-cell-details{grid-template-columns:repeat(auto-fit,minmax(130px,1fr));margin-top:10px;padding-top:10px;}
.signal-log-cell .signal-log-cell-details .hover-card-detail{padding-bottom:6px;}
/* values keep the table's metric colours, which .hover-card-kv span would otherwise flatten */
.signal-log-cell .metric-good{color:var(--good);}
.signal-log-cell .metric-warning{color:var(--ok);}
.signal-log-cell .metric-bad{color:var(--poor);}

.table-wrapper{contain:inline-size;overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;scrollbar-color:var(--gray-4) var(--bg);}
.data-table{width:100%;border-collapse:collapse;white-space:nowrap;}
.data-table th{background:rgba(var(--ink-rgb),0.05);padding:10px;text-align:left;font-size:0.82rem;font-weight:700;border-bottom:1px solid var(--border);color:var(--text-secondary);}
.data-table td{padding:7px 10px;border-bottom:1px solid rgba(var(--ink-rgb),0.04);font-size:0.88rem;vertical-align:middle;}
.data-table tbody tr:nth-child(even){background:rgba(var(--ink-rgb),0.02);}
.data-table tbody tr:hover{background:rgba(var(--brand-rgb),0.07);}
/* CARD VIEW - jquery/table-cards.js adds .is-cards once a wide table would scroll sideways. Each
   row becomes a card and each cell a "Label: value" line, with the label taken from data-label.
   Child combinators throughout, so a nested table that isn't in cards keeps its own layout. The
   label stays absolutely positioned rather than in a grid column, because a grid would split a
   "value<br><span>sub</span>" cell into separate items. The !important on white-space and the
   label padding beats the inline nowrap/padding/text-align some table builders put on their cells. */
table.is-cards, table.is-cards > tbody { display: block; white-space: normal; }
/* !important: the compare filters .toggle() head rows too, and jQuery's show writes an inline
   display:table-row on anything CSS still hides */
table.is-cards > thead, table.is-cards > tbody > tr[data-card-head] { display: none !important; }
table.is-cards > tbody > tr { display: block; margin: 0 0 10px; padding: 6px 0; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
table.is-cards > tbody > tr > td { display: block; width: auto !important; border: 0; padding: 4px 12px; text-align: left !important; white-space: normal !important; overflow-wrap: anywhere; }
table.is-cards > tbody > tr > td[data-label] { position: relative; padding-left: calc(40% + 10px) !important; }
table.is-cards > tbody > tr > td[data-label]::before { content: attr(data-label); position: absolute; left: 12px; top: 4px; width: calc(40% - 14px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-muted); font-size: 0.8rem; font-weight: 600; }
table.is-cards > tbody > tr > td:empty { display: none; }
/* Buttons sized for a table column (.menu_button's 200px min-width, .cs-chip--narrow's 100px, a long
   email) fit the value column, wrapping their label rather than clipping it. Any depth, since action
   buttons usually sit in a <form> inside a flex row. */
table.is-cards > tbody > tr > td :is(a, button) { min-width: 0 !important; max-width: 100%; white-space: normal !important; }
table.is-cards > tbody > tr > td > div { flex-wrap: wrap !important; }
/* Control-only cells (.card-actions, set by table-cards.js): the wrappers between cell and buttons
   (flex rows, one <form> per button) step aside with display:contents, so the buttons flex straight
   off the cell - full width each on a phone, side by side once each fits its usual 200px. A .helptip*
   trigger keeps its box and flexes instead: on touch its floating tip is placed from the trigger's
   rect, which a display:contents element doesn't have. The hidden tip text is left alone entirely. */
table.is-cards > tbody > tr > td.card-actions { display: flex; flex-wrap: wrap; gap: 8px; padding-top: 8px; padding-bottom: 8px; }
table.is-cards:not(.is-compact) > tbody > tr > td.card-actions[class*="hide-below-"] { display: flex !important; }
table.is-cards > tbody > tr > td.card-actions :is(div, form, span):has(a, button):not(.helptip, .helptipleft, .helptipdown, .helptiptext, .helptiplefttext, .helptipdowntext, :is(.helptiptext, .helptiplefttext, .helptipdowntext) *) { display: contents !important; }
table.is-cards > tbody > tr > td.card-actions :is(.helptip, .helptipleft, .helptipdown) { display: flex; flex: 1 1 200px; }
table.is-cards > tbody > tr > td.card-actions :is(a, button).menu_button { flex: 1 1 200px; justify-content: center; margin: 0; }
table.is-cards > tbody > tr > td.card-actions :is(.field-control, input:not([type="hidden"]), select, textarea) { flex: 1 1 120px; min-width: 0; width: auto !important; }
/* Rows that span the table (month headings, group headers, totals, import detail rows) and spacers */
table.is-cards > tbody > tr.card-banner { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; height: auto; padding: 0; border: 0; background: none; }
table.is-cards > tbody > tr.card-banner > td { flex: 1 1 auto; }
table.is-cards > tbody > tr.card-banner > td:only-child { flex-basis: 100%; }
/* ...unless it carries content rather than a heading (table-cards.js .card-panel): then it's a card too */
table.is-cards > tbody > tr.card-banner.card-panel { padding: 6px 0; border: 1px solid var(--border); background: var(--surface); }
/* Undo table-only hiding: a card has room for the columns the hide-below-* utilities drop, and
   the compact info icon (.c-only) only stood in for them. Compact view still hides what it hides. */
table.is-cards:not(.is-compact) > tbody > tr > td[class*="hide-below-"] { display: block !important; }
table.is-cards:not(.is-compact) > tbody > tr > td.c-only { display: none; }
table.is-cards.is-compact > tbody > tr > td.c-only, table.is-cards.is-compact > tbody > tr.is-month-hidden { display: block; }
/* A collapsed month (list-fetch.js) hides its rows - the .card-banner flex above outranks the plain
   .data-table tr.is-month-hidden rule, which left the performance rows showing under a closed month. */
table.is-cards:not(.is-compact) > tbody > tr.is-month-hidden { display: none; }
table.is-cards .row-label, table.is-cards .row-label + br { display: none; }
/* Survey Results column groups: a titled section head per group in each card, expanding that card
   only. !important beats the inline display jQuery fadeIn/fadeOut leaves on th-hidden cells. */
.card-group-head { display: none; }
table.is-cards > tbody > tr > td.card-group-head { display: block; padding: 8px 12px 2px; }
table.is-cards > tbody > tr > td.card-group-head ~ td.card-group-head { margin-top: 4px; border-top: 1px solid var(--border); }
.card-group-head button { display: flex; justify-content: space-between; align-items: center; width: 100%; padding: 2px 0; border: 0; background: none; color: var(--text); font: inherit; font-weight: 700; text-align: left; cursor: pointer; }
.card-group-head button:disabled { cursor: default; color: var(--text); }
.card-group-chevron { transition: transform .15s ease; }
.card-group-head button[aria-expanded="true"] .card-group-chevron { transform: rotate(90deg); }
table.is-cards > tbody > tr > td[th-hidden] { display: none !important; }
table.is-cards > tbody > tr > td[th-hidden].card-open { display: block !important; }
.progressbar{position:relative;width:100%;min-width:160px;height:26px;background:rgba(var(--ink-rgb),0.08);border-radius: var(--radius-pill);overflow:hidden;}
.progress-label{position:absolute;left:10px;top:50%;transform:translateY(-50%);z-index:1;font-size:0.78rem;font-weight:700;color: var(--text);white-space:nowrap;pointer-events:none;}
.progress-fill{height:100%;border-radius: var(--radius-pill);transition:width 0.4s ease;}
.progress-fill.good{background:linear-gradient(90deg,#22c55e,var(--good-strong));}
.progress-fill.ok{background:linear-gradient(90deg,#f59e0b,var(--ok-strong));}
.progress-fill.poor{background:linear-gradient(90deg,#ef4444,var(--poor-strong));}
/* Results-table cell signal bar + hover card (includes/functions.php render_cell_signal_bar()). The
   fill's edge is the average; the outlined band is the observed min-max across cycles. The bar sits
   in a .helptipdown, which is inline-block and would shrink it to min-width without this. */
td[signal_data] > .helptipdown{display:block;}
.progress-range{position:absolute;top:4px;bottom:4px;border:1.5px solid rgba(var(--ink-rgb),.55);border-radius:6px;background:repeating-linear-gradient(135deg,rgba(var(--ink-rgb),.14) 0 4px,rgba(var(--ink-rgb),.05) 4px 8px);pointer-events:none;}
/* A range starting or ending at the average sits flush with the fill's rounded tip - push that
   edge 5px past it so the band visibly overlaps. content-box keeps the other edge where it was. */
.progress-range.from-avg{box-sizing:content-box;margin-left:-5px;padding-left:5px;}
.progress-range.to-avg{box-sizing:content-box;padding-right:5px;}
.signal-range-tiles{flex:1;min-width:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;}
.signal-range-tile{padding:8px 9px;border:1px solid var(--border);border-radius:10px;background:rgba(var(--ink-rgb),.03);}
.signal-range-tile label{display:block;color:var(--text-muted);font-size:.6rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;}
.signal-range-tile strong{color:var(--text);font-size:1.05rem;white-space:nowrap;}
.signal-range-tile strong small{color:var(--text-muted);font-size:.62rem;}
.hover-card-signal-empty{color:var(--text-muted);font-size:.72rem;font-weight:600;}
.hover-card-signal-empty{margin-top:6px;}
.signal-range-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:10px;color:var(--text);font-size:.8rem;font-weight:900;}
.signal-range-head small{display:block;margin-top:2px;color:var(--text-muted);font-size:.68rem;font-weight:500;}
.signal-range-track{position:relative;height:14px;margin:18px 4px 0;border-radius:6px;background:linear-gradient(to right,var(--poor),var(--ok) 33%,var(--good) 76%);opacity:.9;}
.signal-range-area{position:absolute;top:-5px;bottom:-5px;border:2px solid var(--text);border-radius:6px;background:rgba(var(--ink-rgb),.18);}
.signal-range-avg{position:absolute;top:-9px;bottom:-9px;width:4px;margin-left:-2px;border-radius:3px;background:var(--text);box-shadow:0 0 0 1px var(--surface);}
.signal-range-ruler{display:flex;justify-content:space-between;margin:10px 4px 0;color:var(--text-muted);font-size:.6rem;}
.signal-range-spread{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:12px;padding:9px 11px;border:1px solid rgba(var(--hc-rgb),.4);border-radius:11px;background:rgba(var(--hc-rgb),.08);}
.signal-range-spread strong{color:var(--text);font-size:.78rem;}
.signal-range-spread p{margin:2px 0 0;color:var(--text-muted);font-size:.68rem;}
.signal-range-spread b{color:var(--text);font-size:1.3rem;white-space:nowrap;}
.signal-range-spread b small{color:var(--text-muted);font-size:.65rem;}
.signal-range-note{margin:14px 0 0;padding:10px 12px;border:1px solid var(--border);border-radius:10px;background:rgba(var(--ink-rgb),.03);color:var(--text-muted);font-size:.7rem;line-height:1.45;white-space:normal;}
.signal-range-note strong{display:block;margin-bottom:8px;color:var(--text);}
.signal-range-insights{display:grid;gap:6px;}
.signal-range-insights>div{display:flex;align-items:center;gap:8px;min-width:0;line-height:1.35;}
.signal-range-insights .hover-card-pill{flex:none;min-width:64px;padding:2px 7px;font-size:.6rem;text-align:center;}

/* scrollbar-gutter: body overflow:hidden (updateBodyScrollLock) otherwise drops the scrollbar and
   shifts the page ~15px wider behind the menu/modal. */
html { scroll-behavior: smooth; scrollbar-gutter: stable; }

.sticky-bar {
    position: sticky;
    top: 0;
    z-index: 900;
}
.technology-jump-title {
    margin: 0 0 12px 0;
    color: var(--text);
    font-size: 1.02rem;
    font-weight: 850;
    letter-spacing: -0.015em;
}
.technology-jump-subtitle {
    margin: -6px 0 14px 0;
    color: var(--text-secondary);
    font-size: 0.86rem;
    line-height: 1.35;
}
.technology-jump-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}
.radial-blue-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    height: 40px;
    padding: 0 16px;
    border: 1px solid rgba(var(--accent-soft-rgb),0.24);
    border-radius: var(--radius-pill);
    background:
    radial-gradient(circle at top left, rgba(var(--ink-rgb),0.14), transparent 38%),
    linear-gradient(135deg, rgba(var(--brand-bright-rgb),0.24), rgba(var(--brand-deep-rgb),0.17));
    color: var(--text);
    font-size: 0.85rem;
    font-weight: 800;
    line-height: 1;
    text-decoration: none;
    box-shadow:
    0 7px 18px rgba(0,0,0,0.22),
    inset 0 1px 0 rgba(var(--ink-rgb),0.12);
    transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease, background 0.15s ease;
    cursor: pointer;
}
.radial-blue-pill:hover,
.radial-blue-pill:focus {
    transform: translateY(-1px);
    border-color: rgba(var(--accent-soft-rgb),0.48);
    background:
    radial-gradient(circle at top left, rgba(var(--ink-rgb),0.18), transparent 38%),
    linear-gradient(135deg, rgba(var(--brand-bright-rgb),0.34), rgba(var(--brand-deep-rgb),0.24));
    box-shadow:
    0 11px 24px rgba(0,0,0,0.30),
    0 0 18px rgba(var(--brand-rgb),0.18),
    inset 0 1px 0 rgba(var(--ink-rgb),0.16);
    outline: none;
}
.radial-blue-pill svg { flex-shrink: 0; }
.radial-blue-pill--circle {
    width: 40px;
    height: 40px;
    padding: 0;
    border-radius: 50%;
    font-size: 0.75rem;
}
.technology-jump-button {
    min-height: 40px;
    padding: 10px 15px;
    border-radius: 20px;
    font-size: 0.9rem;
}
.technology-jump-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: var(--radius-pill);
    background: rgba(var(--ink-rgb),0.12);
    border: 1px solid rgba(var(--ink-rgb),0.13);
    color: var(--accent-soft);
    font-size: 0.78rem;
    font-weight: 900;
}
.technology-anchor {
    scroll-margin-top: 112px;
}
.sticky-bar > .section-title { margin: 0; }
@media (max-width: 760px) {
    .sticky-bar { position: static; }
    .technology-jump-buttons { gap: 8px; }
    .technology-jump-button { flex: 1 1 calc(50% - 8px); padding: 10px 12px; }
}
.report-header-logo-mountain{
    background:none;
    box-shadow:none;
    border-radius:0;
    width:64px;
    height:64px;
    min-width:64px;
    max-width:64px;
    min-height:64px;
    max-height:64px;
    padding:0;
    color: var(--text-on-accent);
    overflow:visible;
    display:inline-flex;
    align-items:center;
    justify-content:center;
}
.report-header-logo-mountain .siretta-mountain-svg{
    display:block;
    width:64px;
    height:64px;
    max-width:64px;
    max-height:64px;
    color: var(--text-on-accent);
    fill:currentColor;
    overflow:visible;
}
.report-header-logo-mountain .siretta-mountain-svg *{
    fill:currentColor;
    stroke:currentColor;
}         /* Keep the sticky bar clear of the floating CloudSURVEY button */
@media (min-width: 761px) {
    .sticky-bar:hover {
        background: var(--bg) !important;
    }
    .sticky-bar {
        padding-right: 430px;
        box-sizing: border-box;
    }
    .technology-jump-buttons {
        max-width: 100%;
        box-sizing: border-box;
    }
}
@media (min-width: 761px) and (max-width: 1120px) {
    .sticky-bar {
        padding-right: 390px;
    }
}
@media (max-width: 760px) {
    .sticky-bar {
        padding-right: 16px;
    }
}

/* ============================================================
CS-CHIP: Unified button / chip system
Applied to all existing button classes so no PHP changes needed
============================================================ */
.cs-chip,
.all_menu_button, button.all_menu_button, a .all_menu_button,
.all_menu_button_thin, button.all_menu_button_thin, a .all_menu_button_thin,
.menu_button, button.menu_button, a .menu_button, select.menu_button, select.cs-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 8px 16px;
    height: auto;
    min-height: 38px;
    border: 1px solid rgba(var(--accent-soft-rgb),0.26);
    border-radius: 13px;
    background:
    radial-gradient(circle at top left, rgba(var(--ink-rgb),0.15), transparent 42%),
    linear-gradient(135deg, rgba(var(--brand-bright-rgb),0.24), rgba(var(--brand-deep-rgb),0.18));
    color: var(--text);
    font-size: 0.9rem;
    font-weight: 700;
    letter-spacing: 0.01em;
    backdrop-filter: blur(8px);
    box-shadow:
    0 6px 18px rgba(0,0,0,0.22),
    0 0 14px rgba(var(--brand-rgb),0.12),
    inset 0 1px 0 rgba(var(--ink-rgb),0.12);
    cursor: pointer;
    text-decoration: none;
    transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
    font-family: var(--font);
    white-space: nowrap;
    line-height: 1;
    margin: 2px;
    box-sizing: border-box;
    width: auto;
}
select.menu_button option, select.cs-chip option { background: var(--surface); color: var(--text); font-weight: normal; }

.cs-chip:hover,
.all_menu_button:hover, button.all_menu_button:hover,
.all_menu_button_thin:hover, button.all_menu_button_thin:hover,
.menu_button:hover, button.menu_button:hover, select.menu_button:hover, select.cs-chip:hover {
    transform: translateY(-1px);
    border-color: rgba(var(--accent-soft-rgb),0.48);
    box-shadow:
    0 10px 24px rgba(0,0,0,0.28),
    0 0 20px rgba(var(--brand-rgb),0.20),
    inset 0 1px 0 rgba(var(--ink-rgb),0.18);
    color: var(--text);
    text-decoration: none;
}

/* .menu_button PRIMARY - solid blue CTA, split out of the shared glass-chip look above (still
   used for geometry: padding/radius/font/etc) so save/submit actions read as one clear primary
   action instead of blending in with every other chip/pill. Solid fill + white text reads fine
   unchanged in both themes, so no separate light/dark variant. */
.menu_button, button.menu_button, a .menu_button, select.menu_button {
    background: linear-gradient(#0aa2e6, #0879c6);
    border-color: #0789cf;
    color: var(--text-on-accent);
    box-shadow: 0 6px 16px rgba(0,137,208,.22);
    backdrop-filter: none;
}
.menu_button:hover, button.menu_button:hover, select.menu_button:hover {
    transform: translateY(-1px);
    filter: brightness(1.06);
    border-color: #0789cf;
    box-shadow: 0 8px 20px rgba(0,137,208,.28);
    color: var(--text-on-accent);
}
.menu_button:disabled:hover, button.menu_button:disabled:hover {
    transform: none;
    filter: none;
    box-shadow: 0 6px 16px rgba(0,137,208,.22);
}

/* .menu_button variants - same geometry/hover-lift as the blue default above, different fill.
   Text always var(--text-on-accent) (fixed white, doesn't flip with theme) so every variant
   stays readable without a per-button inline color override. */
.menu_button.button-dark, button.menu_button.button-dark, a.menu_button.button-dark {
    background: linear-gradient(var(--navy-900), var(--navy-975));
    border-color: rgba(255,255,255,.18);
    color: var(--text-on-accent);
    box-shadow: 0 6px 16px rgba(0,0,0,.3);
}
.menu_button.button-dark:hover, button.menu_button.button-dark:hover {
    filter: brightness(1.4);
    border-color: rgba(255,255,255,.3);
    color: var(--text-on-accent);
}

.menu_button.button-danger, button.menu_button.button-danger, a.menu_button.button-danger {
    background: linear-gradient(#e6423a, var(--record-red));
    border-color: var(--record-red);
    color: var(--text-on-accent);
    box-shadow: 0 6px 16px rgba(213,16,16,.28);
}
.menu_button.button-danger:hover, button.menu_button.button-danger:hover {
    filter: brightness(1.08);
    border-color: var(--record-red);
    color: var(--text-on-accent);
}

.menu_button.button-warning, button.menu_button.button-warning, a.menu_button.button-warning {
    background: linear-gradient(#f5a623, var(--ok-strong));
    border-color: var(--ok-strong);
    color: var(--text-on-accent);
    box-shadow: 0 6px 16px rgba(180,83,9,.28);
}
.menu_button.button-warning:hover, button.menu_button.button-warning:hover {
    filter: brightness(1.08);
    border-color: var(--ok-strong);
    color: var(--text-on-accent);
}

.menu_button.button-light, button.menu_button.button-light, a.menu_button.button-light {
    background: linear-gradient(#ffffff, #f1f3f5);
    border-color: rgba(15,23,32,.14);
    color: var(--navy-975);
    box-shadow: 0 6px 16px rgba(15,23,32,.12);
}
.menu_button.button-light:hover, button.menu_button.button-light:hover {
    filter: brightness(.97);
    border-color: rgba(15,23,32,.22);
    color: var(--navy-975);
}

.menu_button.button-dark:disabled:hover, .menu_button.button-danger:disabled:hover, .menu_button.button-light:disabled:hover {
    transform: none;
    filter: none;
}

.menu_button.stripe-button {
    width: 300px;
    justify-content: space-between;
    gap: 14px;
}
.menu_button.stripe-button img { flex: 0 0 auto; }
/* Purchase Tokens pack cards: the buy button fills the card under the price detail; columns step down
   4 -> 3 -> 2 -> 1 (.metrics.token-packs) so "Purchase 50,000 Tokens" + the Stripe logo always fit */
.metric-card .menu_button.stripe-button { width: 100%; min-width: 0; margin-top: 12px; }

/* Action buttons inside a table row's Actions column shouldn't inherit the legacy
   .menu_button min-width:200px meant for full-width menu-style buttons elsewhere */
.button-group .menu_button {
    min-width: 0;
}

.cs-chip:disabled,
.all_menu_button:disabled, button.all_menu_button:disabled,
.all_menu_button_thin:disabled, button.all_menu_button_thin:disabled,
.menu_button:disabled, button.menu_button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.cs-chip:disabled:hover,
.all_menu_button:disabled:hover, button.all_menu_button:disabled:hover,
.all_menu_button_thin:disabled:hover, button.all_menu_button_thin:disabled:hover,
.menu_button:disabled:hover, button.menu_button:disabled:hover {
    transform: none;
    border-color: rgba(var(--accent-soft-rgb),0.26);
    box-shadow:
    0 6px 18px rgba(0,0,0,0.22),
    0 0 14px rgba(var(--brand-rgb),0.12),
    inset 0 1px 0 rgba(var(--ink-rgb),0.12);
}

/* sizing for a .menu_button used as the attached button of an .input-group (see above) -
   keeps the accent colours/hover from .menu_button, just trims it down to fit inline */
.input-group-btn, button.input-group-btn {
    min-width: 44px;
    width: auto;
    margin: 0;
    padding: 8px 14px;
    flex: 0 0 auto;
    white-space: nowrap;
}

/* Active / selected chip (non-tab) */
.cs-chip.active {
    background:
    radial-gradient(circle at top left, rgba(var(--ink-rgb),0.22), transparent 42%),
    linear-gradient(135deg, rgba(var(--brand-bright-rgb),0.50), rgba(var(--brand-deep-rgb),0.40));
    border-color: rgba(var(--accent-soft-rgb),0.60);
    box-shadow:
    0 8px 22px rgba(0,0,0,0.28),
    0 0 24px rgba(var(--brand-rgb),0.26),
    inset 0 1px 0 rgba(var(--ink-rgb),0.20);
    opacity: 1;
}

/* ---- Tab bar: underline indicator style ---- */
div.tab_list {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    /* padding: 10px 16px;
    min-height: 62px; */
}
div.tab_list center {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
}
div.tab_list button.tablinks {
    position: relative;
    height: 42px;
    min-width: 160px;
    padding: 0 22px;
    border: none !important;
    border-radius: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
    color: var(--heading);
    font-size: 13px;
    font-weight: 750;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    white-space: nowrap;
    transition: color 0.15s ease;
    margin: 0;
}
div.tab_list button.tablinks::before,
div.tab_list button.tablinks::after {
    content: "";
    position: absolute;
    left: 0; right: 0;
    height: 2px;
    border-radius: var(--radius-pill);
    background: transparent;
    transition: background 0.2s ease, box-shadow 0.2s ease;
}
div.tab_list button.tablinks::before { top: 0; }
div.tab_list button.tablinks::after  { bottom: 0; }
div.tab_list button.tablinks:hover { color: var(--text); background: transparent !important; transform: none; }
div.tab_list button.tablinks:hover::after { background: rgba(var(--accent-rgb),0.30); }
div.tab_list button.tablinks.active {
    color: var(--text);
}
div.tab_list button.tablinks.active::before {
    background: rgba(var(--accent-rgb),0.22);
}
div.tab_list button.tablinks.tablinks-single.active::before {
    background: transparent;
}
div.tab_list button.tablinks.active::after {
    background: var(--accent);
    box-shadow: 0 0 10px rgba(var(--accent-rgb),0.45);
}

.portal-nav {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 14px;
    padding: 14px min(4vw, 32px);
    margin: -20px -20px 0;
    background: radial-gradient(circle at 78% -60%,rgba(0,140,255,.20),transparent 38%), linear-gradient(90deg,var(--navy-975),var(--navy-900));
    border: none !important;
    border-bottom: 1px solid rgba(var(--brand-rgb),0.18) !important;
    border-radius: 0;
}

.portal-nav #nav-row {
    align-self: stretch;
    align-items: stretch;
    justify-content: flex-start;
    gap: 30px;
    margin: -14px 0;
}
.nav-tab {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    gap: 9px;
    height: 100%;
    padding: 0 6px;
    padding-top: 6px;
    border: none;
    border-bottom: 3px solid transparent;
    background: transparent;
    color: rgba(255,255,255,0.82);
    font-size: 0.9rem;
    font-weight: 680;
    white-space: nowrap;
    cursor: pointer;
}
.nav-tab .mdi {
    font-size: 1.15rem;
}
.nav-tab:hover {
    color: #fff;
}
.nav-tab.active {
    color: #fff;
    border-color: var(--accent);
}
.nav-tab.active .mdi {
    color: var(--accent);
}
.nav-tab.admin-signed-in.active {
    border-color: var(--poor);
    background: rgba(239, 68, 68, 0.2);
}
.nav-tab.admin-signed-in.active .mdi {
    color: var(--poor);
}

.portal-nav .feedback {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: 18px;
    padding: 9px 14px;
    border: 0;
    border-radius: 9px;
    color: #fff;
    font-family: var(--font);
    font-size: 0.85rem;
    font-weight: 800;
    line-height: 1;
    background: linear-gradient(#ff8a1f,#f16a0d);
    box-shadow: 0 6px 18px rgba(241,106,13,.22);
    cursor: pointer;
}
.portal-nav .feedback:hover {
    background: linear-gradient(#ff9633,#f5751a);
}

.portal-nav .headingtitletext {
    font-size: 0.78rem;
    font-weight: 700;
    white-space: nowrap;
    letter-spacing: 0;
    padding: 0 6px;
    vertical-align: middle;
}

.portal-nav .headingtitletext,
.portal-nav .maintext,
.portal-nav .titletext,
.portal-nav .radial-blue-pill,
.portal-nav .menu_button,
.portal-nav .all_menu_button,
.portal-nav .all_menu_button_thin,
.portal-nav .cs-chip,
.portal-nav .menu_button:hover,
.portal-nav .all_menu_button:hover,
.portal-nav .all_menu_button_thin:hover,
.portal-nav .cs-chip:hover {
    color: var(--text-on-accent) !important;
}

.top-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin-left: auto;
}
.portal-nav .field-control {
    height: 44px;
    border-color: #263b52;
    border-radius: 13px;
    background: rgba(2,12,24,.72);
}
.portal-nav .field-control:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(var(--accent-rgb),.14);
}
.portal-nav .field-icon {
    color: #aeb9c8;
}
.portal-nav .field-control input {
    color: #fff;
}
.portal-nav .field-control input::placeholder {
    color: #aeb9c8;
}
.nav-pill-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    height: 44px;
    padding: 0 16px;
    border: 1px solid var(--border);
    border-radius: 13px;
    background: var(--surface);
    color: var(--text);
    font-size: 0.9rem;
    font-weight: 680;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: border-color .15s ease, background .15s ease;
}
.nav-pill-btn:hover,
.nav-pill-btn:focus {
    border-color: var(--accent);
}
.top-actions .nav-pill-btn {
    border-color: #263b52;
    background: rgba(2,12,24,.72);
    color: #fff;
}
.top-actions .nav-pill-btn:hover,
.top-actions .nav-pill-btn:focus {
    border-color: var(--accent);
    background: rgba(2,12,24,.85);
    color: #fff;
}
/* Nav plan pill: border and tier icon in the plan's colour (--plan-hc, inline from $account_plan_copy) */
.top-actions .nav-pill-btn--plan { border-color: color-mix(in srgb, var(--plan-hc) 60%, transparent); }
.top-actions .nav-pill-btn--plan:hover,
.top-actions .nav-pill-btn--plan:focus { border-color: var(--plan-hc); }
.nav-pill-btn--plan > .mdi { color: var(--plan-hc); }
.helptipdowntext .nav-pill-btn {
    border-color: var(--border);
    background: var(--surface);
    color: var(--text);
}
.helptipdowntext .nav-pill-btn:hover,
.helptipdowntext .nav-pill-btn:focus {
    border-color: var(--accent);
    background: var(--surface);
    color: var(--text);
}
.radial-blue-pill--circle-sm {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: rgba(255,255,255,0.16);
    color: inherit;
    font-size: 0.65rem;
    font-weight: 800;
}
.subscription-alert-bar {
    display: flex;
    justify-content: flex-end;
    padding: 10px min(4vw, 32px);
}

.page-hero {
    display: flex;
    flex-direction: column;
    position: relative;
    overflow: hidden;
    gap: 14px;
    /* devices.php's hero: title row + one 39px controls row, ~156-158px depending on font
       rendering, rounded up so it never decides the height itself. render_page_hero() keeps
       every hero to those two rows, so all of them land on exactly this. */
    min-height: 160px;
    padding: 24px min(4vw, 32px);
    margin: 0 -20px 20px;
    color: var(--text-on-accent);
    background:
    radial-gradient(circle at 28% 10%, rgba(0,150,255,.18), transparent 28%),
    linear-gradient(105deg, var(--hero-1), var(--hero-2) 55%, var(--hero-3));
}
.page-hero:after {
    content: "";
    position: absolute;
    right: -8%;
    bottom: -150px;
    width: 720px;
    height: 300px;
    pointer-events: none;
    border: 1px solid rgba(var(--accent-rgb),.14);
    border-radius: 50%;
    box-shadow:
    0 0 0 30px rgba(var(--accent-rgb),.024),
    0 0 0 60px rgba(var(--accent-rgb),.019),
    0 0 0 90px rgba(var(--accent-rgb),.014);
}
.page-hero-content {
    position: relative;
    z-index: 1;
}
.page-hero-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
}
.page-hero-eyebrow {
    margin: 0 0 4px;
    color: var(--accent-soft);
    font-size: 0.75rem;
    font-weight: 850;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.page-hero-title {
    margin: 0;
    font-size: 1.9rem;
    line-height: 1.15;
    letter-spacing: -0.02em;
}
.page-hero-sub {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 16px;
    margin-top: auto;
}
.page-hero-sub > .page-hero-devcontrols,
.page-hero-sub > .tab_list {
    flex: 1 1 auto;
}
/* the nav tabs always take the bottom-left row to themselves, with any pills on the row above */
.page-hero-sub > .tab_list {
    flex-basis: 100%;
}
.page-hero-alert {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    width: max-content;
    padding: 7px 12px;
    border-radius: var(--radius-pill);
    border: 1px solid rgba(196,170,255,.28);
    background: rgba(84,54,124,.34);
    color: #eadfff;
    font-size: 0.7rem;
    font-weight: 800;
}
.page-hero-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    /* shrink allowed (flex-shrink:1, not the old 0) + min-width:0 - .page-hero-row's own
       flex-wrap only moves this onto its own line, it doesn't stop the row rendering at its
       full content width; without shrink this stayed wide enough to overflow the page even on
       its own line, since nothing ever gave hero-filter-group's .chip-row (devices.php inline
       style, already flex-wrap:wrap) a narrower width to wrap against. */
    flex: 0 1 auto;
    min-width: 0;
}
.page-hero-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.page-hero .tab_list {
    margin: auto 0 -24px;
    justify-content: flex-start;
}
.page-hero .tab_list .nav-tab {
    height: auto;
    padding-bottom: 10px;
}
.page-hero .tablinks,
.page-hero .headingtitletext,
.page-hero .maintext,
.page-hero .radial-blue-pill,
.page-hero .menu_button {
    color: var(--text-on-accent) !important;
}
.page-hero .radial-blue-pill.active {
    border-color: rgba(var(--accent-soft-rgb),0.6);
    background:
    radial-gradient(circle at top left, rgba(255,255,255,0.22), transparent 38%),
    linear-gradient(135deg, rgba(var(--brand-bright-rgb),0.55), rgba(var(--brand-deep-rgb),0.4));
    box-shadow:
    0 7px 18px rgba(0,0,0,0.28),
    0 0 0 1px rgba(255,255,255,0.08) inset;
}
/* devices.php: type filter chips (left) + view/search controls (right), on the hero gradient */
.page-hero-devcontrols {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}
/* .info-pill is a .filter-chip that doesn't filter: same box and type, no dot, no hover/active.
   Chip markup is label + <b>count</b>; pill markup is <span>label</span>value, so the pill's own
   text takes the <b> style and its span takes the chip's label style. */
.filter-chip,.info-pill{display:inline-flex;align-items:center;gap:8px;height:32px;padding:0 13px;border-radius:999px;border:1px solid rgba(64,105,130,.5);background:rgba(var(--ink-rgb),.02);color:var(--text);font-size:13px;transition:border-color .15s,background .15s,color .15s;font-family:inherit;text-decoration:none;white-space:nowrap}
.filter-chip{cursor:pointer}
.filter-chip:hover{border-color:rgba(var(--accent-rgb),.5);color:var(--text)}
.filter-chip.is-active{border-color:var(--accent);background:rgba(var(--accent-rgb),.13);color:var(--text)}
.filter-chip b{font-weight:800;color:inherit;font-size:12px}
.info-pill{font-weight:800;font-size:12px}
.info-pill span{font-weight:400;font-size:13px}
/* A lone chip in the actions slot (the back links) takes the height of the control beside it,
   e.g. MySCAN's export select, instead of sitting 6px shorter. */
.page-hero-actions > .filter-chip { height: auto; min-height: 32px; align-self: stretch; }
.page-hero .chip-row { border-bottom: none; margin-bottom: 0; }
.page-hero .devices-controls { margin-bottom: 0; }
.page-hero .filter-chip, .page-hero .info-pill { border-color: rgba(255,255,255,.22); background: rgba(255,255,255,.08); color: var(--text-on-accent); }
/* Related .info-pills in a .pill-group. Groups sit further apart than the pills inside them, so the gap
   does the separating rather than a divider. Tint a group (or one pill) inline with style="--pill-rgb:r,g,b",
   the way .chip-dot takes its colour. */
.pill-group { display: inline-flex; flex-wrap: wrap; gap: 8px; }
.page-hero-pills:has(.pill-group) { column-gap: 20px; }
[style*="--pill-rgb"] .info-pill, .info-pill[style*="--pill-rgb"] { border-color: rgba(var(--pill-rgb),.6); background: rgba(var(--pill-rgb),.18); }
.page-hero .filter-chip:hover { background: rgba(255,255,255,.22); border-color: rgba(255,255,255,.5); color: var(--text-on-accent); }
.page-hero .filter-chip.is-active { background: rgba(255,255,255,.22); border-color: rgba(255,255,255,.5); color: var(--text-on-accent); }
.page-hero .view-toggle { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.18); }
.page-hero .view-toggle button { color: rgba(255,255,255,.72); }
.page-hero .view-toggle button.is-active { color: var(--text-on-accent); }
.page-hero .vt-slider { background: rgba(255,255,255,.18); }
.page-hero .field-control { border-color: rgba(255,255,255,.22); background: rgba(255,255,255,.08); }
.page-hero .field-control input { color: var(--text-on-accent); }
.page-hero .field-control input::placeholder { color: rgba(255,255,255,.55); }
.page-hero .field-icon { color: rgba(255,255,255,.65); }
/* MySCAN hero export dropdown (myscan.php) - the translucent overlay above is tuned for
   var(--text-on-accent) (always white), which would disappear on a solid light-mode field.
   This control wants a solid, opaque field rather than the translucent hero look, so it opts
   out of the .field-control rule above - but still flips with the theme like every other
   .field-control select, via the same tokens. */
.page-hero .field-control--export { background: var(--surface); border-color: var(--border); }
.page-hero .field-control--export select { color: var(--text); }
@media (max-width: 640px) {
    .page-hero-devcontrols { flex-direction: column; align-items: stretch; }
    .page-hero .devices-controls { justify-content: space-between; }
}
@media (max-width: 900px) {
    .page-hero {
        padding: 20px 22px;
    }
}
@media (max-width: 640px) {
    .page-hero-title {
        font-size: 1.5rem;
    }
}

/* Size modifiers */
.cs-chip--xs                          { padding: 5px 12px  !important; min-height: 28px !important; font-size: 0.72rem !important; border-radius: 9px !important; }
.menu_button.cs-chip--xs { min-width: 0; }
.cs-chip--sm, .all_menu_button_thin   { padding: 8px 16px !important; min-height: 36px !important; font-size: 0.82rem !important; border-radius: 11px !important; }
.cs-chip--md                          { padding: 12px 22px !important; min-height: 44px !important; font-size: 0.90rem !important; border-radius: 13px !important; }
.cs-chip--lg                          { padding: 14px 26px !important; min-height: 50px !important; font-size: 1.0rem !important; border-radius: 15px !important; }
.cs-chip--xl                          { padding: 17px 32px !important; min-height: 56px !important; font-size: 1.1rem !important; border-radius: 16px !important; }

/* Narrow variant - the legacy .menu_button min-width:200px (line 119) is what stretches a
   button out to fill its cell. .cs-chip--xs already zeroes it; this halves it instead and
   keeps the label on one line, for rows that carry two actions side by side. */
.menu_button.cs-chip--narrow { min-width: 100px; }
.cs-chip--narrow { white-space: nowrap; }



/* ============================================================
MOBILE RESPONSIVENESS
============================================================ */

/* Tab bar on narrow screens: smaller tabs, still wrapping. This used to switch to a single
   max-content row that scrolled sideways, but the bar sits inside the page's auto-layout tables, so
   that width pushed the whole page wider instead of scrolling the bar. */
@media (max-width: 860px) {
    div.tab_list center {
        gap: 8px;
    }
    div.tab_list button.tablinks {
        flex: 0 0 auto;
        width: auto !important;
        min-width: 110px;
    }
}

/* Section heading bar: auto-height so long titles don't clip */
@media (max-width: 860px) {
    .boxed-title {
        height: auto !important;
        min-height: 44px;
        padding: 10px 14px;
        line-height: 1.3;
    }
    .headingtitletext {
        font-size: 15px;
    }
}

/* Sticky nav scroll offset smaller on mobile (nav is static there) */
@media (max-width: 760px) {
    .technology-anchor {
        scroll-margin-top: 16px;
    }
}

/* Chips and header text: scale down on small screens */
@media (max-width: 660px) {
    .summary-graph-grid {
        grid-template-columns: 1fr;
    }
    .report-header-chip {
        padding: 9px 14px;
        font-size: 0.82rem;
    }
    .report-header-title {
        font-size: 1.5rem;
    }
    .report-header-company {
        font-size: 1.15rem;
    }
}

/* div-grid responsive — collapses all grid variants (including inline-style overrides) */
@media (max-width: 1100px) {
    .div-grid {
        grid-template-columns: repeat(2, 1fr) !important;
    }
}
@media (max-width: 580px) {
    .div-grid {
        grid-template-columns: 1fr !important;
    }
    .summary-graph-grid {
        grid-template-columns: 1fr !important;
    }
    /* Legacy "label | spacer | control | spacer | message" form tables (scheduling.php Add Schedule,
       account.php deletion box): one cell per line, label above its control. Display isn't
       !important, so rows the page hides inline (the schedule steps) stay hidden until jQuery shows them. */
    table.stack-form, table.stack-form > tbody, table.stack-form > tbody > tr, table.stack-form > tbody > tr > td { display: block; width: auto !important; }
    table.stack-form > tbody > tr > td[width="20"], table.stack-form > tbody > tr > td[width="50"] { display: none; }
    table.stack-form .field-control { display: flex; }
    /* a select's min-content is its longest option; a % width drops that out of the table's width sum */
    table.stack-form .field-control select { width: 100%; }
    table.stack-form .maintext { white-space: normal; }
    /* account.php's Request Account Deletion matches the reason select above it (beats its inline width:auto) */
    table.stack-form .button-danger { width: 100% !important; }
    .toggle-group { flex-wrap: wrap; }
}

.portal-nav .headertitletext {
    display: flex;
    align-items: center;
}
.portal-nav .headertitletext img {
    display: block;
    max-width: 260px;
    width: auto !important;
    height: auto;
}

@media (max-width: 1400px) {
    .portal-nav .headertitletext img {
        max-width: 280px;
    }
}

@media (max-width: 1100px) {
    .portal-nav .headertitletext img {
        max-width: 220px;
    }
}

@media (max-width: 850px) {
    .radial-blue-pill .txt {
        display: none;
    }
    .radial-blue-pill {
        padding: 0 10px;
    }
}

@media (max-width: 640px) {
    input.filter_button {
        max-width: 90px;
    }
}

/* Antenna overview: collapse to 1-col and resize circles on mobile */
@media (max-width: 860px) {
    .siretta-antenna-grid.div-grid {
        grid-template-columns: 1fr !important;
    }
    .siretta-antenna-grid .recommended-highlight-circle {
        --size: min(260px, 72vw) !important;
        width: min(260px, 72vw) !important;
        height: min(260px, 72vw) !important;
        padding: 0 !important;
    }
}

/* Antenna recommended grid (by mounting type) — same rules */
@media (max-width: 860px) {
    .siretta-recommended-grid.div-grid {
        grid-template-columns: 1fr !important;
    }
    .siretta-recommended-grid .recommended-highlight-circle {
        --size: min(260px, 72vw) !important;
        width: min(260px, 72vw) !important;
        height: min(260px, 72vw) !important;
        padding: 0 !important;
    }
}

/* Antenna table: wrap in horizontal scroll on mobile */
@media (max-width: 860px) {
    #antenna_overview table[width="100%"][border="0"][cellpadding="10"] {
        display: block;
        overflow-x: auto;
        white-space: nowrap;
    }
}



/* Tab bar .boxed wrapper: flush padding, keep background */
table.boxed:has(.tab_list) {
    padding: 0;
}

/* Tables need visible overflow for dropdowns/tooltips inside cells */
table.boxed {
    overflow: visible;
}


/* ---- KPI Card (Survey Overview) ----
|-------------------------------|
|  label        |               |
|  value        |   icon/image  |
|  sub-text     |               |
|-------------------------------|
*/
.kpi-card {
    height: 166px;
    border-radius: 11px;
    padding: 20px 22px;
    position: relative;
    overflow: hidden;
    border: 1px solid rgba(var(--ink-rgb),.07);
    box-shadow: inset 0 1px 0 rgba(var(--ink-rgb),.06), 0 14px 28px rgba(0,0,0,.36);
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    transition: box-shadow 0.15s ease, border-color 0.15s ease;
}
.kpi-card[onclick] { cursor: pointer; }
.kpi-card[onclick]:hover {
    box-shadow: inset 0 1px 0 rgba(var(--ink-rgb),.14), 0 20px 36px rgba(0,0,0,.52);
    border-color: rgba(var(--ink-rgb),.16);
}
.kpi-card::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(45deg, rgba(0,0,0,.22) 0%, rgba(var(--ink-rgb),.008) 58%, rgba(var(--ink-rgb),.025) 100%);
    pointer-events: none;
}
/* Left text column — constrained to ~60% so it never overlaps the blended icon */
.kpi-card-body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    flex: 0 0 60%;
    max-width: 60%;
    min-width: 0;
    z-index: 2;
    position: relative;
}
.kpi-card-label {
    font-size: 0.78rem;
    font-weight: 600;
    color: rgba(235,244,250,.72);
    margin: 0;
    line-height: 1.3;
}
.kpi-card-value {
    font-size: 1.8rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1.05;
    color: var(--text-on-accent);
    margin: 0;
}
.kpi-card-sub {
    font-size: 0.85rem;
    font-weight: 700;
    color: rgba(255,255,255,.82);
    margin: 0;
}
.kpi-card-icon {
    position: absolute;
    right: 0;
    top: 0;
    bottom: 0;
    width: 68%;
    pointer-events: none;
    z-index: 1;
    mix-blend-mode: multiply;
    -webkit-mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,.7) 32%, black 62%);
    mask-image: linear-gradient(to right, transparent 0%, rgba(0,0,0,.7) 32%, black 62%);
}
.kpi-card-icon img {
    position: absolute;
    right: 0;
    top: -8%;
    height: 116%;
    width: 100%;
    object-fit: contain;
    object-position: right center;
    opacity: 0.92;
}
@media (max-width: 760px) {
    .kpi-card { height: auto; min-height: 110px; padding: 16px 18px; }
    .kpi-card-body { flex: 0 0 55%; max-width: 55%; }
    .kpi-card-icon { width: 52%; }
}

/* ---- METRIC CARD - render_metric_card() in includes/functions.php ---- */
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; margin-bottom: 20px; }
.metrics.token-packs { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1600px) { .metrics.token-packs { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 1050px) { .metrics.token-packs { grid-template-columns: repeat(2, 1fr); } }
.metric-card {
    --metric-accent: var(--accent);
    min-height: 142px;
    display: flex;
    flex-direction: column;
    position: relative;
    overflow: hidden;
    padding: 19px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: 0 6px 18px rgba(0,0,0,.22);
    transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.metric-card[onclick] { cursor: pointer; }
.metric-card:hover {
    transform: translateY(-2px);
    border-color: color-mix(in srgb, var(--metric-accent) 45%, var(--border));
    box-shadow: 0 13px 28px rgba(0,0,0,.32);
}
.metric-card:before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 4px;
    background: var(--metric-accent);
}
.metric-card:after {
    content: "";
    position: absolute;
    right: -42px; top: -48px;
    width: 132px; height: 132px;
    border-radius: 50%;
    background: color-mix(in srgb, var(--metric-accent) 14%, transparent);
}
.metric-card:has(.metric-image):after { display: none; }
/* account Subscription tab's top row - washed in the card's own accent (the active plan colour) */
.plan-summary-grid .metric-card { background: color-mix(in srgb, var(--metric-accent) 10%, var(--surface)); }
.metric-card .metric-image {
    position: absolute; right: 0; top: 0; bottom: 0; width: 55%; z-index: 0; pointer-events: none;
    background-size: contain; background-repeat: no-repeat; background-position: center right;
    -webkit-mask-image: linear-gradient(to left, black 0%, transparent 70%);
    mask-image: linear-gradient(to left, black 0%, transparent 70%);
    opacity: .5;
}
.metric-top {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    position: relative;
    z-index: 1;
}
.metric-label { color: var(--text-muted); font-size: .78rem; font-weight: 800; }
.metric-icon {
    width: 40px; height: 40px;
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    border-radius: 12px;
    color: var(--metric-accent);
    background: color-mix(in srgb, var(--metric-accent) 16%, transparent);
}
.metric-icon .mdi { font-size: 20px; line-height: 1; }
.metric-value {
    margin-top: auto;
    position: relative;
    z-index: 1;
    color: var(--text);
    font-size: 1.9rem;
    line-height: 1;
    font-weight: 900;
    letter-spacing: -.035em;
}
.metric-value.date { font-size: 1.5rem; line-height: 1.12; }
.metric-detail {
    min-height: 18px;
    margin-top: 7px;
    position: relative;
    z-index: 1;
    color: var(--text-muted);
    font-size: .78rem;
    font-weight: 650;
}
.metric-detail strong { color: var(--text); }
.metric-detail-pills { display: flex; flex-wrap: wrap; gap: 5px; }
.metric-pill {
    display: inline-flex; align-items: center; gap: 3px;
    padding: 2px 8px; border-radius: var(--radius-pill);
    background: rgba(var(--ink-rgb),.06);
    border: 1px solid rgba(var(--ink-rgb),.1);
    color: var(--text-muted);
    font-size: .72rem; font-weight: 700; white-space: nowrap;
}
.metric-pill strong { color: var(--text); font-weight: 800; }
@media (max-width: 760px) {
    .metric-card { min-height: 128px; }
    .metrics.token-packs { grid-template-columns: 1fr; }
}

/* ---- PLAN CARDS - account.php Subscription tab, pages/account.php ----
   The cards themselves are .hover-card (below) - the same markup the nav plan pill's tooltip uses -
   so this only lays them out in a grid and gives them the surface a tooltip gets from .helptipdowntext. */
.plan-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 14px 0; }
@media (max-width: 1100px) { .plan-grid { grid-template-columns: repeat(2, 1fr) !important; } }
@media (max-width: 580px) { .plan-grid { grid-template-columns: 1fr !important; } }
.plan-grid .hover-card, .plan-col > .hover-card {
    border: 1px solid color-mix(in srgb, var(--hc) 35%, var(--border)); border-radius: var(--radius);
    background: var(--surface); box-shadow: 0 6px 18px rgba(0,0,0,.22);
}

.plan-pending-label {
    display: inline-flex; align-items: center; gap: 6px;
    color: var(--text-muted); font-size: .8rem; font-weight: 800;
}
/* in the plan's own colour (--hc, from the hover card it sits in) */
.plan-current-pill {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    width: 100%; min-height: 40px; padding: 0 16px;
    border: 1px solid color-mix(in srgb, var(--hc) 35%, transparent);
    border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--hc) 14%, transparent);
    color: var(--hc);
    font-size: .85rem; font-weight: 800;
}
.plan-current-pill .mdi { color: inherit; }
/* green and gold text washes out on white - darken it in light mode, same as the Compare Plans ticks */
body.light-mode .plan-current-pill { color: color-mix(in srgb, var(--hc) 60%, #000); }

/* ---- HOVER CARD - includes/functions.php render_hover_card() ----
   Inside a .helptipdowntext (nav plan pill) and as the account Subscription tab's plan cards
   (.plan-grid above) - one set of rules so the two can't drift. */
.helptipdown .helptipdowntext--card { min-width: 380px; max-width: min(440px, calc(100vw - 24px)); padding: 0; }
.hover-card {
    --hc: var(--accent); --hc-rgb: var(--accent-rgb); --hc-on: var(--text-on-accent);
    position: relative; display: flex; flex-direction: column; padding: 20px; overflow: hidden; border-radius: 11px;
}
.hover-card--gold { --hc: #ffc857; --hc-rgb: 255,200,87; --hc-on: #241700; }
.hover-card--red { --hc: #e8444d; --hc-rgb: 232,68,77; --hc-on: #fff; }
.hover-card::after {
    content: ""; position: absolute; right: -80px; bottom: -120px; width: 280px; height: 280px;
    border-radius: 46% 54% 55% 45%; border: 1px solid rgba(var(--hc-rgb),.28);
    transform: rotate(-24deg); pointer-events: none;
}
.hover-card > * { position: relative; z-index: 1; }
.hover-card-head { display: flex; align-items: flex-start; gap: 14px; }
.hover-card-orb {
    width: 52px; height: 52px; flex: 0 0 auto; display: grid; place-items: center;
    border: 2px solid var(--hc); border-radius: 50%; color: var(--hc); font-size: 26px;
    background: rgba(var(--hc-rgb),.12); box-shadow: 0 0 18px rgba(var(--hc-rgb),.28);
}
.hover-card-heading { min-width: 0; }
.hover-card-kicker { color: var(--hc); font-size: .62rem; font-weight: 900; letter-spacing: .12em; text-transform: uppercase; }
.hover-card-title-row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 3px; }
.hover-card-title { margin: 0; color: var(--text); font-size: 1.35rem; font-weight: 900; line-height: 1.05; }
.hover-card-badge {
    padding: 3px 9px; border: 1px solid rgba(var(--hc-rgb),.5); border-radius: var(--radius-pill);
    background: rgba(var(--hc-rgb),.1); color: var(--hc);
    font-size: .6rem; font-weight: 900; letter-spacing: .06em; text-transform: uppercase; white-space: nowrap;
}
/* "badge_pinned": a tab flush in the card's top-right corner, inside the 20px top padding so it clears the kicker */
.hover-card > .hover-card-badge--pinned {
    position: absolute; top: 0; right: 0;
    border-width: 0 0 1px 1px; border-radius: 0 0 0 var(--radius-sm);
    background: color-mix(in srgb, var(--hc) 18%, var(--surface));
}
/* render_hover_card()'s "head_actions" - the SENTRY card's Prev/Next. Right margin clears .hover-card-close. */
.hover-card-head-actions { margin: 0 36px 0 auto; display: flex; gap: 8px; align-self: center; flex: 0 0 auto; }
.hover-card-nav-btn {
    display: inline-flex; align-items: center; gap: 4px; height: 34px; padding: 0 14px; cursor: pointer;
    border: 1px solid rgba(var(--hc-rgb),.5); border-radius: var(--radius-pill);
    background: rgba(var(--hc-rgb),.08); color: var(--text); font-size: .8rem; font-weight: 900;
}
.hover-card-nav-btn .mdi { font-size: 1.2rem; }
.hover-card-nav-btn:hover:not(:disabled) { background: rgba(var(--hc-rgb),.2); }
.hover-card-nav-btn:disabled { opacity: .35; cursor: default; }
@media (max-width: 600px) {
    .hover-card-nav-btn { width: 34px; padding: 0; justify-content: center; }
    .hover-card-nav-btn span { display: none; }
}
/* The SENTRY card's heading dial (render_sentry_compass()). Its auto margins centre it in the gap
   between the title and Prev/Next, so the actions after it drop their own. Full size with its label
   only in the 880px split card (same condition as that width rule further down). The 380px card has
   no room for the label, so the dial shrinks to the buttons' height and Prev/Next go icon-only there. */
.sentry-compass { margin: 0 auto; align-self: center; display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.sentry-compass + .hover-card-head-actions { margin-left: 0; }
.sentry-compass + .hover-card-head-actions .hover-card-nav-btn { width: 34px; padding: 0; justify-content: center; }
.sentry-compass + .hover-card-head-actions .hover-card-nav-btn span { display: none; }
.sentry-compass-dial { width: 34px; height: 34px; display: block; }
.sentry-compass-face { fill: rgba(var(--hc-rgb),.10); stroke: rgba(var(--hc-rgb),.55); stroke-width: 3; }
.sentry-compass-dial line { stroke: var(--text-muted); stroke-width: 3; stroke-linecap: round; }
.sentry-compass-dial line.minor { stroke-width: 2; opacity: .6; }
.sentry-compass-dial text { fill: var(--text); font-family: var(--font); font-size: 20px; font-weight: 900; text-anchor: middle; }
/* The outline mixes in --text so a pale marker colour (#7CB9E8 on the light card) still reads. */
.sentry-compass-needle { fill: var(--hc); stroke: color-mix(in srgb, var(--hc) 75%, var(--text)); stroke-width: 3; stroke-linejoin: round; }
.sentry-compass-dot { fill: var(--text-muted); }
.sentry-compass-text { display: none; flex-direction: column; line-height: 1.1; white-space: nowrap; }
.sentry-compass-text span { color: var(--text-muted); font-size: .58rem; font-weight: 900; letter-spacing: .12em; text-transform: uppercase; }
.sentry-compass-text b { color: var(--text); font-size: 1.05rem; font-weight: 900; }
.sentry-compass-text small { color: var(--text-muted); font-size: .72rem; font-weight: 700; }
@media (min-width: 900px) {
    .hover-card:has(.hover-card-body--split) .sentry-compass-dial { width: 52px; height: 52px; }
    .hover-card:has(.hover-card-body--split) .sentry-compass-text { display: flex; }
    .hover-card:has(.hover-card-body--split) .sentry-compass + .hover-card-head-actions .hover-card-nav-btn { width: auto; padding: 0 14px; }
    .hover-card:has(.hover-card-body--split) .sentry-compass + .hover-card-head-actions .hover-card-nav-btn span { display: inline; }
}
.hover-card-tagline { margin: 7px 0 0; color: var(--text-muted); font-size: .8rem; line-height: 1.45; }
.hover-card-pills { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.hover-card-pill {
    padding: 3px 10px; border: 1px solid rgba(var(--hc-rgb),.35); border-radius: var(--radius-pill);
    background: rgba(var(--hc-rgb),.08); color: var(--hc); font-size: .68rem; font-weight: 800;
    white-space: nowrap;
}
.hover-card-rule { height: 1px; margin: 16px 0 14px; background: linear-gradient(90deg, rgba(var(--hc-rgb),.45), rgba(var(--hc-rgb),.05)); }
.hover-card-features { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.hover-card-feature { display: flex; align-items: center; gap: 10px; color: var(--text-soft); font-size: .8rem; line-height: 1.35; }
.hover-card-feature::before {
    content: "\2713"; flex: 0 0 auto; width: 20px; height: 20px; display: grid; place-items: center;
    border-radius: 50%; background: var(--hc); color: var(--hc-on); font-size: 11px; font-weight: 900;
}
.hover-card-strip {
    display: flex; align-items: center; gap: 10px; margin-top: 16px; padding: 11px 12px;
    border: 1px solid rgba(var(--hc-rgb),.4); border-radius: 12px; background: rgba(var(--hc-rgb),.08);
    color: inherit; text-decoration: none; transition: border-color .15s ease;
}
.hover-card-strip + .hover-card-strip { margin-top: 8px; }
/* render_hover_card()'s strip "tone" - same greens/ambers as .pill-ready/.pill-new */
.hover-card-strip--good { --hc: #22c55e; --hc-rgb: 34,197,94; }
.hover-card-strip--warn { --hc: #f59e0b; --hc-rgb: 245,158,11; }
.hover-card-strip:hover { border-color: var(--hc); }
.hover-card-strip .mdi { color: var(--hc); font-size: 22px; line-height: 1; }
.hover-card-strip-copy { flex: 1; min-width: 0; }
.hover-card-strip-title { display: block; color: var(--text); font-size: .78rem; }
.hover-card-strip-desc { display: block; margin-top: 2px; color: var(--text-muted); font-size: .7rem; line-height: 1.35; }
/* Top-bar light/dark hover card (index.php): each option is a mini preview of that theme, so its
   colours are fixed rather than tokens - the light one has to look light while the page is dark. */
.appearance-options { display: grid; grid-template-columns: 1fr 1fr; gap: 11px; margin-top: 16px; }
.appearance-option {
    display: block; padding: 9px; border: 2px solid var(--border); border-radius: 12px; background: transparent;
    color: var(--text); font: inherit; text-align: left; cursor: pointer; transition: border-color .15s ease, background .15s ease;
}
.appearance-option:hover { border-color: rgba(var(--hc-rgb),.6); }
.appearance-preview {
    display: flex; flex-direction: column; justify-content: center; gap: 6px; height: 68px; padding: 10px 14px;
    border-radius: 7px; background: #f8fcff; box-shadow: inset 0 0 0 1px rgba(170,185,192,.2);
}
.appearance-preview i { display: block; width: 75%; height: 9px; border-radius: 3px; background: #0a84cd; }
.appearance-preview span { display: block; width: 94%; height: 6px; border-radius: 4px; background: #b6d7e7; }
.appearance-preview span:last-child { width: 62%; }
.appearance-dark .appearance-preview { background: #142b41; }
.appearance-dark .appearance-preview i { background: #1ac6ff; }
.appearance-dark .appearance-preview span { background: #39566d; }
.appearance-option-caption { display: flex; align-items: center; gap: 6px; margin-top: 9px; font-size: .75rem; font-weight: 900; }
.appearance-option-caption .mdi { color: var(--hc); font-size: 16px; line-height: 1; }
.appearance-active-tag { display: none; margin-left: auto; padding: 2px 7px; border-radius: 999px; font-size: .56rem; background: rgba(var(--hc-rgb),.18); color: var(--hc); }
body.light-mode .appearance-light, body:not(.light-mode) .appearance-dark { border-color: var(--hc); background: rgba(var(--hc-rgb),.1); cursor: default; }
body.light-mode .appearance-light .appearance-active-tag, body:not(.light-mode) .appearance-dark .appearance-active-tag { display: inline-block; }
body.light-mode .appearance-when-dark, body:not(.light-mode) .appearance-when-light { display: none; }
/* CTA text follows the theme (white in dark mode, ink in light) rather than each accent's --hc-on, so every card's button reads the same. */
.hover-card-cta {
    display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 42px; margin-top: 12px;
    border-radius: 11px; color: var(--text); font-size: .82rem; font-weight: 900; text-decoration: none;
    background: linear-gradient(180deg, var(--hc), color-mix(in srgb, var(--hc) 78%, #000));
    box-shadow: 0 8px 18px rgba(var(--hc-rgb),.22), inset 0 1px 0 rgba(255,255,255,.3);
}
.hover-card-cta:hover { filter: brightness(1.08); }
.hover-card-cta .mdi { font-size: 1.1rem; }
/* Account plan cards: capacity line + the subscribe/cancel forms and status pills where a tooltip has
   .hover-card-cta. The form buttons are restyled to look like it; Cancel keeps its danger red. */
.hover-card-footer { margin-top: auto; padding-top: 16px; text-align: center; }
.hover-card-capacity { margin: 0 0 10px; color: var(--hc); font-size: .82rem; font-weight: 850; }
.hover-card-footer > *:not(.hover-card-capacity) { margin: 0; }
.hover-card-footer .menu_button {
    width: 100%; min-height: 42px; margin: 0; border-radius: 11px; font-size: .82rem; font-weight: 900;
}
.hover-card-footer .menu_button:not(.button-danger) {
    border-color: transparent; color: var(--text);
    background: linear-gradient(180deg, var(--hc), color-mix(in srgb, var(--hc) 78%, #000));
    box-shadow: 0 8px 18px rgba(var(--hc-rgb),.22), inset 0 1px 0 rgba(255,255,255,.3);
}
.hover-card-footer .menu_button:not(.button-danger):hover { color: var(--text); border-color: transparent; }
.hover-card-footer .plan-current-pill { min-height: 42px; border-radius: 11px; }

/* Nav tab hover cards (render_nav_tab()): the wrapper stays a flex item like the bare <a> was, the card hangs
   left-aligned under the tab instead of right-aligned under a pill, and hover has no meaning in the mobile
   drawer (there the wrapper goes back to a plain block so the full-width button rules still apply). */
.tab_list .helptipdown { display: flex; cursor: auto; }
.helptipdown .helptipdowntext--left { right: auto; left: 0; }
.helptipdown .helptipdowntext--left::after { right: auto; left: 24px; margin: 0 0 0 -9px; }
/* full-viewport-width card on phones - width/left/--tip-arrow are set by summary-charts.js */
.helptipdown .helptipdowntext--full { min-width: 0; max-width: none; }
.helptipdown .helptipdowntext--full::after { right: auto; left: var(--tip-arrow); margin: 0 0 0 -9px; }
#nav-row.responsive .helptipdown { display: block; }
#nav-row.responsive .helptipdowntext { display: none; }

/* Account menu hover card (render_hover_card()'s avatar/icon-feature/actions opts, index.php's
   $profile_message): same card, an initials orb instead of a glyph, icon-led stat lines instead of
   ticks, and grouped link rows instead of the single strip/cta. */
.hover-card-orb--text { font-size: 18px; font-weight: 900; letter-spacing: -.02em; }
.hover-card-feature--icon::before { content: none; }
.hover-card-feature--icon .mdi {
    flex: 0 0 auto; width: 20px; height: 20px; display: grid; place-items: center;
    border-radius: 50%; background: var(--hc); color: var(--hc-on); font-size: 12px;
}
/* "Not included" lines in the survey result cards' .locked block - same shape as
   .hover-card-action--danger, reusing the same --poor token. */
.hover-card-feature--danger .mdi { background: var(--poor); color: #fff; }
.hover-card-group-label {
    margin: 4px 0 8px; color: var(--text-muted); font-size: .62rem; font-weight: 900;
    letter-spacing: .1em; text-transform: uppercase;
}
.hover-card-actions { display: grid; gap: 8px; }
.hover-card-action {
    display: grid; grid-template-columns: 34px minmax(0,1fr) 18px; align-items: center; gap: 10px;
    min-height: 48px; padding: 0 12px; border: 1px solid var(--border); border-radius: 12px;
    background: rgba(var(--ink-rgb),.03); color: inherit; text-decoration: none;
    transition: border-color .15s ease, background .15s ease;
}
.hover-card-action:hover { border-color: var(--hc); background: rgba(var(--hc-rgb),.08); }
.hover-card-action-icon {
    width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center;
    color: var(--hc); background: rgba(var(--hc-rgb),.1); font-size: 18px;
}
.hover-card-action-copy { min-width: 0; }
.hover-card-action-title { display: block; color: var(--text); font-size: .78rem; font-weight: 900; }
.hover-card-action-desc { display: block; margin-top: 2px; color: var(--text-muted); font-size: .68rem; line-height: 1.3; }
.hover-card-action-arrow { color: var(--hc); font-size: 1.1rem; text-align: right; }
.hover-card-action--gold { border-color: rgba(255,200,87,.4); background: rgba(255,200,87,.08); }
.hover-card-action--gold .hover-card-action-icon { color: #ffc857; background: rgba(255,200,87,.14); }
.hover-card-action--gold .hover-card-action-arrow { color: #ffc857; }
.hover-card-action--gold:hover { border-color: #ffc857; background: rgba(255,200,87,.14); }
.hover-card-action--danger { border-color: color-mix(in srgb, var(--poor) 35%, transparent); background: color-mix(in srgb, var(--poor) 8%, transparent); }
.hover-card-action--danger .hover-card-action-icon { color: var(--poor); background: color-mix(in srgb, var(--poor) 12%, transparent); }
.hover-card-action--danger .hover-card-action-arrow { color: var(--poor); }
.hover-card-action--danger:hover { border-color: var(--poor); background: color-mix(in srgb, var(--poor) 14%, transparent); }

/* Map marker tooltips (render_hover_card()'s "body" passthrough, index.php's cellular/eNodeB/S/P/C/G
   marker builders and jquery/mapping_helpers.js's renderHoverCard() JS twin for the client-built
   ones): a stack of existing .progressbar rows for signal metrics, then a two-column identity/
   network/radio/location detail grid - same --hc/--text tokens as the rest of the card. */
.hover-card-metrics { display: grid; gap: 8px; margin: 14px 0; }
/* RSRP/RSSI/RSRQ readout (hover_card_signal_grid() / hoverCardSignalGrid()): a bordered strip of
   cells, each value + thin bar (.progress-fill supplies the good/ok/poor gradient) + quality word
   (.metric-good/-warning/-bad). auto-fit so a GSM cell with only an RSSI just shows one cell. */
.hover-card-signal-grid { display: grid; grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr)); margin: 14px 0 4px; border: 1px solid var(--border); border-radius: 12px; background: rgba(var(--ink-rgb),.03); overflow: hidden; }
.hover-card-signal { padding: 10px 12px; }
/* Header row of the same panel: overall signal quality, full width above the per-reading cells. */
.hover-card-signal-overall { grid-column: 1 / -1; padding: 11px 12px 12px; }
.hover-card-signal-grid > .hover-card-signal-overall ~ .hover-card-signal { border-top: 1px solid var(--border); }
.hover-card-signal-operator { grid-column: 1 / -1; display: flex; align-items: center; gap: 6px; padding: 10px 12px; color: var(--text); font-size: .82rem; font-weight: 800; }
.hover-card-signal-operator ~ * { border-top: 1px solid var(--border); }
.hover-card-signal-overall-head {display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.hover-card-signal-overall-value { color: var(--text); font-size: 1.05rem; font-weight: 900; white-space: nowrap; }
.hover-card-signal-overall-value small { font-size: .68rem; }
.hover-card-signal-overall .hover-card-signal-bar { height: 9px; margin-top: 7px; }
.hover-card-signal + .hover-card-signal { border-left: 1px solid var(--border); }
.hover-card-signal-label { color: var(--text-muted); font-size: .62rem; font-weight: 900; letter-spacing: .06em; }
.hover-card-signal-value { margin-top: 2px; color: var(--text); font-size: 1.05rem; font-weight: 900; white-space: nowrap; }
.hover-card-signal-value small { color: var(--text-muted); font-size: .68rem; font-weight: 700; }
.hover-card-signal-bar { height: 7px; margin-top: 6px; border-radius: var(--radius-pill); background: rgba(var(--ink-rgb),.1); overflow: hidden; }
.hover-card-signal-quality { margin-top: 5px; font-size: .68rem; }
.hover-card-details { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.hover-card-detail { padding-bottom: 12px; }
.hover-card-detail-title { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; color: var(--text); font-size: .78rem; font-weight: 900; }
.hover-card-detail-title .mdi { color: var(--hc); font-size: 18px; }
.hover-card-kv { display: grid; grid-template-columns: 1fr auto; gap: 4px 10px; font-size: .74rem; line-height: 1.35; }
.hover-card-kv b { color: var(--text-muted); font-weight: 700; }
.hover-card-kv span { color: var(--text); text-align: right; }
/* Value on its own line under the label, for values too long to share a half-width row. */
.hover-card-kv--stack { grid-template-columns: 1fr; gap: 0; }
.hover-card-kv--stack span { text-align: left; margin-bottom: 4px; }
.hover-card-detail--wide { grid-column: 1 / -1; }
/* Label/count table with a ruled total row (eNodeB band breakdown). */
.hover-card-kv--table b { color: var(--text); font-weight: 400; }
/* A bare <hr> in the grid takes one cell and knocks the next label into the value column. */
.hover-card-kv hr { grid-column: 1 / -1; width: 100%; margin: 4px 0; border: 0; border-top: 1px solid var(--border); }
.hover-card-kv .hover-card-kv-total { color: var(--text); font-weight: 900; }
/* Single key/value block under the head (C/P/G position pins): one column, and no bottom padding
   since nothing follows it - the card's own padding is the chin. */
.hover-card-details--plain { grid-template-columns: 1fr; margin-top: 6px; }
.hover-card-details--plain .hover-card-detail { padding-bottom: 0; }
.hover-card-kv .hover-card-nowrap { white-space: nowrap; }
.hover-card-kv .hover-card-link { cursor: pointer; text-decoration: underline; }
@media (max-width: 460px) {
    .hover-card-details { grid-template-columns: 1fr; }
}
/* The "S" marker keeps its existing index.php-built <font>/<b>/<br> content wholesale (see the
   render_hover_card() call around its $json_marker row) rather than being reshaped into the grid
   above - it's built alongside CSV-export/telematics code that isn't worth the risk of touching for
   a styling pass. That content still opens with <font color="#000000"> for legibility against the
   old forced-white .gm-style-iw panel; now that panel is transparent (see the
   .gm-style-iw:has(.hover-card) rule) hard black would sit flat in dark mode, so neutralise
   specifically that literal colour - a <font>'s own color attribute is an explicit per-element
   value, so a plain inherited --text on the wrapper below can't override it, only a same-element
   rule can. The section sub-headings' own accent colours (network_color()-derived, not #000000)
   aren't touched by this and keep their colour in both themes. */
.hover-card-body { margin-top: 14px; font-size: .8rem; line-height: 1.55; }
.hover-card-body font[color="#000000"] { color: inherit; }
/* The "S" marker's "Cells Located" list - one row per cell a survey position saw, unbounded in count
   (a busy position can carry dozens) - so it scrolls in its own box instead of growing the whole card
   (and the page under it) to fit however many rows one position happens to have.
   Tried column-count:2 here first to show more rows before scrolling - broke visibly in testing:
   once there's more content than 2 columns' worth at this height, browsers spill extra columns
   sideways past the container's own width instead of scrolling within a fixed 2, and that overflow
   isn't reliably clipped. Single column with a taller cap is boring but actually contains it.
   380px happens to fit ~4 rows more than the box this replaced before scrolling kicks in. */
.hover-card-scroll { max-height: 380px; overflow-y: auto; margin-top: 8px; padding-right: 4px; }
.hover-card-cells { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
/* Wide screens: the list becomes a right-hand column beside the details. It grows to fit its rows,
   up to a cap tied to the map's height, then scrolls inside itself. A short list leaves no empty
   space below it. */
@media (min-width: 900px) {
    .gm-style-iw:has(.hover-card-body--split) .hover-card { width: 880px; max-width: calc(100vw - 32px); }
    .hover-card-body--split { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 0 20px; }
    /* cqh is .gm-style's height (the size container above). Google caps .gm-style-iw-d at map
       height - 161px (measured at 601/801/1141px maps) and scrolls the whole InfoWindow past that,
       so 300px = that 161 + the card's own 122px of header/padding + 17px slack. A 95cqh-style
       percentage can't do it: the cap's offset is fixed, so any percentage overshoots on a short
       map. The 240px floor only overshoots below a ~525px map. */
    .hover-card-body--split .hover-card-cells { display: flex; flex-direction: column; max-height: max(240px, calc(100cqh - 300px)); }
    .hover-card-body--split .hover-card-scroll { flex: 0 1 auto; min-height: 0; max-height: none; }
    /* The cells column now sets the row height, so pack the details rows at the top instead of
       stretching them apart. */
    .hover-card-body--split .hover-card-details { align-content: start; }
}
.hover-card-tech-group { margin-bottom: 10px; }
.hover-card-tech-title { margin-bottom: 6px; color: var(--text-muted); font-size: .66rem; font-weight: 900; letter-spacing: .05em; text-transform: uppercase; }
.hover-card-scroll .progressbar { margin-bottom: 6px; }
/* The currently-transmitting cell's row (index.php's SENTRY "Cells Located" list) - was a hardcoded
   #d4af37 background on the old legacy bar; an inset ring reads better on the modern .progressbar's
   own good/ok/poor fill without fighting its colour. */
.progressbar.transmitting { box-shadow: inset 0 0 0 2px #d4af37; }
/* render_hover_card()'s "close" - only ever passed by the map marker tooltips (a click-opened
   InfoWindow needs its own close affordance; every other hover-card closes on mouseout). z-index
   above .hover-card::after's decorative blob and level with the rest of the card's own content. */
.hover-card-close {
    position: absolute; top: 12px; right: 12px; z-index: 2; width: 26px; height: 26px;
    display: grid; place-items: center; border: 0; border-radius: 50%; cursor: pointer;
    background: rgba(var(--ink-rgb),.06); color: var(--text-muted); font-size: 15px; padding: 0;
}
.hover-card-close:hover { background: rgba(var(--ink-rgb),.12); color: var(--text); }

/* ---- COMPARE PLANS - account.php Subscription tab ----
   A table: feature column, then one column per plan headed by that plan's hover card. Each .plan-col
   cell sets --hc/--hc-on to its plan colour; the current plan's column is washed in it and its card
   glows. Below the table's min-width the .table-wrapper scrolls sideways; narrow cards restack it
   instead (see "Narrow card" below). */
.compare-table { width: 100%; min-width: 1000px; table-layout: fixed; border-collapse: collapse; }
.compare-table th, .compare-table td { padding: 14px 16px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: middle; }
.compare-table th:first-child { width: 25%; vertical-align: bottom; }
.compare-table td.plan-col { text-align: center; }
.compare-table tbody tr:last-child td { border-bottom: 0; }
.compare-table thead th.plan-col { padding: 20px 12px; vertical-align: top; }
.compare-table .plan-col > .hover-card { text-align: left; }
/* Keep the three cards the same height and their feature lists level when the fact boxes wrap
   differently, with the slack going above the features list rather than above the footer. The card's
   height:100% needs a definite height up the chain: 1px on the table (content still grows it) and 100%
   on the row and cell. A 1px cell height alone works in Chrome but collapses the cards in Firefox. */
.compare-table { height: 1px; }
.compare-table thead tr, .compare-table thead th.plan-col { height: 100%; }
.compare-table .plan-col > .hover-card { height: 100%; }
.compare-table .plan-col .plan-facts { margin-bottom: auto; }
.compare-table .plan-col .hover-card-footer { margin-top: 0; }
/* td only - a washed th boxes in the card's glow with a hard-edged square */
.compare-table td.plan-col.is-current { background: color-mix(in srgb, var(--hc) 9%, transparent); }
.compare-table .plan-col.is-current > .hover-card { box-shadow: 0 0 0 1px var(--hc), 0 0 28px rgba(var(--hc-rgb), .45); background: color-mix(in srgb, var(--hc) 10%, var(--surface)); }
/* group header row spanning every column; the colour-mix keeps the cyan readable on white in light mode */
.compare-table tr.compare-group td {
    padding: 11px 16px; background: rgba(var(--ink-rgb),.04);
    color: color-mix(in srgb, var(--accent) 70%, var(--text));
    font-size: 12px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
}
/* "Differences only" toggle in the card head: hide the rows every plan shares */
.compare-plans-card:has(#compareDiffOnly:checked) tr.is-same { display: none; }

/* the four fact boxes in each plan card (render_hover_card()'s "body" slot) */
.plan-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 14px; }
.plan-fact { display: grid; align-content: start; gap: 3px; padding: 10px 12px; border: 1px solid color-mix(in srgb, var(--hc) 25%, var(--border)); border-radius: 10px; background: rgba(var(--ink-rgb),.03); }
.plan-fact span { color: var(--text-muted); font-size: .62rem; font-weight: 900; letter-spacing: .06em; text-transform: uppercase; }
.plan-fact strong { color: var(--text); font-size: .9rem; font-weight: 900; line-height: 1.25; }

/* same tick as the plan card's own feature list (.hover-card-feature::before) */
.plan-feature-mark {
    width: 20px; height: 20px; display: inline-grid; place-items: center;
    border-radius: 50%; background: var(--hc); color: var(--hc-on); font-size: 11px; font-weight: 900;
}
.plan-feature-payg { color: var(--text-muted); font-size: .74rem; font-weight: 700; white-space: nowrap; }
/* allowance tile in place of the tick, in the plan colour (Cell Tower Location Markers' monthly lookups) */
.plan-allowance {
    display: inline-flex; align-items: center; gap: 12px; max-width: 100%; padding: 10px 18px;
    border-radius: 12px; text-align: left; color: var(--hc-on);
    background: linear-gradient(135deg, color-mix(in srgb, var(--hc) 75%, #fff), var(--hc));
    box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--hc) 50%, #fff), 0 4px 16px color-mix(in srgb, var(--hc) 40%, transparent);
}
.plan-allowance .mdi { font-size: 30px; line-height: 1; }
.plan-allowance strong { display: block; font-size: 1rem; font-weight: 900; letter-spacing: .02em; text-transform: uppercase; }
.plan-allowance small { display: block; font-size: .78rem; font-weight: 600; opacity: .9; }

/* Narrow card (phones, small tablets): the plan cards swipe sideways, then each feature reads as a
   full-width name with one cell per plan under it, every plan side by side, under the plan-name row
   (.compare-names, hidden on the wide table) that sticks to the top. --plans is set by account.php.
   The card clips rather than hides its overflow - hidden would become the sticky row's scroller. */
.compare-plans-card { container-type: inline-size; overflow: clip; }
.compare-table tr.compare-names { display: none; }
@container (max-width: 759px) {
    .compare-plans-card .table-wrapper { overflow: visible; contain: none; }
    .compare-table, .compare-table thead, .compare-table tbody { display: block; min-width: 0; height: auto; }
    .compare-table tr { display: grid; grid-template-columns: repeat(var(--plans, 3), minmax(0, 1fr)); }
    .compare-table th, .compare-table td { display: block; }

    .compare-table thead tr { display: flex; gap: 12px; height: auto; padding: 16px 0; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: thin; }
    .compare-table thead th:first-child { display: none; }
    /* 90% leaves the next card peeking in; the trimmed padding and fact size keep the fact boxes'
       longest words ("subscription") inside them down to a 360px phone */
    .compare-table thead th.plan-col { flex: 0 0 min(90%, 360px); height: auto; padding: 0; border: 0; scroll-snap-align: start; }
    .compare-table .plan-col > .hover-card { padding: 16px; }
    .compare-table .plan-fact { padding: 10px; }
    .compare-table .plan-fact strong { font-size: .85rem; }
    /* the swipe row clips anything outside the cards, so the current plan's glow becomes an inset ring */
    .compare-table .plan-col.is-current > .hover-card { box-shadow: inset 0 0 0 1px var(--hc); }

    /* opaque, in the card's own tint, so the rows scrolling under it don't show through */
    .compare-table tr.compare-names {
        display: grid; position: sticky; top: 0; z-index: 2;
        background: color-mix(in srgb, rgb(var(--ink-rgb)) 2.8%, var(--bg));
        border-top: 1px solid var(--border);
    }
    .compare-table tr.compare-names td:first-child { display: none; }
    .compare-table tr.compare-names td.plan-col { padding: 10px 4px; font-size: 12px; font-weight: 900; color: color-mix(in srgb, var(--hc) 70%, var(--text)); }

    .compare-table tr.compare-group td, .compare-table tbody td:first-child { grid-column: 1 / -1; }
    .compare-table tbody td:first-child { padding: 12px 16px 4px; border-bottom: 0; }
    .compare-table td.plan-col { padding: 6px 4px 12px; }

    .compare-table .plan-allowance { padding: 6px 8px; justify-content: center; text-align: center; }
    .compare-table .plan-allowance .mdi, .compare-table .plan-allowance small { display: none; }
    .compare-table .plan-allowance strong { font-size: .8rem; }
    .compare-table .plan-feature-payg { white-space: normal; }
}

/* Map section: bottom margin to separate from map controls/tab bar */
table.boxed#map_section {
    margin-bottom: 20px;
}
/* Cap map + options row to the viewport so the options stay visible on short screens. The map
   cell is height:100%, so the table gives the options row its natural (possibly wrapped) height
   and the map takes the rest. 1260px keeps the old ~1200px map on tall screens. */
table#map_section:has(#map) {
    height: 1260px;
    height: min(1260px, 95dvh);
}
/* Collapsible options row (render_collapsible_section) - closing it lets the map take the height.
   The map div's own bottom margin already spaces it, so drop the section's. */
#map_section .list-collapse { margin-bottom: 0; }
#map_section .list-collapse-head { padding-top: 0; }

/* Shared table sub-header */
th.section-th {
    background: rgba(var(--ink-rgb),.07);
    color: var(--heading);
    font-family: var(--font);
    font-size: 0.82rem;
    font-weight: 700;
    padding: 8px 12px;
    text-align: left;
}

/* ---- Reusable data-table pill components ---- */
.band-pill {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    font-size: 0.70rem;
    font-weight: 700;
    background: rgba(var(--accent-rgb),.10);
    border: 1px solid rgba(var(--accent-rgb),.28);
    color: var(--accent-soft);
    white-space: nowrap;
    margin: 1px 2px;
}
.mount-pill {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border-radius: var(--radius-pill);
    font-size: 0.74rem;
    font-weight: 600;
    background: rgba(var(--ink-rgb),.06);
    border: 1px solid rgba(var(--ink-rgb),.13);
    color: rgba(220,232,240,.80);
    white-space: nowrap;
}
.band-support-pill {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border-radius: var(--radius-pill);
    font-size: 0.76rem;
    font-weight: 700;
    background: rgba(34,197,94,.10);
    border: 1px solid rgba(34,197,94,.28);
    color: #86efac;
    white-space: nowrap;
}
.rank-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: var(--radius-pill);
    font-size: 0.70rem;
    font-weight: 700;
    line-height: 1;
    background: rgba(var(--ink-rgb),.07);
    border: 1px solid rgba(var(--ink-rgb),.12);
    color: rgba(220,232,240,.55);
}
.rank-pill.rank-pill--top {
    background: rgba(251,191,36,.14);
    border-color: rgba(251,191,36,.35);
    color: #fde68a;
}
/* Light mode: same low-contrast pastel-on-pale-tint issue as .net-chip-* / .pill-* -
   swap in darker variants so text stays readable on a white surface. */
body.light-mode .band-pill         { color: #0e7490; }
body.light-mode .mount-pill        { color: var(--text-secondary); }
body.light-mode .band-support-pill { color: #15803d; }
body.light-mode .rank-pill         { color: var(--text-secondary); }
body.light-mode .rank-pill--top    { color: #a16207; }

/* ===== DEVICE CARDS — devices_new.php ===== */
.card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 14px;
    padding: 4px 0;
}
.device-card {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 14px;
    cursor: pointer;
    transition: border-color .15s, transform .15s;
    min-height: 170px;
}
.device-card:hover { border-color: rgba(var(--accent-rgb),.45); transform: translateY(-2px); }
.device-card.is-selected {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px rgba(var(--accent-rgb),.18),
                inset 0 1px 0 rgba(var(--ink-rgb),.06),
                0 6px 22px rgba(0,0,0,.28);
}
.device-card.is-hidden { display: none; }

/* Device card content rows (also reused by the JS-rendered list/matrix views) */
.dv-card-head { display: flex; align-items: center; justify-content: space-between; }
.dv-card-type { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); }
.dv-card-name { font-size: 16px; font-weight: 900; color: var(--text); white-space: normal; }
.dv-card-pills { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.dv-card-footer { margin-top: auto; padding-top: 10px; border-top: 1px dashed rgba(var(--ink-rgb),.08); display: flex; justify-content: space-between; align-items: baseline; }
.dv-card-count { font-size: 22px; font-weight: 900; color: var(--text); line-height: 1; }
.dv-card-meta { font-size: 11px; color: var(--text-muted); }

/* Status pills */
.pill {
    display: inline-flex;
    align-items: center;
    height: 20px;
    padding: 0 8px;
    border-radius: var(--radius-pill);
    font-size: 10px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: .04em;
}
.pill-recording { background: rgba(239,68,68,.18); color: #f87171;  border: 1px solid rgba(239,68,68,.3);    }
.pill-paused    { background: rgba(245,158,11,.18); color: #fbbf24; border: 1px solid rgba(245,158,11,.3);  }
.pill-stopped   { background: rgba(var(--ink-rgb),.08); color: var(--text-muted); border: 1px solid rgba(var(--ink-rgb),.12); }
.pill-offline   { background: rgba(239,68,68,.10); color: #ef9f9f;  border: 1px solid rgba(239,68,68,.18);   }
.pill-field     { background: rgba(var(--accent-rgb),.08); color: #67d5f7; border: 1px solid rgba(var(--accent-rgb),.2);   }
.pill-ready     { background: rgba(34,197,94,.14); color: #22c55e; border: 1px solid rgba(34,197,94,.3);    }

body.light-mode .pill-recording { color: #b91c1c; }
body.light-mode .pill-paused    { color: #a16207; }
body.light-mode .pill-offline   { color: #b91c1c; }
body.light-mode .pill-field     { color: #0e7490; }
body.light-mode .pill-ready     { color: #15803d; }
body.light-mode .section-pill { color: #0e7490; }

/* SENTRY Uptime admin page (pages/sentry_uptime.php) - status line + 90-day bar strip */
.uptime-status { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.uptime-status .pill { height: 26px; padding: 0 12px; font-size: 12px; }
.uptime-status-text { color: var(--text); font-size: 14px; }
.uptime-status-meta { margin-top: 8px; color: var(--text-muted); font-size: 12px; overflow-wrap: anywhere; }
.uptime-bars { display: flex; gap: 2px; height: 36px; }
.uptime-bar { flex: 1 1 0; min-width: 0; height: 36px; border-radius: 2px; background: var(--good); cursor: help; transition: opacity .15s ease; }
.uptime-bar:hover { opacity: .7; }
.uptime-bar--warn { background: var(--ok); }
.uptime-bar--bad  { background: var(--poor); }
.uptime-bar--none { background: rgba(var(--ink-rgb),.12); }
.uptime-axis { display: flex; justify-content: space-between; margin-top: 8px; color: var(--text-muted); font-size: 11px; }
.uptime-axis-30 { display: none; }
/* 90 bars don't fit a phone - show the last 30 days, as status pages do */
@media (max-width: 600px) {
    .uptime-bar:nth-child(-n+60) { display: none; }
    .uptime-axis-90 { display: none; }
    .uptime-axis-30 { display: inline; }
}

/* ===== GENERIC LIBRARY LIST CARD — reusable file/plan row, not tied to any one page ===== */
.list-card {
    display: grid;
    grid-template-columns: 150px minmax(0,1fr) auto;
    gap: 14px;
    align-items: center;
    padding: 14px;
    border: 1px solid rgba(var(--ink-rgb),.09);
    border-radius: 14px;
    background: rgba(var(--ink-rgb),.028);
    transition: border-color .15s, transform .15s;
}
/* Same lift-on-hover as .device-card (devices_new.php) */
.list-card:hover { border-color: rgba(var(--accent-rgb),.45); transform: translateY(-2px); }
.list-card-thumb {
    width: 150px;
    height: 112px;
    border-radius: 10px;
    overflow: hidden;
    background: rgba(var(--ink-rgb),.05);
    display: grid;
    place-items: center;
}
.list-card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.list-card-thumb-empty { font-size: 11px; font-weight: 800; letter-spacing: .04em; color: var(--text-muted); }
.list-card-body { min-width: 0; }
.list-card-actions { flex-direction: column; min-width: 150px; }


@media (max-width: 1920px) {
    .list-card { grid-template-columns: 150px minmax(0, 1fr); }
    .list-card-actions {
        grid-column: 1 / -1;
        flex-direction: row;
        flex-wrap: wrap;
        min-width: 0;
        justify-content: space-between;
    }
}

@media (max-width: 640px) {
    .list-card { grid-template-columns: 1fr; }
    .list-card-thumb { width: 100%; height: 140px; }
}

/* ===== GENERIC TWO-PANEL SPLIT ===== */
.split-panel-layout {
    display: grid;
    grid-template-columns: minmax(300px, 380px) minmax(0, 1fr);
    column-gap: 16px;
    align-items: start;
}
@media (max-width: 1600px) {
    .split-panel-layout { grid-template-columns: 1fr; }
}

/* ===== MYSCAN FIRST-RUN EXPLAINER (pages/myscan.php, no floorplans yet) =====
   Four .boxed cards in a 2x2 grid. auto-fit drops to one column on its own when the content
   area (not the viewport - the nav takes a slice of it) can't fit two 520px tracks. The inner
   layouts react to each card's own width via @container for the same reason. */
.ms-intro-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 520px), 1fr)); gap: 16px; align-items: stretch; margin-bottom: 20px; }
.ms-intro-grid > .boxed { margin-bottom: 0; }
.ms-intro-grid .card-body { container-type: inline-size; }

.ms-intro-steps { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 8px; }
.ms-intro-step { padding: 12px 6px; border-radius: 10px; border: 1px solid rgba(var(--ink-rgb),.07); background: rgba(var(--ink-rgb),.03); text-align: center; }
.ms-intro-step .mdi, .ms-intro-feature .mdi { display: block; color: var(--accent); font-size: 26px; line-height: 1; margin-bottom: 8px; }
.ms-intro-step strong { display: block; color: var(--text); font-size: 11.5px; line-height: 1.35; }

.ms-intro-visual-row { display: grid; grid-template-columns: minmax(0, 1fr) 140px minmax(0, 1fr); gap: 12px; align-items: center; margin-top: 16px; }
.ms-intro-visual-card { min-width: 0; padding: 10px; border-radius: 12px; border: 1px solid var(--border); background: rgba(var(--ink-rgb),.03); }
.ms-intro-visual-head { display: flex; align-items: center; gap: 8px; padding: 0 2px 8px; color: var(--text); font-size: 11px; font-weight: 800; }
.ms-intro-visual-head .mdi { color: var(--accent); font-size: 16px; line-height: 1; }
.ms-intro-visual-card img { display: block; width: 100%; height: auto; border-radius: 8px; border: 1px solid var(--border); }
.ms-intro-device { display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; }
.ms-intro-device img { width: 100%; max-width: 140px; height: auto; filter: drop-shadow(0 12px 18px rgba(0,0,0,.45)); }
.ms-intro-device-caption { padding: 5px 10px; border-radius: var(--radius-pill); border: 1px solid rgba(var(--accent-rgb),.3); background: rgba(var(--accent-rgb),.08); color: var(--text); font-size: 11px; font-weight: 800; line-height: 1.25; }
.ms-intro-device-caption span { color: var(--accent); }

.ms-intro-features { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 8px; margin-bottom: 16px; }
.ms-intro-feature { padding: 12px 8px; border-radius: 10px; border: 1px solid rgba(var(--ink-rgb),.07); background: rgba(var(--ink-rgb),.03); text-align: center; }
.ms-intro-feature strong { display: block; color: var(--text); font-size: 12px; line-height: 1.25; margin-bottom: 4px; }
.ms-intro-feature span { display: block; color: var(--text-muted); font-size: 11px; line-height: 1.35; }
.ms-intro-overview { display: block; width: 100%; height: auto; border-radius: 10px; border: 1px solid var(--border); }

/* Dark/light screenshot pairs - both are in the markup, the theme class picks one. The selectors
   carry .ms-intro-grid because .ms-intro-visual-card img / .ms-intro-overview set display too. */
.ms-intro-grid img.ms-img-light { display: none; }
body.light-mode .ms-intro-grid img.ms-img-dark { display: none; }
body.light-mode .ms-intro-grid img.ms-img-light { display: block; }

.ms-intro-copy { margin: 0 0 12px; color: var(--text-muted); font-size: 14px; line-height: 1.65; white-space: normal; }
.ms-intro-copy strong { color: var(--text); }
.ms-intro-list { display: grid; gap: 9px; margin: 0 0 14px; padding: 0; list-style: none; }
.ms-intro-list li { position: relative; padding-left: 24px; color: var(--text-muted); font-size: 14px; line-height: 1.5; }
.ms-intro-list li::before { content: "\2713"; position: absolute; left: 0; top: 0; color: var(--accent); font-weight: 900; }
.ms-intro-list strong { color: var(--text); }
.ms-intro-actions { margin-top: 16px; }
.ms-intro-actions .menu_button { display: flex; width: 100%; box-sizing: border-box; justify-content: center; text-align: center; }

@container (max-width: 640px) {
    .ms-intro-steps, .ms-intro-features { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@container (max-width: 480px) {
    .ms-intro-steps, .ms-intro-features { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ms-intro-visual-row { grid-template-columns: 1fr; }
}

/* Status pills (myscan floorplan lifecycle) */
.pill-new         { background: rgba(245,158,11,.14); color: #f59e0b; border: 1px solid rgba(245,158,11,.3);  }
.pill-configured  { background: rgba(var(--accent-rgb),.08); color: #67d5f7; border: 1px solid rgba(var(--accent-rgb),.2); }
.pill-complete    { background: rgba(167,124,255,.16); color: #a77cff; border: 1px solid rgba(167,124,255,.32); }
body.light-mode .pill-new        { color: #b45309; }
body.light-mode .pill-configured { color: #0e7490; }
body.light-mode .pill-complete   { color: #7e22ce; }

/* ===== WIZARD TABS — numbered step nav + fixed-height panel area + summary footer, for any
   multi-section form that shouldn't resize as the user steps through it ===== */
.wizard-tabs {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 8px;
    margin-bottom: 16px;
}
.wizard-tab {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    color: var(--text-muted);
    cursor: pointer;
    text-align: left;
    font-family: inherit;
    transition: border-color .15s, background .15s, color .15s;
}
.wizard-tab:hover { border-color: rgba(var(--accent-rgb),.35); }
.wizard-tab.is-active { border-color: var(--accent); background: rgba(var(--accent-rgb),.1); color: var(--text-on-accent); }
.wizard-tab.is-active .wizard-tab-text small { color: rgba(255,255,255,.75); }
.wizard-tab-num {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    font-size: 12px;
    font-weight: 800;
    background: rgba(var(--ink-rgb),.08);
    color: var(--text-muted);
}
.wizard-tab.is-active .wizard-tab-num { background: var(--accent); color: var(--text-on-accent); }
.wizard-tab-text { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.wizard-tab-text b { font-size: 12.5px; font-weight: 800; color: inherit; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wizard-tab-text small { font-size: 10.5px; font-weight: 600; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.wizard-panels { min-height: 340px; overflow-y: auto; margin-top: 0; margin-bottom: 0; }
.wizard-panel { display: none; }
.wizard-panel.is-active { display: block; }

.wizard-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 16px;
    margin-bottom: 0;
}
.wizard-footer-actions { display: flex; gap: 8px; flex: 0 0 auto; }
@media (max-width: 640px) {
    .wizard-tabs { grid-template-columns: repeat(2, 1fr); }
    .wizard-footer { flex-direction: column; align-items: stretch; }
    .wizard-footer-actions { justify-content: flex-end; }
}

.wizard-summary { display: flex; flex-wrap: wrap; gap: 6px; }

.wizard-panel-heading { display: block; margin-bottom: 8px; font-weight: 700; color: var(--heading); }

.tech-swatch { display: inline-block; width: 8px; height: 8px; border-radius: 999px; margin-right: 6px; flex: 0 0 auto; }

/* MySCAN config modal - Sync Devices tab (pages/myscan.php). Chips are .state-button/.group-filter-item,
   so checked styling comes from dots/css/style.css; these only lay them out. */
.sync-mode-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 8px; margin-bottom: 16px; }
.sync-mode-row .mdi { color: var(--accent); margin-right: 4px; }
#scan-config-form .group-filter-name small { display: block; margin-top: 2px; font-size: 11px; font-weight: 600; color: var(--text-muted); }
.sync-model-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 8px; }
.sync-model-name { display: flex; align-items: center; gap: 10px; }
/* cards are sized to fit the longest model name, so never break one mid-word (.group-filter-name wraps anywhere) */
.sync-model-name > span { white-space: nowrap; overflow-wrap: normal; }
.sync-model-name img { width: 40px; height: 40px; object-fit: contain; flex: 0 0 auto; }
.sync-unit-list { display: grid; gap: 6px; max-height: 260px; overflow-y: auto; padding-right: 2px; }
.sync-unit-row[hidden] { display: none; }
/* MySCAN tech no Sync Devices target can scan (refreshTechAvailability). Scoped to techs: the
   covered-by-model unit boxes above are disabled too but are "on", so they keep full opacity. */
.scan-config-tech:disabled + label { opacity: .5; cursor: not-allowed; }

/* ===== EL-TOAST — floating notification anchored inside a container element, not the page as a
   whole. Generic/reusable - see jquery/toast.js's showElementToast(). ===== */
.el-toast {
    position: absolute;
    left: 50%;
    z-index: 20;
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: calc(100% - 32px);
    padding: 8px 12px;
    border: 1px solid;
    border-radius: 10px;
    background: var(--surface-hover);
    font-size: 12.5px;
    font-weight: 700;
    box-shadow: 0 10px 28px rgba(0,0,0,.32), inset 0 1px 0 rgba(var(--ink-rgb),.06);
    opacity: 0;
    pointer-events: none;
    transition: opacity .2s ease, transform .2s ease;
}
.el-toast-top { top: 10px; transform: translate(-50%, -8px); }
.el-toast-bottom { bottom: 10px; transform: translate(-50%, 8px); }
.el-toast.is-visible { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.el-toast-message { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.el-toast-close { flex: 0 0 auto; background: none; border: none; color: inherit; font-size: 16px; line-height: 1; cursor: pointer; opacity: .7; padding: 0; }
.el-toast-close:hover { opacity: 1; }

/* Clickable variant of .pill - a single-select filter chip row (e.g. myscan status filters) */
.pill-filter {
    cursor: pointer;
    font-family: inherit;
    appearance: none;
    -webkit-appearance: none;
    opacity: .5;
    transition: opacity .15s ease;
}
.pill-filter:hover { opacity: .8; }
.pill-filter.is-active { opacity: 1; }

/* View toggle pill */
.view-toggle {
    position: relative;
    display: inline-flex;
    background: rgba(var(--ink-rgb),.05);
    border: 1px solid rgba(var(--ink-rgb),.09);
    border-radius: 10px;
    padding: 4px;
}
.vt-slider {
    position: absolute;
    top: 4px; bottom: 4px;
    border-radius: 7px;
    background: rgba(var(--ink-rgb),.12);
    transition: left .25s cubic-bezier(.22,.8,.32,1), width .25s cubic-bezier(.22,.8,.32,1);
    pointer-events: none;
}
.view-toggle button {
    position: relative;
    z-index: 1;
    padding: 6px 12px;
    border-radius: 7px;
    font-size: 13px;
    background: transparent;
    color: var(--text-muted);
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 5px;
    white-space: nowrap;
    transition: color .15s;
    font-family: var(--font);
}
.view-toggle button.is-active { color: var(--text); }
.view-toggle button svg { width: 14px; height: 14px; flex: 0 0 auto; }

/* Right-side drawer — non-blocking (no backdrop) */
.drawer {
    position: fixed;
    top: 0; right: 0; bottom: 0;
    width: 960px;
    max-width: 100vw;
    background: linear-gradient(160deg, var(--surface) 0%, var(--bg) 100%);
    border-left: 1px solid rgba(var(--ink-rgb),.09);
    box-shadow: -12px 0 40px rgba(0,0,0,.55);
    z-index: 200;
    display: flex;
    flex-direction: column;
    transform: translateX(100%);
    transition: transform .22s cubic-bezier(.22,.8,.32,1);
    overflow: hidden;
}
.drawer.is-open { transform: translateX(0); }
.drawer-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px;
    border-bottom: 1px solid rgba(var(--ink-rgb),.07);
    flex: 0 0 auto;
}
.drawer-body {
    flex: 1;
    overflow-y: auto;
    padding: 18px 20px 28px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    scrollbar-width: thin;
    scrollbar-color: var(--gray-3) transparent;
    container-type: inline-size;
}
/* analyticsSectionsHtml() (jquery/device-analytics.js) mounts plain .boxed cards straight into
   the drawer body - the sitewide .boxed margin-bottom would double up with this flex gap, so
   drop it here and let the gap alone set the spacing, same as it always did for .drawer-section.
   flex-shrink:0: .boxed sets overflow:hidden (to clip content to its rounded corners), and a flex
   item's automatic minimum size becomes 0 - instead of its content's height - the moment it has
   any overflow value other than visible. Inside this flex column that let .drawer-body's default
   flex-shrink squash each card shorter than its content and clip the rest, instead of the cards
   staying full height and .drawer-body's own overflow-y:auto scrolling like it's meant to. */
.drawer-body > .boxed { margin-bottom: 0; flex-shrink: 0; }

/* analyticsSectionsHtml() sections in the drawer are wrapped in .drawer-analytics-grid
   (openDrawer(), devices.php) so they can pair up 2-per-row now the drawer is double width -
   container query on .drawer-body above so it still collapses to one column on the mobile
   breakpoint where the drawer goes full-width-of-a-narrow-viewport (see the max-width:600px rule
   below). Same stretch-height fix #device-summary-mount uses for the same reason (a 3-stat card
   next to a taller chart card in the same row). */
.drawer-analytics-grid { display: flex; flex-direction: column; gap: 12px; }
.drawer-analytics-grid > .boxed { margin-bottom: 0; flex-shrink: 0; }
@container (min-width: 640px) {
    .drawer-analytics-grid { display: grid; grid-template-columns: repeat(2, 1fr); }
    .drawer-analytics-grid > .boxed { display: flex; flex-direction: column; }
    .drawer-analytics-grid > .boxed > .card-body { flex: 1; display: flex; flex-direction: column; justify-content: center; }
    /* the "No surveys recorded for this device." card is the sole section when a device has no
       survey history - let it span both columns instead of sitting half-width with an empty cell
       beside it. */
    .drawer-analytics-grid > .boxed:only-child { grid-column: 1 / -1; }
    /* An odd card count (e.g. Key Insights as the 5th section) otherwise leaves the last card
       alone in column 1 with an empty cell beside it - same fix as .drawer-actions below. */
    .drawer-analytics-grid > .boxed:last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* Device Management cards (render_device_management_section(), includes/functions.php) - Software
   & Firmware status, Reset, and Delete, each its own row rather than buttons in the top action
   bar, so a destructive action isn't one click away from View Surveys. --icon-accent set once per
   row (good/amber/record-red) - .mdi-icon-backed's own --icon-tone (line ~4454) and
   .mgmt-row-warn below both inherit it. */
.mgmt-row { display: flex; align-items: center; flex-wrap: wrap; gap: 16px; padding: 16px; border: 1px solid rgba(var(--ink-rgb),.08); border-radius: 12px; background: rgba(var(--ink-rgb),.03); }
.mgmt-row + .mgmt-row { margin-top: 12px; }
.mgmt-row-copy { flex: 1 1 240px; min-width: 0; }
.mgmt-row-copy h4 { margin: 0; color: var(--text); font-size: .95rem; }
.mgmt-row-copy p { margin: 4px 0 0; color: var(--text-muted); font-size: .82rem; }
.mgmt-row-warn { margin-top: 8px; color: var(--icon-accent); font-size: .78rem; font-weight: 700; }
/* a long nowrap label would set the row's minimum width and push the page sideways on phones */
.mgmt-row > .menu_button { flex: 0 0 auto; min-width: 0; max-width: 100%; white-space: normal; height: auto; text-align: center; }
/* an action <form> (Manage Access, Survey Schedules) is a flex item too - let its .input-group shrink */
.mgmt-row form { min-width: 0; }
/* Map Options' unlock row (index.php) - tightened to the height of the Options box beside it, so the
   description stays on one line and the hover card carries the detail. Sized to its content, capped at
   the cell's width so it wraps on small screens. */
#map_section .list-collapse .mgmt-row { padding: 8px 12px; width: fit-content; max-width: 100%; box-sizing: border-box; }
#map_section .list-collapse .mgmt-row-copy p { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* one line would set the row's minimum width, so phones let it wrap (same 580px as .stack-form) */
@media (max-width: 580px) { #map_section .list-collapse .mgmt-row-copy p { white-space: normal; } }

/* Bookmark + comments editor (render_survey_note_editor(), includes/functions.php) - one card modal for
   the survey list's bookmark glyph and the results page's Comments action. Built on existing pieces: each
   option is a .mgmt-row, the headings are .detail-copy/.detail-label/.detail-description, the comment
   box is .field-control. New here is only the picking: a native radio inside each <label> (keyboard and
   screen readers for free) and its checked state. --bm-tone resolves the row's --icon-accent(-light) the
   way .mdi-icon-backed does, falling back to the brand accent so Standard/Archived still show a selection. */
.bm-editor { display: flex; flex-direction: column; gap: 10px; }
.bm-editor > .detail-copy:not(:first-child) { margin-top: 8px; }
.bm-opt { --bm-tone: var(--icon-accent, var(--accent)); cursor: pointer; transition: border-color .15s ease, background .15s ease; }
body.light-mode .bm-opt { --bm-tone: var(--icon-accent-light, var(--icon-accent, var(--accent))); }
.bm-radio { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
.bm-opt:hover { border-color: color-mix(in srgb, var(--bm-tone) 55%, transparent); }
.bm-opt:has(.bm-radio:checked) { border-color: var(--bm-tone); background: color-mix(in srgb, var(--bm-tone) 10%, transparent); }
.bm-opt:has(.bm-radio:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.bm-opt .mdi-icon-backed { flex: 0 0 auto; cursor: inherit; }
.bm-opt:hover .mdi-icon-backed { transform: none; }
.bm-current-tag {
    flex: 0 0 auto; padding: 2px 8px; border-radius: 999px; font-size: .6rem; font-weight: 800; letter-spacing: .04em;
    text-transform: uppercase; background: color-mix(in srgb, var(--bm-tone) 18%, transparent); color: var(--bm-tone);
}
/* Tighter than Device Management's rows, and never wrapping the Current tag under the copy (.mgmt-row
   wraps, its copy has a 240px basis). */
.bm-options { display: flex; flex-direction: column; gap: 6px; }
.bm-options > .mgmt-row { margin: 0; padding: 10px 14px; gap: 12px; flex-wrap: nowrap; }
.bm-options .mgmt-row-copy { flex: 1 1 auto; }
.bm-options .mgmt-row-copy h4 { font-size: .88rem; }
.bm-options .mgmt-row-copy p { margin-top: 2px; font-size: .76rem; line-height: 1.35; }
.bm-comments { width: 100%; }
.bm-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
.bm-actions .menu_button { width: auto; min-width: 110px; margin: 0; }
/* In a card-as-modal (.is-card-modal strips the glass panel), .boxed's near-transparent fill lets the
   page show through - the feedback card fixes it with hard white; this one follows the theme. */
.bm-card.boxed { background: var(--surface); box-shadow: 0 24px 60px rgba(0,0,0,.45); }
/* clear the modal's close button, same as .feedback-card */
.bm-card > .card-head { padding-right: 56px; }
/* .is-card-modal's close button is white-on-tint for the feedback card's blue header; this card's
   header is plain surface, so it follows the text colour instead (else it vanishes in light mode). */
.modal_content.is-card-modal:has(.bm-card) > .close { background: rgba(var(--ink-rgb),.08); color: var(--text); }
.modal_content.is-card-modal:has(.bm-card) > .close:hover { background: rgba(var(--ink-rgb),.16); color: var(--text); }
/* ~620px tall: .modal_content centres on translate(-50%,-50%), so in a shorter window its top would sit
   above the viewport where the overlay can't scroll to it - cap it and scroll inside instead. */
.modal_content.is-card-modal:has(.bm-card) { max-height: calc(100% - 24px); overflow-y: auto; border-radius: 14px; scrollbar-width: thin; }
/* The survey list's bookmark glyph is a button now - strip the UA chrome so it looks as it did. */
.bm-trigger { padding: 0; border: 0; background: none; font: inherit; color: inherit; cursor: pointer; border-radius: 50%; }
.bm-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (max-width: 600px){
    .bm-options > .mgmt-row { padding: 8px 10px; gap: 10px; }
}
.drawer-close {
    width: 30px; height: 30px;
    border-radius: 50%;
    border: 1px solid rgba(var(--ink-rgb),.12);
    background: rgba(var(--ink-rgb),.06);
    color: var(--text-muted);
    cursor: pointer;
    display: grid;
    place-items: center;
    font-size: 20px;
    line-height: 1;
    transition: background .15s, color .15s;
}
.drawer-close:hover { background: rgba(var(--ink-rgb),.1); color: var(--text); }
.drawer-section {
    border: 1px solid rgba(var(--ink-rgb),.07);
    border-radius: 12px;
    background: rgba(var(--ink-rgb),.03);
    padding: 14px;
    /* same recipe as the sitewide .boxed rule (render_boxed_card()) below in the drawer -
       without it these top sections read as flat next to the lifted analytics/software cards. */
    box-shadow: inset 0 1px 0 rgba(var(--ink-rgb),.06), 0 6px 22px rgba(0,0,0,.28);
}
.drawer-section.boxed {
    background:
        radial-gradient(circle at 12% 18%, rgba(var(--brand-rgb),.10), transparent 34%),
        radial-gradient(ellipse at 88% 82%, rgba(var(--brand-deep-rgb),.07), transparent 40%),
        radial-gradient(ellipse at 12% 0%, rgba(var(--ink-rgb),.05) 0%, transparent 55%),
        rgba(var(--ink-rgb),.028);
    border: 1px solid rgba(var(--ink-rgb),.09);
    border-radius: 14px;
    padding: 16px;
}
.drawer-section-title {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 13px;
    font-weight: 800;
    color: var(--heading);
    margin: 0 0 8px;
    font-family: var(--font);
}
.drawer-section-title small { font-size: 10.5px; font-weight: 700; text-transform: uppercase; color: var(--text-muted); }
.drawer-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
/* .menu_button's sitewide min-width:200px (line ~119) otherwise forces this 2-column grid past
   its .drawer-top-actions flex-basis - two 200px buttons don't fit in 260px, so the column
   overflows and eats space from .drawer-top-info next to it instead of respecting its cap. */
.drawer-actions > .menu_button { min-width: 0; }
/* A lone odd-one-out button (5 or 7 total) would otherwise sit alone in column 1 with an empty
   cell beside it - if it's both the last child AND at an odd position, it's alone in its row, so
   span both columns instead. */
.drawer-actions > *:last-child:nth-child(odd) { grid-column: 1 / -1; }

/* Top-of-drawer identity+recording+actions split (openDrawer(), devices.php) - device details,
   recording controls (sentryCtrlsDrawer(), SENTRY only, may be absent) and the View/Edit/History
   action buttons. Mobile default: plain column - flex-direction:column already stretches each
   item to full width on its own row, no flex-wrap needed (flex-wrap on a COLUMN-direction
   container wraps into new side-by-side columns, not new rows below - that's what made
   drawer-top-rec balloon to fill the leftover cross-axis height). Container query (not a
   viewport media query, because .drawer-body already sets container-type:inline-size for
   .drawer-analytics-grid below) switches to a row once there's room, wrapping info+rec onto one
   line and actions - forced to flex-basis:100% - onto its own full-width line underneath; a
   wrapped flex row rather than grid-template-areas because drawer-top-rec is entirely absent for
   non-SENTRY/demo devices, and flex-wrap just lets drawer-top-info fill the row alone instead of
   leaving a named grid cell empty. */
.drawer-top-split { display: flex; flex-direction: column; gap: 14px; }
.drawer-top-info { min-width: 0; }
/* Fixed-height device photo + name/type/meta column - flex-wrap here too, so a long device name
   or an Access/Location/Owner/Tags meta line wraps under the photo on the narrowest drawers
   instead of forcing the row wider than its column and bleeding into the recording button beside
   it (the overlap this whole block exists to fix). */
.drawer-info-media { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.drawer-info-img { height: 110px; width: auto; max-width: 100%; opacity: .9; flex: 0 0 auto; }
.drawer-info-text { display: flex; flex-direction: column; gap: 7px; min-width: 0; flex: 1 1 160px; }
.drawer-info-name { font-size: 21px; }
/* align-items:flex-start - without it a flex column's default stretch would grow each button to
   the full column width below, notably wider than the button-dark actions beside it. */
.drawer-top-rec { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
/* same override as .drawer-actions > .menu_button below - the sitewide min-width:200px would
   otherwise blow out this column's flex-basis */
.drawer-top-rec > .menu_button { min-width: 0; }
@container (min-width: 460px) {
    .drawer-top-split { flex-direction: row; flex-wrap: wrap; }
    .drawer-info-img { height: 150px; }
    .drawer-top-info { flex: 1 1 200px; }
    .drawer-top-rec { flex: 0 1 200px; }
    /* align-self:flex-start - without it, the row's default cross-axis stretch grows this to
       match .drawer-top-info's height (tall, thanks to the device photo). .drawer-top-rec's own
       button stays compact because it's a column flexbox with align-items:flex-start already, but
       .drawer-top-actions is a CSS grid (.drawer-actions), and a grid's own default
       align-content:normal behaves as stretch - it was expanding the (auto-sized) row tracks to
       fill that extra height, and each .menu_button's height:auto (css/style_survey_new.css's
       shared cs-chip rule, ~line 2407) stretched right along with its track instead of staying at
       its natural ~38px. */
    .drawer-top-actions { flex: 1 1 100%; align-self: flex-start; }
}
/* XL drawers (wide desktop) - enough room for the original single-row layout: info | rec |
   actions side by side, rather than actions dropping to its own line below. 820px is comfortably
   past rec's ~200px + actions' ~260px + a usable info column, so this only kicks in once all
   three genuinely fit on one line - flex-wrap on .drawer-top-split (set above) still wraps
   actions below if a drawer ever falls short of that. */
@container (min-width: 820px) {
    .drawer-top-actions { flex: 0 0 260px; }
}
.drawer-pill-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }

/* Boxed icon+label+value tile - was .sw-version-box (render_software_status_row(),
   includes/functions.php, back when it was a full standalone card), generalised so
   deviceStatusHtml() (pages/devices.php) can reuse the same look for the drawer's
   recording/online/sessions/etc row. --box-accent is optional: unset, the border/icon fall back
   to the sitewide --accent tint and the value stays plain --text; set it (inline style, or a
   --warning/--good ancestor) and add .info-box--accent to also tint the icon + value. */
.info-box { min-height: 74px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 10px 14px; border: 1px solid color-mix(in srgb, var(--box-accent, var(--accent)) 25%, transparent); border-radius: 12px; background: rgba(var(--ink-rgb),.03); }
.info-box .mdi { display: block; margin-bottom: 4px; font-size: 22px; color: var(--text-muted); }
.info-box span { color: var(--text-muted); font-size: .66rem; font-weight: 850; text-transform: uppercase; }
.info-box strong { display: block; margin-top: 6px; color: var(--text); font-size: 1.25rem; }
.info-box--accent .mdi { color: var(--box-accent, var(--accent)); }
.info-box--accent span { color: var(--box-accent, var(--accent)); }
.info-box--accent strong { color: var(--box-accent, var(--accent)); }
/* One unified gap - between boxes and between the row and .drawer-section's own 14px padding
   (line ~3828) - so the row reads as evenly spaced on every side, not just left-to-right. */
.info-box-row { display: flex; flex-wrap: wrap; gap: 14px; }
.info-box-row .info-box { flex: 1 1 100px; }
/* Label/value sizing inverted vs. the base .info-box (label reads as the headline here, the
   value underneath it as the smaller description) - scoped to this row only so the software
   update card's Installed/Recommended Version boxes above keep the base sizing. */
.info-box-row .info-box span { font-size: 1.25rem; }
.info-box-row .info-box strong { font-size: .8rem; }
/* Narrowest drawers (single-column mobile) - shrink the tile basis/padding so two still fit per
   row instead of the 100px basis leaving one alone with a wide gap next to it. */
@container (max-width: 380px) {
    .info-box-row { gap: 8px; }
    .info-box-row .info-box { flex-basis: 84px; min-height: 64px; padding: 8px 10px; }
}

/* analyticsSectionsHtml() (jquery/device-analytics.js) emits plain .boxed cards (same markup as
   render_boxed_card()), shared between the devices.php drawer (.drawer-body below) and
   edit_device.php's #device-summary-mount. That id only exists on edit_device.php, so this can't
   affect the drawer. Cards stay the normal full-width .boxed stack below 1600px, pair up 2-per-row
   from 1600px, and only go 3-per-row past 2560px (true 4K) so a 4K monitor doesn't stretch a single
   card edge to edge. margin-bottom moves from the (now zeroed) card onto the container so the gap
   before the next section (Software & Firmware) survives. */
@media (min-width: 1600px) {
    #device-summary-mount {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        gap: 20px;
        margin-bottom: 20px;
    }
    /* Grid's default align-items:stretch grows every card in a row to match its tallest sibling
       (e.g. Network Scan by Technology or Survey Date Range next to the taller Survey Activity
       card) - same problem as .summary-graph-grid/.perf-metric-grid/.signal-log-grid below, and
       the same fix: let .card-body absorb the extra height, then centre its (fixed-height)
       content in it instead of leaving it pinned to the top with empty space underneath. */
    #device-summary-mount > .boxed { margin-bottom: 0; display: flex; flex-direction: column; }
    #device-summary-mount > .boxed > .card-body { flex: 1; display: flex; flex-direction: column; justify-content: center; }
    /* Same fix as .drawer-analytics-grid > .boxed:only-child above - a device with no survey
       history renders just the "No surveys recorded for this device." card (analyticsSectionsHtml()
       returns early), which would otherwise sit half-width with an empty cell beside it. */
    #device-summary-mount > .boxed:only-child { grid-column: 1 / -1; }
    /* account.php: a settings panel whose detail-grid cards hold one row each (Admin) would leave
       each card half empty, so the cards themselves pair up instead. Any card with a second row
       opts the whole panel out. */
    div.tabcontent.active:has(> .detail-grid + .detail-grid):not(:has(> .detail-grid > .card-body > .detail-row:nth-child(2))) {
        display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); column-gap: 20px;
    }
}
@media (min-width: 2560px) {
    #device-summary-mount { grid-template-columns: repeat(3, 1fr); }
}

/* edit_device.php only (id unique to that page): Device Naming + SENTRY Configuration sit side
   by side by default - the config card gets more room (9 fields) than naming (4), same
   asymmetric split as device_settings_responsive_layout.html's .settings-layout - collapsing to
   one column once the page itself gets too narrow to hold both comfortably. Naming-only devices
   never get this wrapper (see edit_device.php), so there's nothing to fix up for a lone card. */
/* Same repeat(3, 1fr) tracks + gap as #device-summary-mount's 3-per-row layout below (2560px+,
   see that block), with Device Naming taking column 1 and SENTRY Configuration spanning columns
   2-3 - not just a 1fr/2fr ratio, the literal same grid, so Device Naming's right edge lines up
   with Survey Summary's right edge exactly instead of approximating 1/3 with different gap math. */
#edit-device-top-cards {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
    margin-bottom: 20px;
}
#edit-device-top-cards > .device-settings-card:nth-child(2) { grid-column: span 2; }
#edit-device-top-cards > .boxed { margin-bottom: 0; }
@media (max-width: 1240px) {
    #edit-device-top-cards { grid-template-columns: 1fr; }
    #edit-device-top-cards > .device-settings-card:nth-child(2) { grid-column: auto; }
}

/* Device Naming / SENTRY Configuration field grids (pages/edit_device.php) - container queries
   so each card's OWN field grid responds to the CARD's rendered width, not the viewport. That
   matters here specifically because the same two cards render at very different widths depending
   on device type: paired side by side (SENTRY, narrower each) or Device Naming alone at full
   page width (non-SENTRY) - a viewport media query can't tell those two layouts apart, a
   container query can. See device_settings_responsive_layout.html for the reference this
   replicates - .naming-grid/.config-grid here are that file's classes verbatim. */
.device-settings-card { container-type: inline-size; }

/* Grid, 4 columns down to 1 - 4x1 on wide cards, 2x2 mid-width, 1x4 narrow (container-width
   driven, same reasoning as .config-grid below: each card responds to its own rendered width). */
.naming-grid { display: grid; grid-template-columns: minmax(0,1fr); gap: 14px; }
@container (min-width: 420px) { .naming-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@container (min-width: 900px) { .naming-grid { grid-template-columns: repeat(4, minmax(0,1fr)); } }

.config-grid { display: grid; grid-template-columns: minmax(0,1fr); gap: 10px; margin-bottom: 16px; }
@container (min-width: 520px) { .config-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@container (min-width: 760px) { .config-grid { grid-template-columns: repeat(3, minmax(0,1fr)); } }
@container (min-width: 1180px) { .config-grid.primary-settings { grid-template-columns: repeat(6, minmax(0,1fr)); } }

/* .field-control is inline-flex (shrink-to-fit its content) so left alone it sizes to the
   selected option's text - e.g. "9600 Baud" ends up visibly narrower than "New Survey Control"
   next to it even though both sit in equal-width grid columns. Force it to fill the column. */
.naming-grid .field-control, .config-grid .field-control { width: 100%; }

/* Very old browsers without container query support - falls back to the old auto-fill behaviour
   rather than being stuck at one column forever. */
@supports not (container-type: inline-size) {
    .naming-grid, .config-grid { grid-template-columns: repeat(auto-fill, minmax(200px,1fr)); }
}

/* Donut charts (CSS-only conic-gradient, no library) */
.donut-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); gap: 10px; }
.donut-card {
    border: 1px solid rgba(var(--ink-rgb),.07); border-radius: 12px; background: rgba(var(--ink-rgb),.03);
    display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 12px 8px;
}
.donut-chart {
    position: relative; border-radius: 50%;
    box-shadow: inset 0 1px 0 rgba(var(--ink-rgb),.08), 0 8px 18px rgba(0,0,0,.25);
}
.donut-slice { position: absolute; inset: 0; pointer-events: auto; cursor: pointer; transition: filter .12s ease; }
.donut-slice:hover { filter: brightness(1.18); }
.donut-slice-wrap.helptip, .donut-slice-wrap.helptipleft, .donut-slice-wrap.helptipdown { position: absolute; inset: 0; display: block; pointer-events: none; }
.donut-slice-wrap .helptiptext, .donut-slice-wrap .helptiplefttext, .donut-slice-wrap .helptipdowntext { display: none; }
.floating-chart-tip {
    position: fixed; z-index: 100000; display: none; min-width: 220px;
    /* the antenna tips carry ~21 feature blocks - without a cap they stretch across the viewport */
    max-width: min(420px, calc(100vw - 16px));
    background: radial-gradient(circle at 20% 15%, rgba(var(--brand-rgb),.14), transparent 60%), var(--surface);
    color: var(--text); border: 1px solid rgba(var(--accent-soft-rgb),.28); border-radius: 12px;
    padding: 10px 12px; font-size: 13px; line-height: 1.4;
    box-shadow: 0 10px 28px rgba(0,0,0,.35), inset 0 1px 0 rgba(var(--ink-rgb),.06);
    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    pointer-events: none;
    /* a card taller than the screen (a cell's signal hover card on a phone) scrolls inside itself
       rather than losing its bottom off-screen; summary-charts.js adds .is-scrollable to a tapped
       one that overflows, so a finger can scroll it */
    max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px);
    overflow-y: auto; overscroll-behavior: contain;
}
.floating-chart-tip.is-scrollable { pointer-events: auto; }
/* touch only: the tapped button repeated at the card's foot (addTriggerCopy() in summary-charts.js),
   so the tip has to take the tap rather than pass it through to what's underneath */
.floating-chart-tip:has(.hover-card-trigger-copy) { pointer-events: auto; }
.hover-card .hover-card-trigger-copy { width: 100%; margin: 16px 0 0; }
/* map control tooltips (index.php's .svg-icon-button.has-tip) carry a render_hover_card() card
   instead of chart-tip-row text - .hover-card supplies its own 20px padding, so drop the wrapper's
   to avoid a doubled inset, same as .helptipdown .helptipdowntext--card does for the CSS-hover cards. */
.floating-chart-tip:has(.hover-card) { padding: 0; min-width: 300px; max-width: min(380px, calc(100vw - 16px)); }
/* multi-line chart tooltip body (chart_tip_html() / chartTipHtml()): bold title, then one
   label/value row per stat. Used inside .floating-chart-tip and the .helptip*text originals. */
.chart-tip-title { display: block; font-weight: 700; margin-bottom: 5px; }
.chart-tip-row { display: flex; gap: 18px; justify-content: space-between; align-items: baseline; }
.chart-tip-label { color: var(--text-secondary); }
.chart-tip-value { font-weight: 600; white-space: nowrap; }

.donut-hole {
    position: absolute; border-radius: 50%; z-index: 1;
    background: linear-gradient(180deg, #07131d, #040a10); border: 1px solid rgba(var(--ink-rgb),.06);
}
.donut-center-text {
    position: absolute; inset: 0; z-index: 2; display: flex; flex-direction: column;
    align-items: center; justify-content: center; text-align: center; pointer-events: none;
}
.donut-center-text b { font-size: 15px; font-weight: 900; color: var(--text-on-accent); line-height: 1.1; }
.donut-center-text span { font-size: 8.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); margin-top: 2px; }
.donut-value { position: absolute; inset: 0; display: grid; place-items: center; z-index: 1; font-size: 15px; font-weight: 900; color: var(--text-on-accent); }
.donut-label { font-size: 10px; font-weight: 800; text-transform: uppercase; color: var(--text-muted); text-align: center; }
.donut-total { font-size: 11px; font-weight: 800; color: var(--accent); }
.donut-legend { display: flex; flex-wrap: wrap; gap: 6px 10px; font-size: 11px; color: var(--text-muted); margin-top: 4px; }
.donut-key { display: inline-block; width: 8px; height: 8px; border-radius: var(--radius-pill); margin-right: 3px; }
/* .donut-hole above is a hardcoded dark gradient with no var() at all, so the donut's centre
   stayed near-black on a light card - reuses existing surface tokens instead of new colours.
   .donut-center-text b above uses --text-on-accent, correctly fixed white for its ~30 other
   call sites (text on solid brand-colour buttons/pills that never go light) but was also
   relied on here for contrast against the old fixed-dark .donut-hole - now that the hole
   flips, pin this one spot to --text instead so the value doesn't render white-on-white.
   Scoped to these two selectors only; --text-on-accent itself is untouched. See
   INVESTIGATION_REPORT_2026-08-05_light-mode.md. */
body.light-mode .donut-hole { background: linear-gradient(180deg, var(--surface), var(--surface-hover)); }
body.light-mode .donut-center-text b { color: var(--text); }

/* Bar chart (CSS-only, no library) */
.bar-chart { display: flex; flex-direction: column; gap: 10px; }
.bar-row { display: grid; grid-template-columns: 96px 1fr 44px; align-items: center; gap: 10px; }
.bar-label {
    font-size: .82rem; font-weight: 650; color: var(--text-secondary);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bar-track {
    position: relative; height: 20px; border-radius: var(--radius-pill);
    background: rgba(var(--ink-rgb),.08); overflow: hidden;
}
.bar-fill {
    display: block; height: 100%; border-radius: inherit; cursor: pointer;
    transition: width .4s ease, height .4s ease, filter .12s ease;
}
.bar-fill:hover { filter: brightness(1.15); }
/* tip text is read via JS (trigger.querySelector) for the floating-chart-tip clone, same as
   donut slices - it must never render inline, and no .helptipdown ancestor exists here to
   drive that through the native :hover reveal rule, so hide it explicitly */
.bar-fill .helptipdowntext { display: none; }
/* same deal for anything opted into the floating tip via render_metric_card()'s 'tip' - a
   .metric-card is overflow:hidden, so an inline tip would be clipped by the card anyway */
.has-tip .helptipdowntext { display: none; }
/* .table-wrapper is contain:inline-size + overflow, so a CSS-only .helptip* bubble inside one is
   always clipped by the table. Those wrappers are in summary-charts.js's TRIGGER_SELECTOR instead,
   so the tip is cloned onto <body>; the inline copy must stay hidden (JS still reads its innerHTML). */
.table-wrapper .helptiptext, .table-wrapper .helptiplefttext, .table-wrapper .helptipdowntext { display: none; }
/* disabled form controls swallow mouseover in Chrome/Safari, so the handler would never see the
   hover - let the event through to the wrapper, which is the trigger anyway (e.g. the "Insufficient
   Tokens" tip on a disabled View Results button, pages/survey_list.php) */
.table-wrapper .helptip button:disabled,
.table-wrapper .helptipleft button:disabled,
.table-wrapper .helptipdown button:disabled { pointer-events: none; }
.bar-value { font-size: .82rem; font-weight: 800; color: var(--text); text-align: right; }

/* "Product Features" blocks inside an antenna tooltip - the two-line blue icon_box from the
   siretta.com product page (render_feature_chips(), includes/functions.php) */
.feature-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.feature-chip {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    min-width: 54px; padding: 5px 8px; border-radius: var(--radius);
    background: rgba(var(--accent-rgb),.12); border: 1px solid rgba(var(--accent-rgb),.24);
    font-size: 10px; line-height: 1.25; color: var(--text-muted); text-align: center;
}
.feature-chip b { font-size: 12.5px; font-weight: 800; color: var(--text); }
/* inside a render_hover_card() the chips take the card's own accent (the antenna family colour) */
.hover-card .feature-chips { margin-bottom: 0; }
.hover-card .feature-chip { background: rgba(var(--hc-rgb),.12); border-color: rgba(var(--hc-rgb),.24); }

/* Bar chart - vertical orientation (render_bar_chart($data, $colors, ['orientation' => 'vertical'])) */
.bar-chart-vertical { flex-direction: row; align-items: stretch; gap: 16px; height: 200px; }
.bar-chart-vertical .bar-col { display: flex; flex-direction: column; align-items: center; gap: 8px; flex: 1; min-width: 0; }
.bar-chart-vertical .bar-track {
    flex: 1; width: 100%; max-width: 48px; display: flex; align-items: flex-end;
    border-radius: 6px;
}
.bar-chart-vertical .bar-fill { width: 100%; height: auto; }
.bar-chart-vertical .bar-label { max-width: 100%; }

/* Line chart (inline SVG, no library) */
.line-chart-wrap { display: flex; flex-direction: column; gap: 8px; }
.line-chart { display: block; width: 100%; height: auto; }
.line-chart-grid { stroke: var(--border); stroke-width: 1; }
/* Axis labels (render_chart_yaxis_labels()/render_chart_xaxis_labels() in includes/functions.php)
   are plain HTML positioned to match the SVG's own coordinates, not in-SVG <text>. SVG text set
   in viewBox user-units scales by (rendered width / viewBox width) same as every other
   coordinate in the SVG - confirmed by measuring rendered bounding boxes directly: a "16px"
   in-SVG font in a ~1034px standalone .boxed card painted at a 25.9px visual equivalent (scale
   1034/640 = 1.62x), the same "12px" in a ~310px .summary-chart-card grid cell painted at 6.0px
   (scale 310/640 = 0.48x) - a real 3.3x gap between the two contexts. A cqw-based size (tried
   first) scales font-size by container width AND then gets scaled again by that same built-in
   ratio, compounding instead of correcting. HTML text sidesteps the whole problem: it uses
   ordinary CSS font-size, untouched by the SVG's viewBox transform, so one size is legible at
   any container width. */
.line-chart-plot { display: flex; align-items: stretch; gap: 8px; }
.line-chart-yaxis { display: flex; flex-direction: column; justify-content: space-between; flex: 0 0 auto; text-align: right; font-size: .68rem; line-height: 1; color: var(--text-secondary); padding: 1px 0; }
.line-chart-svg-wrap { flex: 1 1 auto; min-width: 0; }
.line-chart-svg-wrap > svg { display: block; width: 100%; height: auto; }
.line-chart-xaxis { position: relative; height: 14px; margin-top: 4px; font-size: .68rem; line-height: 1; color: var(--text-secondary); }
.line-chart-xaxis span { position: absolute; top: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.line-chart-xaxis span.anchor-start { transform: translateX(0); }
.line-chart-xaxis span.anchor-middle { transform: translateX(-50%); }
.line-chart-xaxis span.anchor-end { transform: translateX(-100%); }
.line-chart-series {
    fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round;
    pointer-events: none; transition: stroke-width .12s ease, filter .12s ease;
}
.line-chart-series-hit { fill: none; stroke: transparent; stroke-width: 10; cursor: pointer; }
.line-chart-series-hit:hover + .line-chart-series { filter: brightness(1.3); stroke-width: 3; }
/* per-point marker (render_line_chart()'s opts['points'], default on) - drawn after every
   series' lines/hit-polylines (see that function's doc comment) so it's never covered by a
   later series' hit-polyline crossing over it; each carries its own data-tip (exact value +
   x_labels entry), independent of .line-chart-series-hit's aggregate tooltip. */
.line-chart-point { cursor: pointer; transition: filter .12s ease; }
.line-chart-point:hover { filter: brightness(1.3); }
/* click-to-toggle (jquery/summary-charts.js) - hidden state only, no change to the resting look */
.line-chart-series.is-hidden, .line-chart-series-hit.is-hidden, .line-chart-point.is-hidden { display: none; }
/* Signal Over Time's "surveys x,y excluded from this metric" line (render_signal_over_time_card()).
   Deliberately muted rather than a loud warning colour - a legacy survey having no RSRQ is expected,
   not an error, and it must still be legible on paper hence no colour-only signal. */
.chart-exclusion-note {
    display: flex; align-items: center; gap: 6px;
    margin: 8px 0 0; font-size: .76rem; line-height: 1.5;
    color: var(--text-muted);
}
/* Cap the legend and let it scroll - a per-cell legend is unbounded (index.php's Signal Log can
   emit 500+ entries on a long survey, a legend many times taller than the chart it labels).
   max-height only engages once it actually overflows, so small legends are unaffected. Scrollbar
   styling matches the .table-wrapper/.bar-chart-scroll convention used elsewhere in this file. */
.line-chart-legend {
    display: flex; flex-wrap: wrap; gap: 10px 16px;
    font-size: .78rem; color: var(--text-secondary);
    max-height: 120px; overflow-y: auto; overscroll-behavior: contain;
    scrollbar-width: thin; scrollbar-color: var(--gray-4) var(--bg);
}
.line-chart-legend-item { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
.line-chart-legend-item.is-hidden { opacity: .4; }
.line-chart-legend-item .legend-dot {
    width: 9px; height: 9px; border-radius: 50%; background: var(--dot);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--dot) 16%, transparent);
}
/* Dashed-series swatch (render_line_chart()'s $opts['dashes']) - a rule rather than a dot, because
   the whole reason a series is dashed is that another series shares its colour. Same 15px box the
   dot occupies with its glow ring, so mixed legends stay on one baseline. */
.line-chart-legend-item .legend-dash { width: 15px; height: 9px; display: inline-flex; align-items: center; }
.line-chart-legend-item .legend-dash svg { width: 100%; height: 2px; overflow: visible; }

/* Scatter chart (render_scatter_chart()) - shares .line-chart-wrap/-grid/-yaxis/-xaxis/-legend*
   above */
.scatter-chart-point { cursor: pointer; transition: filter .12s ease; }
.scatter-chart-point:hover { filter: brightness(1.3); }
.scatter-chart-point.is-hidden { display: none; }

/* Candlestick / range chart (render_candle_chart()) - shares .line-chart-wrap/-grid/-yaxis/-xaxis
   above for the value-axis gridlines/labels, tooltip and .line-chart-wrap chrome. The hover/
   tooltip target is .candle-chart-hit, an invisible rect spanning the category's whole column -
   not the box or whisker, which can be tiny when a network is consistent (sometimes the whole
   point of this chart). Whisker/box are decorative (pointer-events:none) and brighten via the
   sibling rule below, same pattern as .line-chart-series-hit + its line. */
.candle-chart-hit { fill: transparent; cursor: pointer; }
.candle-chart-whisker { stroke-width: 2; pointer-events: none; transition: stroke-width .12s ease, filter .12s ease; }
.candle-chart-box { pointer-events: none; transition: filter .12s ease; }
.candle-chart-hit:hover ~ .candle-chart-whisker { filter: brightness(1.3); stroke-width: 3; }
.candle-chart-hit:hover ~ .candle-chart-box { filter: brightness(1.3); }

/* Line/scatter/candle charts in a row stretched taller by a row-mate (e.g. a fixed-height gauge,
   or a .usage-card donut - grid's default align-items:stretch grows every .boxed in the row to
   match) otherwise leave the extra height as dead space below the chart, same problem as
   .summary-graph-grid's bar-chart fix above but that fix explicitly excludes these chart types
   (their SVGs use preserveAspectRatio="none" against a fixed viewBox, so growing height
   independently of width would elongate the Y-axis instead of scaling cleanly like a bar-fill
   percentage does). Scoped via :has() on .line-chart-plot rather than a grid class -
   .line-chart-plot is a reliable, exclusive signal for line/scatter/candle
   (render_stacked_bar_chart()/render_grouped_bar_chart() also wrap their output in
   .line-chart-wrap for the shared legend, but never emit .line-chart-plot) - so this works in
   every grid (.div-grid, .summary-graph-grid, .perf-metric-grid, anywhere else) with no PHP
   call-site changes needed, unlike the grid-scoped bar-chart fix above. */
.boxed:has(> .card-body > .line-chart-wrap > .line-chart-plot) {
	display: flex;
	flex-direction: column;
}
.boxed:has(> .card-body > .line-chart-wrap > .line-chart-plot) > .card-body {
	flex: 1;
	display: flex;
	flex-direction: column;
}
.boxed:has(> .card-body > .line-chart-wrap > .line-chart-plot) > .card-body > .line-chart-wrap {
	flex: 1;
	justify-content: center;
}
/* .line-chart-plot itself grows into the extra room too, instead of just being centered as a
   fixed-size block - capped by max-height so it can't stretch the Y-axis past readability (the
   distortion the comment above calls out). container-type:inline-size + a cqw max-height makes
   the cap track the plot's own rendered width (a 4-col grid cell and a 2-col one need different
   caps in px, but the same cap as a ratio) - 1.6:1 (width:height) is a judgment call,
   not a measured constant; natural aspect is ~2.46:1 (640/260 default viewBox) so this allows
   roughly 1.5x growth before capping. Tune the 1.6 if charts still read too squished/stretched.
   Any further leftover height (grid row taller than even the capped plot) still centers via the
   .line-chart-wrap rule above. */
.boxed:has(> .card-body > .line-chart-wrap > .line-chart-plot) > .card-body > .line-chart-wrap > .line-chart-plot {
	container-type: inline-size;
	flex: 1 1 auto;
	min-height: 0;
	max-height: calc(100cqw / 1.6);
}
.boxed:has(> .card-body > .line-chart-wrap > .line-chart-plot) > .card-body > .line-chart-wrap > .line-chart-plot > .line-chart-svg-wrap {
	display: flex;
	flex-direction: column;
	min-height: 0;
}
.boxed:has(> .card-body > .line-chart-wrap > .line-chart-plot) > .card-body > .line-chart-wrap > .line-chart-plot > .line-chart-svg-wrap > svg {
	flex: 1 1 auto;
	min-height: 0;
	height: auto;
}

/* Stacked column chart (render_stacked_bar_chart()) - reuses .bar-chart-vertical's column/
   track layout entirely; only the stacking direction is new. Segments stack bottom-up
   (first series in $series renders at the bottom) - .bar-track's own border-radius +
   overflow:hidden already rounds off the top/bottom segments for free, no per-segment
   radius logic needed. */
.bar-track-stacked { flex-direction: column-reverse; align-items: stretch; }
.bar-track-stacked .bar-fill { width: 100%; height: auto; flex: 0 0 auto; border-radius: 0; }
/* Stacked bar chart, horizontal orientation (render_stacked_bar_chart(..., ['orientation' =>
   'horizontal'])) - reuses .bar-row's label/track/value layout, segments stack left-to-right
   instead of .bar-track-stacked's bottom-up. */
.bar-track-stacked-h { display: flex; flex-direction: row; align-items: stretch; }
.bar-track-stacked-h .bar-fill { height: 100%; width: auto; flex: 0 0 auto; border-radius: 0; }
/* Unlike .bar-track-stacked (vertical stack, 6px track radius - a flat inner edge next to that
   small a corner is barely visible), .bar-track-stacked-h's track is a full pill (radius = half
   its height) - relying on overflow:hidden alone only rounds a segment that's flush against the
   track's actual edge, which the last segment rarely is (categories don't all sum to the same
   max). Round the first/last rendered segment explicitly instead - 0-value series are skipped
   in PHP, so :first-child/:last-child already land on the right elements with no extra markup. */
.bar-track-stacked-h .bar-fill:first-child:not(:last-child) { border-radius: var(--radius-pill) 0 0 var(--radius-pill); }
.bar-track-stacked-h .bar-fill:last-child:not(:first-child) { border-radius: 0 var(--radius-pill) var(--radius-pill) 0; }
.bar-track-stacked-h .bar-fill:first-child:last-child { border-radius: var(--radius-pill); }
.bar-fill.is-hidden { display: none; }
/* opts['scroll'] (render_stacked_bar_chart()) - a real/unbounded category count (e.g. one
   column per day) can't flex evenly down to fit forever without eventually crushing every
   column+label unreadably thin. flex:1 1 44px keeps the normal .summary-chart-card behaviour
   (columns grow to fill the full available width, same as the fixed-6-category case) right up
   until there isn't room for every column's 44px floor, at which point they stop shrinking
   further and the row scrolls instead - so this only ever activates on a narrow viewport/many
   categories, never shrinking things that already fit. contain:inline-size is the same
   technique .table-wrapper already uses for wide data tables elsewhere in this codebase - it's
   what actually lets overflow-x:auto activate inside a `<table>` cell's default auto layout,
   which otherwise expands the whole table to fit the content instead of scrolling it. */
.bar-chart-scroll { overflow-x: auto; overflow-y: hidden; contain: inline-size; scrollbar-width: thin; scrollbar-color: var(--gray-4) var(--bg); }
.bar-chart-scroll .bar-col { flex: 1 1 44px; min-width: 44px; }
/* The horizontal scrollbar drawn by overflow-x:auto above has nowhere of its own to sit -
   it paints at the bottom of the fixed 200px .bar-chart-vertical box, right over the .bar-col
   axis labels. padding-bottom carves out a dedicated gutter for it (box-sizing:border-box, so
   the extra height exactly offsets the padding and .bar-col keeps its original 200px). */
.bar-chart-vertical.bar-chart-scroll { height: 214px; padding-bottom: 14px; }
/* Same problem, one level out: that scrollbar's 14px gutter above is INSIDE the box's own
   border-box, so .line-chart-legend (a plain flex sibling, .line-chart-wrap's 8px gap) sits
   almost flush underneath it - when the scrollbar is actually drawn there's no breathing room
   left between it and the legend text. Matching margin-top gives it the same clearance. */
/* .bar-chart-scroll + .line-chart-legend { margin-top: 14px; } */

/* Grouped/clustered column chart (render_grouped_bar_chart()) - reuses .bar-chart-vertical's
   column layout at the category level (.bar-col), but each category holds a .bar-group row of
   independent side-by-side bars instead of one .bar-track - each with its own value label,
   for comparing series' actual values directly rather than a combined stacked total. Mirrors
   .bar-chart-vertical's own flex:1 + align-items:stretch pattern one level deeper (.bar-group
   stretches .bar-group-item to the full row height, .bar-group-item's own flex:1 .bar-track
   then fills whatever's left below its value label) rather than percentage heights, which don't
   reliably resolve through this many nested flex levels. */
.bar-chart-grouped { flex-direction: row; align-items: stretch; gap: 16px; height: 200px; }
.bar-chart-grouped .bar-col { display: flex; flex-direction: column; align-items: center; gap: 8px; flex: 1; min-width: 0; }
.bar-chart-grouped .bar-group { display: flex; flex-direction: row; align-items: stretch; justify-content: center; gap: 4px; flex: 1; width: 100%; }
.bar-chart-grouped .bar-group-item { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 1 1 32px; max-width: 56px; min-width: 0; }
.bar-chart-grouped .bar-group-item .bar-track { flex: 1; width: 100%; display: flex; align-items: flex-end; border-radius: 4px; }
.bar-chart-grouped .bar-group-item .bar-fill { width: 100%; height: auto; }
.bar-chart-grouped .bar-group-item .bar-value { font-size: .68rem; white-space: nowrap; }
.bar-chart-grouped .bar-group-item.is-hidden { display: none; }
.bar-chart-grouped .bar-label { max-width: 100%; }

/* Gauge (render_gauge_chart(), includes/functions.php - was jquery/drawGauge.js + d3 until the
   gauges moved server-side so they could survive into the static HTML export).
   Its zone/needle/tick colours are set via SVG presentation attributes (fill="..."/stroke="...",
   not inline style=) - an author stylesheet rule like the ones below already wins over a
   presentation attribute in the cascade, so this can retint everything without editing the PHP.
   Ticks/needle-base/marker borders are hardcoded stroke="white" by the renderer - harmless on the
   old dark-only theme, but a real light-mode bug once body.light-mode exists (a bright white
   ring against a light card). var(--surface) below fixes that for both themes at once. */
.svg-gauge .needle,
.svg-gauge .needle-base,
.svg-gauge .marker { stroke: var(--surface) !important; }
.svg-gauge .needle,
.svg-gauge .needle-base { filter: drop-shadow(0 1px 2px rgba(0,0,0,.35)); cursor: pointer; }
.svg-gauge line { stroke: var(--border) !important; }
.svg-gauge .label { font-family: var(--font); font-size: 22px; font-weight: 800; fill: var(--heading); }
.svg-gauge .tick-label { font-family: var(--font); font-size: 11px; font-weight: 600; fill: var(--text-secondary); }

/* Gauge container (index.php - replaces the old per-call-site inline style='width:400px;
   height:400px'/'height:300px') - shrinks to the card's actual width on a narrow screen instead
   of overflowing a fixed 400px box, caps at 400px on a wide one (the original design size),
   stays square via aspect-ratio so the dial (the renderer's radius = min(w,h)/2) always has the
   full box to work with. Combined with .svg-gauge's width/height:100% above and the renderer's
   fixed 400x400 viewBox, the gauge scales continuously via CSS as this box resizes, same as the
   line/candle charts' viewBox scaling - no resize listener needed. */
.gauge-box { width: 100%; max-width: 400px; aspect-ratio: 1; margin: 0 auto; }

/* Same "row-mate stretched my card taller, chart doesn't fill/center in it" gap as the
   line/scatter/candle fix above, for gauge cards - .gauge-box is a reliable, exclusive signal
   (only the gauge() call sites in index.php use it). Unlike those charts, no growth cap is
   needed here: .gauge-box already stops growing at max-width:400px on its own, so centering the
   already-capped box is enough. */
.boxed:has(> .card-body .gauge-box) {
	display: flex;
	flex-direction: column;
}
.boxed:has(> .card-body .gauge-box) > .card-body {
	flex: 1;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
}

/* Report footer */
.report-footer {
    padding: 18px 20px 20px;
}
.report-footer-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; margin-bottom: 18px; }
.report-footer-title { margin: 0; font-size: 1.22rem; font-weight: 700; color: var(--heading); }
.report-footer-subtitle { margin: 5px 0 0 0; color: var(--text-secondary); font-size: .88rem; }
.report-status-pill {
    display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto;
    padding: 8px 12px; border-radius: 999px; font-size: .82rem; font-weight: 700;
}
.report-status-pill--success { border: 1px solid color-mix(in srgb, var(--good) 35%, transparent); background: color-mix(in srgb, var(--good) 14%, transparent); color: var(--good); }
.report-status-pill--warning { border: 1px solid color-mix(in srgb, var(--ok) 35%, transparent); background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok); }
.report-status-pill--danger  { border: 1px solid color-mix(in srgb, var(--poor) 35%, transparent); background: color-mix(in srgb, var(--poor) 14%, transparent); color: var(--poor); }
.report-status-dot { width: 8px; height: 8px; border-radius: 999px; background: currentColor; box-shadow: 0 0 10px currentColor; }
.report-footer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.report-footer-card { min-width: 0; padding: 14px; border: 1px solid var(--border); border-radius: 12px; background: rgba(var(--ink-rgb),.035); }
.report-footer-card-title { margin: 0 0 10px 0; color: var(--text-secondary); font-size: .72rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
.report-footer-value { color: var(--text); font-size: .92rem; font-weight: 650; line-height: 1.45; overflow-wrap: anywhere; }

/* Software & Firmware status used to be its own card here (replicating
   software_firmware_status_examples.html) - it's now render_software_status_row()
   (includes/functions.php), one .mgmt-row inside the Device Management section
   (see .mgmt-row below), so the amber/green .sw-status-* treatment no longer has a caller. */
.report-footer-muted { color: var(--text-secondary); font-weight: 500; }
.report-footer-meta { display: grid; grid-template-columns: auto 1fr; column-gap: 10px; row-gap: 6px; font-size: .88rem; line-height: 1.4; }
.report-footer-meta-label { color: var(--text-secondary); }
.report-footer-meta-value { color: var(--text); font-weight: 650; overflow-wrap: anywhere; }
@media (max-width: 680px) {
    .report-footer-header { display: block; }
    .report-status-pill { margin-top: 14px; }
}

.card-head { display: flex; flex: 0 0 auto; flex-wrap: wrap; align-items: center; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--border); }
.section-icon { width: 38px; height: 38px; flex: 0 0 auto; display: grid; place-items: center; border-radius: 10px; color: var(--accent); background: rgba(var(--accent-rgb),.14); }
.section-icon .mdi { font-size: 20px; line-height: 1; }
/* flex:1 is a zero basis, so on its own the title never makes .card-head wrap - on a phone it got
   squeezed to a word per line beside .card-actions instead. The min-width is what the wrap is
   measured against: once title + actions can't both fit, the actions drop to their own row. Wide
   cards never hit it, so desktop layout is unchanged; min() keeps a very narrow card from overflowing. */
.card-title { flex: 1; min-width: min(12rem, 100%); }
.card-title h3 { margin: 0 0 2px; color: var(--text); font-size: .95rem; }
.card-title p { margin: 0; color: var(--text-muted); font-size: .76rem; }
.section-pill { flex: 0 0 auto; padding: 5px 10px; border-radius: var(--radius-pill); color: var(--accent-soft); background: rgba(var(--accent-rgb),.12); font-size: .7rem; font-weight: 850; white-space: nowrap; }
.card-actions { flex: 1 1 auto; min-width: 0; margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; justify-content: end; }
/* card-title's own flex:1 already grows to fill leftover space, so this only needs to hug its
   content and let margin-left:auto push it flush to the header's right edge - scoped to this one
   card rather than every .card-actions bar (search forms etc.) that already relies on the
   default flex-grow to share the row. */
#daily-usage-card .card-actions { flex: 0 0 auto; }

/* Bare clickable mdi glyph - e.g. table-row action icons like the invoice download link */
.mdi-icon { display: inline-flex; color: var(--text-muted); font-size: 22px; line-height: 1; cursor: pointer; transition: color .15s ease, transform .15s ease; }
.mdi-icon:hover { color: var(--accent); transform: translateY(-1px); }
/* Same glyph with a circle/square background chip - pair with .mdi-icon-backed.square for a rounded-square instead of a circle.
   Defaults to the neutral --text-muted, matching the plain white glyph that the /location/images
   PNGs most of these replaced actually used (and the bare .mdi-icon above). Only a chip whose
   original carried a real colour needs to say so, by passing style="--icon-accent:<colour>" at the
   call site - same idea as .metric-card / --metric-accent above. See pages/survey_list.php's
   status glyphs, the Excel-green export links, and pages/scheduling.php's power/expiry states. */
.mdi-icon-backed { --icon-tone: var(--icon-accent, var(--text-muted)); display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; border-radius: 50%; color: var(--icon-tone); background: color-mix(in srgb, var(--icon-tone) 14%, transparent); font-size: 20px; line-height: 1; cursor: pointer; transition: background-color .15s ease, transform .15s ease; }
button:has(> .mdi-icon-backed) { background: none; border: none; padding: 0; margin: 0; }
.mdi-icon-backed:hover { background: color-mix(in srgb, var(--icon-tone) 26%, transparent); transform: translateY(-1px); }
.mdi-icon-backed.square { border-radius: 10px; }
/* Labelled pill - same chip with text on the right: <i class="mdi mdi-x mdi-icon-backed mdi-icon-labelled" data-label="Comments">.
   The label is a ::after so the markup stays a single <i>; the glyph (::before) gets its own
   --surface circle inside the tinted pill. Tone comes from --icon-accent exactly as above. */
.mdi-icon-labelled { width: auto; height: auto; gap: 8px; padding: 4px 16px 4px 4px; border-radius: 999px; font-family: inherit; font-size: 13px; font-weight: 500; white-space: nowrap; }
.mdi.mdi-icon-labelled::before { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: 50%; background: var(--surface); font-size: 20px; }
.mdi-icon-labelled::after { content: attr(data-label); }
/* A call site may pass --icon-accent-light alongside --icon-accent for the white card: colours
   picked against the dark theme can land as low as 1.1:1 there. The rule below has to assign
   --icon-tone rather than --icon-accent, because an inline style beats an author stylesheet rule -
   overwriting --icon-accent here would lose to the very call sites this needs to serve.
   --icon-tone is never set inline, so it stays overridable. Pass --icon-accent-light on its own
   and nothing happens; it is only ever read as an override of --icon-accent. */
body.light-mode .mdi-icon-backed { --icon-tone: var(--icon-accent-light, var(--icon-accent, var(--text-muted))); }
/* Status/read-only variant - same chip, no pointer affordance (it isn't clickable) */
.mdi-icon-static { cursor: default; }
.mdi-icon-static:hover { transform: none; }
/* Status tones - same palette as the SENTRY play/pause/stop controls on pages/devices.php */
.mdi-icon-backed--run   { color: #34d399; background: rgba(22,155,88,.18); }
.mdi-icon-backed--pause { color: #fbbf24; background: rgba(245,158,11,.18); }
.mdi-icon-backed--stop  { color: #f87171; background: rgba(239,68,68,.18); }
.mdi-icon-backed--run:hover   { background: rgba(22,155,88,.28); }
.mdi-icon-backed--pause:hover { background: rgba(245,158,11,.28); }
.mdi-icon-backed--stop:hover  { background: rgba(239,68,68,.28); }
/* Row of config glyphs in a table cell (e.g. SENTRY history: mode / tech / duration / power) */
.icon-strip { display: inline-flex; align-items: center; gap: 6px; }

.usage-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 14px; }
.usage-card { display: flex; flex-direction: column; min-width: 0; container-type: inline-size; }
.usage-card > .card-body { padding: 0; flex: 1; display: flex; }
.usage-card .chart-body { display: flex; flex: 1; flex-wrap: wrap; align-items: center; justify-content: center; gap: 20px; padding: 18px; }
.usage-card .donut-wrap { display: grid; place-items: center; flex: 0 0 auto; }
.usage-card .legend { display: grid; gap: 10px; min-width: 0; flex: 0 1 auto; }
.usage-card .legend.legend-2col { grid-template-columns: repeat(2, minmax(0,1fr)); gap: 10px 18px; }
.usage-card .legend-item { display: grid; grid-template-columns: 10px minmax(0,1fr) auto; align-items: center; align-content: center; gap: 9px; }
.usage-card .legend-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--dot); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dot) 16%, transparent); }
.usage-card .legend-copy { min-width: 0; }
.usage-card .legend-name { display: block; overflow: hidden; color: var(--text); font-weight: 750; font-size: .82rem; text-overflow: ellipsis; white-space: nowrap; }
.usage-card .legend-meta { display: block; margin-top: 1px; color: var(--text-muted); font-size: .7rem; }
.usage-card .legend-value { color: var(--text); font-weight: 850; font-size: .82rem; text-align: right; }
.usage-card .progress { grid-column: 2/4; height: 5px; margin-top: 2px; overflow: hidden; border-radius: var(--radius-pill); background: rgba(var(--ink-rgb),.08); }
.usage-card .progress span { display: block; height: 100%; width: var(--width); border-radius: inherit; background: var(--dot); }
/* Reacts to the card's own rendered width, not viewport width — a .usage-card squished by
   a 4-up parent grid (e.g. Combined Distribution Matrix Data) is just as narrow at 1920px
   wide as it is on a phone, so a viewport media query can't catch it. */
@container (max-width: 340px) {
    .usage-card .chart-body { flex-direction: column; }
    .usage-card .legend.legend-2col { grid-template-columns: 1fr; }
}

/* .boxed is overflow:hidden, and that also drops a grid item's min-width to 0 - so in a 4-up .div-grid
   (the Combined row) a many-band table was silently cut off at the card edge. Scroll it instead,
   styled like .table-wrapper. */
.heatmap-card > .card-body { padding: 0; overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--gray-4) var(--bg); }
.heatmap-card table.heatmap-table { margin-bottom: 0; }

/* Drawer analytics: stat grid, technology bars, activity chart, date range, insights */
.stat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.mini-stat { padding: 10px; border-radius: 10px; border: 1px solid rgba(var(--ink-rgb),.07); background: rgba(var(--ink-rgb),.03); }
.mini-stat b { display: block; color: var(--accent); font-size: 17px; line-height: 1; }
.mini-stat span { display: block; margin-top: 4px; color: var(--text-muted); font-size: 9.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .03em; white-space: normal; }

/* .activity-chart/.chart-dots-wrap/.chart-dot/.chart-axis-label are buildTrendChartSvg()'s own
   markup (jquery/device-analytics.js) - still used by the devices.php portfolio cards' mini
   sparklines. Survey Activity itself moved to render_survey_activity_chart()/.line-chart-wrap
   (includes/functions.php) so it looks like every other line chart on the site. */
.activity-chart { width: 100%; height: 100%; display: block; }
.chart-dots-wrap { position: relative; width: 100%; height: 100%; }
.chart-dot { position: absolute; transform: translate(-50%, -50%); border-radius: 50%; border: 1px solid var(--surface); pointer-events: none; }
/* x-axis labels are real HTML, not SVG <text>, for the same reason as .chart-dot */
.chart-axis-label { position: absolute; transform: translate(-50%, 0); font-size: 8px; color: #8296a3; white-space: nowrap; pointer-events: none; font-style: normal; }
.activity-summary { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.activity-summary span { display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 10px; border-radius: var(--radius-pill); border: 1px solid rgba(var(--ink-rgb),.1); background: rgba(var(--ink-rgb),.03); color: var(--text-muted); font-size: 11px; }
.activity-summary b { color: var(--text); }

/* Survey Date Range dropped its bespoke .timeline/.range-track widget for the shared
   .stat-grid/.mini-stat + buildBarChartHtml() ("tech bar" chart) already used elsewhere on
   this same card set, so it looks like the rest of the site instead of a one-off component. */

.insight-list { display: grid; gap: 8px; }
.insight-item { display: grid; grid-template-columns: 26px 1fr; gap: 9px; align-items: start; padding: 9px; border-radius: 10px; border: 1px solid rgba(var(--ink-rgb),.07); background: rgba(var(--ink-rgb),.03); }
.insight-item b { display: block; color: var(--text); font-size: 12px; margin-bottom: 2px; }
.insight-item span { display: block; color: var(--text-muted); font-size: 11px; line-height: 1.4; }
.insight-item .insight-icon { box-sizing: border-box; width: 26px; height: 26px; border-radius: 50%; display: block; text-align: center; line-height: 24px; background: rgba(var(--accent-rgb),.14); border: 1px solid rgba(var(--accent-rgb),.3); color: var(--accent); font-size: 12px; font-weight: 900; }

/* Portfolio summary row — 6 tinted cards above the device list, real data + real sparklines
   (render_sparkline() in includes/functions.php), not decorative fakes. */
.summary-row { display: flex; flex-wrap: wrap; gap: 9px; margin-bottom: 16px; }
.summary-card {
    position: relative; flex: 1 1 116px; min-width: 116px; min-height: 100px;
    padding: 15px 13px 12px; border-radius: 10px; overflow: hidden; isolation: isolate;
    border: 1px solid rgba(var(--ink-rgb),.09); background: rgba(var(--ink-rgb),.03);
    box-shadow: inset 0 1px 0 rgba(var(--ink-rgb),.05);
}
.summary-card > span, .summary-card > b, .summary-card > .family-mini-dots { position: relative; z-index: 2; }
.summary-card span { display: block; font-size: 9.5px; font-weight: 850; letter-spacing: .02em; color: var(--text-muted); max-width: 90px; line-height: 1.25; }
.summary-card b { display: block; margin-top: 8px; font-size: 21px; line-height: 1; color: var(--text); }
.summary-card.total     { color: var(--accent); background: linear-gradient(180deg, rgba(var(--accent-rgb),.14), rgba(var(--ink-rgb),.03)); border-color: rgba(var(--accent-rgb),.22); }
.summary-card.recording { color: #ef4444; background: linear-gradient(180deg, rgba(239,68,68,.14), rgba(var(--ink-rgb),.03)); border-color: rgba(239,68,68,.22); }
.summary-card.scanners  { color: #4da6ff; background: linear-gradient(180deg, rgba(77,166,255,.14), rgba(var(--ink-rgb),.03)); border-color: rgba(77,166,255,.22); }
.summary-card.surveys   { color: #20c879; background: linear-gradient(180deg, rgba(32,200,121,.14), rgba(var(--ink-rgb),.03)); border-color: rgba(32,200,121,.22); }
.summary-card.cycles    { color: #f2b233; background: linear-gradient(180deg, rgba(242,178,51,.14), rgba(var(--ink-rgb),.03)); border-color: rgba(242,178,51,.22); }
.summary-card.families  { color: #a77cff; background: linear-gradient(180deg, rgba(167,124,255,.14), rgba(var(--ink-rgb),.03)); border-color: rgba(167,124,255,.22); }
.summary-card.total b, .summary-card.recording b, .summary-card.scanners b,
.summary-card.surveys b, .summary-card.cycles b, .summary-card.families b { color: currentColor; }
.summary-spark { position: absolute; left: 8px; right: 8px; bottom: 8px; width: calc(100% - 16px); height: 28px; z-index: 1; opacity: .8; }
.summary-spark path { vector-effect: non-scaling-stroke; }
.family-mini-dots { display: flex; gap: 5px; margin-top: 14px; }
.family-mini-dots i { width: 8px; height: 8px; border-radius: 999px; background: currentColor; opacity: .8; }
@media (max-width: 900px) { .summary-row { flex-wrap: wrap; } .summary-card { flex-basis: calc(33.333% - 6px); } }
@media (max-width: 560px) { .summary-card { flex-basis: calc(50% - 5px); } }

/* Controls bar — device list pages */
.devices-controls { display: flex; align-items: center; justify-content: flex-end; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.devices-controls-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

@media (max-width: 600px) {
    .card-grid { grid-template-columns: 1fr; }
    .drawer { width: 100%; max-width: 100%; }
    .drawer-actions { grid-template-columns: 1fr; }
    .devices-controls { flex-direction: column; align-items: stretch; }
    .devices-controls-right { justify-content: space-between; }
}

/* CARD BODY - the content area of any render_boxed_card() card (includes/functions.php),
   padded to sit under the shared .card-head above. .detail-row (render_detail_row()) is one
   possible content shape, a flat list of label+control rows - not tied to any one page, reuse
   it wherever that shape of content comes up (account defaults, a device's info panel,
   anything that's "a list of labelled controls/values"); .card-body itself just holds whatever
   HTML the caller passes in. */
.card-body { padding: 10px 16px; }
/* CARD CHIN - optional footer strip below .card-body for warnings/fine print (render_boxed_card()'s
   'chin' opt), e.g. "Screen size, browser and timezone are included to help us reproduce issues." */
.card-chin { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 10px 16px; border-top: 1px solid var(--border); background: rgba(var(--ink-rgb),.02); color: var(--text-muted); font-size: .76rem; text-align: center; }
.card-chin .mdi { flex: 0 0 auto; font-size: 15px; color: var(--accent); }

/* FEEDBACK CARD - the #submit_feedback modal (index.php). A deliberate fixed-appearance card,
   same reasoning as the login/2FA splash further up (search "doesn't flip with the theme"): a
   solid blue header over a solid white body, matching the reference mock, rather than the
   site's own dark/light toggle - it's a one-off modal, not a themed page section, so it doesn't
   need to track var(--surface)/var(--text) the way every other render_boxed_card() card does. */
.feedback-card.boxed { background: #ffffff; border: 0; box-shadow: 0 24px 60px rgba(0,0,0,.45); }
.feedback-card > .card-head { background: linear-gradient(118deg,#168ddd 0%,#116fc3 42%,#13487f 100%); border-bottom: 0; padding: 22px 56px 22px 20px; }
.feedback-card > .card-head .card-title h3 { color: #fff; }
.feedback-card > .card-head .card-title p { color: rgba(255,255,255,.84); }
.feedback-card > .card-head .section-icon { background: rgba(234,247,255,.92); color: #0a73d2; }
.feedback-card > .card-body { background: #ffffff; color: #17304f; }
.feedback-card .field-control { background: #ffffff; border-color: #b7c7d8; }
.feedback-card .field-control:focus-within { border-color: #1687ef; box-shadow: 0 0 0 4px rgba(22,135,239,.11); }
.feedback-card .field-icon { color: #71839a; }
.feedback-card textarea { color: #213d5f; }
.feedback-card textarea::placeholder { color: #a9b5c4; }
.feedback-card > .card-chin { background: #f5f9fd; border-top-color: #e6edf4; color: #60758d; }
.feedback-card > .card-chin .mdi { color: #0d74d8; }
.detail-row { display: grid; grid-template-columns: minmax(0,1fr) auto; align-items: center; gap: 20px; padding: 12px 0; border-bottom: 1px solid var(--border); }
.detail-row:last-child { border-bottom: 0; }
.detail-copy { min-width: 0; }
.detail-label { display: flex; align-items: center; gap: 6px; font-weight: 700; color: var(--text); font-size: .88rem; }
.detail-description { display: block; margin-top: 3px; color: var(--text-secondary); font-size: 12px; font-weight: 400; line-height: 1.4; }
.detail-control { display: flex; align-items: center; justify-content: flex-end; }
@media (max-width: 560px) {
    .detail-row { grid-template-columns: minmax(0,1fr); gap: 8px; }
    .detail-control { justify-content: flex-start; }
    /* a lone button control (Purchase Tokens' View plans) spans the row once it's stacked */
    .detail-control > .menu_button { flex: 1 1 auto; }
}
/* render_boxed_card(..., ["class" => "detail-grid"]): rows pair up side by side once the card is
   wide enough, else stay one per line. The last pair drops its divider like :last-child does. */
.detail-grid { container-type: inline-size; }
@container (min-width: 1000px) {
    .detail-grid > .card-body { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); column-gap: 48px; }
    .detail-grid > .card-body > .detail-row:nth-last-child(2):nth-child(odd) { border-bottom: 0; }
    /* a lone row has nothing to pair with, so it takes the full width */
    .detail-grid > .card-body > .detail-row:only-child { grid-column: 1 / -1; }
}
/* Account settings + Purchase Tokens: each row is its own bubble, same look as the device drawer's
   .mgmt-row, instead of a divider line - spaced by the card body's gap, which also replaces
   .detail-grid's 48px column gap. Roomier on larger screens, roughly where .detail-grid pairs rows
   up (the per-tech signal rows get theirs next to their own rule). Scoped to account.php because
   index.php's compare stats are detail rows too. */
:is(#settingsForm, #purchase_tokens) .card-body:has(> :is(.detail-row, .signal-levels-group)) { display: grid; grid-auto-columns: minmax(0,1fr); gap: 12px; padding: 16px; }
:is(#settingsForm, #purchase_tokens) .detail-row { padding: 16px; border: 1px solid rgba(var(--ink-rgb),.08); border-radius: 12px; background: rgba(var(--ink-rgb),.03); }
/* Quality Bands heading row (label + Default button) stays plain, the rows under it are bubbles */
:is(#settingsForm, #purchase_tokens) .detail-row:has(.signal-levels-reset) { padding: 0; border: 0; border-radius: 0; background: none; }
#token_msg:empty { display: none; }
@media (min-width: 1100px) {
    :is(#settingsForm, #purchase_tokens) .detail-row { padding: 22px 20px; }
    /* same outer height as a .metric-card (142px + 2x19px padding) - both are content-box. The
       Quality Bands heading row is plain text, not a bubble, so it keeps its natural height. */
    :is(#settingsForm, #purchase_tokens) .detail-row:not(:has(.signal-levels-reset)) { min-height: 136px; }
    .tech-matrix th, .tech-matrix td { padding: 14px 10px; }
}

.tech-matrix-wrap { overflow-x: auto; padding: 4px 16px 14px; }
/* each technology row is a bubble like the account .detail-rows above - separate borders so the
   row can round its ends, border-spacing as the gap between them */
.tech-matrix { width: 100%; min-width: 720px; border-collapse: separate; border-spacing: 0 12px; }
.tech-matrix th, .tech-matrix td { padding: 8px 10px; text-align: center; }
.tech-matrix tbody td { background: rgba(var(--ink-rgb),.03); border: 1px solid rgba(var(--ink-rgb),.08); border-width: 1px 0; }
.tech-matrix tbody td:first-child { border-left-width: 1px; border-radius: 12px 0 0 12px; }
.tech-matrix tbody td:last-child { border-right-width: 1px; border-radius: 0 12px 12px 0; }
.tech-matrix th:first-child, .tech-matrix td:first-child { text-align: left; }
.tech-matrix thead th { color: var(--text-muted); font-size: .72rem; font-weight: 800; text-transform: uppercase; }
.tech-matrix .column-toggle-head { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.tech-matrix .switch { justify-content: center; }
.tech-matrix .technology-name { display: flex; align-items: center; gap: 8px; font-weight: 700; color: var(--text); }
.tech-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex: 0 0 auto; }

/* FLOATING BOTTOM BAR - one bar, one save button, shared by any form that used to have a save
   button per section (account.php Settings tab). Shown/hidden by the small inline script that
   renders alongside it, based on whether the form differs from its loaded defaults. */
.floating-bottom-bar {
    position: fixed;
    left: 50%;
    bottom: 20px;
    z-index: 500;
    display: flex;
    align-items: center;
    gap: 14px;
    width: min(680px, calc(100vw - 28px));
    padding: 12px 14px 12px 18px;
    border: 1px solid rgba(var(--accent-rgb),.3);
    border-radius: 15px;
    background: linear-gradient(160deg, rgba(255,255,255,.06), rgba(255,255,255,.02)), var(--navy-900);
    color: var(--text-on-accent);
    box-shadow: 0 16px 40px rgba(0,0,0,.35);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translate(-50%, 12px);
    transition: opacity .2s ease, transform .2s ease, visibility .2s ease;
}
.floating-bottom-bar.show { opacity: 1; visibility: visible; pointer-events: auto; transform: translate(-50%, 0); }
.floating-bottom-bar-text { flex: 1; min-width: 0; font-size: .85rem; color: rgba(255,255,255,.85); }
/* while the bar shows, give the page enough room to scroll its last content out from under it */
body:has(.floating-bottom-bar.show) { padding-bottom: 90px; }
html:has(.floating-bottom-bar.show) { scroll-padding-bottom: 110px; }
@media (max-width: 620px) {
    .floating-bottom-bar { flex-wrap: wrap; padding: 12px 14px; }
    .floating-bottom-bar-text { flex-basis: 100%; text-align: center; }
    /* side by side under the text - stacked, the bar outgrew the padding-bottom below */
    .floating-bottom-bar > .menu_button { flex: 1 1 0; min-width: 0; margin: 0; }
    body:has(.floating-bottom-bar.show) { padding-bottom: 140px; }
    html:has(.floating-bottom-bar.show) { scroll-padding-bottom: 160px; }
}

/* BACK TO TOP - jquery/back-to-top.js. z-index sits under drawers (200) and the save bar (500). */
#back-to-top {
    position: fixed;
    right: max(20px, env(safe-area-inset-right));
    bottom: max(20px, env(safe-area-inset-bottom));
    z-index: 150;
    width: 46px;
    height: 46px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid rgba(var(--accent-rgb),.45);
    border-radius: 50%;
    background: var(--surface);
    color: var(--accent);
    font-size: 1.5rem;
    cursor: pointer;
    box-shadow: 0 8px 24px rgba(0,0,0,.35);
    opacity: 0;
    visibility: hidden;
    transform: translateY(10px);
    transition: opacity .2s ease, transform .2s ease, visibility .2s ease, bottom .2s ease;
}
#back-to-top.show { opacity: 1; visibility: visible; transform: none; }
#back-to-top:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (hover: hover) { #back-to-top:hover { background: var(--surface-hover); } }
/* clear the save bar and the open mobile menu */
body:has(.floating-bottom-bar.show) #back-to-top { bottom: 100px; }
body:has(#nav-back.nav-back) #back-to-top { visibility: hidden; }
@media (max-width: 620px) {
    #back-to-top { width: 42px; height: 42px; right: 14px; bottom: 14px; }
    body:has(.floating-bottom-bar.show) #back-to-top { bottom: 150px; }
}

.data-table-subtext { font-size: 0.76rem; color: var(--text-muted); font-weight: 400; }

.data-table td.wrap { white-space: normal; word-break: break-word; overflow: visible; }

.data-table td:not(:has(.helptip)) { overflow: hidden; text-overflow: ellipsis; }

/* NETWORK TECH CHIPS */
.net-chips { display: flex; flex-wrap: wrap; gap: 3px; }
.net-chip {
    display: inline-flex; align-items: center; gap: 2px;
    padding: 2px 7px; border-radius: 999px;
    font-size: 0.72rem; font-weight: 700; white-space: nowrap;
}
/* Technology chips: --net is the technology's colour, set inline from CellTechType::getColor()
   so the chips match the map, tables and charts. Tint and border come from it; the text is
   lifted towards white on dark and pulled towards black on light, so it stays readable on the tint. */
.net-chip-tech {
    background: color-mix(in srgb, var(--net) 20%, transparent);
    border: 1px solid color-mix(in srgb, var(--net) 50%, transparent);
    color: color-mix(in srgb, var(--net) 55%, white);
}
body.light-mode .net-chip-tech { color: color-mix(in srgb, var(--net) 75%, black); }

/* Counts (assigned devices on the schedules table) reuse the chip shape but have no
   technology to colour them by, so: neutral for none, accent for one or more */
.net-chip-none  { background: rgba(var(--ink-rgb),.08);    border: 1px solid rgba(var(--ink-rgb),.12);   color: var(--text-muted); }
.net-chip-count { background: rgba(var(--accent-rgb),.10); border: 1px solid rgba(var(--accent-rgb),.3); color: #67d5f7; }
body.light-mode .net-chip-count { color: #0e7490; }

.date-input {
    color: var(--text);
    background-color: var(--gray-6);
}
.date-input:focus {
    color: var(--text);
    background-color: var(--gray-6);
}
.date-input::-webkit-datetime-edit,
.date-input::-webkit-datetime-edit-fields-wrapper,
.date-input::-webkit-datetime-edit-text,
.date-input::-webkit-datetime-edit-month-field,
.date-input::-webkit-datetime-edit-day-field,
.date-input::-webkit-datetime-edit-year-field,
.date-input::-webkit-inner-spin-button,
.date-input::-webkit-calendar-picker-indicator {
    color: var(--text);
    /* Transparent so Chrome's inner parts show whatever the input sits on (e.g. white .field-control) */
    background-color: transparent;
}

/* MONTH-GROUPED LIST TABLES - pages/history.php, pages/survey_list.php, driven by
   jquery/list-fetch.js. Headings are the existing .sticky_header_row, made clickable; collapsing
   hides that month's rows with a class so compact view (which drops the headings) can override it
   and never strand rows behind a heading it isn't showing. */
.data-table tr.month-head > td { cursor: pointer; user-select: none; }
.month-head .month-chevron { display: inline-block; margin-right: 6px; transition: transform .15s ease; }
.month-head.is-collapsed .month-chevron { transform: rotate(-90deg); }
.month-head .month-count { margin-left: 10px; color: var(--text-muted); font-size: .82em; font-weight: 600; }
.data-table tr.is-month-hidden { display: none; }
.data-table.is-compact tr.is-month-hidden { display: table-row; }
.list-fetch.is-loading [data-lf="rows"] { opacity: .45; transition: opacity .15s ease; }
/* The thead is already position:sticky (STICKY HEADER above), but .table-wrapper's overflow-x makes
   it the sticky container and it never scrolls vertically, so the header scrolled away with the page.
   Capping the wrapper's height makes it scroll, and the header sticks to its top. The inset shadow
   stands in for the th border-bottom, which border-collapse drops once the row is stuck. */
.list-fetch .table-wrapper { max-height: 90vh; overflow-y: auto; }
.list-fetch .data-table thead th { box-shadow: inset 0 -1px 0 var(--border); }
/* Scroll-position bar across the top of the header, standing in for the page scrollbar the capped
   height took away. list-fetch.js sets --lf-progress (0-1) on scroll and adds .has-overflow only when
   the wrapper actually scrolls. The negative margin keeps it from taking layout space; it sits over
   the stuck header's top edge. */
.list-fetch .table-wrapper.has-overflow::before {
    content: ""; display: block; position: sticky; top: 0; left: 0; z-index: 2;
    height: 3px; margin-bottom: -3px; pointer-events: none;
    background: linear-gradient(var(--accent), var(--accent)) no-repeat 0 0 / calc(var(--lf-progress, 0) * 100%) 100%, rgba(var(--ink-rgb),.08);
}

.list-load-more { display: flex; justify-content: center; padding: 14px 0 2px; }
.list-load-more[hidden] { display: none; }
.list-load-more { align-items: center; flex-wrap: wrap; gap: 8px 12px; }
.list-load-count { color: var(--text-muted); font-size: .82rem; font-weight: 600; }
/* Month headings stick under the stuck <thead> while their month scrolls past. --lf-head-h is the
   thead's height, measured by list-fetch.js; the -2px tucks it under the header's collapsed bottom
   border, which doesn't travel with a stuck th and otherwise leaves a slit the rows show through.
   The tint is layered over --surface because a see-through sticky cell shows the rows beneath it. */
.list-fetch .data-table tr.month-head > td {
    position: sticky; top: calc(var(--lf-head-h, 38px) - 2px); z-index: 1;
    background: linear-gradient(rgba(var(--brand-rgb),0.08), rgba(var(--brand-rgb),0.08)), var(--surface);
}
.list-empty { padding: 22px 12px; text-align: center; color: var(--text-muted); }
.row-actions { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
/* Survey list rows linked to a MySCAN floorplan - .metric-card's left accent bar plus a faint wash, in
   the MySCAN gold. The bar is an inset shadow on the first *visible* cell: the compact info cell
   whenever it shows (compact, or any width up to 1600), otherwise the device cell. */
.data-table tbody tr.myscan-linked { --ms-gold: #ffc857; background: color-mix(in srgb, var(--ms-gold) 7%, transparent); }
body.light-mode .data-table tbody tr.myscan-linked { --ms-gold: #c25e00; }
.data-table tbody tr.myscan-linked:hover { background: color-mix(in srgb, var(--ms-gold) 13%, transparent); }
.data-table tr.myscan-linked > td.c-only,
.data-table tr.myscan-linked[performance_data] > td:first-child { box-shadow: inset 4px 0 0 var(--ms-gold); }
@media (min-width: 1601px) {
    .data-table:not(.is-compact) tr.myscan-linked > td.device-cell { box-shadow: inset 4px 0 0 var(--ms-gold); }
}
.session-cell { white-space: normal; }
.session-cell b { display: block; }
.session-cell .icon-strip { margin-top: 4px; flex-wrap: wrap; }
.status-cell { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
@media (max-width: 760px) {
    .list-load-more .menu_button { width: 100%; }
    /* The empty row spans every column, so centring puts it in the middle of the scroll width -
       off-screen once the table overflows. */
    td.list-empty { text-align: left; }
}
/* Tables whose <thead> already names every column drop the per-cell "Label:" lines in list view -
   the compact tooltip clones cells outside the table, so it keeps them. */
.data-table.labels-in-head .row-label,
.data-table.labels-in-head .row-label + br { display: none; }
/* Collapsible section - render_collapsible_section() (functions.php), e.g. the chart row above the
   month-grouped lists. Native <details>, so the marker is swapped for the same chevron the month
   headings use. */
.list-collapse { margin-bottom: 20px; }
.list-collapse-head { display: flex; align-items: center; gap: 8px; width: fit-content; padding: 6px 2px; list-style: none; cursor: pointer; user-select: none; color: var(--text-secondary, var(--text)); font-size: .85rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
.list-collapse-head::-webkit-details-marker { display: none; }
.list-collapse-head:hover { color: var(--text); }
.list-collapse-chevron { transition: transform .15s ease; }
.list-collapse:not([open]) .list-collapse-chevron { transform: rotate(-90deg); }
.list-collapse > .summary-graph-grid { margin-top: 8px; }
/* The grid's gap spaces the cards; their sitewide 20px margin-bottom can't collapse inside a grid,
   so the last row stacked it on .list-collapse's own 20px - 40px above the next card. */
.list-collapse > .summary-graph-grid > .boxed { margin-bottom: 0; }
/* Buttons in the month-grouped list pages size to their label - the legacy .menu_button
   min-width:200px (line ~119) stretched View/Delete/Compare to fill their cells. */
.list-fetch .menu_button { min-width: 0; }
/* Phone width for the survey list: let the device name/filename wrap and drop the desktop minimum
   widths, so the columns that stay (device, date, technologies, actions) fit without a sideways scroll. */
@media (max-width: 600px) {
    .list-fetch .device-cell { min-width: 0 !important; white-space: normal; overflow-wrap: anywhere; }
    .list-fetch .chips-cell { min-width: 0 !important; }
}
/* Removable device-scope chip at the start of the survey list's filter bar. */
.scope-chip { display: inline-flex; align-items: center; gap: 6px; min-height: 32px; }
/* In a .filter-bar column: one line at the inputs' 38px height, truncated - the hover card has the full text */
.filter-bar .helptipdown:has(> .scope-chip) { display: block; min-width: 0; }
.filter-bar .scope-chip { display: flex; width: 100%; min-height: 38px; box-sizing: border-box; white-space: nowrap; overflow: hidden; }
.filter-bar .scope-chip-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.scope-chip-x { display: inline-flex; color: inherit; opacity: .7; text-decoration: none; }
.scope-chip-x:hover { opacity: 1; }

/* ---- Area Explorer (pages/area_explorer.php) - laid out like the survey map (#map_section) ---- */
.ae-isolate { margin-top: 10px; }
.ae-isolate button { border: 0; background: none; color: inherit; cursor: pointer; padding: 0; }
.ae-map-wrap { position: relative; height: 100%; }
/* Loading bar across the top of the map while a drawn area's results load (area_explorer.js
   loadTabs) - the list pages' scroll bar look (.list-fetch .table-wrapper::before). It's one request
   with no byte count to track, so the fill sweeps rather than pretending to measure. */
.ae-map-wrap.is-loading::before {
    content: ""; position: absolute; top: 0; left: 0; right: 0; z-index: 5; height: 3px; pointer-events: none;
    background: linear-gradient(var(--accent), var(--accent)) no-repeat 0 0 / 30% 100%, rgba(var(--ink-rgb),.08);
    animation: aeLoadSweep 1.1s ease-in-out infinite;
}
@keyframes aeLoadSweep { from { background-position: -50% 0, 0 0; } to { background-position: 150% 0, 0 0; } }
@media (prefers-reduced-motion: reduce) { .ae-map-wrap.is-loading::before { animation: none; background-size: 100% 100%, auto; opacity: .6; } }
/* header controls waiting on a drawn area (Operators, Country, Saved views, Export) or the towers loading (Jump to) */
.field-control:has(> select[id^="ae_"]:disabled), #ae_ops_dropdown .field-control:has(> button:disabled) { opacity: .5; cursor: not-allowed; }
.field-control > select[id^="ae_"]:disabled, #ae_ops_dropdown .network_dropdown_toggle:disabled { cursor: not-allowed; }
/* a surveyed region too small to see at this zoom (area_explorer.js showRegionPins), centred on it */
.ae-region-pin { display: flex; align-items: center; gap: 5px; transform: translateY(50%); padding: 4px 10px; border-radius: 999px; background: var(--surface); border: 2px dashed #555555; color: var(--text); font-size: 12px; white-space: nowrap; box-shadow: 0 2px 8px rgba(0,0,0,.3); cursor: pointer; }
.ae-region-pin:hover { border-color: #0074d9; }
.ae-region-pin i { color: #0074d9; font-size: 14px; }
/* chips on their own line under the centred name + count; a column, so the bubble is only as wide as its widest line */
.ae-region-pin { flex-direction: column; gap: 3px; border-radius: 14px; }
.ae-region-pin-head { display: flex; align-items: center; gap: 5px; }
.ae-region-pin .net-chips { flex-wrap: nowrap; justify-content: center; }
/* area_explorer.js moves the hint into the map's LEFT_TOP controls under Draw, so Google stacks it
   without overlapping the other controls. Capped short of the map width so it never runs under the
   fullscreen button (top right); a pill radius only suits one line, so it rounds less when it wraps. */
.ae-map-wrap > .ae-map-hint { display: none; }
/* phones: Jump to / Saved views / Export stack full width under the title, like the filter bar */
@media (max-width: 600px) {
    .ae-head-card .card-actions { flex-basis: 100%; flex-direction: column; align-items: stretch; }
    .ae-head-card .card-actions > .field-control { display: flex; width: 100%; margin: 0; }
}
.ae-map-hint { margin: 0 10px 10px; width: max-content; max-width: min(460px, calc(100vw - 100px)); box-sizing: border-box; padding: 8px 14px; border-radius: 14px; background: var(--surface); border: 1px solid var(--border); color: var(--text); font-size: 13px; box-shadow: 0 4px 14px rgba(0,0,0,.2); pointer-events: none; }
.ae-map-hint[hidden] { display: none; }
/* Map Options: technology groups on the left, Options on the right - index.php's two columns */
.ae-map-options { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: 12px 26px; }
.ae-map-options-techs { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 6px 26px; }
.ae-map-options-general { margin-left: auto; }
.map-options-group[hidden] { display: none !important; }
#map.ae-drawing gmp-advanced-marker { pointer-events: none; }
/* while drawing, a finger on the map draws instead of scrolling or zooming the page (area_explorer.js) */
#map.ae-drawing, #map.ae-drawing * { touch-action: none; }
/* the Draw buttons, moved onto the map's top-left controls by area_explorer.js - hidden until then */
.ae-map-wrap > .ae-draw-controls { display: none; }
.ae-draw-controls { margin: 10px; background: var(--surface); border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,.3); }
/* the theme's own surface behind the buttons, not the Options group's see-through blue (inline, so !important) */
.ae-draw-controls .map-options-group { background-color: var(--surface) !important; }
/* ...where buttons.js's white-on/white-icon-off look vanishes: picked (or Clear while pressed) is solid
   blue, the rest a blue icon on the panel */
.ae-draw-controls .svg-icon-button[data-toggle-state='on'] { background-color: #0074d9 !important; color: #ffffff !important; }
.ae-draw-controls .svg-icon-button[data-toggle-state='off'] { background-color: transparent !important; color: #0074d9 !important; }
body:not(.light-mode) .ae-draw-controls .svg-icon-button[data-toggle-state='off'] { color: #5aa9ff !important; }
/* Area Results */
.ae-empty { color: var(--text-muted); text-align: center; padding: 10px; }
.ae-pct { font-weight: 600; }
.ae-pct--good { color: var(--good, #22c55e); }
.ae-pct--ok { color: var(--ok, #f59e0b); }
.ae-pct--poor { color: var(--poor, #ef4444); }
/* tower card's "Seen in N surveys" list and the survey card's buttons */
.ae-seen { max-height: 220px; overflow: auto; }
.ae-seen-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto auto; align-items: center; gap: 8px; padding: 6px 0; border-top: 1px solid var(--border); }
.ae-seen-row b { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
.ae-seen-row small { color: var(--text-muted); font-size: 11px; }
.ae-card-actions { display: grid; gap: 8px; margin-top: 10px; }
.ae-card-actions > * { width: 100%; box-sizing: border-box; justify-content: center; text-align: center; }
#ae_tabs.is-refreshing { opacity: .45; transition: opacity .15s ease; }
/* Advanced Overview (pages/area_explorer.php ae_render_advanced()) and the tower card's history line */
.ae-adv-table > tbody > tr > td { vertical-align: top; }
#advanced_overview .data-table small { color: var(--text-muted); font-weight: 400; }
.ae-adv-techs { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.ae-adv-change { font-weight: 700; }
.ae-adv-timeline { margin-top: 6px; }
.ae-adv-timeline summary { cursor: pointer; color: var(--accent); font-size: .78rem; font-weight: 700; }
.ae-adv-runs { margin-top: 6px; border-collapse: collapse; font-size: .78rem; }
.ae-adv-runs td { padding: 3px 12px 3px 0; border-top: 1px solid var(--border); white-space: nowrap; }
.ae-adv-foot { margin: 10px 0 0; color: var(--text-muted); font-size: .8rem; }
.ae-history { font-size: .8rem; line-height: 1.5; }
.ae-history small { color: var(--text-muted); }
.ae-history--inconsistent b { color: var(--poor, #ef4444); }
.ae-history--replanned b { color: var(--accent); }
/* a value seen in one survey only; its tip is summary-charts.js's floating tip (.has-tip) */
.ae-pill { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: .68rem; font-weight: 800; white-space: nowrap; vertical-align: 1px; cursor: help; }
.ae-pill--unconfirmed { color: var(--ok, #f59e0b); border: 1px solid currentColor; background: rgba(245,158,11,.1); }
.ae-tip-note { display: block; max-width: 240px; margin-top: 6px; color: var(--text-muted); white-space: normal; }

/* Wiki-driven pages (pages/help.php, sentry/index.php): section heads, topic accordions and the wiki's
   ::: blocks, rendered by help_markdown() in includes/functions.php. */
.sec-head { display: flex; align-items: end; justify-content: space-between; gap: 15px; margin: 10px 0 17px; }
.sec-head .eyebrow { display: block; color: var(--accent); font-size: 11px; font-weight: 900; letter-spacing: .16em; text-transform: uppercase; }
.sec-head h2 { margin: 2px 0 0; font-size: 22px; letter-spacing: -.02em; color: var(--text); }
.sec-head p { color: var(--text-secondary); margin: 5px 0 0; font-size: .85rem; }
/* <details class='topic'> accordions, one per topic, inside a render_boxed_card() with class 'topic-list' */
.topic-list .card-body { display: grid; gap: 12px; }
.topic { border: 1px solid var(--border); border-radius: 13px; background: rgba(var(--ink-rgb),.025); overflow: hidden; }
.topic[open] { border-color: rgba(var(--accent-rgb),.5); box-shadow: 0 8px 22px rgba(0,0,0,.18); }
.topic > summary { display: flex; align-items: center; gap: 15px; padding: 16px 18px; cursor: pointer; list-style: none; }
.topic > summary::-webkit-details-marker { display: none; }
.topic-number { flex: 0 0 35px; height: 35px; border-radius: 10px; background: rgba(var(--accent-rgb),.12); border: 1px solid rgba(var(--accent-rgb),.35); color: var(--accent); display: grid; place-items: center; font-weight: 900; font-size: 12px; }
.topic-heading { flex: 1; min-width: 0; }
.topic-heading strong { font-size: 15px; color: var(--text); }
.topic-heading small { display: block; margin-top: 2px; color: var(--text-secondary); font-size: 12px; }
.topic .chevron { font-size: 20px; color: var(--accent); transition: transform .15s; }
.topic[open] .chevron { transform: rotate(180deg); }
.topic-content { padding: 2px 22px 22px 68px; border-top: 1px solid var(--border); color: var(--text-soft); font-size: 13px; line-height: 1.75; }
.topic-content p { margin: 10px 0; }
.topic-content strong, .topic-content h3, .topic-content h4 { color: var(--text); }
.topic-content h3, .topic-content h4 { margin: 18px 0 9px; }
.topic-content ul, .topic-content ol { padding-left: 24px; margin: 10px 0 15px; }
.topic-content li { margin-bottom: 6px; line-height: 1.55; }
.topic-content blockquote { margin: 12px 0 18px; padding: 13px 16px; background: rgba(var(--accent-rgb),.07); border-left: 3px solid var(--accent); border-radius: 8px; }
.topic .topic-content a { color: var(--accent); text-decoration: underline; }
.topic-content code { padding: 2px 5px; background: rgba(var(--ink-rgb),.08); border-radius: 4px; }
.topic-content .table-wrapper { margin: 11px 0 14px; }
.callout { padding: 12px 14px; border: 1px solid rgba(var(--accent-rgb),.4); background: rgba(var(--accent-rgb),.08); border-radius: 11px; margin: 12px 0; }
.callout strong { color: var(--accent); }
.callout.amber { border-color: var(--ok); background: rgba(245,158,11,.1); }
.callout.amber strong { color: var(--ok); }
.callout p:first-child { margin-top: 0; }
.callout p:last-child { margin-bottom: 0; }
.help-tiles { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 10px; margin: 17px 0; }
.help-tiles small { display: block; color: var(--text-muted); font-size: 11px; line-height: 1.4; }
.help-flow { display: flex; align-items: center; flex-wrap: wrap; gap: 9px; margin: 17px 0; }
.help-flow .filter-chip { cursor: default; }
.help-flow .mdi { color: var(--accent); font-size: 18px; }
@media (max-width: 760px) {
	.sec-head { flex-direction: column; align-items: flex-start; }
	.topic-content { padding: 0 16px 16px; }
	.topic > summary { padding: 14px; gap: 12px; }
	.help-tiles { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
@media (max-width: 640px) { .help-tiles { grid-template-columns: 1fr; } }
@media print { .sec-head .filter-chip { display: none !important; } .topic { break-inside: avoid; } }
