/* ==========================================================================
   Destiny SMS — premium layer (np-pro.css)
   Loads LAST, after np-tn.css. It never re-declares a palette: every colour
   comes from the --tn-* tokens in np-tn.css, so light and dark stay in sync
   automatically. Two jobs only:
     1. polish  — elevation, density, focus, scrollbars, small type fixes
     2. motion  — one shared easing/duration scale, entrance + interaction
                  animation, fully disabled under prefers-reduced-motion
   Functionality is untouched: no selector here changes layout order,
   sizing contracts, or anything np-router.js / app.js / np-tn.js depend on.
   ========================================================================== */

/* ── motion + elevation scale ──────────────────────────────────────── */
body.tnui {
  --tn-ease: cubic-bezier(.22, .61, .36, 1);      /* decelerate — entrances */
  --tn-ease-io: cubic-bezier(.4, .0, .2, 1);      /* standard */
  --tn-ease-out: cubic-bezier(.55, .05, .68, .19); /* accelerate — exits */
  --tn-in: 120ms;   --tn-fast: 180ms;
  --tn-mid: 260ms;  --tn-slow: 360ms;
  --tn-ring: 0 0 0 3px var(--accent-glow);
  --tn-elev-1: var(--tn-shadow);
  --tn-elev-2: 0 2px 6px rgba(17, 16, 24, .08), 0 12px 30px rgba(17, 16, 24, .12);
}
body.tnui.tn-dark {
  --tn-elev-2: 0 2px 6px rgba(0, 0, 0, .5), 0 14px 34px rgba(0, 0, 0, .46);
}

/* ══════════════════════════════════════════════════════════════════════
   1. SHELL
   ══════════════════════════════════════════════════════════════════════ */

/* header: lift the brand, give the tools a consistent hover language */
.tnui .tn-top { box-shadow: 0 1px 0 rgba(255, 255, 255, .07) inset; }
.tnui .tn-logo { transition: opacity var(--tn-fast) var(--tn-ease), transform var(--tn-fast) var(--tn-ease); }
.tnui .tn-logo:hover { opacity: .92; }
.tnui .tn-burger, .tnui .tn-tool {
  border-radius: 9px; transition: background var(--tn-in) var(--tn-ease-io),
    color var(--tn-in) var(--tn-ease-io), transform var(--tn-in) var(--tn-ease);
}
.tnui .tn-burger { border-radius: 8px; }
.tnui .tn-burger:active, .tnui .tn-tool:active { transform: scale(.94); }
.tnui .tn-tool.on { background: rgba(255, 255, 255, .16); }
.tnui .tn-avatar { border-radius: 50%; box-shadow: 0 0 0 2px rgba(255, 255, 255, .18); }
.tnui .tn-avatar img, .tnui .tn-avatar svg { display: block; }
.tnui .tn-avatar:hover { box-shadow: 0 0 0 2px var(--tn-lime); }

/* band: deepen it with a lime sheen so the header/band read as one object */
.tnui .tn-band { position: relative; overflow: hidden; }
.tnui .tn-band::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(680px 200px at 88% -30%, rgba(91, 77, 164, .28), transparent 68%),
    radial-gradient(420px 160px at 4% 130%, rgba(91, 77, 164, .12), transparent 70%);
}
.tnui .tn-band > * { position: relative; z-index: 1; }
.tnui .tn-title { font-weight: 700; letter-spacing: -.2px; }
/* 78% white reads as 3.84:1 over the band's lightest stop (the lime glow lifts
   #4A3E86 right where the crumb sits), under the 4.5:1 floor for 13px text.
   .92 clears it at 4.77:1 and still reads as secondary against the 700-weight
   title above it. */
