/* ============================================================
   ADMIN WORKBENCH — claude-design master-detail system
   ============================================================
   Implements the design_handoff_tracker_admin bundle:
   - 3b  list card + right edit drawer (dev.info, group.info)
   - 3c  full-page user workbench with permission matrix (user.info)
   - 3a  shared field/chip/pill patterns
   DOM is built by the adminWorkbench module in modern-patches.js
   over the untouched server forms. All selectors are scoped to
   html.modern-ui and the mpw-/mpe- namespaces.
   ============================================================ */

/* ── page scaffold ─────────────────────────────────────────── */
html.modern-ui body.mpw-on td.contentCell {
    padding-top: 22px !important;
}
html.modern-ui .mpw-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin: 0 0 16px;
    font-family: var(--cmw-font);
}
html.modern-ui .mpw-title {
    margin: 0;
    font-family: var(--cmw-font-display);
    font-size: 18px;
    font-weight: 600;
    color: var(--cmw-ink);
}
html.modern-ui .mpw-sub {
    font-size: 12.5px;
    color: var(--cmw-text-faint);
}
html.modern-ui .mpw-spacer { flex: 1; }

/* search pill */
html.modern-ui .mpw-search {
    position: relative;
}
html.modern-ui .mpw-search input {
    width: 280px;
    height: 36px;
    box-sizing: border-box;
    padding: 0 34px 0 34px;
    font: 13px var(--cmw-font);
    color: var(--cmw-ink);
    background: var(--cmw-surface);
    border: 1px solid var(--cmw-border-input);
    border-radius: var(--cmw-radius-pill);
    outline: none;
}
html.modern-ui .mpw-search input::placeholder { color: var(--cmw-text-faint); }
html.modern-ui .mpw-search input:focus {
    border-color: var(--cmw-primary);
    box-shadow: var(--cmw-focus-ring);
}
html.modern-ui .mpw-search::before {
    content: "";
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    width: 14px;
    height: 14px;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%238fa2b3' stroke-width='2.4' stroke-linecap='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3C/svg%3E") no-repeat center;
    pointer-events: none;
}

/* clear ✕ (v3.117.0) — sits at the pill's right end, shown only once
   there is something to clear (:placeholder-shown does the hiding, no
   JS state). The generic button skin paints every button primary blue
   with a hover lift — flattened back out here. */
html.modern-ui .mpw-clear {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%) !important;
    /* width/min-width/margin armored: the ≤520px global button rule
       sets min-width:72px and a margin on every button, !important */
    width: 24px !important;
    min-width: 0 !important;
    margin: 0 !important;
    height: 24px;
    min-height: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    background: none !important;
    border-radius: 50%;
    color: var(--cmw-text-faint) !important;
    font: 13px/24px var(--cmw-font);
    text-align: center;
    cursor: pointer;
    box-shadow: none !important;
}
html.modern-ui .mpw-clear:hover {
    color: var(--cmw-ink) !important;
    background: var(--cmw-neutral-chip-bg) !important;
}
/* !important against the ≤520px global button rule's display flip */
html.modern-ui .mpw-search input:placeholder-shown ~ .mpw-clear { display: none !important; }

/* status filter chips */
html.modern-ui .mpw-chips { display: flex; gap: 8px; }
html.modern-ui .mpw-fchip {
    appearance: none;
    border: 1px solid transparent;
    background: var(--cmw-neutral-chip-bg);
    color: var(--cmw-neutral-chip);
    font: 600 12px var(--cmw-font);
    padding: 7px 14px;
    border-radius: var(--cmw-radius-pill);
    cursor: pointer;
}
html.modern-ui .mpw-fchip.on {
    background: var(--cmw-ink);
    color: #fff;
}
html.modern-ui .mpw-fchip.f-yes:not(.on) { background: var(--cmw-success-bg); color: var(--cmw-success); }
html.modern-ui .mpw-fchip:hover:not(.on) { border-color: var(--cmw-border-strong); }

/* create controls */
html.modern-ui .mpw-new { display: flex; gap: 10px; align-items: center; }
html.modern-ui .mpw-new input,
html.modern-ui .mpw-new select {
    height: 36px;
    box-sizing: border-box;
    padding: 0 14px;
    font: 13px var(--cmw-font);
    color: var(--cmw-ink);
    background: var(--cmw-surface);
    border: 1px solid var(--cmw-border-input);
    border-radius: var(--cmw-radius-pill);
    outline: none;
    width: 170px;
}
html.modern-ui .mpw-new input:focus { border-color: var(--cmw-primary); box-shadow: var(--cmw-focus-ring); }
html.modern-ui .mpw-newbtn {
    appearance: none;
    border: none;
    height: 36px;
    padding: 0 18px;
    font: 600 13px var(--cmw-font-display);
    color: #fff;
    background: var(--cmw-primary);
    border-radius: var(--cmw-radius-pill);
    box-shadow: var(--cmw-shadow-primary);
    cursor: pointer;
}
html.modern-ui .mpw-newbtn:hover { background: var(--cmw-btn-bg-hover); }
html.modern-ui .mpw-newbtn:active { background: var(--cmw-btn-bg-active); }

/* create bar — sits under the list, mirroring the legacy
   "Create a new user:" step. Kept off the toolbar so the only text
   field up there is the one that actually searches. */
html.modern-ui .mpw-create {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    box-sizing: border-box;
    max-width: 1160px;
    margin-top: 14px;
    padding: 12px 16px;
    background: var(--cmw-surface);
    border: 1px solid var(--cmw-border);
    border-radius: var(--cmw-radius);
}
html.modern-ui .mpw-createlab {
    font: 600 10.5px var(--cmw-font-mono);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--cmw-text-muted);
}

/* ── list card ─────────────────────────────────────────────── */
html.modern-ui .mpw-card {
    background: var(--cmw-surface);
    border: 1px solid var(--cmw-border);
    border-radius: var(--cmw-radius);
    overflow: hidden;
    font-family: var(--cmw-font);
    max-width: 1160px;
}
html.modern-ui .mpw-gridhead,
html.modern-ui .mpw-row {
    display: grid;
    grid-template-columns: var(--mpw-grid, 1.8fr 1fr 0.7fr 1fr);
    align-items: center;
    column-gap: 12px;
    padding: 0 16px;
}
html.modern-ui .mpw-gridhead {
    background: var(--cmw-surface-row);
    border-bottom: 1px solid var(--cmw-border);
}
html.modern-ui .mpw-gridhead span {
    font: 600 10.5px var(--cmw-font-mono);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--cmw-text-muted);
    padding: 10px 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
html.modern-ui .mpw-row {
    border-bottom: 1px solid var(--cmw-divider);
    cursor: pointer;
    min-height: 52px;
}
html.modern-ui .mpw-row:last-child { border-bottom: none; }
html.modern-ui .mpw-row:hover { background: var(--cmw-surface-row); }
html.modern-ui .mpw-row.sel {
    background: var(--cmw-primary-tint);
    box-shadow: inset 3px 0 0 var(--cmw-primary);
}
html.modern-ui .mpw-cell {
    font-size: 13px;
    color: var(--cmw-text);
    padding: 8px 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* ── full-detail list (user.info) ───────────────────────────
   Lists that carry every column the legacy select table had.
   The default row ellipsises anything that overruns its track;
   here the values genuinely vary in length (a URL in Address, a
   comma list in Assigned Groups) so they WRAP instead — the row
   grows a line, no character is lost. */
html.modern-ui .mpw-card.mpw-detail .mpw-gridhead {
    align-items: end;
}
html.modern-ui .mpw-card.mpw-detail .mpw-gridhead span {
    white-space: normal;
    overflow: visible;
    line-height: 1.25;
}
html.modern-ui .mpw-card.mpw-detail .mpw-cell {
    white-space: normal;
    overflow: visible;
    overflow-wrap: anywhere;
    line-height: 1.35;
}
html.modern-ui .mpw-card.mpw-detail .mpw-main i {
    overflow-wrap: anywhere;
}
html.modern-ui .mpw-card.mpw-detail .mpw-chipcell {
    padding: 8px 0;
}

html.modern-ui .mpw-main b {
    font-weight: 600;
    color: var(--cmw-ink);
}
html.modern-ui .mpw-main i {
    font: 11px var(--cmw-font-mono);
    font-style: normal;
    color: var(--cmw-primary-dark);
    margin-left: 7px;
}
html.modern-ui .mpw-main small {
    display: block;
    font: 10.5px var(--cmw-font-mono);
    color: var(--cmw-text-faint);
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
}
html.modern-ui .mpw-foot {
    padding: 11px 16px;
    font-size: 12px;
    color: var(--cmw-text-faint);
    border-top: 1px solid var(--cmw-divider);
}
html.modern-ui .mpw-empty {
    padding: 40px 16px;
    text-align: center;
    font-size: 13px;
    color: var(--cmw-text-faint);
    display: none;
}

/* per-row delete (classic pages without a drawer footer) */
html.modern-ui .mpw-rowdel {
    appearance: none;
    border: none;
    background: transparent;
    color: var(--cmw-text-faint);
    font-size: 12px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    cursor: pointer;
    justify-self: end;
}
html.modern-ui .mpw-rowdel:hover {
    background: var(--cmw-danger-bg);
    color: var(--cmw-danger);
}

/* status chips (shared) */
html.modern-ui .mpw-chip {
    display: inline-block;
    font: 600 11.5px var(--cmw-font);
    padding: 3px 11px;
    border-radius: var(--cmw-radius-pill);
    background: var(--cmw-neutral-chip-bg);
    color: var(--cmw-neutral-chip);
}
html.modern-ui .mpw-chip.yes { background: var(--cmw-success-bg); color: var(--cmw-success); }
html.modern-ui .mpw-chip.warn { background: var(--cmw-warning-bg); color: var(--cmw-warning-ink); }

html.modern-ui .mpe-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px 12px;
}
html.modern-ui .mpe-field label {
    display: block;
    font: 600 11px var(--cmw-font);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--cmw-text-muted);
    margin: 0 0 5px;
}
html.modern-ui .mpe-field input[type="text"],
html.modern-ui .mpe-field input[type="password"],
html.modern-ui .mpe-field input:not([type]),
html.modern-ui .mpe-field select,
html.modern-ui .mpe-field textarea {
    width: 100% !important;
    box-sizing: border-box;
    border: 1px solid var(--cmw-border-input) !important;
    border-radius: var(--cmw-radius-field) !important;
    padding: 8px 12px !important;
    font: 13px var(--cmw-font) !important;
    color: var(--cmw-ink) !important;
    background: var(--cmw-surface) !important;
    height: auto !important;
    outline: none;
}
html.modern-ui .mpe-field.mono input,
html.modern-ui .mpe-field.mono textarea {
    font-family: var(--cmw-font-mono) !important;
    font-size: 12.5px !important;
}
html.modern-ui .mpe-field input:focus,
html.modern-ui .mpe-field select:focus,
html.modern-ui .mpe-field textarea:focus {
    border-color: var(--cmw-primary) !important;
    box-shadow: var(--cmw-focus-ring);
}
html.modern-ui .mpe-field input[readonly] {
    background: var(--cmw-surface-soft) !important;
    border-color: var(--cmw-divider) !important;
    color: var(--cmw-text-faint) !important;
}
html.modern-ui .mpe-field.wide { grid-column: 1 / -1; }
html.modern-ui .mpe-field textarea { min-height: 74px; resize: vertical; }

