/* =============================================================================
   CacheGenie NOC - the stylesheet
   Loaded after noc_tokens.css, which holds every colour and every measurement. Nothing
   in here may contain a hex literal: a colour that is not a token cannot follow a theme,
   and that is the single rule that makes dark mode a re-map rather than a bug hunt.

   This file was two files until 20 September 2026 (dashboard.css for "components",
   noc.css for "the frame"). The seam was fiction - noc.css carried 500 lines of
   components and dashboard.css reached forward for noc.css's tokens, so neither could
   load without the other - and it cost four !important declarations whose only job was
   to beat the other file. They are gone with it.

   The rule the panel is built on: COLOUR IS RESERVED FOR STATE. A page where nothing is
   wrong is ink, muted ink and hairlines. Depth is a hairline and two tones, never a
   shadow and never a gradient.

   Contents, in the order they appear. Each is a banner comment, so the number greps.
     1. Reset, base, type              8. The frame: top bar, sheet, footer, tab bar
     2. Boxes                          9. State, and the status page it came from
     3. Figures and tiles             10. Docs site chrome (body is docs_body.css)
     4. Form controls: the kit        11. Modals
        4a fields and their skins     12. Responsive
        4b buttons                    12b. Billing, then the later page sections (7)
        4c the icon button            13. Reduced motion
        4d checks and switches        14. The builders' sections: A, B, C, E, Z (the zone page), the lead
        4e the upload control
        4f search, group headings, the confirm dialog, the live chip, times
        4g the rich text editor (TinyMCE) in the panel's colours
     5. Notices
     6. Row tables and lists
     7. Page sections
   7 and 8 interleave: the page-specific blocks (zone, support, account, admin, nodes,
   backups, stats) start at 7, the frame sits among them at 8, and the rest follow it.
   Section 4 is the kit every page is built from (25 September 2026): functions/ui.php
   prints the markup of its composite parts, and a page that needs a control reaches for
   one of these before it writes a rule of its own.

   Breakpoints: phone <= 640px, tablet <= 900px, laptop <= 1200px, desktop above.
   ============================================================================= */

/* -----------------------------------------------------------------------------
   1. Reset, base, type
   -------------------------------------------------------------------------- */

/* Every box measures its own border and padding. Without this the panel needs a
   calc(100% - 2 * padding) on each container, which is what the thirteen width hacks
   in the old files were, and any padding change had to be made twice. */
*, *::before, *::after { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    height: auto;
    width: 100%;
    /* "hidden" turns these into scroll containers, which stops position: sticky
       (the docs sidebar) working. "clip" clips without doing that. */
    overflow-x: clip;
    position: relative;
    background-color: var(--noc-page);
    color: var(--noc-ink-2);
    font: var(--t-body);
}

* { -webkit-tap-highlight-color: transparent; }

/* A pointer click should not draw a ring; a keyboard user has to see where they are.
   This replaces a blanket `*:focus { outline: none }`, which took the focus ring off
   every control in the product, including the login form. The ring is the teal as
   ink (25 September 2026): the bright teal measured 2.31:1 against the white sheet,
   under the 3:1 a focus indicator needs, so in the light theme the ring was the
   faintest thing on a page it exists to point at. In the dark theme the two are
   the same colour. */
:focus { outline: none; }
:focus-visible {
    outline: 2px solid var(--noc-accent-ink);
    outline-offset: 2px;
    border-radius: var(--noc-radius-sm);
}

::selection {
	color: var(--noc-surface);
	background-color: var(--noc-ink);
}
::-moz-selection {
    color: var(--noc-surface);
	background-color: var(--noc-ink);
}

.dark_bg_select::selection {
	color: var(--noc-inverse);
	background-color: var(--noc-inverse-ink);
}
.dark_bg_select::-moz-selection {
    color: var(--noc-inverse);
	background-color: var(--noc-inverse-ink);
}

/* Every native checkbox, radio and range in the panel takes the primary button's colour, in both
   themes. Three of the ticks used to set the teal on themselves and the rest showed the browser's
   blue, so a ticked box was one colour in one popup and another in the next. It is not teal: teal
   means focus, hover or the current item, and a ticked box is none of the three. */
:root { accent-color: var(--noc-control-on); }

/* A link in running text used to carry three signals at once: bold, a brighter ink
   than the sentence around it, and a solid underline tight against the baseline. On
   a dark ground those three together read as a smear rather than a link. It wears the
   house device instead - the accent underline the docs body already uses - which is
   one signal, offset from the text, and it goes to full strength on hover. */
a, a:visited, a:active {
    color: var(--noc-ink);
    font-weight: 600;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: var(--noc-accent);
    text-underline-offset: 3px;
}
a:hover {
    color: var(--noc-ink);
    font-weight: 600;
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-decoration-color: var(--noc-accent);
    text-underline-offset: 3px;
}
/* An underline is for a link in a sentence. A link that wraps a control - and several
   here wrap a <button>, which is how "Manage" and the row actions are built - is the
   control, and the rule above was drawing a line straight through it. The button's own
   text-decoration: none cannot help, because the line belongs to the anchor's box. */
a:has(button), a:has(img), a:has(svg), a:has(i.fa-solid), a:has(.dashboard_form_button),
a.a_nostyle, a.a_nostyle:visited, a.a_nostyle:hover {
    text-decoration: none;
}

/* -----------------------------------------------------------------------------
   The logged-out shell: log in, 2FA, reset, verify, offline, and the logout confirm.
   No top bar and no tab bar - one card on the navy field. 320px was too narrow for a
   15px body and a 24px title; the fields were shorter than the words above them.
   -------------------------------------------------------------------------- */
.login_inner_cont {
    --login-pad-x: 28px;
    --login-pad-y: 28px;
    width: 100%;
    max-width: 380px;
    text-align: center;
    background-color: var(--noc-surface);
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    padding: var(--login-pad-y) var(--login-pad-x);
    margin: 72px auto;
}
/* The card sits in the middle of the field, its margin the least it keeps above and below. In plain flow the top
   margin collapsed through <body> (no padding or border to stop it), so the 100vh-high body was pushed 72px down:
   the page scrolled by 72px it did not have, and all of that, plus whatever height the card left, landed under
   the card. A flex body does not collapse margins, and centring shares the leftover height evenly. Every page that
   carries the card puts it straight in the body: log in, two-factor, reset, verify, log out, offline, pay, errors.
   svh so a phone's toolbar cannot push the centre below what is visible. */
body:has(> .login_inner_cont) {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 100vh;
    min-height: 100svh;
}

/* -----------------------------------------------------------------------------
   2. Boxes
   A box is a hairline and a title. The grey gradient bar this header used to carry
   (six vendor-prefixed lines for a four-percent ramp) was the panel's only idea of
   depth and the one thing that dated it most. Depth is now a hairline and, where a
   box genuinely needs two tones, a --noc-surface-2 body over a --noc-surface foot
   split by a rule: see .dbc_foot.
   The title is 15px where it used to be 20px bold. It is furniture; the data in the
   box is the content, and the weight belongs there.
   -------------------------------------------------------------------------- */

.dashboard_box_cont {
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    overflow: hidden;
    width: 100%;
    background: var(--noc-surface);
}

.dbc_header {
    font: var(--t-h3);
    letter-spacing: 0.01em;
    color: var(--noc-ink);
    cursor: default;
    padding: var(--sp-3) var(--noc-pad-box);
    min-height: 52px;
    display: flex;
    align-items: center;
    background: var(--noc-surface);
    border-bottom: 1px solid var(--noc-line);
}

.dbc_inner_content {
    padding: var(--noc-pad-box);
}
.dbch_split {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--sp-3);
}
.dbch_split .dashboard_form_button {
    margin-top: 0;
}
.dbc_margin {
    margin-top: var(--noc-gap);
}

.account_settings_form {
    display: flex;
    justify-content: space-between;
    /* stretch, not flex-start: the save button in one column aligns itself to the
       bottom of the pair, and a column that is only as tall as its own content gives
       margin-top: auto nothing to push against - the button then floats level with
       nothing at all. */
    align-items: stretch;
    margin-top: -10px;
}
.asf_left {
    width:50%;
    padding-right:10px;
}
.asf_right {
    width:50%;
    padding-left:10px;
}
.account_settings_form .dashboard_form_input_title {
    margin-top:10px;
    height:17px;
}
.asf_right .dashboard_form_button {
    margin-top:32px;
}
.asf_cont .notify_box {
    margin-top:10px;
    margin-bottom:20px;
}
.ads_cont2 {
    margin-top:-10px;
}
/* The type of these two, and of .asp_api_title and .dashboard_2fa_title, is the kit's group heading
   (section 4f). Only where each sits is said here. */
.ads_billing_title {
    margin-top:20px;
    margin-bottom:10px;
}
.asf_email_notice {
    font-size:12px;
    width: calc(50% - 10px);
    margin-top:5px;
}

.asp_api_row {
    width:100%;
    display:flex;
    justify-content: space-between;
    align-items: center;
}
.asp_api_title {
    margin-top:20px;
    margin-bottom:10px;
}
/* The API key is 64 unbroken characters, so it has to be allowed to wrap or it runs straight out of its box as
   soon as the sheet narrows: a fixed calc() width on the left column and no wrapping rule on the value was the
   whole of the bug. Same shape as the zone page's signing key row (.tok_key_row). */
.api_row_left {
    flex: 1 1 auto;
    min-width: 0;
}
.api_row_right {
    flex: 0 0 auto;
    width:120px;
    margin-left: 10px;
}
.api_key_holder {
    margin: 0px;
    padding: 9px 10px;
    border: 1px solid var(--noc-line);
    background-color: var(--noc-surface);
    color: var(--noc-ink-2);
    border-radius: var(--noc-radius);
    font: var(--t-mono);
    width: 100%;
    overflow-wrap: anywhere;
}

/* A label and its value, inset by the box's own padding so the labels line up under the box title (they sat 8px
   left of it at 10px until 25 September 2026) */
.dbs_info_row {
    width: 100%;
    display:flex;
    padding: 10px var(--noc-pad-box);
}
.dbs_info_row { border-top: 1px solid var(--noc-line); }
.dbs_info_row:first-child { border-top: 0; }
.dbs_info_row a {
    text-decoration: none;
    font-weight:normal;
}
.dbsir_left {
    width:65%;
}
.dbsir_right {
    width:35%;
}

/* -----------------------------------------------------------------------------
   3. Figures and tiles
   The one place the panel is allowed to be loud. A tile used to shout its label at
   22px bold and centre a 40px number under it; the label is furniture and the number
   is the content, so they have swapped register. Left-aligned, because a column of
   figures is read down, and tabular so the digits line up when they change.
   -------------------------------------------------------------------------- */

.dashboard_stats_overview {
    width: 100%;
    margin: var(--noc-gap) 0;
    cursor: default;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    overflow: hidden;
    background: var(--noc-surface);
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    position: relative;
}
.dso_item {
    width: auto;
    min-width: 0;
    padding: var(--sp-4) var(--noc-pad-box);
    border-left: 1px solid var(--noc-line);
    text-align: left;
}
.dso_item:first-child { border-left: 0; }
.dsoi_title {
    font: var(--t-micro);
    color: var(--noc-ink-muted);
    letter-spacing: 0.02em;
}
.dsoi_content {
    font: var(--t-figure);
    color: var(--noc-ink);
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    margin-top: 2px;
    overflow-wrap: anywhere;
}
/* The unit rides with the figure but is not part of it: 32px of "MB" is noise. */
.dsoi_unit {
    font: var(--t-sub);
    font-weight: 500;
    color: var(--noc-ink-muted);
    letter-spacing: 0;
    margin-left: 4px;
}
.dsoi_subtitle {
    font: var(--t-sub);
    color: var(--noc-ink-muted);
    margin-top: 4px;
    width: auto;
}

.account_page_title_cont {
    margin-bottom:20px;
    width:100%;
    display:flex;
    justify-content: space-between;
    align-items: center;
}
.aptc_menu a {
    text-decoration: none;
}
.aptc_title {
    font: var(--t-h1);
    letter-spacing: -0.01em;
    color: var(--noc-ink);
}

/* Two lockups, one shown. The dark wordmark is drawn for a white card and the light one
   for a dark card; which is right depends on a theme the server cannot always know at
   render time, so CSS picks rather than PHP. */
/* Two classes each, so these out-specify .login_logo's own display: block below.
   One class would lose the cascade to it and both marks would stack. */
.login_logo.login_logo_dark { display: none; }
:root[data-theme="dark"] .login_logo.login_logo_light { display: none; }
:root[data-theme="dark"] .login_logo.login_logo_dark { display: block; }
.login_logo {
    width:100%;
    max-width:230px;
    display:block;
    margin-left:auto;
    margin-right:auto;
}
.login_strap {
    font: var(--t-h1);
    font-size: 21px;
    letter-spacing: -0.01em;
    color: var(--noc-ink);
    margin-top: var(--noc-gap);
}
.error_strap {
    font-size:32px;
}
.login_form {
    width: 100%;
    text-align: left;
}
/* The first field sits directly under the title; the 20px the label normally carries
   would double the gap the title already leaves. */
.login_form .dashboard_form_input_title:first-child { margin-top: var(--sp-4); }

/* -----------------------------------------------------------------------------
   4. Form controls: the kit (25 September 2026)
   One control of each kind, drawn the same way on every page. functions/ui.php prints the markup of the composite
   ones (the upload control, the month select, the icon button, a confirm popup's action strip), loading.php carries
   the confirm dialog, and a page that needs a control reaches for one of these before it writes a rule of its own.
   The scale: a field, a select and a button are 40px tall (44px below 640px, where a thumb is the pointer), a small
   button 34px (40px), an icon button 32px (44px). Every size is border-box.
   -------------------------------------------------------------------------- */

/* ---- 4a. Fields and their skins ----------------------------------------------------------------------------- */

.dashboard_form_input_title {
    font: var(--t-micro);
    color: var(--noc-ink-muted);
    letter-spacing: 0.02em;
    margin-bottom: 5px;
    display: block;
    margin-top: var(--noc-gap);
}
/* The field. 40px with the text on its own 38px line, so a field, a select and a button in one row share an edge at
   the top and at the bottom. */
.dashboard_form_input {
    display: block;
    width: 100%;
    height: 40px;
    min-height: 0;
    margin: 0;
    padding: 0 10px;
    border: 1px solid var(--noc-line-2);
    background-color: var(--noc-surface);
    color: var(--noc-ink);
    border-radius: var(--noc-radius);
    font: var(--t-body);
    line-height: 38px;
    transition: border-color 150ms, box-shadow 150ms;
}
/* Muted rather than faint: a placeholder is text somebody has to read (the faint ink measured 2.65:1), and muted is
   still plainly not a value next to the ink a typed one is in. */
.dashboard_form_input::placeholder { color: var(--noc-ink-muted); opacity: 1; }
/* The border takes the teal as ink, which clears 3:1 against the sheet where the bright accent (2.31:1 in the light
   theme) did not, and the soft ring outside it says the same thing in the accent itself. */
.dashboard_form_input:focus {
    outline: none;
    border-color: var(--noc-accent-ink);
    box-shadow: 0 0 0 3px var(--noc-accent-ring);
}
textarea.dashboard_form_input {
    height: 150px;
    min-height: 150px;
    padding: 8px 10px;
    line-height: 1.45;
    resize: vertical;
}
/* Help text under a field. It used to run the full column width, which on the zone page made a wall of grey taller
   than the fields it explained. */
.dfi_input_sub, .asf_email_notice {
    font: var(--t-sub);
    color: var(--noc-ink-muted);
    max-width: var(--measure-help);
    margin-top: 6px;
}

/* A select wears the field's skin and one chevron, drawn in the muted ink so it follows the theme, with the same
   10px inset on the right that the text has on the left. The browser's own arrow was a different glyph in every
   browser and the one thing in a row of controls that did not change with the theme. Two gradients draw the two
   strokes because a data: image cannot read a token; a gradient here is a glyph, not depth. A list box (multiple,
   or a size) keeps the browser's own drawing. .dfi_select is the name the selects were written with, and asks for
   nothing more than any select gets. */
select.dashboard_form_input, .dfi_select { width: 100%; line-height: normal; }
select.dashboard_form_input:not([multiple]):not([size]) {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    padding: 0 32px 0 10px;
    text-overflow: ellipsis;
    background-image:
        linear-gradient(45deg, transparent calc(50% - 1.1px), var(--noc-ink-muted) calc(50% - 0.5px), var(--noc-ink-muted) calc(50% + 0.5px), transparent calc(50% + 1.1px)),
        linear-gradient(135deg, transparent calc(50% - 1.1px), var(--noc-ink-muted) calc(50% - 0.5px), var(--noc-ink-muted) calc(50% + 0.5px), transparent calc(50% + 1.1px));
    background-size: 6px 6px, 6px 6px;
    background-position: right 16px center, right 10px center;
    background-repeat: no-repeat;
}

/* A date keeps the browser's calendar, in the field's type and at its height. The picker's icon follows the theme
   (color-scheme is set on :root) and is the pointer it looks like. A month is never one of these: UI_MONTH_SELECT()
   prints it as a select of named months, because Firefox and desktop Safari draw type="month" as a bare text box. */
.dashboard_form_input[type="date"], .dashboard_form_input[type="month"],
.dashboard_form_input[type="time"], .dashboard_form_input[type="datetime-local"] {
    line-height: normal;
    font-variant-numeric: tabular-nums;
}
.dashboard_form_input::-webkit-calendar-picker-indicator {
    cursor: pointer;
    opacity: 0.75;
    padding: 3px;
    border-radius: var(--noc-radius-sm);
}
.dashboard_form_input::-webkit-calendar-picker-indicator:hover {
    opacity: 1;
    background-color: var(--noc-surface-3);
}
/* A number is typed, not nudged: the two tiny arrows were a target nobody could hit and a scroll wheel over the
   field changed the value under the reader. */
.dashboard_form_input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.dashboard_form_input[type="number"]::-webkit-inner-spin-button,
.dashboard_form_input[type="number"]::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
/* A search field is a field: no rounded pill in Safari, no magnifier of the browser's own (the kit draws one, 4f) */
.dashboard_form_input[type="search"] { -webkit-appearance: none; appearance: none; }
.dashboard_form_input[type="search"]::-webkit-search-decoration,
.dashboard_form_input[type="search"]::-webkit-search-results-decoration { -webkit-appearance: none; }
.dashboard_form_input[type="search"]::-webkit-search-cancel-button { cursor: pointer; }
/* Autofill paints its own pale yellow or blue under a remembered value, which in the dark theme is a pale slab with
   pale ink on it. An inset shadow the size of the field is the one paint that goes over it, so the field keeps its
   own two tones (this is paint, not depth). Kept out of a list with :autofill, which a browser that does not know
   it would take the whole rule down with. */
.dashboard_form_input:-webkit-autofill,
.dashboard_form_input:-webkit-autofill:hover {
    -webkit-text-fill-color: var(--noc-ink);
    caret-color: var(--noc-ink);
    -webkit-box-shadow: 0 0 0 1000px var(--noc-surface-2) inset;
    box-shadow: 0 0 0 1000px var(--noc-surface-2) inset;
}
.dashboard_form_input:-webkit-autofill:focus {
    -webkit-text-fill-color: var(--noc-ink);
    -webkit-box-shadow: 0 0 0 1000px var(--noc-surface-2) inset, 0 0 0 3px var(--noc-accent-ring);
    box-shadow: 0 0 0 1000px var(--noc-surface-2) inset, 0 0 0 3px var(--noc-accent-ring);
}
.dashboard_form_input:autofill { filter: none; }
/* A disabled field: the inset tone, with the value still readable (the muted ink, not the faint one) */
.dashboard_form_input:disabled {
    color: var(--noc-ink-muted);
    -webkit-text-fill-color: var(--noc-ink-muted);
    background-color: var(--noc-surface-2);
    border-color: var(--noc-line);
    cursor: not-allowed;
    opacity: 1;
}

/* A file input keeps the field's edge and lets its content decide the height, since the browser draws a button of
   its own inside it and the 40px text line clipped that. The button inside is the secondary button at the small
   size, nested in the field, so it takes the small corner.
   The upload control (4e) is the one to reach for; this is the field it starts as before its script has run. */
input[type="file"].dashboard_form_input,
.noc_upload:not(.nu_ready) .nu_input {
    display: block;
    width: 100%;
    height: auto;
    min-height: 40px;
    padding: 5px;
    border: 1px solid var(--noc-line-2);
    border-radius: var(--noc-radius);
    background-color: var(--noc-surface);
    color: var(--noc-ink-2);
    font: var(--t-body);
    line-height: normal;
    cursor: pointer;
}
input[type="file"].dashboard_form_input::file-selector-button,
.nu_input::file-selector-button {
    margin: 0 12px 0 0;
    min-height: 28px;
    padding: 4px 12px;
    border: 1px solid var(--noc-line-2);
    border-radius: var(--noc-radius-sm);
    background-color: var(--noc-surface-2);
    color: var(--noc-ink);
    font: var(--t-micro);
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
    transition: background-color 150ms, border-color 150ms;
}
input[type="file"].dashboard_form_input::file-selector-button:hover,
.nu_input::file-selector-button:hover {
    background-color: var(--noc-surface-3);
    border-color: var(--noc-ink-muted);
}

/* ---- 4b. Buttons ---------------------------------------------------------------------------------------------
   Three kinds and one size below the standard. The PRIMARY (the navy fill, indigo in the dark theme) is the action
   a box or a popup exists for, and a strip carries one of them, at its end. The SECONDARY is the same shape with no
   fill: everything else in the strip (Cancel, Add, Preview, Check again). The DANGER is the secondary drawn in the
   error colour, for an action that removes or ends something, and it fills only under the pointer, which is the
   moment it is about to be pressed. A disabled button of any kind is the inset tone with faint ink. */
.dashboard_form_button {
    display: block;
    width: 100%;
    min-height: 40px;
    margin: 0;
    margin-top: var(--noc-gap);
    padding: 8px 0;
    border: 1px solid var(--noc-btn-bg);
    border-radius: var(--noc-radius);
    background-color: var(--noc-btn-bg);
    color: var(--noc-btn-ink);
    font: var(--t-h3);
    cursor: pointer;
    transition: background-color 150ms, border-color 150ms, color 150ms;
}
.dashboard_form_button:hover {
    background-color: var(--noc-btn-bg-hover);
    border-color: var(--noc-btn-bg-hover);
}
.dashboard_form_button i {
    margin-right: 5px;
}
/* As wide as its words: the shape every strip and every header uses */
.dfb_auto_button {
    width: auto;
    margin-top: 0;
    padding: 8px 16px;
}
/* The one small size, for a button inside a row, a header or a readout: 34px. It used to be three (13px, 14px
   beside an auto button, and a padding of its own in each), which is how two small buttons in one header came out
   at different sizes. */
.dashboard_form_button.dfb_small {
    min-height: 34px;
    padding: 5px 12px;
    font: var(--t-micro);
    font-size: 13px;
    font-weight: 700;
}
/* A link drawn as a button keeps the button's ink and weight whatever the link rules say: the global a:visited and
   a:hover would give it the sheet's ink, an underline and a lighter weight, so its words changed width under the
   pointer. */
a.dashboard_form_button, a.dashboard_form_button:visited, a.dashboard_form_button:hover, a.dashboard_form_button:active {
    color: var(--noc-btn-ink);
    text-decoration: none;
    font-weight: 700;
}
a.dashboard_form_button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* The secondary. .bkp_help_button, .node_key_button and .da_preview_button are the names it was born with on three
   pages, kept so those pages are untouched; a page writing a new one says .dfb_secondary. The closing button of a
   popup's yes/no row (the type="button" one: No, Cancel, Keep it) is a secondary too, whatever it was written as. */
.dashboard_form_button.dfb_secondary,
.dashboard_form_button.bkp_help_button, .dashboard_form_button.node_key_button, .dashboard_form_button.da_preview_button,
.dfb_middle_yesno > .dashboard_form_button[type="button"] {
    background-color: transparent;
    border-color: var(--noc-line-2);
    color: var(--noc-ink);
}
.dashboard_form_button.dfb_secondary:hover,
.dashboard_form_button.bkp_help_button:hover, .dashboard_form_button.node_key_button:hover, .dashboard_form_button.da_preview_button:hover,
.dfb_middle_yesno > .dashboard_form_button[type="button"]:hover {
    background-color: var(--noc-surface-2);
    border-color: var(--noc-ink-muted);
    color: var(--noc-ink);
}
a.dashboard_form_button.dfb_secondary, a.dashboard_form_button.dfb_secondary:visited,
a.dashboard_form_button.dfb_secondary:hover, a.dashboard_form_button.dfb_secondary:active {
    color: var(--noc-ink);
}

/* The danger. At rest it is the error colour as ink on the sheet (4.75:1), and under the pointer the down fill with
   white on it (4.64:1 in both themes). Its ground is the sheet's own colour rather than transparent: on the inset tone
   the same ink measured 4.43:1, under the 4.5:1 its words need, and "never put it on one" was a rule nothing held. */
.dashboard_form_button.dfb_danger {
    background-color: var(--noc-surface);
    border-color: var(--st-error);
    color: var(--st-error);
}
.dashboard_form_button.dfb_danger:hover {
    background-color: var(--st-down);
    border-color: var(--st-down);
    color: var(--st-ink-on-fill);
}
a.dashboard_form_button.dfb_danger, a.dashboard_form_button.dfb_danger:visited, a.dashboard_form_button.dfb_danger:active {
    color: var(--st-error);
}
a.dashboard_form_button.dfb_danger:hover { color: var(--st-ink-on-fill); }

/* Disabled, after the three kinds so it wins over each of their hovers. A button the loading guard disabled while its
   form posts (loading.php adds .is_busy) keeps its own look: it is working, not unavailable. A link cannot be
   disabled, so one that says aria-disabled="true" is drawn the same and takes no pointer. */
.dashboard_form_button:disabled:not(.is_busy),
.dashboard_form_button:disabled:not(.is_busy):hover,
.dashboard_form_button[aria-disabled="true"],
.dashboard_form_button[aria-disabled="true"]:hover {
    background-color: var(--noc-surface-2);
    border-color: var(--noc-line);
    color: var(--noc-ink-faint);
    cursor: not-allowed;
}
a.dashboard_form_button[aria-disabled="true"], a.dashboard_form_button[aria-disabled="true"]:visited,
a.dashboard_form_button[aria-disabled="true"]:hover {
    color: var(--noc-ink-faint);
    pointer-events: none;
}
.dashboard_form_button.is_busy { cursor: progress; }

.api_button {
    margin:0px;
    padding:8px 12px;
    width:100%;
}

/* ---- 4c. The icon button -------------------------------------------------------------------------------------
   A control that is only an icon: 32px square (44px below 640px), the icon at 16px in the ink of the text around
   it, and under the pointer the raised tone behind it. UI_ICON_BUTTON() prints the real thing (.noc_icon_btn: a
   <button> that carries its label); the row actions and the popup close controls written before the kit
   (.row_action_edit, .row_action_delete, .row_action_disabled, .npb_close) are drawn the same way
   whatever element they are, and a <form> wearing a row action class lays its own button out in the row in its
   place. A div with a data-act cannot take the keyboard focus, so a page moving to the kit makes it a <button>. */
form.row_action_edit, form.row_action_delete, form.row_action_disabled { display: contents; }
.noc_icon_btn, .npb_close,
.row_action_edit:not(form), .row_action_delete:not(form), .row_action_disabled:not(form) {
    -webkit-appearance: none;
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: var(--noc-radius);
    background-color: transparent;
    color: var(--noc-ink-2);
    font: inherit;
    font-size: 16px;
    font-weight: 400;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 150ms, color 150ms;
}
.noc_icon_btn:hover, .npb_close:hover,
.row_action_edit:not(form):hover, .row_action_delete:not(form):hover {
    background-color: var(--noc-surface-3);
    color: var(--noc-ink);
}
/* An icon that removes or ends something says so under the pointer: the error colour on its own tint (4.03:1 light,
   4.08:1 dark, over the 3:1 a graphic needs). */
.noc_icon_btn.is_danger:hover, .row_action_delete:not(form):hover {
    background-color: var(--st-error-soft);
    color: var(--st-error);
}
/* Not available: the faint ink, no hover, and the pointer says why nothing will happen (its title says what) */
.noc_icon_btn:disabled, .noc_icon_btn:disabled:hover,
.row_action_disabled:not(form), .row_action_disabled:not(form):hover {
    background-color: transparent;
    color: var(--noc-ink-faint);
    cursor: not-allowed;
}
/* The close control of a popup is the kit's plain cross, whatever glyph the markup names (forty-four of them still
   say the circled one), and in a header it gives back the room its target takes, so the header keeps its height and
   the cross lines up with the box's padding. */
.npb_close .fa-circle-xmark::before { content: "\f00d"; }
.dbc_header .npb_close { margin: -6px -8px -6px 0; }
/* In a list row the 32px target would make every one-line row taller than the 48px it is, so it takes the room of
   24px and lets the rest of the target lie over the row's own padding. */
.row_actions .noc_icon_btn,
.row_actions .row_action_edit:not(form), .row_actions .row_action_delete:not(form), .row_actions .row_action_disabled:not(form) {
    margin-top: -4px;
    margin-bottom: -4px;
}

