/* ---------------------------------------------------------------------------
   NarneTech WaaS design system.

   Bootstrap 5 does the layout, grid, components and utilities (CLAUDE.md §2).
   This file only does what Bootstrap cannot express on its own: the brand
   token set, the type scale, the focus treatment, and the application shell
   (fixed sidebar + top bar) that no Bootstrap component covers.

   Everything is a custom property so a later phase can reskin without hunting
   through selectors, and so Bootstrap's own --bs-* variables can be pointed at
   the same values rather than duplicated.
   --------------------------------------------------------------------------- */

:root {
    /* Brand. WhatsApp-adjacent green, darkened for contrast: --nt-brand on
       white is ~5.9:1, so brand-coloured text and outline buttons stay AA. The
       bright WhatsApp green is kept for accents only, never for text. */
    --nt-brand-050: #eaf7f2;
    --nt-brand-100: #cdeade;
    --nt-brand-200: #97d7bd;
    --nt-brand-400: #25d366;
    --nt-brand: #0b7a5c;
    --nt-brand-600: #096a50;
    --nt-brand-700: #075440;
    --nt-brand-contrast: #ffffff;

    /* Surfaces */
    --nt-canvas: #f4f6f7;
    --nt-surface: #ffffff;
    --nt-surface-2: #f8fafa;
    --nt-sidebar: #0d2b25;
    --nt-sidebar-2: #123a31;
    --nt-sidebar-text: #cfe3dc;
    --nt-sidebar-text-strong: #ffffff;

    /* Text — --nt-text on --nt-surface is 15.9:1, --nt-text-muted is 5.4:1.
       --nt-text-faint is measured against the DARKEST surface it is ever set on, not white. It
       colours .nt-bubble-kind, which sits on an outbound bubble (--nt-brand-050, #eaf7f2) and on a
       failed one (--nt-danger-tint, #fbeceb). The old #66746f came to 4.45:1 and 4.26:1 there and
       missed AA on both. #616f6a is 4.78:1, 4.59:1, and 5.26:1 on white. */
    --nt-text: #14201d;
    --nt-text-muted: #5c6b66;
    --nt-text-faint: #616f6a;

    /* Lines */
    --nt-border: #dde5e2;
    --nt-border-strong: #c3d0cb;

    /* Status. Each has a tint for backgrounds and a darker ink for text on it. */
    --nt-success: #0b7a5c;
    --nt-success-tint: #e7f5ef;
    --nt-warning: #8a5300;
    --nt-warning-tint: #fdf3e2;
    --nt-danger: #a3231f;
    --nt-danger-tint: #fbeceb;
    --nt-info: #1d5c8f;
    --nt-info-tint: #e9f2f9;

    /* Spacing scale (4px base) */
    --nt-space-1: 0.25rem;
    --nt-space-2: 0.5rem;
    --nt-space-3: 0.75rem;
    --nt-space-4: 1rem;
    --nt-space-5: 1.5rem;
    --nt-space-6: 2rem;
    --nt-space-7: 3rem;

    /* Radius */
    --nt-radius-sm: 6px;
    --nt-radius: 10px;
    --nt-radius-lg: 16px;
    --nt-radius-pill: 999px;

    /* Shadows — low, wide and cool, so cards lift off the canvas without glare. */
    --nt-shadow-sm: 0 1px 2px rgba(16, 34, 29, 0.06), 0 1px 3px rgba(16, 34, 29, 0.04);
    --nt-shadow: 0 2px 4px rgba(16, 34, 29, 0.05), 0 6px 16px rgba(16, 34, 29, 0.07);
    --nt-shadow-lg: 0 8px 24px rgba(16, 34, 29, 0.12);

    /* Type scale — 1.2 minor third off a 1rem base. */
    --nt-font: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
    --nt-font-mono: "Cascadia Mono", ui-monospace, SFMono-Regular, Consolas, monospace;
    --nt-text-xs: 0.75rem;
    --nt-text-sm: 0.8125rem;
    --nt-text-base: 0.9375rem;
    --nt-text-md: 1.0625rem;
    --nt-text-lg: 1.25rem;
    --nt-text-xl: 1.5rem;
    --nt-text-2xl: 1.875rem;
    --nt-text-3xl: 2.25rem;

    --nt-sidebar-width: 248px;
    --nt-topbar-height: 60px;

    /* Point Bootstrap's own tokens at the brand so .btn-primary, .text-primary,
       .form-control:focus and friends inherit it instead of Bootstrap blue. */
    --bs-primary: var(--nt-brand);
    --bs-primary-rgb: 11, 122, 92;
    --bs-link-color: var(--nt-brand-600);
    --bs-link-color-rgb: 9, 106, 80;
    --bs-link-hover-color: var(--nt-brand-700);
    --bs-body-color: var(--nt-text);
    --bs-body-bg: var(--nt-canvas);
    --bs-border-color: var(--nt-border);
    --bs-border-radius: var(--nt-radius);
    --bs-font-sans-serif: var(--nt-font);
}

/* Bootstrap's stock outline-button ink fails AA on this product's page background. Bootstrap picks
   its greys and reds against pure white; every screen here sits on --nt-canvas (#f4f6f7) or a card
   tint, which costs about 0.35 of a ratio point and drops both below 4.5:1 — #6c757d came to 4.33:1
   ("Tags", "Segments", "Sync from Meta") and #dc3545 to 4.18:1 ("Cancel", "Delete"). These are
   button labels, not decoration, so they are darkened here rather than left to the framework.
   Only the resting ink moves: the hover state fills the button and prints white, which already
   passes, so the buttons still read as the same grey and the same red. */
.btn-outline-secondary {
    --bs-btn-color: #5c636a;
    --bs-btn-disabled-color: #5c636a;
}

.btn-outline-danger {
    --bs-btn-color: #c22c3b;
    --bs-btn-disabled-color: #c22c3b;
}

/* --------------------------------------------------------------- base type */

html {
    font-size: 16px;
}

body {
    background-color: var(--nt-canvas);
    color: var(--nt-text);
    font-family: var(--nt-font);
    font-size: var(--nt-text-base);
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
}

h1, .h1 { font-size: var(--nt-text-2xl); }
h2, .h2 { font-size: var(--nt-text-xl); }
h3, .h3 { font-size: var(--nt-text-lg); }
h4, .h4 { font-size: var(--nt-text-md); }

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    font-weight: 650;
    letter-spacing: -0.012em;
    color: var(--nt-text);
}

/* overflow-wrap:anywhere because a page title can be tenant-supplied — a campaign name is 120 free
   characters and need not contain a space. Without it one pasted URL scrolls the whole page. */
.nt-page-title {
    font-size: var(--nt-text-xl);
    margin-bottom: var(--nt-space-1);
    min-width: 0;
    overflow-wrap: anywhere;
}

.nt-page-lede {
    color: var(--nt-text-muted);
    font-size: var(--nt-text-base);
    margin-bottom: 0;
    max-width: 62ch;
}

.nt-eyebrow {
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--nt-text-faint);
}

.nt-muted { color: var(--nt-text-muted); }
.nt-mono { font-family: var(--nt-font-mono); }

/* ---------------------------------------------------------------- focus ring
   One visible ring everywhere, keyboard-only, and never removed. Two layers so
   it reads on both light surfaces and the dark sidebar. */

:focus-visible {
    outline: 2px solid var(--nt-brand-400);
    outline-offset: 2px;
    border-radius: var(--nt-radius-sm);
}

.btn:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible,
.form-check-input:focus-visible {
    outline: 2px solid var(--nt-brand-400);
    outline-offset: 2px;
    box-shadow: 0 0 0 4px var(--nt-brand-100);
}

.form-control:focus,
.form-select:focus {
    border-color: var(--nt-brand-200);
    box-shadow: 0 0 0 3px var(--nt-brand-100);
}

