﻿/* ---------------------------------------------------------------------------
   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;

    /* rem, not px, so the laptop density scale below shrinks the shell along with the content.
       15.5rem/3.75rem == the original 248px/60px at the default 16px root. */
    --nt-sidebar-width: 15.5rem;
    --nt-topbar-height: 3.75rem;

    /* Aliases. Rules further down ask for these names; without them the declaration is invalid at
       computed-value time and silently drops (an un-rounded corner, a slate panel on a green site). */
    --nt-radius-md: var(--nt-radius);
    --nt-surface-muted: var(--nt-surface-2);
    --nt-shadow-md: var(--nt-shadow);
    --nt-focus: var(--nt-brand-400);

    /* 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);

    /* The rest of Bootstrap's primary family. --bs-primary alone does not cover these: the subtle
       trio is what .alert-primary, .bg-primary-subtle, .text-primary-emphasis and the open
       .accordion-button are actually built from, and each is a separate literal in the stylesheet. */
    --bs-primary-text-emphasis: var(--nt-brand-700);
    --bs-primary-bg-subtle: var(--nt-brand-050);
    --bs-primary-border-subtle: var(--nt-brand-200);
    --bs-focus-ring-color: rgba(11, 122, 92, 0.25);

    /* Native controls the framework never touches — <input type=checkbox|radio|range>, <progress>,
       and the browser's own selected-option highlight — paint from the UA accent, which is blue on
       every platform. One property retints all of them. */
    accent-color: var(--nt-brand);
}

/* 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;
}

/* ------------------------------------------------------- the rest of the blue
   Bootstrap compiles its components from the Sass variable $primary, and bakes the result in as a
   literal — #0d6efd, or a shade of it — rather than reading --bs-primary at runtime. Retargeting
   --bs-primary above therefore fixes the utilities (.text-primary, .bg-primary, .badge) and nothing
   else: every component below still carried a hard-coded blue on a green product. They are gathered
   here rather than scattered through the file so the next Bootstrap upgrade has one place to check.

   The list came from auditing the compiled stylesheet for the primary family — #0d6efd, #0a58ca,
   #86b7fe, #052c65, #cfe2ff, #9ec5fe and rgba(13,110,253,…) — and covers every one that survived. */

/* Checkboxes, radios and switches. The biggest offender by reach: ~24 views use .form-check-input,
   and every ticked box, selected radio and "on" switch in the product printed Bootstrap blue.
   :focus is listed as well as the :focus-visible ring further down, because :focus also fires on a
   plain mouse click — that is the blue halo you get just from clicking a checkbox. */
.form-check-input:checked,
.form-check-input[type=checkbox]:indeterminate {
    background-color: var(--nt-brand);
    border-color: var(--nt-brand);
}

.form-check-input:focus {
    border-color: var(--nt-brand-200);
    box-shadow: 0 0 0 3px var(--nt-brand-100);
}

/* The switch knob is a background SVG with the fill baked into the data URI, so it cannot be
   themed with a custom property — the whole URL has to be restated with a brand fill. */
.form-switch .form-check-input:focus {
    --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2397d7bd'/%3e%3c/svg%3e");
}

/* Focus glow behind a pressed button. Bootstrap ships a blue rgb triplet per variant, so a green
   .btn-primary bloomed blue the moment it took focus. */
.btn-primary,
.btn-link {
    --bs-btn-focus-shadow-rgb: 11, 122, 92;
}

.btn-check:focus-visible + .btn {
    box-shadow: 0 0 0 4px var(--nt-brand-100);
}

/* Selection and navigation surfaces. */
.list-group {
    --bs-list-group-active-bg: var(--nt-brand);
    --bs-list-group-active-border-color: var(--nt-brand);
}

.dropdown-menu {
    --bs-dropdown-link-active-bg: var(--nt-brand);
    --bs-dropdown-link-active-color: var(--nt-brand-contrast);
}

.pagination {
    --bs-pagination-active-bg: var(--nt-brand);
    --bs-pagination-active-border-color: var(--nt-brand);
    --bs-pagination-focus-box-shadow: 0 0 0 3px var(--nt-brand-100);
}

.nav-link:focus-visible {
    box-shadow: 0 0 0 3px var(--nt-brand-100);
}

.btn-close {
    --bs-btn-close-focus-shadow: 0 0 0 3px var(--nt-brand-100);
}

/* Progress bars — campaign dispatch and knowledge-base ingestion both render one. */
.progress,
.progress-stacked {
    --bs-progress-bar-bg: var(--nt-brand);
}

/* Accordion. The open state paints from the subtle trio (fixed at :root) but the chevron is another
   data URI with the stroke baked in, so it needs restating too. */
.accordion {
    --bs-accordion-btn-focus-box-shadow: 0 0 0 3px var(--nt-brand-100);
    --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23075440' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
}

/* Table row tint and the primary link utility. .link-primary in particular hard-codes its hover
   colour as a literal rgb() rather than reading a token, so it goes blue only under the cursor. */
.table-primary {
    --bs-table-bg: var(--nt-brand-050);
    --bs-table-border-color: var(--nt-brand-200);
    --bs-table-color: var(--nt-text);
}

.link-primary:hover,
.link-primary:focus {
    color: var(--nt-brand-700) !important;
    text-decoration-color: var(--nt-brand-700) !important;
}

/* Range slider. accent-color at :root cannot reach these — Bootstrap sets the thumb's
   background-color explicitly, and an explicit paint always beats the UA accent. */
.form-range::-webkit-slider-thumb { background-color: var(--nt-brand); }
.form-range::-moz-range-thumb { background-color: var(--nt-brand); }
.form-range::-webkit-slider-thumb:active { background-color: var(--nt-brand-200); }
.form-range::-moz-range-thumb:active { background-color: var(--nt-brand-200); }

.form-range:focus::-webkit-slider-thumb { box-shadow: 0 0 0 1px #fff, 0 0 0 3px var(--nt-brand-100); }
.form-range:focus::-moz-range-thumb { box-shadow: 0 0 0 1px #fff, 0 0 0 3px var(--nt-brand-100); }

/* --------------------------------------------------------------- base type */

html {
    font-size: 16px;
}

/* ------------------------------------------------------ laptop density scale
   The product is designed against a ~1920px desktop. On a small laptop (1366 or
   1536 CSS px — i.e. a 1920 panel at 125% Windows scaling) the same layout
   overflows: dashboard cards spill and the inbox's fixed-width columns crush
   the thread. Zooming the browser to 80% fixed it, which is the tell that the
   layout is right and only the density is wrong.

   Everything in this system — spacing scale, type scale, card padding, the
   inbox columns, the shell tokens (--nt-sidebar-width, --nt-topbar-height),
   and Bootstrap's own components — is sized in rem, so stepping the ROOT
   font-size down on laptop-width viewports reproduces that zoom exactly: each
   tier keeps the effective design width (viewport ÷ scale) at roughly the
   1920px the product was designed for.

       992–1439  @ 12px root (75%)     → effective 1323–1920
      1440–1699  @ 13px root (81.25%)  → effective 1772–2091
      1700–1919  @ 14px root (87.5%)   → effective 1943–2193
      ≥1920      @ 16px root (100%)    → the designed-for desktop

   Media queries, not vw, on purpose: a vw-based root font cancels the user's
   own browser zoom (zooming in shrinks the CSS viewport, which shrinks the
   font right back). Tiers respond to zoom the way the rest of the layout does.
   Below 992px the sidebar collapses into the offcanvas drawer and the
   phone/tablet layouts take over at full size, so no scaling there.
   These must stay AFTER the base html rule above — same specificity, so the
   cascade order is what lets the tiers win. */
@media (min-width: 992px) and (max-width: 1439.98px) {
    html { font-size: 12px; }
}

@media (min-width: 1440px) and (max-width: 1699.98px) {
    html { font-size: 13px; }
}

@media (min-width: 1700px) and (max-width: 1919.98px) {
    html { font-size: 14px; }
}

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 {
    /* Declared here rather than beside .btn-primary above so this class is styled in exactly one
       place: two blocks for one selector means the later one silently wins for anything they share. */
    --bs-btn-focus-shadow-rgb: 11, 122, 92;
    --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);
}

/* Bootstrap compiles the active-pill background from its own $primary (#0d6efd), a literal it does
   NOT read from --bs-primary — so an un-themed .nav-pills renders a blue pill on a green site. Point
   the pill tokens at the brand, and give the segmented control a pill-shaped rail. */
.nav-pills {
    --bs-nav-pills-link-active-bg: var(--nt-brand);
    --bs-nav-pills-link-active-color: var(--nt-brand-contrast);
    --bs-nav-pills-border-radius: var(--nt-radius-pill);
    gap: var(--nt-space-1);
}

.nav-pills .nav-link {
    color: var(--nt-text-muted);
    font-weight: 600;
    font-size: var(--nt-text-sm);
    padding: var(--nt-space-2) var(--nt-space-4);
    transition: background 0.18s ease, color 0.18s ease;
}

.nav-pills .nav-link:hover:not(.active) {
    background: var(--nt-brand-050);
    color: var(--nt-brand-700);
}

.nav-pills .nav-link.active { box-shadow: var(--nt-shadow-sm); }

.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);
    /* Hover life works with or without the entrance animation; it yields to reduced-motion where the
       transition is zeroed further down. Declared here rather than in a second .nt-card rule so the
       class is defined once (see StylesheetCollisionTests). */
    transition: transform 0.18s ease, box-shadow 0.18s ease;
}

.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);
}

/* AI conversation summary in the inbox details panel: a readable, wrapping note
   that preserves the model's line breaks without letting a long run overflow. */
.nt-ai-summary {
    font-size: var(--nt-text-sm);
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* -------------------------------------------------------------- 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;
}

/* Nav + footer scroll together as one region, instead of the nav owning its own scrollbar and the
   footer sitting pinned outside it. min-height:0 is required for a flex child to shrink below its
   content size and actually hand scrolling to overflow-y rather than stretching the sidebar. */
.nt-sidebar-scroll {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /* A slim, translucent thumb instead of the OS's default grey/arrowed scrollbar, which reads as
       a stray light rectangle against the dark rail. */
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
}

.nt-sidebar-scroll::-webkit-scrollbar { width: 6px; }
.nt-sidebar-scroll::-webkit-scrollbar-track { background: transparent; }

.nt-sidebar-scroll::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.22);
    border-radius: var(--nt-radius-pill);
}

.nt-sidebar-scroll::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.34); }

.nt-nav {
    padding: var(--nt-space-3) var(--nt-space-3);
}

.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);
}

/* Group header: a collapse toggle, not a link. It is a full-width button, so it is reset to look
   like a nav row and its chevron is pushed to the end. */
.nt-nav-toggle {
    width: 100%;
    background: none;
    border: 0;
    text-align: start;
    cursor: pointer;
    font-family: inherit;
}

.nt-nav-chevron {
    margin-inline-start: auto;
    font-size: 0.8rem;
    opacity: 0.7;
    transition: transform 0.15s ease;
}

/* Rotated when the group is open. Server-rendered .is-open covers the pre-Alpine paint; Alpine's
   :class then keeps it in step with the toggle. */
.nt-nav-toggle.is-open .nt-nav-chevron,
.nt-nav-chevron.is-open {
    transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
    .nt-nav-chevron { transition: none; }
}

/* Children of an open group: indented under their header and set slightly quieter, so the hierarchy
   reads at a glance without a second colour. */
.nt-nav-sub {
    margin: 0 0 2px;
    padding-inline-start: calc(var(--nt-space-3) + var(--nt-space-2));
}

.nt-nav-sub .nt-nav-link {
    /* Tighter than a top-level row: the body's 1.55 line-height was inflating each entry to 42px,
       so a 7-item group like Billing ran to nearly 300px of mostly empty rows. */
    padding-block: 0.3rem;
    line-height: 1.25;
    font-weight: 500;
    font-size: var(--nt-text-sm);
}

.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;
}

/* Behind the subscription paywall right now. Dimmed less than .is-pending — the row is still a live
   link to the checkout, not a dead one — and the padlock says so without relying on the dimming,
   which alone would read as "disabled" to some and as nothing at all to others. */
.nt-nav-link.is-locked {
    opacity: 0.72;
}

.nt-nav-link.is-locked:hover,
.nt-nav-link.is-locked:focus-visible {
    opacity: 1;
}

.nt-nav-lock {
    margin-inline-start: auto;
    font-size: 0.7rem;
    line-height: 1;
    opacity: 0.75;
}

.nt-nav-lock .bi { font-size: 0.7rem; }

.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 {
    /* Stays pinned to the bottom of .nt-sidebar-scroll while the nav is short; once the nav grows
       past the viewport this margin collapses to 0 and the footer scrolls with it instead of
       staying fixed in place. */
    margin-top: auto;
    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);
    /* The shadow appears only once content actually slides beneath the bar (site.js toggles
       .is-scrolled); the transition lives here so gaining and losing it is equally soft. */
    transition: box-shadow 0.25s ease;
}

.nt-topbar.is-scrolled {
    box-shadow: 0 0.375rem 1rem rgba(13, 43, 37, 0.08);
}

.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 ----

   The skin used to swap the accent ramp for a blue one, which broke in two ways. --nt-brand-600 is
   what .btn-primary uses for :hover, and --nt-brand is NOT part of the override — so every primary
   button in the admin console sat green at rest and turned blue under the cursor. --nt-brand-400 is
   the global focus ring, so keyboard focus went blue too, on a product that is green everywhere
   else. The realm cue now lives entirely in the shell chrome: a near-black forest sidebar, darker
   than the tenant's, so you still know which console you are in. The accent ramp is left alone,
   which means buttons, rings and links behave identically in both realms. */

.nt-shell-platform {
    --nt-sidebar: #071c17;
    --nt-sidebar-2: #0e2f27;
}

/* ============================================================== 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 card brand now carries the real mark image, so it drops the coloured tile the icon needed. */
.nt-auth-brand-mark {
    width: 2.75rem;
    height: 2.75rem;
    object-fit: contain;
    display: block;
}

/* Public sign-in (business/agent) sits inside the marketing navbar + footer, so it must not claim the
   whole viewport the way the standalone auth screen does — it fills the space between the two instead. */
.nt-auth-public {
    min-height: auto;
    padding-block: clamp(2.5rem, 7vw, 5rem);
}

/* The business ⇄ agent segmented control. One clear choice up front, so a visitor does not fail
   against a store that never held their account. */
.nt-auth-toggle {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.25rem;
    padding: 0.25rem;
    margin-bottom: var(--nt-space-4);
    background: var(--nt-canvas);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-pill);
}

.nt-auth-toggle-opt {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.45rem 0.75rem;
    border-radius: var(--nt-radius-pill);
    font-size: var(--nt-text-sm);
    font-weight: 600;
    color: var(--nt-text-muted);
    text-decoration: none;
    transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}

.nt-auth-toggle-opt:hover { color: var(--nt-text); }

.nt-auth-toggle-opt.is-active {
    background: var(--nt-surface);
    color: var(--nt-brand-700);
    box-shadow: var(--nt-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.08));
}

.nt-auth-toggle-opt.is-active i { color: var(--nt-brand); }

/* Brand tile carrying the real mark image (shell sidebar + top bar) — no coloured square behind it. */
.nt-brand-mark-img {
    background: transparent;
    padding: 0;
    overflow: hidden;
}

.nt-brand-mark-img img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* 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 {
    position: relative;
    overflow: hidden;
    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); }

/* The countdown bar: a full-width sliver pinned to the toast's bottom edge that drains to nothing
   over exactly the autohide delay (duration is set inline in JS to match). Tinted to the toast kind.
   scaleX from a left origin so it shrinks toward the close, and it inherits the toast's rounded
   corners because the toast clips its overflow. */
.nt-toast-progress {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 3px;
    transform-origin: left center;
    background: var(--nt-info);
    animation-name: ntToastProgress;
    animation-timing-function: linear;
    animation-fill-mode: forwards;
}
.nt-toast-success .nt-toast-progress { background: var(--nt-success); }
.nt-toast-danger  .nt-toast-progress { background: var(--nt-danger); }
.nt-toast-info    .nt-toast-progress { background: var(--nt-info); }

@keyframes ntToastProgress {
    from { transform: scaleX(1); }
    to   { transform: scaleX(0); }
}

@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; }
}

/* The .nt-card entrance transition is declared once on the base rule above; the hover lift and its
   reduced-motion opt-out live here. */
.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 (CLAUDE.md §13): it owns the full height below the top bar, scrolls
   internally, and collapses from two panes to one on a phone. The visual
   language here is deliberately WhatsApp's own — the beige chat wallpaper, the
   white inbound and green outbound bubbles with their little tails, the day
   pills, the grey/blue delivery ticks — so an agent feels at home instantly.
   Those WhatsApp-specific colours are scoped to .nt-inbox and do not leak into
   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 {
    /* WhatsApp palette, local to this pane — these tokens never leak into the rest of the app. */
    --wa-panel: #f0f2f5;          /* header / list chrome            */
    --wa-panel-line: #d1d7db;     /* hairline between chrome + body  */
    --wa-wallpaper: #efeae2;      /* the chat background beige       */
    --wa-in-bubble: #ffffff;      /* received message                */
    --wa-out-bubble: #d9fdd3;     /* sent message                    */
    --wa-bubble-shadow: 0 1px 0.5px rgba(11, 20, 26, 0.13);
    --wa-tick-read: #53bdeb;      /* the famous blue double-tick     */
    --wa-tick-plain: #667781;     /* grey single / double tick       */
    --wa-meta: #667781;           /* time + faint text on bubbles    */
    --wa-accent: #00a884;         /* send button / active accents    */
    --wa-accent-strong: #008069;
    /* One height for the thread and details column headers so their bottom hairlines line up. */
    --nt-inbox-head-height: max(52px, 3.75rem);
    /* The inbox is read and typed in all day, so its text never shrinks with the laptop density tier
       (12px root at 992-1439px: an iPad in landscape, a 14-inch laptop). The type scale is re-pinned
       here with pixel floors; every inbox element already reads these tokens, and on a wide screen,
       where the rem value is larger, nothing changes. */
    --nt-text-xs: max(11.5px, 0.75rem);
    --nt-text-sm: max(13px, 0.8125rem);
    --nt-text-base: max(14.5px, 0.9375rem);
    --nt-text-md: max(16px, 1.0625rem);
    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(--wa-panel);
}

/* ---- fit-to-screen: the inbox is a full-height application pane, not a document ----
   A flush page (only the inbox sets ContentVariant=flush) pins to the viewport and never scrolls the
   page — the conversation list, the message history and the details panel each own their own scroll.
   Doing this as a flex column from the body down means the message area fills whatever height is left
   after the top bar, whatever the top bar's real height turns out to be, rather than the old
   calc(100dvh - guessed-topbar) that broke the moment the bar wrapped a pixel taller. */
.nt-shell-flush {
    height: 100vh;
    /* svh = the SMALL viewport height (browser toolbars shown), which excludes the space behind a
       browser's own top/bottom chrome. */
    height: 100svh;
    /* The robust height: the SMALLER of the JS measure and svh.
       - --app-height (site.js, from visualViewport/innerHeight) is the real visible height and shrinks
         for the on-screen keyboard, so it wins when the keyboard is up.
       - but some mobile / in-app browsers OVER-report that height (counting the area behind their own
         bottom toolbar), which pushed the composer behind the toolbar and the Android nav bar. Capping
         at 100svh — which never counts chrome — keeps the composer on-screen in exactly that case.
       min() gives the best of both: keyboard-aware when the measure is smaller, chrome-safe when it is
       larger. */
    height: min(var(--app-height, 100svh), 100svh);
    overflow: hidden;
}

.nt-shell-flush .nt-main {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.nt-shell-flush .nt-topbar { flex: 0 0 auto; }

.nt-shell-flush .nt-content-flush {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

.nt-shell-flush .nt-content-inner-flush {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

/* Fill the space the flex parent hands down rather than measuring the viewport ourselves. */
.nt-shell-flush .nt-inbox {
    height: auto;
    min-height: 0;
    flex: 1 1 auto;
}

.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; }

    /* One pane at a time, so make the inbox a plain flex column here instead of a grid: the single
       visible pane then flex-fills the exact height its parent hands down, which passes cleanly to the
       thread and its composer. The grid's align-content stretch did the same on desktop but is handled
       inconsistently by some mobile / in-app webviews — enough to leave the thread taller than the
       viewport and drop the composer off the bottom. Flex-fill is universally reliable. */
    .nt-inbox {
        display: flex;
        flex-direction: column;
    }

    .nt-inbox-list,
    .nt-inbox-thread {
        flex: 1 1 auto;
        min-height: 0;
    }
}

/* The list takes a share of the width (clamped), not a fixed 26rem: at 992-1439px the root font is
   12px (the laptop density tier) and a sidebar is docked too, so a fixed list left an iPad in
   landscape a 526px chat. minmax(0, 1fr) keeps a long unbroken message from widening the thread. */
@media (min-width: 992px) {
    .nt-inbox { grid-template-columns: clamp(16rem, 27vw, 26rem) minmax(0, 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(--wa-panel-line);
    background: var(--nt-surface);
    overflow-y: auto;
}

.nt-inbox-thread {
    background: var(--wa-wallpaper);
}

/* ---- 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(--wa-panel);
    border-bottom: 1px solid var(--wa-panel-line);
    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; }

/* WhatsApp's rounded, chrome-coloured search field. */
.nt-conv-search .form-control {
    border-radius: var(--nt-radius-pill);
    background: var(--nt-surface);
    border-color: transparent;
}

.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 transparent;
    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 { color: var(--wa-accent-strong); }

.nt-conv-filter.is-active {
    background: #d9f2ea;
    color: var(--wa-accent-strong);
}

.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 #f0f0f0;
    text-decoration: none;
    color: inherit;
    transition: background-color 0.12s ease;
}

.nt-conv-item:hover { background: var(--nt-surface-2); }

.nt-conv-item.is-selected { background: #f0f2f5; }

/* "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); }

/* Unread (a genuinely new message not yet opened): the strongest emphasis — fully bold name and
   preview, darkened — that drops back to normal the moment the thread is read (§13). */
.nt-conv-item.is-unread .nt-conv-name,
.nt-conv-item.is-unread .nt-conv-preview {
    font-weight: 700;
    color: var(--nt-text);
}

.nt-conv-item.is-unread .nt-conv-time { color: var(--wa-accent-strong); font-weight: 700; }

/* The small "New" capsule, WhatsApp's green. Sits among the row's meta pills but reads louder. */
.nt-conv-new {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    padding: 0.05rem 0.45rem;
    border-radius: var(--nt-radius-pill);
    background: var(--wa-accent, #00a884);
    color: #ffffff;
    font-size: var(--nt-text-xs);
    font-weight: 700;
    line-height: 1.5;
    letter-spacing: 0.02em;
}

.nt-conv-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.9rem;
    height: 2.9rem;
    border-radius: var(--nt-radius-pill);
    /* The positioning context for the channel mark and the profile image that
       sit over this disc (see .nt-chan / .nt-conv-avatar-img). */
    position: relative;
    /* WhatsApp's placeholder avatar is a soft grey disc with a lighter glyph. */
    background: #dfe5e7;
    color: #8696a0;
    font-weight: 700;
    font-size: var(--nt-text-base);
}

.nt-conv-avatar-lg { width: 2.6rem; height: 2.6rem; }

.nt-conv-body {
    display: block;
    min-width: 0;
    flex: 1 1 auto;
    align-self: center;
}

.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-size: var(--nt-text-base);
    font-weight: 600;
    color: #111b21;
}

.nt-conv-time {
    flex: 0 0 auto;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
}

.nt-conv-item.is-awaiting .nt-conv-time { color: var(--wa-accent-strong); font-weight: 600; }

.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;
}

/* The row's status pills. Kept to a single, non-wrapping line and shrunk hard so more conversations
   fit on screen — the pills are a glance, not the content. Anything that overflows is clipped rather
   than pushing the row taller. */
.nt-conv-meta {
    display: flex;
    flex-wrap: nowrap;
    gap: 0.25rem;
    margin-top: 0.35rem;
    overflow: hidden;
}

.nt-conv-meta .nt-pill {
    flex: 0 1 auto;
    min-width: 0;
    padding: 0.02rem 0.4rem;
    font-size: 0.6875rem;
    font-weight: 600;
    line-height: 1.45;
    gap: 0.15rem;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nt-conv-meta .nt-pill .bi { font-size: 0.7rem; flex: 0 0 auto; }

/* The assigned-agent pill can carry a long name; it may ellipsize while the fixed-width status pills
   (window, awaiting) stay whole. */
.nt-conv-meta .nt-pill-neutral { min-width: 0; }
.nt-conv-meta .nt-pill-success,
.nt-conv-meta .nt-pill-warning,
.nt-conv-meta .nt-pill-info { flex: 0 0 auto; }

/* A team row somebody is waiting on. A tint rather than a colour on the text, so the row still reads
   normally and the emphasis survives both themes. */
.nt-dash-team-attention {
    background: color-mix(in srgb, var(--wa-accent-strong) 7%, transparent);
}

/* The admin's assignee selector, sharing the search row. Allowed to shrink so a long member name
   never pushes the Search button off the pane. The number selector shares the same shape. */
.nt-conv-assignee,
.nt-conv-number {
    flex: 1 1 8rem;
    min-width: 0;
    max-width: 12rem;
}

/* The number pill carries a full display number; like the assigned-agent pill it may ellipsize
   rather than push the fixed-width status pills around. */
.nt-conv-meta .nt-conv-number-pill { min-width: 0; }

/* The thread header's "via <business number>" note — quieter than the contact's own number so the
   two numbers on one line cannot be mistaken for each other. */
.nt-thread-via {
    color: var(--nt-text-muted);
    font-size: var(--nt-text-xs);
}

/* Phones. Search box, member picker and button on one row is a desktop shape: at 375px it leaves the
   search field 162px (the placeholder truncates mid-word) and the picker 101px (a member's name
   clips mid-word), and the row reads as three mismatched controls because a default-size input is
   41px tall next to a 33px small button. So the search box takes a row of its own and the picker and
   button share the next, stretched to one height.

   The filter chips become a scrolling strip rather than wrapping: with "Mine" there are five of them,
   which fit at 375px and wrap onto a second row just below it — pushing the conversation list down by
   a whole row on the narrowest phones. The strip bleeds to the panel edges so a half-scrolled chip is
   cut at the screen edge rather than floating inside the padding, and the taps grow to a size a thumb
   can actually hit. */
@media (max-width: 575.98px) {
    .nt-conv-search {
        flex-wrap: wrap;
        align-items: stretch;
    }

    .nt-search-field { flex: 1 0 100%; }

    /* Basis 0, not auto: wrapping is decided on base sizes, and a picker whose base is the width of
       its longest member name pushes the button onto a third row. From zero it simply takes whatever
       the button leaves. */
    .nt-conv-assignee,
    .nt-conv-number {
        flex: 1 1 0;
        max-width: none;
    }

    .nt-conv-search .btn { flex: 0 0 auto; }

    .nt-conv-filters {
        flex-wrap: nowrap;
        overflow-x: auto;
        margin-inline: calc(var(--nt-space-4) * -1);
        padding-inline: var(--nt-space-4);
        /* The strip is scrolled by thumb, and a scrollbar over 27px-tall chips only eats them. */
        scrollbar-width: none;
    }

    .nt-conv-filters::-webkit-scrollbar { display: none; }

    .nt-conv-filter {
        flex: 0 0 auto;
        padding: 0.35rem 0.85rem;
    }
}

/* ---- search results: "Chats" / "Messages" ----------------------------------------------------
   Only rendered while a term is being searched. Sticky so the heading stays overhead as the agent
   scrolls a long result set and never has to guess which half they are looking at. */
.nt-conv-section {
    position: sticky;
    top: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0;
    padding: var(--nt-space-2) var(--nt-space-4);
    background: var(--nt-surface-2, var(--nt-surface));
    border-bottom: 1px solid var(--nt-border);
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--nt-text-muted);
}

.nt-conv-section-count {
    font-weight: 600;
    letter-spacing: 0;
    color: var(--nt-text-muted);
    opacity: 0.75;
}

/* A message hit is about the words, not the thread's state — no pills, and the snippet is allowed
   the room the status row would otherwise take. */
.nt-conv-hit .nt-conv-preview { white-space: nowrap; }

.nt-conv-hit-who {
    font-weight: 600;
    color: var(--nt-text-muted);
    margin-right: 0.2rem;
}

/* The matched run. <mark>'s default is a flat yellow that fights every surface it lands on and is
   unreadable in dark mode; this tints the accent instead and keeps the text colour inherited. */
.nt-conv-hit-mark {
    background: color-mix(in srgb, var(--wa-accent-strong) 22%, transparent);
    color: inherit;
    padding: 0 0.1em;
    border-radius: 0.15rem;
    font-weight: 650;
}

.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;
}