/* ---- 4d. Checks and switches ---------------------------------------------------------------------------------
   A tick is the browser's own checkbox in the primary colour (the accent-color on :root, section 1) at 16px, beside
   a label the whole of which is the target, lined up with the label's first line however many lines the label runs
   to. .noc_check is the kit's name; the four the pages wrote one at a time answer to it too, and keep only where
   they sit. */
.noc_check, .bill_check, .acc_evidence_check, .cred_check, .stv_check {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font: var(--t-body);
    color: var(--noc-ink);
    cursor: pointer;
}
.noc_check input, .bill_check input, .acc_evidence_check input, .cred_check input, .stv_check input {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    margin: 3px 0 0;
    cursor: pointer;
}
.noc_check input:disabled, .bill_check input:disabled, .acc_evidence_check input:disabled,
.cred_check input:disabled, .stv_check input:disabled { cursor: not-allowed; }
.noc_check:has(input:disabled), .bill_check:has(input:disabled) { color: var(--noc-ink-muted); cursor: not-allowed; }

/* The switch: a setting that is on or off and takes effect as it is pressed. The checkbox lies invisibly over the
   track, so it is what the pointer, the keyboard and a screen reader reach - it used to be display: none, which took
   every switch in the panel out of the tab order - and its focus ring is drawn on the track. On is the primary colour
   and never the teal, with a white knob. A page adds role="switch" to the checkbox so it is announced as one. */
/* block, not inline-block: an inline-block sits on the text baseline, so the line box reserves descender space under
   it and a flex row centres the line rather than the switch, which put every toggle two or three pixels above the
   middle of its row. */
.toggle-switch, .toggle-switch-small {
    display: block;
    position: relative;
    flex: 0 0 auto;
}
.toggle-switch { width: 60px; height: 30px; }
.toggle-switch-small { width: 40px; height: 20px; }
.toggle-switch input[type="checkbox"], .toggle-switch-small input[type="checkbox"] {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    opacity: 0;
    cursor: pointer;
}
.toggle-switch input[type="checkbox"]:disabled, .toggle-switch-small input[type="checkbox"]:disabled { cursor: not-allowed; }
/* Off is a pale track under a hairline with the knob in the muted ink at the start; on is the primary fill with a
   white knob at the end. The off knob is what makes an off switch visible at all: it was a white disc on a pale grey
   pill, 1.55:1 against the sheet, and the muted knob measures 4.83:1 against the sheet and 4.24:1 against its track
   (5.68:1 and 4.48:1 in the dark theme). The knob changes colour as well as place, so the state never rests on the
   position alone. */
.slider, .slider-small {
    position: absolute;
    inset: 0;
    cursor: pointer;
    background-color: var(--noc-surface-3);
    border: 1px solid var(--noc-line-2);
    transition: background-color 200ms, border-color 200ms;
}
.slider { border-radius: 30px; }
.slider-small { border-radius: 20px; }
.slider:before, .slider-small:before {
    position: absolute;
    content: "";
    background-color: var(--noc-ink-muted);
    border-radius: 50%;
    transition: transform 200ms, background-color 200ms;
}
/* Inside the track's 1px edge, so 2px (1px on the small one) plus the edge is the 3px (2px) it always sat in */
.slider:before { height: 24px; width: 24px; left: 2px; bottom: 2px; }
.slider-small:before { height: 16px; width: 16px; left: 1px; bottom: 1px; }
input:checked + .slider, input:checked + .slider-small {
    background-color: var(--noc-control-on);
    border-color: var(--noc-control-on);
}
input:checked + .slider:before, input:checked + .slider-small:before {
    background-color: var(--noc-btn-ink);
}
input:checked + .slider:before { transform: translateX(30px); }
input:checked + .slider-small:before { transform: translateX(20px); }
input:focus-visible + .slider, input:focus-visible + .slider-small {
    outline: 2px solid var(--noc-accent-ink);
    outline-offset: 2px;
}
/* A switch that cannot be pressed now is faded as a whole, as .toggle_disabled on the label already draws one (Force
   SSL before a certificate): a disabled checkbox inside it is the same state, and without this it looked exactly like
   a switch that is merely off. Both land on the label, so a page that says it both ways is faded once, not twice. */
.toggle-switch:has(input:disabled), .toggle-switch-small:has(input:disabled) { opacity: 0.45; }

/* ---- 4e. The upload control ----------------------------------------------------------------------------------
   UI_UPLOAD() prints it and assets/js/noc_upload.js drives it. Before the script has run it is the file field
   above (4a); once it has (.nu_ready) the real input lies invisibly over a drop area, so a click anywhere in the
   area, a drop and the keyboard all reach the input, and the area draws the field's focus ring. Each chosen file is
   a chip with its size and a remove button; a refusal is a line in the error colour under the area, never an
   alert. */
.noc_upload { min-width: 0; }
.nu_drop { position: relative; }
.nu_prompt { display: none; }
.noc_upload.nu_ready .nu_drop {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 88px;
    padding: var(--sp-4);
    border: 1px dashed var(--noc-line-2);
    border-radius: var(--noc-radius);
    background-color: var(--noc-surface-2);
    text-align: center;
    transition: border-color 150ms, background-color 150ms, box-shadow 150ms;
}
.noc_upload.nu_ready .nu_input {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}
.noc_upload.nu_ready .nu_prompt {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    font: var(--t-body);
    color: var(--noc-ink-2);
    pointer-events: none;
}
.nu_icon { font-size: 18px; color: var(--noc-ink-muted); }
.nu_browse {
    font-weight: 700;
    color: var(--noc-ink);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: var(--noc-accent);
    text-underline-offset: 3px;
}
/* Pointed at, dropped on, or holding the focus: the edge takes the teal as ink, which is what hover and focus mean */
.noc_upload.nu_ready .nu_drop:hover,
.noc_upload.is_dragover .nu_drop {
    border-style: solid;
    border-color: var(--noc-accent-ink);
    background-color: var(--noc-surface-3);
}
.noc_upload.nu_ready .nu_drop:focus-within {
    border-style: solid;
    border-color: var(--noc-accent-ink);
    box-shadow: 0 0 0 3px var(--noc-accent-ring);
}
.noc_upload.is_invalid .nu_drop { border-color: var(--st-error); }
/* A single file chosen: the area makes room for the chip under it and offers a different one */
.noc_upload.nu_ready.has_files .nu_drop { min-height: 56px; }
.noc_upload.has_files .nu_icon { display: none; }
.nu_hint {
    margin-top: 6px;
    font: var(--t-sub);
    color: var(--noc-ink-muted);
}
.nu_hint:empty { display: none; }
.nu_current {
    margin-top: 6px;
    font: var(--t-sub);
    color: var(--noc-ink-2);
    overflow-wrap: anywhere;
}
.nu_current i { margin-right: 4px; color: var(--noc-ink-muted); }
.nu_list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 0;
    padding: 0;
    list-style: none;
}
.nu_list:not(:empty) { margin-top: 10px; }
.nu_chip {
    display: flex;
    align-items: center;
    gap: 10px;
    min-height: 42px;
    padding: 4px 4px 4px 12px;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    background-color: var(--noc-surface);
}
.nu_chip_icon { flex: 0 0 auto; color: var(--noc-ink-muted); }
.nu_name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font: var(--t-body);
    color: var(--noc-ink);
}
.nu_size {
    flex: 0 0 auto;
    font: var(--t-sub);
    color: var(--noc-ink-muted);
    font-variant-numeric: tabular-nums;
}
.nu_error {
    margin-top: 8px;
    font: var(--t-sub);
    color: var(--st-error);
    overflow-wrap: anywhere;
}
.nu_error[hidden] { display: none; }

/* ---- 4f. Search, group headings, the confirm dialog, the live chip, times --------------------------------- */

/* One search field for the lists staff search (Customers, Zones, Audit, System's log): the field with the magnifier
   inside it. It is a field that says what it is before anything is typed, not a form with a button beside it: Enter
   searches, and the placeholder says so. (The docs hub has its own, larger one, .docs_search.) */
.noc_search { position: relative; }
.noc_search_icon {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 14px;
    color: var(--noc-ink-muted);
    pointer-events: none;
}
.noc_search .dashboard_form_input { padding-left: 36px; }

/* The heading of a group of fields inside a box: one weight, one size, one colour, for the six the pages write, each
   keeping only where it sits. (.ads_company_title, Settings' "Company Details" and "User Details", was left out of the
   list and read as plain body text beside a bold "Address" until 25 September 2026.) */
.ads_company_title, .ads_billing_title, .asp_api_title, .dashboard_2fa_title, .s3_group_title, .blk_group_title {
    font: var(--t-strong);
    color: var(--noc-ink);
}
/* The first thing in a box needs no gap above it: the box's padding is already there */
:is(.dbc_inner_content, .dbc_content_pad, .npb_content) > :is(.ads_company_title, .ads_billing_title, .asp_api_title, .dashboard_2fa_title):first-child {
    margin-top: 0;
}

/* The confirm dialog (loading.php): a modal of the one shell (section 11), above any other because it can open over
   one. The sentence is body text, left-aligned, and the strip at its foot is the save strip every form ends on. */
#noc_confirm_popup_wrap { z-index: 65; }
#noc_confirm_popup { z-index: 66; }
.noc_confirm_text {
    margin: 0;
    font: var(--t-body);
    color: var(--noc-ink-2);
    overflow-wrap: anywhere;
}

/* The live chip (assets/js/noc_live.js): one line at the foot of the window when something changed that the page
   cannot show in place, with the reload beside it. A readout like the loading badge - a hairline on the sheet's
   surface, no shadow - edged in amber when it is something to act on (a session or a view that has ended). Hidden
   by opacity and not by visibility, so it stays a live region a screen reader is listening to before its words
   arrive; the button in it is hidden properly until then, so it is never in the tab order unseen. */
.noc_live_chip {
    position: fixed;
    left: 50%;
    bottom: calc(16px + env(safe-area-inset-bottom));
    z-index: 55;
    display: flex;
    align-items: center;
    gap: 12px;
    width: max-content;
    max-width: calc(100vw - (var(--noc-gutter) * 2));
    padding: 5px 5px 5px 14px;
    background-color: var(--noc-surface);
    border: 1px solid var(--noc-line-2);
    border-radius: var(--noc-radius);
    font: var(--t-body);
    color: var(--noc-ink);
    opacity: 0;
    transform: translate(-50%, 8px);
    pointer-events: none;
    transition: opacity 0.16s ease, transform 0.16s ease;
}
.noc_live_chip.is_shown {
    opacity: 1;
    transform: translate(-50%, 0);
    pointer-events: auto;
}
.noc_live_chip[data-message]:not([data-message="Updated"]) { border-left: 3px solid var(--st-warn); }
.nlc_text { min-width: 0; overflow-wrap: anywhere; }
.nlc_reload {
    flex: 0 0 auto;
    min-height: 34px;
    padding: 5px 12px;
    border: 1px solid var(--noc-btn-bg);
    border-radius: var(--noc-radius);
    background-color: var(--noc-btn-bg);
    color: var(--noc-btn-ink);
    font: var(--t-micro);
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
    visibility: hidden;
    transition: background-color 150ms, border-color 150ms;
}
.nlc_reload:hover {
    background-color: var(--noc-btn-bg-hover);
    border-color: var(--noc-btn-bg-hover);
}
.noc_live_chip.is_shown .nlc_reload { visibility: visible; }

/* A relative time (UI_TIME(), kept true by noc_live.js): its digits keep their width as they count, and it never
   breaks across a line */
.noc_time {
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* ---- 4g. The rich text editor (TinyMCE) in the panel's colours -----------------------------------------------
   Admin > Docs and Admin > Status's incident editor. CSS only: the skin (oxide, or oxide-dark in the dark theme)
   stays, and these re-colour its frame, toolbar, menus and dialogs from the tokens, so the editor is the panel's
   and follows the theme. The skin's stylesheet loads after this file, so each rule starts :root to outrank the
   skin's own. The area that is typed in stays light, from content_css (docs_body.css), because what is written in
   it is published on pages that always are. The editor's branding and its upgrade prompt are the page's config
   (branding: false, promotion: false), not this file's. */
:root .tox-tinymce {
    border: 1px solid var(--noc-line-2);
    border-radius: var(--noc-radius);
}
:root .tox .tox-editor-header {
    border-bottom: 1px solid var(--noc-line);
    box-shadow: none;
}
:root .tox .tox-toolbar-overlord { background-color: var(--noc-surface); }
:root .tox .tox-toolbar, :root .tox .tox-toolbar__overflow, :root .tox .tox-toolbar__primary {
    background-color: var(--noc-surface);
    background-image: repeating-linear-gradient(var(--noc-line) 0 1px, transparent 1px 39px);
}
:root .tox .tox-tbtn {
    border-radius: var(--noc-radius-sm);
    color: var(--noc-ink-2);
}
:root .tox .tox-tbtn svg { fill: var(--noc-ink-2); }
:root .tox .tox-tbtn:hover {
    background: var(--noc-surface-2);
    color: var(--noc-ink);
}
:root .tox .tox-tbtn:hover svg { fill: var(--noc-ink); }
:root .tox .tox-tbtn--enabled, :root .tox .tox-tbtn--enabled:hover {
    background: var(--noc-surface-3);
    color: var(--noc-ink);
}
:root .tox .tox-tbtn--enabled svg, :root .tox .tox-tbtn--enabled:hover svg { fill: var(--noc-ink); }
:root .tox .tox-edit-area::before {
    border-color: var(--noc-accent-ink);
    border-radius: var(--noc-radius-sm);
}
:root .tox .tox-statusbar {
    background-color: var(--noc-surface);
    border-top: 1px solid var(--noc-line);
    color: var(--noc-ink-muted);
}
:root .tox .tox-statusbar a, :root .tox .tox-statusbar__path-item, :root .tox .tox-statusbar__wordcount {
    color: var(--noc-ink-muted);
}
:root .tox .tox-menu {
    background-color: var(--noc-surface);
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    box-shadow: none;
}
:root .tox .tox-collection--list .tox-collection__item { color: var(--noc-ink); }
:root .tox .tox-collection--list .tox-collection__item--active {
    background-color: var(--noc-surface-3);
}
:root .tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled) {
    color: var(--noc-ink);
}
:root .tox .tox-dialog {
    background-color: var(--noc-surface);
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    box-shadow: none;
}
:root .tox .tox-dialog__header, :root .tox .tox-dialog__footer { background-color: var(--noc-surface); }
:root .tox .tox-button:not(.tox-button--secondary):not(.tox-button--naked) {
    background-color: var(--noc-btn-bg);
    border-color: var(--noc-btn-bg);
    color: var(--noc-btn-ink);
    border-radius: var(--noc-radius);
}
:root .tox .tox-button:not(.tox-button--secondary):not(.tox-button--naked):hover:not(:disabled) {
    background-color: var(--noc-btn-bg-hover);
    border-color: var(--noc-btn-bg-hover);
}
:root .tox .tox-button--secondary {
    background-color: transparent;
    border-color: var(--noc-line-2);
    color: var(--noc-ink);
    border-radius: var(--noc-radius);
}
:root .tox .tox-button--secondary:hover:not(:disabled) {
    background-color: var(--noc-surface-2);
    border-color: var(--noc-ink-muted);
    color: var(--noc-ink);
}

/* ---- 4h. The one-time code (25 September 2026) ---------------------------------------------------------------
   UI_OTP() and assets/js/noc_otp.js. Six boxes, one digit each, in two groups of three because that is how every
   authenticator app prints a code ("482 913"), so what is typed looks like what is read off the phone. The digits
   are the one large thing on a sign-in card. A box shrinks on a narrow phone rather than wrapping, and keeps its
   height, so the six always sit on one line. The caret shows in an empty box, centred, which is what says where the
   next digit goes; a filled box takes a firmer edge; a refused code turns every edge and the line under the boxes
   to the error colour and shakes the row once; and while the code is checked the edges take the teal ink one after
   another, left to right (the loading rail's colour, for the same reason). */
.otp {
    margin-top: var(--sp-5);
}
/* The prompt belongs to the card's heading, so it is centred like it, and balanced so a narrow card does not leave
   one word on a line of its own */
.otp_prompt {
    font: var(--t-body);
    color: var(--noc-ink-2);
    margin: 0 0 var(--sp-4);
    text-align: center;
    text-wrap: balance;
}
.otp_boxes {
    display: flex;
    justify-content: center;
    gap: 18px;
}
.otp_group {
    display: flex;
    gap: 8px;
    min-width: 0;
}
.otp_digit {
    flex: 0 1 44px;
    width: 44px;
    min-width: 0;
    height: 56px;
    margin: 0;
    padding: 0;
    border: 1px solid var(--noc-line-2);
    border-radius: var(--noc-radius);
    background-color: var(--noc-surface);
    color: var(--noc-ink);
    font: var(--t-h1);
    font-size: 26px;
    line-height: 54px;
    font-variant-numeric: tabular-nums;
    text-align: center;
    caret-color: var(--noc-accent-ink);
    transition: border-color 150ms, box-shadow 150ms, background-color 150ms, color 150ms;
}
.otp_digit.has_digit {
    border-color: var(--noc-ink-muted);
}
.otp_digit:focus {
    outline: none;
    border-color: var(--noc-accent-ink);
    box-shadow: 0 0 0 3px var(--noc-accent-ring);
}
/* A digit selected on focus (so the next one typed replaces it) shows as the digit, not as a selection block */
.otp_digit::selection { background-color: transparent; color: var(--noc-ink); }
/* Empty, the line takes no room, so the card closes up under the boxes like any form; it stays in the page rather
   than display: none, which is what lets a screen reader hear "Checking the code" when it arrives */
.otp_status {
    margin: var(--sp-3) 0 0;
    font: var(--t-sub);
    color: var(--noc-ink-muted);
    text-align: center;
    text-wrap: balance;
}
.otp_status:empty { margin-top: 0; }
.otp_status i { margin-right: 4px; }
.otp.is_error .otp_digit { border-color: var(--st-error); }
.otp.is_error .otp_digit:focus { box-shadow: 0 0 0 3px var(--st-error-soft); }
.otp.is_error .otp_status { color: var(--st-error); }
.otp.is_error .otp_boxes { animation: otp_shake 380ms cubic-bezier(0.36, 0.07, 0.19, 0.97) 1; }
@keyframes otp_shake {
    15%, 85% { transform: translateX(-2px); }
    30%, 70% { transform: translateX(4px); }
    45% { transform: translateX(-6px); }
    60% { transform: translateX(6px); }
}
.otp.is_busy .otp_digit {
    border-color: var(--noc-accent-ink);
    background-color: var(--noc-surface-2);
    box-shadow: none;
}
.otp.is_busy .otp_group:first-child .otp_digit:nth-child(2) { transition-delay: 40ms; }
.otp.is_busy .otp_group:first-child .otp_digit:nth-child(3) { transition-delay: 80ms; }
.otp.is_busy .otp_group:last-child .otp_digit:nth-child(1) { transition-delay: 120ms; }
.otp.is_busy .otp_group:last-child .otp_digit:nth-child(2) { transition-delay: 160ms; }
.otp.is_busy .otp_group:last-child .otp_digit:nth-child(3) { transition-delay: 200ms; }

/* ---- 4i. Link rows (25 September 2026) ---------------------------------------------------------------------------
   UI_ROW(), UI_ROW_BUTTON() and UI_ROW_EMAIL(): a row that is one target. The icon sits in a small tile, the line
   above says when you would want the row and the line below is the action, in the ink, because the action is what
   an eye scans for; the chevron says the row goes somewhere. A link or a submit button, drawn the same. Hover lifts
   the row to the sheet's own colour, not to the tint: the muted line measured 4.21:1 on the tint and 4.83:1 on the
   sheet. The icon and the chevron take the teal ink on hover and focus, which is what teal is for. */
.noc_row {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    width: 100%;
    margin: 0;
    padding: 12px var(--noc-row-pad, var(--noc-pad-box));
    border: 0;
    background: none;
    color: var(--noc-ink);
    font: var(--t-body);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 150ms;
}
a.noc_row, a.noc_row:visited, a.noc_row:hover, a.noc_row:active {
    color: var(--noc-ink);
    font-weight: 400;
    text-decoration: none;
}
.noc_row_icon {
    flex: 0 0 32px;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    background-color: var(--noc-surface);
    color: var(--noc-ink-muted);
    font-size: 13px;
    transition: color 150ms, border-color 150ms;
}
.noc_row_text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
}
.noc_row_lead {
    font: var(--t-sub);
    line-height: 1.35;
    color: var(--noc-ink-muted);
}
.noc_row_action {
    font: var(--t-strong);
    line-height: 1.35;
    color: var(--noc-ink);
    overflow-wrap: anywhere;
}
.noc_row_go {
    flex: 0 0 auto;
    font-size: 11px;
    color: var(--noc-ink-muted);
    transition: color 150ms, transform 150ms;
}
.noc_row:hover { background-color: var(--noc-surface); }
.noc_row:hover .noc_row_icon, .noc_row:focus-visible .noc_row_icon { color: var(--noc-accent-ink); border-color: var(--noc-line-2); }
.noc_row:hover .noc_row_go, .noc_row:focus-visible .noc_row_go { color: var(--noc-accent-ink); transform: translateX(3px); }
.noc_row:focus-visible {
    outline: 2px solid var(--noc-accent-ink);
    outline-offset: -2px;
    border-radius: 0;
}

/* ---- The sign-in card (25 September 2026) ------------------------------------------------------------------------
   Log in, two-factor, set up two-factor, reset, and the logout confirm. The card's padding is two variables, so
   its foot can reach out to the card's edges at every width. The foot is a tray of link rows on the inset tone,
   under a hairline: where to go when this card is not the one you need. Each row's own top hairline divides it
   from the one above, and the first one's is the tray's edge. */
.login_lede {
    font: var(--t-body);
    color: var(--noc-ink-muted);
    max-width: 34ch;
    margin: var(--sp-2) auto 0;
    text-wrap: balance;
}
/* Which account the code is for. The app lists its entries by that address ("CacheGenie NOC (you@example.com)"),
   so it is what somebody with several looks for. */
.login_account {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    margin-top: var(--sp-3);
    padding: 3px 10px 3px 8px;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius-sm);
    background-color: var(--noc-surface-2);
    font: var(--t-sub);
    color: var(--noc-ink-2);
}
.login_account i { color: var(--noc-ink-muted); }
.login_account span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.login_foot {
    --noc-row-pad: var(--login-pad-x);
    margin: var(--sp-6) calc(-1 * var(--login-pad-x)) calc(-1 * var(--login-pad-y));
    background-color: var(--noc-surface-2);
    border-radius: 0 0 calc(var(--noc-radius) - 1px) calc(var(--noc-radius) - 1px);
    overflow: hidden;
    text-align: left;
}
.login_foot .noc_row { border-top: 1px solid var(--noc-line); }

/* The setup page's card is the phone card's width: the QR code and the key it stands for share a row, and at 380px
   the title and the key each broke onto a second line. */
.login_inner_cont.is_wide { max-width: 420px; }

/* The setup page's two steps. They are numbered because they are a sequence: the app has to know the key before
   it can show a code. The QR code sits beside the key it stands for, so the two ways to do step one read as two
   ways and not two steps. The QR code keeps its own white ground in both themes, as a code has to be scanned. */
.tfa_steps {
    margin-top: var(--sp-5);
    text-align: left;
}
.tfa_step + .tfa_step { margin-top: var(--sp-6); }
.tfa_step_head {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    font: var(--t-strong);
    color: var(--noc-ink);
}
.tfa_step_t { text-wrap: balance; }
.tfa_step_n {
    flex: 0 0 22px;
    width: 22px;
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background-color: var(--noc-surface-3);
    color: var(--noc-ink);
    font: var(--t-micro);
    font-weight: 700;
}
.tfa_scan {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    margin-top: var(--sp-3);
}
.tfa_qr {
    flex: 0 0 124px;
    width: 124px;
    height: 124px;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    overflow: hidden;
}
.tfa_qr img {
    display: block;
    width: 100%;
    height: 100%;
}
.tfa_key {
    flex: 1 1 auto;
    min-width: 0;
}
.tfa_key_label {
    font: var(--t-sub);
    color: var(--noc-ink-muted);
}
/* The key in groups of four, as it is read out and typed. The groups are spans with a gap between them and no
   spaces, so selecting the key by hand copies it whole. */
.tfa_key_value {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 10px;
    margin-top: 4px;
    font: var(--t-mono);
    font-size: 15px;
    letter-spacing: 0.04em;
    color: var(--noc-ink);
}
.tfa_key .dashboard_form_button { margin-top: var(--sp-3); }
.tfa_step .otp { margin-top: var(--sp-3); }

.cyp_button a {
    text-decoration: none;
}

/* -----------------------------------------------------------------------------
   5. Notices and the annotation rule
   These were centred, bold and pink or green: two Bootstrap-era alert colours doing
   four jobs, and shouting each one. A notice is now left-aligned body text behind a
   3px rule in the colour of what it is saying, which is the same left-rule device
   .noc_note, the docs blockquote and the status timeline already use.
   -------------------------------------------------------------------------- */

.notify_box {
    cursor: default;
    color: var(--noc-ink-2);
    width: 100%;
    text-align: left;
    padding: var(--sp-3) var(--sp-4);
    font: var(--t-body);
    margin: var(--noc-gap) 0;
    border-radius: 0 var(--noc-radius) var(--noc-radius) 0;
    border-left: 3px solid var(--st-pending);
    background: var(--noc-surface-2);
}
.notify_box b, .notify_box strong { font-weight: 700; color: var(--noc-ink); }
.notify_box a { font-weight: 700; }
/* .warning is the class 70 call sites already carry, and most of them are refusals
   rather than disasters, so it reads as attention and not alarm. */
.warning {
    border-left-color: var(--st-warn);
    background: var(--st-warn-soft);
}
.success {
    border-left-color: var(--st-ok);
    background: var(--st-ok-soft);
}
.notify_box.is_error, .dashboard_notify_box.dnb_error {
    border-left-color: var(--st-error);
    background: var(--st-error-soft);
}
/* .dashboard_notify_box existed in the markup with no rule anywhere, so a failed
   location save on Admin > Nodes printed as unstyled body text. */
.dashboard_notify_box {
    cursor: default;
    color: var(--noc-ink-2);
    width: 100%;
    text-align: left;
    padding: var(--sp-3) var(--sp-4);
    font: var(--t-body);
    margin: var(--sp-3) 0;
    border-radius: 0 var(--noc-radius) var(--noc-radius) 0;
    border-left: 3px solid var(--st-pending);
    background: var(--noc-surface-2);
}

.ads_company_title {
    margin-bottom:10px;
}

/* -----------------------------------------------------------------------------
   6. Row tables and lists
   Rows were separated by a zebra stripe and 20px of padding, which made a long table
   a stack of grey blocks. They are separated by a hairline now, and the header row
   states what the columns are in muted micro type rather than in bold black - a
   column heading is a label, not a finding.
   -------------------------------------------------------------------------- */

.bic_row_cont {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--sp-3) var(--noc-pad-box);
    min-height: 48px;
    cursor: default;
    border-top: 1px solid var(--noc-line);
}
/* No gap here. The column widths add up to exactly 100%, and each carries its own
   padding-right as the gutter; a flex gap on top of that is extra width the row does
   not have, and now that the columns hold their declared size rather than shrinking,
   the last one is pushed outside the sheet. */
/* A name with a quiet line under it is the commonest cell in the panel; give the
   pair its own rhythm rather than letting the two lines touch. */
.adm_name + .row_sub, .node_name + .node_sub, .bkp_name + .row_sub,
.adm_name + .node_sub { margin-top: 3px; }
.bic_row_cont:first-child { border-top: 0; }
.bic_row_title {
    font: var(--t-micro);
    color: var(--noc-ink-muted);
    letter-spacing: 0.02em;
    padding: 9px var(--noc-pad-box);
    border-top: 0;
    border-bottom: 1px solid var(--noc-line);
}
.bic_row_title + .bic_row_cont { border-top: 0; }
.bicr_column {
    width: 25%;
    min-width: 0;
    /* The declared width is the column, not a suggestion. Without this a row whose
       content happens to be wider - an indented child row, a long hostname - shrinks
       and grows its own columns, so the selects and figures in a table stop lining up
       from row to row. That is the "nothing lines up" of a flex table. */
    flex: 0 0 auto;
}
.bicr_column i {
    margin-left: 5px;
}
/* An icon inside a control is centred by the control; the 5px above is for an icon beside words in a cell, and in an
   icon button it pushed the glyph off centre. */
.bicr_column :is(.noc_icon_btn, .row_action_edit, .row_action_delete, .row_action_disabled, .dashboard_form_button) i {
    margin-left: 0;
}
.bicr_column a {
    text-decoration: none;
    font-weight: normal;
}
/* The name in a row, and the quiet line under it. That pair is repeated on nearly
   every list in the panel and used to be written out per page. */
.adm_name, .node_name, .bkp_name {
    font: var(--t-strong);
    color: var(--noc-ink);
}
.row_sub, .node_sub {
    font: var(--t-sub);
    color: var(--noc-ink-muted);
    overflow-wrap: anywhere;
}
.adm_column_3 {
    width:35%;
}
.adm_column_4 {
    width:15%;
}

.adm_column_1 {
    width:15%;
}
.adm_column_2 {
    width:35%;
}