/* toggle pills */
html.modern-ui .mpe-toggles {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 18px;
}
html.modern-ui .mpe-toggle {
    appearance: none;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 14px;
    font: 600 12.5px var(--cmw-font);
    border-radius: var(--cmw-radius-pill);
    border: 1px solid var(--cmw-border-input);
    background: var(--cmw-surface);
    color: var(--cmw-text-muted);
    cursor: pointer;
}
/* knob AFTER the label, always (user, 2026-08-15): text leads, the
   switch sits at the pill's right end */
html.modern-ui .mpe-toggle .knob {
    order: 1;
    margin-left: auto;
    flex: 0 0 auto;
    width: 26px;
    height: 15px;
    border-radius: 8px;
    background: #cfd9e2;
    position: relative;
    transition: background 0.15s ease;
}
html.modern-ui .mpe-toggle .knob::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: #fff;
    transition: left 0.15s ease;
}
html.modern-ui .mpe-toggle.on {
    border-color: var(--cmw-success-border);
    background: var(--cmw-success-bg);
    color: var(--cmw-success);
}
html.modern-ui .mpe-toggle.on .knob { background: #3f9e5a; }
html.modern-ui .mpe-toggle.on .knob::after { left: 13px; }

/* advanced disclosure (alerts tab) */

/* group chips */
html.modern-ui label.mpe-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 14px;
    font: 600 12.5px var(--cmw-font);
    border-radius: var(--cmw-radius-pill);
    border: 1px solid var(--cmw-border-input);
    background: var(--cmw-surface);
    color: var(--cmw-text-muted);
    cursor: pointer;
    user-select: none;
}
html.modern-ui label.mpe-chip input { display: none; }
html.modern-ui label.mpe-chip:has(input:checked) {
    background: var(--cmw-primary-tint);
    border-color: var(--cmw-border-strong);
    color: var(--cmw-primary-dark);
}
html.modern-ui label.mpe-chip:has(input:checked)::before {
    content: "✓";
    font-size: 11px;
}
html.modern-ui label.mpe-chip.locked {
    opacity: 0.75;
    cursor: not-allowed;
}
html.modern-ui label.mpe-chip.locked::after { content: "🔒"; font-size: 10px; }

/* read-only member table (group.info drawer) */

/* footer */
html.modern-ui .mpe-btn {
    appearance: none;
    height: 38px;
    padding: 0 20px;
    border-radius: var(--cmw-radius-pill);
    font: 600 13px var(--cmw-font-display);
    cursor: pointer;
}
html.modern-ui .mpe-btn.ghost {
    background: var(--cmw-surface);
    border: 1px solid var(--cmw-border-strong);
    color: var(--cmw-text-muted);
}
html.modern-ui .mpe-btn.ghost:hover { color: var(--cmw-ink); }
html.modern-ui .mpe-btn.primary {
    background: var(--cmw-primary);
    border: none;
    color: #fff;
    box-shadow: var(--cmw-shadow-primary);
}
html.modern-ui .mpe-btn.primary:hover { background: var(--cmw-btn-bg-hover); }
/* destructive: outlined rather than filled, so the only solid button in
   a footer stays the confirming one and Delete never reads as the
   default action */
html.modern-ui .mpe-btn.danger {
    background: var(--cmw-surface);
    border: 1px solid var(--cmw-danger);
    color: var(--cmw-danger);
}
html.modern-ui .mpe-btn.danger:hover {
    background: var(--cmw-danger-bg);
}

/* legacy edit form remnants stay hidden once the workbench built */
html.modern-ui .mpe-hidden-src { display: none !important; }

/* ── page bar + card (acct.info, report picker) ─────────────── */
html.modern-ui .mpu-bar {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 2px;
}
html.modern-ui .mpu-back {
    font: 600 13px var(--cmw-font);
    color: var(--cmw-primary-dark);
    text-decoration: none;
    cursor: pointer;
    background: none;
    border: none;
    padding: 0;
}
html.modern-ui .mpu-bar b {
    font: 600 18px var(--cmw-font-display);
    color: var(--cmw-ink);
}
html.modern-ui .mpu-bar span.desc { font-size: 13px; color: var(--cmw-text-muted); }
html.modern-ui .mpu-bar .right {
    margin-left: auto;
    font: 11.5px var(--cmw-font-mono);
    color: var(--cmw-text-faint);
}
html.modern-ui .mpu-card {
    background: var(--cmw-surface);
    border: 1px solid var(--cmw-border);
    border-radius: var(--cmw-radius);
    padding: 18px 20px;
}
html.modern-ui .mpu-card > h3 {
    margin: 0 0 14px;
    font: 600 14px var(--cmw-font-display);
    color: var(--cmw-ink);
}

/* ── responsive ────────────────────────────────────────────── */
@media (max-width: 899px) {
    html.modern-ui .mpe-grid { grid-template-columns: 1fr; }
    html.modern-ui .mpw-head { gap: 10px; }
    html.modern-ui .mpw-search input { width: 100%; }
    html.modern-ui .mpw-search { flex: 1 1 100%; order: 5; }
    /* create bar stacks: caption on its own line, field takes the rest.
       min-width:0 on the wrapper matters — its automatic minimum is the
       field's intrinsic width, which pushes the button off-card at 320. */
    html.modern-ui .mpw-create { gap: 8px; }
    html.modern-ui .mpw-createlab { flex: 1 1 100%; }
    html.modern-ui .mpw-new { flex: 1 1 100%; min-width: 0; flex-wrap: wrap; }
    html.modern-ui .mpw-new input,
    html.modern-ui .mpw-new select { flex: 1 1 130px; width: auto; min-width: 0; }
}
/* ── stacked record layout ──────────────────────────────────
   A detail list stops being columns below the width its columns
   actually need, and becomes a record: identity on its own line, then
   every remaining field as a labelled pair that flows. Nothing is
   dropped — hiding columns is what lost the data in the first place.
   The .mpw-stacked class is set by the card measuring itself, because
   three columns and eleven columns do not share a breakpoint. */
html.modern-ui .mpw-card.mpw-stacked .mpw-gridhead { display: none; }
html.modern-ui .mpw-card.mpw-stacked .mpw-row {
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
    column-gap: 18px;
    row-gap: 10px;
    padding: 12px 16px 14px;
    position: relative;
    align-items: start;    /* labels line up even when a value wraps */
}
html.modern-ui .mpw-card.mpw-stacked .mpw-row > .mpw-main {
    grid-column: 1 / -1;
    padding: 0 34px 4px 0;
}
html.modern-ui .mpw-card.mpw-stacked .mpw-row > .mpw-cell {
    padding: 0;
}
html.modern-ui .mpw-card.mpw-stacked .mpw-row > .mpw-cell::before {
    content: attr(data-label);
    display: block;
    font: 600 9.5px var(--cmw-font-mono);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--cmw-text-muted);
    margin-bottom: 1px;
}
/* the identity keeps its heading too — it is a real column of the
   legacy table, not decoration */
html.modern-ui .mpw-card.mpw-stacked .mpw-row > .mpw-main::before {
    margin-bottom: 2px;
}
html.modern-ui .mpw-card.mpw-stacked .mpw-rowdel {
    position: absolute;
    top: 10px;
    right: 12px;
}
/* the "all" group and anything else the legacy table lists but does not
   let you select: shown in full, just not clickable */
html.modern-ui .mpw-row.mpw-fixed { cursor: default; }
html.modern-ui .mpw-row.mpw-fixed:hover { background: transparent; }
@media (max-width: 640px) {
    /* 375px: list keeps only the composite cell + status chip */
    html.modern-ui .mpw-card:not(.mpw-detail) .mpw-gridhead,
    html.modern-ui .mpw-card:not(.mpw-detail) .mpw-row {
        grid-template-columns: 1fr auto !important;
    }
    html.modern-ui .mpw-card:not(.mpw-detail) .mpw-gridhead span:nth-child(n+2),
    html.modern-ui .mpw-card:not(.mpw-detail) .mpw-row > *:not(.mpw-main):not(.mpw-chip) {
        display: none !important;
    }
}

/* ============================================================
   ARMOR — modern-overrides.css paints every bare <button> and
   <input> with !important (teal bg, white text, hover lift).
   Re-assert every contested property here with !important; these
   selectors carry class specificity so they win.
   ============================================================ */