/* Skip link — first thing in the tab order, invisible until focused. */
.nt-skip-link {
    position: absolute;
    left: var(--nt-space-3);
    top: -4rem;
    z-index: 1080;
    background: var(--nt-surface);
    color: var(--nt-brand-700);
    padding: var(--nt-space-2) var(--nt-space-4);
    border-radius: var(--nt-radius);
    box-shadow: var(--nt-shadow-lg);
    font-weight: 600;
    transition: top 120ms ease-out;
}

.nt-skip-link:focus {
    top: var(--nt-space-3);
}

/* ------------------------------------------------------------------ controls */

.btn {
    --bs-btn-font-weight: 600;
    --bs-btn-border-radius: var(--nt-radius-sm);
    font-size: var(--nt-text-base);
}

.btn-primary {
    --bs-btn-bg: var(--nt-brand);
    --bs-btn-border-color: var(--nt-brand);
    --bs-btn-hover-bg: var(--nt-brand-600);
    --bs-btn-hover-border-color: var(--nt-brand-600);
    --bs-btn-active-bg: var(--nt-brand-700);
    --bs-btn-active-border-color: var(--nt-brand-700);
    --bs-btn-disabled-bg: var(--nt-brand-200);
    --bs-btn-disabled-border-color: var(--nt-brand-200);
    --bs-btn-color: var(--nt-brand-contrast);
    --bs-btn-hover-color: var(--nt-brand-contrast);
    --bs-btn-active-color: var(--nt-brand-contrast);
}

.btn-outline-primary {
    --bs-btn-color: var(--nt-brand-600);
    --bs-btn-border-color: var(--nt-border-strong);
    --bs-btn-hover-bg: var(--nt-brand-050);
    --bs-btn-hover-border-color: var(--nt-brand);
    --bs-btn-hover-color: var(--nt-brand-700);
    --bs-btn-active-bg: var(--nt-brand-100);
    --bs-btn-active-border-color: var(--nt-brand);
    --bs-btn-active-color: var(--nt-brand-700);
}

.form-label {
    font-size: var(--nt-text-sm);
    font-weight: 600;
    color: var(--nt-text);
    margin-bottom: var(--nt-space-1);
}

.form-control, .form-select {
    border-color: var(--nt-border-strong);
    border-radius: var(--nt-radius-sm);
    font-size: var(--nt-text-base);
    padding: 0.5rem 0.75rem;
}

.form-text, .nt-hint {
    color: var(--nt-text-muted);
    font-size: var(--nt-text-sm);
}

/* A hint that is a caution, not a note. Weight and wording carry it as well as hue. */
.nt-hint-warning {
    color: var(--nt-warning);
    font-weight: 600;
}

/* ------------------------------------------------------------------- cards */

.nt-card {
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow-sm);
}

.nt-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-3);
    padding: var(--nt-space-4) var(--nt-space-5);
    border-bottom: 1px solid var(--nt-border);
}

.nt-card-title {
    font-size: var(--nt-text-md);
    font-weight: 650;
    margin: 0;
}

.nt-card-body {
    padding: var(--nt-space-5);
}

/* Bootstrap cards used elsewhere in the app inherit the same shape. */
.card {
    border-color: var(--nt-border);
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow-sm);
}

.card-header {
    background: var(--nt-surface);
    border-bottom-color: var(--nt-border);
    font-weight: 600;
}

/* --------------------------------------------------------------- stat tiles */

.nt-stat {
    display: flex;
    flex-direction: column;
    gap: var(--nt-space-1);
    height: 100%;
    padding: var(--nt-space-5);
}

.nt-stat-label {
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--nt-text-faint);
}

.nt-stat-value {
    font-size: var(--nt-text-2xl);
    font-weight: 680;
    letter-spacing: -0.02em;
    line-height: 1.15;
}

.nt-stat-note {
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

/* -------------------------------------------------------------- status pills
   Never colour alone: every pill carries its own words, and the icon is
   aria-hidden so a screen reader hears the label, not the glyph. */

.nt-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--nt-space-1);
    padding: 0.15rem 0.6rem;
    border-radius: var(--nt-radius-pill);
    font-size: var(--nt-text-xs);
    font-weight: 650;
    line-height: 1.6;
    border: 1px solid transparent;
    white-space: nowrap;
}