/* No zebra. The hairline above does the separating, and an alternating fill on a
   page of tables reads as five different greys rather than one system. */
.bic_no_invoices {
    padding: var(--noc-pad-box);
    color: var(--noc-ink-muted);
}

.dopt_cont {
    margin-bottom: 20px;
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.dashboard_overview_page_title {
    font: var(--t-h1);
    letter-spacing: -0.01em;
    color: var(--noc-ink);
}

.noc_popup_box {
    width: 560px;
    max-width: 100%;
    margin: 0 auto var(--noc-gap);
    display: none;
}

.dbc_content {
    width:100%;
    background-color: var(--noc-surface);
}
/* The box's own padding, so the save strip at the foot of a popup (which reaches out by exactly that much) meets
   both edges. It was 20px against the strip's 18px, which left the strip two pixels short of each side. */
.npb_content {
    padding: var(--noc-pad-box);
    width:auto;
}
.dfit_notopmargin {
    margin-top:0px;
}
.dashboard_form_button_right {
    width:100%;
    display:flex;
    justify-content: right;
    margin-top:20px;
}
.dfi_input_sub {
    width:auto;
}
.npb_force_show {
    display:block;
}
.notify_box_notopmargin {
    margin-top:0px;
}

.zlist_titles {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    font: var(--t-body);
}
/* The zone list. The title track used to claim 48/15/37 while the cells claimed
   48/15/22/15, so "Cache Hit Rate" never sat over its own column. Both read 46/16/16/22
   now, and the figures are right-aligned and tabular, because a column of numbers is
   there to be read down. */
.zlist_c1_title, .zlist_c1 { width: 46%; min-width: 0; }
.zlist_c2_title, .zlist_c2 { width: 16%; }
.zlist_c3_title, .zlist_c3 { width: 16%; }
.zlist_c4_title, .zlist_c4 { width: 22%; }
.zlist_c2, .zlist_c3 {
    text-align: right;
    padding-right: var(--sp-6);
    font-variant-numeric: tabular-nums;
    color: var(--noc-ink);
}
.zlist_c2_title, .zlist_c3_title { text-align: right; padding-right: var(--sp-6); }
.zlist_c4 {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: var(--sp-2);
}
.zlist_c4 .dfb_small { width: auto; margin: 0; }
.zlist_name { font: var(--t-strong); color: var(--noc-ink); display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
/* The hostname under the name is the thing a customer copies, so it is set in the
   face they will paste it into rather than in the reading face. */
.zlist_sub {
    font: var(--t-mono);
    font-size: 12px;
    color: var(--noc-ink-muted);
    margin-top: 3px;
    overflow-wrap: anywhere;
}
.zlist_more { color: var(--noc-ink-muted); }
/* The unit rides with the figure and is not part of it. */
.zlist_unit { font: var(--t-sub); font-weight: 500; color: var(--noc-ink-muted); margin-left: 3px; }
.zlist_titles { display: flex; align-items: center; }
.zlist_titles:not(.zlist_row) {
    font: var(--t-micro);
    color: var(--noc-ink-muted);
    letter-spacing: 0.02em;
    padding: 0 var(--noc-pad-box) var(--sp-2);
}

.zlist_cont {
    margin-top: var(--sp-2);
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    overflow: hidden;
}
.zlist_row {
    font-weight: normal;
    padding: var(--sp-3) var(--noc-pad-box);
    border-top: 1px solid var(--noc-line);
    transition: background-color 150ms;
}
.zlist_row:first-child { border-top: 0; }
.zlist_row:hover { background: var(--noc-surface-2); }

.row_end_column {
    display:flex;
    justify-content: space-between;
    align-items: center;
}
/* The row's actions are icon buttons (section 4c) with 4px between them; the 20px bold glyphs and their hand-set
   margins went with the kit. */
.row_actions {
    display:flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--sp-1);
}

/* A popup's yes/no row. The closing button (the type="button" one: No, Cancel, Keep it) is the secondary and is drawn
   first, and the action it answers is the primary at the end, as in every other strip; a page that writes the
   closing button first in its markup (the kit's order, UI_CONFIRM_ACTIONS()) needs the order rule for nothing. The
   gap is a gap: a page that shows one of two submit buttons used to leave the old margin on the hidden one, and the
   two visible buttons touched. As the last thing in a popup's last form it is that popup's save strip (section 8). */
.dfb_middle_yesno {
    width: 100%;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: 10px;
    margin-top: var(--noc-gap);
}
.dfb_middle_yesno > .dashboard_form_button { margin: 0; }
.dfb_middle_yesno > .dashboard_form_button[type="button"] { order: -1; }
/* The question a confirm popup asks: body text, left-aligned like every other sentence in a box. It was 18px and
   centred, the one centred paragraph left in the panel, and a paragraph of consequences set that way was hard to
   read to the end. */
.popup_question {
    font: var(--t-body);
    color: var(--noc-ink-2);
    text-align: left;
    overflow-wrap: anywhere;
    cursor: default;
}
.popup_question b, .popup_question strong { color: var(--noc-ink); }

.dfi_ta {
    font-family: 'Maven Pro', sans-serif;
    color: var(--noc-ink-2);
    min-height:150px;
    height:150px;
}

.stats_chart_cont {
    position: relative;
    height: 380px;
    margin-top: var(--sp-4);
}
/* "This session" beside a row in the sessions list: a tinted chip, the same recipe
   as every other chip in the panel (state colour at low alpha as the fill, the state
   colour at full strength as the text). */
.user_this_session {
    border-radius: var(--noc-radius-sm);
    display: inline-flex;
    color: var(--st-ok);
    background-color: var(--st-ok-soft);
    border: 0;
    padding: 2px 8px;
    font: var(--t-micro);
    font-weight: 700;
}
.user_sessions_row {
    height:26px;
}

/* The zone name and its fixed suffix. This used to be a 129px box floated over the
   right end of the input, with the input reserving 144px of padding for it - two magic
   numbers that had to agree with each other AND with the width of the text. Under
   border-box the 129px started including the 17px of padding, so ".zone.cg-cdn.com"
   no longer fitted and wrapped onto a second line out of the field.
   It is an attached addon now: the field and the suffix are flex siblings, the suffix
   is as wide as its own text and never wraps, and $CDN_ZONE_DOMAIN can be any length
   without a number anywhere needing to change. */
.cdn_input_name_cont {
    position: relative;
    display: flex;
    align-items: stretch;
}
.cdn_input_name_append {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    padding: 0 12px;
    white-space: nowrap;
    font: var(--t-body);
    color: var(--noc-ink-muted);
    background: var(--noc-surface-2);
    border: 1px solid var(--noc-line-2);
    border-left: 0;
    border-radius: 0 var(--noc-radius) var(--noc-radius) 0;
}
.dfi_cdn_name {
    flex: 1 1 auto;
    min-width: 0;
    width: auto;
    padding-right: 10px;
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

.ch_column_1 {
    width:38%;
    display:flex;
}
.ch_column_2 {
    width:14%;
    display:flex;
}
.ch_column_3 {
    width:14%;
    display:flex;
}
.ch_column_4 {
    width:34%;
    display:flex;
}
/* Certificate cell: what secures the hostname (Let's Encrypt or the customer's own certificate) and its actions */
.ch_cert {
    display:flex;
    flex-wrap:wrap;
    align-items:center;
    gap:2px 10px;
    min-width:0;
}
.ch_cert_sub {
    color:var(--noc-ink-muted);
    font-size:13px;
}
.ch_cert_warn {
    color: var(--st-error);
    font-weight:700;
}
.ch_cert_actions {
    display:inline-flex;
    align-items:center;
    gap:10px;
}
.ch_cert_actions form {
    display:inline-flex;
}
.ch_cert_link {
    background:none;
    border:0;
    padding:0;
    margin:0;
    font:inherit;
    font-size:13px;
    color:var(--noc-indigo-ink);
    text-decoration:underline;
    text-underline-offset:2px;
    cursor:pointer;
    min-height:24px;
}
.ch_cert_link:hover,
.ch_cert_link:focus-visible {
    /* Teal is the hover colour, and it is also the only one that works in both themes: --noc-navy is an alias
       for --noc-chrome, which is DARKER in dark mode, so hovering this link used to turn it to near-black on a
       near-black sheet (measured 1.07:1) and the label disappeared under the cursor. --noc-navy is safe as ink
       ONLY where the surface is light whatever the theme: the other rules that still use it that way are the
       status hero and the docs ones, and docs and status are pinned to the light theme. Move any of them onto a
       NOC surface and it is this bug again. (No colour written here, in a comment or otherwise: the one grep
       that keeps this file free of hex does not know the difference.) */
    color:var(--noc-accent-ink);
}
.cert_intro {
    margin-bottom:14px;
}
textarea.cert_ta {
    font: var(--t-mono);
    font-size: 12px;
    white-space:pre;
    overflow-x:auto;
}

.cdn_toggle_switch {
    display:inline-flex;
    align-items: center;
}

/* Loading badge (loading.php): shown while a form POST is in flight, so the person
   knows the click landed and the page is about to be replaced under them.

   ONE moving element, the rail along the bottom edge. The old badge had two things
   saying the same thing - a 48px spinning glyph AND a sliding card - and the rail
   is the better of the two because it reads as indeterminate progress rather than
   as decoration beside the words.

   It carries NO fixed height, and that is the fix as much as the styling: the old
   rule paired height:58px with padding:20px, so when box-sizing:border-box arrived
   with the refresh of 20 September 2026 the content box became 16px and held 72px
   of text - "Loading" was clipped off the top, "Please Wait..." off the bottom and
   the spinner burst through both edges. Height comes from the content now.

   The hidden state is opacity + transform, never a negative top: -100px was a guess
   at the height, so any change to the content either left the badge peeking or moved
   it further than it needed to go. It also sat at top:20px, which is inside the navy
   top bar; it hangs below the bar now. */
.loading_popup {
    position: fixed;
    top: calc(var(--noc-top-height) + 12px);
    right: var(--noc-gutter);
    z-index: 70;

    width: 300px;
    max-width: calc(100vw - (var(--noc-gutter) * 2));

    background-color: var(--noc-surface);
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    /* the rail is clipped by the radius rather than squared off in the corners */
    overflow: hidden;

    /* it is a readout, never a target: it must not eat a click meant for the page */
    pointer-events: none;

    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
}
.lp_body {
    padding: 13px 15px 14px;
}
.lp_title {
    font: var(--t-strong);
    color: var(--noc-ink);
}
.lp_note {
    font: var(--t-sub);
    color: var(--noc-ink-muted);
    margin-top: 1px;
}
/* The rail: a track in the inset tone with one teal segment sweeping across it.
   Teal because this is the panel's colour for the live thing right now, and a
   request in flight is a state - the rest of the badge carries no colour at all.
   --noc-accent-ink rather than --noc-accent: the segment is the one part of this
   badge that carries meaning without words, so it has to clear 3:1 against its own
   track, and the bright accent measures 2.15:1 on --noc-surface-2 in the light
   theme. accent-ink is the teal as ink on light and the bright accent on dark,
   which is this exact problem; measured 4.6:1 light and 8.2:1 dark. */
.lp_rail {
    /* 3px, the width .notify_box already uses for a rule "in the colour of what it is
       saying". This is the same device turned on its side, and it is the one expressive
       element on the badge, so it is allowed to be seen. */
    height: 3px;
    background-color: var(--noc-surface-2);
    overflow: hidden;
}
.lp_rail span {
    display: block;
    height: 100%;
    width: 38%;
    background-color: var(--noc-accent-ink);
    transform: translateX(-100%);
    animation: lp_sweep 1.15s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
@keyframes lp_sweep {
    from { transform: translateX(-100%); }
    to   { transform: translateX(263%); }
}

/* The zone page's own actions, beside its title: separate buttons 8px apart, and one primary among them (Purge), with
   Statistics as the secondary and Delete Zone in the danger style. They were one joined navy bar of equal weight,
   which on a phone left Delete Zone as the widest, heaviest thing on the page. */
.cdn_page_buttons {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: 8px;
}
/* The Purge popup's second form, a single file: a group under a rule, and the address and its button on one line,
   the field taking whatever the button leaves. It was a centred pair at fixed widths (312px and 112px) that sat
   off-centre in the 560px popup. */
.purge_single_form {
    margin-top: var(--noc-pad-box);
    padding-top: var(--noc-pad-box);
    border-top: 1px solid var(--noc-line);
}
.purge_single_cont {
    width: 100%;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    gap: 10px;
    margin-top: 10px;
}
.purge_single_cont .purge_input {
    flex: 1 1 auto;
    min-width: 0;
    width: auto;
    margin: 0;
}
.purge_single_cont .purge_single_button {
    flex: 0 0 auto;
    width: auto;
    margin: 0;
}

/* -----------------------------------------------------------------------------
   7. Page sections - the zone page
   Six boxes stacked into 3,400px of scrolling. The boxes are unchanged - every one is
   its own form with its own handler - but they now sit under four named sections, so
   the page reads as four decisions rather than six equally-weighted panels, and the
   eight setting toggles go two-up on a wide screen, which is where most of the height
   went.
   -------------------------------------------------------------------------- */
.zone_section {
    margin-top: 32px;
    padding-bottom: var(--sp-2);
    border-bottom: 1px solid var(--noc-line);
}
.zone_section:first-child { margin-top: var(--sp-2); }
.zone_section_title {
    font: var(--t-h2);
    color: var(--noc-ink);
    margin: 0;
}
.zone_section_sub {
    font: var(--t-sub);
    color: var(--noc-ink-muted);
    margin-top: 3px;
    max-width: var(--measure-help);
}
/* Two toggles per row above 1100px. Each row is its own form, so this is a grid over
   the rows and nothing about the forms changes. */
@media (min-width: 1100px) {
    .cdn_settings_grid {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
    .cdn_settings_grid > .bic_row_cont { border-top: 1px solid var(--noc-line); }
    .cdn_settings_grid > .bic_row_cont:nth-child(-n+2) { border-top: 0; }
    .cdn_settings_grid > .bic_row_cont:nth-child(odd) { border-right: 1px solid var(--noc-line); }
}

/* A setting row is a name and an explanation, and they were the same weight, so the
   name did not read as a name. The name carries the weight; the explanation is the
   sub-line every other list in the panel uses. */
.cdn_settings_1 {
    width: 85%;
    text-align: left;
    font: var(--t-strong);
    color: var(--noc-ink);
    white-space: normal;
}
.cdn_settings_2 {
    width:15%;
    text-align:right;
    display:flex;
    justify-content: flex-end;
    align-items: center;
}

.stc_toggle {
    display:flex;
    justify-content: flex-start;
    align-items: center;
}

/* Incident type colours on the pills (status page and admin). The .so_* class names the incident type; a past
   incident on the status page carries the same class to colour its left rule (noc.css). */
/* The incident types are the same five states under another set of names. */
.st_pill.so_up { background: var(--st-up); color: var(--st-ink-on-warm); }
.st_pill.so_partial { background: var(--st-degraded); color: var(--st-ink-on-warm); }
.st_pill.so_major { background: var(--st-down); color: var(--st-ink-on-fill); }
.st_pill.so_maintenance { background: var(--st-maint); color: var(--st-ink-on-fill); }

.status_page_cont a, .status_page_cont a:visited, .status_page_cont a:hover, .status_page_cont a:active {
    text-decoration: none;
    font-weight:bold;
    color: inherit;
}

.spi_service_outer {
    border:1px solid var(--noc-line);
    border-radius: var(--noc-radius);
}
.spi_service_cont {
    display:flex;
    justify-content: space-between;
    align-items: center;
    padding:10px;
    border-bottom:1px solid var(--noc-line);
}
.spi_service_cont:last-child {
    border-bottom:0px;
}
.spi_service_title {
    display:flex;
    justify-content: flex-start;
    align-items: center;
    line-height: 1;
    margin-right:10px;
}

.status_message_style p:first-child {
    margin-top:0px;
}
.status_message_style p:last-child {
    margin-bottom:0px;
}

.status_message_style a, .status_message_style a:visited, .status_message_style a:active {
    text-decoration: none;
    font-weight:bold;
    color: inherit;
}
.status_message_style a:hover {
    text-decoration: underline;
    font-weight:bold;
    color: inherit;
}

.a_nostyle, .a_nostyle:visited, .a_nostyle:hover, .a_nostyle:active {
    text-decoration: none;
    font-weight:inherit;
    color: inherit;
}

/* Its type is the kit's group heading (section 4f); only where it sits is said here */
.dashboard_2fa_title {
    margin-top:20px;
    margin-bottom:10px;
}
code {
    display:inline-flex;
    background: var(--noc-surface-2);
    color:inherit;
    font-size: 85%;
    border-radius: var(--noc-radius-sm);
    padding:4px;
    word-wrap: break-word;
}
.code_big {
    font-size: 90%;
    padding:20px;
    display:block;
}

#SA_RESPONSE_CODE {
    display:none;
}
#SA_RESPONSE_REDR {
    display:none;
}

html { -webkit-text-size-adjust: 100%; }
body { min-height: 100vh; }
body.site_dashboard { padding-bottom: env(safe-area-inset-bottom); }

/* Invisible bot shield honeypot: kept out of sight and out of the tab order */
.bs_hp {
    position: absolute !important;
    left: -10000px !important;
    top: auto !important;
    width: 1px !important;
    height: 1px !important;
    overflow: hidden !important;
    opacity: 0;
    pointer-events: none;
}

/* Loading badge visibility (loading.php toggles .lp_visible). visibility is in the
   transition so the hidden badge is out of the accessibility tree and off the tab
   order between submits, and it is delayed out and instant in so it never blinks. */
.loading_popup { transition: opacity 0.16s ease, transform 0.16s ease, visibility 0s linear 0.16s; }
.loading_popup.lp_visible {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: opacity 0.16s ease, transform 0.16s ease, visibility 0s;
}

/* ---------------------------------------------------------------------------
   Top bar
   --------------------------------------------------------------------------- */
/* -----------------------------------------------------------------------------
   8. The frame: top bar, sheet, footer, phone tab bar
   -------------------------------------------------------------------------- */
.noc_top {
    position: sticky;
    top: 0;
    z-index: 40;
    background-color: var(--noc-navy);
    border-bottom: 1px solid var(--noc-chrome-line);
    padding-top: env(safe-area-inset-top);
}
.noc_top_inner {
    max-width: var(--noc-max);
    margin: 0 auto;
    padding: 0 var(--noc-gutter);
    height: var(--noc-top-height);
    display: flex;
    align-items: center;
    gap: 8px;
}
.noc_logo {
    display: flex;
    align-items: center;
    margin-right: 20px;
    flex: 0 0 auto;
}
.noc_logo img {
    height: 40px;
    width: auto;
    display: block;
}
.noc_nav {
    display: flex;
    align-items: stretch;
    gap: 4px;
    height: 100%;
    flex: 1 1 auto;
    min-width: 0;
}
/* The global a:visited is coloured for the sheet; the chrome links keep their own. */
.noc_top a, .noc_top a:visited, .noc_tabbar a, .noc_tabbar a:visited, .noc_footer a:visited { text-decoration: none; font-weight: 600; }
.noc_nav_item, .noc_nav_item:visited, .noc_util_item, .noc_util_item:visited, .noc_tab_item, .noc_tab_item:visited { color: var(--noc-chrome-ink); }
.noc_nav_item.is_active, .noc_nav_item.is_active:visited, .noc_tab_item.is_active, .noc_tab_item.is_active:visited, .noc_util_item.is_logout, .noc_util_item.is_logout:visited { color: var(--noc-chrome-ink-strong); }
.noc_nav_item, .noc_util_item {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 0 14px;
    color: var(--noc-chrome-ink);
    text-decoration: none;
    font-weight: 600;
    font-size: 15px;
    line-height: 1;
    border-radius: var(--noc-radius);
    position: relative;
    white-space: nowrap;
    transition: color 0.15s ease, background-color 0.15s ease;
}
.noc_nav_item { height: calc(var(--noc-top-height) - 20px); align-self: center; }
.noc_nav_item i { font-size: 16px; width: 18px; text-align: center; }
.noc_nav_item:hover, .noc_util_item:hover { color: var(--noc-chrome-ink-strong); background-color: var(--noc-navy-raised); }
.noc_nav_item.is_active { color: var(--noc-chrome-ink-strong); }
.noc_nav_item.is_active::after {
    content: "";
    position: absolute;
    left: 14px;
    right: 14px;
    bottom: -10px;
    height: 3px;
    border-radius: 3px 3px 0 0;
    background-color: var(--noc-teal);
}
.noc_utils {
    display: flex;
    align-items: center;
    gap: 2px;
    margin-left: auto;
    flex: 0 0 auto;
}
.noc_util_item { padding: 10px 12px; font-weight: 500; font-size: 14px; }
/* Log out has to be a POST, because a GET can be triggered by any cross-site
   link, while still reading as the utility link it replaces. display:contents
   takes the form out of the layout so the button sits in the flex row itself. */
.post_form { display: contents; }
button.noc_util_item { background: none; border: 0; font: inherit; cursor: pointer; }
.noc_util_item i { font-size: 15px; }
/* The toggle shows the theme it will switch TO, so the icon is the destination and
   not the current state. Both icons are in the markup and CSS picks one, which keeps
   it correct on a page the browser restored from the back/forward cache. */
.noc_theme_toggle .noc_theme_sun { display: none; }
:root[data-theme="dark"] .noc_theme_toggle .noc_theme_moon { display: none; }
:root[data-theme="dark"] .noc_theme_toggle .noc_theme_sun { display: inline-block; }
.noc_util_item.is_logout { color: var(--noc-chrome-ink-strong); }
.noc_nav_item:focus-visible, .noc_util_item:focus-visible, .noc_tab_item:focus-visible {
    outline: 2px solid var(--noc-teal);
    outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
   Content sheet
   --------------------------------------------------------------------------- */
.noc_main {
    max-width: var(--noc-max);
    margin: 0 auto;
    padding: var(--noc-gutter);
}
.noc_sheet {
    background-color: var(--noc-surface);
    border-radius: var(--noc-radius);
    padding: 24px;
    color: var(--noc-ink);
    text-align: left;
    min-height: 240px;
}
.noc_sheet.is_narrow { max-width: 900px; margin: 0 auto; }
.noc_footer {
    max-width: var(--noc-max);
    margin: 0 auto;
    padding: 0 var(--noc-gutter) 24px;
    text-align: center;
    color: var(--noc-ink-faint);
    font-size: 13px;
}
.noc_footer a { color: var(--noc-chrome-ink); text-decoration: none; font-weight: 600; }
.noc_footer a:hover { color: var(--noc-chrome-ink-strong); }
.noc_footer_copy { line-height: 1.3; }
/* The link is a 13px line, 17px tall: the padding gives a thumb a 29px target and the negative margin keeps the
   footer exactly where it was (27 September 2026, the status page's visual pass; the footer is every public host's) */
.noc_footer_link { display: inline-block; line-height: 1.3; padding: 6px 8px; margin: -6px -8px; }

/* Page title + sub navigation (Account: Settings | Billing | Users, Admin: Accounts | Docs | ...) */
.account_page_title_cont { flex-wrap: wrap; gap: 12px 16px; }
.aptc_menu {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
}
/* The sub-nav wears the top bar's mark - a teal underline on the current item - so
   the two levels of navigation speak the same language instead of one being outlined
   pills and the other a bar. */
.aptc_menu { gap: var(--sp-5); }
.aptc_menu a, .aptc_menu a:visited {
    padding: 8px 0;
    border: 0;
    border-bottom: 3px solid transparent;
    border-radius: 0;
    color: var(--noc-ink-muted);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.2;
    transition: color 150ms, border-color 150ms;
}
.aptc_menu a:hover { color: var(--noc-ink); border-bottom-color: var(--noc-line-2); }
.aptc_menu .aptc_menu_selected, .aptc_menu a.aptc_menu_selected, .aptc_menu a.aptc_menu_selected:visited {
    background: transparent;
    color: var(--noc-ink);
    border-bottom-color: var(--noc-accent);
    font-weight: 700;
}

/* Row tables: long values wrap instead of being clipped */
.bicr_column { white-space: normal; overflow-wrap: anywhere; }

/* Fluid, not fixed. These carried calc() widths written for a card of a set size. */
.dashboard_box_cont { width: 100%; }
.dashboard_stats_overview { width: 100%; }

img, svg, video { max-width: 100%; }

/* (The control scale and the link-as-button rules that sat here are the kit's now, section 4, where they are said
   once: a copy here came later in the file and quietly won, which is how the small button had a second size.) */
/* A note that is information rather than a problem: the smaller, inline form of
   the same left-rule device .notify_box uses, in the muted register the rest of
   the panel explains itself in. It predates the notices having four semantic
   variants - before that, telling somebody what a save was about to do meant
   borrowing an alert and reading as a failure - and it survives because a note
   beside a field is not a flash message and should not look like one. */
.noc_note { margin-top: 10px; padding: 10px 12px; border-left: 3px solid var(--noc-indigo); background: var(--noc-surface-2); border-radius: 0 var(--noc-radius) var(--noc-radius) 0; font-size: 13px; line-height: 1.5; color: var(--noc-ink); }

/* Button rows: space between buttons, never flush against the box corner */
.dashboard_form_button_right { gap: 10px; flex-wrap: wrap; justify-content: flex-end; align-items: center; }
.dfbr_pad { width: auto; margin-top: 0; padding: var(--sp-3) var(--noc-pad-box); border-top: 1px solid var(--noc-line); }
.dbc_content_pad { padding: var(--noc-pad-box); }
.dbc_content_pad .dashboard_form_button_right { margin-top: var(--noc-gap); }

.cdn_settings_1 .dfi_input_sub, .cdn_settings_1 .asf_email_notice {
    font: var(--t-sub);
    color: var(--noc-ink-muted);
    width: auto;
    max-width: var(--measure-help);
    margin-top: 4px;
}

/* A submit inside a two-column form column used to stretch to the column's full width,
   which on a 1440 screen is a 570px navy slab sitting beside a select. A save is an
   action: it takes the width of its label and sits at the end of the column.

   margin-top: auto rather than a fixed gap, and this matters. The field it sits beside
   carries a label above it, so any fixed margin puts the button on the label's line
   instead of the field's - it was 16px high. Pushed to the bottom of a stretched flex
   column, its bottom edge meets the bottom of the last field in the other column
   whatever that column contains, which is the alignment a person actually sees. */
/* .asf_left and .asf_right only. .ads_cont2 looks like a column name and
   is not: it is a modifier on the FORM (.account_settings_form.ads_cont2), so
   putting flex-direction: column on it stacked the two columns of Billing Details and
   Origin Details on top of each other and the right-hand fields vanished below the
   fold. */
.asf_left, .asf_right {
    display: flex;
    flex-direction: column;
    align-items: stretch;
}

/* Two columns that actually share their rows.
   A two-column form used to be two independent stacks side by side, so the moment one
   side's help text wrapped to a different number of lines every field below it drifted
   out of line with its partner - the field pairs stopped being rows. Wrapping each
   label + control + help in .asf_field and letting the FORM own the grid fixes it: each
   pair shares one row track, so the row is as tall as the taller of the two and both
   fields start on the same line whatever the copy does.
   display: contents on the columns is what lets the .asf_field elements become grid
   items of the form while the .asf_left / .asf_right wrappers stay in the markup, and
   `dense` is what puts the right-hand column's first field on row one instead of on the
   row after the left column's last. */
.account_settings_form:has(.asf_field) {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-auto-flow: row dense;
    column-gap: 20px;
    align-items: start;
}
.account_settings_form:has(.asf_field) > .asf_left,
.account_settings_form:has(.asf_field) > .asf_right {
    display: contents;
}
.account_settings_form:has(.asf_field) > .asf_left > .asf_field { grid-column: 1; }
.account_settings_form:has(.asf_field) > .asf_right > .asf_field { grid-column: 2; }
.asf_field { min-width: 0; }
/* The save strip is a grid item too when the form itself is the grid, so it has to be
   told to span both columns; left to auto-placement it lands in column two beside the
   last field, with its hairline stopping half way across the box. */
.account_settings_form:has(.asf_field) > .dashboard_form_button_right { grid-column: 1 / -1; }
.asf_field .dashboard_form_input_title:first-child { margin-top: var(--noc-gap); }
@media (max-width: 640px) {
    .account_settings_form:has(.asf_field) { grid-template-columns: minmax(0, 1fr); }
    .account_settings_form:has(.asf_field) > .asf_left > .asf_field,
    .account_settings_form:has(.asf_field) > .asf_right > .asf_field { grid-column: 1; }
}

/* THE SAVE STRIP. Every form in the panel ends the same way: one action, right
   aligned, on its own strip that reaches back out through the content padding to the
   box edges and sits above a hairline. It is the form's foot rather than a button
   floating in the last column, which is what makes a long two-column form read as one
   thing with one action instead of a field that happens to be a button.
   Pure CSS on markup that was already there: a .dashboard_form_button_right as the last
   child of a box body. A popup's yes/no row is the same foot when it ends the popup's last
   form (25 September 2026): the first form of the Purge popup keeps its row in place,
   because the single-file form follows it. */
.dbc_inner_content > form > .dashboard_form_button_right:last-child,
.dbc_inner_content > .dashboard_form_button_right:last-child,
.dbc_content_pad > form > .dashboard_form_button_right:last-child,
.dbc_content_pad > .dashboard_form_button_right:last-child,
.npb_content > form > .dashboard_form_button_right:last-child,
.npb_content > .dashboard_form_button_right:last-child,
.npb_content > form:last-child > .dfb_middle_yesno:last-child {
    margin: var(--noc-pad-box) calc(-1 * var(--noc-pad-box)) calc(-1 * var(--noc-pad-box));
    padding: var(--sp-3) var(--noc-pad-box);
    width: auto;
    border-top: 1px solid var(--noc-line);
    background: var(--noc-surface);
}

.dopt_left { min-width: 0; }
/* The title row's actions: a row of their own with the kit's 8px between them, so two link buttons are not left the
   width of a space apart (4px on an invoice page, found by the DOM audit on 25 September 2026) */
.dopt_right { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 8px; }

/* ---------------------------------------------------------------------------
   Support page
   Two jobs in the order a person needs them: look it up, or write to us. The documentation box opens with the same
   search field the docs hub opens with, so the two surfaces feel like one; the sections below it are the way in when
   you do not yet know what to search for. The Support PIN sits beside the reason it exists rather than in a box of
   its own, and it is plain text with its own button, not a button you regenerate by clicking the number you came to
   read (14 September 2026, when support tickets were replaced by email).
   --------------------------------------------------------------------------- */
.sup_page_subtitle { margin-top: 8px; color: var(--noc-ink-muted); }
.sup_search { margin-bottom: 20px; }
.sup_sections {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 10px;
}
.sup_section {
    display: block;
    padding: 12px 14px;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    background: var(--noc-surface-2);
    transition: border-color 200ms;
}
.sup_section:hover { border-color: var(--noc-accent-ink); }
.sup_section_name { display: block; font-weight: bold; font-size: 15px; }
.sup_section_count { display: block; margin-top: 2px; font-size: 13px; color: var(--noc-ink-muted); }

.sup_contact { display: flex; flex-wrap: wrap; gap: 24px 40px; align-items: flex-start; }
.sup_contact_intro { flex: 1 1 340px; min-width: 0; }
.sup_lead { margin: 0; font-size: 17px; line-height: 1.5; max-width: 56ch; }
.sup_note { margin: 12px 0 0; font-size: 14px; line-height: 1.55; color: var(--noc-ink-muted); max-width: 62ch; }
.sup_address { color: var(--noc-ink); font-weight: bold; overflow-wrap: anywhere; }

/* The one bold element on the page: six digits on the inverted panel, where every other
   surface on the page is the sheet or its inset tone - in either theme. */
.sup_pin_cont { flex: 0 1 260px; min-width: 0; }
.sup_pin_title { font-weight: bold; font-size: 15px; }
.sup_pin {
    margin-top: 8px;
    padding: 12px 18px;
    border-radius: var(--noc-radius);
    background: var(--noc-inverse);
    color: var(--noc-inverse-ink);
    font-size: 26px;
    font-weight: bold;
    letter-spacing: 0.12em;
    text-align: center;
    user-select: all;
}
.sup_pin_cont .dashboard_form_button { margin-top: 12px; }

/* The compose modal: the same overlay the zone page uses for its own forms */
.sup_popup_note { margin: 0 0 16px; }

/* Zone page and the create popup, private S3 bucket origins: the five bucket fields are one group under the two
   columns rather than five fields sprinkled through them, and the addressing select shows the URL it produces. */
.s3_group, .blk_group { margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--noc-line); }
/* (the two titles' type is the kit's group heading, section 4f) */
.s3_group_sub, .blk_group_sub { font-size: 13px; color: var(--noc-ink-muted); margin-top: 4px; }
.s3_grid, .blk_grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 20px; align-items: start; }
.s3_cell, .blk_cell { min-width: 0; }
.s3_group .dashboard_form_input_title, .blk_group .dashboard_form_input_title { margin-top: 16px; }
.s3_cell_wide { grid-column: 1 / -1; }
.s3_url_preview {
    margin-top: 8px;
    padding: 8px 10px;
    border-radius: var(--noc-radius);
    background: var(--noc-surface-2);
    font-size: 13px;
    line-height: 1.5;
    color: var(--noc-ink-muted);
}
.s3_url_value {
    color: var(--noc-ink);
    font: var(--t-mono);
    font-size: 12px;
    overflow-wrap: anywhere;
}

/* Visitor blocking (the zone page's Blocking box). Everything here is the existing kit - the box, the
   two-column setting rows, the toggles, the textareas, the grouped-fields rule above - except the country
   list, which needed two pieces of its own.

   The country list is the one control on this page that can be read backwards: the same three codes mean
   "refuse these" or "serve only these" depending on the mode above them, and reading it the wrong way round
   takes a customer's site off the air for everyone they meant to serve. A colour cannot say that, and a
   colour would have to be learned. So the list states its consequence in words, in a .noc_note that is
   rewritten when the mode changes, and the chips themselves stay quiet: a chosen country is not an
   exception to report, it is a setting. */
.blk_chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.blk_chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 6px 4px 9px;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius-sm);
    background: var(--noc-surface-2);
    font: var(--t-micro);
    color: var(--noc-ink);
}
/* Muted until it is pointed at, and it darkens rather than turning red: removing a country from a list is
   not a destructive act worth a colour, and colour here is reserved for state.
   Two measured constraints, both of which the first version of this failed. --noc-ink-faint on the chip's own
   surface is 2.48:1 in light, and this is a control rather than decoration, so it needs the 3:1 that non-text
   contrast asks for: muted ink measures 4.83:1 light and 5.68:1 dark. And it was a 15px box, well under the 24px
   a pointer target wants, so it is a 22px flex box inside the chip's padding instead. */