/* The infinite-scroll sentinel row: invisible until its spinner shows during a page fetch. */
.nt-conv-loadmore {
    display: flex;
    justify-content: center;
    padding: 0.5rem 0;
    min-height: 0.25rem;
    color: var(--nt-text-muted);
}

/* ---- list loading skeleton (shown by inbox.js while a pane is fetched) ---- */

.nt-skeleton-list { list-style: none; margin: 0; padding: 0; }

.nt-skel-row {
    display: flex;
    gap: var(--nt-space-3);
    align-items: center;
    padding: var(--nt-space-3) var(--nt-space-4);
    border-bottom: 1px solid #f0f0f0;
}

.nt-skel-avatar {
    flex: 0 0 auto;
    width: 2.9rem;
    height: 2.9rem;
    border-radius: var(--nt-radius-pill);
}

.nt-skel-lines { flex: 1 1 auto; display: flex; flex-direction: column; gap: 0.5rem; }
.nt-skel-line { height: 0.7rem; border-radius: var(--nt-radius-pill); }
.nt-skel-line.is-short { width: 40%; }
.nt-skel-line.is-mid { width: 75%; }

/* The shimmer itself. One keyframe, reused by every skeleton shape in the inbox. Named apart from
   the generic nt-shimmer above: @keyframes share one global namespace, and a second definition
   under the same name silently replaces the first for EVERY user of it — the same last-one-wins
   trap StylesheetCollisionTests guards against for classes. */
.nt-skel {
    background: linear-gradient(100deg, #ececec 30%, #f5f5f5 50%, #ececec 70%);
    background-size: 200% 100%;
    animation: nt-skel-shimmer 1.3s ease-in-out infinite;
}

@keyframes nt-skel-shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .nt-skel { animation: none; }
}

/* ---- thread ---- */

.nt-thread {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.nt-thread-placeholder {
    margin: auto;
    max-width: 26rem;
    text-align: center;
    padding: var(--nt-space-6);
}

.nt-thread-head {
    display: flex;
    align-items: center;
    /* nowrap, never wrap: the name truncates (min-width:0 + ellipsis below) long before it would push
       the window pill, the Details pill or the menu onto a second row. A wrapped header grew a row
       taller than the details column's header beside it, so their hairlines no longer lined up — that
       was the "not aligned" the screenshot shows. One row keeps the three column headers on one band. */
    flex-wrap: nowrap;
    /* Shared height with the details column header (.nt-controls-head) so the two right-hand columns'
       bottom hairlines sit on exactly the same line. */
    min-height: var(--nt-inbox-head-height);
    gap: var(--nt-space-3);
    padding: var(--nt-space-2) var(--nt-space-4);
    background: var(--wa-panel);
    border-bottom: 1px solid var(--wa-panel-line);
}

.nt-thread-back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: var(--nt-radius-pill);
    color: var(--nt-text-muted);
    text-decoration: none;
    font-size: 1.2rem;
}

.nt-thread-back:hover { background: rgba(0, 0, 0, 0.05); color: var(--nt-text); }

/* Takes the slack and gives it back first: everything else in the header holds its natural size, so
   the name is what truncates when the row runs short — not the window pill or the Details pill. */
.nt-thread-who { flex: 1 1 auto; min-width: 0; margin-right: auto; }

/* Avatar, window pill, Details pill and the menu never shrink — they keep their shape on a phone. */
.nt-thread-head > .nt-conv-avatar,
.nt-thread-head > [data-nt-window],
.nt-thread-head > .nt-controls-toggle,
.nt-thread-head > .nt-thread-menu,
.nt-thread-head > .nt-thread-back { flex: 0 0 auto; }

.nt-thread-name {
    font-size: var(--nt-text-md);
    font-weight: 650;
    margin: 0;
    color: #111b21;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nt-thread-sub {
    margin: 0;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
    /* One line: on a phone the number and the "via" line wrapped to three and spilled out of the
       header. The full text stays available to screen readers and on the details panel. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 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-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.
       flex-shrink: 0 matters as much as the cap: overflow-y: auto strips flexbox's automatic
       minimum-size protection, so on any thread tall enough to overflow the screen, the shrink
       algorithm would otherwise compress this flex item (and the send button inside it) right along
       with the scrolling message list — down past its own content size, clipping the button off the
       bottom instead of just capping the composer's growth. */
    .nt-composer {
        max-height: 45%;
        overflow-y: auto;
        flex-shrink: 0;
    }

    /* 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; }
}

/* A narrow phone: the contact's name is what the header is for. The avatar goes (the back arrow and
   the name carry the identity) and the window pill is capped, so the name is not squeezed to a
   stub between them. */
@media (max-width: 480px) {
    .nt-thread-head > .nt-conv-avatar { display: none; }
    .nt-thread-head .nt-pill { max-width: 6.25rem; padding-inline: 0.45rem; text-overflow: ellipsis; }
}

/* The chat wallpaper: WhatsApp's beige with a faint doodle texture, inlined as a data URI so it
   needs no extra request and no external host. */
.nt-thread-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: var(--nt-space-4);
    display: flex;
    flex-direction: column;
    background-color: var(--wa-wallpaper);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80'%3E%3Cg fill='%23000000' fill-opacity='0.03'%3E%3Cpath d='M14 16c0-2 2-4 4-4s4 2 4 4-2 4-4 4-4-2-4-4zm40 30c3 0 5 2 5 5s-2 5-5 5-5-2-5-5 2-5 5-5zM30 52c0-1 1-2 2-2s2 1 2 2-1 2-2 2-2-1-2-2zM62 14c0-1 1-2 2-2s2 1 2 2-1 2-2 2-2-1-2-2z'/%3E%3C/g%3E%3C/svg%3E");
}

.nt-bubbles {
    list-style: none;
    margin: auto auto 0;
    padding: 0;
    width: 100%;
    max-width: 60rem;
}

.nt-day-sep {
    display: flex;
    align-items: center;
    justify-content: center;
    margin: var(--nt-space-4) 0 var(--nt-space-3);
}

/* WhatsApp's centred date chip: a soft off-white pill with a small shadow. */
.nt-day-sep span {
    background: #ffffff;
    border-radius: var(--nt-radius-sm);
    padding: 0.25rem 0.8rem;
    font-size: var(--nt-text-xs);
    font-weight: 500;
    color: #54656f;
    box-shadow: var(--wa-bubble-shadow);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.nt-bubble-row {
    display: flex;
    margin-bottom: 0.35rem;
    padding: 0 0.25rem;
}

.nt-bubble-row.is-out { justify-content: flex-end; }
.nt-bubble-row.is-in { justify-content: flex-start; }

/* The bubble body, plus the little tail via a positioned pseudo-element — the detail that makes it
   read as WhatsApp rather than "a chat app". */
.nt-bubble {
    position: relative;
    max-width: min(80%, 32rem);
    background: var(--wa-in-bubble);
    border-radius: 0.5rem;
    border-top-left-radius: 0;
    padding: 0.375rem 0.5rem 0.5rem 0.6rem;
    box-shadow: var(--wa-bubble-shadow);
    font-size: var(--nt-text-base);
    color: #111b21;
}

.nt-bubble::before {
    content: "";
    position: absolute;
    top: 0;
    width: 0.6rem;
    height: 0.6rem;
    background: inherit;
}

/* Inbound tail on the top-left. */
.nt-bubble-row.is-in .nt-bubble::before {
    left: -0.55rem;
    border-radius: 0 0 0 0;
    clip-path: polygon(100% 0, 0 0, 100% 100%);
}

.nt-bubble-row.is-out .nt-bubble {
    background: var(--wa-out-bubble);
    border-radius: 0.5rem;
    border-top-right-radius: 0;
}

/* Outbound tail on the top-right. */
.nt-bubble-row.is-out .nt-bubble::before {
    right: -0.55rem;
    clip-path: polygon(0 0, 100% 0, 0 100%);
}

.nt-bubble.is-failed {
    background: var(--nt-danger-tint);
}

.nt-bubble.is-failed::before { background: var(--nt-danger-tint); }

.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.03em;
    text-transform: uppercase;
    color: var(--wa-accent-strong);
}

/* Sender line breaks are preserved by CSS, not by turning text into markup. The inline emphasis
   marks (*bold*, _italic_, ~strike~, ```mono```) become real <strong>/<em>/<del>/<code> via
   WhatsAppText, which encodes first — so this styling never colours a sender-authored tag. */
.nt-bubble-text {
    margin: 0;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    line-height: 1.35;
}

.nt-bubble-text strong { font-weight: 700; }
.nt-bubble-text em { font-style: italic; }
.nt-bubble-text del { text-decoration: line-through; }

.nt-bubble-text code {
    font-family: var(--nt-font-mono, ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace);
    font-size: 0.9em;
    background: rgba(0, 0, 0, 0.05);
    padding: 0.05rem 0.25rem;
    border-radius: 4px;
}

/* ---- quoted reply: the message a reply points at, shown above it (WhatsApp-style) ---- */
.nt-quote {
    display: flex;
    flex-direction: column;
    width: 100%;
    text-align: left;
    margin: 0 0 0.3rem;
    padding: 0.25rem 0.45rem;
    border: 0;
    border-radius: 0.35rem;
    background: rgba(0, 0, 0, 0.05);
    box-shadow: inset 3px 0 0 var(--wa-accent-strong);
    cursor: pointer;
    overflow: hidden;
}

.nt-bubble-row.is-out .nt-quote { background: rgba(0, 0, 0, 0.06); }
.nt-quote:hover { background: rgba(0, 0, 0, 0.09); }

.nt-quote-author {
    font-size: var(--nt-text-xs);
    font-weight: 700;
    color: var(--wa-accent-strong);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nt-quote-text {
    font-size: 0.82rem;
    color: var(--nt-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- per-bubble reply affordance, revealed on hover/focus, sitting just toward the centre ---- */
.nt-bubble-reply {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 1.6rem;
    height: 1.6rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: var(--nt-surface);
    color: var(--nt-text-muted);
    box-shadow: var(--wa-bubble-shadow);
    font-size: 0.8rem;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.12s ease;
    z-index: 2;
}

.nt-bubble-row.is-in .nt-bubble-reply { right: -2.1rem; }
.nt-bubble-row.is-out .nt-bubble-reply { left: -2.1rem; }

/* Triggered on the whole row, not just the bubble: the button sits in the gap beside the bubble, so
   keying off .nt-bubble:hover left a dead strip between them where the cursor was over neither element
   and the button vanished mid-reach. The row spans that gap, so the affordance stays put all the way
   to the click. */
.nt-bubble-row:hover .nt-bubble-reply,
.nt-bubble-row:focus-within .nt-bubble-reply,
.nt-bubble-reply:focus-visible {
    opacity: 1;
    pointer-events: auto;
}

.nt-bubble-reply:hover { color: var(--wa-accent-strong); }

/* No hover on touch: keep the affordance faintly visible so it is still reachable. */
@media (hover: none) {
    .nt-bubble-reply { opacity: 0.5; pointer-events: auto; }
}

/* Flash the original when a quoted block is tapped, so the jump is legible. */
.nt-bubble-row.is-quote-jumped .nt-bubble { animation: nt-quote-flash 1.4s ease-out; }

@keyframes nt-quote-flash {
    0%, 100% { box-shadow: var(--wa-bubble-shadow); }
    18% { box-shadow: 0 0 0 3px var(--wa-accent-strong); }
}

@media (prefers-reduced-motion: reduce) {
    .nt-bubble-row.is-quote-jumped .nt-bubble { animation: none; }
}

/* ---- composer reply-target strip ---- */
.nt-reply-strip {
    display: flex;
    align-items: stretch;
    gap: 0.5rem;
    margin-bottom: 6px;
    padding: 6px 8px;
    background: var(--nt-surface);
    border-radius: 12px;
    box-shadow: 0 1px 1px rgba(11, 20, 26, 0.06);
    font-size: max(13px, var(--nt-text-sm));
    animation: nt-composer-rise 0.2s ease-out;
}

.nt-reply-strip-bar {
    flex: 0 0 3px;
    border-radius: 3px;
    background: var(--wa-accent-strong);
}

.nt-reply-strip-body {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.nt-reply-strip-author {
    font-size: var(--nt-text-xs);
    font-weight: 700;
    color: var(--wa-accent-strong);
}

.nt-reply-strip-text {
    font-size: 0.82rem;
    color: var(--nt-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nt-reply-strip-close {
    flex: 0 0 auto;
    border: 0;
    background: transparent;
    color: var(--nt-text-muted);
    cursor: pointer;
    padding: 0 0.25rem;
}

.nt-reply-strip-close:hover { color: var(--nt-danger); }

/* ---- media tile: an attachment WhatsApp would show inline; here, a labelled stand-in ---- */

.nt-media-tile {
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    padding: var(--nt-space-2) var(--nt-space-3);
    margin-bottom: var(--nt-space-1);
    background: rgba(0, 0, 0, 0.04);
    border-radius: var(--nt-radius-sm);
}

.nt-media-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--nt-radius-sm);
    background: var(--wa-accent);
    color: #ffffff;
    font-size: 1.2rem;
}

.nt-bubble-row.is-in .nt-media-icon { background: #8696a0; }

.nt-media-meta { display: flex; flex-direction: column; min-width: 0; }

.nt-media-kind {
    font-size: var(--nt-text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--nt-text-muted);
}

.nt-media-name {
    font-size: var(--nt-text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 16rem;
}

/* ---- openable / downloadable media: real bytes proxied from Meta on demand (§6.6) ---- */

/* Image and video share a figure so the download button can float over the top-right corner. */
.nt-media-figure {
    position: relative;
    margin: 0 0 var(--nt-space-1);
    max-width: 17rem;
}

.nt-media-open { display: block; line-height: 0; }

.nt-media-img,
.nt-media-video {
    display: block;
    width: 100%;
    max-width: 17rem;
    max-height: 20rem;
    border-radius: var(--nt-radius-sm);
    object-fit: cover;
    background: rgba(0, 0, 0, 0.04);
}

.nt-media-video { object-fit: contain; }

.nt-media-img { cursor: zoom-in; }

/* The little round download button that overlays image/video, and sits inline for audio. */
.nt-media-dl {
    position: absolute;
    top: 0.35rem;
    right: 0.35rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    color: #ffffff;
    font-size: 0.95rem;
    text-decoration: none;
    transition: background-color 0.12s ease;
}

.nt-media-dl:hover,
.nt-media-dl:focus-visible {
    background: rgba(0, 0, 0, 0.75);
    color: #ffffff;
}

/* Voice notes: the native audio control with the download button tucked alongside it. */
.nt-media-audio {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    margin-bottom: var(--nt-space-1);
}

.nt-media-audio-el {
    height: 2.25rem;
    max-width: 15rem;
}

.nt-media-audio .nt-media-dl {
    position: static;
    flex: 0 0 auto;
    background: var(--wa-accent);
}

.nt-bubble-row.is-in .nt-media-audio .nt-media-dl { background: #8696a0; }

/* A document tile is itself the download link: clickable, with a trailing download glyph. */
a.nt-media-file {
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    transition: background-color 0.12s ease;
}

a.nt-media-file:hover,
a.nt-media-file:focus-visible { background: rgba(0, 0, 0, 0.08); }

.nt-media-file .nt-media-name { color: inherit; }

.nt-media-dl-inline {
    display: inline-flex;
    align-items: center;
    margin-left: auto;
    padding-left: var(--nt-space-2);
    color: var(--nt-text-muted);
    font-size: 1.1rem;
}

/* Time + tick, tucked to the bottom-right of the bubble the way WhatsApp does it. */
.nt-bubble-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.25rem;
    margin-top: 0.1rem;
    float: right;
    margin-left: var(--nt-space-3);
    font-size: 0.6875rem;
    color: var(--wa-meta);
    line-height: 1;
}

.nt-bubble-foot time { white-space: nowrap; }

.nt-bubble-error {
    clear: both;
    margin: var(--nt-space-2) 0 0;
    font-size: var(--nt-text-sm);
    color: var(--nt-danger);
}

/* Delivery ticks. The word is always present in the accessible name; colour is the redundant signal.
   The tick label text itself is visually hidden on the bubble (WhatsApp shows only the glyph), but
   it stays in the DOM as the accessible name a screen reader announces. */
.nt-tick {
    display: inline-flex;
    align-items: center;
}

/* The tick is a button (it opens the delivery-timeline popup), so strip the native chrome back to
   the plain glyph it used to be and make it clearly tappable. */
button.nt-tick {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    line-height: 1;
    cursor: pointer;
    border-radius: 0.25rem;
}

button.nt-tick:hover { opacity: 0.75; }
button.nt-tick:focus-visible {
    outline: 2px solid var(--nt-focus, currentColor);
    outline-offset: 2px;
}

.nt-tick .bi { font-size: 0.95rem; }
.nt-tick-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}
.nt-tick-plain { color: var(--wa-tick-plain); }
.nt-tick-read { color: var(--wa-tick-read); }
.nt-tick-failed { color: var(--nt-danger); }

/* ---- delivery-timeline popup: opened by clicking a message's tick (§13) ---- */

.nt-tick-modal {
    position: fixed;
    inset: 0;
    z-index: 1090;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--nt-space-4);
}

.nt-tick-modal[hidden] { display: none; }

.nt-tick-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.35);
}

.nt-tick-modal-panel {
    position: relative;
    width: min(22rem, 100%);
    background: var(--nt-surface);
    color: var(--nt-text);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow-lg);
    padding: var(--nt-space-4);
}

.nt-tick-modal-title {
    margin: 0 0 var(--nt-space-3);
    font-size: var(--nt-text-md);
}

.nt-tick-timeline {
    list-style: none;
    margin: 0;
    padding: 0;
}

.nt-tick-step {
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    padding: var(--nt-space-2) 0;
}

.nt-tick-step + .nt-tick-step { border-top: 1px solid var(--nt-border); }

.nt-tick-step-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    flex: 0 0 auto;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-surface-2);
    color: var(--nt-text-muted);
}

/* A step that has actually happened reads in the "read" tone, so the timeline shows progress at a glance. */
.nt-tick-step.is-done .nt-tick-step-icon {
    background: color-mix(in srgb, var(--wa-tick-read) 16%, transparent);
    color: var(--wa-tick-read);
}

.nt-tick-step-body {
    display: flex;
    flex-direction: column;
    line-height: 1.3;
}

.nt-tick-step-name { font-weight: 600; }

.nt-tick-step-time {
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

.nt-tick-modal-foot {
    margin-top: var(--nt-space-4);
    display: flex;
    justify-content: flex-end;
}

/* ---- edit-contact modal: opened from the pencil in the details panel (§9) ---- */

.nt-contact-edit-btn { margin-top: var(--nt-space-2); }

.nt-contact-modal {
    position: fixed;
    inset: 0;
    z-index: 1090;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--nt-space-4);
}

.nt-contact-modal[hidden] { display: none; }

.nt-contact-modal-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.35);
}

.nt-contact-modal-panel {
    position: relative;
    width: min(26rem, 100%);
    max-height: min(90vh, 44rem);
    display: flex;
    flex-direction: column;
    background: var(--nt-surface);
    color: var(--nt-text);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow-lg);
    overflow: hidden;
}

.nt-contact-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-2);
    padding: var(--nt-space-3) var(--nt-space-4);
    border-bottom: 1px solid var(--nt-border);
}

.nt-contact-modal-title { margin: 0; font-size: var(--nt-text-md); }

/* The one scrolling region, so a long tag/field list never pushes the footer off-screen. */
.nt-contact-modal-body {
    padding: var(--nt-space-4);
    overflow-y: auto;
}

.nt-contact-modal-section {
    margin-top: var(--nt-space-4);
    padding-top: var(--nt-space-4);
    border-top: 1px solid var(--nt-border);
}

.nt-contact-form .nt-field + .nt-field { margin-top: var(--nt-space-3); }
.nt-contact-form-actions { margin-top: var(--nt-space-4); }

.nt-contact-attrs { margin: 0; }

.nt-contact-attr {
    display: flex;
    justify-content: space-between;
    gap: var(--nt-space-3);
    padding: var(--nt-space-1) 0;
}

.nt-contact-attr dt { font-weight: 600; color: var(--nt-text-muted); margin: 0; }
.nt-contact-attr dd { margin: 0; text-align: right; word-break: break-word; }

.nt-contact-modal-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: var(--nt-space-2);
    padding: var(--nt-space-3) var(--nt-space-4);
    border-top: 1px solid var(--nt-border);
}

/* ---- Contacts-page CRUD modals (view/edit/delete/create + tag manager, §9) ---- */

/* Lock the page behind an open modal so a phone doesn't scroll the list under it. */
html.nt-modal-open,
html.nt-modal-open body { overflow: hidden; }

/* Centred spinner while a modal body is fetched. */
.nt-modal-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 8rem;
    color: var(--nt-text-muted);
}

/* Modal head: avatar + name + phone. */
.nt-contact-modal-who {
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    min-width: 0;
}

.nt-contact-modal-sub {
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
    overflow-wrap: anywhere;
}