.nt-pill-success { background: var(--nt-success-tint); color: var(--nt-success); border-color: #bfe3d5; }
.nt-pill-warning { background: var(--nt-warning-tint); color: var(--nt-warning); border-color: #f0dcb6; }
.nt-pill-danger  { background: var(--nt-danger-tint);  color: var(--nt-danger);  border-color: #f0c7c5; }
.nt-pill-info    { background: var(--nt-info-tint);    color: var(--nt-info);    border-color: #c7ddee; }
.nt-pill-neutral { background: var(--nt-surface-2);    color: var(--nt-text-muted); border-color: var(--nt-border); }

/* -------------------------------------------------------------- empty state */

.nt-empty {
    text-align: center;
    padding: var(--nt-space-7) var(--nt-space-5);
    max-width: 46rem;
    margin-inline: auto;
}

.nt-empty-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3.5rem;
    height: 3.5rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-brand-050);
    color: var(--nt-brand);
    font-size: var(--nt-text-xl);
    margin-bottom: var(--nt-space-4);
}

.nt-empty-title {
    font-size: var(--nt-text-lg);
    margin-bottom: var(--nt-space-2);
}

.nt-empty-body {
    color: var(--nt-text-muted);
    margin-inline: auto;
    max-width: 44ch;
    margin-bottom: var(--nt-space-5);
}

/* ------------------------------------------------------------ notice / alert */

.alert {
    border-radius: var(--nt-radius);
    border-width: 1px;
    font-size: var(--nt-text-base);
}

.nt-notice {
    display: flex;
    gap: var(--nt-space-3);
    align-items: flex-start;
    padding: var(--nt-space-4);
    border-radius: var(--nt-radius);
    border: 1px solid var(--nt-border);
    background: var(--nt-surface);
}

.nt-notice-warning { background: var(--nt-warning-tint); border-color: #f0dcb6; color: var(--nt-warning); }
.nt-notice-danger  { background: var(--nt-danger-tint);  border-color: #f0c7c5; color: var(--nt-danger); }
.nt-notice-success { background: var(--nt-success-tint); border-color: #bfe3d5; color: var(--nt-success); }
.nt-notice-info    { background: var(--nt-info-tint);    border-color: #c7ddee; color: var(--nt-info); }

/* =========================================================== application shell */

.nt-shell {
    min-height: 100vh;
}

/* ---- sidebar ---- */

.nt-sidebar {
    background: var(--nt-sidebar);
    color: var(--nt-sidebar-text);
    width: var(--nt-sidebar-width);
    display: flex;
    flex-direction: column;
}

/* Desktop: the sidebar is a fixed rail and the content is inset by its width. */
@media (min-width: 992px) {
    .nt-sidebar {
        position: fixed;
        inset-block: 0;
        inset-inline-start: 0;
        z-index: 1030;
    }

    /* Only when a sidebar is actually rendered — the signed-out shell has none. */
    .nt-main.has-sidebar {
        margin-inline-start: var(--nt-sidebar-width);
    }
}

/* Below lg the same markup is reused inside a Bootstrap offcanvas drawer. */
.offcanvas .nt-sidebar {
    width: 100%;
    height: 100%;
}

.offcanvas.nt-drawer {
    width: min(84vw, var(--nt-sidebar-width));
    background: var(--nt-sidebar);
}

.nt-brand {
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    padding: var(--nt-space-4) var(--nt-space-4);
    color: var(--nt-sidebar-text-strong);
    text-decoration: none;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.nt-brand:hover, .nt-brand:focus { color: var(--nt-sidebar-text-strong); }

.nt-brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    flex: 0 0 auto;
    border-radius: var(--nt-radius-sm);
    background: var(--nt-brand-400);
    color: #06251c;
    font-size: 1.05rem;
}

.nt-brand-name {
    font-weight: 700;
    letter-spacing: -0.01em;
    line-height: 1.15;
}

.nt-brand-sub {
    display: block;
    font-size: var(--nt-text-xs);
    font-weight: 500;
    color: var(--nt-sidebar-text);
    opacity: 0.75;
}

.nt-nav {
    padding: var(--nt-space-3) var(--nt-space-3);
    overflow-y: auto;
    flex: 1 1 auto;
}

.nt-nav-section {
    padding: var(--nt-space-4) var(--nt-space-3) var(--nt-space-1);
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.55);
}

.nt-nav-link {
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    padding: 0.5rem var(--nt-space-3);
    margin-bottom: 2px;
    border-radius: var(--nt-radius-sm);
    color: var(--nt-sidebar-text);
    text-decoration: none;
    font-weight: 550;
    font-size: var(--nt-text-base);
}

.nt-nav-link .bi {
    font-size: 1.05rem;
    opacity: 0.85;
    flex: 0 0 auto;
}

.nt-nav-link:hover {
    background: var(--nt-sidebar-2);
    color: var(--nt-sidebar-text-strong);
}

.nt-nav-link:focus-visible {
    outline: 2px solid var(--nt-brand-400);
    outline-offset: -2px;
}

/* Active section: a tinted row plus a left marker, so it is not colour alone —
   the link also carries aria-current="page". */
.nt-nav-link.is-active {
    background: var(--nt-brand-600);
    color: #fff;
    box-shadow: inset 3px 0 0 var(--nt-brand-400);
}

.nt-nav-link.is-active .bi { opacity: 1; }

.nt-nav-link.is-pending {
    opacity: 0.6;
    cursor: default;
}

.nt-nav-badge {
    margin-inline-start: auto;
    font-size: 0.625rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 0.05rem 0.4rem;
    border-radius: var(--nt-radius-pill);
    background: rgba(255, 255, 255, 0.14);
    color: rgba(255, 255, 255, 0.9);
}

/* Live count of conversations awaiting a reply. Sits at the end of the Inbox row; a solid,
   high-contrast pill so it reads as an alert, not as the muted "Soon" badge beside it. */
.nt-nav-count {
    margin-inline-start: auto;
    min-width: 1.25rem;
    text-align: center;
    font-size: 0.6875rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
    padding: 0.1rem 0.4rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-brand-400);
    color: #06281c;
}

/* On the active (brand-filled) Inbox row the pill inverts, so it stays legible against the row. */
.nt-nav-link.is-active .nt-nav-count {
    background: #fff;
    color: var(--nt-brand-700);
}

.nt-sidebar-foot {
    padding: var(--nt-space-4);
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    font-size: var(--nt-text-xs);
    color: rgba(255, 255, 255, 0.6);
}

/* ---- top bar ---- */

.nt-topbar {
    position: sticky;
    top: 0;
    z-index: 1020;
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    min-height: var(--nt-topbar-height);
    padding: var(--nt-space-2) var(--nt-space-4);
    background: rgba(255, 255, 255, 0.94);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--nt-border);
}

.nt-tenant {
    min-width: 0;
}

.nt-tenant-name {
    font-weight: 650;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nt-tenant-sub {
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
    line-height: 1.2;
}

.nt-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--nt-space-2);
    padding: 0.3rem 0.7rem;
    border-radius: var(--nt-radius-pill);
    border: 1px solid var(--nt-border-strong);
    background: var(--nt-surface);
    font-size: var(--nt-text-sm);
    font-weight: 600;
    color: var(--nt-text);
    text-decoration: none;
    white-space: nowrap;
}

.nt-chip:hover { border-color: var(--nt-brand); color: var(--nt-brand-700); }

.nt-chip-low {
    background: var(--nt-warning-tint);
    border-color: #e6cb96;
    color: var(--nt-warning);
}

.nt-chip-unknown { color: var(--nt-text-muted); }

/* The chip stops claiming to be current when its live connection has gone. The dashed border is a
   hint; the words beside the amount are the message, so nothing here depends on seeing a colour. */
.nt-chip-stale-mark { display: none; }

.nt-chip-stale {
    border-style: dashed;
    border-color: var(--nt-border-strong);
}

.nt-chip-stale .nt-chip-stale-mark {
    display: inline;
    font-weight: 500;
    color: var(--nt-text-muted);
}

.nt-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    flex: 0 0 auto;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-brand-100);
    color: var(--nt-brand-700);
    font-weight: 700;
    font-size: var(--nt-text-sm);
}

.nt-content {
    padding: var(--nt-space-5) var(--nt-space-4) var(--nt-space-7);
}

@media (min-width: 768px) {
    .nt-content { padding: var(--nt-space-6) var(--nt-space-6) var(--nt-space-7); }
}

/* Fluid by default so data-heavy screens — dashboards, tenant tables, campaign and inbox reports —
   fill the whole window rather than sitting in a 1240px column. Individual narrow forms keep their own
   max-width on the elements inside, so they do not stretch awkwardly. A very wide cap keeps text-only
   pages from running edge-to-edge on ultra-wide monitors. */
.nt-content-inner {
    max-width: 2200px;
    margin-inline: auto;
}

.nt-page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--nt-space-3);
    margin-bottom: var(--nt-space-5);
}

/* Impersonation is a dangerous mode; it gets a permanent, unmissable banner. */
.nt-impersonation-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--nt-space-3);
    padding: var(--nt-space-2) var(--nt-space-4);
    background: var(--nt-warning-tint);
    border-bottom: 1px solid #e6cb96;
    color: var(--nt-warning);
    font-size: var(--nt-text-sm);
    font-weight: 600;
}

/* ---- platform realm: same shell, different skin, so you always know where you are ---- */

.nt-shell-platform {
    --nt-sidebar: #16202b;
    --nt-sidebar-2: #1f2d3c;
    --nt-brand-400: #6aa9ff;
    --nt-brand-600: #1f4f8f;
}

/* ============================================================== auth screens */

.nt-auth {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--nt-space-5) var(--nt-space-4);
    background:
        radial-gradient(1100px 520px at 50% -10%, var(--nt-brand-050), transparent 70%),
        var(--nt-canvas);
}

.nt-auth-card {
    width: 100%;
    max-width: 26rem;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow);
    padding: var(--nt-space-6);
}

.nt-auth-brand {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--nt-space-3);
    margin-bottom: var(--nt-space-5);
    color: var(--nt-text);
    text-decoration: none;
}

.nt-auth-brand .nt-brand-mark {
    background: var(--nt-brand);
    color: #fff;
}

.nt-auth-realm {
    display: inline-flex;
    align-items: center;
    gap: var(--nt-space-2);
    padding: 0.2rem 0.7rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-brand-050);
    color: var(--nt-brand-700);
    border: 1px solid var(--nt-brand-100);
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.nt-auth-title {
    font-size: var(--nt-text-lg);
    margin: var(--nt-space-3) 0 var(--nt-space-1);
}

.nt-auth-sub {
    color: var(--nt-text-muted);
    font-size: var(--nt-text-sm);
    margin-bottom: var(--nt-space-5);
}

.nt-auth-switch {
    margin-top: var(--nt-space-5);
    padding-top: var(--nt-space-4);
    border-top: 1px solid var(--nt-border);
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

.nt-auth-foot {
    margin-top: var(--nt-space-5);
    font-size: var(--nt-text-xs);
    color: var(--nt-text-faint);
    text-align: center;
}

/* The platform tenant "danger zone" — a destructive-action card set off by a red edge. */
.nt-danger-zone {
    border-color: var(--nt-danger);
    box-shadow: inset 3px 0 0 var(--nt-danger);
}

/* -------------------------------------------------------------- modals
   Every app modal gets a scale-and-rise entrance for a bit of delight, replacing Bootstrap's plain
   slide. The shared confirmation modal (site.js) adds a popping icon badge. Reduced-motion users get
   the modal with no transform. */
.modal.fade .modal-dialog {
    transform: scale(0.94) translateY(16px);
    transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.22s ease;
}
.modal.show .modal-dialog { transform: none; }

.modal-content {
    border: 0;
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow-lg);
}
.modal-backdrop.show { opacity: 0.55; }