.blk_chip_x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    min-height: 22px;
    margin: -2px -3px -2px 0;
    border: 0;
    border-radius: var(--noc-radius-sm);
    background: none;
    padding: 0;
    line-height: 1;
    font-size: 15px;
    color: var(--noc-ink-muted);
    cursor: pointer;
}
.blk_chip_x:hover { color: var(--noc-ink); background: var(--noc-surface-3); }
.blk_chips_empty { font: var(--t-sub); color: var(--noc-ink-muted); margin-top: 10px; }
/* Only shown when an entry is refused as it is typed, so the customer is not left wondering why pressing Add did
   nothing. Text in the error colour rather than a filled notice: it belongs to the field above it, not to the
   box. */
.blk_add_error { display: none; font: var(--t-sub); color: var(--st-error); margin-top: 6px; }
/* The segmented control again, as links rather than radios: on the statistics page the Served / Blocked switch
   changes what is queried, so it is navigation and has to be a link a customer can open in a new tab or come
   back to. Same shape, same current-item mark, so the two read as one control wherever they appear. */
.stats_view_switch { margin-top: 0; }
.blk_seg_link {
    display: block;
    padding: 5px 12px 4px;
    border: 1px solid transparent;
    border-bottom-width: 2px;
    border-radius: var(--noc-radius-sm);
    font: var(--t-sub);
    color: var(--noc-ink-muted);
    text-decoration: none;
    transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.blk_seg_link:hover { color: var(--noc-ink); text-decoration: none; }
.blk_seg_link.is_current {
    background: var(--noc-surface);
    border-color: var(--noc-line);
    border-bottom-color: var(--noc-accent);
    color: var(--noc-ink);
}
/* The visitor breakdown tables. A name, two figures and a share: the figures are tabular and right aligned like
   every other figure column in the panel, and the name takes whatever is left. */
.ctry_column_1 { width: 38%; }
.ctry_column_2 { width: 18%; }
.ctry_column_3 { width: 20%; }
.ctry_column_4 { width: 24%; }
.ctry_column_2, .ctry_column_3, .ctry_column_4 { text-align: right; font-variant-numeric: tabular-nums; }
/* Where visitors came from (25 September 2026): one box holding the countries, the network types and, in the Blocked
   view, the AS numbers the zone blocks. Each section opens with its own header row and every one sits on the
   .ctry_column widths above, so a figure lines up with the figure over it however many sections there are. A
   section after the first is set off by a hairline and a little air, so it reads as the next part of the box rather
   than as one more row. */
.vis_section + .vis_section { border-top: 1px solid var(--noc-line); }
.vis_section + .vis_section > .bic_row_title { padding-top: var(--sp-6); }
.vis_section:not(.is_open) .is_extra { display: none; }
/* The name a blocked AS is registered under, quiet under its number. It is the registry's own wording and a few run
   past 120 characters, so it stops at two lines and the whole of it is in the title. */
.net_name { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
/* The share as a bar beside its figure, so a column of shares reads as a distribution at a glance: the page's first
   series colour (--chart-1, the Delivered line in the chart above), square where it starts and rounded where it
   ends. The longest share in the box fills the bar's width and every other is to scale with it: shares of a few per
   cent on a 0 to 100 scale were slivers. The track is one step off the surface and no more - drawn in the hairline
   colour it was a column of empty rails, the loudest thing in the box - and it is there so a small share reads as
   the start of a bar rather than as a stray dot. The bar carries nothing the figure does not, and is aria-hidden. */
.vis_share { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-3); }
.vis_bar { flex: 0 1 112px; height: 4px; background: var(--noc-surface-2); border-radius: 0 2px 2px 0; }
.vis_bar > span { display: block; height: 100%; min-width: 2px; background: var(--chart-1); border-radius: 0 2px 2px 0; }
.vis_pct { flex: 0 0 auto; min-width: 3.2em; }
/* "Show all 42 countries": the house link on a button, in its own row under the first ten. */
.vis_more { padding: var(--sp-3) var(--noc-pad-box); border-top: 1px solid var(--noc-line); }
.vis_more_btn {
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    font: var(--t-sub);
    font-weight: 600;
    color: var(--noc-ink);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-decoration-color: var(--noc-accent);
    text-underline-offset: 3px;
}
.vis_more_btn:hover { text-decoration-thickness: 2px; }
/* A 14px line is a 22px target: 6px of padding on a matching negative margin makes it 34px, the small button's
   height, without moving the row (27 September 2026, the NOC's visual pass; the Backups fold shares the button) */
.vis_more_btn { padding: 6px 0; margin: -6px 0; }
.vis_more_btn i { margin-left: 6px; font-size: 11px; }
.vis_section.is_open .vis_more_btn i { transform: rotate(180deg); }
/* On a phone every row table stacks its cells a line each; three figures and a name was four lines a country here.
   These rows keep the name on its own line and put the figures side by side under their labels, two lines a row. */
@media (max-width: 640px) {
    .vis_section .bic_row_cont { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-1) var(--sp-3); }
    .vis_section .ctry_column_1 { grid-column: 1 / -1; }
    /* The header row's first cell names the section ("Country", "Network type"), so it stays as the section's label;
       the other three are each figure's own label here, printed above it. */
    .vis_section .bic_row_title { display: block; }
    .vis_section .bic_row_title .ctry_column_2, .vis_section .bic_row_title .ctry_column_3, .vis_section .bic_row_title .ctry_column_4 { display: none; }
    .vis_section .ctry_column_2, .vis_section .ctry_column_3, .vis_section .ctry_column_4 { width: auto; text-align: left; }
    .vis_share { justify-content: flex-start; gap: var(--sp-2); }
    .vis_bar { flex-basis: 48px; }
    .vis_more { padding: var(--sp-3) 14px; }
}
/* An address list is read down the left edge - one address per line, ranges lining up with ranges - so it is set
   in the mono face rather than Maven Pro. Specific enough to beat .dfi_ta, which sets the family. */
textarea.dashboard_form_input.blk_ta { font: var(--t-mono); color: var(--noc-ink-2); }
/* A setting row inside a padded box, so unlike .bic_row_cont it carries no side padding of its own: that one is a
   full-bleed row of a box with no inner padding, and nesting it here would indent every row by a second 18px. */
.blk_toggle_row { display: flex; justify-content: space-between; align-items: center; gap: 16px; min-height: 48px; padding: var(--sp-3) 0; border-top: 1px solid var(--noc-line); }
.blk_toggle_row:first-of-type { border-top: 0; padding-top: var(--sp-2); }
.blk_toggle_label { flex: 1 1 auto; min-width: 0; font: var(--t-body); color: var(--noc-ink); }
.blk_toggle_control { flex: 0 0 auto; }
.blk_pick { display: flex; gap: 10px; align-items: center; margin-top: 8px; }
.blk_pick select { flex: 1 1 auto; min-width: 0; }
/* The country mode is a choice between two readings of one list, not two independent settings, so it is drawn as
   one control with two halves rather than two loose radio dots. The real inputs are still there and still
   focusable - they are what posts, and what a screen reader announces as a radio group - they are just not what
   is drawn.
   The selected half is marked the way the rest of the panel marks a current item: it sits on the raised tone
   above the track (depth is a hairline and two tones, never a shadow) and carries a 2px accent edge, the same
   device as the top bar's current section and the docs sidebar's current article. Teal for the current item is
   one of the three things colour is allowed to mean here.
   Nothing changes weight between states: bold is wider, and a control that resizes as you choose between its own
   options is a control that moves under the cursor. */
.blk_segmented {
    display: inline-flex;
    margin-top: 8px;
    padding: 3px;
    gap: 3px;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    background: var(--noc-surface-2);
    max-width: 100%;
}
.blk_seg { position: relative; display: flex; }
/* Hidden from the eye, not from the keyboard or the accessibility tree. */
.blk_seg input { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none; }
.blk_seg span {
    display: block;
    padding: 7px 14px 6px;
    border: 1px solid transparent;
    border-bottom-width: 2px;
    border-radius: var(--noc-radius-sm);
    font: var(--t-body);
    color: var(--noc-ink-muted);
    cursor: pointer;
    transition: background-color 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.blk_seg span:hover { color: var(--noc-ink); }
.blk_seg input:checked + span {
    background: var(--noc-surface);
    border-color: var(--noc-line);
    border-bottom-color: var(--noc-accent);
    color: var(--noc-ink);
}
.blk_seg input:focus-visible + span { outline: 2px solid var(--noc-accent-ink); outline-offset: 2px; }
/* Attribution owed to DB-IP under CC BY 4.0 for the country data. It sits with the rule it belongs to
   rather than in a page footer, where nobody would connect the two. */
.blk_credit { margin-top: 16px; font: var(--t-sub); color: var(--noc-ink-muted); }
.blk_credit a { color: var(--noc-ink-muted); text-decoration: underline; }
.blk_credit a:hover { color: var(--noc-accent-ink); }
@media (max-width: 640px) {
    /* Stacked rather than side by side: at 390px each half is about 180px, and "Serve only these countries" wraps
       to three lines in it. */
    .blk_segmented { display: flex; flex-direction: column; width: 100%; }
    .blk_seg span { padding: 9px 12px 8px; }
    /* The statistics switch is the same control with one-word labels, so it keeps its row: stacking "Served" over
       "Blocked" makes a box header 107px tall to say something that fits on one line. It moves under the title
       instead, which is what .dbch_split does on a phone anyway. */
    .stats_view_switch { flex-direction: row; width: auto; }
    .blk_pick { flex-wrap: wrap; }
    .blk_pick select { flex: 1 1 100%; }
    .blk_pick .dashboard_form_button { flex: 1 1 100%; }
}

/* Zone page, Token Authentication: the signing key beside its two buttons. The key is 64 unbroken characters,
   so it has to be allowed to wrap or it pushes the sheet sideways at phone width. */
.tok_pad { border-top: 1px solid var(--noc-line); }
.tok_key_row { display: flex; align-items: center; gap: 10px; }
.tok_key {
    flex: 1 1 auto;
    min-width: 0;
    box-sizing: border-box;
    padding: 9px 10px;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    background-color: var(--noc-surface);
    color: var(--noc-ink-2);
    font: var(--t-mono);
    line-height: 1.5;
    overflow-wrap: anywhere;
}
.tok_key_actions { flex: 0 0 auto; display: flex; gap: 10px; }
.tok_key_empty { font: var(--t-body); color: var(--noc-ink-muted); }

/* Zone page, Origin Certificate (26 September 2026): what it is and Create; the certificate and its private key the
   one time they are shown, each on the navy inverse block the node log uses; or its facts (the System page's own
   .sys_facts) and a strip with Copy and Download to the left of Reissue and Revoke. Amber only for what needs doing:
   the key that has to be copied now, and a certificate the zone is not checking. */
/* The page links to the box (#origin_cert: every form here posts back to it), so it clears the sticky top bar the
   way the admin pages' sections do (.adm_anchor), or the title lands underneath it. Its sentences are help text and
   wear the help text of the boxes around it. */
#origin_cert { scroll-margin-top: calc(var(--noc-top-height) + 16px); }
.ocert_text { margin: 0; max-width: var(--measure-help); font: var(--t-sub); color: var(--noc-ink-muted); }
.ocert_text + .ocert_text, .ocert_facts + .ocert_text { margin-top: var(--sp-3); }
.ocert_muted { color: var(--noc-ink-muted); }
.ocert_warn { color: var(--st-warn); }
.ocert_once { margin-bottom: var(--sp-4); }
.ocert_pem_head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: var(--sp-4); }
.ocert_once + .ocert_pem_head { margin-top: 0; }
.ocert_pem_title { font: var(--t-strong); color: var(--noc-ink); }
.ocert_pem_actions { flex: 0 0 auto; display: flex; gap: 10px; }
.ocert_pem {
    margin: var(--sp-2) 0 0;
    padding: 12px;
    max-height: 240px;
    overflow: auto;
    border-radius: var(--noc-radius);
    background: var(--noc-inverse);
    color: var(--noc-inverse-ink);
    font: var(--t-mono);
    font-size: 12px;
    line-height: 1.5;
    /* A certificate's lines are 64 characters and read as a block only whole: on a narrow screen the block scrolls
       sideways inside itself rather than breaking each line in two (Copy and Download take the text either way) */
    white-space: pre;
}
.ocert_pem + .ocert_text { margin-top: var(--sp-4); }
/* The fingerprint is set smaller in the fixed-width face, which puts its baseline 4px above the other facts' in the
   same 22.5px line (measured at every width, side by side and stacked): the padding puts it back on their line. */
.ocert_fp { font: var(--t-mono); font-size: 12px; padding-top: 4px; overflow-wrap: normal; }
.ocert_strip_start { display: flex; flex-wrap: wrap; gap: 10px; margin-right: auto; }

/* Zone statistics page: filter row, summary tiles, per-node table */
.stats_page_subtitle { margin-top: 8px; color: var(--noc-ink-muted); }
/* Four selects and a button are a toolbar, not a panel. They used to sit in a box of
   their own titled "Show me", which gave the filters the same weight as the figures
   they filter. */
.stats_toolbar { margin-top: var(--sp-2); }
.stats_toolbar_inner { padding: 0; }
.stats_controls { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 16px; }
.stats_control { flex: 1 1 160px; min-width: 0; }
.stats_control.sc_period { flex-basis: 180px; }
.stats_control.sc_date { flex-basis: 150px; }
.stats_control.sc_gran { flex-basis: 130px; }
.stats_control.sc_node { flex-basis: 220px; }
.stats_control.sc_apply { flex: 0 0 auto; }
.stats_control .dashboard_form_input_title { margin-top: 0; }
.stats_tiles .dsoi_subtitle { width: auto; }
.stats_chart_top { margin-top: 0; }

/* Two charts a row above 1200px. A Chart.js canvas with maintainAspectRatio:false
   inside a grid cell needs min-width:0 or it refuses to shrink and overflows the grid. */
.stats_charts { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--noc-gap); }
.stats_charts > * { min-width: 0; }
.stats_charts .dbc_margin { margin-top: 0; }
@media (min-width: 1200px) {
    .stats_charts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .stats_charts .stats_chart_cont { height: 300px; }
    /* An odd number of charts left the last one in the left half with the right half
       empty. A chart with no partner takes the whole row. */
    .stats_charts > :last-child:nth-child(odd) { grid-column: 1 / -1; }
    .stats_charts > :last-child:nth-child(odd) .stats_chart_cont { height: 340px; }
}
.sn_column_1 { width: 25%; padding-right: 10px; }
.sn_column_2, .sn_column_3, .sn_column_4, .sn_column_5, .sn_column_6 { width: 15%; }
/* Every column of figures in the panel: right-aligned so the digits line up down the
   column, and tabular so they stay lined up when the numbers change. The heading goes
   with the column, not with the left edge of the table. */
.sn_column_2, .sn_column_3, .sn_column_4, .sn_column_5, .sn_column_6,
.bicr_column.is_figure {
    text-align: right;
    padding-right: var(--sp-5);
    font-variant-numeric: tabular-nums;
}
.bic_row_cont:not(.bic_row_title) > .bicr_column.is_figure,
.bic_row_cont:not(.bic_row_title) > .sn_column_2,
.bic_row_cont:not(.bic_row_title) > .sn_column_3,
.bic_row_cont:not(.bic_row_title) > .sn_column_4,
.bic_row_cont:not(.bic_row_title) > .sn_column_5,
.bic_row_cont:not(.bic_row_title) > .sn_column_6 { color: var(--noc-ink); }
.sn_column_1 a { font-weight: bold; }
.sn_row_selected { box-shadow: inset 3px 0 0 var(--noc-accent); background: var(--noc-surface-2); }

/* Admin > System's Backups section (#sys_backups; a tab of its own until 25 September 2026): the stored list (when,
   size, contents, status + actions) and the problems box. Its facts are the page's own .sys_facts. */
.bkp_column_1 { width: 30%; padding-right: 10px; }
.bkp_column_2 { width: 16%; padding-right: 10px; }
.bkp_column_3 { width: 28%; padding-right: 10px; }
.bkp_column_4 { width: 26%; }
.bkp_column_wide { width: 54%; }
.bkp_name { font-weight: bold; }
/* The list's fold: the newest ten, then the rest behind the visitor box's own Show all button (.vis_more) */
.bkp_fold:not(.is_open) .is_extra { display: none; }
.bkp_fold.is_open .vis_more_btn i { transform: rotate(180deg); }
/* The short hash stays whole: split, it read as two numbers */
.bkp_hash { white-space: nowrap; }
.bkp_mono { font: var(--t-mono); font-size: 12px; }
.bkp_notice_actions { margin-top: 12px; }
.bkp_notice_actions .dashboard_form_button { margin-top: 0; }
.bkp_header_actions { display: flex; align-items: center; justify-content: flex-end; gap: 10px; }
/* The secondary button (section 4b); only its words staying on one line is said here */
.bkp_help_button { white-space: nowrap; }

/* "How backups work" popup: what the file is, the restore commands as a terminal, and the retention ladder */
.bkp_help_lead { margin: 0; font-size: 14px; line-height: 1.6; }
.bkp_help_facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 16px; margin-top: 16px; font-size: 13px; }
.bkp_fact { display: contents; }
.bkp_fact_label { color: var(--noc-ink-muted); }
.bkp_fact_value { overflow-wrap: anywhere; }
.bkp_help_title { margin: 24px 0 0; padding-top: 16px; border-top: 1px solid var(--noc-line); font-size: 16px; font-weight: bold; }
.bkp_help_note { margin: 8px 0 0; font-size: 14px; line-height: 1.6; color: var(--noc-ink-muted); }
.bkp_help_code { font: var(--t-mono); color: var(--noc-ink); }
.bkp_steps { margin: 14px 0 0; padding: 0; list-style: none; counter-reset: bkp_step; }
.bkp_step { position: relative; padding-left: 34px; counter-increment: bkp_step; }
.bkp_step + .bkp_step { margin-top: 14px; }
.bkp_step::before {
    content: counter(bkp_step);
    position: absolute;
    left: 0;
    top: 0;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    /* The primary fill: white on the indigo read 3.2:1 in the dark theme, where the indigo is lifted for text */
    background: var(--noc-btn-bg);
    color: var(--noc-btn-ink);
    font-size: 12px;
    font-weight: bold;
    line-height: 22px;
    text-align: center;
}
.bkp_step_title { font-weight: bold; font-size: 14px; line-height: 22px; }
.bkp_cmd { display: flex; align-items: flex-start; gap: 10px; margin-top: 8px; padding: 12px; border-radius: var(--noc-radius); background: var(--noc-inverse); }
.bkp_cmd_text {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    color: var(--noc-inverse-ink);
    font: var(--t-mono);
    font-size: 12px;
    line-height: 1.6;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    /* 3px of its own so the first line sits on the copy button's label rather than above it */
    padding: 3px 0;
}
.bkp_cmd_copy {
    flex: 0 0 auto;
    padding: 3px 10px;
    border: 1px solid var(--noc-chrome-line);
    border-radius: var(--noc-radius-sm);
    background: none;
    color: var(--noc-inverse-ink);
    font-family: inherit;
    font-size: 12px;
    font-weight: bold;
    line-height: 18px;
    cursor: pointer;
}
.bkp_cmd_copy:hover { border-color: var(--noc-teal); color: var(--noc-teal); }
.bkp_ladder { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; margin-top: 14px; }
.bkp_ladder_item { box-sizing: border-box; padding: 10px 12px; border: 1px solid var(--noc-line); border-radius: var(--noc-radius); background: var(--noc-surface-2); }
.bkp_ladder_count { font-size: 20px; font-weight: bold; line-height: 1.2; }
.bkp_ladder_label { margin-top: 2px; font-size: 13px; color: var(--noc-ink-muted); }
.bkp_ladder_off { opacity: 0.5; }

/* Admin CDN nodes: fleet list with a live status mark, and the engine settings group in the node form */
/* One table of locations and nodes (1 October 2026): a location's row is the group, on the inset ground as the docs'
   category bands are, and its servers follow it as child rows (.adm_row_child, the monitors' nesting just below on the
   same page). The location's name takes the node name and address columns together (30 + 24), so its Status and
   actions line up with its servers'. */
.loc_row { background: var(--noc-surface-2); }
.loc_column_1 { width: 54%; padding-right: 10px; overflow: visible; }
.loc_column_3 { width: 46%; }
.adm_row_child .node_column_1 { padding-left: 24px; }
/* Two buttons in the header: each as wide as its words (the kit's button is 100% wide, so two in one row squeezed each
   other onto two lines), and on a phone (below) the pair shares the row */
.net_head_actions { display: flex; gap: var(--sp-2); }
.net_head_actions .dashboard_form_button { width: auto; white-space: nowrap; }
.node_mark.is_disabled { background: var(--noc-line); box-shadow: 0 0 0 3px var(--noc-surface-2); }
.node_column_1 { width: 30%; padding-right: 10px; overflow: visible; }
.node_column_2 { width: 24%; padding-right: 10px; }
.node_column_3 { width: 46%; }
/* Above the tablet width the status text gives way and a row's four icons stay on one line: a long "Updates waiting"
   line used to push the fourth onto a line of its own, so the fleet's rows ended in two shapes */
.node_column_3 .row_end_value, .loc_column_3 .row_end_value { flex: 1 1 auto; min-width: 0; }
.node_column_3 .row_actions, .loc_column_3 .row_actions { flex: 0 0 auto; flex-wrap: nowrap; margin-left: var(--sp-3); }
.node_name { display: flex; align-items: center; gap: 10px; font-weight: bold; }
/* The fleet dot is the status page's dot at 10px. "Online" is --st-up and not the teal
   it used to be: teal means "you can interact with this" everywhere else in the panel,
   and a dot you cannot click has no business wearing it. Green here also means the same
   thing as green on status.cachegenie.com, which is the point. */
.node_mark { flex: 0 0 10px; width: 10px; height: 10px; border-radius: 50%; background: var(--st-pending); box-shadow: 0 0 0 3px var(--st-pending-soft); }
.node_mark.is_online { background: var(--st-up); box-shadow: 0 0 0 3px var(--st-up-soft); }
.node_mark.is_offline { background: var(--st-error); box-shadow: 0 0 0 3px var(--st-error-soft); }
.node_sub { font-size: 13px; color: var(--noc-ink-muted); margin-top: 3px; overflow-wrap: anywhere; }
.node_seen { font-weight: 700; color: var(--noc-ink); }
.node_seen.is_offline { color: var(--st-error); }
.node_seen.is_disabled { color: var(--noc-ink-muted); }
.node_seen_meta { font-weight: normal; font-size: 13px; color: var(--noc-ink-muted); }
.node_form_split { display: flex; gap: 16px; }
.node_form_split > div { flex: 1 1 0; min-width: 0; }
/* (.node_key_button and .dfb_secondary are the kit's secondary button, section 4b) */
.node_paths_grid { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 16px; margin-top: 6px; font-size: 14px; }
.node_paths_label { color: var(--noc-ink-muted); }
.node_paths_value { font: var(--t-mono); overflow-wrap: anywhere; }
/* The status column's three lines: the state word carries its colour, everything else is muted unless it needs a
   look (a run in progress in indigo, a failure in red, out of service or updates waiting in amber) */
.node_line_run.is_running, .node_line_run.is_rebooting, .node_line_run.is_verifying, .node_line_run.is_pending, .node_line_run.is_draining { color: var(--noc-indigo-ink); }
.node_line_run.is_failed { color: var(--st-error); font-weight: bold; }
/* The empty state of a list (no zones, no traffic, no backups): the hook the pages use, padded and muted */
/* One empty state, not three. It is dashed rather than solid so it reads as a space
   waiting to be filled and not as a box that failed to load. */