/* A compact key/value grid for the at-a-glance facts at the top of the card. */
.nt-kv-tight {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--nt-space-1) var(--nt-space-3);
    margin: 0;
}

.nt-kv-tight > div {
    display: contents;
}

.nt-kv-tight dt {
    font-weight: 600;
    color: var(--nt-text-muted);
    margin: 0;
}

.nt-kv-tight dd {
    margin: 0;
    text-align: right;
    overflow-wrap: anywhere;
}

/* The small "×" that removes a custom field, sitting after its value. */
.nt-contact-attr dd {
    display: inline-flex;
    align-items: center;
    gap: var(--nt-space-2);
}

.nt-attr-remove {
    border: 0;
    background: none;
    padding: 0;
    line-height: 1;
    color: var(--nt-text-muted);
    cursor: pointer;
}

.nt-attr-remove:hover { color: var(--nt-danger); }

/* Conversation links inside the card. */
.nt-simple-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.nt-simple-list li + li { margin-top: var(--nt-space-1); }

/* Footer: a flexible spacer pushes the "Open full page" link away from the actions. */
.nt-spacer { flex: 1 1 auto; }

/* ---- tag manager ---- */

.nt-tag-create {
    display: flex;
    gap: var(--nt-space-2);
    align-items: center;
}

.nt-tag-create input:not([type=color]) { flex: 1 1 auto; min-width: 0; }

.nt-tag-manage-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.nt-tag-manage-row {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    padding: var(--nt-space-2) 0;
    flex-wrap: wrap;
}

.nt-tag-manage-row + .nt-tag-manage-row { border-top: 1px solid var(--nt-border); }

.nt-tag-manage-edit {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    flex: 1 1 12rem;
    min-width: 0;
}

.nt-tag-manage-edit input:not([type=color]) { flex: 1 1 auto; min-width: 0; }

.nt-tag-manage-count {
    font-size: var(--nt-text-sm);
    margin-inline-start: auto;
    white-space: nowrap;
}

/* Bootstrap's color input is oversized by default; keep it a neat swatch beside the text field. */
.form-control-color { max-width: 2.75rem; padding: 0.15rem; }

/* The shared confirm dialog (used by a delete inside a modal) must sit ABOVE these CRUD modals
   (z-index 1090), or a "Delete this contact?" prompt would open behind the very modal that raised it. */
.nt-confirm-modal.modal { z-index: 1130; }

/* ---- typing indicator: WhatsApp's three bouncing dots in a small inbound bubble ---- */

.nt-typing {
    display: flex;
    align-items: center;
    margin: 0;
    padding: 0 var(--nt-space-4) var(--nt-space-2);
}

.nt-typing[hidden] { display: none; }

.nt-typing-bubble {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.6rem 0.75rem;
    background: var(--wa-in-bubble);
    border-radius: 0.6rem;
    border-top-left-radius: 0;
    box-shadow: var(--wa-bubble-shadow);
}

.nt-typing-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: #9aa6ac;
    animation: nt-typing-bounce 1.2s infinite ease-in-out;
}

.nt-typing-dot:nth-child(2) { animation-delay: 0.18s; }
.nt-typing-dot:nth-child(3) { animation-delay: 0.36s; }

@keyframes nt-typing-bounce {
    0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
    30% { transform: translateY(-0.35rem); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .nt-typing-dot { animation: none; opacity: 0.7; }
}

/* A freshly arrived / just-sent bubble eases in, so live updates feel alive rather than jumping. */
@keyframes nt-bubble-in {
    from { opacity: 0; transform: translateY(0.5rem); }
    to { opacity: 1; transform: translateY(0); }
}

.nt-bubble-row.is-fresh .nt-bubble { animation: nt-bubble-in 0.22s ease-out; }

@media (prefers-reduced-motion: reduce) {
    .nt-bubble-row.is-fresh .nt-bubble { animation: none; }
}

/* ---- thread loading skeleton (injected by inbox.js during a navigation) ---- */

.nt-thread-skeleton {
    flex: 1 1 auto;
    min-height: 0;
    padding: var(--nt-space-5) var(--nt-space-4);
    background-color: var(--wa-wallpaper);
    display: flex;
    flex-direction: column;
    gap: var(--nt-space-3);
}

.nt-skel-bubble {
    height: 2.4rem;
    max-width: 60%;
    border-radius: 0.5rem;
    align-self: flex-start;
    background: #ffffff;
    box-shadow: var(--wa-bubble-shadow);
    opacity: 0.7;
}

.nt-skel-bubble.is-out { align-self: flex-end; background: var(--wa-out-bubble); }
.nt-skel-bubble.is-wide { max-width: 72%; height: 3.4rem; }
.nt-skel-bubble.is-narrow { max-width: 35%; }

/* ---- composer ---- */

.nt-composer {
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    background: var(--wa-panel);
    border-top: 1px solid var(--wa-panel-line);
    /* Pixel floors: the laptop density tier (12px root at 992-1439px) must never shrink the box you
       type into below readable and tappable. */
    padding: max(8px, var(--nt-space-2)) max(10px, var(--nt-space-3));
    /* On a notched iPhone or gesture-nav Android, the composer sat flush against the strip the OS
       reserves for its own home/back swipe — a tap on the send button landed on the system gesture
       instead of the page. viewport-fit=cover (site layout) lets env() resolve to the real inset;
       elsewhere it is 0 and this is a no-op. */
    padding-bottom: calc(max(8px, var(--nt-space-2)) + env(safe-area-inset-bottom, 0px));
}

.nt-composer-form { position: relative; }

/* The composer is a flex bar: a pill-shaped input that grows, and a round send button beside it. */
.nt-composer-bar {
    display: flex;
    align-items: flex-end;
    gap: max(6px, var(--nt-space-2));
}

.nt-composer-field {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
}

.nt-composer-field .form-control {
    display: block;
    min-height: 44px;
    max-height: 160px;
    border-radius: 22px;
    border: 1px solid transparent;
    background: var(--nt-surface);
    resize: none;
    overflow-y: auto;
    padding: 11px 16px;
    font-size: max(15px, var(--nt-text-base));
    line-height: 1.4;
    box-shadow: 0 1px 1px rgba(11, 20, 26, 0.06);
    transition: border-color 0.18s ease, box-shadow 0.18s ease;
}

.nt-composer-field .form-control::placeholder {
    color: var(--wa-meta);
    /* One line: a wrapped placeholder peeked into the one-line box on a narrow phone. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nt-composer-field .form-control:focus {
    border-color: var(--wa-accent);
    box-shadow: 0 0 0 3px rgba(0, 168, 132, 0.16);
}

/* The round green send button, with a spinner state for while a post is in flight. */
.nt-send-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border: 0;
    border-radius: 50%;
    background: var(--wa-accent);
    color: #ffffff;
    font-size: 18px;
    cursor: pointer;
    box-shadow: 0 2px 6px rgba(0, 128, 105, 0.3);
    transition: background-color 0.15s ease, transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1),
        opacity 0.2s ease, box-shadow 0.2s ease;
}

.nt-send-btn:hover { background: var(--wa-accent-strong); box-shadow: 0 4px 12px rgba(0, 128, 105, 0.35); }
.nt-send-btn:active { transform: scale(0.9); }

/* Resting while the box is empty; springs up the moment there is something to send. */
.nt-composer-bar:not(.has-text) .nt-send-btn { opacity: 0.55; transform: scale(0.9); box-shadow: none; }
.nt-composer-bar.has-text .nt-send-icon { animation: nt-send-pop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1); }

@keyframes nt-send-pop {
    0% { transform: translateX(-4px) rotate(-12deg) scale(0.7); opacity: 0; }
    100% { transform: none; opacity: 1; }
}

.nt-send-btn:focus-visible {
    outline: 2px solid var(--wa-accent-strong);
    outline-offset: 2px;
}

.nt-send-icon { display: inline-block; }

.nt-send-spinner {
    display: none;
    width: 1.15rem;
    height: 1.15rem;
    border: 2px solid rgba(255, 255, 255, 0.5);
    border-top-color: #ffffff;
    border-radius: 50%;
    animation: nt-spin 0.7s linear infinite;
}

/* While sending: swap the paper-plane for the spinner and lock the button. */
.nt-send-btn.is-sending { pointer-events: none; }
.nt-send-btn.is-sending .nt-send-icon { display: none; }
.nt-send-btn.is-sending .nt-send-spinner { display: inline-block; }

/* ---- media send: the paperclip attach button and the pending-file preview (§6.5) ---- */

/* A round, muted twin of the send button. It is a <label>, so reset its label defaults. */
.nt-attach-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin: 0;
    border-radius: 50%;
    background: var(--nt-surface);
    color: var(--nt-text-muted);
    font-size: 19px;
    cursor: pointer;
    box-shadow: 0 1px 1px rgba(11, 20, 26, 0.06);
    transition: background-color 0.15s ease, color 0.15s ease;
}

.nt-attach-btn .bi { transition: transform 0.2s ease; }
.nt-attach-btn:hover .bi { transform: rotate(-20deg); }

.nt-attach-btn:hover,
.nt-attach-btn:focus-within {
    background: var(--nt-surface-2, rgba(0, 0, 0, 0.06));
    color: var(--nt-text);
}

/* The media form sits first in the composer and holds only a hidden file input until a file is
   staged, so it takes no space until its preview card appears (which then replaces the text bar). */
.nt-media-form { margin: 0; }

.nt-media-pending {
    display: flex;
    flex-direction: column;
    gap: max(8px, var(--nt-space-2));
    padding: max(10px, var(--nt-space-3));
    background: var(--nt-surface);
    border-radius: 14px;
    box-shadow: 0 2px 10px rgba(11, 20, 26, 0.08);
    animation: nt-composer-rise 0.22s ease-out;
}

@keyframes nt-composer-rise {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: none; }
}

.nt-media-pending-info {
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    min-width: 0;
}

.nt-media-pending-thumb {
    flex: 0 0 auto;
    width: 3rem;
    height: 3rem;
    object-fit: cover;
    border-radius: var(--nt-radius-sm);
}

.nt-media-pending-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 3rem;
    height: 3rem;
    border-radius: var(--nt-radius-sm);
    background: var(--wa-accent);
    color: #ffffff;
    font-size: 1.3rem;
}

.nt-media-pending-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1 1 auto;
}

.nt-media-pending-name {
    font-size: var(--nt-text-sm);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nt-media-pending-size { font-size: var(--nt-text-xs); }

.nt-media-pending-remove {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--nt-text-muted);
    cursor: pointer;
}

.nt-media-pending-remove:hover { background: rgba(0, 0, 0, 0.08); color: var(--nt-text); }

.nt-media-pending-send {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
}

.nt-media-pending-send .form-control { flex: 1 1 auto; min-width: 0; }

/* The single Send: shows label normally, a spinner while the POST is in flight, and locks (disabled)
   so a second click cannot fire a duplicate send before the page redirects. */
.nt-media-pending-btn { flex: 0 0 auto; min-width: 5.25rem; }
.nt-media-pending-btn:disabled { opacity: 0.75; cursor: default; }

.nt-media-send-face { display: inline-flex; align-items: center; }

.nt-media-send-spinner {
    display: inline-block;
    width: 1rem;
    height: 1rem;
    border: 2px solid rgba(255, 255, 255, 0.5);
    border-top-color: #ffffff;
    border-radius: 50%;
    animation: nt-spin 0.7s linear infinite;
}

.nt-media-pending-error {
    margin: 0;
    color: var(--nt-danger, #dc3545);
    font-size: var(--nt-text-sm);
}

@keyframes nt-spin { to { transform: rotate(360deg); } }

/* Touch screens (iPad, phones, touch laptops): Safari zooms the whole page when a field under 16px is
   focused, which is what threw the composer off-screen on an iPad in landscape, where the density
   tier had the box at 11px. 16px stops it, and the header controls grow to finger size. */
@media (pointer: coarse) {
    .nt-composer-field .form-control,
    .nt-media-pending-send .form-control,
    .nt-template-row .form-select { font-size: 16px; }

    .nt-controls-toggle,
    .nt-thread-menu-btn,
    .nt-thread-back { min-width: 40px; min-height: 40px; }

    .nt-media-pending-remove { width: 40px; height: 40px; }
}

@media (prefers-reduced-motion: reduce) {
    .nt-send-btn,
    .nt-attach-btn .bi { transition: none; }

    .nt-composer-bar.has-text .nt-send-icon,
    .nt-media-pending,
    .nt-reply-strip,
    .nt-qr-list { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
    .nt-send-spinner { animation-duration: 1.4s; }
}

.nt-composer-locked {
    background: var(--nt-surface);
    margin-top: var(--nt-space-3);
    border-radius: var(--nt-radius);
}

.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);
    transform-origin: bottom center;
    animation: nt-composer-rise 0.16s ease-out;
    font-size: max(14px, var(--nt-text-base));
    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);
    background: var(--nt-surface);
    padding: var(--nt-space-3);
    border-radius: var(--nt-radius);
}

.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; }

/* ---- quick-controls panel: the third pane (contact, automation, flow, drip, agent) ----
   Kept INSIDE the thread so a live re-render replaces it with everything else, and so a phone can
   slide it in over the conversation. On a wide screen it is a static right-hand column that steals
   width from the message area; below that it is an off-canvas drawer toggled from the header. */

.nt-thread-main {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
    position: relative;
}

.nt-thread-convo {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
}

/* The header control that opens the details panel. On phone/tablet it is the single door to
   everything the details column holds, so it reads as a labelled "Details" pill sitting beside the
   number — not a bare icon a first-time agent has to guess at. Hidden on wide screens (≥1200px) where
   the panel is a permanent column and needs no opener. */
.nt-controls-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--nt-space-1);
    height: 2rem;
    padding: 0 0.7rem;
    border-radius: var(--nt-radius-pill);
    border: 1px solid var(--wa-panel-line);
    background: var(--nt-surface);
    color: var(--nt-text-muted);
    font-size: var(--nt-text-xs);
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    flex: 0 0 auto;
}

.nt-controls-toggle .bi { font-size: 1rem; }

.nt-controls-toggle:hover {
    background: var(--wa-panel);
    border-color: var(--nt-text-faint);
    color: var(--nt-text);
}

.nt-controls-toggle[aria-expanded="true"] {
    background: var(--wa-panel);
    color: var(--nt-text);
}

/* On the narrowest phones the label would crowd a long name; drop to the icon-only circle there and
   let the number keep the width. The pill returns as soon as there is room. */
@media (max-width: 400px) {
    .nt-controls-toggle { width: 2rem; padding: 0; }
    .nt-controls-toggle-label { display: none; }
}

/* The three-dot actions menu (block, clear media, clear chat) in the thread header. Styled like the
   controls toggle, but shown at every width — the destructive actions stay reachable on desktop too. */
.nt-thread-menu { flex: 0 0 auto; }

.nt-thread-menu-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: var(--nt-radius-pill);
    border: 0;
    background: transparent;
    color: var(--nt-text-muted);
    font-size: 1.15rem;
    cursor: pointer;
}

.nt-thread-menu-btn:hover,
.nt-thread-menu-btn.show { background: rgba(0, 0, 0, 0.05); color: var(--nt-text); }

/* Off-canvas by default: fixed to the right edge, slid out of view until the toggle checkbox is on. */
.nt-controls {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    width: min(22rem, 88vw);
    background: var(--nt-surface);
    border-left: 1px solid var(--wa-panel-line);
    overflow-y: auto;
    transform: translateX(100%);
    transition: transform 0.22s ease;
    -webkit-overflow-scrolling: touch;
}

.nt-controls-backdrop {
    position: fixed;
    inset: 0;
    z-index: 39;
    background: rgba(0, 0, 0, 0.35);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.22s ease, visibility 0.22s ease;
}

/* Open state is a class on the persistent shell root (.nt-inbox), toggled by inbox.js, so the panel
   stays open across a live thread re-render (the panel element is replaced, the root is not). */
.nt-inbox.nt-controls-open .nt-controls { transform: none; }
.nt-inbox.nt-controls-open .nt-controls-backdrop { opacity: 1; visibility: visible; }

@media (prefers-reduced-motion: reduce) {
    .nt-controls, .nt-controls-backdrop { transition: none; }
}

/* Wide screens: the panel is a permanent right-hand column that narrows the message area, and the
   toggle/backdrop disappear. This is the "reduce the messaging col, add a controls col" layout.
   From 1400px, not 1200: between the two, a docked panel left the chat under 500px on a 14-inch
   laptop, so there it stays a drawer behind the Details button instead. */
@media (min-width: 1400px) {
    .nt-controls {
        position: static;
        transform: none;
        z-index: auto;
        flex: 0 0 20rem;
        width: 20rem;
    }

    .nt-controls-backdrop { display: none; }
    .nt-controls-toggle { display: none; }
}

/* The panel's own furniture: a header, grouped sections, and a close button (drawer mode only). */
.nt-controls-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-2);
    /* Matches the thread header's height so the two right-hand columns share one hairline. */
    min-height: var(--nt-inbox-head-height);
    padding: var(--nt-space-2) var(--nt-space-4);
    border-bottom: 1px solid var(--wa-panel-line);
    background: var(--wa-panel);
    position: sticky;
    top: 0;
    z-index: 1;
}

.nt-controls-head h2 { margin: 0; font-size: var(--nt-text-md); font-weight: 650; }

.nt-controls-close {
    border: 0;
    background: transparent;
    color: var(--nt-text-muted);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    padding: 0.15rem 0.4rem;
    border-radius: var(--nt-radius-sm);
}

.nt-controls-close:hover { background: rgba(0, 0, 0, 0.06); color: var(--nt-text); }

@media (min-width: 1400px) { .nt-controls-close { display: none; } }

.nt-controls-section {
    padding: var(--nt-space-4);
    border-bottom: 1px solid var(--nt-border);
}

.nt-controls-section:last-child { border-bottom: 0; }

.nt-controls-title {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    margin: 0 0 var(--nt-space-3);
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--nt-text-faint);
}

/* The contact identity block at the top of the panel. */
.nt-controls-identity { text-align: center; padding-top: var(--nt-space-2); }

.nt-controls-identity .nt-conv-avatar {
    width: 4.5rem;
    height: 4.5rem;
    font-size: var(--nt-text-xl);
    margin: 0 auto var(--nt-space-2);
}

.nt-controls-name { font-size: var(--nt-text-md); font-weight: 650; margin: 0; word-break: break-word; }
.nt-controls-phone { font-size: var(--nt-text-sm); color: var(--nt-text-muted); margin: 0; }

/* A label/value line for contact facts. */
.nt-fact {
    display: flex;
    justify-content: space-between;
    gap: var(--nt-space-3);
    padding: 0.35rem 0;
    font-size: var(--nt-text-sm);
}

.nt-fact + .nt-fact { border-top: 1px dashed var(--nt-border); }
.nt-fact-key { color: var(--nt-text-muted); flex: 0 0 auto; }
.nt-fact-val { text-align: right; min-width: 0; overflow-wrap: anywhere; }

/* A control row: a label with a description, and an action (toggle/select/button) beneath it. */
.nt-control-row + .nt-control-row { margin-top: var(--nt-space-3); }
.nt-control-row .form-select { margin-top: var(--nt-space-2); }
.nt-control-row .btn { margin-top: var(--nt-space-2); }

/* The auto-reply switch. A plain submit button (works with scripting off — it posts and redirects),
   styled as a WhatsApp-style toggle whose "on" state is a class, not a checkbox. */
.nt-switch-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--nt-space-2);
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--nt-text);
    font: inherit;
    cursor: pointer;
    width: 100%;
    justify-content: space-between;
}

.nt-switch-track {
    position: relative;
    width: 2.4rem;
    height: 1.35rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-border-strong);
    transition: background-color 0.15s ease;
    flex: 0 0 auto;
}

.nt-switch-track::after {
    content: "";
    position: absolute;
    top: 0.15rem;
    left: 0.15rem;
    width: 1.05rem;
    height: 1.05rem;
    border-radius: 50%;
    background: #fff;
    box-shadow: var(--nt-shadow-sm);
    transition: transform 0.15s ease;
}

.nt-switch-track.is-on { background: var(--wa-accent); }
.nt-switch-track.is-on::after { transform: translateX(1.05rem); }

.nt-switch-btn:focus-visible .nt-switch-track {
    outline: 2px solid var(--wa-accent-strong);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .nt-switch-track, .nt-switch-track::after { transition: none; }
}

/* ---- quick replies management screen ---- */

.nt-key-list {
    margin: 0;
    padding-left: 1.1rem;
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

.nt-key-list li { margin-bottom: 0.25rem; }

/* The saved-reply body, clamped to two lines in the list so a long canned message does not stretch
   the row. The sender's line breaks are kept for the part that shows. */
.nt-qr-body-preview {
    color: var(--nt-text-muted);
    font-size: var(--nt-text-sm);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    max-width: 30rem;
}

/* ====================================================== 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; }
}

/* ------------------------------------------------------- contacts data table
   The contact book reads as a real data table — a column header that stays put
   while the body scrolls — yet every row is still a lifted card. Header and rows
   share one grid template (--nt-dt-cols) so the columns line up, and the accent
   stripe is an inset shadow rather than a left border, so it can never nudge the
   columns out of alignment. Rows cycle a muted six-colour accent set; each
   tint/ink pair keeps the avatar initials at AA on its tint. */
.nt-dtable {
    --nt-dt-cols: minmax(0, 2.6fr) minmax(0, 1.6fr) 8rem 9rem 5.5rem;
    overflow: hidden;
}

.nt-dtable-scroll {
    max-height: min(64vh, 42rem);
    overflow-y: auto;
    overscroll-behavior: contain;
}

.nt-dtable-head {
    position: sticky;
    top: 0;
    z-index: 2;
    display: grid;
    grid-template-columns: var(--nt-dt-cols);
    gap: var(--nt-space-3);
    align-items: center;
    /* Left/right pad = body inset (space-5) + a row's own inset (space-4), so a
       header label sits directly over its column's cell content. */
    padding: var(--nt-space-3) calc(var(--nt-space-5) + var(--nt-space-4));
    background: var(--nt-surface-2);
    border-bottom: 1px solid var(--nt-border);
    font-size: var(--nt-text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--nt-text-muted);
}

.nt-dtable-body {
    display: flex;
    flex-direction: column;
    gap: var(--nt-space-2);
    padding: var(--nt-space-4) var(--nt-space-5);
}

.nt-dtable-row {
    display: grid;
    grid-template-columns: var(--nt-dt-cols);
    gap: var(--nt-space-3);
    align-items: center;
    padding: var(--nt-space-3) var(--nt-space-4);
    color: inherit;
    text-decoration: none;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    box-shadow: inset 4px 0 0 var(--nt-dt-accent, var(--nt-brand)), var(--nt-shadow-sm);
    transition: box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
}

.nt-dtable-row:hover {
    border-color: var(--nt-border-strong);
    box-shadow: inset 4px 0 0 var(--nt-dt-accent, var(--nt-brand)), var(--nt-shadow);
    transform: translateY(-1px);
}

.nt-dtable-row:focus-visible {
    outline: 2px solid var(--nt-brand);
    outline-offset: -2px;
}

/* Six-colour accent cycle. Each pair is muted; the ink clears AA on its tint. */
.nt-dtable-row:nth-child(6n+1) { --nt-dt-accent: #0b7a5c; --nt-dt-tint: #eaf7f2; --nt-dt-ink: #075440; }
.nt-dtable-row:nth-child(6n+2) { --nt-dt-accent: #1d5c8f; --nt-dt-tint: #e9f2f9; --nt-dt-ink: #164a73; }
.nt-dtable-row:nth-child(6n+3) { --nt-dt-accent: #b7791f; --nt-dt-tint: #fdf3e2; --nt-dt-ink: #8a5300; }
.nt-dtable-row:nth-child(6n+4) { --nt-dt-accent: #6b46c1; --nt-dt-tint: #f1ecfb; --nt-dt-ink: #54369c; }
.nt-dtable-row:nth-child(6n+5) { --nt-dt-accent: #0f7d82; --nt-dt-tint: #e4f5f5; --nt-dt-ink: #0c5a5e; }
.nt-dtable-row:nth-child(6n+6) { --nt-dt-accent: #c0395f; --nt-dt-tint: #fdecef; --nt-dt-ink: #9a2b4a; }

.nt-dt-contact {
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    min-width: 0;
}

.nt-dt-avatar {
    flex: 0 0 auto;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-dt-tint, var(--nt-brand-050));
    color: var(--nt-dt-ink, var(--nt-brand-700));
    display: grid;
    place-items: center;
    font-size: var(--nt-text-sm);
    font-weight: 700;
}

.nt-dt-id { min-width: 0; }

.nt-dt-tags {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--nt-space-1);
    min-width: 0;
}

.nt-dt-time {
    font-size: var(--nt-text-sm);
    color: var(--nt-text-faint);
}

/* Phone: the column header cannot help, so drop it and let each row stack its
   cells into a self-labelling card. Page scrolls naturally — no inner scroll. */
@media (max-width: 47.99em) {
    .nt-dtable { --nt-dt-cols: 1fr; }
    .nt-dtable-head { display: none; }
    .nt-dtable-scroll { max-height: none; overflow: visible; }
    .nt-dtable-body { padding: var(--nt-space-3); }
    .nt-dtable-row { gap: var(--nt-space-2); }
    .nt-dt-empty { display: none; }
}

.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); }
}

/* An even split for composer screens where the preview deserves as much room as the form. */
@media (min-width: 62em) {
    .nt-split-even { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

.nt-split-main > .nt-card + .nt-card,
.nt-split-side > .nt-card + .nt-card { margin-top: var(--nt-space-4); }

/* A card's short fields on one line: four equal columns on desktop, two on tablet, a stack on
   phones. Tighter than .nt-map-grid on purpose — these are dropdowns, not content. */
.nt-field-row {
    display: grid;
    gap: var(--nt-space-3);
    grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 36em) {
    .nt-field-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 62em) {
    .nt-field-row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.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));
}

/* The campaign variable picker: one row per {{n}} — its token, the source it draws from, and the
   fallback sent when a contact has no value. The token is a fixed-width chip so the selects line up
   down the column; the row stacks on narrow screens. */
.nt-var-list { display: grid; gap: var(--nt-space-2); }

.nt-var-row {
    display: grid;
    grid-template-columns: auto minmax(9rem, 1fr) minmax(9rem, 1.2fr);
    gap: var(--nt-space-2);
    align-items: center;
}

.nt-var-token {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.75rem;
    padding: 0.3rem 0.4rem;
    background: var(--nt-surface-2);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-sm);
    color: var(--nt-text-muted);
    font-size: var(--nt-text-sm);
}

@media (max-width: 34em) {
    .nt-var-row { grid-template-columns: auto 1fr; }
    .nt-var-row input { grid-column: 1 / -1; }
}

/* The review step's read-only echo of the same bindings: token chip, then what fills it. */
.nt-var-summary {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--nt-space-2);
}