.nt-confirm-modal .modal-header {
    align-items: center;
    gap: var(--nt-space-3);
    border-bottom: 0;
    padding-bottom: 0;
}
.nt-confirm-modal .modal-title { margin: 0 auto 0 0; }
.nt-confirm-modal .modal-body {
    padding-top: var(--nt-space-3);
    color: var(--nt-text-muted);
    line-height: 1.6;
}
.nt-confirm-modal .modal-footer { border-top: 0; padding-top: var(--nt-space-2); }

.nt-confirm-icon {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: var(--nt-danger-tint);
    color: var(--nt-danger);
    font-size: 1.15rem;
}

@media (prefers-reduced-motion: no-preference) {
    .nt-confirm-modal.show .nt-confirm-icon {
        animation: ntConfirmIconPop 0.45s 0.08s cubic-bezier(0.34, 1.56, 0.64, 1) both;
    }
    @keyframes ntConfirmIconPop {
        from { transform: scale(0); }
        to   { transform: scale(1); }
    }
}

@media (prefers-reduced-motion: reduce) {
    .modal.fade .modal-dialog { transition: none; transform: none; }
}

/* -------------------------------------------------------------- toasts
   A clean surface toast with a coloured left accent and matching icon per kind (success / info /
   danger). Success and info slide in from the right and auto-dismiss (see site.js). */
@keyframes ntToastIn {
    from { opacity: 0; transform: translateX(24px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}
@keyframes ntNoticeIn {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
}

.nt-toast-container .toast {
    background: var(--nt-surface);
    color: var(--nt-text);
    border: 1px solid var(--nt-border);
    border-left-width: 3px;
    border-radius: var(--nt-radius);
    box-shadow: var(--nt-shadow);
}
.nt-toast-container .toast.nt-toast-success { border-left-color: var(--nt-success); }
.nt-toast-container .toast.nt-toast-danger  { border-left-color: var(--nt-danger); }
.nt-toast-container .toast.nt-toast-info    { border-left-color: var(--nt-info); }

.nt-toast-icon { font-size: 1.05rem; padding: 0.8rem 0 0 0.85rem; }
.nt-toast-success .nt-toast-icon { color: var(--nt-success); }
.nt-toast-danger  .nt-toast-icon { color: var(--nt-danger); }
.nt-toast-info    .nt-toast-icon { color: var(--nt-info); }

@media (prefers-reduced-motion: no-preference) {
    .nt-toast-container .toast { animation: ntToastIn 0.35s cubic-bezier(0.16, 1, 0.3, 1); }
    /* Success/notice banners rendered after an action pop gently into view. */
    .nt-notice { animation: ntNoticeIn 0.35s ease both; }
}

/* ------------------------------------------------------------- motion / panels
   Entrance cascade and hover life for every panel, gated behind prefers-reduced-motion so it never
   fights a visitor who has asked the OS for less animation. Purely presentational — no layout shift
   once settled, since the cards animate from their final position. */
@media (prefers-reduced-motion: no-preference) {
    @keyframes ntFadeInUp {
        from { opacity: 0; transform: translateY(12px); }
        to   { opacity: 1; transform: translateY(0); }
    }
    @keyframes ntFadeIn {
        from { opacity: 0; }
        to   { opacity: 1; }
    }
    @keyframes ntPop {
        0%   { opacity: 0; transform: scale(0.96); }
        60%  { opacity: 1; transform: scale(1.015); }
        100% { opacity: 1; transform: scale(1); }
    }

    .nt-page-head { animation: ntFadeIn 0.4s ease both; }

    /* Every card fades up into place on load. */
    .nt-card { animation: ntFadeInUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; }

    /* KPI tiles get a little pop instead, and their number counts in. */
    .nt-card.nt-stat { animation: ntPop 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; }
    .nt-stat-value { animation: ntFadeInUp 0.6s 0.15s cubic-bezier(0.16, 1, 0.3, 1) both; }

    /* Staggered cascade — cards deal out left-to-right across a row, and top-to-bottom down the page. */
    .row > [class*="col"]:nth-child(1) .nt-card,
    .nt-content-inner > .nt-card:nth-of-type(1) { animation-delay: 0.03s; }
    .row > [class*="col"]:nth-child(2) .nt-card,
    .nt-content-inner > .nt-card:nth-of-type(2) { animation-delay: 0.08s; }
    .row > [class*="col"]:nth-child(3) .nt-card,
    .nt-content-inner > .nt-card:nth-of-type(3) { animation-delay: 0.13s; }
    .row > [class*="col"]:nth-child(4) .nt-card,
    .nt-content-inner > .nt-card:nth-of-type(4) { animation-delay: 0.18s; }
    .row > [class*="col"]:nth-child(5) .nt-card,
    .nt-content-inner > .nt-card:nth-of-type(5) { animation-delay: 0.23s; }
    .row > [class*="col"]:nth-child(6) .nt-card,
    .nt-content-inner > .nt-card:nth-of-type(6) { animation-delay: 0.28s; }
    .row > [class*="col"]:nth-child(7) .nt-card { animation-delay: 0.33s; }
    .row > [class*="col"]:nth-child(8) .nt-card { animation-delay: 0.38s; }

    /* Rows of table data ripple in. Capped so a 1,000-row table doesn't animate forever. */
    .nt-table tbody tr { animation: ntFadeIn 0.4s ease both; }
    .nt-table tbody tr:nth-child(1) { animation-delay: 0.04s; }
    .nt-table tbody tr:nth-child(2) { animation-delay: 0.08s; }
    .nt-table tbody tr:nth-child(3) { animation-delay: 0.12s; }
    .nt-table tbody tr:nth-child(4) { animation-delay: 0.16s; }
    .nt-table tbody tr:nth-child(5) { animation-delay: 0.20s; }
    .nt-table tbody tr:nth-child(6) { animation-delay: 0.24s; }
    .nt-table tbody tr:nth-child(n+7) { animation-delay: 0.28s; }
}

/* Hover life works with or without the entrance animation, so it sits outside the media query but
   still yields to reduced-motion by zeroing the transition below. */
.nt-card { transition: transform 0.18s ease, box-shadow 0.18s ease; }
.nt-card.nt-stat:hover {
    transform: translateY(-3px);
    box-shadow: var(--nt-shadow);
}
/* Larger content cards deepen their shadow rather than lift, so a full-width table doesn't jump. */
.nt-card:not(.nt-stat):hover { box-shadow: var(--nt-shadow); }

@media (prefers-reduced-motion: reduce) {
    .nt-card { transition: none; }
    .nt-card.nt-stat:hover { transform: none; }
}

/* "or sign up with email" rule between the social/OTP options and the password form. */
.nt-auth-divider {
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    margin: var(--nt-space-4) 0;
    color: var(--nt-text-faint);
    font-size: var(--nt-text-xs);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.nt-auth-divider::before,
.nt-auth-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--nt-border);
}

/* The bad-credentials block: visible, announced (role="alert" in the markup),
   and not signalled by colour alone — it leads with an icon and a heading. */
.nt-auth-error {
    display: flex;
    gap: var(--nt-space-3);
    align-items: flex-start;
    padding: var(--nt-space-3) var(--nt-space-4);
    margin-bottom: var(--nt-space-4);
    border-radius: var(--nt-radius);
    background: var(--nt-danger-tint);
    border: 1px solid #f0c7c5;
    color: var(--nt-danger);
    font-size: var(--nt-text-sm);
}

.nt-auth-error ul { margin: 0; padding-inline-start: 1.1rem; }

/* ------------------------------------------------------------------- tables */

.nt-table {
    --bs-table-bg: transparent;
    margin-bottom: 0;
}

.nt-table > thead > tr > th {
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--nt-text-faint);
    border-bottom-color: var(--nt-border);
    padding: var(--nt-space-3) var(--nt-space-4);
    background: var(--nt-surface-2);
}