.noc_empty_state, .st_empty, .docs_empty, .bic_no_invoices {
    padding: var(--noc-pad-box);
    border: 1px dashed var(--noc-line-2);
    border-radius: var(--noc-radius);
    color: var(--noc-ink-muted);
    font: var(--t-body);
}
.bic_no_invoices { border: 0; }
/* Inside a list that fills its box edge to edge (the rows carry their own padding) the dashed frame drew a second
   edge a hairline inside the box's own, so there it is the sentence alone, at the rows' inset. */
.billing_invoices_cont > .noc_empty_state, .dashboard_box_cont > .noc_empty_state {
    border: 0;
    border-radius: 0;
}
.node_maint { color: var(--st-warn); font-weight: 700; font-size: 13px; }
.node_updates_waiting { color: var(--st-warn); }
.node_updates_error { color: var(--st-error); }
/* (A row action's size, weight and disabled look are the kit's icon button, section 4c: the 17px, 18px and 20px bold
   glyphs the node, zone and status lists each set for themselves went with it.) */
/* Node setup popup: state, progress, facts and the log. The state is a chip: the maintenance fill while a run is in
   progress, the ok tint when it is done and the down fill when it failed, each measured against its ink in both
   themes (7.59:1 and 5.33:1, 5.06:1 and 5.88:1, 4.64:1). The indigo, the ok green and the error red it used were
   text colours lifted for the dark theme, and white on them read 3.24:1, 2.09:1 and 3.22:1 there. */
.nsp_head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.nsp_state { display: inline-block; padding: 4px 10px; border-radius: var(--noc-radius-sm); font-weight: bold; font-size: 13px; background: var(--noc-surface-2); color: var(--noc-ink); }
.nsp_state.is_running, .nsp_state.is_rebooting, .nsp_state.is_verifying, .nsp_state.is_pending, .nsp_state.is_draining { background: var(--st-maint); color: var(--st-ink-on-fill); }
.nsp_state.is_ready { background: var(--st-ok-soft); color: var(--st-ok); }
.nsp_state.is_failed { background: var(--st-down); color: var(--st-ink-on-fill); }
.nsp_meta { font-size: 13px; color: var(--noc-ink-muted); overflow-wrap: anywhere; }
.nsp_bar { height: 8px; margin-top: 14px; border-radius: var(--noc-radius-sm); background: var(--noc-surface-2); overflow: hidden; }
.nsp_bar_fill { height: 100%; width: 0; border-radius: var(--noc-radius-sm); background: var(--noc-indigo); transition: width 0.4s ease; }
.nsp_bar_fill.is_ready { background: var(--st-ok); }
.nsp_bar_fill.is_failed { background: var(--st-error); }
.nsp_message { margin-top: 10px; font-weight: bold; overflow-wrap: anywhere; }
.nsp_message:empty { display: none; }
.nsp_facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px 16px; margin-top: 12px; font-size: 13px; }
.nsp_facts:empty { display: none; }
.nsp_fact { display: flex; gap: 8px; min-width: 0; }
.nsp_fact_label { flex: 0 0 96px; color: var(--noc-ink-muted); }
.nsp_fact_value { overflow-wrap: anywhere; }
.nsp_log { margin: 14px 0 0; padding: 12px; max-height: 55vh; overflow: auto; background: var(--noc-inverse); color: var(--noc-inverse-ink); border-radius: var(--noc-radius); font: var(--t-mono); font-size: 12px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.nsp_confirm { display: none; margin-top: 16px; padding: 14px; border: 1px solid var(--noc-line); border-radius: var(--noc-radius); background: var(--noc-surface-2); }
.nsp_updates { margin-bottom: 10px; font-size: 13px; }
/* (a disabled button here is the kit's disabled look, section 4b) */
.nsp_buttons { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 16px; }
.nsp_buttons .dashboard_form_button_right { margin-top: 0; }

/* -----------------------------------------------------------------------------
   9. State, and the status page it came from (status.cachegenie.com)
   The hero carries the overall state in its colour; the service list keeps every row on
   one line with the 90-day uptime in a fixed column; a group row (the CDN) opens to its
   locations; incidents are a timeline. The vocabulary below is shared with the admin.

   State: dots, pills, rules
   One vocabulary, two shapes, and a rule about which to use.

   The FILLED PILL is for the public status page, where a customer expects to see the
   word Operational in green and the convention is worth more than the density.

   Inside the NOC, a dense list uses the QUIET form: a dot and the word in muted ink.
   A page of green pills is the thing the panel is trying not to be - it colours a
   plain fact, so nothing stands out and a real failure has to compete. Down, Degraded
   and Maintenance keep the fill, because those are the exceptions the page exists to
   surface. Add .st_admin to a list (or .is_quiet to one pill) to get it.
   -------------------------------------------------------------------------- */
.st_pill {
    display: inline-block;
    padding: 3px 10px;
    border-radius: var(--noc-radius-sm);
    font: var(--t-micro);
    font-weight: 700;
    line-height: 18px;
    white-space: nowrap;
    background: var(--st-pending);
    color: var(--noc-chrome-ink-strong);
}
/* The green and the grey are light fills, and white on them measured 2.09:1 and 2.65:1 (the word Operational, the one
   a customer opens the page to read). They take the navy ink the amber already did: 7.99:1 and 6.28:1. Down and
   Maintenance are dark fills and keep white (4.64:1 and 7.59:1). */
.st_pill.st_up { background: var(--st-up); color: var(--st-ink-on-warm); }
.st_pill.st_degraded { background: var(--st-degraded); color: var(--st-ink-on-warm); }
.st_pill.st_down { background: var(--st-down); }
.st_pill.st_maint { background: var(--st-maint); }
.st_pill.st_pending { background: var(--st-pending); color: var(--st-ink-on-warm); }
.st_pill.st_planned { background: var(--st-planned); }

.st_admin .st_pill.st_up, .st_admin .st_pill.st_pending,
.bic_row_cont .st_pill.st_up, .bic_row_cont .st_pill.st_pending,
.st_pill.st_planned,
.st_pill.is_quiet {
    background: transparent;
    color: var(--noc-ink-muted);
    font-weight: 500;
    font-size: 13px;
    padding: 2px 0 2px 17px;
    border-radius: 0;
    position: relative;
}
.st_admin .st_pill.st_up::before, .st_admin .st_pill.st_pending::before,
.bic_row_cont .st_pill.st_up::before, .bic_row_cont .st_pill.st_pending::before,
.st_pill.st_planned::before,
.st_pill.is_quiet::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    margin-top: -4px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--st-pending);
}
.st_admin .st_pill.st_up::before, .bic_row_cont .st_pill.st_up::before { background: var(--st-up); }
.st_pill.st_planned::before { background: var(--st-planned); }

.st_dot {
    flex: 0 0 12px;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--st-pending);
    box-shadow: 0 0 0 3px var(--st-pending-soft);
}
.st_dot.st_up { background: var(--st-up); box-shadow: 0 0 0 3px var(--st-up-soft); }
.st_dot.st_degraded { background: var(--st-degraded); box-shadow: 0 0 0 3px var(--st-degraded-soft); }
.st_dot.st_planned { background: var(--st-planned); box-shadow: 0 0 0 3px var(--st-planned-soft); }
.st_dot.st_down { background: var(--st-down); box-shadow: 0 0 0 3px var(--st-down-soft); }
.st_dot.st_maint { background: var(--st-maint); box-shadow: 0 0 0 3px var(--st-maint-soft); }

/* The same rule as the pills: navy on the two light fills (the default is the grey one), white on the two dark ones.
   .st_hero_title and .st_hero_sub are the names the unavailable state's hero was written with; they are the state
   line and the meta line under other names. */
.st_hero {
    border-radius: var(--noc-radius);
    padding: 28px 32px;
    color: var(--st-ink-on-warm);
    background: var(--st-pending);
}
.st_hero.st_up { background: var(--st-up); color: var(--st-ink-on-warm); }
.st_hero.st_degraded { background: var(--st-degraded); color: var(--st-ink-on-warm); }
.st_hero.st_down { background: var(--st-down); color: var(--st-ink-on-fill); }
.st_hero.st_maint { background: var(--st-maint); color: var(--st-ink-on-fill); }
.st_hero_state, .st_hero_title { font-size: 30px; font-weight: 900; line-height: 1.15; letter-spacing: -0.01em; }
.st_hero_meta, .st_hero_sub { margin-top: 8px; font-size: 15px; opacity: 0.88; }
.st_hero_incident {
    margin-top: 20px;
    padding: 18px 20px;
    border-radius: var(--noc-radius);
    background: var(--noc-surface);
    color: var(--noc-ink);
}
.st_incident_facts {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 16px;
    font-size: 14px;
    color: var(--noc-ink-muted);
}
.st_incident_body { margin-top: 12px; line-height: 1.55; }
.st_incident_body p:first-child { margin-top: 0; }
.st_incident_body p:last-child { margin-bottom: 0; }

.st_section_head { margin: 32px 0 14px; }
.st_section_title { margin: 0; font-size: 22px; font-weight: bold; line-height: 1.2; }
.st_section_sub { margin-top: 4px; font-size: 14px; color: var(--noc-ink-muted); }
.st_empty_inline { margin: 12px 20px 12px 52px; padding: 12px 16px; }

.st_list { border: 1px solid var(--noc-line); border-radius: var(--noc-radius); overflow: hidden; }
.st_row {
    display: flex;
    align-items: center;
    gap: 14px;
    width: 100%;
    padding: 16px 20px;
    border-top: 1px solid var(--noc-line);
    background: var(--noc-surface);
    color: var(--noc-ink);
    font: inherit;
    text-align: left;
    box-sizing: border-box;
}
.st_list > .st_row:first-child { border-top: 0; }
.st_row_group { cursor: pointer; border-left: 0; border-right: 0; border-bottom: 0; }
.st_row_group:hover { background: var(--noc-surface-2); }
.st_row_group:focus-visible { outline: 2px solid var(--noc-accent-ink); outline-offset: -2px; }
.st_group_body { background: var(--noc-surface-2); }
.st_group_body .st_row { background: transparent; padding-left: 52px; }
.st_name { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.st_name_main { font-weight: bold; font-size: 16px; overflow-wrap: anywhere; }
.st_name_sub { font-size: 13px; color: var(--noc-ink-muted); }
.st_uptime { flex: 0 0 auto; min-width: 84px; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
.st_uptime_none { font-weight: normal; font-size: 13px; color: var(--noc-ink-muted); }
.st_row .st_pill { flex: 0 0 auto; min-width: 96px; text-align: center; }
.st_chevron { flex: 0 0 16px; text-align: center; color: var(--noc-ink-muted); transition: transform 0.2s ease; }
.st_row_group.is_open .st_chevron { transform: rotate(180deg); }

.st_timeline { display: flex; flex-direction: column; gap: 6px; }
.st_incident { display: flex; gap: 20px; padding: 14px 0; }
.st_incident + .st_incident { border-top: 1px solid var(--noc-line); }
.st_incident_date { flex: 0 0 64px; display: flex; flex-direction: column; align-items: center; padding-top: 2px; line-height: 1.1; }
.st_date_day { font-size: 26px; font-weight: 900; }
.st_date_month { font-size: 12px; font-weight: 600; color: var(--noc-ink-muted); white-space: nowrap; }
.st_incident_main { flex: 1 1 auto; min-width: 0; padding-left: 16px; border-left: 3px solid var(--st-type, var(--st-pending)); }
.st_incident.so_maintenance { --st-type: var(--st-maint); }
.st_incident.so_partial { --st-type: var(--st-degraded); }
.st_incident.so_major { --st-type: var(--st-down); }
.st_incident.so_up { --st-type: var(--st-up); }
.st_incident_title { margin: 0 0 8px; font-size: 18px; font-weight: bold; line-height: 1.25; }

/* Admin > Status: the monitor list */
.st_admin .adm_column_1 { width: 30%; padding-right: 10px; }
.st_admin .adm_column_2 { width: 30%; padding-right: 10px; }
/* Uptime is a column of figures read down, so it is tabular and right-aligned with
   its heading over it. */
.st_admin .adm_column_3 {
    width: 14%;
    text-align: right;
    padding-right: var(--sp-5);
    font-variant-numeric: tabular-nums;
    color: var(--noc-ink);
}
.st_admin .adm_column_4 { width: 26%; }
.st_admin .bicr_column { white-space: normal; overflow: visible; text-overflow: clip; }
.adm_name { font-weight: bold; }
.row_sub { font-size: 13px; color: var(--noc-ink-muted); margin-top: 3px; overflow-wrap: anywhere; }
.adm_row_child { box-shadow: inset 4px 0 0 var(--noc-line); }
/* The padding is inside the width now that everything is border-box; the old
   calc(30% - 24px) subtracted it a second time and shifted every column in the
   row 24px left of the rows above it. */
.adm_row_child .adm_column_1 { padding-left: 24px; }
/* Admin > Network: the outage history under the monitors, its own family (34 + 22 + 22 + 22) */
.otg_column_1 { width: 34%; padding-right: 10px; }
.otg_column_2, .otg_column_3 { width: 22%; padding-right: 10px; }
.otg_column_4 { width: 22%; }
.dfi_small { height: 34px; line-height: 32px; font-size: 14px; width: auto; min-width: 130px; }
.manual_state_form { display: inline-block; margin-right: 12px; }
.node_geodns_error { color: var(--st-error); }

/* ---------------------------------------------------------------------------
  10. Docs site chrome (the article body itself is docs_body.css)
   A search-led home with every article in view, a section sidebar beside the reading column on an article, and
   developer markup inside articles (method + path bars, parameter rows, navy code blocks). Tokens below are the
   reading palette; the chrome tokens above stay in charge of everything else.
   --------------------------------------------------------------------------- */
.docs_sheet { max-width: var(--noc-max); }
.docs_page { font-size: var(--doc-body); line-height: 1.6; }
.docs_page a, .docs_page a:visited { color: inherit; font-weight: inherit; text-decoration: none; }

/* Search form (big on the home, compact elsewhere) */
.docs_search { position: relative; display: flex; align-items: center; }
.docs_search_icon { position: absolute; left: 14px; color: var(--noc-ink-muted); font-size: 15px; pointer-events: none; }
.docs_search_input {
    box-sizing: border-box;
    width: 100%;
    height: 44px;
    padding: 0 96px 0 40px;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
    background: var(--noc-surface);
    color: var(--noc-ink);
    font: inherit;
    font-size: 15px;
}
/* The field's focus edge is the teal as ink, as on every other field (section 4a) */
.docs_search_input:focus { outline: none; border-color: var(--noc-accent-ink); box-shadow: 0 0 0 3px var(--noc-accent-ring); }
.docs_search_go {
    position: absolute;
    right: 5px;
    top: 5px;
    bottom: 5px;
    padding: 0 14px;
    border: 0;
    border-radius: var(--noc-radius-sm);
    background: var(--noc-inverse);
    color: var(--noc-inverse-ink);
    font: inherit;
    font-size: 14px;
    font-weight: bold;
    cursor: pointer;
}
.docs_search_go:hover { background: var(--noc-navy-raised); }
.docs_search.is_big .docs_search_input { height: 56px; font-size: 18px; padding-left: 48px; padding-right: 112px; }
.docs_search.is_big .docs_search_icon { left: 18px; font-size: 18px; }
.docs_search.is_big .docs_search_go { padding: 0 20px; font-size: 15px; }

/* Home */
.docs_hero { padding: 24px 0 36px; max-width: 760px; }
.docs_hero_title { margin: 0; font-size: 36px; font-weight: 900; line-height: 1.1; letter-spacing: -0.01em; }
.docs_hero_sub { margin: 10px 0 20px; color: var(--noc-ink-muted); font-size: 17px; }
.docs_grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 48px;
    border-top: 1px solid var(--noc-line);
}
.docs_cat { padding: 24px 0 8px; border-bottom: 1px solid var(--noc-line); min-width: 0; }
/* No rule under the last row: with an even count that is the last two, with an odd count the lone last one.
   The help block's own border-top closes the grid instead. */
.docs_cat:last-child, .docs_cat:nth-last-child(2):nth-child(odd) { border-bottom: 0; }
.docs_cat_title { margin: 0; font-size: 20px; font-weight: bold; display: flex; align-items: baseline; gap: 10px; }
.docs_page .docs_cat_title a:hover { text-decoration: underline; text-decoration-color: var(--noc-teal); text-underline-offset: 3px; }
.docs_cat_count { font-size: 13px; font-weight: 600; color: var(--noc-ink-muted); }
.docs_cat_desc { margin: 6px 0 0; color: var(--noc-ink-muted); font-size: 14px; max-width: 52ch; }
.docs_cat_none { margin: 14px 0 12px; color: var(--noc-ink-muted); font-size: 14px; }
.docs_links { list-style: none; margin: 14px 0 12px; padding: 0; }
.docs_links li { padding: 0; }
.docs_page .docs_links a {
    display: block;
    padding: 7px 0 7px 14px;
    border-left: 2px solid var(--noc-line);
    font-weight: 600;
    font-size: 15px;
}
.docs_page .docs_links a:hover { border-left-color: var(--noc-teal); color: var(--noc-navy); }

/* Category, search and article heads */
.docs_crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 14px; color: var(--noc-ink-muted); margin-bottom: 18px; }
/* A crumb is a 14px line, 22px tall: the padding gives a thumb a 30px target and the negative margin keeps the
   row exactly where it was (27 September 2026, the docs site's visual pass) */
.docs_page .docs_crumbs a { display: inline-block; padding: 4px 0; margin: -4px 0; }
.docs_page .docs_crumbs a:hover { text-decoration: underline; }
.docs_head { padding-bottom: 24px; margin-bottom: 8px; }
.docs_head .docs_search { margin-top: 18px; }
.docs_title { margin: 0; font-size: 32px; font-weight: 900; line-height: 1.15; letter-spacing: -0.01em; overflow-wrap: anywhere; }
.docs_lede { margin: 10px 0 0; color: var(--noc-ink-muted); font-size: 17px; }
.docs_page .docs_chip, .docs_page .docs_chip:visited, .dadm_group .docs_chip {
    display: inline-block;
    padding: 3px 10px;
    border-radius: var(--noc-radius-sm);
    background: var(--doc-inline-bg);
    /* The text form of the indigo: the fill form measured 4.06:1 on this tint in the dark theme (the Admin > Docs
       group counts); the two are one value in light, so the docs site, pinned light, is unchanged */
    color: var(--noc-indigo-ink);
    font-size: 13px;
    font-weight: bold;
    text-decoration: none;
    line-height: 18px;
}
.docs_page a.docs_chip:hover { background: var(--noc-indigo); color: var(--noc-chrome-ink-strong); }

/* Result and article lists */
.docs_results { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--noc-line); }
.docs_result { padding: 18px 0; border-bottom: 1px solid var(--noc-line); }
.docs_page .docs_result_title { font-size: 18px; font-weight: bold; }
.docs_page .docs_result_title:hover { text-decoration: underline; text-decoration-color: var(--noc-teal); text-underline-offset: 3px; }
.docs_result_meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; margin-top: 8px; font-size: 13px; color: var(--noc-ink-muted); }
.docs_result_snippet { margin: 10px 0 0; font-size: 15px; color: var(--noc-ink); }
.docs_result_snippet b, .docs_result_title b { background: var(--st-degraded-soft); border-radius: 2px; padding: 0 2px; }

/* Article layout: sidebar + reading column */
.docs_layout { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 48px; align-items: start; }
.docs_side { position: sticky; top: calc(var(--noc-top-height) + 24px); }
.docs_side .docs_search { margin-bottom: 20px; }
.docs_side_title { font-size: 13px; font-weight: bold; text-transform: none; color: var(--noc-ink-muted); margin-bottom: 8px; }
.docs_page .docs_side_title a:hover { text-decoration: underline; }
.docs_side_list { list-style: none; margin: 0; padding: 0; border-left: 2px solid var(--noc-line); }
.docs_side_list li { margin: 0; }
.docs_page .docs_side_list a {
    display: block;
    padding: 7px 12px;
    margin-left: -2px;
    border-left: 2px solid transparent;
    font-size: 14px;
    color: var(--noc-ink);
    overflow-wrap: anywhere;
}
.docs_page .docs_side_list a:hover { border-left-color: var(--noc-line); color: var(--noc-navy); }
.docs_page .docs_side_list li.is_current a { border-left-color: var(--noc-teal); font-weight: bold; color: var(--noc-navy); }
.docs_side_count { float: right; margin-left: 10px; font-size: 12px; font-weight: 600; color: var(--noc-ink-muted); }
/* The same 30px target as a crumb: 4px of padding, the 18px above it and the 4px below taken off the margins */
.docs_page .docs_side_all { display: inline-block; margin: 14px 0 -4px; padding: 4px 0; font-size: 14px; font-weight: 600; color: var(--noc-ink-muted); }
.docs_page .docs_side_all:hover { color: var(--noc-navy); }
.docs_main { min-width: 0; max-width: 820px; }
.docs_meta { margin-top: 8px; font-size: 14px; color: var(--noc-ink-muted); }
.docs_outline { margin: 24px 0 0; padding: 16px 20px; border: 1px solid var(--noc-line); border-radius: var(--noc-radius); background: var(--noc-surface-2); }
.docs_outline_title { font-weight: bold; font-size: 14px; margin-bottom: 8px; }
.docs_outline ol { margin: 0; padding-left: 20px; columns: 2; column-gap: 32px; font-size: 14px; }
.docs_outline li { break-inside: avoid; padding: 2px 0; }
.docs_page .docs_outline a:hover { text-decoration: underline; }
.docs_pager { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 40px; padding-top: 24px; border-top: 1px solid var(--noc-line); }
.docs_page .docs_pager a { display: block; padding: 14px 16px; border: 1px solid var(--noc-line); border-radius: var(--noc-radius); font-weight: bold; overflow-wrap: anywhere; }
.docs_page .docs_pager a span { display: block; font-size: 12px; font-weight: 600; color: var(--noc-ink-muted); margin-bottom: 4px; }
.docs_page .docs_pager a:hover { border-color: var(--noc-teal); }
.docs_pager_next { text-align: right; }

/* Article body: reading measure, headings, lists, tables, code */
.docs_copy {
    padding: 4px 10px;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius-sm);
    background: var(--noc-surface);
    color: var(--noc-ink-muted);
    font: inherit;
    font-size: 12px;
    font-weight: bold;
    cursor: pointer;
    flex: 0 0 auto;
}
.docs_copy:hover { color: var(--noc-navy); border-color: var(--noc-ink-muted); }
.docs_copy_block { position: absolute; top: 10px; right: 10px; border-color: var(--noc-inverse-line); background: var(--noc-inverse-wash); color: var(--noc-chrome-ink); }
.docs_copy_block:hover { background: var(--noc-inverse-wash-2); color: var(--noc-chrome-ink-strong); border-color: var(--noc-inverse-line-2); }

/* Foot of every docs page: the message the button is about to open, shown as a message.
   The fields sit on the grey ground, the action sits on white below a hairline, which
   gives the card two tones without a gradient or a shadow; the values are in the same
   monospace as the code blocks, because that is what they are, literal text.
   (The "Endpoint bar" comment that used to head this block went with the endpoint rules
   themselves into docs_body.css, which is where the editor needs them.) */
.docs_help { margin-top: 48px; }
.docs_help_title { margin: 0 0 12px; font-size: 19px; font-weight: bold; }
.docs_help_card { border: 1px solid var(--noc-line); border-radius: var(--noc-radius); background: var(--noc-surface-2); overflow: hidden; }
.docs_help_fields {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 8px 24px;
    padding: 18px 20px;
    font-size: 14px;
    line-height: 1.5;
}
.docs_help_label { color: var(--noc-ink-muted); }
.docs_help_value { font-family: var(--doc-mono); font-size: 13px; color: var(--noc-ink); overflow-wrap: anywhere; }
.docs_help_foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px 24px;
    padding: 14px 20px;
    border-top: 1px solid var(--noc-line);
    background: var(--noc-surface);
}
.docs_help_note { margin: 0; flex: 1 1 280px; min-width: 0; font-size: 14px; color: var(--noc-ink-muted); }
.docs_page .docs_help_button, .docs_page .docs_help_button:visited {
    display: inline-block;
    box-sizing: border-box;
    flex: 0 0 auto;
    padding: 0 22px;
    height: 44px;
    line-height: 44px;
    border-radius: var(--noc-radius);
    background: var(--noc-inverse);
    color: var(--noc-inverse-ink);
    font-size: 15px;
    font-weight: bold;
    white-space: nowrap;
}
.docs_page .docs_help_button:hover { background: var(--noc-navy-raised); }

/* Admin > Docs: categories and the article list */
.dadm_column_1 { width: 34%; padding-right: 10px; }
.dadm_column_2 { width: 26%; padding-right: 10px; }
.dadm_column_3 { width: 12%; }
.dadm_column_4 { width: 28%; }
.dadm_group { background: var(--noc-surface-2); font-weight: bold; padding: 10px 20px; border-top: 1px solid var(--noc-line); border-bottom: 1px solid var(--noc-line); display: flex; align-items: center; gap: 10px; }
.dadm_group .docs_chip { font-weight: 600; }
.dadm_link, .dadm_link:visited { font-weight: normal; text-decoration: none; }
/* The pill is 22px by design, so the button wears an 11px transparent border on a matching negative margin: the
   painted pill keeps the status page's shape and size while the button a thumb has to hit is 44px tall (a pseudo
   element would be clipped by the cell; 27 September 2026, the NOC's visual pass) */
.dadm_pill_button { border: 11px solid transparent; border-radius: calc(var(--noc-radius-sm) + 11px); background-clip: padding-box; margin: -11px 0; cursor: pointer; font-family: inherit; }
.dadm_pill_button:hover { opacity: 0.85; }
.dadm_link:hover { text-decoration: underline; }
.dadm_slug_hint { font-size: 13px; color: var(--noc-ink-muted); margin-top: 6px; }
/* (the editor's frame is the kit's, section 4g) */
.da_toolbar_row { display: flex; gap: 16px; }
.da_toolbar_row > div { flex: 1 1 0; min-width: 0; }
.da_toolbar_row > .da_order { flex: 0 0 150px; }
.da_form_foot { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 20px; }
.da_form_foot .dashboard_form_button_right { margin-top: 0; }
.da_publish { display: flex; align-items: center; gap: 12px; font-weight: bold; }
/* (.da_preview_button is the kit's secondary button, section 4b) */
.da_help { font-size: 13px; color: var(--noc-ink-muted); margin-top: 8px; }

/* Article preview page (Admin > Docs > Preview) */
.docs_preview_note { margin-bottom: 18px; padding: 10px 14px; border-radius: var(--noc-radius); background: var(--st-degraded-soft); font-size: 14px; }

/* A toggle that cannot be used yet (Force SSL before a certificate exists) */
.toggle_disabled { opacity: 0.45; cursor: not-allowed; }
/* A field a setting elsewhere has taken over: it still holds and still posts its saved value, so it is
   greyed rather than emptied, and read-only rather than disabled (a disabled input posts nothing, and a
   save that writes the column unconditionally would wipe it). Today: Host Header, while Forward Host
   Header is on.
   Muted with explicit colours rather than opacity. The value in the field is the customer's own saved
   hostname, which they still need to read, and opacity fades it against whatever happens to be behind:
   the ink at the 0.55 opacity this first used came out at 3.69:1 on the white sheet, under
   the 4.5:1 AA needs for normal text. Muted ink on the inset surface clears it in both
   themes, and it is measured in the browser rather than calculated. */
.dashboard_form_input.dfi_readonly {
    color: var(--noc-ink-muted);
    background-color: var(--noc-surface-2);
    cursor: not-allowed;
}
.toggle_disabled .slider-small, .toggle_disabled .slider { cursor: not-allowed; }

/* ---------------------------------------------------------------------------
   Mobile tab bar (phones and small tablets)
   --------------------------------------------------------------------------- */
.noc_tabbar {
    display: none;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    background-color: var(--noc-navy);
    border-top: 1px solid var(--noc-chrome-line);
    padding-bottom: env(safe-area-inset-bottom);
}
.noc_tabbar_inner {
    display: flex;
    justify-content: space-around;
    align-items: stretch;
    height: var(--noc-tab-height);
}
.noc_tab_item {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    color: var(--noc-chrome-ink);
    text-decoration: none;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.01em;
    position: relative;
}
.noc_tab_item i { font-size: 19px; }
.noc_tab_item.is_active { color: var(--noc-chrome-ink-strong); }
.noc_tab_item.is_active::before {
    content: "";
    position: absolute;
    top: 0;
    left: 22%;
    right: 22%;
    height: 3px;
    border-radius: 0 0 3px 3px;
    background-color: var(--noc-teal);
}

/* ---------------------------------------------------------------------------
   Zone list: the maintenance marker
   A customer with twenty zones has to see which ones are paused without
   opening each. It reuses --st-maint, the colour maintenance already has on
   the status page, so the two surfaces mean the same thing by the same colour.
   --------------------------------------------------------------------------- */
.zlist_maint {
    display: inline-block;
    margin-left: 0;
    padding: 2px 8px;
    border-radius: var(--noc-radius-sm);
    background: var(--st-maint-soft);
    color: var(--noc-indigo-ink);
    font-size: 12px;
    font-weight: 700;
    line-height: 18px;
    vertical-align: 1px;
}