html.modern-ui .mpw-fchip,
html.modern-ui .mpw-rowdel,
html.modern-ui .mpe-toggle,
html.modern-ui .mpu-back,
html.modern-ui label.mpe-chip,
html.modern-ui .mpe-btn.ghost {
    background: var(--cmw-surface) !important;
    color: var(--cmw-text-muted) !important;
    border: none !important;
    box-shadow: none !important;
    transform: none !important;
    min-height: 0 !important;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
html.modern-ui .mpw-fchip {
    background: var(--cmw-neutral-chip-bg) !important;
    color: var(--cmw-neutral-chip) !important;
    border: 1px solid transparent !important;
    padding: 7px 14px !important;
    font: 600 12px var(--cmw-font) !important;
}
html.modern-ui .mpw-fchip.on {
    background: var(--cmw-ink) !important;
    color: #fff !important;
}
html.modern-ui .mpw-fchip:hover:not(.on) { border-color: var(--cmw-border-strong) !important; }
html.modern-ui .mpw-rowdel {
    background: transparent !important;
    padding: 0 !important;
    font-size: 12px !important;
}
html.modern-ui .mpw-rowdel:hover {
    background: var(--cmw-danger-bg) !important;
    color: var(--cmw-danger) !important;
}
html.modern-ui .mpe-toggle {
    /* display+margin: the ≤520px global button rule flips every button
       to inline-block !important — that collapses the knob span onto
       the label text; the pill must stay a flex row at every width */
    display: inline-flex !important;
    margin: 0 !important;
    border: 1px solid var(--cmw-border-input) !important;
    padding: 7px 14px !important;
    font: 600 12.5px var(--cmw-font) !important;
    border-radius: var(--cmw-radius-pill) !important;
}
html.modern-ui .mpe-toggle.on {
    border-color: var(--cmw-success-border) !important;
    background: var(--cmw-success-bg) !important;
    color: var(--cmw-success) !important;
}
html.modern-ui .mpu-back {
    background: transparent !important;
    padding: 0 !important;
    color: var(--cmw-primary-dark) !important;
    font: 600 12px var(--cmw-font) !important;
}
html.modern-ui .mpu-back { font-size: 13px !important; }
html.modern-ui label.mpe-chip {
    border: 1px solid var(--cmw-border-input) !important;
    padding: 7px 14px !important;
}
html.modern-ui label.mpe-chip:has(input:checked) {
    background: var(--cmw-primary-tint) !important;
    border-color: var(--cmw-border-strong) !important;
    color: var(--cmw-primary-dark) !important;
}
html.modern-ui .mpw-newbtn,
html.modern-ui .mpe-btn.primary {
    background: var(--cmw-primary) !important;
    color: #fff !important;
    border: none !important;
    box-shadow: var(--cmw-shadow-primary) !important;
    transform: none !important;
    font-family: var(--cmw-font-display) !important;
}
html.modern-ui .mpw-newbtn:hover,
html.modern-ui .mpe-btn.primary:hover { background: var(--cmw-btn-bg-hover) !important; }
html.modern-ui .mpe-btn.ghost {
    border: 1px solid var(--cmw-border-strong) !important;
    padding: 0 20px !important;
}
/* restated with !important for the same reason as .primary above:
   modern-overrides paints every <button> teal with !important, which
   otherwise leaves Delete looking like the confirming action */
html.modern-ui .mpe-btn.danger {
    background: var(--cmw-surface) !important;
    border: 1px solid var(--cmw-danger) !important;
    color: var(--cmw-danger) !important;
    box-shadow: none !important;
    transform: none !important;
    padding: 0 20px !important;
    font-family: var(--cmw-font-display) !important;
}
html.modern-ui .mpe-btn.danger:hover {
    background: var(--cmw-danger-bg) !important;
    border-color: var(--cmw-danger) !important;
}
html.modern-ui .mpe-btn { min-height: 38px !important; }

/* inputs: search / new / perm filter vs overrides' padding rules.
   width is armored too (v3.117.0): the same overrides file carries
   `input[type="text"] { width: auto !important }`, which had been
   quietly beating the pill widths all along — the ✕ made it visible,
   anchored to a box wider than the input it belonged to. */
html.modern-ui .mpw-search input {
    width: 280px !important;
    /* the overrides also cap text inputs at max-width:500px !important —
       that left the full-width phone pill stopping 236px short of its ✕ */
    max-width: 100% !important;
    padding: 0 34px 0 34px !important;
    border: 1px solid var(--cmw-border-input) !important;
    border-radius: var(--cmw-radius-pill) !important;
    background: var(--cmw-surface) !important;
    min-height: 36px !important;
    box-shadow: none !important;
}
html.modern-ui .mpw-search input:focus {
    border-color: var(--cmw-primary) !important;
    box-shadow: var(--cmw-focus-ring) !important;
}
/* after the armor rule above, so this one wins below 900px */
@media (max-width: 899px) {
    html.modern-ui .mpw-search input { width: 100% !important; }
}
html.modern-ui .mpw-new input,
html.modern-ui .mpw-new select {
    border: 1px solid var(--cmw-border-input) !important;
    border-radius: var(--cmw-radius-pill) !important;
    padding: 0 14px !important;
    min-height: 36px !important;
    background: var(--cmw-surface) !important;
}

/* status chip must not stretch to the grid track */
html.modern-ui .mpw-chip { justify-self: start; }

/* footer "Close tasks ▾" mini menu */

/* ============================================================
   4a — ACCOUNT SETTINGS (acct.info)
   ============================================================ */
html.modern-ui .mpa {
    display: block;
    font-family: var(--cmw-font);
    max-width: 1060px;
    padding-bottom: 76px;   /* room for the sticky save bar */
}
html.modern-ui .mpu-bar .desc.mono {
    font: 12px var(--cmw-font-mono);
    color: var(--cmw-text-faint);
}
html.modern-ui .mpa-grid {
    display: grid;
    grid-template-columns: 1.25fr 1fr;
    gap: 18px;
    align-items: start;
}
html.modern-ui .mpa-col {
    display: flex;
    flex-direction: column;
    gap: 18px;
    min-width: 0;
}
html.modern-ui .mpe-help {
    font-size: 11px;
    color: var(--cmw-text-faint);
    margin-top: 4px;
}
html.modern-ui .mpa-term {
    display: grid;
    grid-template-columns: 110px 1fr 1fr;
    gap: 10px 12px;
    align-items: center;
}
html.modern-ui .mpa-term .th {
    font: 600 10px var(--cmw-font-mono);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--cmw-text-faint);
}
html.modern-ui .mpa-term .rl {
    font: 600 12px var(--cmw-font);
    color: var(--cmw-text-muted);
}
html.modern-ui .mpa-term input {
    width: 100% !important;
    box-sizing: border-box;
    border: 1px solid var(--cmw-border-input) !important;
    border-radius: var(--cmw-radius-field) !important;
    padding: 8px 12px !important;
    font: 13px var(--cmw-font) !important;
    color: var(--cmw-ink) !important;
    background: var(--cmw-surface) !important;
    height: auto !important;
}
html.modern-ui .mpa-term input:focus {
    border-color: var(--cmw-primary) !important;
    box-shadow: var(--cmw-focus-ring);
    outline: none;
}
html.modern-ui .mpa-note {
    background: var(--cmw-primary-tint);
    border: 1px solid var(--cmw-border-strong);
    border-radius: var(--cmw-radius);
    padding: 12px 14px;
    font-size: 12px;
    color: #1e6f94;
    line-height: 1.5;
}
html.modern-ui .mpa-save {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    gap: 12px;
    background: var(--cmw-surface);
    border-top: 1px solid var(--cmw-border);
    padding: 12px 22px;
    z-index: 800;
}
html.modern-ui .mpa-save .state {
    font-size: 12px;
    color: var(--cmw-text-faint);
}
html.modern-ui .mpa-save .sp { flex: 1; }

@media (max-width: 1100px) {
    html.modern-ui .mpa-grid { grid-template-columns: 1fr; }
}
@media (max-width: 767px) {
    html.modern-ui .mpa-save { padding: 10px 14px; }
    html.modern-ui .mpa-save .state { display: none; }
    html.modern-ui .mpa-save .mpe-btn.ghost { flex: 1; }
    html.modern-ui .mpa-save .mpe-btn.primary { flex: 2; min-height: 46px !important; border-radius: 23px !important; }
}

/* ============================================================
   4b — REPORT PICKER (menu.rpt.*)
   ============================================================ */
html.modern-ui .mpr {
    display: grid;
    grid-template-columns: 320px 1fr;
    gap: 18px;
    align-items: start;
    max-width: 1160px;
    font-family: var(--cmw-font);
}
html.modern-ui .mpr-setup { min-width: 0; }
html.modern-ui .mpr-list { min-width: 0; padding: 0 !important; overflow: hidden; }
html.modern-ui .mpr-listhead {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 16px 18px 12px;
    border-bottom: 1px solid var(--cmw-border);
}
html.modern-ui .mpr-listhead h3 {
    margin: 0;
    font: 600 14px var(--cmw-font-display);
    color: var(--cmw-ink);
}
/* v3.59.1 — code badge, inline right after the report name.
   Deliberately NOT margin-left:auto: it belongs to the name, not to
   the row edge. Neutral surface so it reads on white rows; on the
   selected (tinted) row it flips to white + accent ink. */
html.modern-ui .mpr-code {
    display: inline-block;
    margin-left: 8px;
    padding: 1.5px 7px;
    border-radius: 6px;
    background: var(--cmw-surface-alt);
    font: 600 11.5px var(--cmw-font-mono) !important;
    font-style: normal;
    letter-spacing: 0.05em;
    color: var(--cmw-text-muted) !important;
    vertical-align: 1px;
}
html.modern-ui .mpr-row.sel .mpr-code {
    background: var(--cmw-surface);
    color: var(--cmw-primary-dark) !important;
}
html.modern-ui .mpr-row {
    padding: 11px 18px;
    border-bottom: 1px solid var(--cmw-divider);
    cursor: pointer;
}
html.modern-ui .mpr-row:hover { background: var(--cmw-surface-row); }
html.modern-ui .mpr-row.sel {
    background: var(--cmw-primary-tint);
    box-shadow: inset 3px 0 0 var(--cmw-primary);
}
html.modern-ui .mpr-row span.reportRadioButton {
    display: flex !important;
    align-items: center;
    gap: 10px;
    background: transparent !important;
    padding: 0 !important;
    border: none !important;
}
html.modern-ui .mpr-row input[type="radio"] {
    width: 16px;
    height: 16px;
    accent-color: var(--cmw-primary-dark);
    margin: 0;
    flex: 0 0 16px;
}
html.modern-ui .mpr-row label {
    font: 600 13px var(--cmw-font) !important;
    color: var(--cmw-ink) !important;
    cursor: pointer;
    white-space: normal !important;
}
html.modern-ui .mpr-desc {
    display: none;
    font-size: 12px;
    color: var(--cmw-text);
    margin: 5px 0 0 26px;
    line-height: 1.45;
}
html.modern-ui .mpr-row.sel .mpr-desc { display: block; }
html.modern-ui .mpr-row select {
    display: block;
    margin: 8px 0 2px 26px;
    max-width: calc(100% - 26px);
    border: 1px solid var(--cmw-border-input) !important;
    border-radius: var(--cmw-radius-field) !important;
    padding: 7px 10px !important;
    font: 12.5px var(--cmw-font) !important;
    color: var(--cmw-ink) !important;
    background: var(--cmw-surface) !important;
}
html.modern-ui .mpr-row select:disabled { display: none !important; }
html.modern-ui .mpr-row input[type="text"] {
    display: block;
    margin: 6px 0 2px 26px;
    border: 1px solid var(--cmw-border-input) !important;
    border-radius: var(--cmw-radius-field) !important;
    padding: 7px 10px !important;
    font: 12.5px var(--cmw-font) !important;
}
html.modern-ui .mpr-row:not(.sel) input[type="text"],
html.modern-ui .mpr-row:not(.sel) select { display: none !important; }
html.modern-ui .mpr-row b,
html.modern-ui .mpr-row > span:not(.reportRadioButton) { display: none; }
html.modern-ui .mpr-row.sel > span:not(.reportRadioButton) {
    display: block;
    font-size: 11.5px;
    color: var(--cmw-text-muted);
    margin: 6px 0 0 26px;
}
/* v3.60.0 — report actions float bottom-right, so Format + Get report
   are reachable without scrolling to the end of a long report list.
   It was a sticky card footer, which only sticks while the card itself
   is on screen. NOTE: the element is NOT moved in the DOM — position
   is the only change, so the GetReport form (cross-referenced by the
   page's other forms via document.GetReport.field) stays intact.
   A fixed descendant is not clipped by the card's overflow:hidden,
   and no ancestor carries a transform that would trap it. */
html.modern-ui .mpr-foot {
    position: fixed;
    right: 22px;
    bottom: 22px;
    left: auto;
    top: auto;
    z-index: 1200;
    width: auto;
    max-width: calc(100vw - 44px);
    box-sizing: border-box;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 10px;
    background: var(--cmw-surface);
    border: 1px solid var(--cmw-border);
    border-radius: var(--cmw-radius-lg);
    box-shadow: var(--cmw-shadow);
    padding: 13px 15px;
}
/* the mono summary line is hidden: it read as grey noise in the
   floating panel, and the same information is already on the setup
   card. The element stays in the DOM so updateSummary() keeps working
   (it has several call sites). */
html.modern-ui .mpr-sum {
    display: none;
}
html.modern-ui .mpr-foot .sp { display: none; }   /* column layout: no spacer */
html.modern-ui .mpr-foot form#GetReport {
    display: flex !important;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    margin: 0;
}
/* clear the floating panel so the end of the list stays clickable */
html.modern-ui .mpr {
    padding-bottom: 120px;
}
/* mobile: a full-width action bar reads better than a corner card */
@media (max-width: 899px) {
    html.modern-ui .mpr-foot {
        right: 0;
        bottom: 0;
        left: 0;
        width: auto;
        max-width: none;
        border-width: 1px 0 0;
        border-radius: 0;
    }
    html.modern-ui .mpr { padding-bottom: 150px; }
}
html.modern-ui .mpr-foot form#GetReport > span { padding: 0 !important; margin: 0 !important; }
html.modern-ui .mpr-foot form#GetReport b {
    font: 600 11px var(--cmw-font);
    color: var(--cmw-text-muted);
    text-transform: uppercase;
}
html.modern-ui .mpr-foot select#r_format {
    border: 1px solid var(--cmw-border-input) !important;
    border-radius: var(--cmw-radius-pill) !important;
    padding: 7px 12px !important;
    font: 600 12px var(--cmw-font) !important;
    color: var(--cmw-ink) !important;
    background: var(--cmw-surface) !important;
}
html.modern-ui .mpr-foot input[name="r_submit"] { display: none !important; }
html.modern-ui .mpr-foot br,
html.modern-ui .mpr-foot #formatMsgElem { display: none !important; }

/* Get report submitted → full-screen quiet loader until the output
   page takes over (same look as the task manager's loader) */