.nt-table > tbody > tr > td {
    padding: var(--nt-space-3) var(--nt-space-4);
    border-bottom-color: var(--nt-border);
    vertical-align: middle;
}

/* ------------------------------------------------------------------ loading */

.nt-skeleton {
    background: linear-gradient(90deg, var(--nt-surface-2) 25%, #eef2f1 37%, var(--nt-surface-2) 63%);
    background-size: 400% 100%;
    border-radius: var(--nt-radius-sm);
    animation: nt-shimmer 1.3s ease-in-out infinite;
}

@keyframes nt-shimmer {
    0% { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}

@media (prefers-reduced-motion: reduce) {
    .nt-skeleton { animation: none; }
}

/* Alpine sets x-cloak until it boots, so nothing flashes unstyled. */
[x-cloak] { display: none !important; }

/* ================================================================= team inbox
   The one screen in the product that is an application pane rather than a
   document: it owns the full height below the top bar, scrolls internally, and
   collapses from two panes to one on a phone. Everything else here is the same
   token set as the rest of the design system. */

/* The inbox opts out of the centred, padded content column. */
.nt-content-flush { padding: 0; }
.nt-content-inner-flush { max-width: none; }

.nt-inbox {
    position: relative;
    display: grid;
    grid-template-columns: 1fr;
    height: calc(100vh - var(--nt-topbar-height));
    /* dvh so a mobile browser's collapsing address bar does not push the composer below the fold.
       The vh line above stays as the fallback for anything that does not know dvh. */
    height: calc(100dvh - var(--nt-topbar-height));
    min-height: 32rem;
    background: var(--nt-surface);
}

.nt-inbox-alert { margin: var(--nt-space-3) var(--nt-space-3) 0; }

/* Phone: exactly one pane at a time. 375px cannot hold a list and a thread, and
   a squeezed thread is unusable. */
@media (max-width: 991.98px) {
    .nt-inbox[data-pane="thread"] .nt-inbox-list { display: none; }
    .nt-inbox[data-pane="list"] .nt-inbox-thread { display: none; }
}

@media (min-width: 992px) {
    .nt-inbox { grid-template-columns: minmax(20rem, 24rem) 1fr; }
}

.nt-inbox-list,
.nt-inbox-thread {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.nt-inbox-list {
    border-right: 1px solid var(--nt-border);
    background: var(--nt-surface);
    overflow-y: auto;
}

.nt-inbox-thread {
    background: var(--nt-surface-2);
}

/* ---- the "this is not live" banner ---- */

.nt-live-banner {
    position: absolute;
    inset-inline: 0;
    top: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--nt-space-2);
    padding: var(--nt-space-2) var(--nt-space-4);
    background: var(--nt-warning-tint);
    border-bottom: 1px solid #e6cb96;
    color: var(--nt-warning);
    font-size: var(--nt-text-sm);
    font-weight: 600;
}

.nt-live-banner[hidden] { display: none; }

/* ---- conversation list ---- */

.nt-conv-head {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--nt-surface);
    border-bottom: 1px solid var(--nt-border);
    padding: var(--nt-space-3) var(--nt-space-4);
}

.nt-conv-search {
    display: flex;
    gap: var(--nt-space-2);
    align-items: center;
}

/* Shared by the inbox conversation search and the contacts/templates filter toolbars. Declared once,
   here, because the toolbar section further down used to redeclare the whole block with logical
   properties instead of physical ones — two rules for one component, where whichever came last
   silently decided the padding. */
.nt-search-field {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
}

.nt-search-field .bi {
    position: absolute;
    inset-inline-start: 0.7rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--nt-text-faint);
    pointer-events: none;
}

.nt-search-field .form-control { padding-inline-start: 2.1rem; }

.nt-conv-filters {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-1);
    margin-top: var(--nt-space-3);
}

.nt-conv-filter {
    padding: 0.15rem 0.7rem;
    border-radius: var(--nt-radius-pill);
    border: 1px solid var(--nt-border);
    background: var(--nt-surface);
    color: var(--nt-text-muted);
    font-size: var(--nt-text-sm);
    font-weight: 600;
    text-decoration: none;
}

.nt-conv-filter:hover { border-color: var(--nt-brand-200); color: var(--nt-brand-700); }

.nt-conv-filter.is-active {
    background: var(--nt-brand-050);
    border-color: var(--nt-brand-200);
    color: var(--nt-brand-700);
}

.nt-conv-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.nt-conv-item {
    display: flex;
    gap: var(--nt-space-3);
    padding: var(--nt-space-3) var(--nt-space-4);
    border-bottom: 1px solid var(--nt-border);
    border-left: 3px solid transparent;
    text-decoration: none;
    color: inherit;
}

.nt-conv-item:hover { background: var(--nt-surface-2); }

.nt-conv-item.is-selected {
    background: var(--nt-brand-050);
    border-left-color: var(--nt-brand);
}

/* "Awaiting reply" emphasis: weight plus a labelled pill, never colour alone. */
.nt-conv-item.is-awaiting .nt-conv-name,
.nt-conv-item.is-awaiting .nt-conv-preview { font-weight: 650; color: var(--nt-text); }

.nt-conv-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.4rem;
    height: 2.4rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-brand-100);
    color: var(--nt-brand-700);
    font-weight: 700;
    font-size: var(--nt-text-sm);
}

.nt-conv-body {
    display: block;
    min-width: 0;
    flex: 1 1 auto;
}

.nt-conv-top {
    display: flex;
    align-items: baseline;
    gap: var(--nt-space-2);
}

.nt-conv-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
}

.nt-conv-time {
    flex: 0 0 auto;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
}

.nt-conv-preview {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--nt-text-muted);
    font-size: var(--nt-text-sm);
    margin-top: 0.1rem;
}

.nt-conv-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-1);
    margin-top: var(--nt-space-2);
}

.nt-conv-state {
    text-align: center;
    padding: var(--nt-space-6) var(--nt-space-4);
}

.nt-conv-more {
    padding: var(--nt-space-3) var(--nt-space-4);
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
    margin: 0;
}

/* ---- thread ---- */

.nt-thread {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.nt-thread-placeholder { margin: auto; }

.nt-thread-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--nt-space-3);
    padding: var(--nt-space-3) var(--nt-space-4);
    background: var(--nt-surface);
    border-bottom: 1px solid var(--nt-border);
}

.nt-thread-who { min-width: 0; margin-right: auto; }

.nt-thread-name {
    font-size: var(--nt-text-md);
    font-weight: 650;
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nt-thread-sub {
    margin: 0;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
}

/* The assign control is a disclosure so it costs one line, not three. On a 375px phone the select
   and its button used to wrap onto rows of their own and the header ate a third of the pane. */
.nt-thread-assign { position: relative; }

.nt-thread-assign-check {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.nt-thread-assign-check:focus-visible + .nt-thread-assign-toggle {
    outline: 2px solid var(--nt-brand);
    outline-offset: 2px;
}

.nt-thread-assign-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--nt-space-2);
    max-width: 11rem;
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-sm);
    background: var(--nt-surface);
    font-size: var(--nt-text-xs);
    white-space: nowrap;
    overflow: hidden;
    cursor: pointer;
    list-style: none;
}

.nt-thread-assign-toggle::-webkit-details-marker { display: none; }

.nt-thread-assign-toggle span {
    overflow: hidden;
    text-overflow: ellipsis;
}

.nt-thread-assign-form {
    display: none;
    position: absolute;
    right: 0;
    z-index: 6;
    gap: var(--nt-space-2);
    align-items: center;
    margin-top: var(--nt-space-2);
    padding: var(--nt-space-3);
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-sm);
    box-shadow: var(--nt-shadow-md, 0 8px 24px rgb(0 0 0 / 12%));
}

.nt-thread-assign-check:checked ~ .nt-thread-assign-form { display: flex; }