/* -----------------------------------------------------------------------------
  11. Modals
   One shell for all thirty. There used to be three patterns: per-id blocks here, a
   second set in an inline <style> on the admin page, and .noc_popup_box. The ids were
   hand-sized at 500 / 520 / 600 / 800 / 900 / 1100px, and the id list in the admin
   page went stale the moment a new dialog was added.

   The visibility contract is deliberately untouched: display stays in CSS, so PHP's
   thirteen `style="display:block"` force-opens and every jQuery show/hide/fadeIn
   still win as inline styles. Only the appearance moved. That is also why this is not
   a <dialog>, which would swap `display` for `open` and break both at once.
   -------------------------------------------------------------------------- */
[id$="_popup_wrap"] {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 60;
    background-color: var(--noc-scrim);
}
/* :not(.noc_popup_box) matters and is not tidiness. #purge_popup carries both the id
   suffix and the class, so without it the shell's `display: none` lands on the inline
   disclosure too, at the same specificity as .npb_force_show but later in the file -
   and the Purge popup could never open again. A silent one: the button just does
   nothing. */
[id$="_popup"]:not(.noc_popup_box) {
    display: none;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    margin: 0;
    padding: 0;
    z-index: 61;
    width: var(--modal-w, 620px);
    max-width: calc(100vw - 24px);
    max-height: 90vh;
    overflow: auto;
    background: var(--noc-surface);
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
}
/* Three sizes instead of six hand-set widths. A dialog with no class gets 620. */
.mw_confirm { --modal-w: 460px; }
.mw_wide { --modal-w: 900px; }
.mw_editor { --modal-w: 1100px; }

/* A dialog is one frame. The pages nest a box (.dashboard_box_cont: the title and the body) inside the dialog, and the
   box's own hairline and corners drew a second edge a pixel inside the dialog's, all the way round, so the box gives
   them up there. The kit's own dialog (the confirm, loading.php) has no box inside it. */
[id$="_popup"]:not(.noc_popup_box) > .dashboard_box_cont,
.noc_popup_box > .dashboard_box_cont {
    border: 0;
    border-radius: 0;
}

/* .noc_popup_box stays a separate thing on purpose: it is an in-flow disclosure inside
   the sheet rather than a dialog, and PHP opens it by adding .npb_force_show rather
   than an inline style. It wears the same surface so the two read as one family.
   It must stay AFTER the attribute rules: #purge_popup matches both. */
.noc_popup_box {
    background: var(--noc-surface);
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius);
}

/* ---------------------------------------------------------------------------
   Laptop and below
   --------------------------------------------------------------------------- */
/* -----------------------------------------------------------------------------
  12. Responsive - phone <= 640, tablet <= 900, laptop <= 1200
   -------------------------------------------------------------------------- */
@media (max-width: 1200px) {
    :root { --noc-gutter: 16px; }
}

/* ---------------------------------------------------------------------------
   Tablet: labels stay, spacing tightens
   --------------------------------------------------------------------------- */
@media (max-width: 900px) {
    /* Node list: four action icons beside the status text leave it a word per line at this width, so they go under it */
    .node_column_3.row_end_column, .loc_column_3.row_end_column { flex-wrap: wrap; }
    .node_column_3 .row_end_value, .loc_column_3 .row_end_value { width: 100%; }
    .node_column_3 .row_actions, .loc_column_3 .row_actions { width: 100%; justify-content: flex-start; margin-top: 8px; }
    .noc_nav_item { padding: 0 10px; font-size: 14px; }
    .noc_util_item span { display: none; }
    .noc_util_item { padding: 10px; }
    .noc_sheet { padding: 18px; }
    .docs_layout { grid-template-columns: 220px minmax(0, 1fr); gap: 32px; }
    .docs_grid { gap: 0 32px; }
    .dbc_header { padding: var(--sp-3) var(--sp-4); }
    .stats_chart_cont { height: 320px; }

    .dashboard_stats_overview { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* Wrapped to two per row: the divider is between columns and between rows. */
    .dso_item { width: auto; border-left: 1px solid var(--noc-line); border-top: 0; }
    .dso_item:nth-child(odd) { border-left: 0; }
    .dso_item:nth-child(n+3) { border-top: 1px solid var(--noc-line); }
    .cdn_settings_1 { width: 78%; }
    .cdn_settings_2 { width: 22%; }
    .asf_email_notice { width: 100%; }
}

/* ---------------------------------------------------------------------------
   Phone: top bar becomes logo + utilities, primary sections move to the bottom tab bar,
   tables stack into labelled rows, popups go full width
   --------------------------------------------------------------------------- */
@media (max-width: 640px) {
    :root { --noc-gutter: 12px; --noc-top-height: 56px; }
    /* index.php sets has_tabbar on exactly the pages whose header draws the bar (the logged-in NOC pages and the
       docs and status hosts), so the space is reserved by that class alone: scoping it to site_dashboard left the
       docs and status footers under the bar. */
    body.has_tabbar { padding-bottom: calc(var(--noc-tab-height) + env(safe-area-inset-bottom)); }
    .noc_nav { display: none; }
    .noc_tabbar { display: block; }
    .noc_logo { margin-right: 0; }
    .noc_logo img { height: 34px; }
    .noc_main { padding: var(--noc-gutter); }
    .noc_sheet { padding: 14px; }
    .aptc_title { font-size: 20px; }
    .account_page_title_cont { align-items: flex-start; }
    .dbc_header { padding: var(--sp-3); min-height: 48px; }
    .stats_chart_cont { height: 260px; margin-top: 20px; }
    .dbc_header .dashboard_form_button { margin-top: 8px; }

    /* One per row: every divider is a rule above, and the first tile has none. */
    .dashboard_stats_overview { grid-template-columns: minmax(0, 1fr); }
    .dso_item { width: auto; border-left: 0; border-top: 1px solid var(--noc-line); }
    .dso_item:first-child { border-top: 0; }

    /* Row tables: each column becomes a full-width line, header row hidden */
    .bic_row_title { display: none; }
    .bic_row_cont { flex-wrap: wrap; width: 100%; padding: 12px 14px; gap: 4px 0; }
    /* Stacked cells get a small label where the header row used to explain them */
    .bicr_column[data-label]::before, .zlist_c2[data-label]::before, .zlist_c3[data-label]::before { content: attr(data-label); }
    .ch_column_2::before { content: "SSL"; }
    .ch_column_3::before { content: "Force SSL"; }
    .ch_column_4::before { content: "Certificate"; }
    .bicr_column[data-label]::before, .ch_column_2::before, .ch_column_3::before, .ch_column_4::before, .zlist_c2[data-label]::before, .zlist_c3[data-label]::before {
        display: block;
        font-size: 11px;
        font-weight: 700;
        color: var(--noc-ink-muted);
        text-transform: none;
        margin-top: 4px;
    }
    .ch_column_2, .ch_column_3 { flex-wrap: nowrap; align-items: center; gap: 4px 10px; }
    .ch_column_2 .cdn_toggle_switch, .ch_column_3 .cdn_toggle_switch { margin-left: auto; }
    .ch_column_4 { flex-wrap: wrap; align-items: center; gap: 4px 10px; }
    .ch_column_4 .ch_cert { flex: 1 1 auto; }
    .ch_column_4 .row_actions { width: auto; justify-content: flex-end; margin-left: auto; }
    .asf_left, .asf_right { padding-left: 0; padding-right: 0; }
    .node_column_1, .node_column_2 { padding-right: 0; }
    .bkp_column_1, .bkp_column_2, .bkp_column_3 { padding-right: 0; }
    .sn_column_1 { padding-right: 0; }
    .stats_control, .stats_control.sc_period, .stats_control.sc_node { flex-basis: 100%; }
    .stats_control.sc_date, .stats_control.sc_gran { flex-basis: calc(50% - 8px); }
    .stats_control.sc_apply { flex-basis: 100%; }
    .stats_control.sc_apply .dashboard_form_button { width: 100%; }
    .node_form_split { flex-direction: column; gap: 0; }
    /* Side by side, both labels are on the same line and the second carries
       dfit_notopmargin so they align. Stacked, that label is under a field and
       needs the margin back, or it sits hard against the control above it while
       every other label on the form has room. */
    .node_form_split > div + div .dashboard_form_input_title.dfit_notopmargin { margin-top: 20px; }
    .nsp_facts { grid-template-columns: 1fr; }
    /* Both grouped-field grids stack on a phone. Extending the base rule and not this one is how .blk_grid
       stayed two-up at 390px, where each column is 140px and a mono "2001:db8::/32" needs about 117px of it
       before padding: the field would scroll sideways over the content it exists to show. */
    .s3_grid, .blk_grid { grid-template-columns: 1fr; }
    .bkp_header_actions { width: 100%; flex-direction: column; align-items: stretch; gap: 8px; margin-top: 12px; }
    .bkp_header_actions > form { width: 100%; }
    .bkp_help_facts { grid-template-columns: 1fr; gap: 2px 0; }
    .bkp_fact + .bkp_fact .bkp_fact_label { margin-top: 8px; }
    .bkp_cmd { flex-direction: column; align-items: stretch; gap: 8px; }
    .bkp_ladder { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .node_paths_grid { grid-template-columns: 1fr; gap: 2px 0; }
    .node_paths_label { margin-top: 6px; }
    .nsp_log { max-height: 45vh; }
    .nsp_buttons > * { width: 100%; }
    .nsp_buttons .dashboard_form_button, .nsp_buttons .dashboard_form_button_right { width: 100%; }
    .dopt_cont { flex-wrap: wrap; gap: 10px; }
    .dopt_left { width: 100%; }
    .dashboard_form_button_right .dashboard_form_button, .dashboard_form_button_right form { width: 100%; }
    .dfbr_pad { padding: 14px; }
    .bicr_column, .zlist_c1, .zlist_c2, .zlist_c3, .zlist_c4 { width: 100%; white-space: normal; overflow: visible; text-overflow: clip; }
    .zlist_c4 { justify-content: flex-start; margin-top: 6px; }
    .zlist_c4 .dfb_small { width: 100%; }
    .dbs_info_row { flex-wrap: wrap; gap: 2px 8px; }

    /* Documentation */
    .docs_grid { grid-template-columns: 1fr; gap: 0; }
    .docs_cat:nth-last-child(2):nth-child(odd) { border-bottom: 1px solid var(--noc-line); }
    .docs_layout { grid-template-columns: 1fr; gap: 28px; }
    .docs_side { position: static; order: 2; padding-top: 20px; border-top: 1px solid var(--noc-line); }
    .docs_kind_article .docs_side .docs_search { display: none; }
    .docs_main { order: 1; }
    .docs_hero { padding: 8px 0 24px; }
    .docs_hero_title { font-size: 28px; }
    .docs_title { font-size: 26px; }
    .docs_outline ol { columns: 1; }
    .docs_pager { grid-template-columns: 1fr; }
    .docs_pager_next { text-align: left; }
    .docs_search.is_big .docs_search_input { height: 48px; font-size: 16px; }
    /* The Search button is inset inside the field (top/bottom 5px), so its height is the FIELD's minus 10 - and
       shrinking the field to 48px here took the button to 38px, under the 44px a button owes touch below 640px.
       The inset is what gives way, not the field: a smaller field is the point of this breakpoint. 48 - 2*2 = 44. */
    .docs_search.is_big .docs_search_go { top: 2px; bottom: 2px; }
    .docs_help { margin-top: 36px; }
    .docs_help_fields { padding: 16px; gap: 6px 16px; }
    .docs_help_foot { padding: 14px 16px; }
    .docs_page .docs_help_button { display: block; width: 100%; text-align: center; }
    .dadm_column_1, .dadm_column_2, .dadm_column_3, .dadm_column_4 { width: 100%; padding-right: 0; }
    .da_toolbar_row { flex-direction: column; gap: 0; }
    .da_toolbar_row > .da_order { flex: 1 1 auto; }

    /* Status page */
    .st_hero { padding: 20px 18px; }
    .st_hero_state, .st_hero_title { font-size: 22px; }
    .st_hero_meta, .st_hero_sub { font-size: 14px; }
    .st_row { flex-wrap: wrap; gap: 8px 12px; padding: 14px 16px; }
    .st_name { flex-basis: calc(100% - 24px); }
    .st_uptime { order: 3; min-width: 0; margin-left: 24px; text-align: left; flex: 1 1 auto; }
    .st_row .st_pill { order: 4; }
    .st_chevron { order: 5; }
    .st_group_body .st_row { padding-left: 32px; }
    .st_empty_inline { margin-left: 16px; }
    .st_incident { flex-direction: column; gap: 8px; }
    .st_incident_date { flex: 0 0 auto; flex-direction: row; align-items: baseline; gap: 6px; padding-top: 0; }
    .st_date_day { font-size: 18px; }
    .st_incident_title { font-size: 17px; }
    .st_admin .bicr_column { width: 100%; }
    .adm_row_child .adm_column_1, .adm_row_child .node_column_1 { padding-left: 0; width: 100%; }
    .net_head_actions { width: 100%; }
    .net_head_actions .dashboard_form_button { flex: 1 1 0; width: auto; }
    .dfi_small { min-height: 44px; height: 44px; line-height: 42px; width: 100%; }
    .manual_state_form { display: block; width: 100%; margin: 0 0 8px; }

    /* Forms and inputs */
    .dashboard_form_button { width: 100%; min-height: 44px; }
    .dashboard_form_button.dfb_small { min-height: 40px; }
    /* A field is 44px where a thumb is the pointer, and its text 16px, under which iOS zooms the page on focus */
    .dashboard_form_input { height: 44px; line-height: 42px; font-size: 16px; }
    /* The kit's icon button at the size a thumb needs (section 4c), keeping the room it takes in a header's line */
    .noc_icon_btn, .npb_close,
    .row_action_edit:not(form), .row_action_delete:not(form), .row_action_disabled:not(form) { width: 44px; height: 44px; }
    .dbc_header .npb_close { margin: -10px -10px -10px 0; }
    /* The Purge popup's single file: the address over its button, each the full width */
    .purge_single_cont .purge_input, .purge_single_cont .purge_single_button { flex: 1 1 100%; width: 100%; }
    /* The live chip sits above the tab bar, not under it */
    body.has_tabbar .noc_live_chip { bottom: calc(var(--noc-tab-height) + 12px + env(safe-area-inset-bottom)); }
    /* On a phone the suffix goes under the field rather than beside it: side by side
       there is not enough room left for the name being typed. */
    .cdn_input_name_cont { flex-wrap: wrap; }
    .cdn_input_name_append {
        width: 100%;
        min-height: 36px;
        padding: 0 10px;
        justify-content: flex-start;
        border-left: 1px solid var(--noc-line-2);
        border-top: 0;
        border-radius: 0 0 var(--noc-radius) var(--noc-radius);
    }
    .dfi_cdn_name { border-radius: var(--noc-radius) var(--noc-radius) 0 0; }

    /* Popups. The height is capped against the tab bar, not the viewport: a modal
       sits at z-index 61 and the bar at 40, so a tall one was drawn straight over
       it, its own last button landing on top of "CDN" and "Support" with the bar
       half hidden behind the popup's bottom edge. Leaving the bar its height plus
       a gap ends the popup above it. Twice the bar's height, because the modal is
       centred on the middle of the viewport rather than on the space above the
       bar: half of what is subtracted comes off the top, which is empty anyway,
       and half off the bottom, which is the half that matters. A popup taller
       than that scrolls inside itself, which it already did. This used to need
       !important to beat the per-id rules in the other stylesheet; there are no
       per-id rules any more, so ordering does it. */
    [id$="_popup"]:not(.noc_popup_box) {
        width: calc(100vw - 24px);
        max-height: calc(100vh - (2 * var(--noc-tab-height)) - 24px);
        overflow-y: auto;
    }
    /* In flow inside the sheet: it fills the sheet's content box and scrolls with the page. Sized against the
       viewport it was ~28px wider than the sheet and broke out onto the navy background. */
    .noc_popup_box {
        width: 100%;
        max-height: none;
        overflow-y: visible;
    }
    /* Stacked cards read left to right: the right-alignment that lines a figure column
       up on a wide screen has nothing to line up with here. */
    .zlist_c2, .zlist_c3, .zlist_c2_title, .zlist_c3_title,
    .st_admin .adm_column_3 { text-align: left; padding-right: 0; }
    .zlist_c4 { justify-content: flex-start; }
    /* full width between the gutters on a phone, and clear of the top bar, which is
       shorter here (--noc-top-height is 56px below 640px) */
    .loading_popup { left: var(--noc-gutter); right: var(--noc-gutter); width: auto; top: calc(var(--noc-top-height) + 10px); }

    .account_settings_form, .dbch_split, .spi_service_cont, .asp_api_row, .purge_single_cont, .cdn_page_buttons, .row_end_column, .dopt_cont { flex-wrap: wrap; }
    .account_settings_form, .asp_api_row { flex-direction: column; align-items: stretch; }
    .asf_left, .asf_right, .asf_email_notice, .dbsir_left, .dbsir_right, .api_row_left, .api_row_right, .cdn_settings_1, .cdn_settings_2, .ch_column_1, .ch_column_2, .ch_column_3, .ch_column_4, .loc_column_1, .loc_column_3, .node_column_1, .node_column_2, .node_column_3, .bkp_column_1, .bkp_column_2, .bkp_column_3, .bkp_column_4, .bkp_column_wide, .sn_column_1, .sn_column_2, .sn_column_3, .sn_column_4, .sn_column_5, .sn_column_6,
    .ctry_column_1, .ctry_column_2, .ctry_column_3, .ctry_column_4, .adm_column_1, .adm_column_2, .adm_column_3, .adm_column_4, .otg_column_1, .otg_column_2, .otg_column_3, .otg_column_4, .dsoi_subtitle, .sup_contact_intro, .sup_pin_cont { width: 100%; }
    .asf_right { margin-top: 16px; }
    .api_row_right { margin-left: 0; margin-top: 10px; }
    .cdn_settings_2, .row_actions { justify-content: flex-start; margin-top: 8px; }
    .tok_key_row { flex-direction: column; align-items: stretch; }
    .tok_key_actions .dashboard_form_button { flex: 1 1 0; width: auto; }
    .ocert_pem_actions .dashboard_form_button { width: auto; }
    /* Stacked like Reissue and Revoke below them: side by side, "Copy certificate" wraps onto two lines */
    .ocert_strip_start { width: 100%; }
    .ocert_strip_start .dashboard_form_button { flex: 1 1 100%; width: auto; }
    .zlist_titles:not(.zlist_row) { display: none; }
    .zlist_row { flex-wrap: wrap; gap: 4px 0; }
    .cdn_page_buttons { width: 100%; gap: 8px; justify-content: stretch; margin-top: 4px; }
    .cdn_page_buttons .dashboard_form_button, .cdn_page_buttons a, .cdn_page_buttons form { flex: 1 1 45%; }
    .cdn_page_buttons .dashboard_form_button { width: 100%; }
    /* The card takes the phone's width less the panel's gutter each side. It used to be 100%, so its rounded
       corners and side borders sat on the very edge of the screen. */
    .login_inner_cont { --login-pad-x: 16px; --login-pad-y: 20px; width: calc(100% - 2 * var(--noc-gutter)); max-width: 420px; margin-top: 32px; margin-bottom: 32px; }
}

/* -----------------------------------------------------------------------------
  12b. Billing (22 September 2026). Four sections, one per stream of the billing
   overhaul, each owned by the stream named; tokens only, as everywhere else.
   -------------------------------------------------------------------------- */
/* ==== billing: the customer's Billing page ==== */

/* The set-up checklist (account/billing.php box 1). It is the onboarding flow as the
   customer sees it, so it is a list of three facts and not a progress bar: a bar would
   promise a percentage nobody can act on. A done step is a tick in the UP colour, which
   is the one place colour earns its keep here - the three notes underneath are ordinary
   ink, because "still needed" is a next step and not an error. */
.bill_steps { display: flex; flex-direction: column; }
.bill_step { display: flex; align-items: flex-start; gap: 12px; padding: 12px 0; border-top: 1px solid var(--noc-line); }
.bill_step:first-child { border-top: 0; padding-top: 0; }
.bill_step_mark { flex: 0 0 auto; width: 18px; font-size: 15px; line-height: 22px; color: var(--noc-line-2); }
.bill_step.is_done .bill_step_mark { color: var(--st-ok); }
.bill_step_body { min-width: 0; }
.bill_step_title { font: var(--t-strong); color: var(--noc-ink); }
.bill_step.is_done .bill_step_title { color: var(--noc-ink-2); }
.bill_step_note { font: var(--t-sub); color: var(--noc-ink-muted); margin-top: 2px; overflow-wrap: anywhere; }
.bill_step_note a { text-decoration: none; }
.bill_steps_foot { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--noc-line); font: var(--t-sub); color: var(--noc-ink-muted); }

/* The card on file. Two tones and a hairline, the panel's own way of making a thing
   read as an object rather than as a line of text. */
.bill_card { padding: 14px; border: 1px solid var(--noc-line); border-radius: var(--noc-radius); background: var(--noc-surface-2); }
.bill_card_label { font: var(--t-h3); color: var(--noc-ink); }
.bill_card_label i { margin-right: 8px; color: var(--noc-ink-2); }
.bill_card_none { color: var(--noc-ink-muted); }
.bill_card_meta { font: var(--t-sub); color: var(--noc-ink-muted); margin-top: 4px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* The documents list, and the payments list under it. 16 / 14 / 34 / 14 / 22 adds to
   exactly 100%, which is what .bic_row_cont requires (it carries no flex gap: each
   column's own padding-right is the gutter). The payments list puts the wide column
   second, so its DOM order is 1, 3, 2, 4, 5 and the widths still line up. */
.bill_column_1 { width: 16%; padding-right: var(--sp-3); }
.bill_column_2 { width: 14%; padding-right: var(--sp-3); }
.bill_column_3 { width: 34%; padding-right: var(--sp-3); }
.bill_column_4 { width: 14%; padding-right: var(--sp-3); }
.bill_column_5 { width: 22%; }
/* The row's first cell is its name, and only in a row: the header row's "Number" and "Date" read as the other headings */
.bic_row_cont:not(.bic_row_title) > .bill_column_1 { font: var(--t-strong); color: var(--noc-ink); font-variant-numeric: tabular-nums; }

/* The trailing cell holds the state and, beside it, the two things a customer can do
   with an unpaid invoice. .row_actions is already a flex row; this only gives it the
   gap and the baseline the two different controls need to sit on. */
.bill_actions { align-items: center; gap: 10px; }
/* A 14px line, 20px tall: the padding gives a thumb a 34px target (the small button beside it) and the negative
   margin keeps the row and the sentence it sits in exactly where they were (27 September 2026, the NOC's visual pass) */
.bill_pdf_link { display: inline-block; font: var(--t-sub); color: var(--noc-accent-ink); text-decoration: none; padding: 7px 8px; margin: -7px -8px; }
.bill_pdf_link:hover, .bill_pdf_link:focus-visible { text-decoration: underline; }
.bill_in_progress { font: var(--t-sub); color: var(--noc-ink-muted); white-space: nowrap; }

/* The phone width of these two lists lives here rather than in the shared breakpoint
   block at the foot of the file, so a stream owns one contiguous stretch of this
   stylesheet and nothing of this feature is hiding six hundred lines away. The generic
   rules do the rest: .bic_row_title is hidden and every [data-label] cell prints its
   own heading. */
@media (max-width: 640px) {
    .bill_column_1, .bill_column_2, .bill_column_3, .bill_column_4, .bill_column_5 { width: 100%; padding-right: 0; }
    /* A figure that is its own line does not need to be pushed to the right edge, and
       the 24px of right padding .is_figure carries would put it out of line with every
       other value in the stacked row. */
    .bic_row_cont .bill_column_4.is_figure { text-align: left; padding-right: 0; }
    .bill_column_5.row_end_column { flex-wrap: wrap; gap: 6px 10px; }
    .bill_actions { width: 100%; justify-content: flex-start; }
    .bill_step { gap: 10px; }
}

/* (end of the customer page section) */

/* ==== billing: the banner, the billing lines on Settings and the evidence fields ==== */
/* The billing banner (page_content/dashboard/header.php): one line between the sticky top bar and the sheet,
   never more than one at a time. It is in flow, so the bar above and the fixed tab bar below cannot reach it at
   any width - the overlap the two of them cause is what a fixed banner would have had to fight.
   is_warning is the only coloured state on it, and it is a real one: money is late. is_info is ink on the
   raised tone, because "finish setting up" is a fact and not a fault, and a page of amber teaches a reader to
   stop seeing amber. */
.noc_banner {
    background: var(--noc-surface-2);
    border-bottom: 1px solid var(--noc-line);
}
.noc_banner_inner {
    max-width: var(--noc-max);
    margin: 0 auto;
    padding: 10px var(--noc-gutter);
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px 10px;
    font: var(--t-body);
    color: var(--noc-ink);
}
.noc_banner_inner > i { flex: 0 0 auto; color: var(--noc-ink-muted); }
.noc_banner_text { min-width: 0; }
/* A 22px line padded to a 34px target on a matching negative margin, so the strip keeps its height (27 September
   2026, the NOC's visual pass: on a phone the link is the one thing on the strip a thumb goes for) */
.noc_banner_link { display: inline-block; padding: 6px 0; margin: -6px 0; color: var(--noc-accent-ink); font-weight: 600; text-decoration: none; }
.noc_banner_link:hover { text-decoration: underline; }
.noc_banner_link i { margin-left: 4px; font-size: 12px; }
.noc_banner.is_warning { background: var(--st-warn-bg); border-bottom-color: var(--st-warn); }
.noc_banner.is_warning .noc_banner_inner, .noc_banner.is_warning .noc_banner_inner > i { color: var(--st-warn); }
.noc_banner.is_warning .noc_banner_link { color: var(--st-warn); text-decoration: underline; }
/* Staff viewing a customer's account (functions/join.php): the annotation colour, because it is a fact about the
   session rather than a fault, on an opaque band for the reason is_warning is one (it sits on the navy page). Exit
   is a POST button made to read as the banner's link. */
.noc_banner.is_join { background: var(--noc-indigo-bg); border-bottom-color: var(--noc-indigo); }
.noc_banner.is_join .noc_banner_inner, .noc_banner.is_join .noc_banner_inner > i { color: var(--noc-indigo-ink); }
.noc_banner.is_join b { font-weight: 700; }
/* The sentence is long, so it shrinks and wraps beside the icon rather than dropping below it whole at phone width */
.noc_banner.is_join .noc_banner_text { flex: 1 1 0; }
/* Padded to a 34px target on a matching negative margin, like the band's link (27 September 2026) */
.noc_banner_exit {
    margin-left: auto;
    padding: 6px 0;
    margin-top: -6px;
    margin-bottom: -6px;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 600;
    color: var(--noc-indigo-ink);
    text-decoration: underline;
    cursor: pointer;
}
.noc_banner_exit i { margin-left: 4px; font-size: 12px; }

/* Account > Settings: the line above the billing fields, and the read-only agreement under the form. */
.acc_settings_intro { font: var(--t-sub); color: var(--noc-ink-muted); margin-top: 4px; max-width: var(--measure-help); }
.acc_settings_agreement { margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--noc-line); font: var(--t-sub); color: var(--noc-ink-muted); }
.acc_settings_agreement b { color: var(--noc-ink); font-weight: 700; }
.acc_settings_agreement a { color: var(--noc-accent-ink); }

/* The EU evidence block of Admin > Customers' New customer popup: revealed by the page's own script, so it is
   laid out as a block rather than being squeezed into the two-column grid above it (the note is prose and the grid
   is for fields). */
.acc_evidence { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--noc-line); }
/* (.acc_evidence_check is the kit's tick, section 4d, and the file field .acc_file the kit's, section 4a) */
/* (end of the banner and billing fields section) */

/* ==== billing: an invoice's page, and the run on Admin > Customers ==== */
/* An invoice or credit note's page (admin/billing.php) and the figures of the run on Admin > Customers. Colour is
   reserved for state here as everywhere: the money is ink and tabular figures, the state is a pill. Nothing here
   animates, so nothing of it belongs in the reduced-motion block. */

/* The line under the tiles: what a person needs to know about the machinery before reading any figure */
.bill_meta_line {
    margin: 12px 0 0;
    font: var(--t-sub);
    color: var(--noc-ink-muted);
}
.bill_page_subtitle { margin-top: 8px; color: var(--noc-ink-muted); }
.bill_mono { font: var(--t-mono); font-size: 12px; }
/* The kit's tick (section 4d); only where it sits is said here */
.bill_check { margin-top: 10px; }

/* The run's own total, set apart from the accounts above it by a rule rather than a fill */
.bill_total_row { border-top: 2px solid var(--noc-line-2); }

.bpay_column_1 { width: 22%; padding-right: 10px; }
.bpay_column_2 { width: 20%; padding-right: 10px; }
.bpay_column_3 { width: 26%; padding-right: 10px; }
.bpay_column_4 { width: 14%; padding-right: 10px; }
.bpay_column_5 { width: 18%; }

.bline_column_1 { width: 46%; padding-right: 10px; }
.bline_column_2 { width: 16%; padding-right: 10px; }
.bline_column_3 { width: 16%; padding-right: 10px; }
.bline_column_4 { width: 22%; }

/* The document page: the two parties and the facts, in the order the PDF has them, so the page and the document
   a customer is holding read the same way round */
.bill_parties {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    padding-bottom: 8px;
}
.bill_party { min-width: 0; }
.bill_party_label {
    font: var(--t-micro);
    font-weight: 700;
    letter-spacing: 0.06em;
    color: var(--noc-ink-muted);
}
.bill_party_name { font: var(--t-h3); color: var(--noc-ink); margin-top: 4px; }
.bill_party_body { font: var(--t-body); color: var(--noc-ink-2); margin-top: 6px; line-height: 1.6; overflow-wrap: break-word; }

.bill_facts { margin-top: 16px; }
.bill_fact {
    display: grid;
    grid-template-columns: 190px minmax(0, 1fr);
    gap: 4px 16px;
    padding: 9px 0;
    border-top: 1px solid var(--noc-line);
}
.bill_fact_l { font: var(--t-sub); color: var(--noc-ink-muted); }
.bill_fact_v { font: var(--t-body); color: var(--noc-ink); overflow-wrap: break-word; }

/* The totals block, right aligned under the lines with the figures in their own column so they line up */
.bill_totals { padding: 14px var(--noc-pad-box) 18px; border-top: 1px solid var(--noc-line); }
.bill_total { display: flex; justify-content: flex-end; align-items: baseline; gap: 20px; padding: 5px 0; }
.bill_total .bt_l { font: var(--t-body); color: var(--noc-ink-muted); text-align: right; }
.bill_total .bt_v { width: 150px; text-align: right; font: var(--t-body); color: var(--noc-ink); font-variant-numeric: tabular-nums; }
.bill_total.is_total { border-top: 1px solid var(--noc-line); margin-top: 4px; padding-top: 9px; }
.bill_total.is_total .bt_l, .bill_total.is_total .bt_v { font: var(--t-strong); color: var(--noc-ink); font-variant-numeric: tabular-nums; }
.bill_total.is_due { border-top: 2px solid var(--noc-line-2); margin-top: 4px; padding-top: 9px; }
.bill_total.is_due .bt_l, .bill_total.is_due .bt_v { font: var(--t-h3); color: var(--noc-ink); font-variant-numeric: tabular-nums; }

/* What can be done, and the one line that says what is true TODAY rather than at issue */
.bill_actions { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.bill_action_note { margin-top: 14px; font: var(--t-sub); color: var(--noc-ink-muted); }

/* The audit trail: when, what, who. Muted throughout, because every row of it is a thing that went right. */
.bill_log_row {
    display: grid;
    grid-template-columns: 150px minmax(0, 1fr) 130px;
    gap: 4px 16px;
    padding: 8px 0;
    border-top: 1px solid var(--noc-line);
    font: var(--t-sub);
    color: var(--noc-ink-muted);
}
.bill_log_row:first-child { border-top: 0; padding-top: 0; }
.bill_log_what { color: var(--noc-ink-2); overflow-wrap: break-word; }
.bill_log_what b { color: var(--noc-ink); }
.bill_log_who { text-align: right; }

@media (max-width: 900px) {
    .bill_fact { grid-template-columns: 150px minmax(0, 1fr); }
    .bill_log_row { grid-template-columns: 130px minmax(0, 1fr) 110px; }
}

@media (max-width: 640px) {
    .bpay_column_1, .bpay_column_2, .bpay_column_3, .bpay_column_4, .bpay_column_5,
    .bline_column_1, .bline_column_2, .bline_column_3, .bline_column_4 { width: 100%; padding-right: 0; }

    .bill_parties { grid-template-columns: minmax(0, 1fr); gap: 18px; }
    .bill_fact { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .bill_log_row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .bill_log_who { text-align: left; }
    .bill_total .bt_v { width: 120px; }
    .bill_actions .dashboard_form_button, .bill_actions a.dashboard_form_button { width: 100%; }
}

/* (end of the invoice page section) */

/* ==== billing: the pay page ==== */

/* The public pay page (pages/dashboard/pay.php). It rides on .login_inner_cont, which
   is already the centred card the login and offline pages use, so this only widens it:
   the invoice's label/value rows need more room than a password field does. */
.pay_card { max-width: 520px; }
.pay_card .dashboard_box_cont { text-align: left; }
.pay_line { font: var(--t-body); color: var(--noc-ink); margin: 14px 0 0 0; padding: 0 10px; text-align: left; }
.pay_line.pay_sub { font: var(--t-sub); color: var(--noc-ink-muted); margin-top: 8px; }
/* The box body on this page has no padding of its own (the label/value rows carry theirs), so the
   sentences take the rows' 10px inset, the last one keeps clear of the box's foot, and the one action
   sits on the save strip the rest of the panel ends its forms with. */
.pay_card .dbc_content > .pay_line:last-child { margin-bottom: 14px; }
.pay_card .dbc_content > form > .dashboard_form_button_right {
    margin: 14px 0 0;
    padding: var(--sp-3) 10px;
    width: auto;
    border-top: 1px solid var(--noc-line);
    background: var(--noc-surface);
}
/* The amount due is the one figure on the page somebody is looking for, so it is the
   one thing set larger. Tabular figures, because a money column that jiggles reads as
   an approximation. */
.pay_due_row { align-items: baseline; }
.pay_due { font: var(--t-h2); color: var(--noc-ink); font-variant-numeric: tabular-nums; }
.pay_foot { font: var(--t-sub); color: var(--noc-ink-muted); margin-top: 16px; overflow-wrap: anywhere; }

@media (max-width: 640px) {
    /* .login_inner_cont already goes full width below 640px; this only stops the
       widened card fighting it. */
    .pay_card { max-width: 100%; }
    .pay_due_row { flex-wrap: wrap; }
}

/* (end of the pay page section) */

/* ==== admin: the audit log (23 September 2026; Admin > Zones and its rules went on 25 September 2026) ==== */
/* The audit log (the last box of Admin > System since 25 September 2026): when, who, what, detail. The action code is the row's one strong element; everything else is
   muted, because a trail is read for the one row that matters and forty bold rows hide it. */
.aud_column_1 { width: 16%; padding-right: 10px; }
.aud_column_2 { width: 22%; padding-right: 10px; }
.aud_column_3 { width: 24%; padding-right: 10px; }
.aud_column_4 { width: 38%; }
.aud_action { font-weight: 700; color: var(--noc-ink); overflow-wrap: anywhere; }
.aud_detail { font: var(--t-sub); color: var(--noc-ink-2); overflow-wrap: anywhere; }
.aud_mono { font: var(--t-mono); font-size: 12px; }
.aud_link { color: var(--noc-accent-ink); }
.aud_header_note { font: var(--t-sub); color: var(--noc-ink-muted); font-weight: normal; }
.aud_more { padding: 14px var(--noc-pad-box); font: var(--t-sub); color: var(--noc-ink-muted); border-top: 1px solid var(--noc-line); }
/* The CDN Nodes list's two amber lines, a cache tier that did not come up (NODE_CACHE_WARNING) and live connections
   full or filling up (NODE_LIVE_WARNING, 26 September 2026), both in functions/node_setup.php: attention, in the amber
   the "updates waiting" line beside them already uses, so the status cell keeps its one colour per meaning. */
.node_cache_warning, .node_live_warning { color: var(--st-warn); }
@media (max-width: 640px) {
    .aud_column_1, .aud_column_2, .aud_column_3, .aud_column_4 { width: 100%; padding-right: 0; }
    .aud_header_note { width: 100%; margin-top: 4px; }
}
/* (end of the admin audit section) */

/* -----------------------------------------------------------------------------
   7. Page sections: account credit, and the staff controls on the Billing page
   (24 September 2026)
   -------------------------------------------------------------------------- */

/* The Account credit box (account/billing.php): the balance as a figure, then every
   entry that moved it. The figure is the tile's own register (it is the one number the
   box is about) without the tile's frame, because the box is already the frame. */
.cred_balance { padding: var(--sp-4) var(--noc-pad-box); border-bottom: 1px solid var(--noc-line); }
.cred_balance_figure { font: var(--t-figure); color: var(--noc-ink); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.cred_balance_note { font: var(--t-sub); color: var(--noc-ink-muted); margin-top: 4px; }
.cred_balance + .bic_row_title { border-top: 0; }

/* The ledger. 16 / 62 / 22 adds to exactly 100%, which .bic_row_cont requires (each
   column's padding-right is the gutter, and .is_figure brings its own). The date
   column is the same 16% as the payments list's first column below it. */
.cred_column_1 { width: 16%; padding-right: var(--sp-3); font-variant-numeric: tabular-nums; }
.cred_column_2 { width: 62%; padding-right: var(--sp-3); }
.cred_column_3 { width: 22%; }
.cred_entry_title { font: var(--t-strong); color: var(--noc-ink); }
.cred_entry_note { font: var(--t-sub); color: var(--noc-ink-muted); margin-top: 2px; overflow-wrap: anywhere; }

/* Staff only: the two buttons in the box header, and the tick under the credit forms
   (the evidence tick's shape: a label the whole of which is the target) */
.cred_head_actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--sp-2); }
/* The kit's tick (section 4d); only where it sits is said here */
.cred_check { margin-top: var(--sp-4); }

/* Staff only: the two-up fields of the Change terms and Add credit popups. The grid is
   the bucket group's (.s3_grid), without the group that gives its labels their gap, so
   the gap is said here: the first row sits at the top of the popup and every later
   label keeps the panel's gap, including the second one once the grid is one column. */
.cred_popup_grid .dashboard_form_input_title { margin-top: var(--noc-gap); }
.cred_popup_grid > .s3_cell:nth-child(-n+2) .dashboard_form_input_title { margin-top: 0; }

/* Staff only: every terms row under the agreement, newest first. A quiet list below a
   hairline: it is the record behind the rows above it, not a second agreement. */
.cred_terms_history { margin-top: var(--sp-3); padding: var(--sp-3) var(--noc-pad-box) var(--sp-4); border-top: 1px solid var(--noc-line); }
.cred_terms_history_title { font: var(--t-micro); color: var(--noc-ink-muted); letter-spacing: 0.02em; margin-bottom: 6px; }
.cred_terms_history_line { font: var(--t-sub); color: var(--noc-ink); padding: 3px 0; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }

@media (max-width: 640px) {
    .cred_column_1, .cred_column_2, .cred_column_3 { width: 100%; padding-right: 0; }
    /* A figure that is its own line keeps to the left edge, like the documents list's */
    .bic_row_cont .cred_column_3.is_figure { text-align: left; padding-right: 0; }
    .cred_balance { padding: var(--sp-3) 14px; }
    .cred_terms_history { padding: var(--sp-3) 14px var(--sp-4); }
    /* The two header buttons move under the title as one row of two equal halves, the
       zone page's key buttons' shape, rather than two stacked buttons of two widths */
    .cred_head_actions { width: 100%; flex-wrap: nowrap; justify-content: stretch; }
    .cred_head_actions .dashboard_form_button { flex: 1 1 0; width: auto; }
    .cred_popup_grid > .s3_cell:nth-child(2) .dashboard_form_input_title { margin-top: var(--noc-gap); }
}
/* (end of the account credit section) */

/* -----------------------------------------------------------------------------
   7. Page sections: staff views of a customer's own pages - the settings, users
   and support pages' staff-only controls (24 September 2026)
   -------------------------------------------------------------------------- */
/* What only staff see on a customer's own pages while they view the account (functions/join.php), and the same
   sign-in help on our own account's Users page. Nothing here is a state, so nothing here takes a state colour
   except the two things that need a hand (an address never confirmed, a lockout), which wear the amber text form;
   "Staff only" is an annotation and wears the indigo the staff band above the sheet already wears. */

/* Settings: the VAT position under the one VAT number field. A group like the zone page's Bucket Details
   (.s3_group): a top rule, a title and a muted line, so it reads as part of the box and not a second form. */
.stv_group { margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--noc-line); }
.stv_group_title { display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px; font: var(--t-strong); color: var(--noc-ink); }
/* --noc-indigo-bg is opaque (the tint laid on the surface), so the tag reads the same on the sheet in both themes */
.stv_tag {
    display: inline-block;
    padding: 2px 8px;
    border-radius: var(--noc-radius-sm);
    background: var(--noc-indigo-bg);
    color: var(--noc-indigo-ink);
    font: var(--t-micro);
    font-weight: 700;
}
.stv_group_sub { font: var(--t-sub); color: var(--noc-ink-muted); margin-top: 4px; max-width: var(--measure-help); }
.stv_vies_row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px; margin-top: 10px; }
.stv_vies_text { font: var(--t-sub); color: var(--noc-ink-muted); }
/* A number VIES does not know is attention, never a refusal: the save went through and the invoice is ours to fix */
.stv_vies_text.is_warn { color: var(--st-warn); }
.stv_vies_row .dashboard_form_button { margin-top: 0; }
.stv_group .notify_box { margin-bottom: 0; }
.stv_evidence { margin-top: 14px; }
/* (.stv_check is the kit's tick, section 4d; the evidence file is the kit's upload control, 4e) */
.stv_check + .stv_group_sub { margin-top: 6px; }
.stv_file_link { margin-top: 8px; font: var(--t-sub); }
.stv_file_link a { color: var(--noc-accent-ink); text-decoration: none; }
.stv_file_link a:hover, .stv_file_link a:focus-visible { text-decoration: underline; }
.stv_file_link i { margin-right: 4px; }