html.modern-ui .mp-rptload {
    position: fixed;
    inset: 0;
    z-index: 4000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    background: rgba(255, 255, 255, 0.8);
}
html.modern-ui .mp-rptload-spin {
    width: 26px;
    height: 26px;
    border: 2px solid var(--cmw-border);
    border-top-color: var(--cmw-primary-dark);
    border-radius: 50%;
    animation: mpRptSpin 0.7s linear infinite;
}
html.modern-ui .mp-rptload-txt {
    font: 500 13px var(--cmw-font);
    color: var(--cmw-text-muted);
}
@keyframes mpRptSpin {
    to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
    html.modern-ui .mp-rptload-spin { animation-duration: 2s; }
}

html.modern-ui .mpr-chips { margin: 12px 0; flex-wrap: wrap; }
html.modern-ui .mpr-lab {
    display: block;
    font: 600 11px var(--cmw-font);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--cmw-text-muted);
    margin: 14px 0 5px;
}
html.modern-ui .mpr-setup form { margin: 0 !important; }
html.modern-ui .mpr-setup form#TimeZoneSelect select {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--cmw-border-input) !important;
    border-radius: var(--cmw-radius-field) !important;
    padding: 8px 12px !important;
    font: 13px var(--cmw-font) !important;
    color: var(--cmw-ink) !important;
    background: var(--cmw-surface) !important;
}
html.modern-ui .mpr-mobilebar { display: none; }

@media (max-width: 1100px) {
    html.modern-ui .mpr { grid-template-columns: 1fr; }
}
@media (max-width: 767px) {
    html.modern-ui .mpr-mobilebar {
        display: flex;
        align-items: center;
        /* below about 340px the button leaves the range 56px to live in
           and the longest piece of it is 75px, so it would be cut again.
           Let the button drop to its own line instead: the range keeps
           the full width and stays whole. */
        flex-wrap: wrap;
        gap: 10px;
        background: var(--cmw-surface);
        border: 1px solid var(--cmw-border);
        border-radius: var(--cmw-radius);
        padding: 10px 12px;
        margin-bottom: 12px;
    }
    /* The range wraps, it does not truncate. modern-patches puts each
       part of this line in its own span, so the break can only land
       BETWEEN them: the "to" date moves to the second line whole. A
       date cut off halfway says nothing at all (user, 2026-08-16). */
    html.modern-ui .mpr-mobilebar .txt {
        font: 10.5px var(--cmw-font-mono);
        color: var(--cmw-text-muted);
        flex: 1 1 0%;
        /* wide enough for the longest single piece, which is the "to"
           date with its arrow. Below that the bar wraps rather than
           cutting it. */
        min-width: 84px;
        white-space: normal;
        overflow-wrap: normal;
        line-height: 1.4;
    }
    html.modern-ui .mpr-mobilebar .txt > span { white-space: nowrap; }
    html.modern-ui .mpr-setup { display: none; }
    html.modern-ui .mpr-setup.open { display: block; }
    html.modern-ui .mpr-sum { display: none; }
    html.modern-ui .mpr-foot .mpe-btn.primary { flex: 1; min-height: 46px !important; border-radius: 23px !important; }
}

/* legacy bold labels inside the setup card → small uppercase labels */
html.modern-ui .mpr-setup b {
    display: block;
    font: 600 11px var(--cmw-font) !important;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--cmw-text-muted) !important;
    margin: 12px 0 5px;
}
html.modern-ui .mpr-setup form#DeviceGroup b { margin-top: 0; }
/* stray section heading at the top of the report form */
html.modern-ui .mpr-list form > b,
html.modern-ui .mpr-list form > span[style*="font-weight"] {
    display: none !important;
}
/* chooser select inside the setup card */
html.modern-ui .mpr-setup form#DeviceGroup select,
html.modern-ui .mpr-setup form#DeviceGroup input[type="text"] {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--cmw-border-input) !important;
    border-radius: var(--cmw-radius-field) !important;
    padding: 8px 12px !important;
    font: 600 13px var(--cmw-font) !important;
    color: var(--cmw-ink) !important;
    background: var(--cmw-surface) !important;
}

/* the legacy contentCell carries nowrap — reset it so prose blocks
   (info note, helpers, descriptions) wrap inside their boxes */
html.modern-ui .mpa,
html.modern-ui .mpr,
html.modern-ui .mpw-head,
html.modern-ui .mpw-card,
html.modern-ui .mpw-create,
html.modern-ui .mpb-head,
html.modern-ui .mpb-scroll {
    white-space: normal;
}
html.modern-ui .mpa-note,
html.modern-ui .mpe-help,
html.modern-ui .mpr-desc {
    white-space: normal !important;
    overflow-wrap: break-word;
    box-sizing: border-box;
    max-width: 100%;
}

/* ============================================================
   5a — GEOZONE EDITOR (zone.info edit)
   Full-bleed map + fixed right settings panel; mobile = sheet.
   ============================================================ */
html.modern-ui body.mpz-on { overflow: hidden; }
@media (max-width: 1200px) {
    html.modern-ui body.mpz-on { overflow: auto; }
}
html.modern-ui .mpz-panel {
    position: fixed;
    top: 57px;
    right: 0;
    bottom: 0;
    width: 360px;
    background: var(--cmw-surface);
    border-left: 1px solid var(--cmw-border);
    display: flex;
    flex-direction: column;
    font-family: var(--cmw-font);
    white-space: normal;
    z-index: 900;
}
html.modern-ui .mpz-phead {
    padding: 16px 20px;
    border-bottom: 1px solid var(--cmw-divider);
    display: flex;
    align-items: baseline;
    gap: 10px;
}
html.modern-ui .mpz-phead b {
    font: 600 15px var(--cmw-font-display);
    color: var(--cmw-ink);
}
html.modern-ui .mpz-phead i {
    font: 11px var(--cmw-font-mono);
    font-style: normal;
    color: var(--cmw-text-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
html.modern-ui .mpz-pbody {
    flex: 1;
    overflow-y: auto;
    padding: 16px 20px;
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-height: 0;
}
html.modern-ui .mpz-sec {
    font: 600 11px var(--cmw-font);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--cmw-text-faint);
    border-top: 1px solid var(--cmw-divider);
    padding-top: 14px;
    margin-top: 4px;
}
html.modern-ui .mpz-colorwrap {
    position: relative;
    display: block;
}
html.modern-ui .mpz-dot {
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 1px solid transparent;
    pointer-events: none;
    z-index: 2;
}
html.modern-ui .mpz-colorwrap select {
    padding-left: 32px !important;
    width: 100% !important;
}
html.modern-ui .mpz-pfoot {
    display: flex;
    align-items: center;
    gap: 10px;
    border-top: 1px solid var(--cmw-divider);
    padding: 12px 20px;
}
html.modern-ui .mpz-pfoot .state {
    font-size: 12px;
    color: var(--cmw-text-faint);
}
/* a read-only account is told why the footer is empty, rather than
   being left to wonder where Save went */
html.modern-ui .mpz-pfoot .state.ro { color: var(--cmw-text-muted); }
html.modern-ui .mpz-pfoot .sp { flex: 1; }
/* Delete drops to its own row under Cancel/Save. The panel is 360px and
   state + Delete + Cancel + Save on one line overflows it (measured 375
   into 359), and a destructive button should not sit shoulder to
   shoulder with the confirming one anyway. order puts it last; the
   100% basis is what forces the wrap. */
html.modern-ui .mpz-pfoot { flex-wrap: wrap; }
html.modern-ui .mpz-pfoot .mpz-del {
    order: 9;
    flex: 1 1 100%;
    margin-right: 0;
}

/* notes & instructions — the SERVER's own text, parsed out of
   div.zoneInstructions (it differs per zone shape, so it is never
   re-typed here). Collapsible; the state is remembered. */
html.modern-ui .mpz-noteshead {
    appearance: none;
    width: 100%;
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    text-align: left;
    background: none !important;
    border: none !important;
    border-top: 1px solid var(--cmw-divider) !important;
    border-radius: 0 !important;
    padding: 13px 0 0 !important;
    margin-top: 2px;
    min-height: 0 !important;
    box-shadow: none !important;
    transform: none !important;
    font: 600 11px var(--cmw-font) !important;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--cmw-text-faint) !important;
}
html.modern-ui .mpz-noteshead:hover { color: var(--cmw-text-muted) !important; }
html.modern-ui .mpz-noteshead i {
    margin-left: auto;
    font-style: normal;
    font-size: 13px;
    line-height: 1;
    transition: transform 0.15s ease;
}
html.modern-ui .mpz-noteshead:not(.open) i { transform: rotate(180deg); }
html.modern-ui .mpz-notes {
    list-style: none;
    margin: 0;
    padding: 11px 13px;
    background: var(--cmw-surface-soft);
    border: 1px solid var(--cmw-divider);
    border-radius: 10px;
}
html.modern-ui .mpz-notes li {
    position: relative;
    padding-left: 14px;
    margin-bottom: 6px;
    font: 12.4px/1.5 var(--cmw-font);
    color: var(--cmw-text);
}
html.modern-ui .mpz-notes li:last-child { margin-bottom: 0; }
html.modern-ui .mpz-notes li::before {
    content: "";
    position: absolute;
    left: 2px;
    top: 8px;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--cmw-primary);
}