.nt-thread-assign-form .form-select { min-width: 9rem; }

/* Desktop has the room: no disclosure, the control simply sits in the header. */
@media (min-width: 992px) {
    .nt-thread-assign-toggle { display: none; }

    .nt-thread-assign-form {
        display: flex;
        position: static;
        margin-top: 0;
        padding: 0;
        border: 0;
        box-shadow: none;
        background: transparent;
    }
}

.nt-thread-alert { margin: var(--nt-space-3) var(--nt-space-4) 0; }

/* Phone: the header is one line. It used to wrap the name, the window pill, the select and the
   Assign button onto rows of their own and take a third of the pane, leaving barely three bubbles
   of conversation on a 375x750 screen. */
@media (max-width: 991.98px) {
    .nt-thread-head {
        flex-wrap: nowrap;
        gap: var(--nt-space-2);
        padding: var(--nt-space-2) var(--nt-space-3);
    }

    .nt-thread-head > .nt-conv-avatar { display: none; }

    .nt-thread-head .nt-pill {
        flex: 0 0 auto;
        max-width: 8rem;
        overflow: hidden;
        white-space: nowrap;
    }

    /* Outside the window the composer carries an explanation, a template picker, a price and a
       balance. On a phone that stack can grow taller than the conversation it sits under, so it is
       capped and scrolls within itself — the messages keep the larger half of the screen. */
    .nt-composer {
        max-height: 45%;
        overflow-y: auto;
    }

    /* The empty locked box is decorative: the notice above it already says why writing is closed.
       The readonly one carrying a returned draft is not, and stays. */
    .nt-composer-locked:disabled { display: none; }
}

.nt-thread-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: var(--nt-space-4);
    display: flex;
    flex-direction: column;
}

.nt-bubbles {
    list-style: none;
    margin: auto auto 0;
    padding: 0;
    width: 100%;
    max-width: 52rem;
}

.nt-day-sep {
    display: flex;
    align-items: center;
    justify-content: center;
    margin: var(--nt-space-4) 0 var(--nt-space-3);
}

.nt-day-sep span {
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-pill);
    padding: 0.1rem 0.7rem;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
}

.nt-bubble-row {
    display: flex;
    margin-bottom: var(--nt-space-2);
}

.nt-bubble-row.is-out { justify-content: flex-end; }

.nt-bubble {
    max-width: min(85%, 34rem);
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    border-bottom-left-radius: var(--nt-radius-sm);
    padding: var(--nt-space-3) var(--nt-space-4);
    box-shadow: var(--nt-shadow-sm);
}

.nt-bubble-row.is-out .nt-bubble {
    background: var(--nt-brand-050);
    border-color: var(--nt-brand-100);
    border-bottom-left-radius: var(--nt-radius-lg);
    border-bottom-right-radius: var(--nt-radius-sm);
}

.nt-bubble.is-failed {
    background: var(--nt-danger-tint);
    border-color: #f0c7c5;
}

.nt-bubble-kind {
    display: flex;
    align-items: center;
    gap: var(--nt-space-1);
    margin: 0 0 var(--nt-space-1);
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--nt-text-faint);
}

/* Sender line breaks are preserved by CSS, not by turning text into markup. */
.nt-bubble-text {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.nt-bubble-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--nt-space-2);
    margin-top: var(--nt-space-1);
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
}

.nt-bubble-error {
    margin: var(--nt-space-2) 0 0;
    font-size: var(--nt-text-sm);
    color: var(--nt-danger);
}

/* Delivery ticks. The word is always present; colour is the redundant signal. */
.nt-tick {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
}

.nt-tick-label { font-size: var(--nt-text-xs); }
.nt-tick-plain { color: var(--nt-text-muted); }
.nt-tick-read { color: var(--nt-info); }
.nt-tick-failed { color: var(--nt-danger); font-weight: 650; }

.nt-typing {
    margin: 0;
    padding: 0 var(--nt-space-4) var(--nt-space-2);
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

.nt-typing[hidden] { display: none; }

/* ---- composer ---- */

.nt-composer {
    background: var(--nt-surface);
    border-top: 1px solid var(--nt-border);
    padding: var(--nt-space-3) var(--nt-space-4) var(--nt-space-4);
}

.nt-composer-form { position: relative; }

.nt-composer-field { position: relative; }

.nt-composer-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-3);
    margin-top: var(--nt-space-2);
}

.nt-composer-locked {
    background: var(--nt-surface-2);
    margin-top: var(--nt-space-3);
}

.nt-composer-warn {
    display: flex;
    align-items: flex-start;
    gap: var(--nt-space-2);
    margin: var(--nt-space-2) 0 0;
    font-size: var(--nt-text-sm);
    color: var(--nt-warning);
}

.nt-qr-list {
    position: absolute;
    bottom: calc(100% + 0.35rem);
    left: 0;
    right: 0;
    z-index: 10;
    max-height: 14rem;
    overflow-y: auto;
    list-style: none;
    margin: 0;
    padding: var(--nt-space-1);
    background: var(--nt-surface);
    border: 1px solid var(--nt-border-strong);
    border-radius: var(--nt-radius);
    box-shadow: var(--nt-shadow-lg);
}

.nt-qr-list li {
    display: flex;
    gap: var(--nt-space-3);
    align-items: baseline;
    padding: var(--nt-space-2) var(--nt-space-3);
    border-radius: var(--nt-radius-sm);
    cursor: pointer;
}

.nt-qr-list li.is-active,
.nt-qr-list li:hover { background: var(--nt-brand-050); }

.nt-qr-shortcut { font-weight: 650; color: var(--nt-brand-700); }
.nt-qr-title { color: var(--nt-text-muted); font-size: var(--nt-text-sm); }

.nt-template-picker { margin-top: var(--nt-space-3); }

.nt-template-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-2);
}

.nt-template-row .form-select { flex: 1 1 14rem; min-width: 0; }

/* ====================================================== contacts & templates
   Document screens, so they reuse the card/pill/empty vocabulary above. What is
   new here is a filter toolbar, a row list that collapses on a phone, a wizard
   step marker, a rule editor, and the WhatsApp-shaped bubble the template
   preview needs. Nothing introduces a second colour system. */

/* --------------------------------------------------------- filter toolbar */

.nt-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--nt-space-3);
    padding: var(--nt-space-4);
    margin-bottom: var(--nt-space-5);
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    box-shadow: var(--nt-shadow-sm);
}

.nt-toolbar-field { flex: 0 1 12rem; min-width: 0; }
.nt-toolbar-grow { flex: 1 1 16rem; }
.nt-toolbar-field .form-label { margin-bottom: var(--nt-space-1); }

.nt-toolbar-check { flex: 0 0 auto; padding-bottom: var(--nt-space-2); }

.nt-toolbar-actions {
    display: flex;
    gap: var(--nt-space-2);
    align-items: center;
    margin-inline-start: auto;
}

/* .nt-search-field lives with the inbox section above — the toolbars here reuse it as-is. */

/* ------------------------------------------------------------- row lists */

.nt-rows {
    list-style: none;
    margin: 0;
    padding: 0;
}

.nt-rows > li + li { border-top: 1px solid var(--nt-border); }

.nt-row {
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    padding: var(--nt-space-3) var(--nt-space-4);
    color: inherit;
    text-decoration: none;
}

a.nt-row:hover { background: var(--nt-surface-2); }

a.nt-row:focus-visible {
    outline: 2px solid var(--nt-brand);
    outline-offset: -2px;
}

.nt-row-static { cursor: default; }

.nt-row-avatar {
    flex: 0 0 auto;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-brand-050);
    color: var(--nt-brand-700);
    display: grid;
    place-items: center;
    font-size: var(--nt-text-sm);
    font-weight: 700;
}

.nt-row-main { flex: 1 1 auto; min-width: 0; }
/* overflow-wrap:anywhere because these carry imported data. A CSV name column can hold a pasted
   company name, an email address, or a mis-mapped field with no space in 60 characters, and without
   this the whole page gains a horizontal scrollbar at 375px — on the very screen the import wizard
   sends the owner to. min-width:0 so the row's flex parent is allowed to shrink them. */