.nt-var-summary li {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    min-width: 0;
    font-size: var(--nt-text-sm);
}

.nt-var-summary li > span:last-child { min-width: 0; overflow-wrap: anywhere; }

/* Step two's audience picker: audiences grouped under a small heading (Everyone / Lists /
   Segments / Tags), with an optional search box in the card header when there are many. */
.nt-audience-group + .nt-audience-group { margin-top: var(--nt-space-4); }

.nt-audience-group-head {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0 0 var(--nt-space-2);
    font-size: var(--nt-text-sm);
    font-weight: 650;
    color: var(--nt-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.nt-audience-search {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    color: var(--nt-text-faint);
}

.nt-audience-search input { min-width: 12rem; }

.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 */

/* The library is a data table, not a card grid: a template's height depends on its body and how
   many buttons it has, so a grid of them rendered ragged. The card wraps the table and clips its
   corners to the card radius; the full preview lives in a per-row dialog (.modal). */
.nt-card-flush { overflow: hidden; }

/* Let the name column take the slack and keep the pill/quality/actions columns to their content,
   so a long template name wraps rather than crushing the badges. */
.nt-table-templates > thead > tr > th:first-child,
.nt-table-templates > tbody > tr > td:first-child { width: 40%; }

.nt-template-cell-name { overflow-wrap: anywhere; }

/* A rejection or pause reason, shown under the name so the owner sees why a row needs attention
   without opening it. Meta's own words, passed through as text (§16). */
.nt-template-cell-reason {
    display: flex;
    align-items: baseline;
    gap: var(--nt-space-2);
    margin-top: var(--nt-space-2);
    font-size: var(--nt-text-xs);
    color: var(--nt-danger);
}

.nt-template-cell-reason .bi { flex: 0 0 auto; }

.nt-template-modal-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--nt-space-1) var(--nt-space-3);
    min-width: 0;
}

.nt-template-modal-head .modal-title { overflow-wrap: anywhere; }

.nt-template-badges {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-2);
}

.nt-template-hint {
    display: flex;
    align-items: baseline;
    gap: var(--nt-space-2);
}

.nt-template-hint .bi { flex: 0 0 auto; }

/* Count strip above the grid: what is here, what can be sent, and what needs a look. */
.nt-template-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--nt-space-2) var(--nt-space-4);
    margin-bottom: var(--nt-space-4);
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

.nt-template-summary-item {
    display: inline-flex;
    align-items: center;
    gap: var(--nt-space-2);
}

.nt-template-summary-ok { color: var(--nt-success); }
.nt-template-summary-risk { color: var(--nt-warning); font-weight: 600; }

/* ------------------------------------------------------------ drip campaigns
   The drip list is a card grid (like Contacts/Templates entry points), one tile per campaign.
   Each tile is a whole-card link: hover lifts it, the footer arrow slides, and the tiles deal in
   with a staggered cascade on load — all gated behind prefers-reduced-motion below. */
.nt-drip-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--nt-space-2) var(--nt-space-4);
    margin-bottom: var(--nt-space-4);
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

.nt-drip-summary-item {
    display: inline-flex;
    align-items: center;
    gap: var(--nt-space-2);
}

.nt-drip-summary-live { color: var(--nt-brand); font-weight: 600; }

.nt-drip-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
    gap: var(--nt-space-4);
}

.nt-drip-card {
    display: flex;
    flex-direction: column;
    gap: var(--nt-space-3);
    padding: var(--nt-space-5);
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow-sm);
    text-decoration: none;
    color: inherit;
    position: relative;
    overflow: hidden;
    transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}

/* A brand accent bar that wipes across the top edge on hover — the "wow" without a layout shift. */
.nt-drip-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: linear-gradient(90deg, var(--nt-brand-400), var(--nt-brand));
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.3s ease;
}

.nt-drip-card:hover,
.nt-drip-card:focus-visible {
    transform: translateY(-4px);
    box-shadow: var(--nt-shadow-lg);
    border-color: var(--nt-brand-200);
}

.nt-drip-card:hover::before,
.nt-drip-card:focus-visible::before { transform: scaleX(1); }

.nt-drip-card-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-3);
}

.nt-drip-card-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: var(--nt-radius);
    background: var(--nt-brand-050);
    color: var(--nt-brand);
    font-size: 1.25rem;
    transition: transform 0.25s ease, background 0.25s ease;
}

.nt-drip-card:hover .nt-drip-card-icon {
    transform: scale(1.08) rotate(-6deg);
    background: var(--nt-brand-100);
}

.nt-drip-card-status { flex: 0 0 auto; }

.nt-drip-card-name {
    font-size: var(--nt-text-lg);
    font-weight: 700;
    line-height: 1.3;
    margin: 0;
    color: var(--nt-text);
    /* Names are tenant-supplied and can be long — cap at two lines. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.nt-drip-card-stats {
    display: flex;
    gap: var(--nt-space-5);
    margin-top: auto;
    padding-top: var(--nt-space-3);
}

.nt-drip-stat {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.nt-drip-stat-value {
    font-size: var(--nt-text-xl);
    font-weight: 700;
    line-height: 1;
    color: var(--nt-text);
}

.nt-drip-stat-label {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
}

.nt-drip-card-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-3);
    padding-top: var(--nt-space-3);
    border-top: 1px solid var(--nt-border);
}

.nt-drip-card-go {
    display: inline-flex;
    align-items: center;
    font-size: var(--nt-text-sm);
    font-weight: 600;
    color: var(--nt-brand);
    white-space: nowrap;
}

.nt-drip-card-go .bi { transition: transform 0.2s ease; }
.nt-drip-card:hover .nt-drip-card-go .bi { transform: translateX(3px); }

/* New-drip help panel: numbered how-it-works list. */
.nt-drip-help .nt-card-title { color: var(--nt-text); }

.nt-drip-steps {
    list-style: none;
    margin: var(--nt-space-4) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--nt-space-4);
}

.nt-drip-steps > li {
    display: flex;
    align-items: flex-start;
    gap: var(--nt-space-3);
}

.nt-drip-steps-num {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-brand-050);
    color: var(--nt-brand);
    font-weight: 700;
    font-size: var(--nt-text-sm);
}

@media (prefers-reduced-motion: no-preference) {
    .nt-drip-card { animation: ntFadeInUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; }
    .nt-drip-grid > .nt-drip-card:nth-child(1) { animation-delay: 0.04s; }
    .nt-drip-grid > .nt-drip-card:nth-child(2) { animation-delay: 0.09s; }
    .nt-drip-grid > .nt-drip-card:nth-child(3) { animation-delay: 0.14s; }
    .nt-drip-grid > .nt-drip-card:nth-child(4) { animation-delay: 0.19s; }
    .nt-drip-grid > .nt-drip-card:nth-child(5) { animation-delay: 0.24s; }
    .nt-drip-grid > .nt-drip-card:nth-child(6) { animation-delay: 0.29s; }
    .nt-drip-grid > .nt-drip-card:nth-child(n+7) { animation-delay: 0.33s; }
}

@media (prefers-reduced-motion: reduce) {
    .nt-drip-card { transition: none; }
    .nt-drip-card:hover,
    .nt-drip-card:focus-visible { transform: none; }
}

/* ----------------------------------------------------------------- add-ons
   Storefront cards for recurring add-ons and consumable packs. Aligned so name, price and the
   buy form sit on the same baselines across a row regardless of description length. */
.nt-owned-heading {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--nt-text-faint);
    margin-bottom: var(--nt-space-2);
}

.nt-owned-list { list-style: none; margin: 0; padding: 0; }

.nt-owned-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-3);
    padding: var(--nt-space-2) 0;
    border-bottom: 1px solid var(--nt-border);
}
.nt-owned-row:last-child { border-bottom: 0; }

.nt-addon-card { transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease; }

.nt-addon-card:hover {
    transform: translateY(-4px);
    box-shadow: var(--nt-shadow-lg);
    border-color: var(--nt-brand-200);
}

.nt-addon-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-3);
    margin-bottom: var(--nt-space-3);
}

.nt-addon-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: var(--nt-radius);
    background: var(--nt-brand-050);
    color: var(--nt-brand);
    font-size: 1.25rem;
    transition: transform 0.25s ease, background 0.25s ease;
}

.nt-addon-icon-pack { background: var(--nt-warning-tint, #fdf3e2); color: var(--nt-warning, #b26a00); }

.nt-addon-card:hover .nt-addon-icon {
    transform: scale(1.08) rotate(-6deg);
    background: var(--nt-brand-100);
}

.nt-addon-name {
    font-size: var(--nt-text-lg);
    font-weight: 700;
    line-height: 1.3;
    margin: 0 0 var(--nt-space-1);
    color: var(--nt-text);
}

.nt-addon-cycle {
    display: inline-flex;
    align-items: center;
    margin-bottom: var(--nt-space-2);
}

/* Descriptions vary in length; cap at three lines so every card's price starts at the same place. */
.nt-addon-desc {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-bottom: var(--nt-space-3);
}

.nt-addon-price {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    flex-wrap: wrap;
    margin: var(--nt-space-2) 0 var(--nt-space-4);
}

.nt-addon-price-cur { font-size: var(--nt-text-sm); font-weight: 700; color: var(--nt-text-muted); }
.nt-addon-price-amt { font-size: var(--nt-text-xl); font-weight: 800; color: var(--nt-text); line-height: 1; }
.nt-addon-price-unit { font-size: var(--nt-text-sm); color: var(--nt-text-muted); }

/* Round the stepper's outer corners so the plus/minus caps match the pill-radius buttons. */
.nt-qty .btn { width: 2.75rem; flex: 0 0 auto; }
.nt-qty .form-control { max-width: 5rem; }

.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);
}

/* A template's text header, shown above the body in the thread bubble — bold, like WhatsApp. */
.nt-bubble-tpl-header {
    font-weight: 700;
    margin: 0 0 var(--nt-space-1);
}

.nt-bubble-button .bi { margin-right: 0.35rem; }

/* The delivery report table carries more columns (timeline + reply), so keep the time cells on one
   line and let the whole table scroll inside its wrap on narrow screens rather than wrapping ugly. */
.nt-report-table th,
.nt-report-table td { white-space: nowrap; }
.nt-report-table .nt-reply-btn { white-space: nowrap; }

.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); }

/* The composer's media header + variable-example additions. Same token set; nothing new here is a
   colour. */
.nt-card-header-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-3);
    flex-wrap: wrap;
}

.nt-header-types { flex-wrap: wrap; }

.nt-media-thumb {
    display: block;
    max-width: 100%;
    max-height: 12rem;
    border-radius: var(--nt-radius-sm);
    margin-bottom: var(--nt-space-2);
}

.nt-examples {
    border-top: 1px solid var(--nt-border);
    padding-top: var(--nt-space-3);
}

.nt-example-row {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
}

.nt-example-row + .nt-example-row { margin-top: var(--nt-space-2); }

.nt-example-token {
    flex: 0 0 auto;
    min-width: 3.5rem;
    color: var(--nt-text-muted);
    font-size: var(--nt-text-sm);
}

/* The managed button rows carry a remove control the fixed slots did not. The control spans the
   row's own grid so it sits under the fields rather than squeezing them. */
.nt-button-remove { grid-column: 1 / -1; }

/* A label with an inline action (the "Add variable" affordance) on its right. */
.nt-field-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-2);
    margin-bottom: var(--nt-space-1);
}

.nt-add-var { white-space: nowrap; }

.nt-inline-error {
    display: flex;
    gap: var(--nt-space-2);
    margin: var(--nt-space-2) 0 0;
    color: var(--nt-danger);
    font-size: var(--nt-text-sm);
}

/* The auto-reply editor's mid-card divider: the second section heading ("The reply") inside the same
   card as the first, set off from the fields above it with a hairline top border. */
.nt-card-header-mid {
    border-top: 1px solid var(--nt-border);
    margin-top: var(--nt-space-2);
}

/* The small "insert a {{variable}}" affordances beside the body and footer labels. Reuses .nt-chip
   but tightened for an inline row of add-buttons. */
.nt-var-chips {
    display: inline-flex;
    flex-wrap: wrap;
    gap: var(--nt-space-2);
}

.nt-var-chips .nt-chip {
    padding: 0.1rem 0.5rem;
    font-size: var(--nt-text-xs);
    font-weight: 500;
    cursor: pointer;
}

/* The "file uploaded" confirmation row under the media picker: a check, the file name (which may be
   long, so it truncates), and a plain remove link. */
.nt-media-ready {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
    min-width: 0;
}

.nt-media-ready .text-truncate { min-width: 0; }

.nt-media-clear {
    flex: 0 0 auto;
    text-decoration: none;
}

/* One editable list-menu item: the title input-group with its optional description below, grouped
   with a faint rail so several rows read as a set. */
.nt-list-row {
    border-left: 2px solid var(--nt-border);
    padding-left: var(--nt-space-3);
}

/* The list menu preview under the bubble — a white sheet with a heading and divided rows, echoing
   how WhatsApp presents a list's options. */
.nt-list-preview {
    background: #fff;
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-sm);
    overflow: hidden;
    font-size: var(--nt-text-sm);
    color: #111b21;
}

.nt-list-preview-head {
    padding: var(--nt-space-2) var(--nt-space-3);
    font-weight: 700;
    font-size: var(--nt-text-xs);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    color: var(--nt-text-muted);
    background: var(--nt-surface-2);
}

.nt-list-preview-row {
    padding: var(--nt-space-2) var(--nt-space-3);
    border-top: 1px solid #e9edef;
}