/* map HUD — search + live readouts in ONE card, bottom-left */
html.modern-ui .mpz-hud {
    position: fixed;
    left: 14px;
    bottom: 14px;
    width: 336px;
    max-width: calc(100vw - 28px);
    background: var(--cmw-surface);
    border: 1px solid var(--cmw-border);
    border-radius: var(--cmw-radius);
    box-shadow: 0 4px 16px rgba(22, 32, 43, 0.14);
    overflow: hidden;
    font-family: var(--cmw-font);
    /* the decorated shell inherits white-space:nowrap into the content
       cell — without this the caption is clipped instead of wrapping */
    white-space: normal;
    z-index: 910;
}
html.modern-ui .mpz-hsearch {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 10px 9px 13px;
}
html.modern-ui .mpz-hsearch > svg {
    width: 15px;
    height: 15px;
    flex: none;
    color: var(--cmw-text-faint);
}
html.modern-ui .mpz-hsearch input[type="text"] {
    flex: 1;
    min-width: 0;
    width: auto !important;
    border: none !important;
    background: transparent !important;
    font: 13.5px var(--cmw-font) !important;
    color: var(--cmw-ink) !important;
    padding: 6px 0 !important;
    height: auto !important;
    min-height: 0 !important;
    outline: none !important;
    box-shadow: none !important;
}
html.modern-ui .mpz-hsearch input[type="button"] {
    flex: none;
    background: var(--cmw-btn-bg) !important;
    border: none !important;
    border-radius: var(--cmw-radius-pill) !important;
    color: #fff !important;
    font: 600 12.5px var(--cmw-font) !important;
    padding: 7px 15px !important;
    min-height: 0 !important;
    box-shadow: none !important;
    transform: none !important;
    cursor: pointer;
}
html.modern-ui .mpz-hsearch input[type="button"]:hover {
    background: var(--cmw-btn-bg-hover) !important;
}
html.modern-ui .mpz-hread {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 14px;
    padding: 10px 13px 9px;
    background: var(--cmw-surface-row);
    border-top: 1px solid var(--cmw-divider);
}
html.modern-ui .mpz-stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
html.modern-ui .mpz-stat:first-child { flex: 1; }
html.modern-ui .mpz-stat .k {
    font: 600 10.5px var(--cmw-font);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--cmw-text-faint);
}
html.modern-ui .mpz-stat .v {
    font: 500 15px var(--cmw-font-mono);
    color: var(--cmw-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
html.modern-ui .mpz-hnote {
    flex-basis: 100%;
    margin-top: 7px;
    padding-top: 7px;
    border-top: 1px solid var(--cmw-divider);
    font: 11.5px var(--cmw-font);
    color: var(--cmw-text-faint);
}
html.modern-ui .mpz-hnote b {
    font-weight: 600;
    color: var(--cmw-text-muted);
}

/* mobile / stacked (<1201 = app map breakpoint): panel becomes a sheet */
@media (max-width: 1200px) {
    /* The map-shell rules in modern-overrides catch the editor's OUTER
       content table too, because it also contains td.mapProviderCell:
       `table:has(td.mapProviderCell) { display: block !important }`
       (for map stacking) plus `td > table:... { height: 100% }`. As a
       display:block box pinned to 100% the whole sheet wedges at
       viewport height — the notes and the toggles' tail simply do not
       exist on the page (user: notes cut, 2026-08-15). Restore real
       table layout and auto height on the editor ONLY — dock map pages
       need those rules and keep them, hence the body.mpz-on scope. The
       INNER map table stays block so map-over-form stacking holds. */
    html.modern-ui body.mpz-on table.contentTable {
        display: table !important;
        height: auto !important;
    }
    html.modern-ui body.mpz-on td > table:has(td.mapProviderCell) {
        height: auto !important;
    }
    /* The outermost shell table is hit by the same display:block rule,
       and as a block box it wedges at viewport height no matter what
       its height says (verified live: only display:table releases it —
       846 → 1253). :has scopes this to the one shell table the block
       rule caught; the first, hidden wrapper table stays untouched.
       The inner two-column map table is NOT restored — it must stay
       block so the map stacks above the form. */
    html.modern-ui body.mpz-on .responsive-wrapper > table:has(td.mapProviderCell) {
        display: table !important;
        height: auto !important;
    }
    /* ONE scroll, the page's. The wrapper's global overflow-x:hidden
       silently computes overflow-y to auto, making it a second scroll
       container nested inside the now-scrolling page. clip contains
       sideways spill without creating a scroller. */
    html.modern-ui body.mpz-on .responsive-wrapper {
        overflow-x: clip !important;
        overflow-y: visible !important;
    }
    html.modern-ui .mpz-panel {
        position: static;
        width: auto;
        border-left: none;
        border-radius: 18px 18px 0 0;
        box-shadow: 0 -4px 16px rgba(22, 32, 43, 0.08);
        margin-top: -14px;
        position: relative;
        z-index: 20;
    }
    html.modern-ui .mpz-phead::before {
        content: "";
        position: absolute;
        top: 7px;
        left: 50%;
        transform: translateX(-50%);
        width: 42px;
        height: 4px;
        border-radius: 2px;
        background: var(--cmw-border-strong);
    }
    html.modern-ui .mpz-pbody { overflow: visible; }
    /* nothing floats over a stacked map — the HUD is moved to the top
       of the sheet by placeHud() and flattens into the flow here */
    html.modern-ui .mpz-hud.inline {
        position: static;
        width: auto;
        max-width: none;
        box-shadow: none;
        border-color: var(--cmw-border-input);
    }
    html.modern-ui .mpz-hud.inline .mpz-hsearch { padding: 8px 8px 8px 12px; }
    html.modern-ui .mpz-hud.inline .mpz-hsearch input[type="text"] {
        font-size: 16px !important;   /* < 16px zooms the page on iOS */
    }
    html.modern-ui .mpz-hud.inline .mpz-hsearch input[type="button"] {
        padding: 9px 17px !important;
    }
    html.modern-ui .mpz-pfoot {
        position: sticky;
        bottom: 0;
        background: var(--cmw-surface);
    }
    html.modern-ui .mpz-pfoot .state { display: none; }
    /* the dirty counter is droppable here; "why is there no Save" is not */
    html.modern-ui .mpz-pfoot .state.ro { display: block; flex: 1 1 100%; }
    html.modern-ui .mpz-pfoot .mpe-btn.ghost { flex: 1; }
    html.modern-ui .mpz-pfoot .mpe-btn.primary { flex: 2; min-height: 46px !important; border-radius: 23px !important; }
}
/* ≤480, modern-overrides clips the content table's rows/cells on both
   axes to stop sideways spill; on the flowing editor sheet that
   guillotines the bottom of the form. Keep x contained, free y. */
@media (max-width: 480px) {
    html.modern-ui body.mpz-on table.contentTable > tbody > tr,
    html.modern-ui body.mpz-on table.contentTable > tbody > tr > td {
        overflow-x: clip !important;
        overflow-y: visible !important;
    }
}

/* ============================================================
   6a — BULK GRIDS (dev.info, user.info, group.info)
   ============================================================
   These three pages were rebuilt server-side in Aug 2026 as wide
   inline-edit tables. The markup, the submit buttons, the sort
   handlers and the <dialog> pickers are all the server's — this
   section only re-skins them, so every selector here targets a
   class the server emits (bulk-*, u-/d-/g-bulk-select, g-member-*)
   or the four hooks modern-patches adds (.mpb-head, .mpb-scroll,
   .mpb-table, .mpb-foot).

   The server also ships an inline <style> block per page, scoped by
   #userBulkTable / #deviceBulkTable / #groupBulkTable — an ID there
   beats a class here, so anything contested is re-asserted with
   !important and the reason noted at the site.

   Every selector below carries :not(:has(#jsmap)). That is not
   decoration: Custom.css styles the admin control family through
   `body:not(:has(#jsmap)) td.contentCell input[type=submit]`, and
   :has(#jsmap) gives that rule ID-level specificity, so a plain
   class chain here loses to it however many !importants it carries.
   Matching the guard buys back the weight — and reads true, since
   none of these three pages has a map.
   ============================================================ */

/* How wide the page runs. Every part of the grid — head, search row,
   card, foot — is measured from this one token so they can never drift
   apart. These three pages carry 6, 16 and 20 columns with nothing
   hidden any more, so they get more room than the 1160px reading measure
   the rest of the admin section uses (user request, 2026-08-15): below
   ~1650px the grid runs the full viewport less a 24px gutter, and past
   that it stops at 1600 rather than becoming an unreadable band on a
   very wide monitor. It is still the CARD that scrolls, never the page. */
html.modern-ui body.mpb-on {
    /* no cap any more: "there is just so much wasted space on the left
       and right" (user, 2026-08-19) — the grid runs the viewport less a
       16px gutter each side, whatever the monitor */
    --mpb-max: calc(100vw - 32px);
    --mpb-w: calc(100vw - 32px);
}

/* The content column is centred on a 1160px measure by Custom.css:173,
   and it does that through this cell's own left/right padding. So a card
   wider than 1160 grew the legacy layout table past the viewport, and
   .responsive-wrapper (overflow-x: hidden) clipped the overflow rather
   than scrolling it — the right-hand columns simply became unreachable.
   The gutter therefore has to be computed from the SAME measure the card
   is. Both formulas floor at 24px below ~1650px, so nothing moves on a
   phone or a tablet; only the wide desktop column opens up.

   The guard is copied from Custom.css on purpose: :has(#jsmap) scores as
   an ID, so a plain class chain loses to that rule however many
   !importants it carries. Matching the guard and adding .mpb-on is what
   buys back the weight. */
html.modern-ui body.modern-shell.mpb-on:not(:has(#jsmap)):not(:has(table[height="90%"])) td.contentCell {
    /* the same 16px the width token leaves — a wider floor here would
       clip the card's right edge against the wrapper */
    padding-left: 16px !important;
    padding-right: 16px !important;
    /* the crumb bar already names the page, so 36px of air under it was
       only distance (user, 2026-08-16); 12px now — "wasted space...
       up and down" (user, 2026-08-19). */
    padding-top: 12px !important;
}

/* the title and subtitle are usually hidden here — the crumb bar
   adopts them (buildPageBar marks them .mp-pb-dupe) — which leaves the
   head as the caption row: note on the left, page controls on the right */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px 16px;
    width: var(--mpb-w);
    max-width: var(--mpb-w);
    margin: 0 auto 14px;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-head .mpw-title,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-head .mpw-sub {
    flex: 1 1 100%;
}
/* it rides the tools row now, after the record count. NOT margin-left:
   auto — the count already claims the free space with it, and two auto
   margins split that space between them, which left the count stranded
   in the middle of the row instead of beside the button. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-refresh {
    margin-left: 0;
    padding: 6px 14px;
    border: 1px solid var(--cmw-border-input);
    border-radius: var(--cmw-radius-field);
    background: var(--cmw-surface);
    font: 600 12.5px var(--cmw-font);
    color: var(--cmw-text-muted);
    text-decoration: none;
    white-space: nowrap;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-refresh:hover {
    color: var(--cmw-ink);
    border-color: var(--cmw-border-strong);
    background: var(--cmw-surface-alt);
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-head .mpw-title {
    margin: 0;
    font: 700 25px/1.2 var(--cmw-font-display);
    letter-spacing: -0.2px;
    color: var(--cmw-ink);
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-head .mpw-sub {
    display: block;
    margin-top: 3px;
    font: 14.5px var(--cmw-font);
    color: var(--cmw-text-muted);
}
/* the server's "* Required when creating a new row." carries
   margin:4px 25px 8px, which indents it away from the title */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-head .bulk-required-note {
    margin: 9px 0 0 !important;
    font: 12.5px var(--cmw-font);
    color: var(--cmw-text-muted);
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-required-marker {
    color: var(--cmw-danger);
    font: 700 13px var(--cmw-font);
}

/* ── the grid card ─────────────────────────────────────────── */
/* Server: width/max-width calc(100vw - 70px), margin-left 25px,
   overflow-x scroll !important — measured against the viewport rather
   than this page's content column, so the grid hung off the right edge
   inside our shell.

   The cap has to be viewport-based, not a percentage: these tables
   carry min-width:1900px, and a percentage resolves against a legacy
   layout-table chain that is itself shrink-to-fit, so the table grows
   the ancestors and the PAGE pans sideways instead of the card. Same
   formula as div[data-admin-scroll] in Custom.css, for the same
   reason and so both admin shapes line up. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-scroll {
    width: var(--mpb-w) !important;
    max-width: var(--mpb-w) !important;
    margin: 0 auto !important;
    padding-bottom: 0 !important;
    overflow-x: auto !important;
    overscroll-behavior-x: contain;
    background: var(--cmw-surface);
    border: 1px solid var(--cmw-border);
    border-radius: var(--cmw-radius-lg);
    box-shadow: var(--cmw-shadow-sm);
}
/* ── search ────────────────────────────────────────────────── */
/* The pill itself is .mpw-search, shared with the geozone list — same
   control, same icon, same focus ring. This row is only what carries it,
   with the live match count opposite. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-tools {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    width: var(--mpb-w);
    max-width: var(--mpb-w);
    margin: 0 auto 11px;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-count {
    margin-left: auto;
    font: 11.5px var(--cmw-font-mono);
    color: var(--cmw-text-faint);
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-empty {
    display: none;
    padding: 34px 16px;
    text-align: center;
    font: 13px var(--cmw-font);
    color: var(--cmw-text-faint);
}

/* On user.info and group.info the server's .bulk-table-scroll wraps the
   whole form — table AND the Add/Delete Row bar — so the card is drawn
   one level in (.mpb-scroll on modern-patches' own wrapper) and this
   outer div stands down to a plain block. Its server rule carries
   overflow-x:scroll!important and a viewport-width of its own, both of
   which would otherwise still be in force around the foot. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-plain {
    width: var(--mpb-w) !important;
    max-width: var(--mpb-w) !important;
    margin: 0 auto !important;
    padding-bottom: 0 !important;
    overflow: visible !important;
    background: none;
    border: 0;
    /* zone.info's #zoneBulkScroll is drawn as a card in its own right —
       rounded, with a drop shadow — and the search row is inserted
       INSIDE it, so that shadow read as a stray outline wrapping the
       search bar and the grid together, on the one page that has it
       (user, 2026-08-16). A demoted wrapper draws nothing at all. */
    box-shadow: none !important;
    border-radius: 0 !important;
}
/* a loose form carrying only hidden inputs: it must stay in the
   document, it must not be drawn. display:block rather than the
   inline-block it is served as, so an empty inline box cannot open a
   line of its own the way the stray newlines above the head did. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-bare {
    display: block !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    background: none !important;
    box-shadow: none !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table {
    /* natural width, not max-content and not 100%: the server pins
       width:max-content, and with single-line cells that stretched
       group admin's five columns across 1886px - one of them 614px
       wide (user: "unnecessary wide", 2026-08-19). Auto layout plus
       the max-width on the cells below sizes each column to what it
       actually holds. */
    width: auto !important;
    min-width: 0 !important;
    border-collapse: separate !important;
    border-spacing: 0;
    font-family: var(--cmw-font);
    background: transparent;
}

/* ── header row ────────────────────────────────────────────── */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table thead th {
    position: sticky;
    top: 0;
    z-index: 4;
    background: var(--cmw-surface-alt);
    border-bottom: 1px solid var(--cmw-border);
    padding: 11px 12px !important;
    font: 600 11px var(--cmw-font-mono);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--cmw-text-muted);
    text-align: left;
    vertical-align: middle;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table thead th.bulk-sort-enabled:hover {
    color: var(--cmw-ink);
    background: var(--cmw-surface-soft);
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table thead th:focus-visible {
    outline: 2px solid var(--cmw-primary-dark);
    outline-offset: -2px;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-sort-indicator {
    color: var(--cmw-primary-dark);
    font-size: 9px !important;
}
/* The two frozen columns sit above the rest of the header row — and
   must freeze WITH it. The server pins the body cells of these two
   columns but leaves their header cells static, from an inline
   #<id> thead th block whose id beats any class chain here, so without
   !important the headings scroll away while the cells they name stay
   put and the grid reads one column out of step. The left offsets are
   the server's own, already declared on these cells; only the position
   is being put back. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table thead th:first-child,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table thead th.bulk-id-col {
    position: sticky !important;
    z-index: 6;
    background: var(--cmw-surface-alt);
}

/* ── the pinned header (v3.114.0) ──────────────────────────────
   The build-once copy of the header row that buildBulkGrid pins to
   the top of a desktop viewport while the real one is scrolled away.
   position:fixed on purpose — the one placement the compositor keeps
   put with no script running per frame, which is what "sticky like
   the legacy ui" means on a low-end machine too. It scrolls only
   sideways, in step with the card, and never below 900px where the
   header row itself does not exist. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-fixhead {
    display: none;
    position: fixed;
    top: 0;
    z-index: 40;
    overflow: hidden;
    background: var(--cmw-surface-alt);
    box-shadow: 0 2px 6px rgba(22, 32, 43, 0.10);
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-fixhead.on {
    display: block;
}
@media (max-width: 899px) {
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-fixhead {
        display: none !important;
    }
}

/* ── body rows ─────────────────────────────────────────────── */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody td {
    padding: 10px 12px !important;
    border-bottom: 1px solid var(--cmw-divider);
    font: 13px/1.45 var(--cmw-font);
    color: var(--cmw-text);
    vertical-align: middle;
    background: var(--cmw-surface);
}
/* Space-based wrapping, words kept whole (user, 2026-08-19): the cap
   is what makes a long description fold instead of pricing its whole
   sentence into the column width; ids, dates and coordinates carry
   no spaces and stay on one line. The server breaks view-values
   mid-word (word-break:break-word) - same policy override there.
   DESKTOP ONLY: below 900px the cards depend on overflow-wrap:
   anywhere for values that have no spaces at all, or the card pans
   sideways at 320 again. */
@media (min-width: 900px) {
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody td {
        white-space: normal !important;
        overflow-wrap: normal !important;
        word-break: normal !important;
        max-width: 380px;
        /* tighter rows on desktop (user, 2026-08-20: "more data in a
           single view", then "reduce a bit more") — vertical padding
           only, 10px → 5px, so a 38px view row becomes ~30px. The
           font is untouched. Wins over the base rule above by coming
           later at equal weight. The phone cards below 900px keep
           their approved spacing untouched. */
        padding-top: 4px !important;
        padding-bottom: 4px !important;
    }
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table thead th {
        padding-top: 5px !important;
        padding-bottom: 5px !important;
    }
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table .bulk-view-value {
        overflow-wrap: normal !important;
        word-break: normal !important;
    }
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody tr:last-child td { border-bottom: none; }
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody tr:hover td { background: var(--cmw-surface-alt); }

/* ── row stripes, every width (v3.115.0) ─────────────────────
   Alternating rows are the agreed design ("WE DECIDED TO KEEP
   ALTERNATING EVERYWHERE", user, 2026-08-19) and on a desktop they
   had quietly died in a three-way cascade fight: the cells are
   forced transparent by Custom.css, and the row colour underneath
   was being decided by a late modern-overrides rule that paints
   Even rows white again — leaving, at best, a 2% tint that reads as
   nothing on an ordinary panel. Restated here on the TR (the cells
   stay transparent), striped by POSITION among the saved rows like
   the phone cards, so a sort never breaks the pattern, with the
   draft row outside it. The :has() in the guard carries ID weight,
   which is what outranks every !important white above. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table
    tbody > tr:nth-child(even of .adminTableBodyRowOdd, .adminTableBodyRowEven) {
    background: var(--cmw-stripe) !important;
}
/* the pointer must still visibly land on a striped row */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table
    tbody > tr:nth-child(even of .adminTableBodyRowOdd, .adminTableBodyRowEven):hover {
    background: var(--cmw-surface-alt) !important;
}
/* on hover the frozen pair must take the row tint with everyone else
   (the v3.106 contract e7 guards) - stated before the stripe rule so
   the stripe does not outrank it by order */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table
    tbody > tr:nth-child(even of .adminTableBodyRowOdd, .adminTableBodyRowEven):hover > td:first-child,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table
    tbody > tr:nth-child(even of .adminTableBodyRowOdd, .adminTableBodyRowEven):hover > td.bulk-id-col {
    background: var(--cmw-surface-alt) !important;
}
/* the frozen pair paints its own opaque surface (it slides over the
   grid), so striped rows re-tint those two cells to match */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table
    tbody > tr:nth-child(even of .adminTableBodyRowOdd, .adminTableBodyRowEven) > td:first-child,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table
    tbody > tr:nth-child(even of .adminTableBodyRowOdd, .adminTableBodyRowEven) > td.bulk-id-col {
    background: var(--cmw-stripe) !important;
}
/* The ID column reads as the row's identity, and it also carries the
   row's Edit button — but this cell is NOT the flex line that pairs
   them. The server already ships one:
       td.bulk-id-col > .bulk-id-action-wrap
                          > .bulk-id-value + .bulk-id-actions
   and that is what flexes below.

   `display:flex` on a <td> is never a table cell. The browser wraps it
   in an ANONYMOUS table cell, and that anonymous box — not this one —
   becomes the column. The td's own box then keeps its intrinsic size,
   so its background, its border-right and its height all describe a box
   that is not the column it is supposed to be. Measured 2026-08-16:
   dev.info's id cell came out 23.7px shorter than its own row, and that
   gap is the strip of untinted row that showed under the frozen column
   on hover; and group.info at 1707 and 1957 wide drew this cell's
   border-right — the column divider — 20.3px to the LEFT of the header's,
   because the flex box stayed at its content width while the column
   stretched. One defect, both symptoms. The cell is a cell. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody td.bulk-id-col {
    display: table-cell !important;
    font-family: var(--cmw-font-mono);
    font-size: 12.5px;
    color: var(--cmw-ink);
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table .bulk-id-action-wrap {
    display: flex !important;
    align-items: center;
    justify-content: flex-start !important;
    gap: 10px;
    min-width: 0;
}
/* The id gets a slot rather than all the slack, and Edit follows it.
   The server's own rule holds these two apart with space-between, which
   in a column the table had stretched to 300px on user.info and 450px
   on group.info left Edit stranded 237px and 458px from the id it acts
   on (user, 2026-08-16). A slot also lines every Edit up at one x
   whatever width the column is given, which space-between cannot do —
   under it each button sits wherever its own id ends. 150px is wider
   than the longest id any of the four grids serves. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table .bulk-id-value {
    flex: 1 1 auto;
    min-width: 0;
    max-width: 150px;
    overflow-wrap: anywhere;
    /* zone.info sets nowrap on this span from its id-scoped inline
       block, and nowrap silently beats overflow-wrap — a 32-character
       geozone id ran 100px past its slot and straight under the Edit
       button (user, 2026-08-16). Same trap as the stacked labels. */
    white-space: normal !important;
}
/* a row open for editing puts a field where the id was; it needs the
   width back */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table
    .bulk-id-action-wrap:has(input:not([type="hidden"]), select) .bulk-id-value {
    max-width: none;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table .bulk-id-actions {
    flex: 0 0 auto;
}

/* FROZEN COLUMNS. The server pins the select box and the id column with
   position:sticky but paints neither, and a pinned column with no
   background is not pinned at all — the rest of the grid slides under
   it and the two texts render on top of each other. That is the whole
   of "scrolling horizontally overlaps with the text" (user, 2026-08-16).
   They are given the row's own surface here, the hover tint with it so
   the row still reads as one thing, and an edge that says where the
   frozen part stops.

   Stated for .bulk-id-col, NOT for a position: the server merged what
   used to be a separate .bulk-action-col into this cell, which shifted
   every column index by one and is why the old rules stopped landing.
   A class the markup actually carries cannot drift that way. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody td:first-child,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody td.bulk-id-col {
    background: var(--cmw-surface) !important;
}
/* the row's OWN hover colour, not a paler one of our own: the rest of
   the row is tinted --cmw-surface-alt by the shell's tr:hover, so a
   frozen pair painted --cmw-surface-row read as a white block sitting
   in a blue row — the split visible down the left of every hovered row
   (user, 2026-08-16) */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody tr:hover td:first-child,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody tr:hover td.bulk-id-col {
    background: var(--cmw-surface-alt) !important;
}
/* A new row is flagged yellow by the server; the frozen pair must not
   stay white through it or the card looks half-highlighted. `tbody` is
   carried here so this outranks nothing but matches the hover rule
   above element-for-element and therefore wins on source order: without
   it the hover rule scored one element higher, so putting the pointer
   on an unsaved row turned its frozen columns blue while the rest of it
   stayed yellow — the row stopped reading as unsaved exactly while you
   were pointing at it. Hover does not get to erase that flag. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody tr[data-new="1"] td:first-child,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody tr[data-new="1"] td.bulk-id-col,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody tr[data-new="1"]:hover td:first-child,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody tr[data-new="1"]:hover td.bulk-id-col {
    background: var(--cmw-warning-bg) !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table th.bulk-id-col,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table td.bulk-id-col {
    border-right: 1px solid var(--cmw-border);
}
/* the lift that tells you the pair is holding still while the rest
   moves. Drawn only once the grid is actually scrolled aside, so a grid
   that fits its card carries no stray seam. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-scroll.mpb-panned .mpb-table th.bulk-id-col,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-scroll.mpb-panned .mpb-table td.bulk-id-col {
    box-shadow: 6px 0 8px -7px rgba(15, 34, 51, 0.35);
}
/* a row being added: the server flags it #fff4c8 !important */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tr[data-new="1"] td {
    background: var(--cmw-warning-bg) !important;
}

/* ── controls inside the grid ──────────────────────────────── */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table input[type="checkbox"] {
    width: 15px !important;
    height: 15px !important;
    min-height: 0 !important;
    accent-color: var(--cmw-primary-dark);
    cursor: pointer;
    margin: 0;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table input[type="text"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table input[type="password"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table input:not([type]),
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table select,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table textarea {
    box-sizing: border-box;
    width: 100% !important;
    min-width: 120px;
    height: var(--cmw-control-h-sm) !important;
    min-height: 0 !important;
    padding: 0 10px !important;
    border: 1px solid var(--cmw-border-input) !important;
    border-radius: var(--cmw-radius-field) !important;
    background: var(--cmw-surface) !important;
    font: 13px var(--cmw-font) !important;
    color: var(--cmw-ink) !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table textarea {
    height: auto !important;
    padding: 7px 10px !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table input:focus,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table select:focus,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table textarea:focus {
    border-color: var(--cmw-primary-dark) !important;
    box-shadow: var(--cmw-focus-ring);
    outline: none;
}

/* ── row buttons: Edit, then Update / Cancel ───────────────── */
/* Keyed on the GRID, not on .bulk-action-col — the server deleted that
   column and folded its Edit button into the id cell, which left every
   one of these rules matching nothing. What then painted Edit was the
   site-wide fallback
       button, input[type=button], input[type=submit], … {
           background: var(--primary) !important; color: #fff !important;
           border: 1px solid var(--border); border-radius: 6px }
   from the legacy palette: --primary there is #00BCD4, a Material cyan
   that belongs to no token in this design system, on a 6px radius, in
   whatever font the cell happened to set — the id column's monospace.
   Forty of them down one column (user, 2026-08-16). Each contested
   property is restated, and Edit takes the same quiet register as every
   other repeated row control, leaving Add Row the one filled button on
   the page. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody button,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody input[type="submit"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table input[type="button"] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 0 !important;
    padding: 6px 13px !important;
    border-radius: var(--cmw-radius-field) !important;
    border: 1px solid var(--cmw-border-input) !important;
    background: var(--cmw-surface) !important;
    color: var(--cmw-text-muted) !important;
    font: 600 12px var(--cmw-font) !important;
    letter-spacing: 0 !important;
    box-shadow: none !important;
    transform: none !important;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody button:hover,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody input[type="submit"]:hover,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table input[type="button"]:hover {
    color: var(--cmw-ink) !important;
    border-color: var(--cmw-border-strong) !important;
    background: var(--cmw-surface-alt) !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody button:focus-visible,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody input[type="submit"]:focus-visible {
    outline: 2px solid var(--cmw-primary-dark);
    outline-offset: 1px;
}
/* Edit is the one row control that is always there, on every row, and it
   opens the record — so it carries the page's blue rather than the
   neutral edge the situational controls (Select, Cancel) use. Outlined,
   not filled: Add Row stays the only solid button on the page. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody .bulk-id-actions button {
    border-color: var(--cmw-btn-bg) !important;
    color: var(--cmw-primary-dark) !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody .bulk-id-actions button:hover {
    border-color: var(--cmw-primary-dark) !important;
    color: var(--cmw-primary-dark) !important;
    background: var(--cmw-surface-alt) !important;
}
/* Update commits the row — the one filled control while a row is open.
   `tbody` is carried here deliberately: the rule above it now names
   tbody too, and without it this selector is one element-level short
   and loses to it even though both are !important. Update then came out
   the same white pill as Cancel. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody input[name$="subbulkupd"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody button[name$="subbulkupd"] {
    background: var(--cmw-btn-bg) !important;
    border-color: var(--cmw-btn-bg) !important;
    color: #fff !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody input[name$="subbulkupd"]:hover,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody button[name$="subbulkupd"]:hover {
    background: var(--cmw-btn-bg-hover) !important;
    border-color: var(--cmw-btn-bg-hover) !important;
    color: #fff !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody input[name$="subbulkcancel"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody button[name$="subbulkcancel"] {
    margin-left: 6px !important;
}

/* ── group / ACL pickers ───────────────────────────────────── */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-group-summary,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-summary {
    font: 13px var(--cmw-font);
    color: var(--cmw-text);
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-group-dialog,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-dialog {
    border: 1px solid var(--cmw-border) !important;
    border-radius: var(--cmw-radius-lg) !important;
    box-shadow: var(--cmw-shadow-drawer);
    padding: 18px !important;
    font-family: var(--cmw-font);
    color: var(--cmw-text);
    background: var(--cmw-surface);
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-group-dialog-title,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-dialog-title {
    font: 600 15px var(--cmw-font-display) !important;
    color: var(--cmw-ink);
    margin-bottom: 12px !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-group-dialog-list,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-dialog-list {
    border: 1px solid var(--cmw-border) !important;
    border-radius: var(--cmw-radius) !important;
    padding: 6px !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-group-option {
    padding: 7px 8px !important;
    border-radius: var(--cmw-radius-sm);
    font: 13px var(--cmw-font);
    cursor: pointer;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-group-option:hover { background: var(--cmw-surface-alt); }
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-table td { border-bottom-color: var(--cmw-divider) !important; }
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-label { font: 13px var(--cmw-font); color: var(--cmw-ink); }
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-default-note,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-group-dialog-help,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-dialog-help {
    color: var(--cmw-text-muted) !important;
    font: 11.5px var(--cmw-font) !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-group-dialog-actions input[type="button"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-group-dialog-actions input[type="submit"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-dialog-actions input[type="button"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-dialog-actions input[type="submit"] {
    min-height: 0 !important;
    padding: 8px 16px !important;
    border-radius: var(--cmw-radius-field) !important;
    border: 1px solid var(--cmw-border-input) !important;
    background: var(--cmw-surface) !important;
    color: var(--cmw-text-muted) !important;
    font: 600 13px var(--cmw-font) !important;
    box-shadow: none !important;
    transform: none !important;
    cursor: pointer;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-group-dialog-actions input:first-child,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-acl-dialog-actions input:first-child {
    background: var(--cmw-btn-bg) !important;
    border-color: var(--cmw-btn-bg) !important;
    color: #fff !important;
}

/* ── group.info: the members expander ──────────────────────── */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-member-list { cursor: pointer; }
html.modern-ui body.mpb-on:not(:has(#jsmap)) .g-member-summary {
    font: 600 12.5px var(--cmw-font);
    color: var(--cmw-primary-dark);
    white-space: nowrap;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .g-member-arrow { font-size: 9px; margin-left: 3px; }
html.modern-ui body.mpb-on:not(:has(#jsmap)) .g-member-detail {
    margin-top: 8px;
    padding: 8px 10px;
    border: 1px solid var(--cmw-border);
    border-radius: var(--cmw-radius);
    background: var(--cmw-surface-soft);
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .g-member-detail div {
    font: 12.5px/1.7 var(--cmw-font-mono);
    color: var(--cmw-text);
}

/* ── the action foot ───────────────────────────────────────── */
/* dev.info sets width/max-width calc(100vw - 70px) and margin-left
   25px inline on its bar; ours must sit in the content column */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot {
    display: flex !important;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    /* the buttons belong to the grid, so the bar matches its width -
       full-cell it left Delete Row stranded at the viewport edge of a
       page whose table is half that wide */
    width: var(--mpb-w) !important;
    max-width: var(--mpb-w) !important;
    margin: 14px auto 0 !important;
    box-sizing: border-box;
}
/* Custom.css:227 neutralises the margin of every legacy wrapper div that
   is a direct child of the content cell — `td.contentCell > div[style]`.
   zone.info's action bar IS such a div (the other three keep theirs
   inside the <form>), so it alone lost the gap above and sat flush
   against the grid (user, 2026-08-16). Restated here at a weight that
   clears that rule rather than weakening it for the rest of the site. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) td.contentCell > div.mpb-foot {
    margin: 14px auto 0 !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot > div {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}
/* Add Row builds, the rest destroy — so the two intents sit at opposite
   ends and a mis-click cannot be a slip of one button's width. dev.info
   already splits them with its own flex bar; user.info and group.info
   hand us both buttons in one div, so the destructive one is pushed
   over rather than moved in the DOM. The push itself is asserted AFTER
   the generic button rule below, which resets margin. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot > div:last-child:not(:first-child) {
    margin-left: auto;
    justify-content: flex-end;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot input[type="submit"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-floating-actions input[type="submit"] {
    min-height: 0 !important;
    margin: 0 !important;
    padding: 10px 18px !important;
    border-radius: var(--cmw-radius-field) !important;
    border: 1px solid var(--cmw-border-input) !important;
    background: var(--cmw-surface) !important;
    color: var(--cmw-text-muted) !important;
    font: 600 13px var(--cmw-font) !important;
    box-shadow: none !important;
    transform: none !important;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot input[type="submit"]:hover,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-floating-actions input[type="submit"]:hover {
    color: var(--cmw-ink) !important;
    border-color: var(--cmw-border-strong) !important;
    background: var(--cmw-surface-alt) !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot input[name$="subbulkadd"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-floating-actions input[name$="subbulkadd"] {
    background: var(--cmw-btn-bg) !important;
    border-color: var(--cmw-btn-bg) !important;
    color: #fff !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot input[name$="subbulkadd"]:hover,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-floating-actions input[name$="subbulkadd"]:hover {
    background: var(--cmw-btn-bg-hover) !important;
    border-color: var(--cmw-btn-bg-hover) !important;
}
/* the first destructive button carries the gap to the left edge; the
   ones after it sit beside it. Declared here, after the rule above that
   zeroes margin on every foot submit — order is what makes it stick. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot > input[name$="subbulkdel"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot > input[name$="close_task"] {
    margin-left: auto !important;
}
/* Delete Row is filled, not outlined. Outlined, it was the same white
   pill with red text as Close All Non Pending Tasks and Close All
   Pending Tasks sitting beside it on dev.info — three identical-looking
   buttons, one of which destroys records (user, 2026-08-16). Filling
   the only irreversible one is what tells them apart at a glance, and
   it is the same on all four grids so the position is learnable. */
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot input[name$="subbulkdel"],
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-floating-actions input[name$="subbulkdel"] {
    background: var(--cmw-danger) !important;
    border-color: var(--cmw-danger) !important;
    color: #fff !important;
}
html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot input[name$="subbulkdel"]:hover,
html.modern-ui body.mpb-on:not(:has(#jsmap)) .bulk-floating-actions input[name$="subbulkdel"]:hover {
    background: var(--cmw-danger-hover) !important;
    border-color: var(--cmw-danger-hover) !important;
    color: #fff !important;
}

/* ── narrow screens ────────────────────────────────────────── */
@media (max-width: 899px) {
    /* the shared .mpw-search rule already takes the pill full-width and
       gives it order:5, so the count is sent below it rather than left
       stranded above */
    /* the pill takes the whole first line, so the count wraps below it.
       flex-basis auto rather than 100% leaves room on that second line
       for Refresh, which otherwise had no width left and was pushed up
       to a stranded line of its own ABOVE the search pill. */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-count {
        order: 6;
        flex: 1 1 auto;
        margin-left: 0;
    }
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-refresh {
        order: 7;
        margin-left: auto;
    }

    /* STACKED RECORDS. Below 900px each row becomes a card instead of a
       table row and the grid stops moving sideways entirely — the same
       trade zone.info makes, and the reason a phone never has to pan a
       20-column table. Every column is present; the header row is what
       goes, and each cell prints its own label instead. */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack thead,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tr,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack td {
        display: block;
        width: auto;
    }
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack thead { display: none; }
    /* the server pins min-width:1900px, which is what a scrolling grid
       needs and what a stack of cards must not inherit — this is the one
       place it gets lifted */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack {
        min-width: 0 !important;
        width: 100% !important;
    }
    /* [data-row], not the Odd/Even stripe classes: the blank row the
       server appends for Add Row carries NO class at all, so keying on
       the stripe left it as a bare unlabelled pile of inputs on a phone
       — every other row a card, the one you were filling in not.
       [data-row] is on every real row including that draft, and no
       nested <dialog> row has it (verified: 64 dialog rows on user.info
       mid-edit, 0 with data-row), which is the whole reason the stripe
       classes were used here in the first place. */
    /* two tracks, not three: the row's Edit button lives INSIDE the id
       cell now, so the card's first line is [select][id … Edit] and a
       third track would leave a permanently empty column. */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody > tr[data-row] {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        gap: 5px 10px;
        align-items: center;
        padding: 11px 13px;
        border-bottom: 1px solid var(--cmw-divider);
    }
    /* Alternate cards carry a tint, so one record ends where the colour
       changes — the 1px divider alone is too little (user: hard to read,
       2026-08-15). Striped by POSITION among the saved rows, not by the
       server's Odd/Even classes, so the pattern holds however a sort
       reorders things; the unsaved draft row is deliberately outside the
       stripe, since the server tints it yellow as a new row. Browsers
       without `of` support simply keep the flat white list. !important
       because Custom.css:290 paints these rows white with !important and
       it sits after this file's @import. */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack
        tbody > tr:nth-child(even of .adminTableBodyRowOdd, .adminTableBodyRowEven) {
        background: var(--cmw-stripe) !important;
    }
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody td {
        padding: 0 !important;
        border: 0 !important;
        background: transparent !important;
    }
    /* The frozen columns have nothing left to freeze against, and their
       own rules carry the surface colour with !important — restate both
       here at matching specificity or the two cells keep painting
       themselves as white boxes over the card. Un-pinning the id column
       is the important half: left as sticky it floated over the card,
       which is what put the record id on top of the first field. */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody td:first-child,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody td.bulk-id-col,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody tr:hover td:first-child,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody tr:hover td.bulk-id-col,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tr[data-new="1"] td:first-child,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tr[data-new="1"] td.bulk-id-col {
        position: static !important;
        background: transparent !important;
        border: 0 !important;
        box-shadow: none !important;
    }
    /* Placement is stated as explicit grid areas, not left to auto-flow:
       the labelled columns sit between these two in the DOM, and any
       ambiguity drops the id onto its own line. */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody td:first-child {
        grid-area: 1 / 1 / 2 / 2;
    }
    /* the identity line: id on the left, its Edit button hard right.
       Keyed on the class the server gives the cell — the previous
       nth-child(3) meant "the third column", which after the server
       merged the action column into this one addressed the ACTIVE
       field instead. Two cells then claimed one grid area and printed
       over each other.

       The cell is a grid ITEM here and the server's .bulk-id-action-wrap
       inside it is what carries the two ends, same as on a desktop. It
       is deliberately not a flex container itself: the table is display
       :block at this width so nothing would break today, but keeping one
       structure across both layouts is what stops the two drifting
       apart again. */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody td.bulk-id-col {
        grid-area: 1 / 2 / 2 / -1;
        display: block !important;
        min-width: 0;
        font: 600 13.5px var(--cmw-font-mono) !important;
        color: var(--cmw-ink);
    }
    /* a card is only as wide as the phone, so the id takes what is left
       rather than the 168px slot a stretched desktop column needs */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack
        tbody .bulk-id-action-wrap {
        justify-content: space-between;
    }
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack
        tbody .bulk-id-value {
        max-width: none;
    }
    /* every other column prints its own name */
    /* grid, not flex: the value is a bare text node, and as an anonymous
       FLEX item it shrinks below its own content — with break-anywhere
       in force that split short words like "aditya" mid-letter. A grid
       track gives it a definite width instead. */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody td[data-lab] {
        grid-column: 1 / -1;
        display: grid;
        grid-template-columns: minmax(0, 45%) minmax(0, 1fr);
        gap: 10px;
        font-size: 12.5px;
    }
    /* min-width:0 because a grid item refuses to shrink past its own
       min-content by default, and a label like "Center
       Latitude/Longitude" has no space to break at — it overran its
       track and ran into the value instead of wrapping inside it */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody td[data-lab]::before {
        content: attr(data-lab);
        color: var(--cmw-text-faint);
        min-width: 0;
        overflow-wrap: anywhere;
    }
    /* values here have no spaces to break at — a unique id, a colon list
       of groups, an email — and a grid/flex child defaults to its
       min-content width, so without these the card is pushed wider than
       the viewport at 320 and the horizontal scroll comes back */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table.mpb-stack tbody td {
        min-width: 0;
        overflow-wrap: anywhere;
        /* The server sets nowrap on these cells, which is right for a
           column in a scrolling grid and wrong for a card: it silently
           beats overflow-wrap, so a label with no space to break at
           ("Center Latitude/Longitude") ran straight out of its track
           and into the value. Its rule is id-scoped, hence !important. */
        white-space: normal !important;
    }

    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-head .mpw-title { font-size: 21px; }
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table thead th { padding: 9px 10px !important; }
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table tbody td { padding: 9px 10px !important; }
    /* 16px keeps iOS from zooming the page when a field takes focus */
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table input[type="text"],
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table input[type="password"],
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table input:not([type]),
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table select,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-table textarea {
        font-size: 16px !important;
    }
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot,
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot > div { width: 100% !important; }
    html.modern-ui body.mpb-on:not(:has(#jsmap)) .mpb-foot input[type="submit"] { flex: 1 1 auto; }
}

/* ============================================================
   6b — WHAT THE SERVER ADDED UNDER US (v3.111.0)
   ============================================================
   Two things the four bulk-grid pages picked up that nothing in this
   layer knew about. Both are keyed on td.bulk-id-col, the same thing
   modern-patches identifies a grid by: it is on zone.info, dev.info,
   user.info and group.info and on no other page (checked against ten,
   including passwd, which has a contentCell and no grid).
   ============================================================ */

/* ── 1. the glimpse of the old UI ──────────────────────────────
   Handled in decorators/main.jsp, not here: the browser paints the
   page progressively as it parses, so a body:has(td.bulk-id-col)
   hook in this sheet armed only after the parser reached the grid -
   the legacy heading and the first rows were already painted (user,
   2026-08-19). The decorator arms html.mp-bulkhold inline in <head>,
   before any paint and immune to a stale copy of this file;
   modern-patches releases it with body.mpb-ready, and an 8s
   animation there releases it even if no script ever runs. */

/* ── 2. the server's three floating bars ───────────────────────
   All three arrived in the same rebuild (Aug 2026), built by the
   server's inline script:

     .bulk-floating-actions   a copy of the Add Row / Delete Row bar,
                              pinned to the bottom while the real one
                              is off screen
     .bulk-floating-scroll    a proxy for the card's own horizontal
                              scrollbar, same condition
     .bulk-floating-header    a clone of the header row, pinned to the
                              top while the real thead is off screen

   They are features, and they stay (user, 2026-08-19): the page is
   the scroller and the bars stand in for the real controls while
   those are off screen, retiring themselves the moment the real
   thing is visible again. What could not stay is the dress — legacy
   grey over a grid drawn in surface white — so the bars are skinned
   here and their buttons join the foot rules above. */
/* On a desktop the page ends where the grid does: Custom.css keeps
   the content cell min-height ~100vh so SHORT admin pages paint
   their background to the fold, and on these four it measured as up
   to 461px of blank scroll under the Delete Row bar. Below 900px the
   floor is doing its original job for the card layout. */
@media (min-width: 900px) {
    html.modern-ui body.mpb-on:not(:has(#jsmap)) td.contentCell {
        min-height: 0 !important;
        padding-bottom: 24px !important;
    }
}

html.modern-ui body:has(td.bulk-id-col) .bulk-floating-actions {
    background: var(--cmw-surface) !important;
    /* a hairline in --cmw-border plus an 8% shadow disappears on an
       ordinary office panel, and a white bar over a white table then
       reads as a blank band between the grid and its buttons (user,
       2026-08-19, on a 16:9 external display). The edge has to
       survive a cheap monitor. */
    border-top: 1px solid var(--cmw-border-strong) !important;
    box-shadow: 0 -2px 12px rgba(22, 32, 43, 0.16) !important;
    padding: 8px 16px !important;
}
/* The floating scrollbar has to LOOK like a scrollbar - on every
   machine. It shows exactly when the grid is wider than the screen
   (a 16:9 laptop on geozone), sitting right between the table and
   the buttons bar, and whether anything is visible INSIDE it used
   to be the browser's call: fluent overlay scrollbars paint their
   thumb only under the pointer, so the user saw 20px of blank white
   ("extra space between the sticky bottom and the table", twice).
   Nothing native is relied on any more: the native scrollbar is
   removed outright and buildBulkGrid draws the thumb as a plain
   element and handles the dragging itself - divs render identically
   on every panel, every scrollbar mode, every Chrome. */
html.modern-ui body:has(td.bulk-id-col) .bulk-floating-scroll {
    background: var(--cmw-surface-soft) !important;
    border-top: 1px solid var(--cmw-border-strong) !important;
    box-shadow: none !important;
    height: 16px !important;
    overflow: hidden !important;
    cursor: pointer;
    scrollbar-width: none;
    touch-action: none;
}
html.modern-ui body:has(td.bulk-id-col) .bulk-floating-scroll::-webkit-scrollbar {
    display: none;
}
html.modern-ui body:has(td.bulk-id-col) .bulk-floating-scroll .mpb-thumb {
    position: absolute;
    top: 4px;
    left: 0;
    height: 8px;
    width: 0;
    border-radius: 4px;
    background: var(--cmw-text-faint);
    pointer-events: none;
}
html.modern-ui body:has(td.bulk-id-col) .bulk-floating-scroll:hover .mpb-thumb {
    background: var(--cmw-text-muted);
}

/* The header clone alone is retired: the REAL header row now rides
   the page scroll (buildBulkGrid, v3.113.0), so the clone's own
   show-condition — the real thead off screen — never fires. The
   clone was the expensive way to the same place: ~25 computed styles
   per cell per rebuild, measured at 1.35s of frozen frame under this
   layer's sheets (scratch-u1). A stylesheet cannot make that cheap;
   it can only make it unnecessary.

   These rules only cover the moments the element EXISTS: from the
   server building it to retireServerHeadClone (modern-patches)
   removing it, and briefly again around the server's own load
   handler re-creating it. Removal is the real cure — merely styling
   it invisible still cost ~900ms frozen frames, because its scroll
   handler keeps writing to it and an in-document node makes the
   renderer pay for that (scratch-u1 kill-matrix). While it does
   exist: display pinned to block so its width-mismatch guard never
   sees the zero width that triggers a full rebuild, visibility plus
   height 0 so nothing paints or hit-tests at any width — including
   below 900px, where the real thead is display:none and the script
   would otherwise pin a 3px grey sliver across the top. */
html.modern-ui body:has(td.bulk-id-col) .bulk-floating-header {
    display: block !important;
    visibility: hidden !important;
    height: 0 !important;
    border: 0 !important;
    box-shadow: none !important;
    overflow: hidden !important;
}