.nt-row-title {
    display: block;
    font-weight: 650;
    min-width: 0;
    overflow-wrap: anywhere;
}

.nt-row-sub {
    display: block;
    min-width: 0;
    overflow-wrap: anywhere;
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

.nt-row-tags {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-1);
    margin-top: var(--nt-space-1);
}

.nt-row-meta {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: var(--nt-space-2);
}

.nt-row-time {
    font-size: var(--nt-text-sm);
    color: var(--nt-text-faint);
    min-width: 3rem;
    text-align: end;
}

/* A phone cannot hold a row and its badges side by side; stack instead of squeeze. */
@media (max-width: 47.99em) {
    .nt-row { flex-wrap: wrap; }
    .nt-row-meta { width: 100%; justify-content: flex-start; padding-inline-start: 3rem; }
}

.nt-pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-3);
    margin-top: var(--nt-space-4);
}

/* ------------------------------------------------------------------ tags */

.nt-tag-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--nt-space-1);
    padding: 0.1rem 0.5rem;
    border-radius: var(--nt-radius-pill);
    font-size: var(--nt-text-xs);
    font-weight: 600;
    background: var(--nt-surface-2);
    color: var(--nt-text-muted);
    /* The tenant's colour, only ever a validated hex value (TagChipView.SafeColor). */
    border: 1px solid var(--nt-tag, var(--nt-border));
    box-shadow: inset 3px 0 0 var(--nt-tag, transparent);
}

.nt-tag-list { display: flex; flex-wrap: wrap; gap: var(--nt-space-2); }

.nt-tag-chip form { display: inline; }

.nt-tag-remove {
    border: 0;
    background: none;
    padding: 0;
    line-height: 1;
    color: var(--nt-text-faint);
}

.nt-tag-remove:hover { color: var(--nt-danger); }

/* --------------------------------------------------------- detail layout */

/* minmax(0, 1fr) on the base, not only inside the media query: a grid item's implicit min-width is
   auto, so a wide table inside .nt-split-main would size the column to the table and push the whole
   page sideways at 375px instead of scrolling inside its own .nt-table-wrap. */
.nt-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--nt-space-4);
    align-items: start;
}

.nt-split-main,
.nt-split-side { min-width: 0; }

@media (min-width: 62em) {
    .nt-split { grid-template-columns: minmax(0, 2fr) minmax(18rem, 1fr); }
}

.nt-split-main > .nt-card + .nt-card,
.nt-split-side > .nt-card + .nt-card { margin-top: var(--nt-space-4); }

.nt-kv {
    display: grid;
    grid-template-columns: minmax(8rem, auto) 1fr;
    gap: var(--nt-space-2) var(--nt-space-4);
    margin: 0;
}

.nt-kv dt { font-size: var(--nt-text-sm); color: var(--nt-text-muted); font-weight: 600; }
.nt-kv dd { margin: 0; }

.nt-bullets { margin: 0; padding-inline-start: 1.1rem; color: var(--nt-text-muted); }
.nt-bullets li + li { margin-top: var(--nt-space-2); }

.nt-empty-inline { padding: var(--nt-space-5) var(--nt-space-3); }

.nt-inline-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--nt-space-2);
}

.nt-form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-2);
    align-items: center;
    margin-top: var(--nt-space-4);
}

.nt-card-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-3);
    padding: var(--nt-space-3) var(--nt-space-4);
    border-top: 1px solid var(--nt-border);
    background: var(--nt-surface-2);
}

.nt-req { color: var(--nt-danger); }

.nt-stat-row {
    display: grid;
    gap: var(--nt-space-3);
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
}

/* position: relative is load-bearing, not decoration. Bootstrap's .visually-hidden is
   position: absolute, so a "<th><span class="visually-hidden">Actions</span></th>" inside a table
   wider than the wrapper resolves its containing block to <body> and escapes the wrapper's clip —
   it then sits at the table's full width and drags the whole document sideways. /Settings scrolled
   to 730px at a 375px viewport for exactly that reason. Making the wrapper a containing block keeps
   the offscreen label inside the box that scrolls. */
.nt-table-wrap,
.table-responsive {
    overflow-x: auto;
    position: relative;
}

.nt-row-bad > * { background: var(--nt-danger-tint); }

/* -------------------------------------------------------- import wizard */

.nt-steps {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-4);
    list-style: none;
    margin: 0 0 var(--nt-space-5);
    padding: 0;
    font-size: var(--nt-text-sm);
    color: var(--nt-text-faint);
}

.nt-steps li { display: flex; align-items: center; gap: var(--nt-space-2); }
.nt-steps li.is-current { color: var(--nt-text); font-weight: 650; }
.nt-steps li.is-done { color: var(--nt-success); }

.nt-step-num {
    width: 1.6rem;
    height: 1.6rem;
    border-radius: var(--nt-radius-pill);
    display: grid;
    place-items: center;
    border: 1px solid currentColor;
    font-size: var(--nt-text-xs);
    font-weight: 700;
}

.nt-steps li.is-current .nt-step-num {
    background: var(--nt-brand);
    border-color: var(--nt-brand);
    color: #fff;
}

.nt-steps li.is-done .nt-step-num { background: var(--nt-success-tint); }

.nt-map-grid {
    display: grid;
    gap: var(--nt-space-4);
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}

.nt-attr-grid {
    display: grid;
    gap: var(--nt-space-3);
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

.nt-attr-row {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    padding: var(--nt-space-2) var(--nt-space-3);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-sm);
    background: var(--nt-surface-2);
}

.nt-attr-row .form-check { flex: 1 1 auto; min-width: 0; margin: 0; }
.nt-attr-row .form-control { flex: 0 1 9rem; }

/* --------------------------------------------------------- rule editor */

.nt-rule-list { display: grid; gap: var(--nt-space-3); }

.nt-rule {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--nt-space-3);
    padding: var(--nt-space-3);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    background: var(--nt-surface-2);
}

.nt-rule-field { flex: 1 1 11rem; min-width: 0; }
.nt-rule-field .form-label { margin-bottom: var(--nt-space-1); }
.nt-rule-actions { flex: 0 0 auto; padding-bottom: 0.1rem; }

/* ------------------------------------------------------ template preview */

.nt-template-grid {
    display: grid;
    gap: var(--nt-space-4);
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
}

.nt-template-badges {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-2);
    margin-bottom: var(--nt-space-3);
}

.nt-notice-tight { margin: 0 0 var(--nt-space-3); }

.nt-bubble-wrap {
    background: #ece5dd;
    border-radius: var(--nt-radius);
    padding: var(--nt-space-3);
}

/* Scoped to .nt-bubble-wrap, NOT bare .nt-bubble.
   The inbox thread (§13) already owns .nt-bubble for a real message bubble, several hundred lines
   above. An unscoped rule here is later in the cascade at equal specificity, so it silently won
   everywhere: every inbound message in the team inbox lost its border and its 16px tail radius to
   this 6px preview box, and dropped to the small type meant for a thumbnail. The template preview
   is always inside .nt-bubble-wrap and the thread bubble never is, so scoping separates the two
   without touching either view's markup. */