.tnui .tn-crumb { color: rgba(255, 255, 255, .92); }
.tnui .tn-crumb span.lnk {
  cursor: pointer; border-radius: 4px; padding: 1px 2px;
  transition: color var(--tn-in) var(--tn-ease-io), background var(--tn-in) var(--tn-ease-io);
}
.tnui .tn-crumb span.lnk:hover { color: #fff; background: rgba(255, 255, 255, .12); text-decoration: none; }
.tnui .tn-band-in { animation: tnBandIn var(--tn-slow) var(--tn-ease) both; }
@keyframes tnBandIn { from { opacity: 0; transform: translateY(-6px); } }

/* band action buttons: one shape, one hover language */
.tnui .tn-acts .btn, .tnui .tn-acts button {
  border-radius: 8px; font-weight: 600; letter-spacing: .1px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
  transition: filter var(--tn-in) var(--tn-ease-io), transform var(--tn-in) var(--tn-ease),
    box-shadow var(--tn-fast) var(--tn-ease);
}
.tnui .tn-acts > :first-child { border-top-left-radius: 8px; border-bottom-left-radius: 8px; }
.tnui .tn-acts > :last-child { border-top-right-radius: 8px; border-bottom-right-radius: 8px; }
.tnui .tn-acts .btn:hover, .tnui .tn-acts button:hover { filter: brightness(1.08); box-shadow: 0 5px 14px rgba(0, 0, 0, .22); }
.tnui .tn-acts .btn:active, .tnui .tn-acts button:active { transform: translateY(1px); box-shadow: 0 1px 4px rgba(0, 0, 0, .2); }

/* drawer: separation + nav rhythm.
   Reference panel keeps the rail flat and lifts it with a short, cool shadow
   (`0 2px 8px rgba(40,90,185,.1)`), so the wide-screen docked state drops the
   44px slab entirely — off-canvas still needs it to read above the page. */
.tnui .tn-drawer { box-shadow: 18px 0 44px rgba(17, 16, 24, .16); border-right: 1px solid var(--tn-line); }
body.tnui.tn-dark .tnui, .tnui.tn-dark .tn-drawer { box-shadow: 18px 0 44px rgba(0, 0, 0, .55); }
@media (min-width: 1200px) {
  .tnui .tn-drawer { box-shadow: 0 2px 8px rgba(40, 90, 185, .10); }
  .tnui.tn-dark .tn-drawer { box-shadow: 0 2px 8px rgba(0, 0, 0, .35); }
}
.tnui .tn-drawer { scrollbar-width: thin; scrollbar-color: var(--tn-line) transparent; }
.tnui .tn-me { padding-bottom: 14px; border-bottom: 1px solid var(--tn-line); }
.tnui .tn-me-av { box-shadow: 0 0 0 3px var(--tn-line); }
.tnui .tn-me-role { color: var(--tn-muted); font-weight: 600; text-transform: uppercase; letter-spacing: .6px; font-size: 10.5px; }
.tnui .tn-item {
  /* np-tn.css sets the 50px reference pill; this file loads last, so the
     radius has to be restated here or it would fall back to a flat 8px row */
  border-radius: 50px; transition: background var(--tn-in) var(--tn-ease-io), color var(--tn-in) var(--tn-ease-io);
}
.tnui .tn-item:hover { background: var(--bg-hover); }
/* The chip used to invert to the card's own white whenever its row was lit —
   on hover, and permanently while its group is open. On a lavender pill that
   leaves a plain white disc with a glyph in it and no visible edge, which reads
   as an empty ring rather than an icon. The reference never empties the chip: a
   lit row carries the brand-filled chip with the glyph knocked out in the
   surface's own on-primary ink, which is what the active item already does,
   so hover and open now share that treatment. */
.tnui .tn-item:hover svg.ic, .tnui .tn-group.open > .tn-item svg.ic {
  background: var(--tn-primary); color: var(--tn-on-primary);
}
/* np-tn.css hardcodes #fff for the active chip. That is right in light mode
   (white on deep indigo) but ~2:1 in dark, where the brand flips to a pale
   lavender — so the active chip takes the same token as every other lit chip. */
.tnui .tn-item.nav-item.active svg.ic { color: var(--tn-on-primary); }
/* …and the private-access group keeps its own danger colour through the same
   filled-chip language, so the rule above cannot quietly re-brand it indigo.
   The knockout uses --tn-on-primary rather than #fff: dark mode lifts the red
   to a pale coral, and white on that would be ~3:1. */
.tnui .tn-g-priv > .tn-item:hover svg.ic, .tnui .tn-g-priv.open > .tn-item svg.ic {
  background: var(--tn-red); color: var(--tn-on-primary);
}
.tnui .tn-item.active, .tnui .tn-item.nav-item.active {
  background: var(--tn-tint-bg); color: var(--tn-tint-ink); font-weight: 700; position: relative;
}
/* the reference marks the current page with the filled icon chip, not a bar
   down the left edge, so the rail gets no ::before indicator */
.tnui .tn-nav-cap { letter-spacing: .7px; font-weight: 700; }
.tnui .tn-sub {
  border-radius: 7px; transition: color var(--tn-in) var(--tn-ease-io),
    background var(--tn-in) var(--tn-ease-io);
}
.tnui .tn-sub:hover { background: var(--bg-hover); }
.tnui .tn-sub::before { opacity: .55; transition: opacity var(--tn-in) var(--tn-ease-io), transform var(--tn-fast) var(--tn-ease); }
.tnui .tn-sub:hover::before { opacity: 1; transform: translateX(2px); }
.tnui .tn-sub.active {
  background: var(--tn-tint-bg); color: var(--tn-tint-ink); font-weight: 700;
  box-shadow: inset 2px 0 0 var(--tn-primary);
}
.tnui .tn-sub.active::before { opacity: 1; color: var(--tn-primary); }


/* measured rail width: the reference's white drawer ends at page x=250 and the
   #F6F9FB page background starts at 251, so 250px is the exact number. It is
   7px wider than the source panel's 243px shell, which is what lets
   "Management Group" fit without an ellipsis. */
.tnui .tn-drawer { width: 250px; }
@media (min-width: 1200px) { .tnui .tn-shell { margin-left: 250px; } }


/* the drawer identity circle now carries initials, not an empty disc */
.tnui .tn-me-av { font-size: 21px; font-weight: 800; letter-spacing: .5px;
  color: var(--tn-on-av); text-transform: uppercase; }
.tnui .tn-prof-head .tn-me-av { font-size: 24px; }

/* right side panel + footer */
/* the footer is a floating card in the reference: compact padding, hairline
   edge, and the brand line set in the primary rather than stretched out */
.tnui .tn-foot {
  position: relative; display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 6px 18px;
  padding: 10px 16px; text-align: left;
}
/* the reference footer is a flat hairline strip, not a decorated panel: the
   gradient centre-rule and the wide-tracked uppercase right hand were both
   heavier than the source, so the line now carries one quiet brand mark */
.tnui .tn-foot::before {
  content: ""; position: absolute; inset: 0 auto auto 50%; transform: translateX(-50%);
  width: 64px; height: 2px; border-radius: 999px;
  background: linear-gradient(90deg, transparent, var(--tn-primary), transparent);
  opacity: .35;
}
.tnui .tn-foot b { color: var(--tn-primary); font-weight: 700; letter-spacing: .2px; }
.tnui .tn-foot-l { font-size: 12px; }
@media (max-width: 640px) {
  .tnui .tn-foot { flex-direction: column; justify-content: center; text-align: center; }
}

/* floating actions */
.tnui .tn-fab, .tnui .nx-ai-fab {
  box-shadow: 0 8px 22px rgba(91, 77, 164, .42), var(--tn-elev-2);
  transition: transform var(--tn-fast) var(--tn-ease), box-shadow var(--tn-fast) var(--tn-ease);
}
.tnui .tn-fab:hover, .tnui .nx-ai-fab:hover { transform: translateY(-2px); }
.tnui .tn-fab:active, .tnui .nx-ai-fab:active { transform: translateY(0) scale(.95); }

/* ══════════════════════════════════════════════════════════════════════
   2. CONTENT SURFACES
   ══════════════════════════════════════════════════════════════════════ */
.tnui .card, .tnui .panel-box, .tnui .stat-card, .tnui .ctr, .tnui .chart-box {
  border-radius: var(--tn-radius); box-shadow: var(--tn-elev-1);
  transition: box-shadow var(--tn-fast) var(--tn-ease), transform var(--tn-fast) var(--tn-ease),
    border-color var(--tn-fast) var(--tn-ease-io);
}
.tnui .card:hover, .tnui .stat-card:hover { box-shadow: var(--tn-elev-2); }
.tnui .tn-card-head .t, .tnui .card-title, .tnui .section-title, .tnui .panel-head, .tnui .zp-card-head {
  font-weight: 700; letter-spacing: .3px;
}
.tnui .tn-card-head { padding-bottom: 10px; border-bottom: 1px solid var(--tn-line); margin-bottom: 12px; }
.tnui .tn-h20, .tnui .tn-mini-h, .tnui .tn-small-h { font-weight: 700; }

/* stat cards: number is the hero, tabular figures keep columns aligned */
.tnui .stat-card .stat-value, .tnui .tn-stat-r b, .tnui .ctr-val {
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; letter-spacing: -.4px;
}
.tnui .tn-stat { border-radius: var(--tn-radius); }
.tnui .tn-stat:hover { transform: translateY(-1px); }

/* tables: quieter header, living rows */
.tnui .zp-table thead th, .tnui .table-wrap > table:not(.zp-table) thead th, .tnui table.dt thead th {
  font-weight: 700; color: var(--tn-ink-2); background: var(--tn-stripe);
  border-top: 1px solid var(--tn-line);
}
.tnui .zp-table td, .tnui .zp-table th { font-variant-numeric: tabular-nums; }
.tnui .zp-table tbody tr { transition: background var(--tn-in) var(--tn-ease-io); }
.tnui .zp-table tbody tr:hover td { background: var(--tn-tint-bg); }
.tnui .table-wrap > table:not(.zp-table) tbody tr, .tnui table.dt tbody tr {
  transition: background var(--tn-in) var(--tn-ease-io);
}
.tnui .table-wrap > table:not(.zp-table) tbody tr:hover td { background: var(--bg-hover); }
.tnui table.dt tbody tr:hover td { background: var(--bg-hover); }

/* numeric / code surfaces */
.tnui .tn-token, .tnui .tn-url, .tnui .tn-resp {
  font-family: "JetBrains Mono", Consolas, monospace; font-variant-numeric: tabular-nums;
  border-radius: var(--tn-radius-sm);
}
.tnui .tn-resp { border: 1px solid #16321f; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .04); }

/* ══════════════════════════════════════════════════════════════════════
   3. CONTROLS
   ══════════════════════════════════════════════════════════════════════ */
.tnui .btn, .tnui .tn-btn {
  font-weight: 600; letter-spacing: .1px; border-radius: var(--tn-radius-sm);
  transition: background var(--tn-in) var(--tn-ease-io), color var(--tn-in) var(--tn-ease-io),
    border-color var(--tn-in) var(--tn-ease-io), box-shadow var(--tn-fast) var(--tn-ease),
    transform var(--tn-in) var(--tn-ease);
}
.tnui .btn:hover, .tnui .tn-btn:hover { box-shadow: var(--tn-elev-1); }
.tnui .btn:active, .tnui .tn-btn:active { transform: translateY(1px); box-shadow: none; }
.tnui .btn-primary:hover, .tnui .tn-btn-primary:hover { filter: none; }
.tnui .btn-outline:hover, .tnui .tn-btn-outline:hover { border-color: var(--tn-primary); color: var(--tn-primary); }
.tnui .btn:disabled, .tnui .tn-btn:disabled, .tnui button:disabled { opacity: .5; cursor: not-allowed; transform: none; box-shadow: none; }

/* fields: one focus language everywhere */
.tnui input, .tnui select, .tnui textarea {
  border-radius: var(--tn-radius-sm); font-family: inherit;
  transition: border-color var(--tn-in) var(--tn-ease-io), box-shadow var(--tn-fast) var(--tn-ease),
    background var(--tn-in) var(--tn-ease-io);
}
.tnui input:focus, .tnui select:focus, .tnui textarea:focus { box-shadow: var(--tn-ring); border-color: var(--tn-primary); }
.tnui .form-label, .tnui .tn-f label, .tnui .tn-inline label { font-weight: 600; color: var(--tn-ink-2); }

/* keyboard focus: visible on every interactive element, invisible for mouse */
.tnui :focus { outline: none; }
.tnui :focus-visible { outline: 2px solid var(--tn-primary); outline-offset: 2px; border-radius: 6px; }
.tnui .tn-top :focus-visible, .tnui .tn-band :focus-visible {
  outline-color: var(--tn-lime-2);
}

/* badges + pills */
/* the panel's status vocabulary is badge-green / -red / -yellow / -blue /
   -purple / -gray / -cyan, plus the Bootstrap-style -success / -danger /
   -warning aliases used by the test panel */
.tnui .badge { padding: 3px 9px; font-size: 11px; font-weight: 700; letter-spacing: .3px; border-radius: 999px;
  display: inline-flex; align-items: center; gap: 5px; line-height: 1.5; white-space: nowrap; }
.tnui .badge:empty { display: none; }
.tnui .badge-green, .tnui .badge-success, .tnui .badge.active { background: var(--tn-ok-bg); color: var(--tn-ok-ink); }
.tnui .badge-red, .tnui .badge-danger, .tnui .badge.inactive { background: var(--tn-bad-bg); color: var(--tn-bad-ink); }
.tnui .badge-yellow, .tnui .badge-warning, .tnui .badge-orange { background: var(--tn-warn-bg); color: var(--tn-warn-ink); }
.tnui .badge-blue, .tnui .badge-cyan { background: rgba(31, 111, 156, .13); color: var(--tn-sky); }
/* the tier slot keeps its own hue so it cannot be mistaken for badge-green;
   it is the only purple left in the product and it is muted to brand plum. */
.tnui .badge-purple { background: var(--tn-plum-bg); color: var(--tn-plum-ink); }
.tnui .badge-gray { background: var(--tn-stripe); color: var(--tn-muted); }

/* overlays */
.tnui .tn-dim { backdrop-filter: blur(2px); transition: opacity var(--tn-mid) var(--tn-ease-io); }
.tnui .modal-overlay { background: var(--tn-scrim); }
.tnui .modal { box-shadow: 0 28px 70px rgba(4, 12, 7, .42); border-radius: var(--tn-radius); }
.tnui .modal-header { position: relative; }
.tnui .modal-header::after {
  content: ""; position: absolute; left: 22px; right: 22px; bottom: 0; height: 2px;
  background: var(--tn-pill); opacity: .55; border-radius: 2px;
}

/* ══════════════════════════════════════════════════════════════════════
   4. MOTION
   Entrances are one-off (animation, not transition) and never loop except
   on loading indicators, per UX guidance. Nothing here moves layout.
   ══════════════════════════════════════════════════════════════════════ */
@keyframes tnFadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes tnFadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes tnPop { from { opacity: 0; transform: translateY(8px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes tnSlideLeft { from { opacity: 0; transform: translateX(18px); } to { opacity: 1; transform: none; } }
@keyframes tnShimmer { to { background-position: 200% 0; } }

/* page swap: the routed content fades up, cards stagger behind it */
.tnui #page-content { animation: tnFadeIn var(--tn-fast) var(--tn-ease) both; }
.tnui #page-content > .card, .tnui #page-content > .panel-box, .tnui #page-content > .stats-grid,
.tnui #page-content > .two-col, .tnui #page-content > .tn-two, .tnui #page-content > .page-intro,
.tnui #page-content > .tn-stats, .tnui #page-content > .counters {
  animation: tnFadeUp var(--tn-mid) var(--tn-ease) both;
}
.tnui #page-content > *:nth-child(1) { animation-delay: 20ms; }
.tnui #page-content > *:nth-child(2) { animation-delay: 60ms; }
.tnui #page-content > *:nth-child(3) { animation-delay: 100ms; }
.tnui #page-content > *:nth-child(4) { animation-delay: 130ms; }
.tnui #page-content > *:nth-child(5) { animation-delay: 160ms; }
.tnui #page-content > *:nth-child(n + 6) { animation-delay: 180ms; }

/* scroll reveal: np-pro.js adds .tn-rv to off-screen blocks, .tn-rv-in when seen */
.tnui .tn-rv { opacity: 0; transform: translateY(14px); }
.tnui .tn-rv.tn-rv-in { animation: tnFadeUp var(--tn-mid) var(--tn-ease) both; }

/* drawer / side panel entrances are handled by the existing transform
   transition; give the menu rows a short cascade when the drawer opens */
.tnui .tn-drawer.open .tn-item { animation: tnSlideLeft var(--tn-mid) var(--tn-ease) both; }
.tnui .tn-drawer.open .tn-item:nth-child(n + 8) { animation-delay: 40ms; }
.tnui .tn-drawer.open .tn-item:nth-child(n + 16) { animation-delay: 70ms; }
.tnui .tn-drawer.open .tn-item:nth-child(n + 30) { animation-delay: 95ms; }

/* feedback surfaces */
.tnui .toast { animation: tnPop var(--tn-mid) var(--tn-ease) both; box-shadow: var(--tn-elev-2); border-radius: var(--tn-radius-sm); }
/* np-tn's shorthand border flattened the 3px status rail style.css sets, so
   every toast read as the same type. Restore it from the semantic tokens. */
.tnui .toast { border: 1px solid var(--tn-line); border-left-width: 3px; }
.tnui .toast.success { border-left-color: var(--tn-ok-ink); }
.tnui .toast.error   { border-left-color: var(--tn-bad-ink); }
.tnui .toast.warning { border-left-color: var(--tn-warn-ink); }
.tnui .toast.info    { border-left-color: var(--tn-primary); }
.tnui .toast.success i { color: var(--tn-ok-ink); }
.tnui .toast.error i   { color: var(--tn-bad-ink); }
.tnui .toast.warning i { color: var(--tn-warn-ink); }
.tnui .toast.info i    { color: var(--tn-primary); }
.tnui .modal { animation: tnPop var(--tn-mid) var(--tn-ease) both; }
#tn-tp-modal { animation: tnFadeIn var(--tn-fast) var(--tn-ease-io) both; }
.tnui .alert-success, .tnui .alert-danger, .tnui .zp-alert, .tnui .tn-at-msg { animation: tnFadeUp var(--tn-fast) var(--tn-ease) both; }
.tnui .tn-drop { transition: border-color var(--tn-fast) var(--tn-ease-io), background var(--tn-fast) var(--tn-ease-io); }
.tnui .tn-drop:hover { border-color: var(--tn-primary); background: var(--tn-tint-bg); }

/* live indicators only — these are the sole looping animations */
.tnui .nx-live-dot { animation: tnLive 1.8s var(--tn-ease-io) infinite; }
@keyframes tnLive { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.82); } }

/* loading placeholders */
.tnui .tn-skel {
  border-radius: var(--tn-radius-sm); color: transparent !important; pointer-events: none;
  background-image: linear-gradient(90deg, var(--tn-stripe) 0%, var(--bg-hover) 40%, var(--tn-stripe) 80%);
  background-size: 200% 100%; animation: tnShimmer 1.2s linear infinite;
}


/* accent groups: the two sections that should read apart from the routine
   menu — the AI control area and the private developer zone */
.tnui .tn-g-ai > .tn-item { color: var(--tn-primary); font-weight: 700; }
.tnui .tn-g-ai > .tn-item svg { color: var(--tn-primary); }
.tnui .tn-g-priv { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--tn-line); }
.tnui .tn-g-priv > .tn-item { color: var(--tn-red); font-weight: 700; }
.tnui .tn-g-priv > .tn-item svg { color: var(--tn-red); }
.tnui .tn-g-priv .tn-sub.active { color: var(--tn-red); background: var(--tn-bad-bg); box-shadow: inset 2px 0 0 var(--tn-red); }
.tnui .tn-g-priv .tn-sub.active::before { color: var(--tn-red); }


/* ── contrast guard ────────────────────────────────────────────────────
   In light mode the action fills are dark, so their labels are white. In
   dark mode the same tokens turn bright lime / amber / coral, and white on
   those is ~2:1. Every solid-fill surface therefore flips its label to
   --tn-on-primary (near-black) when .tn-dark is active. The list covers the
   zp-* kit, the .tn-* shell and style.css's legacy .pagination, because all
   three name their fills differently but share the same hardcoded #fff label.
   An open nav group is deliberately not in this list: its row is filled with
   the brand wash (--tn-tint-bg), never with a solid primary, so near-black on
   a dark wash would be ~2:1 the other way. Its label stays --tn-menu; only
   the chip inside it flips, and that sets its own colour explicitly. */
.tnui.tn-dark .btn-primary, .tnui.tn-dark .btn-warning, .tnui.tn-dark .btn-danger,
.tnui.tn-dark .btn-success, .tnui.tn-dark .tn-btn.blue, .tnui.tn-dark .tn-btn.orange,
.tnui.tn-dark .tn-btn.red, .tnui.tn-dark .tn-btn.green, .tnui.tn-dark .tn-btn.sm,
.tnui.tn-dark .zp-pager button.active, .tnui.tn-dark .zp-table .btn-icon,
.tnui.tn-dark .zp-filters .btn, .tnui.tn-dark .zp-filter-actions .btn,
.tnui.tn-dark .tn-dl button,
.tnui.tn-dark .tn-at-tabs button.on, .tnui.tn-dark .tn-fab,
.tnui.tn-dark .dt-export button,
.tnui.tn-dark .dt-pager button.active,
.tnui.tn-dark .pagination button.active, .tnui.tn-dark .tn-req,
.tnui.tn-dark .tn-tb .tn-exp button, .tnui.tn-dark .zp-actions .btn,
.tnui.tn-dark .zp-actions .btn i, .tnui.tn-dark .zp-actions .btn-outline,
.tnui.tn-dark .zp-actions .zp-btn-blue, .tnui.tn-dark .zp-actions .zp-btn-cyan,
.tnui.tn-dark .zp-actions .btn-primary, .tnui.tn-dark .zp-actions .btn-warning,
.tnui.tn-dark .zp-actions .btn-danger, .tnui.tn-dark .zp-table .btn-icon i,
.tnui.tn-dark .zp-sel-opt:hover, .tnui.tn-dark .zp-sel-opt.on,
.tnui.tn-dark .zp-sel-opt:hover i, .tnui.tn-dark .zp-sel-opt.on i,
/* the test panel's own toolbar buttons name their fills .btn-blue/.btn-red/
   .btn-orange and np-testpanel.css pins #fff with !important, so they need the
   same flip here. .tn-testpanel sits on the same <body> as .tnui/.tn-dark, so
   the three classes are chained on one compound selector — writing it as a
   descendant (".tn-dark .tn-testpanel") would never match. */
.tnui.tn-dark.tn-testpanel .btn-blue, .tnui.tn-dark.tn-testpanel .btn-red,
.tnui.tn-dark.tn-testpanel .btn-orange {
  color: var(--tn-on-primary) !important;
}
.tnui.tn-dark .btn-primary, .tnui.tn-dark .zp-pager button.active { font-weight: 700; }

/* ── pagination + table toolbar ─────────────────────────────────────────
   The kit drew the pager as one fused bar with square corners; the rest of
   the panel is rounded, so give every control the same language. */
.tnui .zp-pager button, .tnui .dt-pager button {
  border-radius: var(--tn-radius-sm); margin-left: 4px; min-width: 32px;
  transition: background var(--tn-in) var(--tn-ease-io), color var(--tn-in) var(--tn-ease-io),
    border-color var(--tn-in) var(--tn-ease-io), transform var(--tn-in) var(--tn-ease);
}
.tnui .zp-pager button:not(:disabled):hover, .tnui .dt-pager button:not(:disabled):hover {
  border-color: var(--tn-primary); color: var(--tn-primary);
}
.tnui .zp-pager button.active:hover { color: var(--tn-on-primary); }
.tnui .zp-pager button:active:not(:disabled), .tnui .dt-pager button:active:not(:disabled) { transform: translateY(1px); }
.tnui .zp-info, .tnui .dt-info, .tnui .zp-foot { color: var(--tn-muted); }
.tnui .zp-actions button, .tnui .zp-colvis, .tnui .zp-sel-btn, .tnui .tn-tb .tn-srch input {
  transition: border-color var(--tn-in) var(--tn-ease-io), box-shadow var(--tn-fast) var(--tn-ease);
}
.tnui .zp-actions button:hover, .tnui .zp-sel-btn:hover { border-color: var(--tn-primary); }

/* ══════════════════════════════════════════════════════════════════════
   4b. RESELLER DENSITY
   The reference panel is a working reseller console, not a marketing page:
   0.9rem body with .5rem (8px) cell padding, a cooler #d5d5de grid, and
   #56566a ink inside tables. This layer only retunes measurement and
   borders — every selector here already existed in np-tn.css / np-agent-pro.css.
   ══════════════════════════════════════════════════════════════════════ */
/* ── tables: the source is flat, not striped ────────────────────────────
   Measured off its Ranges/Reports screens: header and every row are #ffffff,
   separated only by a hairline bottom rule, with the sort arrows pushed to
   the right edge of each header cell. The earlier lavender stripe and filled
   header were mine, not the reference's, so they come back out here. */
.tnui .zp-table thead th, .tnui .tn-table thead th,
.tnui .table-wrap > table:not(.zp-table) thead th, .tnui table.dt thead th {
  padding: 10px 8px; font-size: 12.5px; font-weight: 700; text-transform: none;
  letter-spacing: 0; color: var(--tn-ink-2); background: var(--tn-card);
  border: 0; border-bottom: 1px solid var(--tn-ctrl);
}
.tnui .zp-table tbody td, .tnui .tn-table tbody td,
.tnui .table-wrap > table:not(.zp-table) tbody td, .tnui table.dt tbody td {
  padding: 10px 8px; font-size: 12.5px; line-height: 1.5;
  color: var(--tn-table-ink); background: var(--tn-card);
  border: 0; border-bottom: 1px solid var(--tn-line);
}
.tnui .zp-table tbody tr:nth-child(odd) td, .tnui .tn-table.striped tbody tr:nth-child(odd) td,
.tnui .table-wrap > table:not(.zp-table) tbody tr:nth-child(odd) td,
.tnui .tn-testpanel table.dt tbody tr:nth-child(odd) td { background: var(--tn-card); }
.tnui .zp-table tbody tr:last-child td, .tnui table.dt tbody tr:last-child td,
.tnui .table-wrap > table:not(.zp-table) tbody tr:last-child td { border-bottom: 0; }
.tnui .zp-table tfoot td, .tnui .table-wrap > table:not(.zp-table) tfoot td { padding: 8px; }
.tnui .zp-table tbody tr:hover td, .tnui table.dt tbody tr:hover td,
.tnui .table-wrap > table:not(.zp-table) tbody tr:hover td { background: var(--tn-tint-bg); }
.tnui .zp-table th.zp-sort { padding-right: 22px; }
.tnui .zp-table th.zp-sort::before, .tnui .zp-table th.zp-sort::after { right: 8px; }
.tnui .zp-table th.zp-sort::before { border-bottom-color: var(--tn-ink-2); }
.tnui .zp-table th.zp-sort::after { border-top-color: var(--tn-ink-2); }
/* An empty row is row one, so it would otherwise catch the odd-row stripe rule and
   show a grey band around a message that is not data. Matched to np-tn.css's own
   specificity, where the !important actually lands. */
.tnui .zp-table tbody td.empty-state, .tnui .tn-table tbody td.tn-empty { background: var(--tn-card) !important; }

/* ── empty states ──────────────────────────────────────────────────────── */
/* Every list in the panel falls back to the same two nodes — a glyph and a line
   of text — and both raw versions read as unfinished: "No data" in bare muted
   grey, or a 40px inbox glyph shouting over an empty card. One treatment now
   covers the table cell and the block-level card state alike: the glyph drops
   into a small tinted disc and the message sits under it as a label. The disc
   uses the brand wash, which keeps it legible in both themes without a picture,
   and the markup's <br> goes because the disc is already its own line. */
.tnui td.empty-state, .tnui .empty-state {
  text-align: center; color: var(--tn-ink);
  font-size: 13.5px; font-weight: 700; letter-spacing: -.1px;
}
.tnui td.empty-state { padding: 32px 12px !important; }
.tnui .empty-state { padding: 40px 22px; }
.tnui td.empty-state i, .tnui .empty-state i {
  display: grid; place-items: center; box-sizing: border-box;
  width: 46px; height: 46px; margin: 0 auto 11px; border-radius: 50%;
  background: var(--tn-tint-bg); color: var(--tn-tint-ink);
  font-size: 17px; line-height: 1;
}
.tnui td.empty-state br { display: none; }
/* Label, then optional detail. A block state usually carries one line of text, which
   must read exactly like the line in a table cell, so the first paragraph simply takes
   the container's own label style; anything after it — and the long "what to do next"
   sentences the legacy pages wrote inline — stays muted, wraps to a readable measure
   and hangs under the label. Sites that mark up a heading get the same size and weight
   as the label instead of a page-title h3 inside a message box. */
.tnui .empty-state p { margin: 0; font-weight: 500; font-size: 13px; color: var(--tn-muted); max-width: 52ch; margin-inline: auto; }
.tnui .empty-state > p:first-of-type { font: inherit; max-width: none; }
.tnui .empty-state h3 { margin: 0 0 3px; font-size: 13.5px; font-weight: 700; letter-spacing: -.1px; color: var(--tn-ink); }
.tnui .empty-state > p:first-of-type ~ p, .tnui .empty-state h3 + p { margin-top: 4px; }
/* …unless a heading already is the label: a paragraph under an h3 goes back to detail
   text, otherwise the h3 + p pair would show two bold lines and one muted nothing. */
.tnui .empty-state h3 ~ p {
  font-weight: 500; font-size: 13px; color: var(--tn-muted);
  max-width: 52ch; margin-inline: auto;
}
/* An empty state that is really a failure or a wait keeps the disc but takes the
   state's own colours, so tint and glyph always agree. The legacy markup set
   `style="color:…"` on the glyph instead, which painted red ink on a lavender disc
   and left the disc stuck at the brand tint in dark mode. */
.tnui .empty-state.is-bad i, .tnui td.empty-state.is-bad i { background: var(--tn-bad-bg); color: var(--tn-bad-ink); }
.tnui .empty-state.is-warn i { background: var(--tn-warn-bg); color: var(--tn-warn-ink); }
.tnui .empty-state.is-ok i { background: var(--tn-ok-bg); color: var(--tn-ok-ink); }
/* The narrow cards — Recent News and Recent Ranges on the dashboard — cannot
   carry a 46px disc plus 40px of padding without shoving their section header
   out of the card. Same treatment, one step down, so an empty side list and an
   empty table still read as the same component. */
.tnui .empty-state.is-compact { padding: 20px 14px; }
.tnui .empty-state.is-compact i { width: 34px; height: 34px; margin-bottom: 8px; font-size: 13px; }
.tnui .empty-state.is-compact > p:first-of-type { font-size: 12.5px; }
.tnui .empty-state.is-compact p { font-size: 12px; }
/* A week with no traffic has nothing to plot: the greeting card keeps its height and
   the message sits over the empty grid instead of axes around a flat line. */
.tnui .tn-chart-empty {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: var(--tn-card); z-index: 1;
}
/* The reference draws no box around its tables. The card that holds them is
   the only container, and the rows carry the whole structure through hairline
   bottom rules — so the bordered, rounded shell I had put on `.table-wrap` /
   `.zp-wrap` was an extra frame the source never has, and it read as a second
   card nested inside the first. */
.tnui .table-wrap, .tnui .zp-wrap { border: 0; border-radius: 0; }

/* controls: 0.15rem radius, #d5d5de edge, and the reference's own focus
   ring (a soft 25% wash of the brand) rather than a hard outline */
.tnui input, .tnui select, .tnui textarea { border: 1px solid var(--tn-ctrl); }
.tnui input:focus, .tnui select:focus, .tnui textarea:focus {
  border-color: #ADA6D2; box-shadow: 0 0 0 0.25rem rgba(91, 77, 164, .25);
}
.tnui .tn-tb .tn-srch input, .tnui .zp-actions input, .tnui .zp-actions select { min-height: 32px; }
.tnui .zp-pager button, .tnui .dt-pager button {
  min-width: 30px; height: 28px; padding: 0 7px; border-color: var(--tn-ctrl);
}

/* badges: the reference sets them at .75em / 600 on a flat subtle fill */
.tnui .badge { padding: 2px 8px; font-size: 10.5px; font-weight: 600; letter-spacing: .2px; }

/* ══════════════════════════════════════════════════════════════════════
   4c. REFERENCE SHELL
   Structure, not just colour. Measured off the source panel's own screenshots
   (reference/user-ss) with sample-ref.js, because the harvested CSS alone did
   not describe the layout:
     header  #5B4DA4, 60px, white 8px-radius tiles for menu + account,
             live clock in white at the right
     crumb   a second, slimmer #5B4DA4 strip carrying home glyph + breadcrumb —
             the source has no big hero band, the title lives in the page
     page    #F6F9FB, cards sit flush (no overlap onto the indigo)
     rail    white, centred avatar + red notification dot, red role pill,
             Support Contact card pinned at the foot
   ══════════════════════════════════════════════════════════════════════ */

/* ── top bar ─────────────────────────────────────────────────────────── */
/* 50px, measured: header indigo runs from the top of the viewport to the band
   with nothing between them, and the two are the same colour, so the seam is
   invisible and only the total height matters. */
.tnui .tn-top { height: 50px; padding: 0 16px 0 14px; }

/* ── the rail toggles: one cluster, at the left edge ──────────────────── */
/* The menu rail and the clock/ranges rail both slide a panel open, so they sit
   together left of the wordmark instead of one at either end of the bar — two
   near-identical four-line glyphs at opposite extremes read as the same control
   duplicated. Quick Links, which also opens a panel, moved across with them, so
   the bar now reads "rails on the left, account on the right" and the only solid
   tiles left are the menu button and the avatar, one at each end. */
.tnui .tn-tgl { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.tnui .tn-brand { gap: 11px; }
.tnui .tn-burger {
  width: 34px; height: 34px; flex: 0 0 auto; border-radius: 8px; border: 0; padding: 0;
  background: #fff; color: var(--tn-primary); opacity: 1;
  box-shadow: 0 1px 3px rgba(17, 16, 24, .16);
  transition: background var(--tn-in) var(--tn-ease-io), color var(--tn-in) var(--tn-ease-io),
              box-shadow var(--tn-in) var(--tn-ease-io), transform var(--tn-in) var(--tn-ease);
}
.tnui .tn-burger svg { width: 17px; height: 17px; }
.tnui .tn-burger:hover { background: var(--tn-tint-bg); }
.tnui .tn-burger:active { transform: scale(.94); }
/* Inside the cluster the tile means "this rail is open" — but only for the two
   secondary toggles. The menu button keeps its tile whether or not the rail is
   docked: at 1440px the rail is an overlay that starts closed, and an un-tiled
   primary control at the very corner of the bar reads as disabled. */
.tnui .tn-burger-alt[aria-expanded="false"] { background: transparent; color: #fff; box-shadow: none; }
.tnui .tn-burger-alt[aria-expanded="false"]:hover { background: rgba(255, 255, 255, .14); color: #fff; }
/* Quick Links opens a modal rather than a rail, so it never carries aria-expanded
   and must not be tiled by the base rule — including before np-tn.js has run. */
.tnui .tn-burger-alt:not([aria-expanded]) { background: transparent; color: #fff; box-shadow: none; }
.tnui .tn-burger-alt:not([aria-expanded]):hover { background: rgba(255, 255, 255, .14); color: #fff; }

/* The brand is now the PixelCore mark — an SVG tile that is already light on
   dark — plus the name as real text, so the old "lift the dark PNG to a white
   silhouette" filter is gone: on the mark it would bleach the tile flat and
   erase the pixels. The header band is indigo in both themes (#5B4DA4 light,
   #2E2857 dark), so the wordmark's inks are fixed rather than token-driven. */
.tnui .tn-wordmark { display: inline-flex; align-items: center; gap: 9px; min-width: 0; cursor: pointer; user-select: none; }
.tnui .tn-logo { width: 30px; height: 30px; flex: 0 0 auto; }
.tnui .tn-word {
  font-size: 17px; font-weight: 800; letter-spacing: -.4px; line-height: 1;
  color: #fff; white-space: nowrap;
}
.tnui .tn-word b { font-weight: 800; color: #DCD4FA; }
.tnui .tn-wordmark:hover .tn-logo { filter: brightness(1.07); }
/* Under the rail breakpoint the bar carries three toggles, the clock and the
   account tile; the name is the first thing that can go without the brand
   becoming unrecognisable, because the mark is the memorable half. */
@media (max-width: 560px) { .tnui .tn-word { display: none; } }

/* the source's other header controls stay flat white glyphs on the indigo —
   only menu and account get the raised tile, so don't tile every .tn-tool */
.tnui .tn-tool {
  width: 32px; height: 32px; border-radius: 8px; background: transparent; color: #fff;
}
.tnui .tn-tool:hover { background: rgba(255, 255, 255, .14); color: #fff; }
.tnui .tn-clock {
  margin-right: 10px; color: #fff; font-size: 12.5px; font-weight: 700;
  letter-spacing: .1px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.tnui .tn-avatar {
  width: 34px; height: 34px; border-radius: 8px; background: #fff; margin: 0 0 0 4px;
  box-shadow: 0 1px 3px rgba(17, 16, 24, .16);
  transition: box-shadow var(--tn-in) var(--tn-ease), transform var(--tn-in) var(--tn-ease);
}
.tnui .tn-avatar svg { width: 18px; height: 18px; color: var(--tn-primary); }
.tnui .tn-avatar:hover { box-shadow: 0 3px 10px rgba(17, 16, 24, .26); }
.tnui .tn-avatar:active { transform: scale(.94); }

/* ── indigo band: breadcrumb on top, content card overlapping below ──── */
/* np-tn.js lifts a page's own subtitle/description line into the plate and
   hides the original in place rather than deleting it, so a second pass over
   the same page still finds the text to carry. */
.tnui .tn-carried { display: none !important; }

/* Measured on the source's own screenshots (scan-ref.js, column x=250 / x=1000
   of reference/user-ss/Screenshot*154742.png): the indigo block runs 170px
   (page y 171→341) and the white content card starts at page y 225, i.e. 54px
   below the band's top edge. So the band is NOT a slim strip — it is a tall
   indigo field whose lower two thirds the card sits on top of. That is why the
   padding-bottom is 134px and .tn-main pulls itself back up by 116px. */
.tnui .tn-band { padding: 16px 24px 134px; margin-top: 0; }
.tnui .tn-band-in { grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 14px; }
.tnui .tn-crumb { font-size: 13px; font-weight: 500; }
/* the strip leads with a home glyph before "Home" */
.tnui .tn-crumb::before {
  content: ""; flex: 0 0 auto; width: 15px; height: 15px; margin-right: 11px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3 2 12h3v9h6v-6h2v6h6v-9h3z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 3 2 12h3v9h6v-6h2v6h6v-9h3z'/%3E%3C/svg%3E") center / contain no-repeat;
}
.tnui .tn-acts { padding-top: 0; }
/* its action buttons live inside the content card, so anything left on the
   indigo strip needs a light surface to stay legible */
.tnui .tn-acts .btn-primary, .tnui .tn-acts .btn-blue {
  background: #fff !important; color: var(--tn-primary) !important;
  border-radius: var(--tn-radius-sm); font-weight: 700;
}
/* np-agent-pro.css forces .zp-btn-blue/.zp-btn-cyan glyphs to #fff for the dark
   in-page versions of those buttons. liftActions() renames them onto the band,
   where the surface is now white — the label follows the colour above but the
   icon kept its forced white and the button read as an empty white box. */
.tnui .tn-acts .btn-primary i, .tnui .tn-acts .btn-blue i,
.tnui .tn-acts .btn-primary svg, .tnui .tn-acts .btn-blue svg {
  color: var(--tn-primary) !important;
}

/* ── page column: the heading card overlaps the band, content sits below ─ */
.tnui .tn-main { margin-top: -116px; padding: 0 24px 24px; position: relative; z-index: 2; }
.tnui .tn-main #page-content { margin: 0; }
/* The source's dashboard is: a tall white card whose top 100px sit *inside* the
   indigo band (it carries the greeting and the chart), then a 22px gutter, then
   the stat row on plain page background. We cannot move #page-content's
   children around without touching the renderers, so the greeting is promoted
   to that first card instead: 17px of top padding, the 19px line, then 74px of
   bottom padding, which lands its bottom edge exactly on the band's (page
   y 50 + 16 + ~20 + 134 = 220). The 20px margin below it is the gutter, so the
   row underneath shows page grey in its gutters rather than raw indigo — the
   earlier -74px negative margin pulled the first row back up into the band and
   made the whole dashboard read as one merged panel. */
.tnui .tn-title {
  margin: 0 0 20px; padding: 17px 22px 74px;
  border-radius: var(--tn-radius);
  background: var(--tn-card); color: var(--tn-ink); font-size: 19px; font-weight: 600;
  letter-spacing: -.1px; line-height: 1.35; box-shadow: var(--tn-shadow);
}
/* The reference's greeting card carries a second line — "Here is your daily
   activity and reports." It is positioned inside the plate's 74px of bottom
   padding rather than being a child of the h1, because setBand() writes
   tn-title.textContent and would wipe any nested node. Hidden below 640px,
   where the card drops the plate and sits on plain grey. */
.tnui .tn-lead {
  position: absolute; top: 49px; left: 46px; right: 46px;
  font-size: 13px; font-weight: 400; line-height: 1.45; color: var(--tn-ink-2);
}
.tnui .tn-lead.off { display: none; }
/* Table, form and detail pages open with ONE full-width card, and the source
   puts the page heading inside it ("Assigned Numbers" sits at the card's
   top-left with the toolbar 24px below). Pulling that first card up 70px
   buries the plate's bottom edge behind it, so heading and card read as a
   single panel. Multi-card first rows (the dashboard's stat row) deliberately
   do NOT pull up: their gutters would show the plate in the top half and page
   grey in the bottom half, which reads as a rendering fault.
   The `.page-header +` variants exist because the renderer still emits a
   .page-header ahead of the card and np-tn.css hides it with display:none —
   a hidden box still occupies :first-child, so the plain positional selector
   alone never matched the table pages. */
.tnui #page-content > .card:first-child,
.tnui #page-content > .panel-box:first-child,
.tnui #page-content > .table-wrap:first-child,
.tnui #page-content > .zp-wrap:first-child,
.tnui #page-content > .page-header + .card,
.tnui #page-content > .page-header + .panel-box,
.tnui #page-content > .page-header + .table-wrap,
.tnui #page-content > .page-header + .zp-wrap {
  margin-top: -70px; border-top-left-radius: 0; border-top-right-radius: 0;
  box-shadow: none;
}
/* The positional selectors above only reach a page whose card is literally
   first, or the hidden .page-header's immediate neighbour. head() also emits a
   description paragraph and a flash banner between them, which silently turned
   the fuse off for every one of those pages: the card kept its own rounded top,
   shadow and duplicate heading and 134px of band padding stayed visible.
   np-tn.js now finds the real first card and marks it, so the geometry is the
   same as the static rules above rather than a new one. */
.tnui #page-content > .tn-merge {
  margin-top: -70px; border-top-left-radius: 0; border-top-right-radius: 0;
  box-shadow: none;
}
.tnui.tn-has-lead #page-content > .tn-merge { margin-top: -50px; }

/* The source's dashboard row is four equal cards across 1622px of content with
   20px gutters and 24px side padding (measured on row y=800 of
   Screenshot*154716.png: card edges at 274/665, 684/1074, 1094/1484, 1503/1896).
   Its money tiles are a round tinted chip on the left and then, to the right,
   the VALUE printed large over its small label — two of those columns side by
   side. Ours had the value and label on one baseline row, which reads as a
   table rather than a stat. The markup emits span-then-b per row, so the pair
   flips to b-over-span with flex order instead of editing statCard(). */
/* auto-FIT, not auto-fill: the source's dashboard row always fills its
   container edge to edge, whereas auto-fill leaves empty tracks behind and the
   indigo band shows through the gaps on a three-card page. */
.tnui .page-content .stats-grid { gap: 20px; margin-bottom: 20px; grid-template-columns: repeat(auto-fit, minmax(255px, 1fr)) !important; }
.tnui .stat-card {
  flex-direction: column; align-items: center; justify-content: center; gap: 0;
  text-align: center; min-height: 168px; padding: 20px 16px 18px;
}
.tnui .stat-card > div { display: flex; flex-direction: column; align-items: center; min-width: 0; }
.tnui .stat-card .stat-icon {
  order: 0; width: 60px; height: 60px; border-radius: 50%; margin: 0 0 12px; font-size: 24px;
}
.tnui .stat-card .stat-icon i { font-size: 24px; line-height: 1; }
.tnui .stat-card .stat-value { order: 1; font-size: 23px; font-weight: 600; line-height: 1.15; }
.tnui .stat-card .stat-label {
  order: 2; margin: 3px 0 0; font-size: 12.5px; font-weight: 500; letter-spacing: 0; text-transform: none;
}
.tnui .stat-card .stat-change { order: 3; margin-top: 5px; font-size: 11.5px; }

/* The TN dashboard's own summary cards are a different component from
   .stat-card (np-tn.js emits .tn-stat), which is why the pass above left them
   looking untouched. Same treatment: round chip, title across the top, then
   one column per figure with the number over its label. */
.tnui .tn-stats { gap: 20px; margin-bottom: 20px; grid-template-columns: repeat(auto-fit, minmax(255px, 1fr)); }
.tnui .tn-stat { display: flex; align-items: center; gap: 16px; min-height: 132px; padding: 19px 18px 18px; }
.tnui .tn-stat-ic {
  flex: 0 0 auto; width: 56px; height: 56px; border-radius: 50%;
  display: grid; place-items: center;
}
.tnui .tn-stat-ic svg { width: 26px; height: 26px; }
.tnui .tn-stat-bd { flex: 1; min-width: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px; }
.tnui .tn-stat-t { grid-column: 1 / -1; margin: 0; font-size: 13px; font-weight: 600; color: var(--tn-ink-2); }
.tnui .tn-stat-r { flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 1px; line-height: 1.25; }
.tnui .tn-stat-r b { order: -1; font-size: 20px; font-weight: 600; }
.tnui .tn-stat-r span { font-size: 12px; color: var(--tn-muted); }

/* ── plate with a second line: dashboard greeting and lifted subtitles ─── */
/* The reference's greeting card is tall because the 7-day chart is inside it.
   np-tn.js now emits that chart as #page-content's first child so the generic
   -70px merge above fuses it to the plate — but the plate also carries the
   subtitle line, which sits lower than a heading-only page, so the plate needs
   more bottom padding and the card cannot pull up nearly as far or it would
   cover it. Heading box ends ~43px, subtitle ~68px; the card lands at 89px.
   .tn-has-lead is the same geometry for ordinary pages, where np-tn.js lifts a
   legacy .page-subtitle into the plate instead of letting the hidden
   .page-header swallow it. Both merge selectors are needed because on those
   pages the hidden .page-header is still #page-content's first child. */
.tnui.tn-dash .tn-title, .tnui.tn-has-lead .tn-title { padding-bottom: 96px; }
.tnui.tn-dash #page-content > .card:first-child,
.tnui.tn-has-lead #page-content > .card:first-child,
.tnui.tn-has-lead #page-content > .page-header + .card,
.tnui.tn-has-lead #page-content > .page-header + .panel-box,
.tnui.tn-has-lead #page-content > .page-header + .table-wrap,
.tnui.tn-has-lead #page-content > .page-header + .zp-wrap { margin-top: -50px; }
.tnui .tn-dash-chart { padding-top: 14px; }
.tnui .tn-dash-chart .tn-chart-box { margin-top: 4px; }

/* ── rail: identity block, role pill, support card ───────────────────── */
/* The identity block is closed off by a full-width hairline (measured #e3e2e9
   across the whole 250px rail, at the bottom of the avatar/name/role stack). */
.tnui .tn-me { padding: 20px 14px 15px; border-bottom: 1px solid var(--tn-line); }
.tnui .tn-me-av {
  position: relative; width: 68px; height: 68px; margin: 0 auto 11px;
  font-size: 25px; box-shadow: 0 0 0 3px var(--tn-card), 0 0 0 4px var(--tn-line);
}
/* the source flags the avatar with a red dot at the upper right */
.tnui .tn-me-av::after {
  content: ""; position: absolute; top: 1px; right: 1px; width: 13px; height: 13px;
  border-radius: 50%; background: var(--tn-red-fill); border: 2px solid var(--tn-card);
}
/* When the identity disc carries a portrait instead of the monogram, the image
   bleeds to the circle's edge (both discs already clip with overflow: hidden)
   and sits a little high so the face, not the shoulders, owns the crop. The
   notification dot is a later box in the same stacking context, so it still
   paints above the photo without needing a z-index. The header's account tile
   is a rounded square rather than a disc, but it takes the same treatment. */
.tnui .tn-me-av img, .tnui .tn-avatar img {
  display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 22%;
}
/* the My Profile card's 80px disc takes the same portrait; it is the only
   avatar in the page body, and the glyph it replaces is removed by np-pro.js
   once the image has decoded */
.tnui .tn-pav { overflow: hidden; }
.tnui .tn-pav img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 22%; }
.tnui .tn-me-name { font-size: 14.5px; font-weight: 700; color: var(--tn-ink); }
/* role reads as the source's red pill, not as grey small-caps. The pill's own
   red is a shade deeper than the Logout button's (#e66868 vs #e97777), so it
   gets its own value rather than borrowing the action-button token. The source
   writes it in white, which measures 3.21:1 at 11px; every other fill chip in
   this system already uses --tn-on-fill for that reason. The ink is hard-coded
   rather than tokenised because the pill's coral is a fixed mid-tone in both
   themes, while --tn-on-fill flips to near-white in dark mode (3.21:1 again).
   #14121F on #E66868 is 5.74:1 in either theme. */
.tnui .tn-me-role {
  display: inline-block; margin-top: 7px; padding: 3px 13px; border-radius: 50px;
  background: #E66868; color: #14121F; font-size: 11px; font-weight: 700;
  letter-spacing: .2px; text-transform: none;
}

/* The reference's Support Contact block is a phone number, not a link. Only the
   admin panel actually has a Support Tickets page, so the other four ship this
   as a plain <div class="tn-sup">; the pointer affordances therefore hang off
   the anchor, and the static card must not invite a click it cannot honour. */
.tnui .tn-sup {
  display: flex; align-items: center; gap: 11px; margin: auto 12px 14px; padding: 11px 13px;
  border-radius: 10px; background: var(--tn-support-bg); text-decoration: none;
}
.tnui a.tn-sup { cursor: pointer; transition: background var(--tn-in) var(--tn-ease-io), transform var(--tn-in) var(--tn-ease); }
.tnui a.tn-sup:hover { background: var(--tn-tint-bg); }
.tnui a.tn-sup:active { transform: scale(.985); }
.tnui .tn-sup-ic {
  flex: 0 0 auto; display: grid; place-items: center; width: 30px; height: 30px;
  border-radius: 50%; background: var(--tn-tint-bg); color: var(--tn-primary);
}
.tnui a.tn-sup:hover .tn-sup-ic { background: var(--tn-card); }
.tnui .tn-sup-ic svg { width: 15px; height: 15px; }
.tnui .tn-sup-tx { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.tnui .tn-sup-tx b { font-size: 12px; font-weight: 700; color: var(--tn-primary); }
.tnui .tn-sup-no { font-size: 12.5px; font-weight: 600; color: var(--tn-primary); font-variant-numeric: tabular-nums; }
/* the rail has to be a flex column for the card to pin itself to the foot */
.tnui .tn-drawer { display: flex; flex-direction: column; }
.tnui .tn-nav { flex: 1 1 auto; }

/* ── narrow screens: keep the overlap, shrink the indigo field ───────── */
/* np-pro.css loads last, so the shell numbers above also win over np-tn.css's
   own max-width:767px block (media queries add no specificity). Restate them
   here on the same breakpoint, or a phone gets a 134px band of empty indigo. */
@media (max-width: 767px) {
  .tnui .tn-band { padding: 12px 14px 40px; }
  .tnui .tn-main { margin-top: -24px; padding: 0 12px 20px; }
  .tnui .tn-title { margin: 0 0 14px; padding: 15px 15px 15px; font-size: 17px; }
  .tnui .tn-lead { display: none; }
  /* .tn-top is a flex row and .tn-brand defaulted to flex-shrink:1, so on a
     phone the tool cluster (min-width:auto, it cannot compress) squeezed the
     brand down to 51px and the 76px logo spilled out of it, straight under the
     clock. Pin the brand to its content and drop the clock — the source is a
     desktop-only panel and has no narrow layout to copy. */
  .tnui .tn-brand { flex: 0 0 auto; }
  .tnui .tn-clock { display: none; }
  .tnui #page-content > .card:first-child,
  .tnui #page-content > .panel-box:first-child,
  .tnui #page-content > .table-wrap:first-child,
  .tnui #page-content > .zp-wrap:first-child,
  .tnui #page-content > .page-header + .card,
  .tnui #page-content > .page-header + .panel-box,
  .tnui #page-content > .page-header + .table-wrap,
  .tnui #page-content > .page-header + .zp-wrap,
  .tnui.tn-dash #page-content > .card:first-child,
  .tnui.tn-has-lead #page-content > .card:first-child,
  .tnui.tn-has-lead #page-content > .page-header + .card,
  .tnui.tn-has-lead #page-content > .page-header + .panel-box,
  .tnui.tn-has-lead #page-content > .page-header + .table-wrap,
  .tnui.tn-has-lead #page-content > .page-header + .zp-wrap,
  .tnui #page-content > .tn-merge,
  .tnui.tn-has-lead #page-content > .tn-merge {
    margin-top: 0; border-top-left-radius: var(--tn-radius);
    border-top-right-radius: var(--tn-radius); box-shadow: var(--tn-shadow);
  }
  .tnui .page-content .stats-grid, .tnui .tn-stats { gap: 14px; margin-bottom: 14px; }
  .tnui .stat-card { min-height: 150px; }
  /* Wide tables scroll horizontally on phones, and a centred empty cell centres
     itself across the whole scroll width — so only its first character stayed on
     screen. Left-aligning the label and pulling the disc off its auto-centring
     keeps the whole message inside the visible part of the table. */
  .tnui td.empty-state { text-align: left; }
  .tnui td.empty-state i { margin-left: 0; }
  /* The plate carries 96px of bottom padding when it has a second line so the
     merged card can tuck in under it. On phones the lead is hidden and the
     merge is switched off above, so that padding would just be a dead white
     band — and .tnui.tn-dash / .tnui.tn-has-lead outrank the plain .tn-title
     reset two rules up, which is why they need restating here too. */
  .tnui.tn-dash .tn-title, .tnui.tn-has-lead .tn-title { padding: 15px; }
}

/* ── profile dropdown: the source's three coloured actions ───────────── */
.tnui .tn-prof a:nth-of-type(1) { background: var(--tn-green-fill); color: var(--tn-on-fill); }
.tnui .tn-prof a:nth-of-type(2) { background: var(--tn-gold-fill); color: var(--tn-on-fill); }
.tnui .tn-prof a:nth-of-type(3) { background: var(--tn-red-fill); color: var(--tn-on-fill); }
.tnui .tn-prof a {
  margin: 0 14px 9px; padding: 9px 12px; border-radius: 6px; font-weight: 700;
  justify-content: center;
}
.tnui .tn-prof a svg { display: none; }
.tnui .tn-prof a:last-child { margin-bottom: 14px; }

/* ══════════════════════════════════════════════════════════════════════
   4d. ICON SYSTEM
   Two families ship in the panel at once: Font Awesome, behind the ~650
   glyph slots in the legacy markup and the ZP table kit, and the inline
   stroke SVGs the shell builds. They only read as one design if they share
   an optical size, a baseline and a colour rule, so both are normalised
   here rather than page by page:
     • one em tall — a glyph never out-shouts the label beside it
     • dropped 0.125em below the baseline, which is where Plus Jakarta Sans
       needs them; Font Awesome's own centre line is tuned for a taller face
     • one stroke weight across the shell's SVGs, so a header glyph and a
       menu glyph are the same drawing
     • colour inherited, so an icon takes the tint of its own control
   Sizes the markup asks for deliberately (inline font-size) still win, and
   the larger stat glyphs keep their lighter 1.7 stroke on purpose: optical
   weight, not numeric weight, is what has to match.
   ══════════════════════════════════════════════════════════════════════ */
.tnui { --tn-ico-stroke: 1.75; }
.tnui i.fas, .tnui i.far, .tnui i.fab, .tnui i.fal,
.tnui i.fa-solid, .tnui i.fa-regular, .tnui i.fa-brands {
  font-size: 1em; line-height: 1; vertical-align: -.125em;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
.tnui i.fas, .tnui i.fa-solid { font-weight: 900; }
.tnui i.far, .tnui i.fa-regular { font-weight: 400; }
.tnui .btn > i, .tnui .btn > svg, .tnui .tn-btn > i, .tnui .tn-tool > svg { color: inherit; }
.tnui .tn-top svg, .tnui .tn-item svg.ic, .tnui .tn-sup-ic svg { stroke-width: var(--tn-ico-stroke); }

/* Sub-rows were the one place the menu carried no glyph at all — a "»" character
   standing in the icon column, i.e. typography doing an icon's job. A dot on the
   same 23px column keeps the hierarchy and leaves the column honest. */
.tnui .tn-sub::before {
  content: ""; width: 23px; height: 23px; flex-shrink: 0; font-size: 0;
  background: radial-gradient(circle, currentColor 1.9px, transparent 2.2px) center / 23px 23px no-repeat;
}

/* Quick Links: the launcher's tiles use the same tinted-disc glyph as the empty
   states, so the two surfaces that list "what can I do here" speak one language. */
.tnui .tn-apps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.tnui .tn-app {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 9px;
  height: 82px; padding: 10px 8px; border: 1px solid var(--tn-line); border-radius: var(--tn-radius-sm);
  background: var(--tn-card); color: var(--tn-ink); font-size: 12.5px; font-weight: 600;
  text-align: center; cursor: pointer; font-family: inherit; line-height: 1.25;
  transition: background var(--tn-in) var(--tn-ease-io), border-color var(--tn-in) var(--tn-ease-io),
              transform var(--tn-in) var(--tn-ease), box-shadow var(--tn-in) var(--tn-ease);
}
.tnui .tn-app:hover { background: var(--tn-tint-bg); border-color: var(--tn-tint-line, var(--tn-line)); transform: translateY(-1px); box-shadow: var(--tn-shadow); }
.tnui .tn-app:active { transform: translateY(0) scale(.99); }
.tnui .tn-app i {
  display: grid; place-items: center; box-sizing: border-box;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--tn-tint-bg); color: var(--tn-tint-ink); font-size: 14px; line-height: 1;
}
.tnui .tn-app:hover i { background: var(--tn-card); }
@media (max-width: 560px) { .tnui .tn-apps { grid-template-columns: repeat(2, 1fr); } }

/* ── glyph utilities ─────────────────────────────────────────────────────
   The legacy pages styled each <i> inline — colour, size, margin, sometimes a
   raw hex like #A8D35E. That is why one screen could show the same icon at four
   sizes and in colours that ignore the theme. The intent stays, but through the
   token layer: a state colour, a size step and a leading gap are classes, and a
   page that hard-codes one of them now reads as the same design as the rest. */
.tnui .tni-brand { color: var(--tn-tint-ink); }
.tnui .tni-ok { color: var(--tn-ok-ink); }
.tnui .tni-bad { color: var(--tn-bad-ink); }
.tnui .tni-warn { color: var(--tn-warn-ink); }
.tnui .tni-mut { color: var(--tn-muted); }
.tnui .tni-gap { margin-right: 6px; }
.tnui .tni-faint { opacity: .45; }
.tnui .tni-xs { font-size: 10px; }
.tnui .tni-sm { font-size: 12px; }
.tnui .tni-md { font-size: 14px; }
.tnui .tni-lg { font-size: 22px; }
.tnui .tni-xl { font-size: 30px; }
/* a card whose only job is to explain what to do next: the old markup built this
   out of inline padding plus a 40px glyph, so nothing about it matched §4c */
.tnui .tn-note { text-align: center; padding: 36px 24px; }
.tnui .tn-note > i { font-size: 30px; margin-bottom: 14px; }
.tnui .tn-note p { max-width: 58ch; margin: 0 auto 16px; color: var(--tn-muted); font-size: 13px; }

/* ══════════════════════════════════════════════════════════════════════
   5. ACCESSIBILITY / OVERRIDES
   ══════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .tnui *, .tnui *::before, .tnui *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
  /* with motion off, revealed content must already be visible */
  .tnui .tn-rv { opacity: 1; transform: none; }
}
@media (hover: none) {
  /* touch: no hover-only affordances, restore the press state instead */
  .tnui .card:hover, .tnui .stat-card:hover { box-shadow: var(--tn-elev-1); }
  .tnui .zp-table tbody tr:hover td, .tnui table.dt tbody tr:hover td,
  .tnui .table-wrap > table:not(.zp-table) tbody tr:hover td { background: var(--tn-stripe); }
  .tnui .tn-fab:hover, .tnui .nx-ai-fab:hover { transform: none; }
}

/* thin, brand-tinted scrollbars on the surfaces that scroll */
.tnui ::-webkit-scrollbar { width: 10px; height: 10px; }
.tnui ::-webkit-scrollbar-track { background: transparent; }
.tnui ::-webkit-scrollbar-thumb { background: var(--tn-line); border-radius: 8px; border: 2px solid transparent; background-clip: content-box; }
.tnui ::-webkit-scrollbar-thumb:hover { background: var(--tn-muted); background-clip: content-box; }

/* selection */
.tnui ::selection { background: var(--tn-lime); color: var(--tn-on-lime); }

/* high-contrast mode: drop the decorative layers, keep the structure */
@media (forced-colors: active) {
  .tnui .tn-band::after { display: none; }
  .tnui .card, .tnui .stat-panel, .tnui .stat-card { border: 1px solid CanvasText; }
  .tnui :focus-visible { outline: 3px solid Highlight; }
}