.nt-list-preview-row:first-child { border-top: 0; }
.nt-list-preview-title { font-weight: 600; }
.nt-list-preview-desc { color: #667781; font-size: var(--nt-text-xs); }

/* ----------------------------------------------------- the WhatsApp-style preview
   A chat wallpaper (our own doodle line-art, not Meta's asset) with one incoming business
   bubble — no phone chrome, so the message itself is the focus. Never reuses the inbox's
   .nt-bubble, so that thread's tail and borders cannot leak in.

   Sticky on wide screens: the composer form is far taller than the preview, so without
   this the preview scrolls away and the reader is left staring at the empty column the
   height difference leaves behind. */
@media (min-width: 62em) {
    .nt-split-side {
        position: sticky;
        top: var(--nt-space-4);
        align-self: start;
    }
}

.nt-preview-stage {
    background-color: #e5ddd4;
    background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTAiIGhlaWdodD0iMTUwIiB2aWV3Qm94PSIwIDAgMTUwIDE1MCI+PGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzdiMzlhIiBzdHJva2Utd2lkdGg9IjEuNiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBvcGFjaXR5PSIwLjUiPjxjaXJjbGUgY3g9IjI2IiBjeT0iMjQiIHI9IjkiLz48cGF0aCBkPSJNNjQgMTRsMi42IDUuNCA1LjkuOC00LjMgNC4xIDEgNS45LTUuMi0yLjgtNS4yIDIuOCAxLTUuOS00LjMtNC4xIDUuOS0uOHoiLz48cGF0aCBkPSJNMTA4IDIwYy0yLjYtMy40LTgtMS04IDIuNyAwIDMuNiA4IDguMyA4IDguM3M4LTQuNyA4LTguM2MwLTMuNy01LjQtNi4xLTgtMi43eiIvPjxyZWN0IHg9IjE2IiB5PSI1OCIgd2lkdGg9IjIyIiBoZWlnaHQ9IjE1IiByeD0iNSIvPjxwYXRoIGQ9Ik0yMyA3M3Y1bDUtNSIvPjxjaXJjbGUgY3g9IjEyMiIgY3k9IjY2IiByPSIzLjUiLz48Y2lyY2xlIGN4PSIxMjIiIGN5PSI2NiIgcj0iOSIvPjxwYXRoIGQ9Ik02MCA5MmgxM002Ni41IDg1LjV2MTMiLz48cGF0aCBkPSJNOTYgOTZxOC05IDE2IDAiLz48Y2lyY2xlIGN4PSIzMCIgY3k9IjExMiIgcj0iMiIvPjxjaXJjbGUgY3g9IjQwIiBjeT0iMTEyIiByPSIyIi8+PGNpcmNsZSBjeD0iNTAiIGN5PSIxMTIiIHI9IjIiLz48cGF0aCBkPSJNMTI4IDEwMGwtOSAyIDMgOHoiLz48cGF0aCBkPSJNMjAgMTI4aDE2TTI4IDEyMHYxNiIvPjxjaXJjbGUgY3g9IjExMiIgY3k9IjEzMiIgcj0iOCIvPjxwYXRoIGQ9Ik0xMDggMTMyaDhNMTEyIDEyOHY4Ii8+PHBhdGggZD0iTTY2IDEyMHE5IDggMTggMCIvPjwvZz48L3N2Zz4K");
    background-size: 150px 150px;
    border-radius: var(--nt-radius);
    padding: var(--nt-space-4) var(--nt-space-3);
    min-height: 15rem;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
}

.nt-preview-bubble {
    position: relative;
    align-self: flex-start;
    max-width: 88%;
    margin-left: 0.5rem;
    padding: 0.4rem 0.45rem 0.35rem;
    background: #fff;
    border-radius: 0 0.5rem 0.5rem 0.5rem;
    box-shadow: 0 1px 1.5px rgba(11, 20, 26, 0.18);
    font-size: var(--nt-text-sm);
    color: #111b21;
}

/* The incoming-message tail at the top-left, the detail that makes it read as WhatsApp. Drawn
   with clip-path so no overflow:hidden is needed on the bubble (which would clip it away). */
.nt-preview-bubble::before {
    content: "";
    position: absolute;
    top: 0;
    left: -0.5rem;
    width: 0.5rem;
    height: 0.75rem;
    background: #fff;
    clip-path: polygon(100% 0, 100% 100%, 0 0);
}

.nt-preview-bubble .nt-bubble-empty {
    padding: var(--nt-space-3);
    color: var(--nt-text-faint);
    font-style: italic;
}

/* A soft rounded media tile sitting inside the bubble's padding, the way WhatsApp frames a header
   image or video. The image clips to the tile; a placeholder shows the kind's icon. */
.nt-bubble-media-tile {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 20 / 13;
    background: #c9ced4;
    border-radius: 0.375rem;
    overflow: hidden;
    margin-bottom: 0.25rem;
}

.nt-bubble-media-icon { font-size: 2.75rem; color: #1f2c34; }

.nt-bubble-media-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.nt-bubble-content {
    padding: 0.25rem 0.35rem 0.15rem;
}

/* Only the body carries literal line breaks WhatsApp preserves — headers and footers are
   single-line. Scoped here rather than on .nt-bubble-content so incidental whitespace between
   sibling elements (header/body/footer/time) is never mistaken for message content. */
.nt-bubble-body {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.nt-bubble-content .nt-bubble-header,
.nt-bubble-content .nt-bubble-body { color: #111b21; }

/* WhatsApp inline formatting rendered from escaped segments (never innerHTML). Styled explicitly
   rather than leaning on Bootstrap's utilities so the bubble reads correctly on its own. */
.nt-bubble-body .fw-bold { font-weight: 700; }
.nt-bubble-body .fst-italic { font-style: italic; }
.nt-strike { text-decoration: line-through; }

.nt-bubble-time {
    display: block;
    text-align: right;
    font-size: 0.6875rem;
    color: #667781;
    margin-top: 0.15rem;
}

/* The tappable rows WhatsApp draws under the bubble: a divider above the group, blue centred
   labels each with a leading icon, and a hairline between rows. */
.nt-bubble-actions {
    margin-top: 0.25rem;
    border-top: 1px solid #e9edef;
}

.nt-bubble-action {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.55rem var(--nt-space-2);
    color: #0a91d1;
    font-weight: 600;
    font-size: var(--nt-text-sm);
}

.nt-bubble-action + .nt-bubble-action,
.nt-bubble-more { border-top: 1px solid #eef1f2; }

.nt-bubble-more-note {
    padding: 0.1rem var(--nt-space-2) 0.4rem;
    text-align: center;
    font-size: var(--nt-text-xs);
    color: #c1620f;
}


/* ================================================================== 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-3);
}

/* Compact, colourful list card: a rotating accent runs down the left edge and tints the avatar, so
   the list reads as a lively set of rows rather than a stack of tall blocks. Same 6-hue palette as
   the contacts table. */
.nt-campaign {
    position: relative;
    overflow: hidden;
    border-left: 3px solid var(--nt-cc-accent, var(--nt-brand));
}

.nt-campaign:nth-child(6n+1) { --nt-cc-accent: #0b7a5c; --nt-cc-tint: #eaf7f2; --nt-cc-ink: #075440; }
.nt-campaign:nth-child(6n+2) { --nt-cc-accent: #1d5c8f; --nt-cc-tint: #e9f2f9; --nt-cc-ink: #164a73; }
.nt-campaign:nth-child(6n+3) { --nt-cc-accent: #b7791f; --nt-cc-tint: #fdf3e2; --nt-cc-ink: #8a5300; }
.nt-campaign:nth-child(6n+4) { --nt-cc-accent: #6b46c1; --nt-cc-tint: #f1ecfb; --nt-cc-ink: #54369c; }
.nt-campaign:nth-child(6n+5) { --nt-cc-accent: #0f7d82; --nt-cc-tint: #e4f5f5; --nt-cc-ink: #0c5a5e; }
.nt-campaign:nth-child(6n+6) { --nt-cc-accent: #c0395f; --nt-cc-tint: #fdecef; --nt-cc-ink: #9a2b4a; }

.nt-campaign-row {
    display: flex;
    align-items: flex-start;
    gap: var(--nt-space-3);
    padding: var(--nt-space-3) var(--nt-space-4);
}

.nt-campaign-avatar {
    flex: 0 0 auto;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-cc-tint, var(--nt-brand-050));
    color: var(--nt-cc-ink, var(--nt-brand-700));
    display: grid;
    place-items: center;
    font-size: 1.15rem;
}

.nt-campaign-main {
    min-width: 0;
    flex: 1 1 auto;
    display: grid;
    gap: var(--nt-space-2);
}

.nt-campaign-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-2);
}

.nt-campaign-head h2,
.nt-campaign-head a { overflow-wrap: anywhere; }

.nt-campaign-sub { overflow-wrap: anywhere; }
.nt-campaign-dot { color: var(--nt-border-strong); }

/* A slim progress track for the compact card. */
.nt-progress-sm {
    margin-top: 0;
    height: 0.35rem;
}

/* Inline coloured stat chips with icons — the counts, at a glance, in one short row. */
.nt-campaign-chips {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-2);
}

.nt-cchip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.5rem;
    border-radius: var(--nt-radius-pill);
    font-size: var(--nt-text-xs);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    background: var(--nt-surface-2);
    color: var(--nt-text-muted);
    border: 1px solid var(--nt-border);
}

.nt-cchip .bi { font-size: 0.9em; }
.nt-cchip b { font-weight: 800; }

.nt-cchip-label {
    font-weight: 600;
    color: currentcolor;
    opacity: 0.75;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-size: 0.85em;
}

.nt-cchip.is-audience { background: var(--nt-surface-2);    color: var(--nt-text-muted); border-color: var(--nt-border); }
.nt-cchip.is-sent     { background: var(--nt-info-tint);    color: var(--nt-info);       border-color: #c7ddee; }
.nt-cchip.is-delivered{ background: var(--nt-success-tint); color: var(--nt-success);    border-color: #bfe3d5; }
.nt-cchip.is-read     { background: #e8f0ff;                color: #2f4fb0;              border-color: #cdd9f7; }
.nt-cchip.is-failed   { background: var(--nt-danger-tint);  color: var(--nt-danger);     border-color: #f0c7c5; }

.nt-campaign-reason {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    color: var(--nt-warning);
}

.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;
}

/* ------------------------------------------------------- flow keyword chips (§11)

   A Keyword flow starts from any of several keywords. Added ones are chips; the plain text box after
   them is where the next is typed. The box wears .form-control, so the focus ring moves from the bare
   inner input to the whole field — one ring, never none. Chips wrap, so a long list grows the field
   downward instead of pushing a narrow column sideways at 400px. */

.nt-keyword-input {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--nt-space-1);
    height: auto;
    cursor: text;
}

.nt-keyword-input:focus-within {
    border-color: var(--nt-brand-200);
    box-shadow: 0 0 0 3px var(--nt-brand-100);
}

.nt-keyword-input:has(.nt-keyword-entry:focus-visible) {
    outline: 2px solid var(--nt-brand-400);
    outline-offset: 2px;
}

.nt-keyword-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-1);
    min-width: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* A phrase can be long; it wraps inside its chip rather than overflowing the field. */
.nt-keyword-chip {
    max-width: 100%;
    white-space: normal;
    overflow-wrap: anywhere;
    cursor: default;
}

.nt-keyword-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    min-height: 1.25rem;
    margin-right: -0.3rem;
    padding: 0;
    border: 0;
    border-radius: var(--nt-radius-pill);
    background: none;
    color: var(--nt-text-faint);
    line-height: 1;
}

.nt-keyword-remove:hover { color: var(--nt-danger); }

.nt-keyword-entry {
    flex: 1 1 6rem;
    min-width: 6rem;
    padding: 0.1rem 0;
    border: 0;
    background: transparent;
    color: inherit;
}

.nt-keyword-entry:focus,
.nt-keyword-entry:focus-visible { outline: none; }

/* The list page: the first few keywords as pills, then "+N more". */
.nt-keyword-pills {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--nt-space-1);
}

/* ------------------------------------------------------- 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); }

/* A step no path from the Trigger reaches: dashed, so a flow that "does nothing" shows why. */
.nt-node.is-orphan .nt-node-grab { border-style: dashed; }

.nt-node-orphan {
    display: flex;
    align-items: center;
    margin-top: 0.25rem;
    font-size: var(--nt-text-xs);
    font-weight: 600;
    color: var(--nt-danger);
}

.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;
    overflow-wrap: anywhere;
    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);
}

/* ============================================================ dashboard analytics
   The tenant dashboard's charts (Views/Home/Index.cshtml + _DashboardDonut.cshtml). Every chart is
   inline SVG or flexbox — no charting library, no outbound request — coloured from the same tokens
   as the pills so the whole page stays one palette. All motion lives in the prefers-reduced-motion:
   no-preference block at the end; the base rules render the final, static chart, so a reduced-motion
   or no-JS visitor sees the same data without the animation. */

.nt-dash-kpi {
    position: relative;
    overflow: hidden;
}

/* A hairline brand accent along the top edge of each headline tile. */
.nt-dash-kpi::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: linear-gradient(90deg, var(--nt-brand-400), var(--nt-brand));
    opacity: 0.9;
}

.nt-dash-num {
    font-variant-numeric: tabular-nums;
}

.nt-dash-trend {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    padding: 0.05rem 0.4rem;
    border-radius: var(--nt-radius-pill);
    font-size: var(--nt-text-xs);
    font-weight: 700;
    line-height: 1.5;
    background: var(--nt-surface-2);
    color: var(--nt-text-muted);
    border: 1px solid var(--nt-border);
}

.nt-dash-trend-up {
    background: var(--nt-success-tint);
    color: var(--nt-success);
    border-color: #bfe3d5;
}

.nt-dash-trend-down {
    background: var(--nt-danger-tint);
    color: var(--nt-danger);
    border-color: #f0c7c5;
}

.nt-dash-trend-flat {
    background: var(--nt-surface-2);
    color: var(--nt-text-muted);
}

/* ---------------------------------------------------------------- shared legend */

.nt-dash-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nt-space-4);
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

.nt-dash-legend-item {
    display: inline-flex;
    align-items: center;
    gap: var(--nt-space-2);
}

.nt-dash-swatch {
    width: 12px;
    height: 12px;
    border-radius: 3px;
    flex: 0 0 auto;
}

/* ---------------------------------------------------------------- line chart */

.nt-dash-chart {
    position: relative;
    overflow: hidden;
}

.nt-dash-chart-svg {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

.nt-dash-gridline {
    stroke: var(--nt-border);
    stroke-width: 1;
}

.nt-dash-line-out,
.nt-dash-line-in {
    fill: none;
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.nt-dash-line-out { stroke: var(--nt-brand); }
.nt-dash-line-in { stroke: var(--nt-info); }

.nt-dash-area-out {
    fill: var(--nt-brand);
    fill-opacity: 0.12;
    stroke: none;
}

.nt-dash-area-in {
    fill: var(--nt-info);
    fill-opacity: 0.08;
    stroke: none;
}

.nt-dash-dot-out {
    fill: var(--nt-brand);
    stroke: var(--nt-surface);
    stroke-width: 2;
}

.nt-dash-dot-in {
    fill: var(--nt-info);
    stroke: var(--nt-surface);
    stroke-width: 2;
}

.nt-dash-axis {
    display: flex;
    justify-content: space-between;
    margin-top: var(--nt-space-2);
    padding: 0 2px;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-faint);
}

/* ---------------------------------------------------------------- spend bars */

.nt-dash-bars {
    display: flex;
    align-items: flex-end;
    gap: var(--nt-space-2);
    height: 180px;
}

.nt-dash-bar {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    height: 100%;
    min-width: 0;
}

.nt-dash-bar-fill {
    width: 68%;
    max-width: 28px;
    min-height: 2px;
    border-radius: var(--nt-radius-sm) var(--nt-radius-sm) 0 0;
    background: linear-gradient(180deg, var(--nt-brand-400), var(--nt-brand));
    transform-origin: bottom;
}

.nt-dash-bar-label {
    margin-top: var(--nt-space-1);
    font-size: var(--nt-text-xs);
    color: var(--nt-text-faint);
}

/* ---------------------------------------------------------------- delivery funnel */

/* ---- delivery funnel: a fixed 3D cone whose bands FILL vertically to their percentage ----
   The cone silhouette is static (per-row --top/--bot below) so it NEVER resizes with the data; only
   the dark fill level rises. A light track shows the empty remainder. Fully reusable — restyle the four
   --nt-cone-* tokens (or drop in another palette) and it lands in any card / any project unchanged. */
.nt-dash-funnel {
    --nt-cone-track: var(--nt-brand-100);   /* light "empty" remainder             */
    --nt-cone-fill:  var(--nt-brand);       /* dark filled portion                  */
    --nt-cone-rim:   var(--nt-brand-700);   /* the 3D bowl rim on top of each band  */
    --nt-cone-w: 200px;                     /* fixed silhouette width — data never touches it */
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.nt-dash-funnel-row {
    display: flex;
    align-items: center;
    height: 66px;
}

/* Static geometry — three bands narrowing into a cone. Deliberately hard-coded: the shape is constant. */
.nt-dash-funnel-row:nth-child(1) .nt-dash-funnel-layer { --top: 100%; --bot: 74%; }
.nt-dash-funnel-row:nth-child(2) .nt-dash-funnel-layer { --top: 74%;  --bot: 48%; }
.nt-dash-funnel-row:nth-child(3) .nt-dash-funnel-layer { --top: 48%;  --bot: 26%; }

.nt-dash-funnel-cone {
    position: relative;
    flex: 0 0 var(--nt-cone-w);
    height: 100%;
}

.nt-dash-funnel-layer {
    position: absolute;
    inset: 0;
    background: var(--nt-cone-track);
    overflow: hidden;
    clip-path: polygon(
        calc(50% - var(--top) / 2) 0,
        calc(50% + var(--top) / 2) 0,
        calc(50% + var(--bot) / 2) 100%,
        calc(50% - var(--bot) / 2) 100%);
}

/* Dark fill rises from the bottom to --nt-pct of the band height; the parent clip keeps it cone-shaped. */
.nt-dash-funnel-fill {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: var(--nt-pct, 0%);
    background: var(--nt-cone-fill);
}

/* The dark elliptical rim on top of each band — the 3D "bowl" opening, constant regardless of fill. */
.nt-dash-funnel-rim {
    position: absolute;
    top: -6px;
    left: calc(50% - var(--top) / 2);
    width: var(--top);
    height: 13px;
    background: var(--nt-cone-rim);
    border-radius: 50%;
    z-index: 2;
}

/* The % chip is centred so it reads over both the filled and the empty part of a band. */
.nt-dash-funnel-pct {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 3;
    padding: 3px 9px;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-surface);
    color: var(--nt-brand-700);
    font-size: var(--nt-text-xs);
    font-weight: 700;
    line-height: 1;
    box-shadow: var(--nt-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.12));
}

/* Leader line + side label, aligned to each band. */
.nt-dash-funnel-lead {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    min-width: 0;
}

.nt-dash-funnel-dot {
    flex: 0 0 auto;
    width: 7px;
    height: 7px;
    margin-left: -3px;
    border-radius: 50%;
    background: var(--nt-cone-fill);
}

.nt-dash-funnel-line {
    flex: 0 0 20px;
    height: 1px;
    background: var(--nt-border-strong);
}

.nt-dash-funnel-ltext { padding-left: 10px; min-width: 0; }
.nt-dash-funnel-lname { font-size: var(--nt-text-sm); font-weight: 650; }
.nt-dash-funnel-lmeta { margin-top: 1px; font-size: var(--nt-text-xs); color: var(--nt-text-muted); }
.nt-dash-funnel-lmeta strong { color: var(--nt-text); font-weight: 650; }

/* Reusable in dark projects: track/fill flip to legible tones on a dark surface. Harmless where the
   app is light-only. */
@media (prefers-color-scheme: dark) {
    .nt-dash-funnel {
        --nt-cone-track: rgba(37, 211, 102, 0.16);
        --nt-cone-fill: var(--nt-brand-400);
        --nt-cone-rim: var(--nt-brand);
    }
}

/* ---------------------------------------------------------------- donuts */

.nt-dash-donut {
    display: flex;
    justify-content: center;
    padding: var(--nt-space-2) 0 var(--nt-space-4);
}

.nt-dash-donut-svg {
    width: 150px;
    height: 150px;
    transform-origin: center;
}

.nt-dash-donut-track {
    fill: none;
    stroke: var(--nt-border);
    stroke-width: 15;
}

.nt-dash-donut-seg {
    fill: none;
    stroke-width: 15;
}

.nt-dash-donut-total {
    font-size: 22px;
    font-weight: 700;
    fill: var(--nt-text);
    font-family: var(--nt-font);
}

.nt-dash-donut-legend {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--nt-space-2);
    font-size: var(--nt-text-sm);
}

.nt-dash-donut-legend li {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
}

.nt-dash-donut-legend-label {
    flex: 1 1 auto;
    color: var(--nt-text);
}

.nt-dash-donut-legend-val {
    color: var(--nt-text-muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* ---------------------------------------------------------------- template split bar */

.nt-dash-tempbar {
    display: flex;
    gap: 2px;
    height: 10px;
    border-radius: var(--nt-radius-pill);
    overflow: hidden;
    background: var(--nt-surface-2);
}

.nt-dash-tempseg {
    display: block;
    height: 100%;
    transform-origin: left center;
}

.nt-dash-tempseg-ok { background: var(--nt-brand); }
.nt-dash-tempseg-pending { background: var(--nt-warning); }
.nt-dash-tempseg-bad { background: var(--nt-danger); }
.nt-dash-tempseg-blocked { background: var(--nt-border-strong); }

/* ---------------------------------------------------------------- motion
   Everything above renders the finished chart. The animation is added only where motion is welcome,
   so the reduced-motion and no-JS experience is the same data, still. */

@media (prefers-reduced-motion: no-preference) {
    @keyframes ntDashDraw {
        from { stroke-dashoffset: 1; }
        to { stroke-dashoffset: 0; }
    }

    @keyframes ntDashFade {
        from { opacity: 0; }
        to { opacity: 1; }
    }

    @keyframes ntDashPop {
        0% { transform: scale(0); opacity: 0; }
        100% { transform: scale(1); opacity: 1; }
    }

    @keyframes ntDashGrowY {
        from { transform: scaleY(0); }
        to { transform: scaleY(1); }
    }

    @keyframes ntDashGrowX {
        from { transform: scaleX(0); }
        to { transform: scaleX(1); }
    }

    @keyframes ntDashDonutIn {
        0% { transform: rotate(-12deg) scale(0.82); opacity: 0; }
        100% { transform: rotate(0) scale(1); opacity: 1; }
    }

    @keyframes ntDashShimmer {
        from { transform: translateX(-120%); }
        to { transform: translateX(220%); }
    }

    /* Line draw: pathLength="1" makes the dash the whole line, so it wipes on from the left. */
    .nt-dash-line-out,
    .nt-dash-line-in {
        stroke-dasharray: 1;
        animation: ntDashDraw 1.1s cubic-bezier(0.65, 0, 0.35, 1) both;
    }

    .nt-dash-line-in { animation-delay: 0.18s; }

    .nt-dash-area-out,
    .nt-dash-area-in {
        animation: ntDashFade 0.9s ease-out 0.35s both;
    }

    .nt-dash-dot-out,
    .nt-dash-dot-in {
        transform-box: fill-box;
        transform-origin: center;
        animation: ntDashPop 0.4s cubic-bezier(0.16, 1, 0.3, 1) 1.05s both;
    }

    /* A single light sweep across the line chart on first paint — the "loading" flourish. */
    .nt-dash-chart::after {
        content: "";
        position: absolute;
        inset: 0;
        pointer-events: none;
        background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, 0.55) 50%, transparent 70%);
        transform: translateX(-120%);
        animation: ntDashShimmer 1.1s ease-out 0.1s 1 both;
    }

    .nt-dash-bar-fill {
        animation: ntDashGrowY 0.7s cubic-bezier(0.16, 1, 0.3, 1) both;
    }

    /* The cone SHAPE never animates — only the fill level rises, like liquid filling each band. */
    @keyframes ntDashFillRise {
        from { height: 0; }
    }

    .nt-dash-funnel-fill {
        animation: ntDashFillRise 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
    }

    .nt-dash-funnel-row:nth-child(2) .nt-dash-funnel-fill { animation-delay: 0.1s; }
    .nt-dash-funnel-row:nth-child(3) .nt-dash-funnel-fill { animation-delay: 0.2s; }

    .nt-dash-donut-svg {
        animation: ntDashDonutIn 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
    }

    .nt-dash-tempseg {
        animation: ntDashGrowX 0.7s cubic-bezier(0.16, 1, 0.3, 1) both;
    }
}

/* ============================================================================
   PWA install banner (§ _PwaInstall.cshtml + pwa.js)
   A closable, bottom-anchored prompt shown only when the browser reports the
   app is installable. Hidden via the [hidden] attribute until pwa.js reveals it.
   ============================================================================ */
.nt-pwa-install {
    position: fixed;
    left: 50%;
    bottom: 16px;
    transform: translate(-50%, calc(100% + 24px));
    z-index: 1085; /* under the infrastructure toast (1090), over page content */
    width: calc(100% - 24px);
    max-width: 440px;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    background: #fff;
    border: 1px solid var(--nt-brand-100, #cdeade);
    border-radius: 14px;
    box-shadow: 0 12px 40px rgba(11, 122, 92, 0.18);
    opacity: 0;
    transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.28s ease;
}

.nt-pwa-install.is-visible {
    transform: translate(-50%, 0);
    opacity: 1;
}

.nt-pwa-install[hidden] { display: none; }

.nt-pwa-install-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 10px;
    background: var(--nt-brand-050, #eaf7f2);
}

.nt-pwa-install-icon img { width: 32px; height: 32px; display: block; }

.nt-pwa-install-text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    line-height: 1.3;
}