/* Users: under each name, what staff need before they can help (can this person sign in, 2FA, a lockout, the last
   sign-in, a session now). Muted: it is a readout, and only the two words that need a hand take the amber. */
.stu_sub { margin-top: 3px; font: var(--t-sub); color: var(--noc-ink-muted); }
.stu_warn { color: var(--st-warn); font-weight: 700; }
/* The sign-in help dialog, one per user: the modal shell does the frame ([id$="_popup"]), this is the inside. The
   address rides in the title, muted, so the dialog says whose login it is about before anything is pressed. */
.stu_popup_who { margin-left: 6px; font: var(--t-sub); color: var(--noc-ink-muted); overflow-wrap: anywhere; }
.stu_tools { display: flex; flex-direction: column; }
.stu_tool { padding: 14px 0; border-top: 1px solid var(--noc-line); }
.stu_tool:first-child { padding-top: 0; border-top: 0; }
.stu_tool:last-child { padding-bottom: 0; }
.stu_tool_title { font: var(--t-strong); color: var(--noc-ink); }
/* A tool that does not apply to this user is still listed, so staff can see WHY it is not offered */
.stu_tool.is_off .stu_tool_title { color: var(--noc-ink-muted); }
.stu_tool_note { margin-top: 4px; font: var(--t-sub); color: var(--noc-ink-muted); max-width: var(--measure-help); }
.stu_tool form { margin-top: 10px; }
.stu_tool .notify_box { margin: 10px 0 0; }
.stu_tool .dashboard_form_input_title { margin-top: 0; }
/* A field and its button share one row at the control height; the button keeps its size and the field takes the
   rest (min-width: 0, or a long address pushes the button out of the dialog) */
.stu_tool_row { display: flex; align-items: stretch; gap: 10px; }
.stu_tool_row .dashboard_form_input { flex: 1 1 auto; min-width: 0; }
.stu_tool_row .dashboard_form_button { flex: 0 0 auto; margin-top: 0; }

@media (max-width: 640px) {
    .stu_tool_row { flex-direction: column; }
    .stu_tool_row .dashboard_form_button { width: 100%; }
}
/* (end of the staff view section) */

/* -----------------------------------------------------------------------------
   7. Page sections: Admin > Customers, Accounts and Billing in one page
   (24 September 2026)
   -------------------------------------------------------------------------- */
/* One long page (the owner's decision): the money tiles, what needs a person, the customers, the latest invoices
   and payments, and the next run. Colour is reserved for state as everywhere: on a page where nothing is wrong
   there is ink, muted ink and the hairlines, so an amber gap on a live customer and the red of an overdue one are
   the only two things a reader's eye lands on. Nothing here animates, so nothing belongs in the reduced-motion
   block. The component rules (.dashboard_stats_overview, .blk_segmented, the row tables, the pills) are the shared
   ones; this only sizes them for the page. */

/* Five tiles where the shared strip has four. The shared breakpoints re-set the column count for every strip and
   come EARLIER in this file than this section, so each width below says the whole of it again (columns, the
   dividers between them, and the dividers between rows) rather than relying on theirs. */
/* The zone statistics strip is five tiles, not four, once its Blocked tile shows (stats.php adds .is_five): it takes
   the Clients strip's rules at every width rather than wrapping one tile alone onto a second row */
.cst_tiles, .stats_tiles.is_five { grid-template-columns: repeat(5, minmax(0, 1fr)); }

/* The customer list. Each family's widths add to 100% and the padding sits inside them (border-box). */
.cst_column_1 { width: 24%; padding-right: 10px; }
.cst_column_2 { width: 13%; padding-right: 10px; }
.cst_column_3 { width: 24%; padding-right: 10px; }
.cst_column_4 { width: 13%; padding-right: 10px; }
.cst_column_5 { width: 12%; }
.cst_column_6 { width: 14%; }
/* (Up to four controls on a row - View, Invoices, Suspend, Close - are the kit's icon buttons with the row's own gap
   between them, section 4c and .row_actions; they wrap to a second line where the column is too narrow for four.) */

.catt_column_1 { width: 74%; padding-right: 10px; }
.catt_column_2 { width: 26%; }

/* The status cell holds the longest pill in the panel ("Paid 3 Sep 2026") and two icons, which is what sizes it */
.cdoc_column_1 { width: 16%; padding-right: 10px; }
.cdoc_column_2 { width: 22%; padding-right: 10px; }
.cdoc_column_3 { width: 23%; padding-right: 10px; }
.cdoc_column_4 { width: 14%; }
.cdoc_column_5 { width: 25%; }

.cpay_column_1 { width: 20%; padding-right: 10px; }
.cpay_column_2 { width: 20%; padding-right: 10px; }
.cpay_column_3 { width: 26%; padding-right: 10px; }
.cpay_column_4 { width: 14%; }
.cpay_column_5 { width: 20%; }

/* The run: seven columns, the four money ones the narrowest that hold a four figure sum */
.cprev_column_1 { width: 24%; padding-right: 10px; }
.cprev_column_2 { width: 12%; }
.cprev_column_3 { width: 11%; }
.cprev_column_4 { width: 12%; }
.cprev_column_5 { width: 10%; }
.cprev_column_6 { width: 12%; }
.cprev_column_7 { width: 19%; }

/* The chips under the search they filter (the search field has its own margin below it, so this is the gap between
   the two controls and not a second one), and the chips in a box header, where the header already centres them */
.cst_filters { margin-top: 14px; }
.cst_chips { margin-top: 0; }

/* "Acme Ltd. only. Every account": what a list below is narrowed to, beside the title it belongs to */
.cst_scope { margin-left: 10px; font: var(--t-sub); color: var(--noc-ink-muted); }
.cst_scope a { color: var(--noc-accent-ink); }

/* The link under a list that shows more of it, or fewer: a row of the list's own shape rather than a button,
   because it is a view of the same list and not an action */
.cst_more { padding: var(--sp-3) var(--noc-pad-box); border-top: 1px solid var(--noc-line); font: var(--t-sub); color: var(--noc-ink-muted); }
.cst_more a { color: var(--noc-accent-ink); }

/* The two states worth a colour in the customer list, in their text forms (--st-warn / --st-error are the pair
   lifted for dark; a fill under white text cannot also be text on its own tint). A gap on a live customer is
   attention - somebody has to finish the onboarding, or ask for a new card - and overdue is a failure. Both sit
   where a muted sub-line would, at the sub-line's size, so a row with a gap is the same height as one without. */
.cst_warn { margin-top: 3px; font: var(--t-sub); color: var(--st-warn); overflow-wrap: anywhere; }
.cst_error { margin-top: 3px; font: var(--t-sub); font-weight: 700; color: var(--st-error); }

@media (max-width: 1200px) {
    /* Five 32px sums in a strip under 1200px would break inside a figure, so the strip goes three and two, the
       last tile taking the space its row leaves rather than leaving a hole in the hairlines */
    .cst_tiles, .stats_tiles.is_five { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .cst_tiles .dso_item:nth-child(n), .stats_tiles.is_five .dso_item:nth-child(n) { border-left: 1px solid var(--noc-line); border-top: 0; grid-column: auto; }
    .cst_tiles .dso_item:nth-child(3n+1), .stats_tiles.is_five .dso_item:nth-child(3n+1) { border-left: 0; }
    .cst_tiles .dso_item:nth-child(n+4), .stats_tiles.is_five .dso_item:nth-child(n+4) { border-top: 1px solid var(--noc-line); }
    .cst_tiles .dso_item:nth-child(5), .stats_tiles.is_five .dso_item:nth-child(5) { grid-column: span 2; }
}

@media (max-width: 900px) {
    /* Two a row, as every strip is at this width; the fifth takes the whole of the last row */
    .cst_tiles, .stats_tiles.is_five { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .cst_tiles .dso_item:nth-child(n), .stats_tiles.is_five .dso_item:nth-child(n) { border-left: 1px solid var(--noc-line); border-top: 0; grid-column: auto; }
    .cst_tiles .dso_item:nth-child(odd), .stats_tiles.is_five .dso_item:nth-child(odd) { border-left: 0; }
    .cst_tiles .dso_item:nth-child(n+3), .stats_tiles.is_five .dso_item:nth-child(n+3) { border-top: 1px solid var(--noc-line); }
    .cst_tiles .dso_item:nth-child(5), .stats_tiles.is_five .dso_item:nth-child(5) { grid-column: 1 / -1; }
}

@media (max-width: 640px) {
    /* One a row: every divider is a rule above, and the first tile has none */
    .cst_tiles, .stats_tiles.is_five { grid-template-columns: minmax(0, 1fr); }
    .cst_tiles .dso_item:nth-child(n), .stats_tiles.is_five .dso_item:nth-child(n) { border-left: 0; border-top: 1px solid var(--noc-line); grid-column: auto; }
    .cst_tiles .dso_item:first-child, .stats_tiles.is_five .dso_item:first-child { border-top: 0; }

    /* Each column becomes a full-width line under the label its hidden header used to carry */
    .cst_column_1, .cst_column_2, .cst_column_3, .cst_column_4, .cst_column_5, .cst_column_6,
    .catt_column_1, .catt_column_2,
    .cdoc_column_1, .cdoc_column_2, .cdoc_column_3, .cdoc_column_4, .cdoc_column_5,
    .cpay_column_1, .cpay_column_2, .cpay_column_3, .cpay_column_4, .cpay_column_5,
    .cprev_column_1, .cprev_column_2, .cprev_column_3, .cprev_column_4, .cprev_column_5, .cprev_column_6, .cprev_column_7 { width: 100%; padding-right: 0; }
    /* A figure that is its own line is read with the lines above and below it, so it starts where they do: the
       right edge and the 18px .is_figure carries would put it out of line with every other value in the row */
    .bic_row_cont .cst_column_5.is_figure, .bic_row_cont .cdoc_column_4.is_figure, .bic_row_cont .cpay_column_4.is_figure,
    .bic_row_cont .cprev_column_2.is_figure, .bic_row_cont .cprev_column_3.is_figure, .bic_row_cont .cprev_column_4.is_figure,
    .bic_row_cont .cprev_column_5.is_figure, .bic_row_cont .cprev_column_6.is_figure { text-align: left; padding-right: 0; }
    /* The chips in a box header stay one control in a row and wrap, rather than stacking five words into a header
       taller than the list under it (the chips under the search stack, the way every segmented control does here) */
    .cst_chips { flex-direction: row; flex-wrap: wrap; width: auto; }
    .cst_scope { display: block; margin-left: 0; margin-top: 2px; }
}
/* (end of the admin customers section) */

/* -----------------------------------------------------------------------------
   7. Page sections: Admin > System, and uptime corrections on Admin > Status
   (24 September 2026)
   -------------------------------------------------------------------------- */
/* Admin > System (pages/dashboard/admin/system.php): the machinery under the panel, five lists of our own state.
   Colour is reserved for state here as everywhere: a job, a queued message or a dataset that is fine is ink and
   muted ink, attention (a late scheduler, the daily send cap close, a hostname waiting for DNS, a dataset failing)
   is the amber text form and a failure (the cap reached, a certificate order failing, a certificate within a week
   of expiry) the red one. Each list is its own column family, widths adding up to 100% with the gutter carried as
   padding-right, as every row table here does. */
.sysj_column_1 { width: 46%; padding-right: 10px; }
.sysj_column_2 { width: 14%; padding-right: 10px; }
.sysj_column_3 { width: 20%; padding-right: 10px; }
.sysj_column_4 { width: 20%; }
.syse_column_1 { width: 44%; padding-right: 10px; }
.syse_column_2 { width: 22%; padding-right: 10px; }
.syse_column_3 { width: 24%; padding-right: 10px; }
.syse_column_4 { width: 10%; }
.sysl_column_1 { width: 17%; padding-right: 10px; }
.sysl_column_2 { width: 73%; padding-right: 10px; }
.sysl_column_3 { width: 10%; }
.sysh_column_1 { width: 30%; padding-right: 10px; }
.sysh_column_2 { width: 38%; padding-right: 10px; }
.sysh_column_3 { width: 22%; padding-right: 10px; }
.sysh_column_4 { width: 10%; }
.sysd_column_1 { width: 30%; padding-right: 10px; }
.sysd_column_2 { width: 30%; padding-right: 10px; }
.sysd_column_3 { width: 16%; padding-right: 10px; }
.sysd_column_4 { width: 24%; }
.sys_warn { color: var(--st-warn); }
.sys_error { color: var(--st-error); }
/* An error row is read for its words, so it keeps its own line breaks and a type that tells an id from a word */
.sys_log_text { font: var(--t-mono); color: var(--noc-ink-2); white-space: pre-wrap; overflow-wrap: anywhere; }
.sys_log_more { font: var(--t-sub); color: var(--noc-ink-muted); white-space: normal; }
/* Our certificate: label and value pairs, the backup popup's facts shape */
.sys_facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--sp-2) var(--sp-5); font: var(--t-body); }
.sys_fact_label { font: var(--t-sub); color: var(--noc-ink-muted); padding-top: 2px; }
.sys_fact_value { min-width: 0; color: var(--noc-ink); overflow-wrap: anywhere; }
/* Admin > Status, the uptime correction: what it reaches, then the two days side by side (the calendar in the row is
   the kit's icon button, .row_action_uptime beside .row_action_edit) */
.upt_scope { margin-top: var(--sp-3); font: var(--t-sub); color: var(--noc-ink-muted); }
.upt_range { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 0 var(--sp-4); }
.upt_field { min-width: 0; }
/* A monitor row's state cell holds the override select and, with the correction, three actions: below about 930px
   they no longer fit its 26% side by side (two already did not below about 870px), so the actions wrap under the
   select, still at the right, rather than spilling out of the row. The phone layout stacks them already. */
@media (min-width: 641px) {
    .st_admin .row_end_column { flex-wrap: wrap; row-gap: var(--sp-2); }
    .st_admin .row_actions { margin-left: auto; }
}
@media (max-width: 640px) {
    .sysj_column_1, .sysj_column_2, .sysj_column_3, .sysj_column_4,
    .syse_column_1, .syse_column_2, .syse_column_3, .syse_column_4,
    .sysl_column_1, .sysl_column_2, .sysl_column_3,
    .sysh_column_1, .sysh_column_2, .sysh_column_3, .sysh_column_4,
    .sysd_column_1, .sysd_column_2, .sysd_column_3, .sysd_column_4 { width: 100%; padding-right: 0; }
    .sys_facts { grid-template-columns: minmax(0, 1fr); gap: 2px 0; }
    .sys_fact_value + .sys_fact_label { margin-top: var(--sp-2); }
    .upt_range { grid-template-columns: minmax(0, 1fr); }
}
/* (end of the admin system section) */

/* -----------------------------------------------------------------------------
  13. Reduced motion
   Every selector that carries a transition is named here rather than using a blanket
   * override, so adding one somewhere forces a decision about it. The list is kept in
   step by hand: grep the file for "transition:" and every rule should appear below,
   written at least as specifically as the rule it stills (a :not(form) or a
   .nu_ready there is a :not(form) or a .nu_ready here, or the rule wins over this one).
   A builder's section (14) puts its own in a block of its own, inside that section.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    .loading_popup, .noc_nav_item, .noc_util_item, .st_chevron,
    .dashboard_form_input, .dashboard_form_button, .zlist_row,
    .slider, .slider:before, .slider-small, .slider-small:before,
    .aptc_menu a, .aptc_menu a:visited, .sup_section, .nsp_bar_fill,
    .bic_row_cont, .st_row_group, .docs_pager a, .docs_chip,
    .loading_popup.lp_visible, .blk_seg span, .blk_seg_link,
    .noc_icon_btn, .npb_close,
    .row_action_edit:not(form), .row_action_delete:not(form), .row_action_disabled:not(form),
    .noc_upload.nu_ready .nu_drop, .noc_live_chip, .noc_live_chip.is_shown, .nlc_reload,
    .otp_digit, .noc_row, .noc_row_icon, .noc_row_go {
        transition: none;
    }
    /* A refused code is said by the error colour and the sentence under the boxes; the shake goes, and the
       chevron of a hovered link row changes colour without moving */
    .otp.is_error .otp_boxes { animation: none; }
    .noc_row:hover .noc_row_go, .noc_row:focus-visible .noc_row_go { transform: none; }
    /* On its own: a browser that does not know the pseudo-element would drop every selector listed with it */
    input[type="file"].dashboard_form_input::file-selector-button, .nu_input::file-selector-button {
        transition: none;
    }
    /* The live chip appears without its 8px rise; it still fades in no time at all rather than sliding */
    .noc_live_chip { transform: translate(-50%, 0); }

    /* The loading badge still has to read as "in progress" with the motion off, so
       the sweeping segment becomes a filled rail rather than disappearing: the badge
       is a readout, and a readout that says nothing is worse than a still one. */
    .lp_rail span {
        animation: none;
        transform: none;
        width: 100%;
    }
}