.nt-bubble-wrap .nt-bubble {
    background: #fff;
    border: 0;
    border-radius: var(--nt-radius-sm);
    padding: var(--nt-space-3);
    box-shadow: var(--nt-shadow-sm);
    font-size: var(--nt-text-sm);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.nt-bubble-empty { color: var(--nt-text-faint); font-style: italic; }
.nt-bubble-header { font-weight: 700; }

.nt-bubble-footer {
    color: var(--nt-text-faint);
    font-size: var(--nt-text-xs);
    margin-top: var(--nt-space-2);
}

.nt-bubble-media {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    padding: var(--nt-space-4);
    margin-bottom: var(--nt-space-2);
    border-radius: var(--nt-radius-sm);
    background: var(--nt-surface-2);
    color: var(--nt-text-muted);
}

.nt-bubble-buttons {
    list-style: none;
    margin: var(--nt-space-2) 0 0;
    padding: 0;
    display: grid;
    gap: var(--nt-space-2);
}

.nt-bubble-button {
    display: block;
    text-align: center;
    background: #fff;
    /* 4.6:1 on white — a link colour that still reads as a link at small sizes. */
    color: #0a6f9e;
    border-radius: var(--nt-radius-sm);
    padding: var(--nt-space-2);
    font-size: var(--nt-text-sm);
    font-weight: 600;
    box-shadow: var(--nt-shadow-sm);
}

.nt-button-slot {
    display: grid;
    gap: var(--nt-space-3);
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    padding: var(--nt-space-3);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
}

.nt-button-slot + .nt-button-slot { margin-top: var(--nt-space-3); }


/* ================================================================== campaigns
   The broadcast list, the create wizard's choice cards, and the estimate that
   is the moment a business owner commits real money. Same token set as the
   rest of the system; nothing here introduces a new colour. */

.nt-campaign-list {
    display: grid;
    gap: var(--nt-space-4);
}

.nt-campaign { position: relative; }

.nt-campaign-top {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--nt-space-3);
}

.nt-campaign-ident { min-width: 0; }

/* Shown only by the progress script, when the live connection has actually dropped. Warning
   colouring plus the words themselves — the meaning never rides on the hue alone. */
.nt-status-offline {
    color: var(--nt-warning);
    font-weight: 600;
}

/* Same exposure as .nt-row-title: the campaign name is 120 free characters of tenant input. */
.nt-campaign-ident h2,
.nt-campaign-ident a { overflow-wrap: anywhere; }

/* The progress track. A visible border keeps it legible in forced-colours mode,
   where a background-only bar disappears entirely. */
.nt-progress {
    margin-top: var(--nt-space-4);
    height: 0.6rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-surface-2);
    border: 1px solid var(--nt-border);
    overflow: hidden;
}

.nt-progress-bar {
    height: 100%;
    background: var(--nt-brand);
    border-radius: inherit;
    transition: width 320ms ease;
}

@media (prefers-reduced-motion: reduce) {
    .nt-progress-bar { transition: none; }
}

.nt-campaign-counts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
    gap: var(--nt-space-3);
    margin: var(--nt-space-4) 0 0;
}

.nt-campaign-counts dt {
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--nt-text-faint);
}

.nt-campaign-counts dd {
    margin: 0;
    font-size: var(--nt-text-md);
    font-weight: 650;
    font-variant-numeric: tabular-nums;
}

/* ----------------------------------------------------------- choice cards
   A radio the size of a target rather than a dot: the wizard's choices carry
   two or three lines of consequence each, and those lines are the point. */

.nt-choice-list {
    display: grid;
    gap: var(--nt-space-3);
}

.nt-choice {
    display: flex;
    align-items: flex-start;
    gap: var(--nt-space-3);
    padding: var(--nt-space-4);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    background: var(--nt-surface);
    cursor: pointer;
}

.nt-choice:hover { border-color: var(--nt-border-strong); }

.nt-choice:has(input:checked) {
    border-color: var(--nt-brand);
    box-shadow: 0 0 0 1px var(--nt-brand);
    background: var(--nt-brand-050);
}

/* The focus ring belongs to the whole card, not the 1rem dot inside it. */
.nt-choice:has(input:focus-visible) {
    outline: 3px solid var(--nt-brand-400);
    outline-offset: 2px;
}

.nt-choice.is-disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.nt-choice input { margin-top: 0.2rem; flex: 0 0 auto; }

.nt-choice-body {
    display: flex;
    flex-direction: column;
    gap: var(--nt-space-1);
    min-width: 0;
}

.nt-choice-title { font-weight: 650; }

.nt-choice-meta {
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

.nt-choice-preview {
    font-size: var(--nt-text-sm);
    color: var(--nt-text);
    background: var(--nt-surface-2);
    border-radius: var(--nt-radius-sm);
    padding: var(--nt-space-2) var(--nt-space-3);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* ------------------------------------------------------------- the estimate */

.nt-estimate {
    padding: var(--nt-space-5);
    border-radius: var(--nt-radius);
    background: var(--nt-brand-050);
    border: 1px solid var(--nt-brand-100);
}

.nt-estimate-total {
    font-size: var(--nt-text-3xl);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.1;
    margin: 0 0 var(--nt-space-2);
    font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------- the flow builder canvas (§11)

   A drawing surface inside a card. It scrolls in its own box rather than pushing the page
   sideways, which is what keeps the page usable at 375px: the canvas pans, the body never does.
   Every node is a real focusable button, so the graph is navigable without a mouse and the arrow
   keys move the selected step. */

.nt-card-pad-0 { padding: 0; }
.nt-card-pad-0 > .nt-card-header { padding: var(--nt-space-4) var(--nt-space-5); margin-bottom: 0; }

.nt-canvas-scroll {
    overflow: auto;
    max-height: 70vh;
    border-top: 1px solid var(--nt-border);
    border-radius: 0 0 var(--nt-radius-lg) var(--nt-radius-lg);
    background:
        radial-gradient(circle at 1px 1px, var(--nt-border) 1px, transparent 0) 0 0 / 24px 24px,
        var(--nt-surface-2);
}

.nt-canvas {
    position: relative;
    min-width: 100%;
    min-height: 360px;
}

.nt-canvas-edges {
    position: absolute;
    inset: 0;
    pointer-events: none;
    color: var(--nt-border-strong);
}

.nt-edge-branch { color: var(--nt-brand-200); }

.nt-edge-label {
    font-size: 11px;
    font-weight: 600;
    fill: var(--nt-text-muted);
    paint-order: stroke;
    stroke: var(--nt-surface-2);
    stroke-width: 4px;
}

.nt-node { position: absolute; }

.nt-node-grab {
    display: block;
    width: 100%;
    text-align: left;
    padding: var(--nt-space-3) var(--nt-space-4);
    background: var(--nt-surface);
    border: 1px solid var(--nt-border-strong);
    border-radius: var(--nt-radius);
    box-shadow: var(--nt-shadow-sm);
    cursor: grab;
    touch-action: none;
}

.nt-node-grab:hover { border-color: var(--nt-brand-200); }

.nt-node-grab:focus-visible {
    outline: 3px solid var(--nt-brand-400);
    outline-offset: 2px;
}

.nt-node.is-selected .nt-node-grab {
    border-color: var(--nt-brand);
    box-shadow: 0 0 0 3px var(--nt-brand-050), var(--nt-shadow);
}

.nt-node.is-trigger .nt-node-grab { border-left: 4px solid var(--nt-brand); }
.nt-node.is-end .nt-node-grab { border-left: 4px solid var(--nt-text-faint); }

.nt-node-type {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    font-weight: 600;
    font-size: var(--nt-text-base);
    color: var(--nt-text);
}

.nt-node-id {
    display: block;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
}

.nt-node-summary {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
    margin-top: var(--nt-space-1);
    min-height: 1.2em;
}

/* -------------------------------------------------- knowledge base + one-time secrets */

.nt-secret {
    display: block;
    font-family: var(--nt-font-mono);
    font-size: var(--nt-text-base);
    padding: var(--nt-space-3);
    background: var(--nt-surface);
    border: 1px dashed var(--nt-border-strong);
    border-radius: var(--nt-radius-sm);
    overflow-wrap: anywhere;
}

.nt-dropzone {
    border: 2px dashed var(--nt-border-strong);
    border-radius: var(--nt-radius);
    padding: var(--nt-space-5);
    text-align: center;
    background: var(--nt-surface-2);
}

.nt-meter {
    height: 6px;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-border);
    overflow: hidden;
}

.nt-meter > span {
    display: block;
    height: 100%;
    background: var(--nt-brand);
}