.nt-pwa-install-text strong { font-size: 0.95rem; color: var(--nt-text, #14201d); }
.nt-pwa-install-text span { font-size: 0.8rem; color: #4a5a55; }
.nt-pwa-install-ios { margin-top: 4px; }

.nt-pwa-install-action { flex: 0 0 auto; white-space: nowrap; }

.nt-pwa-install-close {
    flex: 0 0 auto;
    appearance: none;
    border: 0;
    background: transparent;
    color: #6b7a75;
    width: 30px;
    height: 30px;
    border-radius: 8px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
}

.nt-pwa-install-close:hover { background: rgba(0, 0, 0, 0.05); color: var(--nt-text, #14201d); }

@media (prefers-reduced-motion: reduce) {
    .nt-pwa-install { transition: opacity 0.2s ease; transform: translate(-50%, 0); }
}

/* -------------------------------------------------------------------------- crm board
   A horizontally scrolling Kanban of pipeline stages. Cards are draggable when the viewer can manage the
   CRM; the column tints by stage outcome (open / won / lost). */

.nt-crm-board {
    display: flex;
    gap: var(--nt-space-3, 0.75rem);
    align-items: flex-start;
    overflow-x: auto;
    padding-bottom: var(--nt-space-3, 0.75rem);
}

.nt-crm-col {
    flex: 0 0 17rem;
    max-width: 17rem;
    background: var(--nt-surface-2, #f4f6f5);
    border: 1px solid var(--nt-border, #dfe4e2);
    border-top: 3px solid var(--nt-info, #2f77c9);
    border-radius: var(--nt-radius, 0.6rem);
    padding: var(--nt-space-2, 0.5rem);
    min-height: 6rem;
}

.nt-crm-col.is-won { border-top-color: var(--nt-success, #1f9d61); }
.nt-crm-col.is-lost { border-top-color: var(--nt-danger, #d1435b); }

.nt-crm-col-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-weight: 600;
    font-size: var(--nt-text-sm, 0.875rem);
    color: var(--nt-text, #14201d);
    padding: 0 var(--nt-space-1, 0.25rem) var(--nt-space-2, 0.5rem);
}

.nt-crm-col-count {
    background: var(--nt-surface, #fff);
    border: 1px solid var(--nt-border, #dfe4e2);
    border-radius: var(--nt-radius-pill, 999px);
    padding: 0 0.5rem;
    font-size: var(--nt-text-xs, 0.75rem);
    color: var(--nt-text-muted, #5b6b66);
}

.nt-crm-col-value {
    font-size: var(--nt-text-xs, 0.75rem);
    color: var(--nt-text-muted, #5b6b66);
    padding: 0 var(--nt-space-1, 0.25rem) var(--nt-space-2, 0.5rem);
}

.nt-crm-col-body {
    display: flex;
    flex-direction: column;
    gap: var(--nt-space-2, 0.5rem);
    min-height: 3rem;
    border-radius: var(--nt-radius-sm, 0.4rem);
}

.nt-crm-col-body.is-dropping {
    outline: 2px dashed var(--nt-brand, #1f9d61);
    outline-offset: 2px;
}

.nt-crm-card {
    display: block;
    background: var(--nt-surface, #fff);
    border: 1px solid var(--nt-border, #dfe4e2);
    border-radius: var(--nt-radius-sm, 0.4rem);
    padding: var(--nt-space-2, 0.5rem) var(--nt-space-3, 0.75rem);
    box-shadow: var(--nt-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.06));
    text-decoration: none;
    color: inherit;
}

.nt-crm-card:hover { border-color: var(--nt-border-strong, #b9c2bf); }
.nt-crm-card[draggable="true"] { cursor: grab; }
.nt-crm-card.is-dragging { opacity: 0.5; }

.nt-crm-card-title {
    font-weight: 600;
    font-size: var(--nt-text-sm, 0.875rem);
    color: var(--nt-text, #14201d);
    margin-bottom: var(--nt-space-1, 0.25rem);
}

.nt-crm-card-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    align-items: center;
    margin-bottom: var(--nt-space-1, 0.25rem);
}

.nt-crm-card-amount { font-weight: 600; font-size: var(--nt-text-xs, 0.75rem); }

.nt-crm-card-foot {
    display: flex;
    justify-content: space-between;
    gap: 0.5rem;
    font-size: var(--nt-text-xs, 0.75rem);
    color: var(--nt-text-muted, #5b6b66);
}

/* crm record timeline */
.nt-crm-timeline { list-style: none; margin: 0; padding: 0; }
.nt-crm-timeline-item {
    border-left: 2px solid var(--nt-border, #dfe4e2);
    padding: 0 0 var(--nt-space-3, 0.75rem) var(--nt-space-3, 0.75rem);
    position: relative;
}
.nt-crm-timeline-item::before {
    content: "";
    position: absolute;
    left: -5px;
    top: 4px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--nt-brand, #1f9d61);
}
.nt-crm-timeline-head { display: flex; gap: 0.5rem; align-items: center; }
.nt-crm-timeline-body { color: var(--nt-text, #14201d); font-size: var(--nt-text-sm, 0.875rem); white-space: pre-wrap; }

@media (prefers-color-scheme: dark) {
    .nt-crm-col { background: var(--nt-surface-2, #1a2320); border-color: var(--nt-border, #2b3733); }
    .nt-crm-card { background: var(--nt-surface, #14201d); border-color: var(--nt-border, #2b3733); }
    .nt-crm-col-count { background: var(--nt-surface, #14201d); border-color: var(--nt-border, #2b3733); }
}

/* crm calendar month grid */
.nt-crm-cal {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 1px;
    background: var(--nt-border, #dfe4e2);
    border: 1px solid var(--nt-border, #dfe4e2);
    border-radius: var(--nt-radius, 0.6rem);
    overflow: hidden;
}
.nt-crm-cal-head {
    background: var(--nt-surface-2, #f4f6f5);
    padding: var(--nt-space-1, 0.25rem) var(--nt-space-2, 0.5rem);
    font-size: var(--nt-text-xs, 0.75rem);
    font-weight: 600;
    color: var(--nt-text-muted, #5b6b66);
    text-align: center;
}
.nt-crm-cal-cell {
    background: var(--nt-surface, #fff);
    min-height: 6rem;
    padding: var(--nt-space-1, 0.25rem);
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.nt-crm-cal-cell.is-muted { background: var(--nt-surface-2, #f4f6f5); }
.nt-crm-cal-cell.is-muted .nt-crm-cal-date { color: var(--nt-text-faint, #9aa8a3); }
.nt-crm-cal-cell.is-today { outline: 2px solid var(--nt-brand, #1f9d61); outline-offset: -2px; }
.nt-crm-cal-date { font-size: var(--nt-text-xs, 0.75rem); font-weight: 600; color: var(--nt-text-muted, #5b6b66); }
.nt-crm-cal-item {
    display: block;
    font-size: var(--nt-text-xs, 0.75rem);
    padding: 1px 4px;
    border-radius: 3px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-decoration: none;
    border-left: 3px solid transparent;
}
.nt-crm-cal-task { background: var(--nt-info-tint); border-left-color: var(--nt-info); color: inherit; }
.nt-crm-cal-appt { background: rgba(31, 157, 97, 0.12); border-left-color: var(--nt-success, #1f9d61); color: inherit; }
.nt-crm-cal-overdue { background: var(--nt-danger-tint, rgba(209, 67, 91, 0.12)); border-left-color: var(--nt-danger, #d1435b); color: inherit; }

@media (prefers-color-scheme: dark) {
    .nt-crm-cal, .nt-crm-cal-head, .nt-crm-cal-cell { background: var(--nt-surface, #14201d); border-color: var(--nt-border, #2b3733); }
    .nt-crm-cal-cell.is-muted { background: var(--nt-surface-2, #1a2320); }
}

/* ---------------------------------------------------------------- CRM dashboard stage bars
   A horizontal distribution bar per stage. The track is fixed; the fill width encodes the stage's
   share of its pipeline's busiest stage, so it adapts to any number of stages a client configures. */
.nt-crm-dash-stages { display: flex; flex-direction: column; gap: var(--nt-space-3, 12px); }
.nt-crm-dash-stage { min-width: 0; }
.nt-crm-dash-stage-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.nt-crm-dash-stage-name { font-size: var(--nt-text-sm); font-weight: 650; }
.nt-crm-dash-bar { height: 10px; border-radius: var(--nt-radius-pill, 999px); background: var(--nt-brand-100, #e6f8ee); overflow: hidden; }
.nt-crm-dash-bar-fill { height: 100%; min-width: 2px; border-radius: inherit; transition: width .3s ease; }

@media (prefers-color-scheme: dark) {
    .nt-crm-dash-bar { background: rgba(37, 211, 102, 0.16); }
}

/* The "message us on WhatsApp" QR code in settings.
   A white plate under the code on purpose: a QR needs light quiet-zone contrast to scan, and in dark
   mode the card behind it is nearly black — the code would still be drawn correctly and simply not
   read. Capped rather than full-width so it stays a code, not a poster, inside the panel. */
.nt-qr-image {
    width: 100%;
    max-width: 260px;
    padding: var(--nt-space-3);
    background: #fff;
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-md);
}

/* ==========================================================================
   Instagram inbox (§B9, §C15)
   A skin over the existing nt-* inbox components, not a second design system:
   the panes, bubbles, composer bar and pills are all the shared classes, and
   only what Instagram genuinely has that WhatsApp does not gets a rule here.
   ========================================================================== */

/* -- the per-row channel mark -------------------------------------------- */
/* Sits on the avatar of every conversation row, on every channel. A mark that
   appeared only on the non-default channel would teach agents to read its
   ABSENCE as "WhatsApp", which stops being true the moment a third one lands.
   The positioning context it hangs off is declared with .nt-conv-avatar itself
   (above), not redeclared here — a second bare declaration of the same class
   silently restyles the first component wherever it is used. */
.nt-chan {
    position: absolute;
    right: -0.15rem;
    bottom: -0.15rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: var(--nt-radius-pill);
    border: 2px solid var(--nt-surface, #fff);
    font-size: 0.62rem;
    line-height: 1;
    color: #fff;
}

.nt-chan-whatsapp { background: #25d366; }
.nt-chan-instagram { background: linear-gradient(45deg, #f09433, #dc2743 55%, #bc1888); }
.nt-chan-messenger { background: #0084ff; }

/* The profile picture, laid over the initials rather than replacing them: Meta's
   profile_pic URL expires after days, and a lapsed one then falls back to a
   readable monogram instead of a broken tile. */
.nt-conv-avatar-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border-radius: inherit;
    object-fit: cover;
}

/* -- the window / capability ledger --------------------------------------- */
.nt-ig-ledger {
    border: 1px solid var(--nt-border, #e5e7eb);
    border-radius: var(--nt-radius-md, 0.5rem);
    margin-bottom: 0.5rem;
    background: var(--nt-surface-muted, #f8fafc);
}

.nt-ig-ledger-summary {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    cursor: pointer;
    list-style: none;
}

.nt-ig-ledger-summary::-webkit-details-marker { display: none; }

.nt-ig-ledger-lead {
    font-size: var(--nt-text-sm, 0.875rem);
    color: var(--nt-text-muted, #64748b);
}

.nt-ig-ledger-body { padding: 0 0.75rem 0.75rem; }

.nt-ig-ledger-row {
    display: grid;
    grid-template-columns: minmax(10rem, auto) 1fr;
    gap: 0.25rem 0.75rem;
    padding: 0.35rem 0;
    border-top: 1px solid var(--nt-border, #e5e7eb);
    font-size: var(--nt-text-sm, 0.875rem);
}

.nt-ig-ledger-row.is-active { font-weight: 600; }

.nt-ig-ledger-name { display: inline-flex; align-items: center; gap: 0.35rem; }
.nt-ig-ledger-state { color: var(--nt-text-muted, #64748b); }
.nt-ig-ledger-when { grid-column: 1 / -1; color: var(--nt-text-muted, #64748b); font-size: 0.78rem; }

/* -- story replies and mentions ------------------------------------------- */
/* A story is a tall crop, so it is boxed rather than rendered at its own ratio;
   a mention is tinted so it never reads as a reply at a glance. */
.nt-ig-story {
    display: block;
    max-width: 9rem;
    border-radius: var(--nt-radius-md, 0.5rem);
    overflow: hidden;
    border: 2px solid var(--nt-border, #e5e7eb);
    margin-bottom: 0.35rem;
}

.nt-ig-story.is-mention { border-color: #bc1888; }

.nt-ig-story-img { display: block; width: 100%; height: auto; }

.nt-ig-mention { color: #bc1888; }

/* -- shared posts and reels ------------------------------------------------ */
.nt-ig-share {
    border: 1px solid var(--nt-border, #e5e7eb);
    border-radius: var(--nt-radius-md, 0.5rem);
    padding: 0.5rem 0.6rem;
    margin-bottom: 0.35rem;
}

.nt-ig-share-title { font-weight: 600; }

.nt-ig-share-open { display: inline-flex; align-items: center; gap: 0.35rem; }

/* -- reactions ------------------------------------------------------------- */
/* On the bubble, overlapping its lower edge, the way Instagram itself renders
   them. Never a bubble of their own: a reaction shown as a message is a lone
   emoji answering nothing. */
.nt-ig-reactions {
    display: flex;
    gap: 0.15rem;
    list-style: none;
    margin: -0.15rem 0 0;
    padding: 0;
}

.nt-ig-reaction {
    display: inline-flex;
    align-items: center;
    padding: 0.05rem 0.3rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-surface, #fff);
    border: 1px solid var(--nt-border, #e5e7eb);
    font-size: 0.8rem;
    line-height: 1.3;
}

/* Whose reaction it is, visible without hovering. An agent who reacted from the Instagram app
   on their phone sees it here as theirs; the customer's stays plain. Never colour alone — the
   accessible label ends in "(you)" or "(them)" in both cases, so the distinction survives for a
   screen reader and for anyone who cannot tell the two borders apart. */
.nt-ig-reaction.is-ours {
    background: var(--nt-brand-050, #eaf7f2);
    border-color: var(--nt-brand-200, #97d7bd);
}

/* -- unsent messages ------------------------------------------------------- */
/* Faded and italic: still in its place in the timeline, visibly not content. */
.nt-bubble.is-removed { opacity: 0.72; font-style: italic; }

/* == Instagram comments desk ==============================================
   Built for volume. A busy reel can carry hundreds of comments, and the desk's first job is to let
   one person read down them quickly, so a comment is a ROW (avatar, name, the words in a bubble,
   actions on the right) and the post is the heading above its own comments. Above the posts: four
   figures on how the desk stands, then one filter bar. Posts sit one, two or three abreast by the
   room there is: one post fills the width instead of stranding in a third of it. */

/* -- at a glance ------------------------------------------------------------ */
.nt-cd-stats {
    list-style: none;
    padding: 0;
    margin: 0 0 1rem;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
    gap: 0.75rem;
}

.nt-cd-stat {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 0.9rem 1rem;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    min-width: 0;
}

.nt-cd-stat.is-attention { border-color: #f1d9a8; background: linear-gradient(135deg, #fffaf0, var(--nt-surface) 60%); }

.nt-cd-stat-icon {
    flex: 0 0 2.4rem;
    width: 2.4rem;
    height: 2.4rem;
    border-radius: 0.65rem;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.1rem;
}

.nt-cd-stat-icon.is-blue { background: #e9f2f9; color: #164a73; }
.nt-cd-stat-icon.is-amber { background: #fdf3e2; color: #8a5300; }
.nt-cd-stat-icon.is-violet { background: #f1ecfb; color: #54369c; }
.nt-cd-stat-icon.is-green { background: #eaf7f2; color: #075440; }

.nt-cd-stat-body { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; flex: 1; }
.nt-cd-stat-label { font-size: var(--nt-text-xs); font-weight: 600; color: var(--nt-text-muted); }

.nt-cd-stat-value {
    font-size: 1.5rem;
    font-weight: 700;
    line-height: 1.2;
    color: var(--nt-text);
    font-variant-numeric: tabular-nums;
}

.nt-cd-stat-value small { font-size: var(--nt-text-sm); font-weight: 600; color: var(--nt-text-muted); }
.nt-cd-stat-hint { font-size: var(--nt-text-xs); color: var(--nt-text-muted); }

.nt-cd-meter {
    display: block;
    height: 0.35rem;
    margin-top: 0.35rem;
    border-radius: 999px;
    background: var(--nt-surface-2);
    border: 1px solid var(--nt-border);
    overflow: hidden;
}

.nt-cd-meter span { display: block; height: 100%; background: var(--nt-brand); border-radius: 999px; }

/* -- the filter bar --------------------------------------------------------- */
.nt-cd-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem 0.75rem;
    align-items: center;
    padding: 0.6rem 0.75rem;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    margin-bottom: 0.4rem;
}

.nt-cd-bar-group { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin: 0; }

/* A two-way switch between everything and what still needs an answer. */
.nt-cd-seg {
    display: inline-flex;
    padding: 0.2rem;
    gap: 0.2rem;
    background: var(--nt-surface-2);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-pill);
}

.nt-cd-seg a {
    padding: 0.3rem 0.85rem;
    border-radius: var(--nt-radius-pill);
    font-size: var(--nt-text-sm);
    font-weight: 600;
    color: var(--nt-text-muted);
    text-decoration: none;
    white-space: nowrap;
}

.nt-cd-seg a:hover { color: var(--nt-text); }
.nt-cd-seg a.is-active { background: var(--nt-surface); color: var(--nt-brand); box-shadow: var(--nt-shadow-sm); }

/* Sized here: the theme's .form-control/.form-select rule beats .form-select-sm at equal specificity. */
.nt-cd-select {
    width: auto;
    min-width: 12rem;
    max-width: 22rem;
    padding: 0.3rem 2rem 0.3rem 0.65rem;
    font-size: var(--nt-text-sm);
}

.nt-cd-sync {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.25rem 0.7rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-brand-050);
    color: #075440;
    font-size: var(--nt-text-xs);
    font-weight: 600;
    white-space: nowrap;
    cursor: default;
}

.nt-cd-sync.is-never { background: var(--nt-surface-2); color: var(--nt-text-muted); }

.nt-cd-note {
    display: flex;
    gap: 0.35rem;
    align-items: baseline;
    margin: 0 0 1rem;
    padding-inline: 0.25rem;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
}

/* -- the grid of posts ------------------------------------------------------- */
/* auto-fit, not a fixed column count: a single post takes the whole width, two share it, and three
   sit abreast only where each still gets 30rem. minmax(0 ...) keeps a long unbroken word from
   setting a column's width. */
.nt-cd-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 30rem), 1fr));
    gap: 1rem;
    align-items: start;
}

.nt-cd-card {
    overflow: hidden;
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow-sm);
    background: var(--nt-surface);
}

.nt-cd-card:has(.nt-cd-post.is-open) { border-color: var(--nt-brand-200); box-shadow: 0 4px 16px rgba(11, 122, 92, 0.08); }

/* -- the post heading ---------------------------------------------------------- */
.nt-cd-post {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 0.85rem;
    background: var(--nt-surface);
    border-bottom: 1px solid transparent;
}

.nt-cd-post:hover { background: var(--nt-surface-2); }
.nt-cd-post.is-open { background: linear-gradient(180deg, var(--nt-brand-050), var(--nt-surface)); border-bottom-color: var(--nt-border); }

/* The whole heading is the hit target; the permalink and automation links stay siblings, because
   an anchor inside a button is invalid and does not reliably reach the keyboard. */
.nt-cd-post-toggle {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    flex: 1;
    min-width: 0;
    background: none;
    border: 0;
    padding: 0;
    text-align: start;
    color: inherit;
    cursor: pointer;
}

.nt-cd-thumb-wrap { position: relative; flex: 0 0 3.25rem; width: 3.25rem; height: 3.25rem; }

.nt-cd-thumb {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 0.6rem;
}

/* The placeholder always sits underneath: an expired CDN thumbnail hides itself (site.js) and the
   glyph shows through, so a heading never collapses sideways. */
.nt-cd-thumb-none {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--nt-surface-2);
    border: 1px solid var(--nt-border);
    color: var(--nt-text-faint);
    font-size: 1.2rem;
}

.nt-cd-post-main { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 0.3rem; }

.nt-cd-post-title {
    display: block;
    font-size: var(--nt-text-base);
    font-weight: 650;
    color: var(--nt-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nt-cd-post-meta { display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; }

/* The Meta id: present for rules and support, quiet enough not to be read as content. */
.nt-cd-post-id { font-size: 0.68rem; color: var(--nt-text-faint); }

.nt-cd-chevron {
    flex: 0 0 auto;
    color: var(--nt-text-muted);
    transition: transform 0.15s ease;
}

.nt-cd-chevron.is-open { transform: rotate(180deg); }

.nt-cd-post-actions { display: flex; gap: 0.15rem; flex: 0 0 auto; }

/* -- chips (kind, counts, states) ------------------------------------------------ */
.nt-cd-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.1rem 0.5rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-surface-2);
    border: 1px solid var(--nt-border);
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--nt-text-muted);
    line-height: 1.5;
    white-space: nowrap;
}

.nt-cd-chip.is-attention { background: #fdf3e2; border-color: #f1d9a8; color: #8a5300; }
.nt-cd-chip.is-warning { background: #fdecea; border-color: #f3c7c1; color: #8a1f13; }
.nt-cd-chip.is-done { background: #eaf7f2; border-color: #bfe6d5; color: #075440; }
.nt-cd-chip.is-rule { background: #f1ecfb; border-color: #dccff5; color: #54369c; }

/* -- one comment ---------------------------------------------------------------- */
.nt-cd-comments { max-height: 30rem; overflow-y: auto; padding: 0.25rem 0; }

.nt-cd-row {
    display: grid;
    grid-template-columns: 2.1rem minmax(0, 1fr);
    gap: 0.65rem;
    padding: 0.7rem 0.9rem;
    border-left: 3px solid transparent;
}

/* Each comment is wrapped with its composers, so the divider goes between the wrappers. */
.nt-cd-comments > div + div { border-top: 1px solid #f0f2f1; }

.nt-cd-row:hover { background: var(--nt-surface-2); }

/* A comment nobody has answered carries an amber edge, so the working set is findable by eye. */
.nt-cd-row.is-open-question { border-left-color: #e0a63a; }

.nt-cd-avatar {
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
    font-weight: 700;
}

.nt-cd-avatar.is-t0 { background: #e9f2f9; color: #164a73; }
.nt-cd-avatar.is-t1 { background: #f1ecfb; color: #54369c; }
.nt-cd-avatar.is-t2 { background: #fdf3e2; color: #8a5300; }
.nt-cd-avatar.is-t3 { background: #fdecea; color: #8a1f13; }
.nt-cd-avatar.is-t4 { background: #eaf7f2; color: #075440; }
.nt-cd-avatar.is-t5 { background: #eef1f4; color: #3d4b57; }
.nt-cd-avatar.is-brand { background: linear-gradient(45deg, #f09433, #dc2743 55%, #bc1888); color: #fff; }

.nt-cd-main { min-width: 0; }

/* Identity on the left, actions pushed right, the comment underneath both: every row puts the
   actions in the same place, within a line of the words they act on. */
.nt-cd-head { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; line-height: 1.3; }
.nt-cd-author { font-weight: 650; font-size: var(--nt-text-sm); color: var(--nt-text); }
.nt-cd-when { font-size: var(--nt-text-xs); color: var(--nt-text-muted); }

.nt-cd-text {
    display: inline-block;
    max-width: 100%;
    margin: 0.35rem 0 0;
    padding: 0.45rem 0.75rem;
    background: var(--nt-surface-2);
    border: 1px solid #edf1ef;
    border-radius: 0.25rem 0.9rem 0.9rem 0.9rem;
    font-size: var(--nt-text-sm);
    line-height: 1.45;
    color: var(--nt-text);
    overflow-wrap: anywhere;
}

.nt-cd-row:hover .nt-cd-text { background: var(--nt-surface); }

/* Answered comments recede: the desk's working set is what still needs a reply. */
.nt-cd-row.is-answered .nt-cd-text { color: var(--nt-text-muted); }

/* What has already happened to this comment, rendered only when something has. */
.nt-cd-state { margin-top: 0.4rem; display: flex; flex-wrap: wrap; gap: 0.35rem; align-items: center; font-size: var(--nt-text-xs); color: var(--nt-text-muted); }

/* -- replies: one indent level, which is all Instagram itself nests ------------ */
.nt-cd-row.is-reply { margin-left: 2.75rem; padding-left: 0.75rem; border-left: 2px solid var(--nt-border); grid-template-columns: 1.75rem minmax(0, 1fr); }
.nt-cd-row.is-reply .nt-cd-avatar { width: 1.75rem; height: 1.75rem; font-size: 0.72rem; }
.nt-cd-row.is-reply .nt-cd-text { background: var(--nt-surface); }

/* -- actions: icon-only, each with a title and an aria-label ----------------------- */
.nt-cd-actions { display: flex; gap: 0.1rem; align-items: center; margin-inline-start: auto; flex: 0 0 auto; }
.nt-cd-actions form { display: inline; }

.nt-cd-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    min-width: 1.9rem;
    height: 1.9rem;
    border: 1px solid transparent;
    background: none;
    color: var(--nt-text-muted);
    border-radius: 0.5rem;
    padding: 0 0.4rem;
    font-size: 0.85rem;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
}

.nt-cd-btn:hover:not(:disabled) { background: var(--nt-surface); border-color: var(--nt-border); color: var(--nt-text); }
.nt-cd-btn:focus-visible { outline: 2px solid var(--nt-brand); outline-offset: 1px; }
.nt-cd-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.nt-cd-btn.is-primary { color: var(--nt-brand); }
.nt-cd-btn.is-danger:hover { color: var(--nt-danger); }

/* Time left on the one private reply, on the only button it governs. */
.nt-cd-btn .nt-cd-left { font-size: 0.7rem; font-variant-numeric: tabular-nums; }

/* -- the inline composer --------------------------------------------------------- */
.nt-cd-compose {
    margin: 0 0.9rem 0.6rem 3.65rem;
    padding: 0.75rem;
    background: var(--nt-surface-2);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
}

/* -- tablet and phone ------------------------------------------------------------ */
@media (max-width: 48rem) {
    .nt-cd-sync { margin-left: 0; }
    .nt-cd-bar-group { flex: 1 1 100%; }
    .nt-cd-select { max-width: none; flex: 1; }
}

@media (max-width: 36rem) {
    .nt-cd-stats { grid-template-columns: 1fr 1fr; gap: 0.5rem; }
    .nt-cd-stat { flex-direction: column; gap: 0.4rem; padding: 0.75rem; }
    .nt-cd-stat-value { font-size: 1.25rem; }
    .nt-cd-seg { width: 100%; }
    .nt-cd-seg a { flex: 1; text-align: center; }
    .nt-cd-post-id { display: none; }
    .nt-cd-post-title { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
    .nt-cd-actions { width: 100%; justify-content: flex-end; margin-inline-start: 0; }
    .nt-cd-row.is-reply { margin-left: 1.25rem; }
    .nt-cd-compose { margin-left: 0.9rem; }
}

/* A nav label is one line. "Instagram Comments" wrapped the row to two and broke the rhythm of the
   rail; the label was shortened, and this keeps any future one from doing it again — a long name now
   ellipsises inside its row instead of growing it. The badge and icon keep their size either way. */
.nt-nav-link > span:not(.nt-nav-count):not(.nt-nav-badge):not(.nt-nav-lock):not(.visually-hidden) {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---- Platform → Tenants → View (the tenant profile 360) ---- */

/* A label/value definition list read down a card. Tightens Bootstrap's default dd spacing and gives
   each pair a faint rule so a long profile stays scannable. Descendant selectors deliberately, so the
   list never claims the bare .nt-* names another component might own. */
.nt-detail-list > dt {
    font-weight: 500;
    padding-block: var(--nt-space-2);
}

.nt-detail-list > dd {
    margin-bottom: 0;
    padding-block: var(--nt-space-2);
}

.nt-detail-list > dt:not(:first-of-type),
.nt-detail-list > dd:not(:first-of-type) {
    border-top: 1px solid var(--nt-border);
}

/* One owner/admin contact block — name, email and phone — set off by a quiet left rule so several
   stack clearly inside one card without each needing its own. */
.nt-person {
    padding-left: var(--nt-space-3);
    border-left: 2px solid var(--nt-border);
}

/* ---- Instagram Content Planner (/InstagramContent) ---- */

/* The view toggle and the grid's status filter share one row; on a narrow screen they stack. */
.nt-cc-bar {
    display: flex;
    align-items: center;
    gap: var(--nt-space-3);
    flex-wrap: wrap;
    margin-bottom: var(--nt-space-4);
}

/* The status filter: its select and Show button on one line, pushed to the bar's end. */
.nt-cc-filter {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    margin-left: auto;
}

.nt-cc-month-bar {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    margin-bottom: var(--nt-space-3);
}

.nt-cc-month {
    font-size: 1.2rem;
    font-weight: 600;
    margin: 0 var(--nt-space-2);
}

.nt-cc-today { margin-left: auto; }

/* The month itself: a fixed-layout table so seven columns stay seven columns whatever a chip
   holds. Cells top-align and scroll their own overflow rather than growing the row unbounded. */
.nt-cc-cal {
    width: 100%;
    /* Seven day columns cannot go below this without the chips becoming unreadable; the
       .nt-table-wrap around the calendar scrolls sideways instead. */
    min-width: 42rem;
    table-layout: fixed;
    border-collapse: collapse;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    overflow: hidden;
    margin-bottom: var(--nt-space-5);
}

.nt-cc-cal th {
    padding: var(--nt-space-2);
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--nt-text-muted);
    text-align: left;
    border-bottom: 1px solid var(--nt-border);
}

.nt-cc-cal td {
    vertical-align: top;
    height: 6.5rem;
    padding: var(--nt-space-1);
    border: 1px solid var(--nt-border);
    overflow: hidden;
}

.nt-cc-cal td.nt-cc-out { background: var(--nt-surface-2); }

.nt-cc-cal td.nt-cc-today-cell { box-shadow: inset 0 0 0 2px var(--nt-brand, var(--nt-success)); }

.nt-cc-daynum {
    display: block;
    font-size: 0.8rem;
    color: var(--nt-text-muted);
    margin-bottom: var(--nt-space-1);
}

.nt-cc-out .nt-cc-daynum { color: var(--nt-text-faint); }

/* A chip is one planned post: time, kind icon, first words. Toned by status through the same
   Bootstrap tone names the card badges use, so the two views never disagree about a colour. */
.nt-cc-chip {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.75rem;
    line-height: 1.2;
    padding: 0.15rem 0.35rem;
    margin-bottom: 0.15rem;
    border-radius: var(--nt-radius-sm);
    text-decoration: none;
    color: var(--nt-text);
    background: var(--nt-surface-2);
    border-left: 3px solid var(--nt-border-strong);
    overflow: hidden;
    white-space: nowrap;
}

.nt-cc-chip:hover { background: var(--nt-border); color: var(--nt-text); }

.nt-cc-chip-primary { border-left-color: var(--nt-info); }
.nt-cc-chip-success { border-left-color: var(--nt-success); background: var(--nt-success-tint); }
.nt-cc-chip-danger { border-left-color: var(--nt-danger); background: var(--nt-danger-tint); }
.nt-cc-chip-info { border-left-color: var(--nt-info); background: var(--nt-info-tint); }

.nt-cc-chip-time { font-variant-numeric: tabular-nums; color: var(--nt-text-muted); }

.nt-cc-chip-title {
    overflow: hidden;
    text-overflow: ellipsis;
}

.nt-cc-section {
    font-size: 1rem;
    font-weight: 600;
    margin: var(--nt-space-5) 0 var(--nt-space-3);
}

.nt-cc-count {
    font-size: 0.8rem;
    color: var(--nt-text-muted);
    font-weight: 500;
}

/* The grid: responsive cards, media on top like the thing they will become. */
.nt-cc-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: var(--nt-space-4);
    margin-bottom: var(--nt-space-5);
}

.nt-cc-card {
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    overflow: hidden;
    box-shadow: var(--nt-shadow-sm);
    display: flex;
    flex-direction: column;
    transition: box-shadow 0.18s ease, border-color 0.18s ease;
}

/* The media is a link (it opens the post); the card answers the pointer like the preview tiles do. */
.nt-cc-card:hover {
    border-color: var(--nt-border-strong);
    box-shadow: var(--nt-shadow);
}

.nt-cc-card-media {
    display: block;
    aspect-ratio: 4 / 3;
    background: var(--nt-surface-2);
    overflow: hidden;
}

.nt-cc-card-media img { transition: transform 0.35s ease; }

.nt-cc-card:hover .nt-cc-card-media img { transform: scale(1.04); }

@media (prefers-reduced-motion: reduce) {
    .nt-cc-card { transition: none; }
    .nt-cc-card-media img { transition: none; }
    .nt-cc-card:hover .nt-cc-card-media img { transform: none; }
}

.nt-cc-card-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.nt-cc-card-glyph {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    font-size: 2rem;
    color: var(--nt-text-faint);
}

.nt-cc-card-body {
    padding: var(--nt-space-3);
    display: flex;
    flex-direction: column;
    gap: var(--nt-space-2);
    flex: 1;
}

.nt-cc-card-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--nt-space-2);
}

.nt-cc-card-kind {
    font-size: 0.8rem;
    color: var(--nt-text-muted);
}

.nt-cc-card-title {
    font-weight: 500;
    margin: 0;
    overflow-wrap: anywhere;
}

.nt-cc-card-meta {
    font-size: 0.8rem;
    color: var(--nt-text-muted);
    margin: 0;
}

.nt-cc-card-fail {
    font-size: 0.8rem;
    color: var(--nt-danger);
    background: var(--nt-danger-tint);
    border-radius: var(--nt-radius-sm);
    padding: var(--nt-space-2);
    margin: 0;
}

.nt-cc-card-note {
    font-size: 0.8rem;
    color: var(--nt-warning);
    background: var(--nt-warning-tint);
    border-radius: var(--nt-radius-sm);
    padding: var(--nt-space-2);
    margin: 0;
}

.nt-cc-card-actions {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    flex-wrap: wrap;
    margin-top: auto;
}

.nt-cc-empty {
    text-align: center;
    padding: var(--nt-space-7) var(--nt-space-4);
    background: var(--nt-surface);
    border: 1px dashed var(--nt-border-strong);
    border-radius: var(--nt-radius);
}

.nt-cc-empty .bi {
    font-size: 2rem;
    color: var(--nt-text-faint);
}

/* The composer and the read-only summary share a comfortable single column. */
.nt-cc-composer,
.nt-cc-summary {
    max-width: 40rem;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    padding: var(--nt-space-5);
}

.nt-cc-summary-caption { white-space: pre-wrap; }

.nt-cc-when { max-width: 16rem; }

.nt-cc-composer-actions {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    flex-wrap: wrap;
}

.nt-cc-assets {
    display: flex;
    gap: var(--nt-space-2);
    flex-wrap: wrap;
}

.nt-cc-asset {
    width: 7rem;
    margin: 0;
}

.nt-cc-asset img,
.nt-cc-asset-glyph {
    width: 7rem;
    height: 7rem;
    object-fit: cover;
    border-radius: var(--nt-radius-sm);
    border: 1px solid var(--nt-border);
}

.nt-cc-asset-glyph {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.6rem;
    color: var(--nt-text-faint);
    background: var(--nt-surface-2);
}

.nt-cc-asset figcaption {
    font-size: 0.7rem;
    color: var(--nt-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Phone width: the calendar's cells shrink; chips drop their title and keep icon + time. */
@media (max-width: 48rem) {
    .nt-cc-cal td { height: 4.5rem; }
    .nt-cc-chip-title { display: none; }
    .nt-cc-filter { margin-left: 0; width: 100%; }
}

/* The composer's delete stands apart from the save row — its own form, its own consequence. */
.nt-cc-composer-delete {
    max-width: 40rem;
    margin-top: var(--nt-space-3);
    display: flex;
    justify-content: flex-end;
}

/* The composer's picked-media list: each pick is added, never replaced, so carousels can be built
   one file at a time. A remove button sits on each thumbnail. */
.nt-cc-picked:empty { display: none; }
.nt-cc-picked { margin-top: 0.5rem; }
.nt-cc-picked-item { position: relative; }

.nt-cc-picked-item img,
.nt-cc-picked-item video {
    width: 7rem;
    height: 7rem;
    object-fit: cover;
    border-radius: var(--nt-radius-sm);
    border: 1px solid var(--nt-border);
    background: var(--nt-surface-2);
    display: block;
}

.nt-cc-picked-remove {
    position: absolute;
    top: 0.25rem;
    right: 0.25rem;
    width: 1.5rem;
    height: 1.5rem;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 0.7rem;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

.nt-cc-picked-remove:hover,
.nt-cc-picked-remove:focus-visible { background: #c0392b; }

/* ---- Instagram "Publish now" pop-up (composer) ----
   A gradient ring spins round the Instagram glyph while the post travels; the ring settles and a
   check pops in when it is live, or turns red with an exclamation when Instagram refuses. The step
   list fills in as the server reports progress. Reduced motion: the ring holds still. */
.nt-igp-modal .modal-dialog { max-width: 26rem; }

.nt-igp-body {
    text-align: center;
    padding: 2rem 1.75rem 1.5rem;
}

.nt-igp-stage {
    position: relative;
    width: 5.5rem;
    height: 5.5rem;
    margin: 0 auto 1.25rem;
}

.nt-igp-ring {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: conic-gradient(from 0deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888, #f09433);
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 0.35rem), #000 calc(100% - 0.3rem));
    mask: radial-gradient(farthest-side, transparent calc(100% - 0.35rem), #000 calc(100% - 0.3rem));
    animation: nt-igp-spin 1.1s linear infinite;
}

.nt-igp-icon {
    position: absolute;
    inset: 0.9rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    font-size: 1.9rem;
    color: #fff;
    background: linear-gradient(45deg, #f09433, #dc2743 55%, #bc1888);
    transition: opacity 0.25s ease, transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.nt-igp-icon-work { animation: nt-igp-pulse 1.6s ease-in-out infinite; }
.nt-igp-icon-done { background: var(--nt-brand); }
.nt-igp-icon-fail { background: #c0392b; }

.nt-igp-icon-done,
.nt-igp-icon-fail { opacity: 0; transform: scale(0.4); }

[data-igp-state="done"] .nt-igp-ring { animation: none; background: var(--nt-brand); }
[data-igp-state="failed"] .nt-igp-ring { animation: none; background: #c0392b; }
[data-igp-state="done"] .nt-igp-icon-work,
[data-igp-state="failed"] .nt-igp-icon-work { opacity: 0; animation: none; }
[data-igp-state="done"] .nt-igp-icon-done,
[data-igp-state="failed"] .nt-igp-icon-fail { opacity: 1; transform: scale(1); }

.nt-igp-title {
    font-size: 1.15rem;
    font-weight: 600;
    margin: 0 0 0.35rem;
}

.nt-igp-lede {
    color: var(--nt-text-muted);
    font-size: 0.9rem;
    margin: 0 0 1rem;
    min-height: 1.35rem;
}

.nt-igp-upload {
    height: 0.4rem;
    margin: 0 auto 1rem;
    max-width: 16rem;
}

.nt-igp-upload[hidden] { display: none; }

.nt-igp-upload .progress-bar {
    background: linear-gradient(90deg, #f09433, #dc2743, #bc1888);
    transition: width 0.2s ease;
}

.nt-igp-steps {
    list-style: none;
    padding: 0;
    margin: 0 auto;
    max-width: 16rem;
    text-align: left;
    font-size: 0.875rem;
    color: var(--nt-text-faint);
}

.nt-igp-steps li {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.2rem 0;
    transition: color 0.2s ease;
}

.nt-igp-steps li .bi::before { content: "\F28A"; } /* bi-circle */
.nt-igp-steps li.is-active { color: var(--nt-text); font-weight: 500; }
.nt-igp-steps li.is-active .bi::before { content: "\F130"; display: inline-block; animation: nt-igp-spin 1s linear infinite; } /* bi-arrow-repeat */
.nt-igp-steps li.is-done { color: var(--nt-brand); }
.nt-igp-steps li.is-done .bi::before { content: "\F26A"; } /* bi-check-circle-fill */
[data-igp-state="failed"] .nt-igp-steps li.is-active { color: #c0392b; }
[data-igp-state="failed"] .nt-igp-steps li.is-active .bi::before { content: "\F622"; animation: none; } /* bi-x-circle-fill */

.nt-igp-note {
    margin: 1rem 0 0;
    padding: 0.6rem 0.75rem;
    border-radius: var(--nt-radius-sm);
    background: var(--nt-surface-2);
    font-size: 0.85rem;
    text-align: left;
}

[data-igp-state="failed"] .nt-igp-note { background: #fdecea; color: #8a1f13; }

.nt-igp-actions {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-top: 1.25rem;
}

.nt-igp-actions[hidden],
.nt-igp-actions [hidden] { display: none; }

.nt-igp-background {
    display: inline-block;
    margin-top: 1rem;
    font-size: 0.8rem;
}

.nt-igp-background[hidden] { display: none; }

@keyframes nt-igp-spin { to { transform: rotate(360deg); } }

@keyframes nt-igp-pulse {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(0.92); }
}

@media (prefers-reduced-motion: reduce) {
    .nt-igp-ring,
    .nt-igp-icon-work,
    .nt-igp-steps li.is-active .bi::before { animation: none; }
}

/* ---- General settings (/Settings/General) ---- */

/* The island's name row is a link now; keep the two-line layout the text-only row had. */
.nt-island-profile strong { line-height: 1.3; }

/* A fluid grid that fills the content area: three cards sit abreast on a wide screen, pair up on
   a laptop, and stack on a phone — auto-fit does the arithmetic, and align-items: start keeps the
   short password card from being stretched to the company card's height. */
.nt-gs-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr));
    gap: var(--nt-space-5);
    align-items: start;
}

.nt-gs-card {
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    padding: var(--nt-space-5);
}

.nt-gs-card-title {
    font-size: 1.05rem;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    margin-bottom: var(--nt-space-1);
}

.nt-gs-card-lede {
    color: var(--nt-text-muted);
    font-size: 0.9rem;
    margin-bottom: var(--nt-space-4);
}

/* ---- Instagram feed preview (/Instagram/Preview) ---- */

/* The preview fills the content area edge to edge — the owner's call (2026-09-22) over the
   earlier centred phone-width column, which read as wasted canvas on a desktop console. */
.nt-ip-frame {
    width: 100%;
}

.nt-ip-account-bar {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    margin-bottom: var(--nt-space-4);
}

/* The profile header, the way Instagram lays it out: avatar left, identity right. */
.nt-ip-profile {
    display: flex;
    align-items: center;
    gap: var(--nt-space-5);
    margin-bottom: var(--nt-space-5);
}

/* The glyph paints first and the photo covers it; an expired photo hides itself (site.js) and
   the glyph is simply what remains — never the browser's broken-image icon. */
.nt-ip-avatar {
    position: relative;
    width: 4.5rem;
    height: 4.5rem;
    flex: none;
    border-radius: 50%;
    overflow: hidden;
    border: 1px solid var(--nt-border);
    background: var(--nt-surface-2);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.8rem;
    color: var(--nt-text-faint);
}

.nt-ip-avatar img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.nt-ip-handle {
    font-size: 1.15rem;
    font-weight: 600;
    margin: 0;
}

.nt-ip-name {
    color: var(--nt-text-muted);
    margin: 0;
}

.nt-ip-stats {
    margin: var(--nt-space-1) 0 0;
    font-size: 0.9rem;
}

.nt-ip-stats-upcoming {
    color: var(--nt-info);
    margin-left: var(--nt-space-2);
}

.nt-ip-legend {
    display: flex;
    align-items: center;
    gap: var(--nt-space-2);
    font-size: 0.85rem;
    color: var(--nt-text-muted);
    margin-bottom: var(--nt-space-3);
}

.nt-ip-legend-mark {
    width: 0.9rem;
    height: 0.9rem;
    flex: none;
    border: 2px dashed var(--nt-info);
    border-radius: 3px;
}

/* The grid itself: true squares with Instagram's hairline gap, as many columns as the width
   holds — a wide console gets a wall of tiles instead of three huge ones or a centred strip,
   and a phone still lands on Instagram's own three-across. */
.nt-ip-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
    gap: 3px;
    width: 100%;
    padding: 0;
    margin: 0 0 var(--nt-space-4);
    list-style: none;
}

/* Phone width: pin Instagram's own three-across so the preview stays the real thing there. */
@media (max-width: 48rem) {
    .nt-ip-grid { grid-template-columns: repeat(3, 1fr); }
}

.nt-ip-tile {
    position: relative;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    background: var(--nt-surface-2);
}

.nt-ip-tile.is-upcoming {
    outline: 2px dashed var(--nt-info);
    outline-offset: -2px;
}

.nt-ip-tile-body {
    position: absolute;
    inset: 0;
    display: block;
    color: inherit;
    text-decoration: none;
}

.nt-ip-tile-body img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Instagram's own quiet zoom on hover; the tile clips it (overflow: hidden above). */
    transition: transform 0.35s ease;
}

.nt-ip-tile:hover .nt-ip-tile-body img {
    transform: scale(1.045);
}

/* Always underneath the image: an expired CDN link then degrades to words, not a broken glyph. */
.nt-ip-tile-fallback {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: var(--nt-space-2);
    font-size: 0.7rem;
    color: var(--nt-text-muted);
    overflow: hidden;
}

.nt-ip-tile-kind {
    position: absolute;
    top: 0.35rem;
    right: 0.35rem;
    color: #ffffff;
    font-size: 0.85rem;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

.nt-ip-tile-when {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    padding: 0.2rem 0.35rem;
    font-size: 0.65rem;
    line-height: 1.2;
    color: #ffffff;
    background: rgba(13, 43, 37, 0.75);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nt-ip-footnote {
    font-size: 0.8rem;
    color: var(--nt-text-faint);
}

/* ---- Dashboard: the Instagram at-a-glance tiles ---- */

.nt-dash-ig-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--nt-space-3);
}

/* A stat that is also a door: same anatomy as the KPI tiles above it, framed hairline-light so
   four of them sit inside one card without reading as cards-in-a-card. */
.nt-dash-ig-tile {
    display: flex;
    flex-direction: column;
    gap: var(--nt-space-1);
    padding: var(--nt-space-3);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-sm);
    text-decoration: none;
    color: var(--nt-text);
    background: var(--nt-surface);
    transition: border-color 0.18s ease, background 0.18s ease, transform 0.18s ease,
        box-shadow 0.18s ease;
}

.nt-dash-ig-tile:hover {
    border-color: var(--nt-border-strong);
    background: var(--nt-surface-2);
    color: var(--nt-text);
    transform: translateY(-2px);
    box-shadow: var(--nt-shadow-sm);
}

@media (max-width: 30rem) {
    .nt-dash-ig-grid { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------------ scroll life
   The page's scroll-driven decoration: entrance animations for the component families that grew
   after the original cascade (§ motion / panels), and the floating back-to-top button that site.js
   builds on every non-flush page. Everything transform-based is neutralised for reduced motion. */

@media (prefers-reduced-motion: no-preference) {
    /* Anchor jumps and the back-to-top button glide instead of teleporting. */
    html { scroll-behavior: smooth; }

    /* CRM board cards, comment-desk posts and the content planner's grid cards fade up like every
       other panel; Instagram preview tiles fade in place (a translate would fight their grid), and
       the dashboard's Instagram doors pop in deal-out order. site.js pauses any of these that
       start below the fold and releases each one as it scrolls into view. */
    .nt-crm-card,
    .nt-cc-card,
    .nt-cd-post { animation: ntFadeInUp 0.45s cubic-bezier(0.16, 1, 0.3, 1) both; }

    .nt-ip-tile { animation: ntFadeIn 0.5s ease both; }
    .nt-ip-tile:nth-child(3n + 2) { animation-delay: 0.06s; }
    .nt-ip-tile:nth-child(3n) { animation-delay: 0.12s; }

    .nt-dash-ig-tile { animation: ntPop 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; }
    .nt-dash-ig-tile:nth-child(2) { animation-delay: 0.06s; }
    .nt-dash-ig-tile:nth-child(3) { animation-delay: 0.12s; }
    .nt-dash-ig-tile:nth-child(4) { animation-delay: 0.18s; }
}

.nt-top-btn {
    position: fixed;
    right: var(--nt-space-4);
    bottom: var(--nt-space-4);
    z-index: 1030;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    border: 1px solid var(--nt-border);
    border-radius: 50%;
    background: var(--nt-surface);
    color: var(--nt-brand-700);
    font-size: 1.1rem;
    box-shadow: var(--nt-shadow-lg);
    opacity: 0;
    visibility: hidden;
    transform: translateY(0.75rem);
    transition: opacity 0.25s ease, transform 0.25s ease, visibility 0.25s ease,
        background 0.18s ease, border-color 0.18s ease;
}

.nt-top-btn.is-shown {
    opacity: 1;
    visibility: visible;
    transform: none;
}

.nt-top-btn:hover {
    background: var(--nt-brand-050);
    border-color: var(--nt-brand-200);
    color: var(--nt-brand-700);
}

@media (prefers-reduced-motion: reduce) {
    .nt-top-btn { transform: none; transition: opacity 0.2s ease, visibility 0.2s ease; }
    .nt-dash-ig-tile { transition: none; }
    .nt-dash-ig-tile:hover { transform: none; }
    .nt-ip-tile-body img { transition: none; }
    .nt-ip-tile:hover .nt-ip-tile-body img { transform: none; }
}

/* ------------------------------------------------------------------ hover life
   One soft transition for every control whose :hover previously snapped between states. Grouped
   here rather than edited into each base rule so the list is auditable in one place; each class
   keeps its single defining rule where it lives. Colours only — deliberately no transform in the
   list, so the CRM board's drag-and-drop and the flow editor's node handles never animate against
   the pointer. Colour fades are gentle enough that no reduced-motion opt-out is needed. */
.nt-nav-link,
.nt-chip,
.nt-brand,
.link-primary,
.nt-conv-filter,
.nt-thread-back,
.nt-quote,
.nt-reply-strip-close,
button.nt-tick,
.nt-attr-remove,
.nt-media-pending-remove,
.nt-qr-list li,
.nt-controls-toggle,
.nt-thread-menu-btn,
.nt-controls-close,
a.nt-row,
.nt-tag-remove,
.nt-choice,
.nt-keyword-remove,
.nt-node-grab,
.nt-pwa-install-close,
.nt-crm-card,
.nt-cd-post,
.nt-cd-row,
.nt-cd-btn,
.nt-cc-chip {
    transition: background 0.15s ease, background-color 0.15s ease, color 0.15s ease,
        border-color 0.15s ease, box-shadow 0.15s ease;
}

/* ---- Instagram analytics (/Instagram/Analytics) ----
   Tiles for the headline numbers, server-drawn SVG columns for the daily series, ranked bar lists
   for breakdowns, and one two-part split for followers vs non-followers. Series colours were run
   through the dataviz validator (light and dark surfaces): brand green and violet, CVD dE 18.7.
   Text never takes a series colour; values sit in text tokens beside the marks. */
.nt-ia-filters,
.nt-ia-custom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.nt-ia-filters { margin-bottom: 0.5rem; }
.nt-ia-account { width: auto; min-width: 12rem; }
.nt-ia-custom { margin-left: auto; }
.nt-ia-custom .form-control { width: auto; }
.nt-ia-custom span { color: var(--nt-text-muted); font-size: var(--nt-text-sm); }

.nt-ia-range-note {
    color: var(--nt-text-muted);
    font-size: var(--nt-text-sm);
    margin: 0 0 1rem;
}

.nt-ia-profile {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 1.25rem;
}

.nt-ia-profile .nt-ip-stats { display: flex; flex-wrap: wrap; gap: 1rem; }

.nt-ia-tiles {
    list-style: none;
    padding: 0;
    margin: 0 0 1.25rem;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: 0.75rem;
}

.nt-ia-tile {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.9rem 1rem;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
}

.nt-ia-tile-label { font-size: var(--nt-text-sm); color: var(--nt-text-muted); font-weight: 600; }
.nt-ia-tile-value { font-size: 1.6rem; font-weight: 700; line-height: 1.15; color: var(--nt-text); font-variant-numeric: tabular-nums; }
.nt-ia-tile.is-empty .nt-ia-tile-value { color: var(--nt-text-faint); }
.nt-ia-tile-hint { font-size: var(--nt-text-xs); color: var(--nt-text-muted); line-height: 1.35; }

.nt-ia-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    gap: 0.75rem;
    margin-bottom: 1.25rem;
}

.nt-ia-card {
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    padding: 1rem 1.1rem;
    min-width: 0;
}

.nt-ia-wide { grid-column: 1 / -1; }

.nt-ia-card-title {
    font-size: var(--nt-text-base);
    font-weight: 650;
    margin: 0 0 0.75rem;
    color: var(--nt-text);
}

.nt-ia-section-title { font-size: var(--nt-text-md); font-weight: 650; margin: 0.5rem 0 0.75rem; }
.nt-ia-section-lede { margin: -0.4rem 0 0.75rem; }
.nt-ia-muted { color: var(--nt-text-muted); }
.nt-ia-empty { color: var(--nt-text-muted); font-size: var(--nt-text-sm); margin: 0; }
p.nt-ia-empty.nt-ia-card { margin-bottom: 1.25rem; }

/* Column chart. */
.nt-ia-chart { margin: 0; }
.nt-ia-chart svg { display: block; width: 100%; height: auto; overflow: visible; }
line.nt-ia-grid { stroke: var(--nt-border); stroke-width: 1; }
.nt-ia-axis { fill: var(--nt-text-muted); font-size: 0.7rem; font-family: var(--nt-font); }
.nt-ia-hit { fill: transparent; }
.nt-ia-mark { fill: var(--nt-ia-series-a, #0b7a5c); transition: opacity 0.15s ease; }
.nt-ia-bar { cursor: default; outline: none; }
.nt-ia-bar.is-hover .nt-ia-hit,
.nt-ia-bar:focus-visible .nt-ia-hit { fill: rgba(11, 122, 92, 0.08); }
.nt-ia-bar.is-hover .nt-ia-mark { opacity: 0.85; }

.nt-ia-table { margin-top: 0.5rem; font-size: var(--nt-text-sm); }
.nt-ia-table summary { cursor: pointer; color: var(--nt-text-muted); }
.nt-ia-table table { margin-top: 0.5rem; }

/* Ranked bar lists. */
.nt-ia-bars { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.5rem; }

.nt-ia-bar-row {
    display: grid;
    grid-template-columns: minmax(6rem, 9rem) 1fr auto;
    align-items: center;
    gap: 0.6rem;
    font-size: var(--nt-text-sm);
    border-radius: var(--nt-radius-sm);
    outline: none;
}

.nt-ia-bar-row.is-hover,
.nt-ia-bar-row:focus-visible { background: var(--nt-surface-2); }
.nt-ia-bar-label { color: var(--nt-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nt-ia-bar-track { height: 0.6rem; background: var(--nt-surface-2); border-radius: 999px; overflow: hidden; }
.nt-ia-bar-fill { display: block; height: 100%; background: var(--nt-ia-series-a, #0b7a5c); border-radius: 999px; min-width: 0.25rem; }
.nt-ia-bar-value { color: var(--nt-text); font-weight: 600; font-variant-numeric: tabular-nums; }

/* Followers vs non-followers: one bar in two parts, a 2px surface gap between them. */
.nt-ia-split {
    display: flex;
    gap: 2px;
    height: 1.1rem;
    border-radius: 999px;
    overflow: hidden;
    margin-bottom: 0.75rem;
}

.nt-ia-split-part { min-width: 0.3rem; outline: none; }
.nt-ia-split-part.is-hover,
.nt-ia-split-part:focus-visible { opacity: 0.85; }
.is-a.nt-ia-split-part,
.is-a.nt-ia-swatch { background: var(--nt-ia-series-a, #0b7a5c); }
.is-b.nt-ia-split-part,
.is-b.nt-ia-swatch { background: var(--nt-ia-series-b, #6b46c1); }

.nt-ia-legend { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 1rem; font-size: var(--nt-text-sm); }
.nt-ia-legend li { display: flex; align-items: center; gap: 0.35rem; }
.nt-ia-swatch { width: 0.7rem; height: 0.7rem; border-radius: 0.2rem; display: inline-block; }

/* Top posts. */
.nt-ia-posts {
    list-style: none;
    padding: 0;
    margin: 0 0 1.25rem;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
    gap: 0.75rem;
}

.nt-ia-post {
    display: flex;
    gap: 0.75rem;
    padding: 0.6rem;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    color: inherit;
    text-decoration: none;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.nt-ia-post:hover,
.nt-ia-post:focus-visible { border-color: var(--nt-brand); box-shadow: var(--nt-shadow-sm); color: inherit; }

.nt-ia-post-thumb {
    position: relative;
    flex: 0 0 4.5rem;
    width: 4.5rem;
    height: 4.5rem;
    border-radius: var(--nt-radius-sm);
    background: var(--nt-surface-2);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--nt-text-faint);
    font-size: 1.3rem;
}

.nt-ia-post-thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.nt-ia-post-thumb.is-large { flex-basis: 11rem; width: 11rem; height: 11rem; font-size: 2rem; }

.nt-ia-post-kind {
    position: absolute;
    left: 0.25rem;
    bottom: 0.25rem;
    padding: 0 0.35rem;
    border-radius: 0.25rem;
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    font-size: 0.65rem;
    line-height: 1.4;
}

.nt-ia-post-body { display: flex; flex-direction: column; gap: 0.35rem; min-width: 0; }

.nt-ia-post-caption {
    font-size: var(--nt-text-sm);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.nt-ia-post-meta { display: flex; flex-wrap: wrap; gap: 0.75rem; font-size: var(--nt-text-xs); color: var(--nt-text-muted); }

.nt-ia-post-head { display: flex; gap: 1.25rem; align-items: flex-start; margin-bottom: 1.25rem; }
.nt-ia-post-detail { min-width: 0; }
.nt-ia-post-full-caption { white-space: pre-wrap; margin: 0 0 0.75rem; max-width: 48rem; }

.nt-ia-footnote { color: var(--nt-text-muted); font-size: var(--nt-text-xs); margin: 0 0 0.5rem; }

.nt-ia-tooltip {
    position: fixed;
    z-index: 1080;
    pointer-events: none;
    padding: 0.35rem 0.55rem;
    border-radius: var(--nt-radius-sm);
    background: #14201d;
    color: #fff;
    font-size: var(--nt-text-xs);
    box-shadow: var(--nt-shadow);
    white-space: nowrap;
}

.nt-ia-tooltip[hidden] { display: none; }

@media (max-width: 48rem) {
    .nt-ia-custom { margin-left: 0; }
    .nt-ia-post-head { flex-direction: column; }
    .nt-ia-grid { grid-template-columns: 1fr; }
    .nt-ia-bar-row { grid-template-columns: minmax(5rem, 7rem) 1fr auto; }
}

/* ---- Instagram post insights (/Instagram/Analytics/Post/{id}) ----
   The post as Instagram shows it on the left (sticky on a wide screen), its figures grouped on the
   right: a headline row, engagement with the interaction mix, and watch time or profile activity.
   Stacks to one column on a tablet or phone. Chip tints mark what a figure is; values stay in text
   colour. A figure Instagram withheld reads "Not available" in quiet text. */
.nt-ips {
    display: grid;
    grid-template-columns: minmax(16rem, 22rem) minmax(0, 1fr);
    gap: 1.25rem;
    align-items: start;
}

.nt-ips-post {
    position: sticky;
    top: calc(var(--nt-topbar-height, 3.75rem) + 1rem);
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow-sm);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.nt-ips-post-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.75rem 0.9rem;
}

.nt-ips-avatar {
    position: relative;
    flex: 0 0 2.25rem;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    background: linear-gradient(45deg, #f09433, #dc2743 55%, #bc1888);
    overflow: hidden;
}

.nt-ips-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

.nt-ips-post-who { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.nt-ips-post-who strong { font-size: var(--nt-text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nt-ips-post-who span { font-size: var(--nt-text-xs); color: var(--nt-text-muted); }

.nt-ips-kind {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.55rem;
    border-radius: var(--nt-radius-pill);
    background: var(--nt-surface-2);
    border: 1px solid var(--nt-border);
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
    white-space: nowrap;
}

.nt-ips-media {
    position: relative;
    aspect-ratio: 1 / 1;
    background: var(--nt-surface-2);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--nt-text-faint);
    font-size: 2.5rem;
    border-top: 1px solid var(--nt-border);
    border-bottom: 1px solid var(--nt-border);
}

.nt-ips-media.is-portrait { aspect-ratio: 4 / 5; }
.nt-ips-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

.nt-ips-play {
    position: absolute;
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.45);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.6rem;
}

.nt-ips-post-counts {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.7rem 0.9rem 0.2rem;
    font-weight: 600;
    font-size: var(--nt-text-sm);
    font-variant-numeric: tabular-nums;
}

.nt-ips-post-counts .bi { margin-right: 0.2rem; }

.nt-ips-caption {
    padding: 0.35rem 0.9rem 0;
    font-size: var(--nt-text-sm);
    line-height: 1.5;
}

.nt-ips-caption p { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }

.nt-ips-caption.is-clamped:not(.is-open) p {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.nt-ips-more { align-self: flex-start; padding: 0.15rem 0.9rem; font-size: var(--nt-text-xs); }

.nt-ips-open { margin: 0.75rem 0.9rem 0.9rem; align-self: flex-start; }

.nt-ips-main { display: flex; flex-direction: column; gap: 1rem; min-width: 0; container-type: inline-size; }

.nt-ips-section {
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    padding: 1rem 1.1rem 1.1rem;
    min-width: 0;
}

.nt-ips-section-title {
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--nt-text-muted);
    margin: 0 0 0.85rem;
}

.nt-ips-sub { font-size: var(--nt-text-sm); font-weight: 650; margin: 1.1rem 0 0.6rem; }

.nt-ips-pair {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
    gap: 1rem;
    align-items: start;
}

.nt-ips-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
    gap: 0.75rem;
}

.nt-ips-stats.is-stack { grid-template-columns: 1fr; }

/* Fixed shapes, so a row never ends on one orphan card: four across or two by two. */
.nt-ips-stats.is-hero { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.nt-ips-stats.is-quad { grid-template-columns: repeat(2, minmax(0, 1fr)); }

@container (max-width: 46rem) {
    .nt-ips-stats.is-hero { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.nt-ips-stat {
    display: flex;
    align-items: flex-start;
    gap: 0.7rem;
    padding: 0.8rem 0.85rem;
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    background: var(--nt-surface);
    min-width: 0;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.nt-ips-stat:hover { border-color: var(--nt-border-strong); box-shadow: var(--nt-shadow-sm); }

.nt-ips-stats.is-hero .nt-ips-stat { padding: 1rem; }

.nt-ips-chip {
    flex: 0 0 2.25rem;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 0.6rem;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
}

.nt-ips-chip.is-green { background: #eaf7f2; color: #075440; }
.nt-ips-chip.is-blue { background: #e9f2f9; color: #164a73; }
.nt-ips-chip.is-violet { background: #f1ecfb; color: #54369c; }
.nt-ips-chip.is-amber { background: #fdf3e2; color: #8a5300; }
.nt-ips-chip.is-rose { background: #fdecea; color: #8a1f13; }

.nt-ips-stat-body { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.nt-ips-stat-label { font-size: var(--nt-text-xs); font-weight: 600; color: var(--nt-text-muted); }

.nt-ips-stat-value {
    font-size: 1.35rem;
    font-weight: 700;
    line-height: 1.2;
    color: var(--nt-text);
    font-variant-numeric: tabular-nums;
}

.nt-ips-stats.is-hero .nt-ips-stat-value { font-size: 1.65rem; }

.nt-ips-stat-na {
    font-size: var(--nt-text-sm);
    font-weight: 600;
    color: var(--nt-text-faint);
    line-height: 1.9;
}

.nt-ips-stat-hint { font-size: var(--nt-text-xs); color: var(--nt-text-muted); line-height: 1.35; }
.nt-ips-stat.is-empty .nt-ips-chip { opacity: 0.55; }

.nt-ips-foot p { color: var(--nt-text-muted); font-size: var(--nt-text-xs); margin: 0 0 0.35rem; }

/* Tablet and phone: one column, the post card first and no longer sticky. */
@media (max-width: 64rem) {
    .nt-ips { grid-template-columns: 1fr; }
    .nt-ips-post { position: static; max-width: 34rem; width: 100%; }

    /* The post card is context here, not the point: a short media strip keeps the figures within
       reach of the first screen instead of a full-height reel preview. */
    .nt-ips-media,
    .nt-ips-media.is-portrait { aspect-ratio: auto; height: 13rem; }
}

@media (max-width: 36rem) {
    .nt-ips-stats,
    .nt-ips-stats.is-hero,
    .nt-ips-stats.is-quad { grid-template-columns: 1fr 1fr; gap: 0.5rem; }
    .nt-ips-stats.is-stack { grid-template-columns: 1fr; }
    .nt-ips-stat { flex-direction: column; gap: 0.45rem; padding: 0.7rem; }
    .nt-ips-stats.is-hero .nt-ips-stat-value { font-size: 1.35rem; }
    .nt-ips-stat-hint { display: none; }
    .nt-ips-pair { grid-template-columns: 1fr; }
}

/* ---- DM Automation (/Instagram/DmAutomation) ----
   How a rule works as three steps, Instagram's limits as three figures, the rules as cards that
   wrap by the room there is (a table scrolled sideways on a phone). */
.nt-dm-intro {
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    padding: 1rem 1.1rem;
    margin-bottom: 1.5rem;
}

.nt-dm-flow {
    list-style: none;
    padding: 0;
    margin: 0 0 1rem;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
    gap: 0.75rem;
    counter-reset: dmstep;
}

.nt-dm-flow li {
    display: flex;
    align-items: flex-start;
    gap: 0.7rem;
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
    line-height: 1.45;
}

.nt-dm-flow strong { color: var(--nt-text); }

.nt-dm-flow-icon {
    flex: 0 0 2.25rem;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 0.6rem;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1rem;
}

.nt-dm-flow-icon.is-blue { background: #e9f2f9; color: #164a73; }
.nt-dm-flow-icon.is-violet { background: #f1ecfb; color: #54369c; }
.nt-dm-flow-icon.is-green { background: #eaf7f2; color: #075440; }

.nt-dm-limits {
    list-style: none;
    padding: 0.75rem 0 0;
    margin: 0;
    border-top: 1px dashed var(--nt-border);
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.75rem;
}

.nt-dm-limits li { display: flex; flex-direction: column; gap: 0.1rem; }
.nt-dm-limits strong { font-size: 1.25rem; font-weight: 700; color: var(--nt-text); line-height: 1.2; }
.nt-dm-limits span { font-size: var(--nt-text-xs); color: var(--nt-text-muted); }

.nt-dm-limits-note {
    display: flex;
    gap: 0.35rem;
    align-items: baseline;
    margin: 0.75rem 0 0;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
}

.nt-dm-list-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    margin-bottom: 0.75rem;
}

.nt-dm-list-title { font-size: var(--nt-text-md); font-weight: 650; margin: 0; }
.nt-dm-list-sub { font-size: var(--nt-text-sm); color: var(--nt-text-muted); margin: 0.15rem 0 0; }

.nt-dm-totals { list-style: none; padding: 0; margin: 0; display: flex; gap: 1rem; font-size: var(--nt-text-sm); color: var(--nt-text-muted); }
.nt-dm-totals strong { color: var(--nt-text); font-variant-numeric: tabular-nums; }

/* -- the rules -------------------------------------------------------------- */
.nt-dm-rules {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
    gap: 0.9rem;
}

.nt-dm-rule {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    padding: 1rem 1.1rem;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow-sm);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.nt-dm-rule:hover { border-color: var(--nt-brand-200); box-shadow: var(--nt-shadow); }
.nt-dm-rule.is-paused { background: var(--nt-surface-2); }
.nt-dm-rule.is-paused .nt-dm-rule-name { color: var(--nt-text-muted); }

.nt-dm-rule-head { display: flex; align-items: center; gap: 0.6rem; min-width: 0; }

.nt-dm-rule-priority {
    flex: 0 0 auto;
    min-width: 1.75rem;
    height: 1.75rem;
    padding: 0 0.4rem;
    border-radius: 0.5rem;
    background: var(--nt-surface-2);
    border: 1px solid var(--nt-border);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--nt-text-xs);
    font-weight: 700;
    color: var(--nt-text-muted);
    font-variant-numeric: tabular-nums;
}

.nt-dm-rule-name {
    flex: 1;
    min-width: 0;
    font-weight: 650;
    color: var(--nt-text);
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nt-dm-rule-name:hover { color: var(--nt-brand); }

.nt-dm-rule-facts { margin: 0; display: grid; gap: 0.5rem; }
.nt-dm-rule-facts > div { display: grid; grid-template-columns: 6rem minmax(0, 1fr); gap: 0.5rem; align-items: baseline; }
.nt-dm-rule-facts dt { font-size: var(--nt-text-xs); font-weight: 600; color: var(--nt-text-muted); }
.nt-dm-rule-facts dd { margin: 0; font-size: var(--nt-text-sm); overflow-wrap: anywhere; }
.nt-dm-rule-does { display: flex; flex-wrap: wrap; gap: 0.35rem; }

.nt-dm-tag {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.1rem 0.55rem;
    border-radius: var(--nt-radius-pill);
    background: #e9f2f9;
    color: #164a73;
    font-size: var(--nt-text-xs);
    font-weight: 600;
}

.nt-dm-tag.is-dm { background: #eaf7f2; color: #075440; }

.nt-dm-rule-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--nt-border);
    margin-top: auto;
}

.nt-dm-rule-stats { display: flex; gap: 0.85rem; font-size: var(--nt-text-xs); color: var(--nt-text-muted); }
.nt-dm-rule-stats strong { color: var(--nt-text); font-size: var(--nt-text-sm); font-variant-numeric: tabular-nums; }
.nt-dm-rule-actions { display: flex; gap: 0.35rem; }

/* -- the empty state ---------------------------------------------------------- */
.nt-dm-empty {
    text-align: center;
    padding: 2rem 1.25rem;
    background: var(--nt-surface);
    border: 1px dashed var(--nt-border-strong);
    border-radius: var(--nt-radius-lg);
}

.nt-dm-empty-icon {
    width: 3.25rem;
    height: 3.25rem;
    margin: 0 auto 0.75rem;
    border-radius: 1rem;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    color: #fff;
    background: linear-gradient(45deg, #f09433, #dc2743 55%, #bc1888);
}

.nt-dm-empty h3 { font-size: var(--nt-text-md); font-weight: 650; margin: 0 0 0.25rem; }
.nt-dm-empty > p { color: var(--nt-text-muted); font-size: var(--nt-text-sm); margin: 0 0 1.1rem; }
.nt-dm-empty .nt-dm-empty-tip { margin: 1rem auto 0; max-width: 34rem; font-size: var(--nt-text-xs); }

.nt-dm-example {
    display: flex;
    width: fit-content;
    margin-inline: auto;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.75rem;
    padding: 0.9rem 1rem;
    margin-bottom: 1.25rem;
    background: var(--nt-surface-2);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius);
    max-width: 100%;
}

.nt-dm-example-step { display: flex; flex-direction: column; align-items: flex-start; gap: 0.3rem; }
.nt-dm-example-label { font-size: 0.68rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--nt-text-muted); }
.nt-dm-example-arrow { color: var(--nt-text-faint); }

.nt-dm-bubble {
    padding: 0.35rem 0.7rem;
    border-radius: 0.25rem 0.9rem 0.9rem 0.9rem;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    font-size: var(--nt-text-sm);
    font-weight: 600;
}

.nt-dm-bubble.is-ours { background: var(--nt-brand-050); border-color: #bfe6d5; color: #075440; font-weight: 500; }

/* -- the rule editor -------------------------------------------------------------- */
.nt-dm-media-pick { max-height: 16rem; overflow-y: auto; padding-right: 0.25rem; }

.nt-dm-save {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 1.25rem;
    padding-top: 1rem;
    border-top: 1px solid var(--nt-border);
}

@media (max-width: 36rem) {
    .nt-dm-limits { grid-template-columns: 1fr; gap: 0.5rem; }
    .nt-dm-limits li { flex-direction: row; align-items: baseline; gap: 0.5rem; }
    .nt-dm-example { flex-direction: column; align-items: stretch; }
    .nt-dm-example-arrow { transform: rotate(90deg); align-self: center; }
    .nt-dm-rule-foot { flex-direction: column; align-items: stretch; }
    .nt-dm-rule-actions { justify-content: flex-end; }
    .nt-dm-save .btn { flex: 1; }
}

/* ---- Content Planner composer (/InstagramContent/New and /Edit) ----
   Numbered steps on the left, a live phone-style preview of the post on the right (sticky on a wide
   screen, below the form on a narrow one). The type is a row of picture tiles, media is a drop zone
   the file input covers invisibly, and the three intents sit in one bar at the end. */
.nt-pc {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(17rem, 21rem);
    gap: 1.5rem;
    align-items: start;
    max-width: 72rem;
}

.nt-pc-form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    min-width: 0;
}

.nt-pc-step {
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    padding: 1.1rem 1.2rem 1.2rem;
}

.nt-pc-step fieldset { margin: 0; padding: 0; border: 0; }

.nt-pc-step-title {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-size: var(--nt-text-base);
    font-weight: 650;
    margin: 0 0 1rem;
}

.nt-pc-step-num {
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--nt-brand-050);
    color: var(--nt-brand);
    font-size: var(--nt-text-xs);
    font-weight: 700;
}

/* -- the type tiles ----------------------------------------------------------- */
.nt-pc-kinds {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.6rem;
}

.nt-pc-kind { position: relative; margin: 0; cursor: pointer; }
.nt-pc-kind input { position: absolute; opacity: 0; pointer-events: none; }

.nt-pc-kind-body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.2rem;
    height: 100%;
    padding: 0.8rem 0.85rem;
    border: 1.5px solid var(--nt-border);
    border-radius: var(--nt-radius);
    background: var(--nt-surface);
    transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}

.nt-pc-kind-body .bi { font-size: 1.25rem; color: var(--nt-text-muted); margin-bottom: 0.2rem; }
.nt-pc-kind-body strong { font-size: var(--nt-text-sm); }
.nt-pc-kind-body span { font-size: var(--nt-text-xs); color: var(--nt-text-muted); line-height: 1.35; }

.nt-pc-kind:hover .nt-pc-kind-body { border-color: var(--nt-border-strong); }
.nt-pc-kind input:focus-visible + .nt-pc-kind-body { box-shadow: 0 0 0 3px rgba(11, 122, 92, 0.25); }

.nt-pc-kind input:checked + .nt-pc-kind-body {
    border-color: var(--nt-brand);
    background: var(--nt-brand-050);
    box-shadow: 0 2px 8px rgba(11, 122, 92, 0.12);
}

.nt-pc-kind input:checked + .nt-pc-kind-body .bi { color: var(--nt-brand); }

/* -- the drop zone -------------------------------------------------------------- */
.nt-pc-drop {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
    padding: 1.6rem 1rem;
    border: 2px dashed var(--nt-border-strong);
    border-radius: var(--nt-radius-lg);
    background: var(--nt-surface-2);
    text-align: center;
    transition: border-color 0.15s ease, background 0.15s ease;
}

.nt-pc-drop:hover,
.nt-pc-drop.is-over { border-color: var(--nt-brand); background: var(--nt-brand-050); }
.nt-pc-drop:focus-within { border-color: var(--nt-brand); box-shadow: 0 0 0 3px rgba(11, 122, 92, 0.2); }

/* The real file input, invisible but covering the zone: clicks and drops both land on it. */
.nt-pc-drop-input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }

.nt-pc-drop-icon {
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    color: var(--nt-brand);
    font-size: 1.4rem;
    pointer-events: none;
}

.nt-pc-drop-text { margin: 0; font-size: var(--nt-text-sm); color: var(--nt-text); pointer-events: none; }
.nt-pc-drop-link { color: var(--nt-brand); font-weight: 600; text-decoration: underline; }
.nt-pc-drop-hint { font-size: var(--nt-text-xs); color: var(--nt-text-muted); pointer-events: none; }

/* -- caption ---------------------------------------------------------------- */
.nt-pc-caption { resize: vertical; min-height: 8rem; }

.nt-pc-caption-foot {
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    margin-top: 0.35rem;
    font-size: var(--nt-text-xs);
    color: var(--nt-text-muted);
}

.nt-pc-count { font-variant-numeric: tabular-nums; white-space: nowrap; }
.nt-pc-count.is-near { color: #8a5300; font-weight: 600; }

/* -- the actions bar ------------------------------------------------------------ */
.nt-pc-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.5rem;
    padding: 0.85rem 1rem;
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: var(--nt-radius-lg);
    box-shadow: var(--nt-shadow-sm);
    position: sticky;
    bottom: 0.75rem;
    z-index: 3;
}

.nt-pc-actions .btn { display: inline-flex; align-items: center; gap: 0.35rem; }

/* -- the preview ---------------------------------------------------------------- */
.nt-pc-preview {
    position: sticky;
    top: calc(var(--nt-topbar-height, 3.75rem) + 1rem);
}

.nt-pc-preview-label {
    font-size: var(--nt-text-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--nt-text-muted);
    margin: 0 0 0.5rem;
}

.nt-pc-phone {
    background: var(--nt-surface);
    border: 1px solid var(--nt-border);
    border-radius: 1.25rem;
    box-shadow: var(--nt-shadow);
    overflow: hidden;
}

.nt-pc-phone-head { display: flex; align-items: center; gap: 0.5rem; padding: 0.65rem 0.8rem; font-size: var(--nt-text-sm); }
.nt-pc-phone-head strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.nt-pc-phone-avatar {
    flex: 0 0 1.9rem;
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 0.85rem;
    background: linear-gradient(45deg, #f09433, #dc2743 55%, #bc1888);
}

.nt-pc-phone-media {
    position: relative;
    aspect-ratio: 4 / 5;
    background: var(--nt-surface-2);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

.nt-pc-phone.is-story .nt-pc-phone-media { aspect-ratio: 9 / 16; }
.nt-pc-phone-media img,
.nt-pc-phone-media video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

.nt-pc-phone-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
    color: var(--nt-text-faint);
    font-size: var(--nt-text-xs);
    text-align: center;
    padding: 1rem;
}

.nt-pc-phone-empty .bi { font-size: 2rem; }
.nt-pc-phone-empty[hidden] { display: none; }

.nt-pc-phone-dots { display: flex; justify-content: center; gap: 0.25rem; padding-top: 0.5rem; }
.nt-pc-phone-dots[hidden] { display: none; }
.nt-pc-phone-dots span { width: 0.35rem; height: 0.35rem; border-radius: 50%; background: var(--nt-border-strong); }
.nt-pc-phone-dots span.is-on { background: #3897f0; }

.nt-pc-phone-actions { display: flex; gap: 0.9rem; padding: 0.55rem 0.8rem 0.2rem; font-size: 1.1rem; color: var(--nt-text); }
.nt-pc-phone.is-story .nt-pc-phone-actions { display: none; }

.nt-pc-phone-caption {
    margin: 0;
    padding: 0.2rem 0.8rem 0.9rem;
    font-size: var(--nt-text-xs);
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.nt-pc-phone-caption[hidden] { display: none; }
.nt-pc-preview-note { margin: 0.6rem 0 0; font-size: var(--nt-text-xs); color: var(--nt-text-muted); }

/* -- tablet and phone ------------------------------------------------------------ */
@media (max-width: 64rem) {
    .nt-pc { grid-template-columns: 1fr; }
    .nt-pc-preview { position: static; max-width: 22rem; width: 100%; margin: 0 auto; }
}

@media (max-width: 48rem) {
    .nt-pc-kinds { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 36rem) {
    .nt-pc-step { padding: 1rem; }
    /* Stacked, three buttons would cover a quarter of a phone screen if they stayed pinned, so here
       they sit at the end of the form instead. */
    .nt-pc-actions { flex-direction: column-reverse; align-items: stretch; position: static; }
    .nt-pc-actions .btn { justify-content: center; }
    .nt-cc-when { max-width: none; }
}

/* ---- Analytics: skeleton while Instagram answers ----
   The analytics pages arrive at once and fetch their figures afterwards; these hold the shape of what
   is coming, in the shared .nt-skel shimmer, and the figures fade in over them. */
.nt-ia-skel-avatar { width: 4.5rem; height: 4.5rem; border-radius: 50%; flex: 0 0 auto; }
.nt-ia-skel-lines { display: flex; flex-direction: column; gap: 0.5rem; }
.nt-ia-skel-line { display: block; height: 0.7rem; border-radius: 999px; }
.nt-ia-skel-value { display: block; height: 1.6rem; width: 45%; border-radius: 0.4rem; margin: 0.35rem 0; }

.nt-ia-skel-bars {
    display: flex;
    align-items: flex-end;
    gap: 2px;
    height: 10rem;
    margin-top: 1rem;
}

.nt-ia-skel-bars > span { flex: 1; border-radius: 4px 4px 0 0; }

.nt-ia-loading {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.75rem 0 0;
    font-size: var(--nt-text-sm);
    color: var(--nt-text-muted);
}

.nt-ia-loading-dot {
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    background: var(--nt-brand);
    animation: nt-ia-breathe 1.2s ease-in-out infinite;
}

@keyframes nt-ia-breathe {
    0%, 100% { opacity: 0.35; transform: scale(0.85); }
    50% { opacity: 1; transform: scale(1.1); }
}

/* A post page shows its stored counts at once; the figures still on their way shimmer in place of
   "Not available" until they land. */
.nt-ia-body[aria-busy="true"] .nt-ips-stat.is-empty .nt-ips-stat-na {
    display: block;
    width: 4.5rem;
    height: 1.35rem;
    margin: 0.15rem 0;
    border-radius: 0.4rem;
    color: transparent;
    background: linear-gradient(90deg, var(--nt-surface-2) 25%, #e9eeec 50%, var(--nt-surface-2) 75%);
    background-size: 200% 100%;
    animation: nt-skel-shimmer 1.4s ease-in-out infinite;
}

.nt-ia-body.is-loaded { animation: nt-ia-fade-in 0.28s ease-out; }

@keyframes nt-ia-fade-in {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .nt-ia-loading-dot,
    .nt-ia-body[aria-busy="true"] .nt-ips-stat.is-empty .nt-ips-stat-na,
    .nt-ia-body.is-loaded { animation: none; }
}