/* -----------------------------------------------------------------------------
  14. The builders' sections
   Five marked blocks, one per stream of the next stage, each owned whole by one
   builder so parallel work merges without a conflict. The rules for writing in one:
     - build from the kit in section 4 first (functions/ui.php prints its composite
       parts), and write a rule of your own only for what the kit does not have;
     - no hex or rgba here or anywhere but noc_tokens.css; colour is reserved for state;
     - a selector of yours that carries a transition or an animation gets its own
       @media (prefers-reduced-motion: reduce) block INSIDE your section, written at
       least as specifically as the rule it stills (section 13 is the kit's, not yours);
     - a new column family's widths add to 100% with the padding inside them, and a
       phone rule stacks them (see the admin system section above for the shape);
     - leave the banner and the "(end of ...)" line where they are: the merge finds
       your section by them.
   -------------------------------------------------------------------------- */

/* -----------------------------------------------------------------------------
  14A. Email contacts (builder A)
   -------------------------------------------------------------------------- */

/* The Email contacts box on Account > Settings: a row per user and per extra address, a tick column per kind of
   email, the save strip at the foot. The list reaches out through the box's padding, so its hairlines run edge to
   edge while each row's own inset keeps the words in line with the intro above it. Four tick columns on a billed
   account, three on one that is not (.is_three); each family adds up to 100%. */
/* Two even columns, so the four kinds read as a key rather than a sentence that wraps wherever it runs out (it left
   Abuse alone on a line of its own at desktop width); one column on a phone, below. */
.ect_kinds {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-1) var(--sp-6);
    margin-top: var(--sp-3);
    font: var(--t-sub);
    color: var(--noc-ink-muted);
}
.ect_kinds b { color: var(--noc-ink); font-weight: 700; }
.ect_problems > div + div { margin-top: var(--sp-1); }
.ect_list {
    margin: var(--sp-4) calc(-1 * var(--noc-pad-box)) 0;
    border-top: 1px solid var(--noc-line);
}
.ect_list .ect_c_who { width: 40%; padding-right: var(--sp-4); }
.ect_list .ect_c_tick { width: 12%; display: flex; justify-content: center; }
.ect_list .ect_c_end { width: 12%; display: flex; justify-content: flex-end; }
.ect_list.is_three .ect_c_who { width: 46%; }
.ect_list.is_three .ect_c_tick { width: 14%; }
.ect_name { font: var(--t-strong); color: var(--noc-ink); overflow-wrap: anywhere; }
.ect_sub { margin-top: 3px; font: var(--t-sub); color: var(--noc-ink-muted); overflow-wrap: anywhere; }
.ect_flag { color: var(--st-warn); }
.ect_add .dashboard_form_input { width: 100%; }
.ect_full {
    padding: var(--sp-3) var(--noc-pad-box);
    border-top: 1px solid var(--noc-line);
    font: var(--t-sub);
    color: var(--noc-ink-muted);
}
/* A tick's own word says which column it is only on a phone, where the header row is gone. Above that the header says
   it, so the word is taken off the screen and the checkbox's aria-label (the kind and the address) names it. The label
   around the 16px box is padded out to a 32px target without moving the box. */
.ect_c_tick .noc_check { padding: 8px; margin: -8px; }
.ect_c_tick .noc_check span,
.ect_default {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* The set-up notice is a live region: while the account waits on a card it is drawn hidden (the banner above says
   it), and a class on .notify_box would otherwise outrank the attribute */
.acs_setup[hidden] { display: none; }

/* "Enforce 2FA for All Users": the words and the line under them, under the user's own switch or, in a staff view, at
   the foot of the Account Details box */
.acs_e2fa_note { margin-top: 4px; font: var(--t-sub); color: var(--noc-ink-muted); line-height: 1.4; }
.acs_enforce { margin-top: var(--noc-gap); }

/* Account > Users: a column family of its own (the name with what the person receives under it, the address, the
   level, the actions; 100% with the gutters inside), and the row's actions as the kit's icon buttons in one line. The
   remove button sits in its own small form, which lays out as if it were not there. */
.usr_column_1 { width: 36%; padding-right: var(--sp-4); }
.usr_column_2 { width: 30%; padding-right: var(--sp-4); }
.usr_column_3 { width: 16%; padding-right: var(--sp-4); }
.usr_column_4 { width: 18%; }
.bic_row_title .usr_column_4 { text-align: right; }
.usr_receives { margin-top: 3px; font: var(--t-sub); color: var(--noc-ink-muted); }
.usr_actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--sp-1); }
.usr_actions form { display: contents; }

/* Support's "Write a message": why a message did not go, said in the popup (sa_javascript.js) and hidden until then */
.sup_send_error[hidden] { display: none; }

@media (max-width: 640px) {
    .ect_kinds { grid-template-columns: minmax(0, 1fr); }
    .usr_column_1, .usr_column_2, .usr_column_3, .usr_column_4 { width: 100%; padding-right: 0; }
    .usr_actions { justify-content: flex-start; }
    .ect_list .ect_c_who { width: 100%; padding-right: 0; }
    .ect_list .ect_c_tick,
    .ect_list.is_three .ect_c_tick { width: auto; justify-content: flex-start; margin-right: var(--sp-4); }
    .ect_list .ect_c_end { width: auto; margin-left: auto; }
    .ect_c_tick .noc_check { padding: 12px 0; margin: 0; }
    .ect_c_tick .noc_check span {
        position: static;
        width: auto;
        height: auto;
        margin: 0;
        overflow: visible;
        clip: auto;
        white-space: normal;
    }
}

/* (end of 14A. Email contacts) */

/* -----------------------------------------------------------------------------
  14B. Plans and billing (builder B)
   -------------------------------------------------------------------------- */

/* The set-up checklist's card step when no card is needed (a Sponsored plan, or Card
   required off): a muted mark and title rather than the empty circle of a step that is
   waiting, because nothing waits on it. */
.bill_step.is_optional .bill_step_mark { color: var(--noc-ink-muted); }
.bill_step.is_optional .bill_step_title { color: var(--noc-ink-2); }

/* Card required, staff only: the sentence and its switch on one line under the card,
   above a hairline, the switch wrapping under the sentence on a phone. */
.bill_card_required { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--noc-line); }
.bill_card_required_text { font: var(--t-body); color: var(--noc-ink-2); min-width: 0; overflow-wrap: anywhere; }
.bill_card_required_text b { color: var(--noc-ink); }
/* The form is display: contents (.post_form), so the button is the flex item: without its own flex it shrank beside a
   long sentence until "Switch on" wrapped to two lines and stood 48px tall at 900px */
.bill_card_required .post_form, .bill_card_required .dashboard_form_button { flex: 0 0 auto; white-space: nowrap; }

/* The Free usage box. Its one colour is the paused state: the figure that says nothing is
   left, and the amber notice under it that says why (.notify_box.warning, toggled live). */
.bill_usage_state { margin-top: 12px; }
.bill_usage.is_paused .bill_usage_left { color: var(--st-warn); font-weight: 700; }

@media (max-width: 640px) {
    .bill_card_required { flex-wrap: wrap; }
}

/* (end of 14B. Plans and billing) */

/* -----------------------------------------------------------------------------
  14C. Usage pause and the zone pages (builder C)
   -------------------------------------------------------------------------- */

/* The usage pause. The zone page's banner and the zone list's pill are always drawn and shown or hidden by their live
   keys ("usage_pause", "usage_pill"), so a [hidden] has to win over the display their classes set. */
.upause_banner[hidden],
.zlist_upause[hidden] {
    display: none;
}
.upause_link {
    margin-left: 6px;
}
/* The zone list's two "not serving right now" chips, the shape "In maintenance" wears: "Paused: free usage used" and
   "Paused by CacheGenie" (or "Stopped: account suspended"). The words say which, and the amber says it was not the
   client's own doing. --st-warn-bg is the opaque tint, so the chip reads the same on the sheet in both themes. */
.zlist_upause,
.zlist_paused {
    display: inline-block;
    margin-left: 0;
    padding: 2px 8px;
    border-radius: var(--noc-radius-sm);
    background: var(--st-warn-bg);
    color: var(--st-warn);
    font-size: 12px;
    font-weight: 700;
    line-height: 18px;
    vertical-align: 1px;
}

/* The certificate cells' live region wraps the hostname rows without adding a box of its own */
.zc_live {
    display: contents;
}

/* Staff Pause / Resume zone: the reason is a sentence or two, not a page */
.zp_reason {
    height: 110px;
    resize: vertical;
}

/* The purge box (the key "purge", functions/cdn.php): the sentence, then a 6px bar whose fill is drawn in twenty-one
   steps by data-state, because a live answer may set that attribute and never a style. Amber while it runs, like the
   box; gone once it is done. */
.pp_box {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.pp_bar {
    display: block;
    position: relative;
    height: 6px;
    border-radius: 3px;
    background: var(--noc-line);
    overflow: hidden;
}
.pp_bar[hidden] {
    display: none;
}
.pp_fill {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 0;
    border-radius: 3px;
    background: var(--st-warn);
    transition: width 0.4s ease;
}
.pp_bar[data-state="p5"] .pp_fill { width: 5%; }
.pp_bar[data-state="p10"] .pp_fill { width: 10%; }
.pp_bar[data-state="p15"] .pp_fill { width: 15%; }
.pp_bar[data-state="p20"] .pp_fill { width: 20%; }
.pp_bar[data-state="p25"] .pp_fill { width: 25%; }
.pp_bar[data-state="p30"] .pp_fill { width: 30%; }
.pp_bar[data-state="p35"] .pp_fill { width: 35%; }
.pp_bar[data-state="p40"] .pp_fill { width: 40%; }
.pp_bar[data-state="p45"] .pp_fill { width: 45%; }
.pp_bar[data-state="p50"] .pp_fill { width: 50%; }
.pp_bar[data-state="p55"] .pp_fill { width: 55%; }
.pp_bar[data-state="p60"] .pp_fill { width: 60%; }
.pp_bar[data-state="p65"] .pp_fill { width: 65%; }
.pp_bar[data-state="p70"] .pp_fill { width: 70%; }
.pp_bar[data-state="p75"] .pp_fill { width: 75%; }
.pp_bar[data-state="p80"] .pp_fill { width: 80%; }
.pp_bar[data-state="p85"] .pp_fill { width: 85%; }
.pp_bar[data-state="p90"] .pp_fill { width: 90%; }
.pp_bar[data-state="p95"] .pp_fill { width: 95%; }
.pp_bar[data-state="p100"] .pp_fill { width: 100%; }
@media (prefers-reduced-motion: reduce) {
    .pp_bar .pp_fill {
        transition: none;
    }
}

/* (end of 14C. Usage pause and the zone pages) */

/* -----------------------------------------------------------------------------
  14E. Admin: Network, System, Clients (builder E)
   -------------------------------------------------------------------------- */
/* The jump bar at the top of Admin > Network and Admin > System (25 September 2026): one row of quiet chips naming
   the page's sections. Ink and a hairline, never colour: the teal is only under the pointer, as on every control. */
.adm_jump {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 16px 0;
}
.adm_jump a {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
    padding: 0 12px;
    border: 1px solid var(--noc-line);
    border-radius: var(--noc-radius-sm);
    background-color: var(--noc-surface);
    color: var(--noc-ink-2);
    font: var(--t-sub);
    text-decoration: none;
    transition: background-color 0.15s ease, color 0.15s ease;
}
.adm_jump a:hover {
    background-color: var(--noc-surface-2);
    color: var(--noc-accent-ink);
}
/* A section the jump bar or a handler's redirect lands on (and the flash moved above it) stops below the sticky top
   bar rather than under it */
.adm_anchor, .notify_box.is_moved { scroll-margin-top: calc(var(--noc-top-height) + 16px); }
/* A live region's part that is hidden stays hidden, whatever display its own component gives it: a notice is
   display: block, which would otherwise beat the attribute */
.adm_region [hidden] { display: none !important; }
/* The audit box's scope under its search: "Account 582 only. Show every account" */
.aud_scope { margin-top: 10px; font: var(--t-sub); color: var(--noc-ink-muted); }
@media (max-width: 640px) {
    .adm_jump a { min-height: 40px; }
}
@media (prefers-reduced-motion: reduce) {
    .adm_jump a { transition: none; }
}

/* (end of 14E. Admin: Network, System, Clients) */

/* -----------------------------------------------------------------------------
  14Z. The zone page (1 October 2026)
   One zone, five sections behind a rail: General, Hostnames, Origin, Caching, Security. The page was eleven boxes
   stacked into 5,000px with a paragraph under every control; it is now one section at a time, each a few boxes of
   rows, and every row a name, one line of what it does and its control. Every switch, select and chip saves where it
   is (cdn.php's script), so each control carries a small status beside it: Saving, Saved, or what went wrong.
   The rail is the one new device: each section's icon and name, the current one marked by the raised tone and the
   accent edge. It carries the names alone (the owner, 1 October 2026: no count or state beside them).
   -------------------------------------------------------------------------- */

/* Anything the script shows and hides by the attribute stays hidden whatever display its component gives it */
.zp_page [hidden] { display: none !important; }

/* ---- The head: the zone's name and state, the two identifiers people copy, and the two actions for any section */
.zp_head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--sp-4) var(--sp-6);
    margin-bottom: var(--sp-5);
}
.zp_head_main { min-width: 0; }
.zp_title {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    margin: 0;
    font: var(--t-h1);
    letter-spacing: -0.01em;
    color: var(--noc-ink);
}
.zp_name { min-width: 0; overflow-wrap: anywhere; }
.zp_pill {
    display: inline-block;
    padding: 2px 8px;
    border-radius: var(--noc-radius-sm);
    font: var(--t-micro);
    font-weight: 700;
    letter-spacing: 0;
    line-height: 18px;
}
.zp_pill.is_maint { background: var(--st-maint-soft); color: var(--noc-indigo-ink); }
.zp_pill.is_warn { background: var(--st-warn-bg); color: var(--st-warn); }
.zp_ids {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-6);
    margin-top: var(--sp-2);
}
.zp_id { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.zp_id_label { font: var(--t-sub); color: var(--noc-ink-muted); }
.zp_id_value { font: var(--t-mono); color: var(--noc-ink); overflow-wrap: anywhere; min-width: 0; }
.zp_copy.noc_icon_btn { margin: -4px 0; }
.zp_copy.is_done { color: var(--st-ok); }
.zp_head_actions {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--sp-2);
}

/* The maintenance banner carries its own way back */
.zp_banner {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--sp-3) var(--sp-4);
}
/* Maintenance is the indigo state everywhere (the pill, the zone list, the status page), never the amber warning */
.zp_banner.is_maint { border-left-color: var(--st-maint); background: var(--noc-indigo-bg); }
.zp_banner_text { flex: 1 1 320px; min-width: 0; }
.zp_banner form { display: flex; align-items: center; gap: var(--sp-3); }

/* ---- The rail and the sections ---------------------------------------------------------------------------- */
.zp_layout {
    display: grid;
    grid-template-columns: 208px minmax(0, 1fr);
    gap: var(--sp-6) 32px;
    align-items: start;
    margin-top: var(--sp-2);
}
.zp_rail {
    position: sticky;
    top: calc(var(--noc-top-height) + var(--sp-4));
}
.zp_rail_list { display: flex; flex-direction: column; gap: 2px; }
.zp_tab {
    position: relative;
    display: grid;
    grid-template-columns: 18px minmax(0, 1fr);
    align-items: center;
    gap: var(--sp-3);
    min-height: 42px;
    padding: 0 var(--sp-3);
    border-radius: var(--noc-radius);
    font: var(--t-body);
    color: var(--noc-ink-2);
    text-decoration: none;
}
.zp_tab:visited { color: var(--noc-ink-2); }
.zp_tab:hover { background: var(--noc-surface-2); color: var(--noc-ink); text-decoration: none; }
.zp_tab_icon { text-align: center; color: var(--noc-ink-muted); font-size: 14px; }
.zp_tab_name { min-width: 0; white-space: nowrap; }
/* The current section: the raised tone and a 2px accent edge, the panel's one mark for "where you are" */
.zp_tab.is_current, .zp_tab.is_current:visited {
    background: var(--noc-surface-2);
    color: var(--noc-ink);
    font-weight: 700;
}
.zp_tab.is_current .zp_tab_icon { color: var(--noc-ink); }
.zp_tab.is_current::before {
    content: "";
    position: absolute;
    left: 0;
    top: 9px;
    bottom: 9px;
    width: 3px;
    border-radius: 0 2px 2px 0;
    background: var(--noc-accent);
}

.zp_panels { min-width: 0; }
.zp_panel:focus { outline: none; }
.zp_panel_head { margin-bottom: var(--sp-4); }
.zp_panel_title { margin: 0; font: var(--t-h2); color: var(--noc-ink); }
.zp_panel_sub { margin: 3px 0 0; max-width: var(--measure-help); font: var(--t-sub); color: var(--noc-ink-muted); }
.zp_anchor { scroll-margin-top: calc(var(--noc-top-height) + 16px); }

/* ---- A setting row: what it is, what it does, and its control -------------------------------------------- */
.zp_row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--sp-4) var(--sp-6);
    padding: 14px var(--noc-pad-box);
    border-top: 1px solid var(--noc-line);
}
.dbc_header + .zp_row, .dbc_header + form.zp_row, .zp_box_notice + .zp_row, .zp_box_notice + form.zp_row { border-top: 0; }
.zp_row_text { flex: 1 1 auto; min-width: 0; }
.zp_row_name { display: block; font: var(--t-strong); color: var(--noc-ink); }
label.zp_row_name { cursor: default; }
.zp_row_help { margin-top: 2px; max-width: var(--measure-help); font: var(--t-sub); color: var(--noc-ink-muted); }
/* The Home PoP note (the "shield" live key): the location in use, under the help text, ink rather than muted since it is state */
.zp_shield_note { margin-top: 6px; color: var(--noc-ink); }
.zp_shield_note[hidden] { display: none; }
/* What the origin type adds to a row: why a setting is not used, or what it would break on a full site. A rule at
   its edge sets it apart from the description; the warning's rule is the attention colour. */
.zp_row_note {
    margin-top: var(--sp-2);
    max-width: var(--measure-help);
    padding-left: 10px;
    border-left: 2px solid var(--noc-line-2);
    font: var(--t-sub);
    color: var(--noc-ink-2);
}
.zp_row_note.is_warn { border-left-color: var(--st-warn); color: var(--noc-ink); }
.zp_row_control {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--sp-3);
}
.zp_row_control form { display: flex; align-items: center; }
/* A row whose control is a select: the select takes the room a switch would not */
.zp_row_select { flex: 0 1 380px; min-width: 0; }
.zp_row_select select { flex: 1 1 220px; min-width: 0; }

/* ---- Save status: words, never only a colour ------------------------------------------------------------- */
.zp_status {
    font: var(--t-sub);
    color: var(--noc-ink-muted);
    white-space: nowrap;
    text-align: right;
}
.zp_status:empty { display: none; }
.zp_status.is_ok { color: var(--st-ok); }
.zp_status.is_ok::before {
    content: "\f00c";
    margin-right: 5px;
    font-family: "Font Awesome 6 Free";
    font-weight: 900;
    font-size: 11px;
}
.zp_status.is_dirty { color: var(--st-warn); }
.zp_status.is_error { color: var(--st-error); white-space: normal; max-width: 340px; }
.zp_foot .zp_status.is_error { flex: 1 1 280px; max-width: none; text-align: left; }

/* ---- Forms with words to type: the fields in one column, the save at the foot with its status ------------ */
.zp_fields { max-width: 640px; }
.zp_field { min-width: 0; }
.zp_field .dfi_input_sub { max-width: none; }
.zp_field + .zp_field { margin-top: var(--sp-5); }
.zp_field .dfi_input_sub, .zp_field .zp_row_note { margin-top: 6px; }
.zp_foot {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--sp-3) var(--sp-4);
    margin: var(--sp-5) calc(-1 * var(--noc-pad-box)) calc(-1 * var(--noc-pad-box));
    padding: var(--sp-3) var(--noc-pad-box);
    border-top: 1px solid var(--noc-line);
    background: var(--noc-surface-2);
}
.zp_box_foot {
    padding: var(--sp-3) var(--noc-pad-box);
    border-top: 1px solid var(--noc-line);
    background: var(--noc-surface-2);
    font: var(--t-sub);
    color: var(--noc-ink-muted);
}
.zp_box_foot b { color: var(--noc-ink); font-weight: 400; }
.zp_mono { font: var(--t-mono); }
.zp_box_notice.notify_box { width: auto; margin: var(--sp-3) var(--noc-pad-box) 0; }

/* ---- Hostnames: the list's own columns -------------------------------------------------------------------- */
.zh_c1 { width: 34%; padding-right: var(--sp-3); }
.zh_c2 { width: 11%; }
.zh_c3 { width: 13%; }
.zh_c4 { width: 35%; padding-right: var(--sp-3); }
.zh_c5 { width: 7%; display: flex; justify-content: flex-end; }
.zh_host { font: var(--t-strong); color: var(--noc-ink); overflow-wrap: anywhere; }
.zh_c1 .zp_status { display: block; margin-top: 2px; text-align: left; }
.zh_c1 .zp_status:empty { display: none; }
.zh_always { font: var(--t-sub); color: var(--noc-ink-muted); }
.zh_row.is_leaving { opacity: 0.5; }
.zh_row .ch_cert_actions form { display: inline-flex; }
/* A text link in a row is padded to 34px on a matching negative margin, so it is a target without moving the line */
.zh_row .ch_cert_link { min-height: 34px; margin: -5px 0; }

/* ---- Blocking ------------------------------------------------------------------------------------------- */
.blk_summary {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--sp-1) var(--sp-4);
    padding: var(--sp-3) var(--noc-pad-box);
    background: var(--noc-surface-2);
    border-bottom: 1px solid var(--noc-line);
    font: var(--t-body);
    color: var(--noc-ink);
}
.blk_summary a { font: var(--t-sub); }
.blk_sec { padding: var(--sp-5) var(--noc-pad-box); border-top: 1px solid var(--noc-line); }
.blk_summary + .blk_sec, .zp_box_notice + .blk_sec { border-top: 0; }
.blk_sec_head { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-4); }
.blk_sec_title { font: var(--t-strong); color: var(--noc-ink); }
.blk_sec_sub { margin-top: 2px; max-width: var(--measure-help); font: var(--t-sub); color: var(--noc-ink-muted); }
.blk_sec .blk_segmented { margin-top: var(--sp-3); }
/* The network switches are rows of the section, so they lose the box's side padding the section already has */
.blk_net_row { padding-left: 0; padding-right: 0; }
.blk_sec_head + .blk_net_row { margin-top: var(--sp-2); border-top: 0; padding-top: var(--sp-2); }
.blk_net_row:last-child { padding-bottom: 0; }
.blk_sec .blk_pick { max-width: 440px; margin-top: var(--sp-3); }
.blk_sec .blk_chips:empty { display: none; }
.blk_chip_code { font-weight: 700; }
.blk_chip_name {
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--noc-ink-muted);
    font-weight: 500;
}
.blk_count { font-weight: 400; color: var(--noc-ink-muted); }
.blk_box .blk_credit { margin: 0; padding: var(--sp-3) var(--noc-pad-box); border-top: 1px solid var(--noc-line); }
.blk_sec .zp_foot { margin: var(--sp-5) calc(-1 * var(--noc-pad-box)) calc(-1 * var(--sp-5)); }
/* The country picker: a field that filters the list as it is typed into, and the list under it */
.blk_combo { position: relative; max-width: 440px; margin-top: var(--sp-3); }
.blk_combo_icon { position: absolute; left: 13px; top: 13px; font-size: 13px; color: var(--noc-ink-muted); pointer-events: none; }
.blk_combo .dashboard_form_input { padding-left: 36px; }
.blk_combo_list {
    position: absolute;
    z-index: 20;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    max-height: 272px;
    overflow-y: auto;
    margin: 0;
    padding: var(--sp-1);
    list-style: none;
    border: 1px solid var(--noc-line-2);
    border-radius: var(--noc-radius);
    background: var(--noc-surface);
}
.blk_combo_opt, .blk_combo_none {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--sp-3);
    min-height: 34px;
    padding: 0 10px;
    border-radius: var(--noc-radius-sm);
    font: var(--t-body);
    color: var(--noc-ink-2);
}
.blk_combo_opt { cursor: pointer; }
.blk_combo_opt.is_active, .blk_combo_opt:hover { background: var(--noc-surface-2); color: var(--noc-ink); }
.blk_combo_none { color: var(--noc-ink-muted); }
.blk_combo_code { font: var(--t-micro); color: var(--noc-ink-muted); }

/* ---- The purge popup: two choices, each its words and its button ----------------------------------------- */
.pg_opt { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3) var(--sp-4); }
.pg_opt_text { flex: 1 1 auto; min-width: 0; }
.pg_opt > .dashboard_form_button { flex: 0 0 auto; white-space: nowrap; }
.pg_opt + .pg_opt { margin-top: var(--sp-5); padding-top: var(--sp-5); border-top: 1px solid var(--noc-line); }
.pg_opt_file { flex-direction: column; align-items: stretch; }
#purge_progress { margin-bottom: var(--sp-5); }

/* ---- Tablet: the rail becomes a strip of tabs above the section, held under the top bar --------------------- */
@media (max-width: 900px) {
    .zp_layout { grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); }
    .zp_rail {
        top: var(--noc-top-height);
        z-index: 5;
        margin: 0 -18px;
        padding: 0 18px;
        background: var(--noc-surface);
        border-bottom: 1px solid var(--noc-line);
    }
    .zp_rail_list {
        flex-direction: row;
        gap: var(--sp-1);
        overflow-x: auto;
        scrollbar-width: none;
    }
    .zp_rail_list::-webkit-scrollbar { display: none; }
    .zp_tab {
        flex: 0 0 auto;
        grid-template-columns: auto auto;
        gap: var(--sp-2);
        min-height: 48px;
        border-radius: 0;
    }
    .zp_tab:hover { background: none; }
    .zp_tab.is_current { background: none; }
    .zp_tab.is_current::before { left: var(--sp-3); right: var(--sp-3); top: auto; bottom: -1px; width: auto; height: 3px; border-radius: 2px 2px 0 0; }
    .zh_c1 { width: 32%; }
    .zh_c4 { width: 37%; }
}

/* ---- Phone ---------------------------------------------------------------------------------------------- */
@media (max-width: 640px) {
    .zp_head { flex-direction: column; align-items: stretch; margin-bottom: var(--sp-4); }
    .zp_head_actions { flex-wrap: nowrap; }
    .zp_head_actions > .dashboard_form_button { flex: 1 1 0; width: auto; min-width: 0; }
    .zp_ids { flex-direction: column; gap: var(--sp-1); }
    .zp_rail { margin: 0 -14px; padding: 0 14px; }
    .zp_tab_icon { display: none; }
    .zp_tab { padding: 0 10px; }
    .zp_tab.is_current::before { left: 10px; right: 10px; }
    .zp_row { gap: var(--sp-3) var(--sp-4); padding: var(--sp-3) 14px; }
    .zp_row.blk_net_row { padding-left: 0; padding-right: 0; }
    .pg_opt { flex-direction: column; align-items: stretch; }
    /* A select row stacks: the select takes the width */
    .zp_row_field { flex-direction: column; align-items: stretch; }
    .zp_row_select { flex: 1 1 auto; flex-direction: row-reverse; justify-content: flex-start; }
    .zp_status.is_error { max-width: none; text-align: left; }
    .zp_banner form { width: 100%; }
    .blk_grid { grid-template-columns: minmax(0, 1fr); }
    /* A hostname is a card: its name and Delete across the top, the two switches side by side under a label each,
       then the certificate */
    .zh_head { display: none; }
    .zh_row { position: relative; flex-wrap: wrap; align-items: flex-start; gap: var(--sp-3) 0; padding: var(--sp-3) 14px; }
    .zh_c1 { width: calc(100% - 48px); padding-right: 0; }
    .zh_c2, .zh_c3 { width: 50%; }
    .zh_c4 { width: 100%; padding-right: 0; }
    .zh_c5 { position: absolute; top: var(--sp-2); right: 8px; width: auto; }
    .zh_c2::before, .zh_c3::before, .zh_c4::before {
        content: attr(data-label);
        display: block;
        margin-bottom: 6px;
        font: var(--t-micro);
        color: var(--noc-ink-muted);
    }
}

/* (end of 14Z. The zone page) */

/* -----------------------------------------------------------------------------
  14L. The lead
   -------------------------------------------------------------------------- */

/* (end of 14L. The lead) */
