/* ==========================================================================
   NimbleSign design system.
   Source of truth: docs/design_guidelines.md (tokens, type, components) and
   the "NimbleSign Design.dc.html" reference (badges 3a-3aj).
   Layered over Bootstrap 5: tokens first, Bootstrap retunes, app shell,
   NimbleSign components, then the functional designer/ceremony rules.
   ========================================================================== */

/* ---------- tokens ---------- */

:root {
  /* brand seeds - the deployment/org theming override points. A branded
     deployment/org overrides only these (plus the -rgb triplets); every
     other brand color aliases or derives from them. Derived shades
     (hover/tint/on-color/rgb) are computed server-side by BrandTheme when
     a custom brand is set; the values here are the stock NimbleSign brand. */
  --ns-primary: #1687C7;
  --ns-primary-hover: #1271A7;
  --ns-primary-tint: #E8F3FA;
  --ns-on-primary: #FFFFFF;
  --ns-primary-rgb: 22, 135, 199;
  --ns-primary-hover-rgb: 18, 113, 167;
  --ns-nav: #0A4E77;
  --ns-nav-2: #0D679D;
  --ns-nav-rgb: 10, 78, 119;
  --ns-accent: #35C6E8;
  --ns-accent-rgb: 53, 198, 232;
  /* Text on an accent-filled surface. Matches BrandTheme.OnColor(--ns-accent) for the
     stock cyan; BrandTheme rewrites it per brand, because a dark accent needs white here
     and a light one needs ink. Never hardcode either at the call site. */
  --ns-on-accent: #0F1B2D;

  /* ---------- feature colours ----------
     One hue per feature area, so a reader learns "violet means Templates" and the site stops
     reading as one field of blue-grey. These name the identity hues below rather than restating
     their hexes — one definition per colour, so a hue cannot mean two slightly different things
     depending on which name you reached for. Every tint is derived by color-mix rather than
     pinned as a second value. Carried to the markup on a --ns-fc local variable (the --ns-rc pattern in
     foundations/tokens.md), so one rule serves every feature instead of one rule per colour.

     Chosen around what is already spoken for: green means done, amber means act, red means
     destructive, and navy is chrome — so a feature hue can never come from those. Envelopes keeps
     the primary blue it already has, In-person sits next to it because an in-person session *is* an
     envelope being signed, and Templates and Walk-In take the two hues furthest from both.

     They colour a band's well and hairline only. Card header washes still mean urgency and card
     icon wells are still keyed to content — a feature hue never overrides either, or "amber means
     act today" stops being true. See docs/design/patterns/dashboard-columns.md. */
  --ns-feat-envelopes: var(--ns-primary);
  --ns-feat-inperson: var(--ns-hue-indigo);
  --ns-feat-templates: var(--ns-hue-violet);
  --ns-feat-walkin: var(--ns-hue-teal);

  /* brand (legacy names — alias the seeds above; keep using these freely) */
  --ns-navy-900: var(--ns-nav);
  --ns-navy-800: var(--ns-nav-2);
  --ns-blue-600: var(--ns-primary);
  --ns-blue-hover: var(--ns-primary-hover);
  --ns-cyan: var(--ns-accent);
  --ns-blue-50: var(--ns-primary-tint);
  --ns-maple: #E8262D;
  --ns-maple-hover: #CD161C;

  /* status */
  --ns-success: #148F5B;
  --ns-success-50: #E7F5EE;
  --ns-success-text: #0C6E45;
  --ns-warning: #B7791F;
  --ns-warning-50: #FBF4E6;
  --ns-warning-tint: #FBEEE0;
  --ns-warning-text: #8A5A12;
  --ns-warning-rgb: 183, 121, 31;
  --ns-danger-50: #FBEAEA;
  --ns-danger-text: #A81B22;
  --ns-danger-border: #F1C7C4;

  /* Identity hues — for telling one *thing* apart from another, never for saying how it is doing.
     Deliberately outside the status range above: a plan or a category drawn in one of those would
     claim a state nobody meant. Each is dark enough to read as text on a tint mixed from itself,
     which is what the plan palette does with them. */
  --ns-hue-teal: #0F8B86;
  --ns-hue-indigo: #4C5FC4;
  --ns-hue-violet: #7A4CB8;
  --ns-hue-plum: #A13D71;

  /* ink + surfaces */
  --ns-ink: #0F1B2D;
  /* Ink as a triplet, for tints of it. Non-brandable on purpose: chrome describing the DEPLOYMENT
     is drawn from these rather than from --ns-primary/--ns-nav, which BrandTheme rewrites per org. */
  --ns-ink-rgb: 15, 27, 45;
  --ns-slate: #5A6B80;
  --ns-muted: #8494A6;
  --ns-text-700: #33465C;
  --ns-canvas: #F6F8FB;
  --ns-surface: #FFFFFF;
  /* An unselected tile in a group where one is selected. Barely off-white on purpose: it has to
     recede behind --ns-surface without reading as disabled. See .ns-stage. */
  --ns-surface-quiet: #FBFCFE;
  --ns-border: #E4EAF2;
  --ns-border-strong: #D6DEE9;
  --ns-divider: #F1F5F9;
  --ns-thead-bg: #F8FAFC;
  /* The ink a count uses when it is zero. Lighter than --ns-muted, because "nothing here" should
     be legible without competing with the counts that have something in them. */
  --ns-ink-zero: #C0CAD6;
  --ns-info-border: #B8DCEF;
  --ns-info-border-soft: #C9E7F6;

  /* card-header washes. A card header is always washed — the question is only which one, and
     the answer is a claim about urgency, not a styling choice. Navy is the everyday wash;
     amber is reserved for "a human has to act today" and the stark primary tint for "this one
     is addressed to you". Both exceptions retint with the brand, so a rebranded deployment
     keeps the same three-way distinction. See .ns-panel-head. */
  --ns-wash: rgba(var(--ns-nav-rgb), .045);
  --ns-wash-attention: rgba(var(--ns-warning-rgb), .07);

  /* type */
  --ns-font-ui: 'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --ns-font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Consolas, monospace;
  --ns-font-brand: 'Poppins', 'Plus Jakarta Sans', system-ui, sans-serif;

  /* radius */
  --ns-radius-control: 10px;
  --ns-radius-card: 12px;
  --ns-radius-panel: 16px;

  /* deep canvas behind PDF pages (designer/ceremony) */
  --ns-canvas-deep: #DCE3EC;

  /* elevation */
  --ns-shadow-card-sm: 0 1px 2px rgba(16, 40, 70, .04);
  --ns-shadow-card: 0 1px 2px rgba(16, 40, 70, .05), 0 18px 44px rgba(16, 40, 70, .13);
  --ns-shadow-raised: 0 8px 30px rgba(16, 40, 70, .08);
  --ns-shadow-modal: 0 24px 60px rgba(16, 40, 70, .14);
  --ns-shadow-dropdown: 0 12px 28px rgba(16, 40, 70, .13);
  --ns-shadow-btn-primary: 0 1px 2px rgba(var(--ns-primary-rgb), .4);
  --ns-focus-ring: 0 0 0 3px rgba(var(--ns-primary-rgb), .15);

  /* Bootstrap bridge */
  --bs-body-bg: var(--ns-canvas);
  --bs-body-color: var(--ns-ink);
  --bs-body-font-family: var(--ns-font-ui);
  --bs-secondary-color: var(--ns-muted);
  --bs-border-color: var(--ns-border);
  --bs-link-color: var(--ns-blue-600);
  --bs-link-hover-color: var(--ns-blue-hover);
  --bs-link-color-rgb: var(--ns-primary-rgb);
  --bs-link-hover-color-rgb: var(--ns-primary-hover-rgb);
  --bs-primary: var(--ns-primary);
  --bs-primary-rgb: var(--ns-primary-rgb);
  --bs-danger: #E8262D;
  --bs-danger-rgb: 232, 38, 45;
  --bs-success: #148F5B;
  --bs-success-rgb: 20, 143, 91;
  --bs-warning: #B7791F;
  --bs-warning-rgb: 183, 121, 31;
  --bs-info: var(--ns-accent);
  --bs-info-rgb: var(--ns-accent-rgb);
  --bs-secondary: #5A6B80;
  --bs-secondary-rgb: 90, 107, 128;
  --bs-dark: var(--ns-nav);
  --bs-dark-rgb: var(--ns-nav-rgb);
  --bs-light: #F6F8FB;
  --bs-light-rgb: 246, 248, 251;
  --bs-font-monospace: var(--ns-font-mono);
}

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

html {
  font-size: 14px;
  /* Reserve the scrollbar's gutter whether or not one is showing, so a page that grows past the
     viewport does not shove its whole layout ~15px left as the bar appears. That shift was most
     visible at the end of a navigation — everything settled, then jumped — and it also made
     centred content drift between a short page and a tall one. */
  scrollbar-gutter: stable;
}

body {
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection {
  background: #C9E7F6;
}

a {
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

h1, .h1 { font-size: 24px; font-weight: 800; letter-spacing: -.015em; }
h2, .h2 { font-size: 19px; font-weight: 700; letter-spacing: -.01em; }
h3, .h3 { font-size: 16px; font-weight: 700; }
h4, .h4 { font-size: 15px; font-weight: 700; }
h5, .h5 { font-size: 14px; font-weight: 700; }
h6, .h6 { font-size: 12px; font-weight: 700; }

p {
  text-wrap: pretty;
}

code, .ns-mono {
  font-family: var(--ns-font-mono);
}

@keyframes ns-rise {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* ---------- Bootstrap retune: buttons ---------- */

.btn {
  --bs-btn-border-radius: var(--ns-radius-control);
  --bs-btn-font-weight: 600;
  --bs-btn-font-size: 13.5px;
  --bs-btn-padding-y: 9px;
  --bs-btn-padding-x: 15px;
  transition: transform .16s ease, background-color .16s ease, border-color .16s ease, color .16s ease, box-shadow .16s ease;
}

.btn-sm, .btn-group-sm > .btn {
  --bs-btn-border-radius: 8px;
  --bs-btn-font-size: 12px;
  --bs-btn-padding-y: 6px;
  --bs-btn-padding-x: 11px;
}

.btn-lg {
  --bs-btn-border-radius: 12px;
  --bs-btn-font-size: 15px;
  --bs-btn-padding-y: 11px;
  --bs-btn-padding-x: 18px;
}

.btn-primary {
  --bs-btn-bg: var(--ns-blue-600);
  --bs-btn-border-color: var(--ns-blue-600);
  --bs-btn-color: var(--ns-on-primary);
  --bs-btn-hover-bg: var(--ns-blue-hover);
  --bs-btn-hover-border-color: var(--ns-blue-hover);
  --bs-btn-hover-color: var(--ns-on-primary);
  --bs-btn-active-bg: var(--ns-blue-hover);
  --bs-btn-active-border-color: var(--ns-blue-hover);
  --bs-btn-active-color: var(--ns-on-primary);
  --bs-btn-disabled-bg: color-mix(in srgb, var(--ns-primary) 42%, #fff);
  --bs-btn-disabled-border-color: color-mix(in srgb, var(--ns-primary) 42%, #fff);
  --bs-btn-disabled-color: var(--ns-on-primary);
  box-shadow: var(--ns-shadow-btn-primary);
}

/* Design "secondary": white surface, blue text, tinted-blue border. */
.btn-secondary, .btn-outline-primary {
  --bs-btn-bg: #fff;
  --bs-btn-color: var(--ns-blue-600);
  --bs-btn-border-color: var(--ns-info-border);
  --bs-btn-hover-bg: var(--ns-blue-50);
  --bs-btn-hover-color: var(--ns-blue-hover);
  --bs-btn-hover-border-color: var(--ns-info-border);
  --bs-btn-active-bg: var(--ns-blue-50);
  --bs-btn-active-color: var(--ns-blue-hover);
  --bs-btn-active-border-color: var(--ns-info-border);
  --bs-btn-disabled-color: var(--ns-muted);
  --bs-btn-disabled-bg: #fff;
  --bs-btn-disabled-border-color: var(--ns-border);
}

/* Design "ghost": transparent, slate text, hairline border. */
.btn-ghost, .btn-outline-secondary, .btn-light, .btn-outline-light, .btn-outline-dark {
  --bs-btn-bg: transparent;
  --bs-btn-color: var(--ns-text-700);
  --bs-btn-border-color: var(--ns-border);
  --bs-btn-hover-bg: #fff;
  --bs-btn-hover-color: var(--ns-text-700);
  --bs-btn-hover-border-color: var(--ns-info-border);
  --bs-btn-active-bg: var(--ns-blue-50);
  --bs-btn-active-color: var(--ns-blue-hover);
  --bs-btn-active-border-color: var(--ns-info-border);
  --bs-btn-disabled-color: var(--ns-muted);
  --bs-btn-disabled-bg: transparent;
  --bs-btn-disabled-border-color: var(--ns-divider);
}

.btn-danger {
  --bs-btn-bg: var(--ns-maple);
  --bs-btn-border-color: var(--ns-maple);
  --bs-btn-hover-bg: var(--ns-maple-hover);
  --bs-btn-hover-border-color: var(--ns-maple-hover);
  --bs-btn-active-bg: var(--ns-maple-hover);
  --bs-btn-active-border-color: var(--ns-maple-hover);
}

.btn-outline-danger {
  --bs-btn-bg: #fff;
  --bs-btn-color: var(--ns-maple-hover);
  --bs-btn-border-color: var(--ns-danger-border);
  --bs-btn-hover-bg: var(--ns-danger-50);
  --bs-btn-hover-color: var(--ns-maple-hover);
  --bs-btn-hover-border-color: var(--ns-danger-border);
  --bs-btn-active-bg: var(--ns-danger-50);
  --bs-btn-active-color: var(--ns-maple-hover);
  --bs-btn-active-border-color: var(--ns-danger-border);
}

.btn-success {
  --bs-btn-bg: var(--ns-success);
  --bs-btn-border-color: var(--ns-success);
  --bs-btn-hover-bg: #0F7A4D;
  --bs-btn-hover-border-color: #0F7A4D;
  --bs-btn-active-bg: #0F7A4D;
  --bs-btn-active-border-color: #0F7A4D;
}

.btn-outline-success {
  --bs-btn-color: var(--ns-success-text);
  --bs-btn-border-color: #C6E7D5;
  --bs-btn-hover-bg: var(--ns-success-50);
  --bs-btn-hover-color: var(--ns-success-text);
  --bs-btn-hover-border-color: #C6E7D5;
  --bs-btn-active-bg: var(--ns-success-50);
  --bs-btn-active-color: var(--ns-success-text);
  --bs-btn-active-border-color: #C6E7D5;
}

.btn-warning {
  --bs-btn-color: var(--ns-ink);
  --bs-btn-bg: var(--ns-warning);
  --bs-btn-border-color: var(--ns-warning);
  --bs-btn-hover-color: var(--ns-ink);
  --bs-btn-hover-bg: color-mix(in srgb, var(--ns-warning) 85%, #000);
  --bs-btn-hover-border-color: color-mix(in srgb, var(--ns-warning) 85%, #000);
  --bs-btn-active-color: var(--ns-ink);
  --bs-btn-active-bg: color-mix(in srgb, var(--ns-warning) 85%, #000);
  --bs-btn-active-border-color: color-mix(in srgb, var(--ns-warning) 85%, #000);
}

.btn-outline-warning {
  --bs-btn-bg: #fff;
  --bs-btn-color: var(--ns-warning-text);
  --bs-btn-border-color: #EFDCB4;
  --bs-btn-hover-bg: var(--ns-warning-50);
  --bs-btn-hover-color: var(--ns-warning-text);
  --bs-btn-hover-border-color: #EFDCB4;
  --bs-btn-active-bg: var(--ns-warning-50);
  --bs-btn-active-color: var(--ns-warning-text);
  --bs-btn-active-border-color: #EFDCB4;
}

.btn:where(.btn-primary, .btn-secondary, .btn-danger, .btn-success, .btn-warning, .btn-ghost,
           .btn-outline-primary, .btn-outline-secondary, .btn-outline-danger,
           .btn-outline-success, .btn-outline-warning, .btn-light):not(:disabled):hover {
  transform: translateY(-1px);
}

.btn:focus-visible {
  box-shadow: var(--ns-focus-ring);
}

/* ---------- Bootstrap retune: forms ---------- */

.form-control, .form-select {
  /* Bootstrap paints these from --bs-body-bg, which is the canvas - see the note on .card. An
     editable field has to be lighter than the page, not the same colour as it, or it reads as
     inert. It is also what makes .ns-set-ro's deliberate --ns-thead-bg tint legible as
     "you cannot change this". :disabled keeps Bootstrap's own grey, which is the point. */
  background-color: var(--ns-surface);
  border-color: var(--ns-border-strong);
  border-radius: var(--ns-radius-control);
  font-size: 13.5px;
  padding: 9px 12px;
  color: var(--ns-ink);
}

.form-control::placeholder {
  color: var(--ns-muted);
}

.form-control:focus, .form-select:focus {
  border-color: var(--ns-blue-600);
  box-shadow: var(--ns-focus-ring);
}

.form-control-sm, .form-select-sm {
  border-radius: 8px;
  font-size: 12.5px;
  padding: 6px 10px;
}

/* Selects render a chevron pinned to the right edge. The shared padding above
   doesn't reserve room for it, so a long selected value would slide under the
   arrow. Reserve right-side clearance for the chevron AND truncate the selected
   label with an ellipsis, so it can never collide with the icon - applies to
   every select on the site, not just the designer. (overflow: hidden only clips
   the closed control; the native option list is unaffected.) */
.form-select,
.form-select-sm {
  padding-right: 2rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.form-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--ns-text-700);
  margin-bottom: 5px;
}

.form-text {
  font-size: 12px;
  color: var(--ns-muted);
}

.form-check-input {
  border-color: var(--ns-border-strong);
}

.form-check-input:checked {
  background-color: var(--ns-blue-600);
  border-color: var(--ns-blue-600);
}

.form-check-input:focus {
  border-color: var(--ns-blue-600);
  box-shadow: var(--ns-focus-ring);
}

.input-group-text {
  border-color: var(--ns-border-strong);
  border-radius: var(--ns-radius-control);
  background: var(--ns-thead-bg);
  color: var(--ns-slate);
  font-size: 13px;
}

/* ---------- Bootstrap retune: cards, tables, lists ---------- */

.card {
  /* Bootstrap defaults --bs-card-bg to --bs-body-bg, which we point at the canvas - so without
     this every .card in the app was canvas grey on a canvas-grey page, shape defined only by its
     1px border. A card is a raised surface; white is the whole point of using one. .ns-card, the
     hand-rolled equivalent, always set --ns-surface explicitly, which is why the rebuilt settings
     pages looked right and the untouched ones did not. */
  --bs-card-bg: var(--ns-surface);
  --bs-card-border-color: var(--ns-border);
  --bs-card-border-radius: var(--ns-radius-card);
  --bs-card-inner-border-radius: calc(var(--ns-radius-card) - 1px);
  --bs-card-cap-bg: transparent;
  box-shadow: var(--ns-shadow-card-sm);
}

.card-header {
  border-bottom-color: var(--ns-divider);
  font-weight: 700;
}

.card-footer {
  border-top-color: var(--ns-divider);
  background: var(--ns-thead-bg);
}

.table {
  --bs-table-color: var(--ns-ink);
  /* Same trap as --bs-card-bg: Bootstrap defaults --bs-table-bg to --bs-body-bg, which we bridge
     to the canvas, so every cell painted itself canvas grey - including tables sitting inside a
     white card, which is what made them read as grey panels. Transparent rather than --ns-surface
     on purpose: a table has no surface of its own, so it should show whatever it is placed on.
     Striping and --bs-table-hover-bg layer over this and are unaffected; thead keeps its own
     --ns-thead-bg tint below. */
  --bs-table-bg: transparent;
  --bs-table-border-color: var(--ns-divider);
  --bs-table-hover-bg: var(--ns-thead-bg);
  font-size: 13px;
  margin-bottom: 0;
}

.table thead th {
  background: var(--ns-thead-bg);
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ns-muted);
  border-bottom: 1px solid var(--ns-border);
  padding: 9px 14px;
  white-space: nowrap;
}

.table td {
  padding: 11px 14px;
  vertical-align: middle;
}

.list-group {
  /* See .card - Bootstrap defaults this to --bs-body-bg too. */
  --bs-list-group-bg: var(--ns-surface);
  --bs-list-group-border-color: var(--ns-divider);
  --bs-list-group-border-radius: var(--ns-radius-control);
  font-size: 13px;
}

.list-group-item-action:hover {
  background: var(--ns-thead-bg);
}

/* ---------- Bootstrap retune: alerts, badges, modals, dropdowns ---------- */

.alert {
  border-radius: var(--ns-radius-card);
  font-size: 13px;
  font-weight: 600;
  padding: 10px 14px;
}

.alert-success {
  --bs-alert-bg: var(--ns-success-50);
  --bs-alert-border-color: #C6E7D5;
  --bs-alert-color: var(--ns-success-text);
}

.alert-danger {
  --bs-alert-bg: var(--ns-danger-50);
  --bs-alert-border-color: var(--ns-danger-border);
  --bs-alert-color: var(--ns-danger-text);
}

.alert-warning {
  --bs-alert-bg: var(--ns-warning-50);
  --bs-alert-border-color: #EFDCB4;
  --bs-alert-color: var(--ns-warning-text);
}

.alert-info {
  --bs-alert-bg: var(--ns-blue-50);
  --bs-alert-border-color: var(--ns-info-border-soft);
  --bs-alert-color: var(--ns-blue-hover);
}

.badge {
  font-weight: 600;
  border-radius: 999px;
}

.modal-content {
  /* A floating panel over a dimmed page - canvas grey made it read as part of the page. */
  --bs-modal-bg: var(--ns-surface);
  border: none;
  border-radius: var(--ns-radius-panel);
  box-shadow: var(--ns-shadow-modal);
}

.modal-header {
  border-bottom: 1px solid var(--ns-divider);
  padding: 16px 20px;
}

.modal-title {
  font-size: 15px;
  font-weight: 700;
}

.modal-body {
  padding: 18px 20px;
}

.modal-footer {
  border-top: 1px solid var(--ns-divider);
  padding: 12px 20px;
}

.dropdown-menu {
  /* Same as .modal-content: a floating surface, so it must be lighter than what it floats over.
     This is the avatar menu and the org switcher, among others. */
  --bs-dropdown-bg: var(--ns-surface);
  /* Bootstrap's default (1000) loses to the app's own fixed chrome — .ns-rail/.ns-savebar/.ns-docnav
     at 1030-1035, .ns-dz-fieldlist/.ns-dz-backdrop at 1045-1046, .ns-dz-status at 1052 — so a select
     or menu opened near any of them rendered underneath it. 1053 clears all of them. */
  --bs-dropdown-zindex: 1053;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
  box-shadow: var(--ns-shadow-dropdown);
  padding: 6px;
  font-size: 13px;
}

.dropdown-item {
  border-radius: 8px;
  padding: 8px 10px;
  font-weight: 500;
  color: var(--ns-text-700);
}

.dropdown-item:hover, .dropdown-item:focus {
  background: var(--ns-divider);
  color: var(--ns-ink);
}

.dropdown-item.active, .dropdown-item:active {
  background: var(--ns-blue-50);
  color: var(--ns-blue-hover);
}

.dropdown-header {
  padding: 8px 10px 6px;
}

.dropdown-divider {
  border-color: var(--ns-divider);
  margin: 5px 0;
}

.nav-tabs {
  /* The active tab is the front edge of the panel below it, so it takes the panel's surface. */
  --bs-nav-tabs-link-active-bg: var(--ns-surface);
  --bs-nav-tabs-border-color: var(--ns-border);
  --bs-nav-tabs-link-active-color: var(--ns-blue-600);
  --bs-nav-link-color: var(--ns-slate);
  --bs-nav-link-hover-color: var(--ns-blue-hover);
  font-size: 13px;
  font-weight: 600;
}

.pagination {
  --bs-pagination-color: var(--ns-text-700);
  --bs-pagination-border-color: var(--ns-border);
  --bs-pagination-hover-bg: var(--ns-divider);
  --bs-pagination-hover-color: var(--ns-ink);
  --bs-pagination-active-bg: var(--ns-blue-600);
  --bs-pagination-active-border-color: var(--ns-blue-600);
  --bs-pagination-focus-box-shadow: var(--ns-focus-ring);
  font-size: 13px;
}

/* ---------- app shell: navy rail + top bar + canvas ---------- */

.ns-shell {
  display: flex;
  min-height: 100vh;
  /* Reserve the collapsed rail's gutter. The rail itself is fixed, so when it
     expands on hover it floats over the content instead of pushing it. */
  padding-left: 56px;
}

/* The rail keeps a single, static internal layout at both widths - icon boxes on
   the left, labels beside them - and only its own width animates. Nothing inside
   reflows per frame (no align/justify flips, no width/padding tweens), so the
   expand stays smooth; labels just fade in and get un-clipped by overflow. */
.ns-rail {
  position: fixed;
  top: 0;
  left: 0;
  height: 100vh;
  width: 56px;
  background: var(--ns-navy-900);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  padding: 12px 10px;
  z-index: 1035;
  overflow: hidden;
  transition: width .22s cubic-bezier(.4, 0, .2, 1), box-shadow .22s ease;
}

/* Expand on hover (pointer) or focus-within (keyboard tab-through) — but only where a real
   hover exists. On touch, a tap on a link used to fire the hover/focus expansion in the same
   instant as the navigation: the flyout lurched open for a few frames and the page left. Touch
   devices open the rail deliberately instead — a tap on its empty background, or the phone
   hamburger — via .ns-nav-open (admin-ui.js). */
@media (hover: hover) {
  .ns-rail:hover,
  .ns-rail:focus-within {
    width: 216px;
    box-shadow: 0 14px 44px rgba(9, 20, 40, .38);
  }
}

body.ns-nav-open .ns-rail {
  width: 216px;
  box-shadow: 0 14px 44px rgba(9, 20, 40, .38);
}

/* One state change dims the page and opens the rail together. Touch-opened only — the scrim
   never appears for the desktop hover flyout. Under the rail, above everything else. */
.ns-nav-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1034;
  background: rgba(7, 25, 40, .4);
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s ease;
}

body.ns-nav-open .ns-nav-backdrop {
  opacity: 1;
  pointer-events: auto;
}

.ns-rail-logo {
  display: flex;
  align-items: center;
  height: 32px;
  padding-left: 8px;
  margin-bottom: 6px;
  line-height: 0;
}

.ns-rail-logo img {
  height: 24px;
  width: auto;
}

.ns-rail-link {
  height: 36px;
  border-radius: 9px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 9px;
  color: #9DC7E0;
  white-space: nowrap;
  overflow: hidden;
  transition: background-color .16s ease, color .16s ease;
}

.ns-rail-link:hover {
  background: rgba(255, 255, 255, .08);
  color: #fff;
  text-decoration: none;
}

.ns-rail-link.active {
  background: var(--ns-blue-600);
  color: #fff;
}

.ns-rail-link svg {
  flex: none;
}

/* The help-desk trigger opens a dialog rather than going anywhere, so it is a <button> that
   borrows .ns-rail-link wholesale and only resets what a button drags in. Deliberately no
   .active state: it is not a destination the rail can be "on". .ns-rail-link:hover outranks
   the transparent background here (class + pseudo-class beats class), so hover still lands. */
.ns-rail-help {
  width: 100%;
  padding: 0 9px;
  background: none;
  border: 0;
  font: inherit;
  text-align: left;
  flex: none;
}

.ns-rail-help:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

/* nav-item label — always laid out (fixed width, clipped by the rail's overflow
   while collapsed); fades in as the rail opens. Opacity is compositor-cheap. */
.ns-rail-label {
  display: flex;
  align-items: center;
  opacity: 0;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 600;
  transition: opacity .16s ease;
}

@media (hover: hover) {
  .ns-rail:hover .ns-rail-label,
  .ns-rail:focus-within .ns-rail-label {
    opacity: 1;
  }
}

body.ns-nav-open .ns-rail .ns-rail-label {
  opacity: 1;
}

/* Phone: the 56px gutter is real estate the content needs, so the rail leaves the flow
   entirely and returns as an overlay drawer from the top bar's menu button. Same element,
   same contents — only its geometry changes (panels doc: build one and move it). */
@media (max-width: 575.98px) {
  .ns-shell {
    padding-left: 0;
  }

  .ns-rail {
    width: 216px;
    transform: translateX(-100%);
    transition: transform .22s cubic-bezier(.4, 0, .2, 1), box-shadow .22s ease;
  }

  body.ns-nav-open .ns-rail {
    transform: translateX(0);
  }
}

/* The top bar's menu button — phone only (d-sm-none in markup), so it is born at the
   touch floor rather than growing to it. */
.ns-nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-left: -10px; /* hugs the bar's edge so the title still starts on the grid line */
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--ns-slate);
  flex: none;
}

.ns-nav-toggle:hover {
  background: var(--ns-divider);
  color: var(--ns-ink);
}

.ns-nav-toggle:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

.ns-rail-spacer {
  margin-top: auto;
}

.ns-rail-user {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-left: 3px;
}

.ns-rail-user .ns-rail-avatar {
  flex: none;
}

.ns-rail-user-meta {
  flex-direction: column;
  align-items: stretch;
  line-height: 1.25;
  min-width: 0;
}

.ns-rail-user-name {
  color: #fff;
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-rail-user-org {
  color: #9DC7E0;
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--ns-canvas);
}

.ns-topbar {
  position: sticky;
  top: 0;
  z-index: 1030;
  min-height: 52px;
  background: #fff;
  border-bottom: 1px solid #E9EEF4;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 20px;
}

.ns-topbar-title {
  font-size: 15px;
  font-weight: 800;
  letter-spacing: -.01em;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 8px;
  /* Lets the trail's leaf ellipsize instead of shoving the right-hand controls off. */
  min-width: 0;
  overflow: hidden;
}

.ns-topbar-right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}

.ns-org-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 0;
  border-radius: 8px;
  background: var(--ns-divider);
  color: var(--ns-text-700);
  font-size: 12px;
  font-weight: 600;
  max-width: 220px;
  transition: background-color .16s ease;
}

.ns-org-chip .ns-org-chip-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

button.ns-org-chip:hover {
  background: #E9EEF4;
}

/* acting in an org other than the account's default */
.ns-org-chip.ns-org-chip-away {
  background: var(--ns-primary-tint);
  color: var(--ns-primary-hover);
}

.ns-org-chip-away-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ns-accent);
  flex: none;
}

/* sized, radius'd wrappers around _OrgIcon's bare <img> - the topbar chip and an overlay row
   need different tile sizes, so the sizing lives on the wrapper, not the shared partial. */
.ns-org-tile-chip,
.ns-org-tile-row {
  flex: none;
  overflow: hidden;
  display: inline-flex;
  background: var(--ns-divider);
}

.ns-org-tile-chip {
  width: 18px;
  height: 18px;
  border-radius: 4px;
}

.ns-org-tile-row {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: #fff;
}

.ns-org-icon-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ---------- organization switcher overlay (command palette) ---------- */

.ns-orgpicker-backdrop {
  position: fixed;
  inset: 0;
  z-index: 2100;
  background: rgba(10, 30, 55, .42);
  display: flex;
  justify-content: center;
  padding-top: 64px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .16s ease;
}

.ns-orgpicker-backdrop.show {
  opacity: 1;
  pointer-events: auto;
}

.ns-orgpicker-panel {
  width: 560px;
  max-width: calc(100vw - 32px);
  height: fit-content;
  max-height: calc(100vh - 96px);
  background: #fff;
  border-radius: var(--ns-radius-panel);
  box-shadow: var(--ns-shadow-modal);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transform: scale(.98) translateY(-6px);
  transition: transform .16s cubic-bezier(.2, .7, .2, 1);
}

.ns-orgpicker-backdrop.show .ns-orgpicker-panel {
  transform: none;
}

.ns-orgpicker-search {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--ns-divider);
  flex: none;
}

.ns-orgpicker-search-icon {
  color: var(--ns-muted);
  flex: none;
}

.ns-orgpicker-input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: none;
  background: transparent;
  font-size: 15px;
  font-weight: 500;
  color: var(--ns-ink);
  font-family: var(--ns-font-ui);
}

.ns-orgpicker-input::placeholder {
  color: var(--ns-muted);
  font-weight: 500;
}

.ns-orgpicker-kbd {
  font-family: var(--ns-font-mono);
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ns-slate);
  background: var(--ns-divider);
  border: 1px solid var(--ns-border);
  border-radius: 6px;
  padding: 3px 7px;
  flex: none;
  white-space: nowrap;
}

.ns-orgpicker-error {
  padding: 9px 16px;
  background: var(--ns-danger-50);
  color: var(--ns-danger-text);
  font-size: 12.5px;
  font-weight: 600;
  border-bottom: 1px solid var(--ns-danger-border);
  flex: none;
}

.ns-orgpicker-list {
  padding: 6px;
  max-height: 500px;
  overflow-y: auto;
  flex: 1 1 auto;
}

.ns-orgpicker-group-heading {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ns-muted);
  padding: 8px 10px 4px;
}

.ns-orgpicker-group-heading ~ .ns-orgpicker-group-heading {
  padding-top: 10px;
}

.ns-orgpicker-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 9px 10px;
  border-radius: 10px;
  cursor: pointer;
  position: relative;
}

.ns-orgpicker-row:hover,
.ns-orgpicker-row.is-highlighted {
  background: var(--ns-divider);
}

.ns-orgpicker-row.is-current {
  background: var(--ns-primary-tint);
  box-shadow: inset 0 0 0 1.5px var(--ns-primary);
}

.ns-orgpicker-row.is-highlighted:not(.is-current) {
  box-shadow: inset 0 0 0 1.5px var(--ns-info-border);
}

.ns-orgpicker-row.is-current .ns-org-tile-row {
  box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--ns-primary);
}

.ns-orgpicker-row.is-pending {
  cursor: default;
  pointer-events: none;
}

.ns-orgpicker-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.ns-orgpicker-row-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ns-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-orgpicker-row-meta {
  font-size: 11px;
  font-weight: 500;
  color: var(--ns-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-orgpicker-star-default {
  color: var(--ns-warning);
  flex: none;
  line-height: 0;
}

.ns-orgpicker-star-set {
  border: 0;
  background: none;
  color: #C7D0DC;
  padding: 4px;
  border-radius: 6px;
  line-height: 0;
  cursor: pointer;
  flex: none;
}

.ns-orgpicker-star-set:hover {
  color: var(--ns-warning);
  background: var(--ns-warning-tint);
}

.ns-orgpicker-current-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px 3px 7px;
  border-radius: 999px;
  background: var(--ns-primary);
  color: #fff;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .04em;
  flex: none;
}

.ns-orgpicker-row-spinner {
  display: none;
  width: 14px;
  height: 14px;
  flex: none;
  border-radius: 50%;
  border: 2px solid var(--ns-border);
  border-top-color: var(--ns-primary);
  animation: ns-orgpicker-spin .6s linear infinite;
}

.ns-orgpicker-row.is-pending .ns-orgpicker-row-spinner {
  display: inline-block;
}

.ns-orgpicker-row.is-pending .ns-orgpicker-star-default,
.ns-orgpicker-row.is-pending .ns-orgpicker-star-set,
.ns-orgpicker-row.is-pending .ns-orgpicker-current-pill {
  display: none;
}

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

.ns-orgpicker-empty {
  padding: 40px 20px;
  text-align: center;
  font-size: 13px;
  color: var(--ns-muted);
}

.ns-orgpicker-footer {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 9px 14px;
  background: var(--ns-thead-bg);
  border-top: 1px solid var(--ns-divider);
  flex: none;
}

.ns-orgpicker-hint {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--ns-slate);
  font-weight: 600;
}

.ns-orgpicker-footer .ns-orgpicker-kbd {
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: 5px;
  padding: 1px 5px;
}

@media (prefers-reduced-motion: reduce) {
  .ns-orgpicker-panel,
  .ns-orgpicker-backdrop {
    transition: none;
  }
}

.ns-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--ns-blue-600);
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.ns-avatar-lg {
  width: 40px;
  height: 40px;
  font-size: 13px;
}

.ns-avatar-btn {
  border: 0;
  background: none;
  padding: 0;
  line-height: 0;
  border-radius: 50%;
}

.ns-avatar-btn:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

/* topbar notifications bell (see _NotificationsBell) */

.ns-notif-btn {
  position: relative;
  width: 32px;
  height: 32px;
  border: 0;
  background: none;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ns-slate);
}

.ns-notif-btn:hover {
  background: var(--ns-divider);
  color: var(--ns-text-700);
}

.ns-notif-btn:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

/* unread count - the one red (maple) badge in the chrome, per the danger/brand-only rule */
.ns-notif-badge {
  position: absolute;
  top: -1px;
  right: -3px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  border: 2px solid var(--ns-surface);
  background: var(--ns-maple);
  color: #fff;
  font-size: 9.5px;
  font-weight: 700;
  line-height: 12px;
  text-align: center;
}

.ns-notif-menu {
  width: 344px;
  /* Never wider than the screen it opens on — at full width on a phone it used to reach
     under the nav rail's higher stacking layer and read as clipped. */
  max-width: calc(100vw - 24px);
  padding: 0;
  overflow: hidden;
}

.ns-notif-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  border-bottom: 1px solid var(--ns-divider);
  font-size: 13px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-notif-clear {
  border: 0;
  background: none;
  padding: 2px 6px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ns-blue-600);
}

.ns-notif-clear:hover {
  background: var(--ns-blue-50);
  color: var(--ns-blue-hover);
}

.ns-notif-list {
  max-height: 384px;
  overflow-y: auto;
}

.ns-notif-row.unread {
  /* in-hue whisper of --ns-blue-50 - the full token reads as "selected" at row size */
  background: oklch(from var(--ns-blue-50) l c h / .45);
}

.ns-notif-msg {
  font-size: 12.5px;
  color: var(--ns-ink);
  line-height: 1.45;
}

.ns-notif-row.unread .ns-notif-msg {
  font-weight: 600;
}

.ns-notif-time {
  font-size: 11px;
  color: var(--ns-muted);
}

.ns-notif-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ns-blue-600);
  flex: none;
}

.ns-notif-empty {
  padding: 30px 16px;
  text-align: center;
}

.ns-notif-empty-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--ns-ink);
  margin-bottom: 3px;
}

.ns-notif-foot {
  border-top: 1px solid var(--ns-divider);
  text-align: center;
}

.ns-notif-foot a {
  display: block;
  padding: 9px 14px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ns-blue-600);
  text-decoration: none;
}

.ns-notif-foot a:hover {
  background: var(--ns-divider);
  color: var(--ns-blue-hover);
}

.ns-rail-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--ns-blue-600);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.ns-content {
  flex: 1;
  padding: 20px 24px 44px;
  animation: ns-rise .18s ease both;
}

/* A page carrying a save bar fades in instead of rising.

   ns-rise animates a transform, and a transformed element becomes the containing block for every
   position: fixed descendant — so the save bar's `bottom: 20px` stops meaning 20px above the window
   and starts meaning 20px above the bottom of the *page*. On a long console page that puts it over
   a thousand pixels below the fold: the bar reports itself as shown, the leave-guard arms, and
   nothing is on screen to save with.

   It only looks fine when the animation happens to have finished, because ns-rise ends at
   `transform: none` — which makes this a bug that hides whenever frames are being painted and
   appears whenever they are not (a background tab, a paused compositor, a re-triggered enter).
   Depending on an animation having completed for a fixed element to be positioned correctly is the
   fragility being removed here; only the transform is the problem, so the page still animates. */
.ns-content:has([data-savebar]) {
  animation-name: ns-fade;
}

/* The same trap as the save bar above, one step worse. Below the 1024px breakpoint a list
   table's drawers become fixed bottom sheets over the page (see the responsive rule under
   "--- popovers ---"), and this animation breaks them twice over:

   - `ns-rise` animates a transform, so .ns-content becomes their containing block and `bottom: 0`
     means the bottom of the *page* rather than the window — the sheet lands below the fold.
   - Swapping to `ns-fade` fixes that, but an element with an opacity animation still in effect is
     a **stacking context**, which traps the sheet and its backdrop inside .ns-content's paint
     layer. Their z-index then only sorts them against each other, and the whole subtree paints
     under the nav rail — no value is high enough to escape, because they are not being compared
     against it at all.

   So on a page with a list table this drops the entrance animation outright, and only at the width
   where the drawers are sheets: above it they are dropdowns positioned against their own trigger
   (.ns-dt-pop-wrap), which does not care what .ns-content is doing, and the animation stays. */
@media (max-width: 1024px) {
  .ns-content:has([data-dt-pop]) {
    animation: none;
  }
}

/* bare chrome (login, signer ceremony, denied/error) */

.ns-bare-main {
  min-height: 100vh;
  padding: 40px 16px;
  animation: ns-rise .18s ease both;
}

.ns-bare-topright {
  position: fixed;
  top: 14px;
  right: 16px;
  z-index: 100;
}

/* ---- legal documents (/Terms, /Privacy) ----
   A reading surface, not an app surface: one measure-constrained column of prose on the bare shell.
   Shared by both documents so an amendment to one cannot drift the other's typography, and so the
   pair reads as one set of terms rather than two pages that happen to be nearby. */

.ns-legal {
  /* ~68 characters at this size — past about 75 the eye loses the line on return. */
  max-width: 46rem;
  margin: 0 auto;
  color: var(--ns-ink);
}

.ns-legal-mark {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 26px;
  font-size: 13px;
  font-weight: 700;
  color: var(--ns-slate);
}

.ns-legal-mark img {
  width: 22px;
  height: 22px;
}

.ns-legal h1 {
  margin: 0 0 4px;
  font-size: 27px;
  font-weight: 800;
  letter-spacing: -.02em;
  text-wrap: balance;
}

/* The service and legal entity the document belongs to — load-bearing on a white-label
   deployment, where these pages are Nimble's own and must not read as the operator's. */
.ns-legal-sub {
  margin: 0 0 22px;
  font-size: 13px;
  color: var(--ns-muted);
}

.ns-legal h2 {
  margin: 30px 0 10px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -.01em;
  /* scroll-margin so a #anchor link from elsewhere does not park the heading under the
     fixed language switcher. */
  scroll-margin-top: 60px;
}

.ns-legal p {
  margin: 0 0 12px;
  font-size: 13.5px;
  line-height: 1.62;
  text-wrap: pretty;
}

/* The clause number hangs in the margin, so the numbering is scannable and the prose still
   starts on one left edge. Collapses to inline on narrow screens, where there is no margin
   to hang into. */
.ns-legal-clause {
  padding-left: 2.6rem;
  text-indent: -2.6rem;
}

.ns-legal-clause > b {
  display: inline-block;
  min-width: 2.6rem;
  font-weight: 700;
  text-indent: 0;
}

.ns-legal-foot {
  margin-top: 32px;
  padding-top: 16px;
  border-top: 1px solid var(--ns-divider);
  font-size: 12.5px;
  color: var(--ns-muted);
}

.ns-legal-foot p {
  font-size: 12.5px;
  margin-bottom: 6px;
}

/* The effective date is the version, in the reader's terms — it is what an acceptance record
   points back at, so it gets weight rather than being buried in the contact line. */
.ns-legal-effective {
  font-weight: 700;
  color: var(--ns-slate);
}

@media (max-width: 575.98px) {
  .ns-legal-clause {
    padding-left: 0;
    text-indent: 0;
  }

  .ns-legal-clause > b {
    min-width: 0;
    margin-right: .4rem;
  }
}

/* Somebody keeping a copy of what they agreed to should get the document, not the chrome. */
@media print {
  .ns-bare-topright,
  .ns-legal-mark {
    display: none;
  }

  .ns-legal {
    max-width: none;
  }
}

/* ---- setup instructions inside a settings card ----
   For the rare row an operator cannot fill in without going somewhere else first and bringing a
   value back. Deliberately plain: it is prose to read once, not a control, so it takes the canvas
   tint rather than the surface and never looks like something to interact with. */

.ns-set-help {
  margin: 4px 16px 16px;
  padding: 12px 14px;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-control);
  background: var(--ns-thead-bg);
}

.ns-set-help-title {
  font-size: 12px;
  font-weight: 700;
  color: var(--ns-text-700);
  margin-bottom: 6px;
}

.ns-set-help-steps {
  margin: 0;
  padding-left: 18px;
  font-size: 12px;
  line-height: 1.55;
  color: var(--ns-slate);
}

.ns-set-help-steps > li + li {
  margin-top: 8px;
}

/* The webhook address: selectable, wrapping anywhere, and monospaced so a hostname with a hyphen
   in it can be read back character by character. */
.ns-set-help-url {
  display: block;
  margin-top: 5px;
  padding: 6px 9px;
  border: 1px solid var(--ns-border-strong);
  border-radius: 7px;
  background: var(--ns-surface);
  font-family: var(--ns-font-mono);
  font-size: 11.5px;
  color: var(--ns-ink);
  overflow-wrap: anywhere;
  user-select: all;
}

.ns-set-help-note {
  margin: 9px 0 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ns-muted);
}

/* centered single-card surface (login, verify, done.) */
.ns-auth {
  min-height: calc(100vh - 80px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.ns-auth-card {
  width: 100%;
  max-width: 340px;
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-panel);
  padding: 26px;
  box-shadow: var(--ns-shadow-raised);
}

/* split sign-in (login): gradient brand panel beside a white form panel.
   Full-bleed - the page owns the whole viewport. Collapses to the form panel
   alone on narrow screens (brand + trust move inline into the form). */
/* Full-bleed immersive: the page owns the whole viewport as a brand-gradient
   hero - deployment logo + name top-left, pitch + floating form card centred,
   product mark / trust line / language toggle in the footer. Every color is
   brand-driven (--ns-nav / --ns-primary / --ns-accent), so login retints with
   the global brand. Stacks to a single centred column on narrow screens. */
.ns-signin {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  color: #fff;
  background: linear-gradient(120deg,
      color-mix(in srgb, var(--ns-nav) 76%, #000) 0%,
      var(--ns-nav) 32%,
      var(--ns-primary) 100%);
}

/* left scrim so the hero text stays legible over the gradient */
.ns-signin::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(90deg, rgba(4, 16, 12, .55) 0%, rgba(4, 16, 12, .18) 52%, rgba(4, 16, 12, 0) 100%);
}

/* soft accent orbs bleeding off the corners */
.ns-signin-orb {
  position: absolute;
  z-index: 0;
  border-radius: 50%;
  pointer-events: none;
}

.ns-signin-orb-1 {
  top: -120px;
  right: -60px;
  width: 460px;
  height: 460px;
  background: rgba(var(--ns-accent-rgb),.22);
  animation: ns-signin-orb-a 1.1s cubic-bezier(.33, 0, .2, 1) both,
             ns-signin-drift-a 11s ease-in-out 1.1s infinite;
}

.ns-signin-orb-2 {
  bottom: -140px;
  left: -40px;
  width: 360px;
  height: 360px;
  background: rgba(var(--ns-accent-rgb),.12);
  animation: ns-signin-orb-b 1.2s cubic-bezier(.33, 0, .2, 1) both,
             ns-signin-drift-b 13s ease-in-out 1.2s infinite;
}

/* orbs sweep in from off-canvas then settle into a slow idle drift; the mark,
   pitch, and card rise in staggered (shared ns-rise). All neutralized by the
   global prefers-reduced-motion rule near the top of this file. */
@keyframes ns-signin-orb-a {
  from { opacity: 0; transform: translate(-160px, 120px) scale(.92); }
  to { opacity: 1; transform: translate(0, 0) scale(1); }
}

@keyframes ns-signin-orb-b {
  from { opacity: 0; transform: translate(160px, -130px) scale(.92); }
  to { opacity: 1; transform: translate(0, 0) scale(1); }
}

@keyframes ns-signin-drift-a {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(26px, 34px) scale(1.1); }
}

@keyframes ns-signin-drift-b {
  0%, 100% { transform: translate(0, 0) scale(1); }
  50% { transform: translate(-22px, -28px) scale(1.12); }
}

/* deployment brand lockup - logo chip + site name, top-left, both from config */
.ns-signin-mark {
  position: absolute;
  top: 28px;
  left: 34px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 14px;
  animation: ns-rise .7s cubic-bezier(.34, 1.4, .5, 1) both;
}

.ns-signin-mark-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 46px;
  padding: 6px 10px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 6px 18px rgba(4, 18, 14, .3);
}

.ns-signin-mark-logo img {
  height: 100%;
  width: auto;
  max-width: 160px;
  display: block;
}

.ns-signin-mark-name {
  font-family: var(--ns-font-brand);
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -.01em;
  text-transform: lowercase;
  color: #fff;
}

.ns-signin-mark-name span {
  color: var(--ns-accent);
}

/* centred content row: pitch + floating form card */
.ns-signin-body {
  position: relative;
  z-index: 2;
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 60px;
  padding: 104px clamp(20px, 5vw, 64px) 92px;
}

.ns-signin-pitch {
  flex: 1 1 380px;
  min-width: 0;
  max-width: 500px;
  animation: ns-rise .7s cubic-bezier(.34, 1.4, .5, 1) .12s both;
}

.ns-signin-headline {
  margin: 0;
  font-size: 46px;
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -.025em;
  text-wrap: balance;
}

.ns-signin-features {
  list-style: none;
  margin: 30px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.ns-signin-features li {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 15px;
  color: rgba(255, 255, 255, .82);
}

.ns-signin-features li > svg {
  flex: none;
  color: var(--ns-accent);
}

.ns-signin-card {
  flex: none;
  width: 410px;
  max-width: 100%;
  background: #fff;
  color: var(--ns-ink);
  border-radius: 16px;
  box-shadow: 0 24px 60px rgba(4, 18, 14, .42);
  padding: 36px 36px 32px;
  animation: ns-rise .7s cubic-bezier(.34, 1.4, .5, 1) .18s both;
}

.ns-signin-title {
  margin: 0 0 6px;
  font-size: 24px;
  font-weight: 800;
  letter-spacing: -.01em;
  /* Balanced like the headline beside it: the French titles wrap at this card's width, and an
     unbalanced wrap leaves one word alone on the second line ("…reçu votre / paiement"). */
  text-wrap: balance;
}

.ns-signin-sub {
  margin: 0 0 24px;
  font-size: 14px;
  color: var(--ns-muted);
}

/* The sign-up return page's two waiting states ("finishing", then "taking longer than it should"),
   stacked in one grid cell so the card holds the taller one's height and never jumps when the copy
   changes. The swap is an opacity crossfade: the outgoing state leaves while the incoming arrives. */
.ns-signin-states {
  display: grid;
}

.ns-signin-states > [data-return-state] {
  grid-area: 1 / 1;
  transition: opacity .18s ease, visibility .18s;
}

.ns-signin-states > [data-return-state]:not(.is-showing) {
  opacity: 0;
  visibility: hidden;
}

/* Password reset — below the sign-in button rather than on the password label row, so the keyboard
   path is email → password → Sign in with no detour through it. Quiet by default: it is the
   exception, and the button above it is the thing almost everybody wants. */
.ns-signin-forgot {
  margin: 12px 0 0;
  font-size: 13px;
  text-align: center;
}

.ns-signin-forgot > a {
  color: var(--ns-muted);
  border-radius: var(--ns-radius-control);
  transition: color .14s ease;
}

.ns-signin-forgot > a:hover {
  color: var(--ns-ink);
}

.ns-signin-forgot > a:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

/* The quiet way out of the sign-in card for somebody who has no account yet. Sized below the form's
   own controls on purpose: it is the minority path, and the divider above it already separates it
   from the primary action. */
.ns-signin-alt {
  margin: 18px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--ns-divider);
  font-size: 13px;
  color: var(--ns-muted);
  text-align: center;
}

.ns-signin-alt > a {
  font-weight: 600;
}

/* footer bar on a scrim */
.ns-signin-foot {
  position: relative;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px 16px;
  padding: 15px 44px;
  border-top: 1px solid rgba(255, 255, 255, .14);
  background: rgba(4, 16, 12, .32);
}

/* the NimbleSign product mark - the "powered by" attribution, brand-independent */
.ns-signin-foot-mark {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--ns-font-brand);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -.01em;
  text-transform: lowercase;
  color: #fff;
}

.ns-signin-foot-mark img {
  height: 20px;
  width: auto;
}

.ns-signin-foot-mark span span {
  color: var(--ns-accent);
}

.ns-signin-foot-trust {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: rgba(255, 255, 255, .72);
}

/* language toggle, styled for the on-scrim footer (host passes this via SwitcherButtonClass) */
.ns-signin-lang-btn {
  border: 1px solid rgba(255, 255, 255, .3);
  border-radius: 8px;
  background: transparent;
  color: #fff;
  font-weight: 600;
  font-size: 12px;
  padding: 7px 12px;
}

.ns-signin-lang-btn:hover {
  background: rgba(255, 255, 255, .12);
  border-color: rgba(255, 255, 255, .5);
  color: #fff;
}

@media (max-width: 900px) {
  .ns-signin-body {
    flex-direction: column;
    gap: 26px;
    padding: 92px 20px 36px;
  }

  .ns-signin-pitch {
    flex: none;
    max-width: 420px;
    text-align: center;
  }

  .ns-signin-headline { font-size: 30px; }
  .ns-signin-features { display: none; }

  .ns-signin-card {
    width: 100%;
    max-width: 420px;
  }

  .ns-signin::before {
    background: linear-gradient(180deg, rgba(4, 16, 12, .5) 0%, rgba(4, 16, 12, 0) 42%);
  }

  .ns-signin-foot {
    flex-direction: column;
    gap: 10px;
    text-align: center;
    padding: 16px 20px 24px;
  }
}

/* ---------- NimbleSign components ---------- */

.ns-card {
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
  box-shadow: var(--ns-shadow-card-sm);
}

/* A card with a washed header has to clip, or the wash squares off the top two corners.
   Scoped with :has() rather than set on every .ns-card, because a plain padded card has
   nothing to clip and hiding its overflow would trap a dropdown opened inside it.
   (.ns-set-card carries overflow: hidden outright — every one of those has a head.) */
.ns-card:has(> .ns-panel-head) {
  overflow: hidden;
}

/* The trap the comment above warns about, made real: every plain select is now a dropdown, so a
   card whose select opens near its bottom edge got its own menu clipped by the very corner-clip
   above. Suspend the clip while anything inside is actually open, rather than removing it — the
   header wash still needs it the rest of the time. Same fix on .ns-dt-card below. */
.ns-card:has(> .ns-panel-head):has(.dropdown-menu.show),
.ns-set-card:has(.dropdown-menu.show) {
  overflow: visible;
}

.ns-card-pad {
  padding: 16px 18px;
}

.ns-lift {
  transition: transform .16s ease, box-shadow .16s ease;
}

.ns-lift:hover {
  transform: translateY(-1px);
  box-shadow: var(--ns-shadow-raised);
}

.ns-page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}

.ns-page-head h1 {
  margin: 0;
  font-size: 22px;
  font-weight: 800;
}

.ns-page-sub {
  font-size: 13px;
  color: var(--ns-muted);
  margin-top: 2px;
}

.ns-stat {
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
  padding: 13px 15px;
}

.ns-stat-label {
  font-size: 12px;
  color: var(--ns-muted);
  font-weight: 600;
}

/* Ink explicitly, because a stat tile is sometimes an <a>: without this the figure inherits the
   link colour and half the strip comes out blue purely because those tiles happen to lead
   somewhere, which reads as a status code that does not exist. The toned variants below still
   win on specificity where a figure genuinely is a verdict. */
.ns-stat-value {
  font-size: 24px;
  font-weight: 800;
  margin-top: 2px;
  line-height: 1.15;
  color: var(--ns-ink);
}

.ns-stat-value.success { color: var(--ns-success-text); }
.ns-stat-value.warning { color: var(--ns-warning-text); }
.ns-stat-value.danger { color: var(--ns-danger-text); }
.ns-stat-value.primary { color: var(--ns-primary); }

/* Not a status — a figure that is deliberately quiet, like a draft count nobody needs to act on. */
.ns-stat-value.neutral { color: var(--ns-slate); }

.ns-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
}

.ns-eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ns-muted);
}

.ns-eyebrow.warning {
  color: var(--ns-warning-text);
}

/* An item count beside a card's title. It is a count of the rows behind the link, so it only
   goes on a card where one honestly exists — a card whose subject is three caps in three
   different units (Plan & limits) carries no pill rather than a number that means nothing.
   The bare class is the amber "needs a human" case; ordinary counts take .is-info. */
.ns-count-pill {
  font-size: 10.5px;
  font-weight: 700;
  background: var(--ns-warning-tint);
  color: var(--ns-warning-text);
  border-radius: 999px;
  padding: 1px 7px;
}

.ns-count-pill.is-info {
  background: var(--ns-primary-tint);
  color: var(--ns-primary-hover);
}

/* Zero still gets a pill — "this area has nothing in it" is the fact the card exists to state —
   but in the ink of an absence, so a quiet account does not read as a page full of badges. */
.ns-count-pill.is-zero {
  background: var(--ns-divider);
  color: var(--ns-ink-zero);
}

/* severity dots for attention rows */

.ns-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
}

.ns-dot-danger { background: var(--ns-maple); }
.ns-dot-warning { background: var(--ns-warning); }
.ns-dot-neutral { background: var(--ns-muted); }
.ns-dot-success { background: var(--ns-success); }
.ns-dot-info { background: var(--ns-blue-600); }

/* usage meter - one thin bar per capped metric on the org overview and the operator's org view.
   Only drawn when a limit exists: a bar with no ceiling has nothing to fill. */
.ns-usage-bar {
  display: block;
  height: 4px;
  margin-top: .3rem;
  border-radius: 2px;
  background: var(--ns-border);
  overflow: hidden;
}

.ns-usage-bar-fill {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--ns-blue-600);
  /* A meter that jumps to its new length reads as a different meter. Declared here with the bar
     itself rather than in a motion block elsewhere — a second .ns-usage-bar-fill rule further down
     the file is how one component ends up owned by two sections. */
  transition: width .3s cubic-bezier(.2, .7, .2, 1);
}

/* Colour follows the row's dot, so "at limit" reads the same in both places. */
.ns-panel-row:has(.ns-dot-warning) .ns-usage-bar-fill { background: var(--ns-warning); }
.ns-panel-row:has(.ns-dot-danger) .ns-usage-bar-fill { background: var(--ns-maple); }

/* back chevron + breadcrumb trail in the top bar (see _Breadcrumb.cshtml) */

.ns-back {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ns-slate);
}

.ns-back:hover {
  background: var(--ns-divider);
  color: var(--ns-text-700);
}

.ns-back:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

.ns-trail {
  display: flex;
  align-items: center;
  min-width: 0;
}

.ns-trail-step {
  flex: none;
}

/* The current page - the only segment allowed to eat the leftover width. */
.ns-trail-leaf {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-crumb {
  color: var(--ns-muted);
  font-weight: 600;
  font-size: 12.5px;
}

.ns-crumb:hover {
  color: var(--ns-blue-hover);
  text-decoration: none;
}

.ns-crumb-sep {
  color: #C7D0DB;
  margin: 0 8px;
  font-weight: 400;
}

/* Narrow bars keep the chevron (the way back) and the page name, dropping the
   intermediate hops - the deepest trail here is only four segments. */
@media (max-width: 860px) {
  .ns-trail-step:not(:last-of-type) {
    display: none;
  }
}

@media (max-width: 600px) {
  .ns-trail-step {
    display: none;
  }
}

/* upload dropzone + selected-file rows */

.ns-dropzone {
  border: 2px dashed var(--ns-info-border);
  border-radius: var(--ns-radius-card);
  background: #F3FAFE;
  padding: 24px;
  text-align: center;
  cursor: pointer;
  transition: border-color .16s ease, background-color .16s ease;
}

.ns-dropzone:hover,
.ns-dropzone.dragover {
  border-color: var(--ns-blue-600);
  background: var(--ns-blue-50);
}

.ns-dropzone-icon {
  width: 42px;
  height: 42px;
  border-radius: 11px;
  background: var(--ns-blue-50);
  color: var(--ns-blue-600);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 10px;
}

.ns-file-row {
  display: flex;
  align-items: center;
  gap: 11px;
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-control);
  padding: 10px 13px;
}

.ns-file-icon {
  width: 30px;
  height: 36px;
  border-radius: 5px;
  background: var(--ns-blue-50);
  color: var(--ns-blue-600);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.ns-file-icon.success {
  background: var(--ns-success-50);
  color: var(--ns-success);
}

.ns-file-remove {
  border: 0;
  background: none;
  color: var(--ns-muted);
  border-radius: 6px;
  padding: 2px 6px;
  line-height: 1;
  font-size: 15px;
}

.ns-file-remove:hover {
  color: var(--ns-maple-hover);
  background: var(--ns-danger-50);
}

/* abbreviated hash chips */

.ns-hash-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--ns-font-mono);
  font-size: 10.5px;
  color: var(--ns-slate);
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: 7px;
  padding: 4px 9px;
  white-space: nowrap;
}

/* compact clickable rows inside a card */

.ns-row-link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 14px;
  color: inherit;
}

.ns-row-link:hover {
  background: var(--ns-thead-bg);
  text-decoration: none;
  color: inherit;
}

/* Inset, because a row-link sits flush against its card's edges and an outset ring would be
   clipped by the card's own overflow: hidden. Same recipe as .ns-dash-columns a.ns-panel-row,
   whose comment already claimed it matched this class - it did not, because until now this
   class had a hover state and no focus state at all, so keyboard users fell back to the
   browser's default ring on every console state card and the whole notifications feed. */
.ns-row-link:focus-visible {
  outline: 0;
  background: var(--ns-thead-bg);
  box-shadow: inset var(--ns-focus-ring);
}

.ns-row-divided {
  border-top: 1px solid var(--ns-divider);
}

/* a .ns-row-link-style row that also carries a trailing dismiss button - the row itself
   isn't the link (a button can't nest inside an anchor), so the clickable content is a
   plain inner anchor and the dismiss form is a sibling. See Pages/Notifications/Index. */

.ns-notif-list-row {
  display: flex;
  align-items: center;
  padding: 11px 14px;
}

.ns-notif-list-row:hover {
  background: var(--ns-thead-bg);
}

.ns-notif-row-body {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1 1 auto;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}

.ns-notif-row-body:hover {
  color: inherit;
  text-decoration: none;
}

.ns-row-dismiss {
  flex: none;
  margin-left: 8px;
  border: 0;
  background: none;
  color: var(--ns-muted);
  border-radius: 8px;
  padding: 4px 6px;
  line-height: 0;
}

.ns-row-dismiss:hover {
  background: var(--ns-divider);
  color: var(--ns-maple-hover);
}

.ns-row-dismiss:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

/* Tenants card row (operator console): the same .ns-notif-list-row, but carrying a role select
   and a Remove button rather than one dismiss icon. Two controls plus an organization name do not
   fit a half-width card on a narrow viewport, so the row is allowed to wrap and the actions drop
   to their own line instead of crushing the name to an ellipsis.
   docs/design/patterns/tables-and-rows.md */

.ns-tenancy-row {
  flex-wrap: wrap;
  row-gap: 8px;
}

/* A basis rather than `1 1 auto`: the name needs a width it is unwilling to shrink below, or the
   row never runs out of room and never wraps. */
.ns-tenancy-row > .ns-notif-row-body {
  flex: 1 1 11rem;
}

.ns-tenancy-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/* overflow (kebab) trigger */

.ns-kebab {
  border: 0;
  background: none;
  color: var(--ns-muted);
  border-radius: 8px;
  padding: 4px 6px;
  line-height: 0;
}

.ns-kebab:hover {
  background: var(--ns-divider);
  color: var(--ns-text-700);
}

.min-w-0 {
  min-width: 0;
}

/* status pills - one shape; add .ns-pill-dot for the coloured status dot */

.ns-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 11px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.25;
  white-space: nowrap;
  background: var(--ns-divider);
  color: var(--ns-slate);
  --ns-pill-dot-color: var(--ns-muted);
}

.ns-pill-dot::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ns-pill-dot-color);
  flex: none;
}

.ns-pill-info {
  background: var(--ns-blue-50);
  color: var(--ns-blue-hover);
  --ns-pill-dot-color: #1E9AD6;
}

.ns-pill-success {
  background: var(--ns-success-50);
  color: var(--ns-success-text);
  --ns-pill-dot-color: var(--ns-success);
}

.ns-pill-warning {
  background: var(--ns-warning-tint);
  color: var(--ns-warning-text);
  --ns-pill-dot-color: var(--ns-warning);
}

.ns-pill-danger {
  background: var(--ns-danger-50);
  color: var(--ns-danger-text);
  --ns-pill-dot-color: var(--ns-maple);
}

/* A size, not a tone: combines with any of the tones above. For a pill that rides *beside* a piece
   of primary content rather than standing in a column of its own — the "you" marker next to an
   account's name, "signs in here" next to an organization. Was six copies of
   `style="padding:1px 8px;font-size:10.5px"` across four pages before it had a name. */
.ns-pill-sm {
  padding: 1px 8px;
  font-size: 10.5px;
}

.ns-icon-well {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: var(--ns-blue-50);
  color: var(--ns-blue-600);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

/* The five variants are keyed to *content*, not to decoration — see the card recipe further
   down: primary (the default) = documents in the pipeline · navy = ways to start, capabilities ·
   success = finished and proven · neutral = reference and background · warning = needs a human
   now. Picking one says what the card holds, so a card never switches variant just to break up
   a run of similar-looking headers. */
.ns-icon-well.success { background: var(--ns-success-50); color: var(--ns-success); }
.ns-icon-well.warning { background: var(--ns-warning-tint); color: var(--ns-warning); }
.ns-icon-well.danger { background: var(--ns-danger-50); color: var(--ns-maple-hover); }
.ns-icon-well.neutral { background: var(--ns-divider); color: var(--ns-slate); }
.ns-icon-well.navy { background: var(--ns-blue-50); color: var(--ns-navy-900); }

/* Header size. The 34px well is for empty states and page furniture; inside a card header it
   would outweigh the 13px title beside it. */
.ns-icon-well.sm {
  width: 24px;
  height: 24px;
  border-radius: 7px;
}

/* Band size: a notch above a card header's well, because a band header sits a level above the
   cards under it and the two are read in the same glance. See .ns-dash-band-head. */
.ns-icon-well.band {
  width: 26px;
  height: 26px;
  border-radius: 8px;
}

/* audit trail — filterable, expandable event log (Envelopes/Audit) */

.ns-audit-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.ns-audit-filter {
  border: 1px solid var(--ns-border-strong);
  border-radius: 8px;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ns-ink);
  background: var(--ns-surface);
  padding: 6px 30px 6px 11px;
  cursor: pointer;
}

.ns-audit-filter:focus-visible {
  outline: none;
  border-color: var(--ns-blue-600);
  box-shadow: var(--ns-focus-ring);
}

.ns-audit-count {
  font-size: 12px;
  color: var(--ns-muted);
}

.ns-audit-grid {
  display: grid;
  grid-template-columns: minmax(160px, 210px) minmax(140px, 190px) minmax(0, 1fr) 168px 26px;
  gap: 10px;
  align-items: center;
  padding: 11px 16px;
}

.ns-audit-head {
  background: var(--ns-thead-bg);
  border-bottom: 1px solid var(--ns-border);
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ns-muted);
}

.ns-audit-item {
  border-top: 1px solid var(--ns-divider);
}

.ns-audit-item:first-of-type {
  border-top: 0;
}

.ns-audit-summary {
  cursor: pointer;
  list-style: none;
}

.ns-audit-summary::-webkit-details-marker { display: none; }
.ns-audit-summary::marker { content: ""; }

.ns-audit-summary:hover { background: var(--ns-thead-bg); }

.ns-audit-summary:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--ns-blue-600);
}

.ns-audit-event {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}

.ns-audit-well {
  width: 28px;
  height: 28px;
  border-radius: 8px;
}

.ns-audit-type {
  font-weight: 700;
  font-size: 12.5px;
  color: var(--ns-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-audit-actor {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

.ns-audit-avatar {
  width: 22px;
  height: 22px;
  font-size: 9px;
}

.ns-audit-avatar.is-system { background: var(--ns-muted); }

.ns-audit-actor-name {
  font-size: 12px;
  color: var(--ns-text-700);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-audit-detail {
  font-size: 12.5px;
  color: var(--ns-text-700);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-audit-time {
  font-size: 11px;
  color: var(--ns-muted);
}

.ns-audit-chevron {
  display: flex;
  justify-content: center;
  color: var(--ns-muted);
}

.ns-audit-chevron svg { transition: transform .15s ease; }
.ns-audit-item[open] .ns-audit-chevron svg { transform: rotate(180deg); }

.ns-audit-body {
  padding: 4px 16px 16px 64px;
  background: var(--ns-thead-bg);
}

.ns-audit-panel {
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: 10px;
  padding: 13px 15px;
}

.ns-audit-fact {
  display: flex;
  gap: 12px;
  font-size: 12px;
  margin-bottom: 6px;
  align-items: baseline;
}

.ns-audit-fact-k {
  width: 140px;
  flex: none;
  color: var(--ns-muted);
  font-weight: 600;
}

.ns-audit-fact-v {
  flex: 1;
  color: var(--ns-text-700);
  word-break: break-word;
}

.ns-audit-hashline {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 10px;
}

.ns-audit-copy {
  border: 1px solid var(--ns-border);
  background: var(--ns-surface);
  color: var(--ns-blue-600);
  border-radius: 7px;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
}

.ns-audit-copy:hover { background: var(--ns-blue-50); }
.ns-audit-copy.is-copied { color: var(--ns-success); border-color: var(--ns-success-50); }

.ns-audit-chain {
  font-size: 10.5px;
  color: var(--ns-muted);
}

.ns-audit-raw { margin-top: 11px; }

.ns-audit-raw-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ns-muted);
  margin-bottom: 5px;
}

.ns-audit-pre {
  margin: 0;
  background: var(--ns-navy-900);
  color: var(--ns-blue-50);
  border-radius: 8px;
  padding: 11px 13px;
  font-family: var(--ns-font-mono);
  font-size: 11px;
  line-height: 1.5;
  overflow: auto;
  white-space: pre;
}

@media (max-width: 720px) {
  .ns-audit-head { display: none; }
  .ns-audit-summary {
    grid-template-columns: 1fr 26px;
    row-gap: 4px;
  }
  .ns-audit-event   { grid-column: 1; }
  .ns-audit-chevron { grid-column: 2; grid-row: 1; }
  .ns-audit-actor   { grid-column: 1 / -1; }
  .ns-audit-detail  { grid-column: 1 / -1; white-space: normal; }
  .ns-audit-time    { grid-column: 1 / -1; }
  .ns-audit-body    { padding-left: 16px; }

  /* The drawer's fact rows: the fixed 140px key column left the value ~120px, which
     word-break chopped mid-word ("Cancelle / d by the signer"). Key above value reads
     cleanly at any width. */
  .ns-audit-fact {
    flex-direction: column;
    align-items: stretch;
    gap: 1px;
  }

  .ns-audit-fact-k {
    width: auto;
  }
}

/* empty / error / no-access states */

.ns-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 48px 16px;
}

.ns-empty-icon {
  width: 66px;
  height: 66px;
  border-radius: 50%;
  background: var(--ns-blue-50);
  color: var(--ns-blue-600);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 18px;
}

.ns-empty-icon.danger { background: var(--ns-danger-50); color: var(--ns-maple-hover); }
.ns-empty-icon.warning { background: var(--ns-warning-tint); color: var(--ns-warning); }
.ns-empty-icon.success { background: var(--ns-success-50); color: var(--ns-success); }

.ns-empty-title {
  margin: 0 0 8px;
  font-size: 20px;
  font-weight: 800;
}

.ns-empty p {
  margin: 0 0 20px;
  max-width: 300px;
  font-size: 13px;
  color: var(--ns-slate);
  line-height: 1.55;
}

.ns-ref-code {
  font-family: var(--ns-font-mono);
  font-size: 11.5px;
  color: var(--ns-text-700);
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: 8px;
  padding: 7px 12px;
}

/* The support panel (_SupportContact.cshtml) — the route to a human every public failure ends
   with. No design basis; built under docs/design/new-component.md Path 2, and specified in
   docs/design/components/support-contact.md. Left-aligned wherever it sits, including inside the
   centred .ns-empty card, because it is something to read and copy rather than a headline.

   Every inner rule is scoped under .ns-support on purpose: .ns-empty p (0,1,1) would otherwise
   win over a bare class and squeeze the notice to 300px with the error page's paragraph margin. */
.ns-support {
  width: 100%;
  max-width: 380px;
  margin: 0 auto;
  padding: 14px 16px;
  text-align: left;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ns-ink);
  background: var(--ns-thead-bg);
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-control);
}

.ns-support .ns-support-notice {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  max-width: none;
  margin: 0 0 12px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ns-success-text);
}

.ns-support .ns-support-notice svg { flex: none; margin-top: 1px; }

.ns-support .ns-support-title {
  max-width: none;
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-support-routes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ns-support-routes li {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  color: var(--ns-slate);
}

.ns-support-routes svg { flex: none; }

/* Mono for the same reason the help desk's address is: it is transcribed exactly. Breaks anywhere
   so a long address never pushes the card wider than a 360px screen. */
.ns-support-link {
  font-family: var(--ns-font-mono);
  font-weight: 500;
  color: var(--ns-primary);
  min-width: 0;
  word-break: break-all;
  border-radius: 4px;
  transition: color 160ms ease;
}

.ns-support-link:hover { color: var(--ns-primary-hover); text-decoration: underline; }
.ns-support-link:focus-visible { outline: none; box-shadow: var(--ns-focus-ring); }

.ns-support-ref {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--ns-border);
}

/* Nothing above it (no notice, no contact): no rule to separate it from. */
.ns-support > .ns-support-ref:first-child,
.ns-support > .visually-hidden:first-child + .ns-support-ref {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.ns-support-ref-head {
  display: flex;
  align-self: stretch;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}

.ns-support-ref-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ns-slate);
}

.ns-support-ref-at {
  font-family: var(--ns-font-mono);
  font-size: 11px;
  color: var(--ns-slate);
  white-space: nowrap;
}

/* Only the id, so one click selects exactly what is quoted. overflow-wrap rather than
   word-break: a GUID breaks at its own hyphens first, and mid-group only when it must. */
.ns-support-ref-code {
  max-width: 100%;
  overflow-wrap: anywhere;
  user-select: all;
}

.ns-support .ns-support-hint {
  max-width: none;
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--ns-slate);
}

/* search field with a leading icon */

.ns-search {
  position: relative;
}

.ns-search > svg {
  position: absolute;
  left: 11px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ns-muted);
  pointer-events: none;
}

.ns-search > .form-control {
  padding-left: 33px;
}

/* trust footnote (maple + PIPEDA line) */

.ns-trust {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--ns-muted);
}

/* logo + wordmark */

.ns-brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}

.ns-brand img {
  height: 28px;
  width: auto;
}

.ns-brand-name {
  font-family: var(--ns-font-brand);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -.01em;
  text-transform: lowercase;
  color: var(--ns-primary);
}

.ns-brand-name > span {
  color: var(--ns-nav);
}

/* "or" divider between auth options */

.ns-divider-or {
  display: flex;
  align-items: center;
  gap: 10px;
  color: #A9B4C0;
  font-size: 11px;
}

.ns-divider-or::before,
.ns-divider-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: #EAEFF5;
}

/* organization cards (switcher page) */

.ns-org-card {
  display: flex;
  align-items: center;
  gap: 12px;
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
  padding: 13px 14px;
}

.ns-org-card.active {
  border: 1.5px solid var(--ns-blue-600);
  box-shadow: 0 0 0 3px rgba(var(--ns-primary-rgb),.1);
}

.ns-org-tile {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 800;
  flex: none;
}

/* full-screen designer chrome (navy action bar + palette + deep canvas) */

.ns-designer {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh; /* mobile browsers: track the visible viewport, not the largest one */
}

.ns-designer-bar {
  background: var(--ns-navy-900);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 16px;
  min-height: 46px;
  flex: none;
}

.ns-designer-name {
  font-size: 13px;
  font-weight: 700;
  color: #fff;
}

.ns-designer-sub {
  font-size: 11px;
  color: #9DC7E0;
  white-space: nowrap;
}

/* One recipe, two icon buttons on the navy designer bar: the back chevron and the help-desk
   trigger. They share the selector rather than the values so they cannot drift apart — the
   two designers' left clusters are the only chrome they have in common. */
.ns-designer-back,
.ns-designer-help {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  color: #9DC7E0;
  flex: none;
  transition: background-color .16s ease, color .16s ease;
}

.ns-designer-back:hover,
.ns-designer-help:hover {
  background: rgba(255, 255, 255, .1);
  color: #fff;
  text-decoration: none;
}

/* The help trigger is a <button> where the back affordance is an <a>. */
.ns-designer-help {
  padding: 0;
  background: none;
  border: 0;
}

/* A white ring rather than --ns-focus-ring. Both of these sit on the navy bar, and the token is
   rgba(var(--ns-primary-rgb), .15) — a 15%-opacity blue over #0F2A44, which is invisible. Same
   reason the bar's buttons take their colours from --ns-btn-* whites instead of the blue seeds.
   (Resolved across PR 1103 and PR 1107, which each added this rule with different values.) */
.ns-designer-back:focus-visible,
.ns-designer-help:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, .3);
  color: #fff;
}

/* ---------- the help-desk dialog (see _HelpDeskModal) ---------- */

.ns-help-lead {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ns-ink);
  margin-bottom: 14px;
}

/* The address gets a surface of its own: it is the one thing here a user has to carry somewhere
   else, so it should read as a value to be taken rather than as more prose. */
.ns-help-address {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 12px;
  background: var(--ns-thead-bg);
  border: 1px solid var(--ns-divider);
  border-radius: var(--ns-radius-control);
}

/* Mono for the same reason URLs and hashes get it — this is an address to be transcribed
   exactly. See docs/design/foundations/type.md. */
.ns-help-email {
  font-family: var(--ns-font-mono);
  font-size: 13px;
  font-weight: 500;
  color: var(--ns-ink);
  min-width: 0;
  word-break: break-all;
}

.ns-help-copy {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
}

/* Colour is not carrying this alone — the label itself changes to "Copied". */
.ns-help-copy.is-copied {
  color: var(--ns-success-text);
}

.ns-help-hint {
  font-size: 12px;
  line-height: 1.5;
  color: var(--ns-muted);
  margin: 12px 0 0;
}

/* buttons that sit on navy or amber bars */
.ns-btn-navy {
  --bs-btn-bg: rgba(255, 255, 255, .1);
  --bs-btn-color: #fff;
  --bs-btn-border-color: rgba(255, 255, 255, .18);
  --bs-btn-hover-bg: rgba(255, 255, 255, .18);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-border-color: rgba(255, 255, 255, .28);
  --bs-btn-active-bg: rgba(255, 255, 255, .22);
  --bs-btn-active-color: #fff;
  --bs-btn-active-border-color: rgba(255, 255, 255, .28);
}

.flex-none {
  flex: none;
}

/* ---------- settings framework: category rail + setting row ----------
   Source mocks: "Settings Controls.dc.html" (row anatomy, the sixteen controls, the ten
   states), "Settings Screens.dc.html" (the consoles) and "Settings Handoff.dc.html" (save
   bar, empty/loading, French, narrow, a11y) in the "NimbleSign settings" design project.
   The rules these classes encode - and the ones a new row has to follow - are written down
   in docs/settings-design.md. Read that before adding a control here.

   Two measurements are load-bearing and everything else hangs off them:
     • the control column is a fixed 300px, so every control on a page lines up;
     • the page reserves 92px of bottom padding whether the save bar is there or not, so
       nothing shifts when it appears. */

/* Bleeds back out of .ns-content's 20px/24px/44px padding so the category rail meets the
   shell's edges and its divider runs the full height of the viewport. .ns-set-body puts
   that padding back on the content side only. */
.ns-set {
  display: flex;
  align-items: stretch;
  margin: -20px -24px -44px;
  min-height: calc(100vh - 52px);
}

/* The rail is the control that switches the content, so it must not look like part of what
   changed. .ns-content animates the whole page in on every navigation, which swept the rail up
   with it - cancel that here and animate only the body, so the rail lands identically each time
   and reads as fixed furniture. It renders byte-identical across every page in a console, which
   is what makes this hold.

   Opacity, not ns-rise: the body contains the position:fixed save bar, and a transform on an
   ancestor would make it a containing block for those 180ms - the bar would jump. Opacity creates
   a stacking context but not a containing block, so fixed positioning is unaffected.

   :has() is the only way for a descendant to opt its ancestor out; baseline in every browser this
   app supports, and if it were ever unsupported the fallback is today's behaviour. */
.ns-content:has(> .ns-set) {
  animation: none;
}

.ns-set-body {
  animation: ns-fade .18s ease both;
}

@keyframes ns-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ---- the category rail ---- */

.ns-cat-rail {
  width: 216px;
  flex: none;
  align-self: stretch;
  background: var(--ns-surface);
  border-right: 1px solid #E9EEF4;
  padding: 16px 12px 24px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ns-cat-head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 4px 9px 12px;
}

.ns-cat-head-mark {
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--ns-nav);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.ns-cat-head-name {
  font-size: 12.5px;
  font-weight: 800;
  line-height: 1.2;
}

.ns-cat-head-scope {
  font-size: 10.5px;
  color: var(--ns-muted);
}

/* The ?K affordance. It sits on every settings screen, which is a promise that one box
   finds anything the person can reach - see .ns-setfind below. */
.ns-cat-find {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: 9px;
  padding: 6px 9px;
  margin-bottom: 10px;
  color: var(--ns-muted);
  font-size: 11.5px;
  text-align: left;
}

.ns-cat-find:hover {
  border-color: var(--ns-info-border);
  color: var(--ns-text-700);
}

.ns-cat-find kbd {
  margin-left: auto;
  font-family: var(--ns-font-mono);
  font-size: 10px;
  background: var(--ns-divider);
  color: var(--ns-slate);
  border-radius: 4px;
  padding: 1px 4px;
}

.ns-cat-group {
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ns-muted);
  padding: 12px 9px 4px;
}

.ns-cat-link {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ns-text-700);
  padding: 7px 9px;
  border-radius: 8px;
  /* French runs long - "Enregistrements et conservation" needs two lines, so no nowrap. */
  line-height: 1.35;
}

.ns-cat-link:hover {
  background: var(--ns-divider);
  color: var(--ns-ink);
  text-decoration: none;
}

.ns-cat-link.active {
  background: var(--ns-primary);
  color: var(--ns-on-primary);
  font-weight: 700;
}

.ns-cat-link.active:hover {
  background: var(--ns-primary-hover);
  color: var(--ns-on-primary);
}

/* A category whose rows are all environment-only. Reachable, but it holds nothing to change. */
.ns-cat-link.is-locked {
  color: var(--ns-muted);
}

/* Nothing trails a rail item but the padlock - no counts, no state dots. See _SettingsRail and
   docs/settings-design.md for why both were removed rather than restyled. */

/* Under 1100px the rail becomes a select, not a scrolling chip row - eleven categories is
   well past the ~3 the design system allows a segmented row. */
.ns-cat-select {
  display: none;
}

/* ---- the page body beside the rail ---- */

.ns-set-body {
  flex: 1;
  min-width: 0;
  padding: 20px 24px 92px;
}

.ns-set-head {
  max-width: 76ch;
  margin-bottom: 18px;
}

.ns-set-head h1 {
  margin: 0;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: -.015em;
}

.ns-set-head p {
  font-size: 13px;
  line-height: 1.55;
  color: var(--ns-muted);
  margin: 3px 0 0;
}

.ns-set-stack {
  display: flex;
  flex-direction: column;
  gap: 16px;
  max-width: 1000px;
}

/* ---- a section card ---- */

.ns-set-card {
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
  box-shadow: var(--ns-shadow-card-sm);
  overflow: hidden;
  /* Suspended while a select's menu inside is open — see .ns-set-card:has(.dropdown-menu.show)
     next to .ns-card:has(> .ns-panel-head) above. */
}

/* A section whose settings can destroy customer data gets a maple left edge and a
   plain-language consequence line - no icon, no alert box competing with the controls.
   .ns-destroys carries the same edge onto an ordinary card outside the settings page - the
   per-tenant retention card on /System/Tenants/{id}/Edit is the other place a control can
   destroy records, and it has to read as the same kind of control, not a differently-styled
   one. Keep the two selectors on one rule so the edge can never drift between them. */
.ns-set-card.is-destructive,
.ns-destroys {
  border-left: 3px solid var(--ns-maple);
}

/* A section card's head is the same recipe as a panel's (see .ns-panel-head): washed, with the
   title and its blurb stacked. It carries no icon well and no area link — a section is part of
   the form you are already on, so there is no area to open, and forty sections each needing an
   invented glyph is how a content-keyed well stops carrying meaning. The wash is the part that
   generalizes, and it is what keeps a settings page and the dashboard recognizably one system. */
/* The head is the control that opens the section, so it is a real <button> reset back to
   looking like the header it replaced. Full width and left-aligned: the whole bar is the
   target, not the words in it. */
.ns-set-card-head {
  width: 100%;
  padding: 14px 18px;
  border: 0;
  border-bottom: 1px solid var(--ns-divider);
  display: flex;
  align-items: flex-start;
  gap: 11px;
  background: var(--ns-wash);
  text-align: left;
  cursor: pointer;
  transition: background .13s ease;
}

.ns-set-card-head:hover {
  background: var(--ns-thead-bg);
}

.ns-set-card-head:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

.ns-set-card-headtext {
  flex: 1;
  min-width: 0;
}

/* Always rotates. A disclosure whose marker does not move reads as not having responded. */
.ns-set-card-chev {
  flex: none;
  margin-top: 3px;
  color: var(--ns-muted);
  transition: transform .18s ease;
}

.ns-set-card.is-open > .ns-set-card-head > .ns-set-card-chev {
  transform: rotate(90deg);
}

/* A collapsed card's head owns the bottom edge of the card, so the divider would draw a line
   under nothing. */
.ns-set-card:not(.is-open) > .ns-set-card-head {
  border-bottom: 0;
}

/* The grid-template-rows recipe \— docs/design/patterns/disclosures.md. No padding on the
   sizer: it does not collapse at 0fr and would leave a visible sliver. visibility is in the
   transition so a collapsed row is not tab-reachable, while the inputs stay in the form and
   still post. */
.ns-set-card-bodywrap {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows .2s ease, opacity .18s ease, visibility .2s;
}

.ns-set-card.is-open > .ns-set-card-bodywrap {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}

/* The direct child of the sizer has to be able to clip. */
.ns-set-card-body {
  min-height: 0;
  overflow: hidden;
}

/* 15px, not 13.5: a card header sat at the same size as the 13px body under it and read as
   another line of content. 15/700 is the existing heading step (h4, modal title) — see
   docs/design/foundations/type.md. */
.ns-set-card-title {
  font-size: 15px;
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.ns-set-card-sub {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ns-muted);
  margin: 3px 0 0;
  max-width: 78ch;
}

/* ---- the setting row ---- */

.ns-set-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 28px;
  align-items: start;
  padding: 15px 18px;
  scroll-margin-top: 72px;
}

.ns-set-row + .ns-set-row,
.ns-set-sub + .ns-set-row,
.ns-set-row + .ns-set-sub {
  border-top: 1px solid var(--ns-divider);
}

/* The label line wraps: a long French label plus its badge does not fit on one line. */
.ns-set-label {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-set-desc {
  font-size: 12.25px;
  line-height: 1.5;
  color: var(--ns-muted);
  margin: 4px 0 0;
  max-width: 52ch;
}

/* The config path. Deployment scope only - it is what operators grep for in .env. */
.ns-set-key {
  font-family: var(--ns-font-mono);
  font-size: 11.5px;
  color: #B6C1CE;
  margin-top: 6px;
  word-break: break-word;
}

.ns-set-control {
  min-width: 0;
}

/* The default value, the range, Revert, a probe result - whatever the row has to add after
   its control. Never the description repeated. */
.ns-set-foot {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ns-muted);
  margin-top: 5px;
}

.ns-set-foot.is-warning {
  color: var(--ns-warning-text);
}

.ns-set-foot.is-ok {
  color: var(--ns-success-text);
  font-weight: 600;
}

/* Replaces the footnote when a validator refuses the value, and states the accepted range. */
.ns-set-error {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ns-danger-text);
  font-weight: 600;
  margin-top: 5px;
}

/* A group governed by the switch above it: indented, dimmed while the switch is off, and
   still legible - an operator should see what they are turning back on before they do it. */
.ns-set-sub {
  padding-left: 18px;
  /* The dim below is a state change in place, so it transitions rather than snapping - 150ms, the
     hover/colour band in docs/design/foundations/motion.md. Added when the personal console's
     out-of-office rows became the second user of this group. */
  transition: opacity .15s ease;
}

.ns-set-sub > .ns-set-row {
  border-left: 2px solid var(--ns-border);
  padding-left: 16px;
}

.ns-set-sub.is-inactive {
  opacity: .5;
}

/* ---- the Advanced disclosure ---- */

.ns-set-adv {
  border-top: 1px solid var(--ns-divider);
}

.ns-set-adv-head {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 11px 18px;
  background: var(--ns-thead-bg);
  border: 0;
  text-align: left;
}

.ns-set-adv-head:hover {
  background: #F1F5F9;
}

.ns-set-adv-head > svg {
  color: var(--ns-slate);
  flex: none;
  transition: transform .16s ease;
}

.ns-set-adv[open] > .ns-set-adv-head > svg {
  transform: rotate(90deg);
}

.ns-set-adv-title {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-text-700);
}

.ns-set-adv-hint {
  font-size: 11.5px;
  color: var(--ns-muted);
  min-width: 0;
}

.ns-set-adv-head > .ns-pill {
  margin-left: auto;
  flex: none;
}

/* Rows inside are ordinary setting rows; only the background separates them. */
.ns-set-adv-body {
  background: #FCFDFE;
  border-top: 1px solid var(--ns-border);
}

.ns-set-adv-body > .ns-set-row:first-child {
  border-top: 0;
}

/* ---- controls ---- */

/* A real checkbox with role="switch", restyled - never a styled div. 36×20 on the desktop,
   with the whole label row as the hit area. */
.ns-switch {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.ns-switch > input {
  appearance: none;
  -webkit-appearance: none;
  width: 36px;
  height: 20px;
  flex: none;
  margin: 0;
  border-radius: 999px;
  background: var(--ns-border-strong);
  position: relative;
  transition: background .16s ease;
}

.ns-switch > input::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(16, 40, 70, .2);
  transition: transform .16s ease;
}

.ns-switch > input:checked {
  background: var(--ns-primary);
}

.ns-switch > input:checked::after {
  transform: translateX(16px);
}

.ns-switch > input:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

.ns-switch > input:disabled {
  opacity: .6;
}

.ns-switch-state {
  font-size: 13px;
  font-weight: 600;
  color: var(--ns-text-700);
}

.ns-switch > input:disabled ~ .ns-switch-state,
.ns-switch > input:not(:checked) ~ .ns-switch-state {
  color: var(--ns-muted);
}

.ns-switch > input:checked ~ .ns-switch-state {
  color: var(--ns-text-700);
}

/* A quantity whose unit is fixed and worth showing: input welded to a unit chip. */
.ns-unit {
  display: flex;
}

.ns-unit > .form-control {
  flex: 1;
  min-width: 0;
  border-right: 0;
  border-radius: var(--ns-radius-control) 0 0 var(--ns-radius-control);
}

.ns-unit > .ns-unit-suffix {
  display: inline-flex;
  align-items: center;
  background: var(--ns-thead-bg);
  border: 1px solid var(--ns-border-strong);
  border-radius: 0 var(--ns-radius-control) var(--ns-radius-control) 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ns-slate);
  padding: 0 11px;
  flex: none;
  white-space: nowrap;
}

/* A number with a hard ceiling the operator must feel. */
.ns-set-meter {
  height: 4px;
  border-radius: 999px;
  background: var(--ns-divider);
  margin-top: 8px;
  overflow: hidden;
}

.ns-set-meter > span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--ns-primary);
}

/* A value this UI can never change. Dashed, so it never reads as an empty field, and its
   footnote always says where the value lives instead. */
.ns-set-ro {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px dashed var(--ns-border-strong);
  border-radius: var(--ns-radius-control);
  background: var(--ns-thead-bg);
  color: var(--ns-slate);
  font-size: 13.5px;
  padding: 8px 11px;
  min-height: 40px;
  word-break: break-word;
}

.ns-set-ro > svg {
  flex: none;
  color: var(--ns-muted);
}

.ns-set-ro-value {
  min-width: 0;
  font-family: var(--ns-font-mono);
  font-size: 12.5px;
  word-break: break-all;
}

/* ---- naming the controls inside one row ---- */

/* A row's label names the setting. Where the control column draws more than one input - a relay
   and its port, an address and its display name - the row label cannot name either, so each input
   carries its own. Label above control, per docs/design/components/forms-and-inputs.md. */
.ns-set-field {
  display: flex;
  flex-direction: column;
  min-width: 0; /* or a long value shoves its neighbour out of the 300px column */
  flex: 1;
}

.ns-set-sublabel {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ns-text-700);
  margin-bottom: 4px;
}

/* Two controls sharing one row: side by side, or stacked where both want the full width. */
.ns-set-pair {
  display: flex;
  gap: 8px;
}

.ns-set-pair.is-stacked {
  flex-direction: column;
}

/* A port is three digits; it does not need a share of the column proportional to a host name. */
.ns-set-field-port {
  flex: 0 0 82px;
}

/* An action that proves a setting works. Never mutates configuration. */
.ns-set-probe {
  display: flex;
}

.ns-set-probe > .form-control {
  flex: 1;
  min-width: 0;
  border-right: 0;
  border-radius: var(--ns-radius-control) 0 0 var(--ns-radius-control);
}

.ns-set-probe > .btn {
  border-radius: 0 var(--ns-radius-control) var(--ns-radius-control) 0;
  flex: none;
}

/* Secret: write-only. Shown as a masked placeholder, replaced or cleared, never read back. */
.ns-set-secret {
  display: flex;
  gap: 8px;
}

.ns-set-secret > .form-control {
  flex: 1;
  min-width: 0;
}

.ns-set-mask {
  font-family: var(--ns-font-mono);
  color: var(--ns-muted);
}

/* ---- row states ---- */

/* Edited but not committed: an amber edge on the control, counted in the save bar. */
.ns-set-row.is-dirty .form-control,
.ns-set-row.is-dirty .form-select {
  border-color: var(--ns-warning);
  border-left-width: 3px;
}

.ns-set-row.is-dirty .ns-unit-suffix {
  border-color: var(--ns-warning);
  background: var(--ns-warning-50);
  color: var(--ns-warning-text);
}

.ns-set-row.is-dirty .ns-switch > input:checked {
  background: var(--ns-warning);
}

.ns-set-row.is-dirty .ns-switch > input:not(:checked) {
  background: #D9C7A4;
}

/* A console page can also be a grid rather than a stack of rows - Admin ? Features is a
   person × feature matrix. Same amber signal, carried on the table row. */
tbody tr.is-dirty > :first-child {
  box-shadow: inset 3px 0 0 var(--ns-warning);
}

tbody tr.is-dirty {
  background: var(--ns-warning-50);
}

/* Refused by a server-side validator. The save bar refuses the whole batch to match the
   all-or-nothing save, so the row only has to say what is wrong. */
.ns-set-row.is-invalid .form-control,
.ns-set-row.is-invalid .form-select {
  border-color: var(--bs-danger);
}

.ns-set-row.is-invalid .ns-unit-suffix {
  border-color: var(--bs-danger);
  background: var(--ns-danger-50);
  color: var(--ns-danger-text);
}

/* In flight. Inert, but keeping its value and its height - nothing collapses or jumps. */
.ns-set-row.is-saving .ns-set-control {
  opacity: .55;
  pointer-events: none;
}

/* Committed and live. Holds for a few seconds, then the row settles back into Overridden. */
.ns-set-row.is-saved > .ns-set-control > .ns-set-foot {
  color: var(--ns-success-text);
}

/* ---- the save bar ----
   One per page. Absent until something is dirty; it never covers the last row, because
   .ns-set-body reserves its height whether the bar is there or not. */

.ns-savebar {
  position: fixed;
  left: calc(56px + 24px);
  right: 24px;
  /* Clears the home indicator on a phone — it is docked to the bottom edge, so the inset applies
     (polish-pass §4). Falls back to plain 20px anywhere without a safe area. */
  bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  z-index: 1035;
  background: var(--ns-ink);
  border: 1px solid transparent;
  border-radius: var(--ns-radius-card);
  box-shadow: 0 18px 44px rgba(16, 40, 70, .28);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 16px;
  overflow: hidden;
  transform: translateY(140%);
  transition: transform .2s ease, opacity .2s ease;
  opacity: 0;
  pointer-events: none;
  /* Not just invisible - out of the tab order and out of the accessibility tree, so a bar with
     nothing to save cannot be tabbed into or read out. */
  visibility: hidden;
}

.ns-savebar.is-shown {
  transform: none;
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
}

.ns-savebar-text {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.ns-savebar-count {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ns-warning);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.ns-savebar-label {
  font-size: 13px;
  font-weight: 600;
  color: #fff;
  white-space: nowrap;
}

/* The change list. First thing to go on a narrow viewport - the count is the part you
   cannot reconstruct by looking at the page. */
.ns-savebar-list {
  font-size: 12.25px;
  color: #9DC7E0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-savebar-actions {
  display: flex;
  gap: 8px;
  flex: none;
}

.ns-savebar-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: linear-gradient(90deg, rgba(var(--ns-accent-rgb), 0), var(--ns-accent), rgba(var(--ns-accent-rgb), 0));
  background-size: 200% 100%;
  animation: ns-savebar-sweep 1.1s linear infinite;
  display: none;
}

@keyframes ns-savebar-sweep {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

.ns-savebar.is-saving .ns-savebar-progress {
  display: block;
}

.ns-savebar.is-saving .ns-savebar-count {
  background: rgba(255, 255, 255, .14);
  color: #9DC7E0;
}

.ns-savebar.is-saving .ns-savebar-actions .btn {
  opacity: .5;
  pointer-events: none;
}

/* Refused. Nothing was written, so every edit is still on the page exactly as typed. */
.ns-savebar.is-error {
  border-color: var(--bs-danger);
}

.ns-savebar.is-error .ns-savebar-count {
  background: var(--bs-danger);
  color: #fff;
}

.ns-savebar.is-error .ns-savebar-list {
  color: var(--ns-danger-border);
}

.ns-savebar.is-saved .ns-savebar-count {
  background: var(--ns-success);
  color: #fff;
}

/* ---- console overviews ----
   A console's front door is an answer, not a menu: the rail is the navigation, so the page is a
   stat strip plus panels that say what is running, what changed, and what needs you. */

/* A console page that is not a settings form - no save bar, so no reserved bar height. */
.ns-set-body-plain {
  padding-bottom: 44px;
}

.ns-overview-grid {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  gap: 16px;
  align-items: start;
  max-width: 1060px;
}

.ns-stat-foot {
  font-size: 10.5px;
  color: var(--ns-muted);
  margin-top: 1px;
}

/* ---- the card recipe ----
   One recipe, everywhere, so a new area drops in without redesign. Source: the approved
   dashboard (design 3a, "Main dashboard redesign exploration"), whose rules are recorded in
   docs/design/components/cards-and-surfaces.md. A card is:

     .ns-card / .ns-set-card            white, 1px --ns-border, 12px radius, overflow hidden
       .ns-panel-head                   washed header: icon well + title + optional sub +
                                        optional right-aligned link to the area it opens
       .ns-panel-row × n                divided rows
       .ns-panel-foot                   optional --ns-thead-bg strip

   The header is the load-bearing part, and the two things it is easy to get wrong are the
   wash and the well. The wash is never decorative: navy is everyday, .is-attention (amber) is
   only ever "a human has to act today", .is-primary (the stark tint) is only ever "addressed
   to you". Reach for either exception on an ordinary panel and both stop meaning anything.
   The well's variant is likewise keyed to content — see .ns-icon-well above. */
.ns-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--ns-divider);
  background: var(--ns-wash);
}

/* Needs a human today. Paired with a warning well and, on a full-width strip, an amber left
   edge (.ns-panel-attention) — colour alone never carries it. */
.ns-panel-head.is-attention {
  background: var(--ns-wash-attention);
}

/* Addressed to you personally: the one panel on the dashboard about the signed-in person
   rather than their organization's work. Stark on purpose. */
.ns-panel-head.is-primary {
  background: var(--ns-primary-tint);
  border-bottom-color: var(--ns-info-border-soft);
}

/* The header's left half: well + (title over optional sub). min-width:0 so a long title
   truncates instead of shoving the right-hand link off the card. */
.ns-panel-head-main {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

/* Same step as .ns-set-card-title — one card header size across the app. */
.ns-panel-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-panel-head.is-primary .ns-panel-title {
  color: var(--ns-nav);
}

/* The sub is one line on what the panel holds — never a restatement of the title. */
.ns-panel-sub {
  font-size: 11px;
  line-height: 1.4;
  color: var(--ns-muted);
  margin: 1px 0 0;
}

.ns-panel-head.is-primary .ns-panel-sub {
  color: var(--ns-primary-hover);
}

/* Right-aligned, and it names the destination ("All envelopes", "Templates", "Plan") rather
   than saying "View all" — the card is a window onto an area, and this is the way in. */
.ns-panel-link {
  flex: none;
  font-size: 11.5px;
  font-weight: 600;
}

/* On the primary-tint wash the tinted well vanishes into the header, so that one inverts. */
.ns-panel-head.is-primary .ns-icon-well {
  background: var(--ns-surface);
  color: var(--ns-primary);
}

/* The footer strip. For the standing rule a reader needs *after* the rows — how the reminder
   cadence works, where a value actually lives, the secondary ways to start. Not for a count,
   and not a second header. */
.ns-panel-foot {
  padding: 10px 16px;
  border-top: 1px solid var(--ns-divider);
  background: var(--ns-thead-bg);
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ns-muted);
}

/* A footer strip used as a divider rather than a sentence: it labels the rows *below* it,
   which is how a plan cap with no meter behind it stays visibly separate from metered
   usage. See "never fake a metric" in docs/design/polish-pass.md. */
.ns-panel-foot-eyebrow {
  padding: 9px 16px;
  border-top: 1px solid var(--ns-divider);
  background: var(--ns-thead-bg);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ns-muted);
}

/* A card whose whole subject is "act on this today" carries the edge as well as the wash. */
.ns-panel-attention {
  border-left: 3px solid var(--ns-warning);
}

/* ---- tap targets on a touch pointer ----
   A finger needs about 44px. These controls sit at 13–32px because they are sized for a cursor: a
   card's area link is 17px of text, a footer link 13–15px, a row's small button 32px.

   Scoped to `pointer: coarse` rather than `hover: none`, because this is about the size of the thing
   doing the pointing, not whether it can hover — so desktop density is untouched.

   `min-height` with `inline-flex` rather than padding, so the label stays optically centred and the
   growth is the element's own box. A `::after` overlay would be cheaper for layout but these links
   sit in a flex row beside a title, and an overlay tall enough to matter would reach across and
   start swallowing the title's clicks.

   Rows are deliberately absent: a row carrying a `.stretched-link` is already 54px or more, because
   the overlay covers the row rather than the title inside it. Measuring the anchor's own box makes
   those look like failures when the actual target is fine. */
@media (pointer: coarse) {
  .ns-panel-link,
  .ns-panel-foot a,
  .ns-panel-foot-eyebrow a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* Only a row's own action, not every .btn-sm in the app — the designer's toolbars are dense on
     purpose and would break if their small buttons all grew. */
  .ns-stage-row .btn,
  .ns-panel-row .btn {
    min-height: 44px;
  }

  /* The app chrome itself: rail links (the stack just gets taller), and the top bar's four
     controls. The top bar's min-height gives it room to grow a few px on touch rather than
     clipping — nothing docks to it. */
  .ns-rail-link {
    min-height: 44px;
  }

  .ns-notif-btn,
  .ns-avatar-btn {
    width: 44px;
    height: 44px;
  }

  .ns-avatar-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .ns-org-chip,
  .ns-topbar .btn-sm,
  .ns-bare-topright .btn-sm,
  .ns-org-card .btn {
    min-height: 44px;
  }

  /* Plain Bootstrap tables (the kiosk Details device list is the main one): row buttons keep
     their density and gain the invisible hit area, same treatment as the shared list table. */
  .ns-content .table .btn-sm {
    position: relative;
  }

  .ns-content .table .btn-sm::after {
    content: "";
    position: absolute;
    inset: -6px;
  }
}

.ns-panel-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 16px;
}

.ns-panel-row + .ns-panel-row {
  border-top: 1px solid var(--ns-divider);
}

.ns-panel-row-title {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-panel-row-sub {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ns-muted);
}

.ns-panel-row-who {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ns-text-700);
}

/* The same two tones the dashboard rail's value carries, on the overview panel's value slot: a
   zero reads as an absence rather than a loud nothing, and a count somebody can still act on takes
   the warning ink. The words say it either way - the tone reinforces, never carries it alone. */
.ns-panel-row-who.is-zero {
  color: var(--ns-muted);
}

.ns-panel-row-who.is-warning {
  color: var(--ns-warning-text);
}

.ns-panel-body {
  padding: 13px 16px;
}

.ns-panel-empty {
  padding: 20px 16px;
  text-align: center;
  font-size: 11.5px;
  color: var(--ns-muted);
}

.ns-panel-empty-title {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-ink);
  margin-bottom: 3px;
}

@media (max-width: 1100px) {
  .ns-overview-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .ns-set-body-plain {
    padding-bottom: 24px;
  }
}

/* ---- the dashboard (design 6d) ----
   Three ideas. The first stops an empty account looking broken; the other two answer "where does
   a new panel go" without taste entering into it.

   1. One switcher over one list (.ns-stage-card), full bleed. The five stages of the pipeline are
      the tabs, the numbers are on the tabs, and only the selected stage renders a list. A stage
      with nothing in it is a quiet number rather than an empty panel. It opens on "Needs you", so
      the page lands on exactly what the old amber attention card showed.
   2. Two fixed columns below it (.ns-dash-columns), pushed to opposite margins with the slack
      falling in the gap between them: .ns-dash-work (1180px) is what the reader acts on,
      .ns-dash-rail (300px) is what they only refer to. The separation is the label — neither
      column needs a heading to say which is which, which is why they are pushed apart rather
      than centred as a pair.
   3. A card is filed under the band whose link it opens. "Everything that goes to /Admin" is
      literally that group, and the band header is that link, so adding an area adds a band rather
      than starting an argument about an existing one.

   The card recipe inside both columns is the app-wide one (.ns-card + .ns-panel-head + rows +
   optional .ns-panel-foot) — that is the point of having a recipe. It runs one notch tighter here
   than on a console page, because these cards are 384px and 300px wide rather than half a screen;
   the tightening is scoped to .ns-dash-columns below rather than forked into new class names. */

/* The switcher card. The left edge is amber only while "Needs you" is the selected stage, which
   is the same rule .ns-panel-attention follows: amber means a human has to act today, so it
   cannot be on the card permanently or it stops meaning anything. */
.ns-stage-card {
  border-left: 3px solid var(--ns-primary);
}

.ns-stage-card.is-blocked {
  border-left-color: var(--ns-warning);
}

/* The billing strip (Shared/_BillingAttention): the app's "Needs attention" card, full bleed above
   the switcher, admins only and absent unless a charge is at risk. The same gap below it as the
   first-run card, which takes the same slot. */
.ns-dash-attention {
  margin-bottom: 14px;
}

.ns-dash-attention-action {
  flex: none;
}

.ns-stage-switch {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  /* Anchors the sliding indicator. */
  position: relative;
}

/* The selection, as one thing that moves. Each tile keeps a transparent bottom rule so the strip's
   height never changes; this rides over it. Placed by dashboard.js — with no script it stays at
   width 0 and .ns-stage.is-active's own rule carries the selection instead. */
.ns-stage-ind {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 2px;
  width: 0;
  background: var(--ns-primary);
  transform: translateX(0);
  /* Enter easing: it settles into the new tile rather than arriving at a constant rate. */
  transition: transform .2s cubic-bezier(.2, .7, .2, 1),
              width .2s cubic-bezier(.2, .7, .2, 1),
              background .16s ease;
}

.ns-stage-card.is-blocked .ns-stage-ind {
  background: var(--ns-warning);
}

/* Only the selected stage's panel is in flow; the rest are `hidden`, which also keeps them out of
   the tab order and off the accessibility tree. The wrapper animates its own height on a swap, so
   the card grows or shrinks into the new panel instead of jumping. */
.ns-stage-panel[hidden] {
  display: none;
}

/* The incoming panel fades up a few pixels — a content swap, so 4–8px per the motion vocabulary,
   not a slide across the card. */
.ns-stage-panel.is-entering {
  animation: ns-stage-in .19s ease both;
}

@keyframes ns-stage-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* Measured and driven by dashboard.js, then released back to auto so the card keeps sizing itself. */
.ns-stage-body.is-resizing {
  overflow: hidden;
  transition: height .2s cubic-bezier(.2, .7, .2, 1);
}

/* A stage tile. An anchor, not a button: ?stage= survives a reload, a middle-click and a
   bookmark, and the five lists are different enough shapes that rendering one server-side beats
   shipping all five and hiding four. */
.ns-stage {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px 18px 12px;
  border: 0;
  border-left: 1px solid var(--ns-divider);
  border-bottom: 2px solid transparent;
  background: var(--ns-surface-quiet);
  text-align: left;
  /* Background only. Everything else about a tile changes with .is-active, and selecting a stage
     is a navigation — there is no old document left to transition from. */
  transition: background .13s ease;
}

.ns-stage:first-child {
  border-left: 0;
}

/* An unselected tile lightens toward the selected tile's white rather than tinting: the strip is
   a group where one is chosen, so hover previews the choice instead of inventing a third state.
   The label darkens with it — an inactive tab that only changes its background is the hover state
   the tabs doc calls mandatory and easy to miss. */
.ns-stage:hover {
  background: var(--ns-surface);
  text-decoration: none;
}

.ns-stage:hover .ns-stage-label {
  color: var(--ns-ink);
}

/* The ring sits inside the tile — an outline on a grid cell with a 1px rule beside it gets clipped
   by the card's overflow:hidden, and the outermost tiles would lose the outer half of theirs. */
.ns-stage:focus-visible {
  outline: 0;
  box-shadow: inset var(--ns-focus-ring);
  background: var(--ns-surface);
}

.ns-stage.is-active {
  background: var(--ns-surface);
  border-bottom-color: var(--ns-primary);
}

.ns-stage-card.is-blocked .ns-stage.is-active {
  border-bottom-color: var(--ns-warning);
}

.ns-stage-label {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ns-muted);
}

.ns-stage.is-active .ns-stage-label {
  color: var(--ns-ink);
}

.ns-stage-value {
  font-size: 26px;
  font-weight: 800;
  line-height: 1.1;
  color: var(--ns-ink);
}

/* Zero is stated, never hidden — but in the ink of an absence, so four quiet zeros do not read
   as four things to deal with. */
.ns-stage-value.is-zero {
  color: var(--ns-ink-zero);
}

.ns-stage-value.is-warning { color: var(--ns-warning-text); }
.ns-stage-value.is-success { color: var(--ns-success-text); }

.ns-stage-sub {
  font-size: 10.5px;
  color: var(--ns-muted);
}

/* The selected stage's list header. Same recipe as .ns-panel-head, but it divides from the
   switcher above it rather than the rows below, so the rule moves to the top. */
.ns-stage-head {
  padding: 11px 16px;
  border-top: 1px solid var(--ns-divider);
  border-bottom: 0;
}

/* A row in the selected stage's list: severity dot, name over its one line of detail, the state
   as a pill, and the one action that moves it. Four columns so they read down the card. */
.ns-stage-row {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr) 150px 132px;
  gap: 12px;
  align-items: center;
  padding: 11px 16px;
  border-top: 1px solid var(--ns-divider);
}

.ns-stage-row-action {
  text-align: right;
}

/* The stage header carries its rule on top, so the empty state below it needs its own — and a
   little more air than a card's empty state, because here it is the whole point of the panel. */
.ns-stage-card .ns-panel-empty {
  padding: 30px 16px;
  border-top: 1px solid var(--ns-divider);
}

/* One dot per recipient, filled as each signs — the progress of an envelope at a glance,
   without a bar that would imply a percentage nobody measured. */
.ns-sig-dots {
  display: flex;
  gap: 3px;
  flex: none;
}

.ns-sig-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ns-border-strong);
}

.ns-sig-dot.is-signed {
  background: var(--ns-primary);
}

/* ---- the bands ----
   A band is a destination and the cards that open it. The header is the whole contract:
   well + what this area is + one line on why you would go there + the link, with a hairline
   filling whatever is left so two bands read as two shelves rather than two headings.

   The band is what makes "where does this card go" answerable without taste. Adding an area to
   the nav adds a band; a card goes under the band it links to. There is no second question. */
.ns-dash-band {
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* The feature hue for everything inside. Envelopes is the default because it is the biggest area
     and the one a bare band most likely belongs to. */
  --ns-fc: var(--ns-feat-envelopes);
}

.ns-dash-band.f-inperson { --ns-fc: var(--ns-feat-inperson); }
.ns-dash-band.f-templates { --ns-fc: var(--ns-feat-templates); }
.ns-dash-band.f-walkin { --ns-fc: var(--ns-feat-walkin); }

/* Solid rather than tinted, and the one place a feature hue is stated at full strength. A card's
   well is a tint, so the two never compete: the band names the area, the cards under it say what
   they hold. */
.ns-dash-band-head > .ns-icon-well.band {
  background: var(--ns-fc);
  color: #fff;
}

/* The hairline picks up the hue at low alpha, which is what carries the colour across the width of
   the shelf rather than leaving it in one 26px square. */
.ns-dash-band .ns-dash-band-rule {
  background: color-mix(in srgb, var(--ns-fc) 30%, var(--ns-border));
}

/* The band's link goes to the feature's own area, so it reads in the feature's own colour. */
.ns-dash-band-head > .ns-panel-link {
  color: var(--ns-fc);
}

.ns-dash-band-head > .ns-panel-link:hover {
  color: color-mix(in srgb, var(--ns-fc) 82%, #000);
}

/* A row of one-card features side by side. Templates, Walk-In and In-person each own a band — they
   are separate areas with separate destinations — but each has a single card, and three full-width
   shelves holding one card apiece is mostly empty shelf. Sitting them in one row keeps every card
   at the same 384px the Envelopes band uses and gives each feature its own header. */
.ns-dash-band-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  align-items: start;
}

.ns-dash-band-row > .ns-dash-band > .ns-dash-band-grid {
  grid-template-columns: minmax(0, 1fr);
}

/* Inside a narrow band the note has nowhere to go — the label and the link are the whole header. */
.ns-dash-band-row .ns-dash-band-note {
  display: none;
}

.ns-dash-band-head {
  display: flex;
  align-items: center;
  gap: 12px;
  /* Held at the well's own height rather than left intrinsic, so the rail's mirrored heading
     opposite sits on the same line as the first band header beside it. */
  min-height: 26px;
}

.ns-dash-band-label {
  flex: none;
  font-size: 13.5px;
  font-weight: 800;
  letter-spacing: -.01em;
  color: var(--ns-ink);
}

.ns-dash-band-note {
  flex: none;
  font-size: 11.5px;
  color: var(--ns-muted);
}

.ns-dash-band-rule {
  flex: 1;
  height: 1px;
  background: var(--ns-border);
}

.ns-dash-band-grid {
  display: grid;
  gap: 14px;
  align-items: start;
}

/* Fixed thirds rather than `auto-fit`, so a card is the same width in every band and the page
   reads as one set of shelves. Under auto-fit a band holding one card stretched it to the full
   column width while its neighbours' cards sat at a third. A short row leaves the tail of the
   shelf empty, which is the honest picture: that area has fewer places to go. */
.ns-dash-band-grid.is-thirds {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* ---- the two columns ----
   Both fixed, both pushed to a margin, with the leftover width falling in the gap between them
   (≈294px at a 1904px window). That gap is doing the work a heading would otherwise do: it says
   the right-hand column is a different kind of thing without labelling either one. So neither
   column may flex and the pair is never centred as a block — above the breakpoints below. */
.ns-dash-columns {
  display: flex;
  gap: 24px;
  align-items: flex-start;
  justify-content: space-between;
  /* Owned here rather than as margin-bottom on whatever precedes it — that used to be
     .ns-stage-card, which only renders when the account has a pipeline. On an empty account the
     "No envelopes yet" invitation card stands in for it instead, and the two never both carried
     the same margin, so the columns sat flush against the invitation with no gap at all. */
  margin-top: 22px;
}

.ns-dash-work {
  width: 1180px;
  flex: none;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 22px;
}

.ns-dash-rail {
  width: 300px;
  flex: none;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* Deliberately mirrored: the hairline comes first so the label lands against the right margin,
   which is the edge the rail is pinned to. Height matches .ns-dash-band-head opposite.
   The label itself is a plain .ns-eyebrow — same 11px/700 uppercase the rest of the app uses —
   so only its flex behaviour is set here, and the hairline takes all the slack. */
.ns-dash-rail-head {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 26px;
}

.ns-dash-rail-head > .ns-eyebrow {
  flex: none;
}

/* A signer-only account has no work bands — every one of them needs send permission — so there is
   no left column to sit beside. The rail takes the width and lays its cards out as the same
   reference grid it uses below the two-column breakpoint, rather than leaving 1180px of nothing. */
.ns-dash-columns.is-rail-only .ns-dash-rail {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  align-items: start;
}

.ns-dash-columns.is-rail-only .ns-dash-rail-head {
  grid-column: 1 / -1;
}

/* ---- card density inside the columns ----
   The app-wide recipe tuned for a 384px or 300px card rather than half a console page. Scoped to
   the container instead of forked into new class names, so a card dropped into either column is
   still a .ns-card + .ns-panel-head and picks this up by being where it is. */
.ns-dash-columns .ns-panel-head {
  padding: 11px 14px;
}

.ns-dash-columns .ns-panel-title {
  font-size: 12.5px;
}

.ns-dash-columns .ns-panel-link {
  font-size: 11px;
}

.ns-dash-columns .ns-panel-row {
  gap: 10px;
  padding: 10px 14px;
}

.ns-dash-columns .ns-panel-row-title {
  font-size: 12.25px;
}

.ns-dash-columns .ns-panel-row-sub {
  font-size: 10.5px;
}

.ns-dash-columns .ns-panel-body {
  padding: 14px;
  border-top: 1px solid var(--ns-divider);
}

/* A row that *is* the link gets a hover fill and a focus ring, matching .ns-row-link. */
.ns-dash-columns a.ns-panel-row {
  color: inherit;
  transition: background .13s ease;
}

.ns-dash-columns a.ns-panel-row:hover {
  background: var(--ns-thead-bg);
  color: inherit;
  text-decoration: none;
}

.ns-dash-columns a.ns-panel-row:focus-visible {
  outline: 0;
  background: var(--ns-thead-bg);
  box-shadow: inset var(--ns-focus-ring);
}

/* ---- whole-row links ----
   Any row standing for something with a destination is clickable across its whole width, not just
   on the words. Where the row also carries its own control the row cannot *be* an anchor — a button
   may not nest inside one — so the row's title is the anchor and Bootstrap's .stretched-link pulls
   its ::after over the row. Keyboard and screen readers get one properly-named link; the pointer
   gets the whole row. Any trailing control is lifted above the overlay so it still wins a click.

   The hover fill is keyed off :has(), so a row only lights up when something in it is actually a
   target. A row that reacts to the pointer and then does nothing is worse than one that sits still. */
.ns-stage-row,
.ns-dash-columns .ns-panel-row,
.ns-dash-rail .ns-dash-rail-row {
  position: relative;
}

.ns-stage-row:has(.stretched-link):hover,
.ns-dash-columns .ns-panel-row:has(.stretched-link):hover,
.ns-dash-rail .ns-dash-rail-row:has(.stretched-link):hover {
  background: var(--ns-thead-bg);
}

.ns-stage-row:has(.stretched-link),
.ns-dash-columns .ns-panel-row:has(.stretched-link),
.ns-dash-rail .ns-dash-rail-row:has(.stretched-link) {
  transition: background .13s ease;
}

/* The overlay covers the row, so the real controls need to sit over it. */
.ns-stage-row-action,
.ns-stage-row-badge,
.ns-stage-row .ns-pill,
.ns-dash-columns .ns-panel-row > .ns-pill,
.ns-dash-columns .ns-panel-row .btn {
  position: relative;
  z-index: 2;
}

/* A stretched title keeps the row's ink rather than turning link-blue — the row is the affordance,
   and five blue titles in a card would read as a link list rather than a set of records. */
.ns-stage-row a.stretched-link,
.ns-dash-columns a.stretched-link,
.ns-dash-rail a.stretched-link {
  color: inherit;
}

.ns-stage-row a.stretched-link:hover,
.ns-dash-columns a.stretched-link:hover,
.ns-dash-rail a.stretched-link:hover {
  color: inherit;
  text-decoration: none;
}

/* The ring goes on the row, not on the text, because the row is what the click target actually is. */
.ns-stage-row:has(.stretched-link:focus-visible),
.ns-dash-columns .ns-panel-row:has(.stretched-link:focus-visible),
.ns-dash-rail .ns-dash-rail-row:has(.stretched-link:focus-visible) {
  background: var(--ns-thead-bg);
  box-shadow: inset var(--ns-focus-ring);
}

.ns-stage-row a.stretched-link:focus-visible,
.ns-dash-columns a.stretched-link:focus-visible,
.ns-dash-rail a.stretched-link:focus-visible {
  outline: 0;
}

.ns-dash-columns .ns-panel-foot {
  padding: 9px 14px;
  font-size: 11px;
}

/* The rail's own footer runs a half-step smaller again: at 300px these carry the longest
   sentences on the page, so they need the extra leading more than the extra size. */
.ns-dash-rail .ns-panel-foot {
  font-size: 10.5px;
  line-height: 1.45;
}

/* ---- the rail's three row types ----
   A rail card is 300px wide, so it cannot use the work column's row: there is no room for a
   title, a sub, a badge and an action side by side. Three narrower shapes cover everything the
   rail holds — an event that wraps, a measured bar, and a labelled value. */
.ns-dash-rail-row {
  padding: 10px 14px;
}

.ns-dash-rail-row + .ns-dash-rail-row {
  border-top: 1px solid var(--ns-divider);
}

/* An event. The title wraps rather than truncating — a notification cut to one line at 300px
   loses the half that says what happened. */
.ns-dash-rail-row.is-activity {
  display: flex;
  align-items: flex-start;
  gap: 9px;
}

.ns-dash-rail-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  flex: none;
  /* Sits on the first line's optical centre, not the top of the text box. */
  margin-top: 5px;
  background: var(--ns-muted);
}

.ns-dash-rail-title {
  font-size: 11.75px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--ns-ink);
}

.ns-dash-rail-sub {
  font-size: 10.5px;
  color: var(--ns-muted);
  margin-top: 1px;
}

/* A labelled value: the rail's workhorse, for a fact with no bar behind it. */
.ns-dash-rail-row.is-pair {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
}

.ns-dash-rail-label {
  flex: 1;
  min-width: 0;
  font-size: 11.75px;
  font-weight: 600;
  color: var(--ns-ink);
}

.ns-dash-rail-value {
  flex: none;
  font-size: 11px;
  font-weight: 600;
  color: var(--ns-text-700);
}

.ns-dash-rail-value.is-success {
  color: var(--ns-success-text);
}

.ns-dash-rail-value.is-zero {
  color: var(--ns-muted);
}

/* A second, smaller fact riding beside a pair row's value - the retention window's due count next
   to the window itself. Its own tone, because the window is a neutral fact and what it is about to
   destroy may not be. Words carry the meaning ("none due" / "3 due"); the tone reinforces. */
.ns-dash-rail-aside {
  font-weight: 700;
}

.ns-dash-rail-aside.is-zero {
  color: var(--ns-muted);
}

.ns-dash-rail-aside.is-warning {
  color: var(--ns-warning-text);
}

/* A meter. Only ever drawn where both halves are real: a counted numerator and a ceiling that
   exists. See "never fake a metric" in docs/design/polish-pass.md — an unmetered cap is a sentence in
   the footer, never a bar sitting at some invented width. */
.ns-meter-head {
  display: flex;
  align-items: center;
  gap: 10px;
}

.ns-meter-track {
  height: 4px;
  border-radius: 999px;
  background: var(--ns-divider);
  margin-top: 7px;
  position: relative;
  overflow: hidden;
}

/* No transition: the width is stamped server-side and never changes at runtime, so a transition
   here would be a declaration that can never fire. Give one to whatever first updates a meter
   live — width is not a property this vocabulary animates for movement, so that would be a
   `transform: scaleX()` on a full-width fill. */
.ns-meter-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  border-radius: 999px;
  background: var(--ns-primary);
}

/* Four-fifths of the ceiling and the bar stops being information and starts being a warning. */
.ns-meter-fill.is-near {
  background: var(--ns-warning);
}

/* The page head's action group. These carry their own layout rather than Bootstrap's
   d-inline-flex utility, because that utility is `display: inline-flex !important` — with it on
   the button, the phone breakpoint below cannot hide or widen anything, and the three buttons
   push the page into a horizontal scroll at 390px. */
.ns-dash-actions .btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* ---- the dashboard at four widths ----
   The layout is fixed-width by design, so it has to degrade on purpose rather than by accident.
   1180 + 24 + 300 = 1504px of content, and .ns-content adds 24px of side padding, so the shell
   needs ≈1552px before the fixed pair is honest. Below that it gives up one property at a time:
   first the work column's fixed width, then the two-column split itself, then every grid.

   The 56px nav rail is identical from 576px up; below that it leaves the flow and becomes the
   top bar's drawer (see the .ns-rail media block by .ns-nav-backdrop) — the destinations are
   still one tap away, they just stop costing a phone 56px of every page. */

/* The work column flexes; the rail stays exactly 300px, because a reference column that grows
   is just a second work column. justify-content stops mattering once the pair fills the row. */
@media (max-width: 1551px) {
  .ns-dash-work {
    width: auto;
    flex: 1;
  }
}

/* One column. The rail's cards do not simply stack — four of them in a row down a full-width page
   is a very long page — so they become their own auto-fit grid beneath the work bands, still in
   rail order and still reading as reference. */
@media (max-width: 1199px) {
  .ns-dash-columns {
    flex-direction: column;
  }

  .ns-dash-work,
  .ns-dash-rail {
    width: 100%;
    flex: none;
  }

  .ns-dash-rail {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    align-items: start;
  }

  /* The mirrored heading spans the grid so the hairline still runs the width of the section it
     labels rather than the width of one card. */
  .ns-dash-rail-head {
    grid-column: 1 / -1;
  }

  /* Three cards to two: a third of a tablet is narrower than the card's own title. The single-card
     bands step with them, or their cards end up narrower than the Envelopes band's and the page
     stops reading as one set of shelves. */
  .ns-dash-band-grid.is-thirds,
  .ns-dash-band-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .ns-stage-switch {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  /* Wrapped tiles: the sliding indicator is glued to the strip's bottom edge, which is no
     longer under the active tile — it read as a second, meaningless underline above the list.
     The active tile's own bottom rule carries the selection alone once the strip wraps. */
  .ns-stage-ind {
    display: none;
  }

  /* Row three of the switcher holds two tiles, so the ones above it keep their left rules and
     the wrapped pair does not float against a stray divider. */
  .ns-stage:nth-child(4) {
    border-left: 0;
  }

  /* The badge repeats what the row's sub already says once there is no room for both. */
  .ns-stage-row {
    grid-template-columns: 20px minmax(0, 1fr) 132px;
  }

  .ns-stage-row > .ns-stage-row-badge {
    display: none;
  }
}

@media (max-width: 899px) {
  .ns-dash-band-grid.is-thirds,
  .ns-dash-band-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .ns-dash-rail {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Tiles wrap to a 2×3 grid, so the left rules swap parity to keep each wrapped row clean. */
  .ns-stage-switch {
    grid-template-columns: 1fr 1fr;
  }

  .ns-stage:nth-child(odd) {
    border-left: 0;
  }

  .ns-stage:nth-child(even) {
    border-left: 1px solid var(--ns-divider);
  }
}

/* Phone. The 900px step above is still a narrow tablet; these are the treatments that only earn
   their keep once a row cannot hold a document name and a button side by side. */
@media (max-width: 680px) {
  /* The note is the first thing to go: the band label and its link carry the meaning, and the
     explanatory line pushes the hairline off the row at 390px. */
  .ns-dash-band-note {
    display: none;
  }

  /* The action drops to its own line beneath the name. Sharing one row does not survive a phone:
     at 375px "Design & send" claims 106px of 375 and leaves the document name about eight
     characters, which is not a name. Both still fit without the page scrolling sideways. */
  .ns-stage-row {
    grid-template-columns: 20px minmax(0, 1fr);
    gap: 8px 10px;
  }

  .ns-stage-row > .ns-stage-row-action {
    grid-column: 2;
    text-align: left;
  }

  /* Touch: the primary action spans the width and clears 44px. The two named shortcuts beside
     it go — three buttons on one line is what pushed the page into a horizontal scroll at
     390px, and both are reachable from the bands below, so nothing becomes unreachable.
     Both selectors are scoped through .ns-dash-actions deliberately: they have to outrank
     `.ns-dash-actions .btn` above, and a bare `.ns-dash-secondary` silently loses to it. */
  .ns-dash-actions {
    width: 100%;
  }

  .ns-dash-actions .ns-dash-secondary {
    display: none;
  }

  .ns-dash-actions .ns-dash-cta {
    display: flex;
    width: 100%;
    justify-content: center;
    min-height: 44px;
  }

  /* The billing strip's button drops beneath the sentence and spans the width, like a stage row's
     action: beside it, "Mettre à jour la carte" leaves the explanation a word per line. */
  .ns-dash-attention .ns-panel-row {
    flex-wrap: wrap;
  }

  /* A zero basis keeps the sentence beside its dot: sized by its content, it is wider than the
     row and wraps under the dot, leaving the dot alone on a line of its own. */
  .ns-dash-attention-text {
    flex: 1 1 0;
  }

  .ns-dash-attention-action {
    flex: 1 0 100%;
  }

  .ns-dash-attention-action .btn {
    width: 100%;
    min-height: 44px;
  }
}

/* The counts in a destructive confirmation. States the consequence in numbers, not policy. */
.ns-danger-well {
  background: var(--ns-danger-50);
  border: 1px solid var(--ns-danger-border);
  border-radius: var(--ns-radius-control);
  padding: 11px 13px;
  color: var(--ns-danger-text);
}

/* ---- the search palette ---- */

.ns-setfind-backdrop {
  position: fixed;
  inset: 0;
  z-index: 2100;
  background: rgba(10, 30, 55, .42);
  display: flex;
  justify-content: center;
  /* flex-start, not the default stretch: without this the panel grows to the full viewport and
     a handful of results sit at the top of a tall empty white box. */
  align-items: flex-start;
  padding: 64px 16px 16px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .16s ease;
}

.ns-setfind-backdrop.show {
  opacity: 1;
  pointer-events: auto;
}

.ns-setfind-panel {
  width: 100%;
  max-width: 560px;
  max-height: calc(100vh - 96px);
  display: flex;
  flex-direction: column;
  background: var(--ns-surface);
  border-radius: var(--ns-radius-panel);
  box-shadow: var(--ns-shadow-modal);
  overflow: hidden;
  transform: translateY(-8px);
  transition: transform .16s ease;
}

.ns-setfind-backdrop.show .ns-setfind-panel {
  transform: none;
}

.ns-setfind-search {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--ns-divider);
  flex: none;
}

.ns-setfind-search > svg {
  color: var(--ns-muted);
  flex: none;
}

.ns-setfind-input {
  flex: 1;
  min-width: 0;
  border: 0;
  padding: 0;
  font-size: 14px;
  color: var(--ns-ink);
  background: transparent;
}

.ns-setfind-input:focus {
  outline: none;
}

.ns-setfind-list {
  overflow-y: auto;
  padding-bottom: 4px;
}

.ns-setfind-group {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ns-muted);
  padding: 12px 16px 5px;
}

.ns-setfind-row {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 9px 16px;
  border: 0;
  background: transparent;
  text-align: left;
}

.ns-setfind-row:hover,
.ns-setfind-row.is-highlighted {
  background: var(--ns-divider);
}

.ns-setfind-icon {
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--ns-divider);
  color: var(--ns-slate);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.ns-setfind-text {
  flex: 1;
  min-width: 0;
}

.ns-setfind-name {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ns-ink);
}

.ns-setfind-path {
  display: block;
  font-size: 11px;
  color: var(--ns-muted);
}

.ns-setfind-empty {
  padding: 26px 16px;
  text-align: center;
}

.ns-setfind-foot {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 16px;
  border-top: 1px solid var(--ns-divider);
  background: var(--ns-thead-bg);
  flex: none;
}

.ns-setfind-hint {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--ns-muted);
}

.ns-setfind-hint kbd {
  font-family: var(--ns-font-mono);
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: 4px;
  color: var(--ns-slate);
  padding: 1px 5px;
}

/* The row the search sent you to, so the eye lands where focus did. */
.ns-set-row.is-targeted {
  animation: ns-set-target 2.4s ease-out;
}

@keyframes ns-set-target {
  0%, 45% { background: var(--ns-primary-tint); }
  100% { background: transparent; }
}

/* ---- loading ----
   A panel reserves its height before it knows its content: row height, column widths and
   the header are final, only the text is a block. */

.ns-set-skel {
  border-radius: 4px;
  background: var(--ns-divider);
  display: block;
  height: 10px;
}

.ns-set-skel-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 28px;
  align-items: start;
  padding: 15px 18px;
  min-height: 76px;
}

.ns-set-skel-row + .ns-set-skel-row {
  border-top: 1px solid var(--ns-divider);
}

/* ---- narrow ---- */

@media (max-width: 1100px) {
  .ns-set {
    display: block;
    margin: 0;
    min-height: 0;
  }

  .ns-cat-rail {
    display: none;
  }

  .ns-cat-select {
    display: block;
    margin-bottom: 14px;
  }

  .ns-set-body {
    padding: 0 0 92px;
  }

  /* Label and description above, control below at full width. The 300px column is what
     makes the two-column row work; below that it only squeezes the text. */
  .ns-set-row,
  .ns-set-skel-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 10px;
  }

  /* On touch the control itself has to clear 44px, so the switch grows. */
  .ns-switch > input {
    width: 44px;
    height: 26px;
  }

  .ns-switch > input::after {
    width: 20px;
    height: 20px;
    top: 3px;
    left: 3px;
  }

  .ns-switch > input:checked::after {
    transform: translateX(18px);
  }

  .ns-savebar {
    left: 16px;
    right: 16px;
    bottom: 16px;
  }

  .ns-savebar-list {
    display: none;
  }
}

@media (max-width: 640px) {
  .ns-savebar-actions .btn-ghost {
    display: none;
  }
}

/* ---------- designer v2: mobile-first step flow ----------
   One layout scales phone ? tablet ? desktop. A compact breadcrumb sits on top at every
   size (never a left rail); each step renders in its own panel gated by [data-step] on
   the designer root. The Fields step is a grid: doc tabs on top, the PDF canvas in the
   middle, and the tray (Placing for + field types) at the bottom on phones or as a left
   rail from 768px, with the placed-fields list as a bottom sheet (phone) / right panel. */

/* The step crumbs live INSIDE the navy bar (no second strip): left group shrinks,
   the crumb strip flexes and scrolls, the action cluster stays put on the right. */

.ns-dz-bar-left {
  flex: 0 1 auto;
  min-width: 56px;
}

.ns-dz-crumbstrip {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: safe center;
  gap: 2px;
  overflow-x: auto;
  scrollbar-width: none;
}

.ns-dz-crumbstrip::-webkit-scrollbar {
  display: none;
}

.ns-dz-bar-divider {
  width: 1px;
  height: 20px;
  background: rgba(255, 255, 255, .25);
  flex: none;
}

.ns-dz-crumb {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 0;
  background: transparent;
  border-radius: 20px;
  padding: 4px 10px 4px 4px;
  cursor: pointer;
  font-family: inherit;
  transition: background .15s ease;
}

.ns-dz-crumb:hover:not(.active) {
  background: rgba(255, 255, 255, .08);
}

.ns-dz-crumb-dot,
.ns-dz-crumb-label {
  transition: background .18s ease, border-color .18s ease, color .18s ease;
}

.ns-dz-crumb-dot {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .08);
  border: 1.5px solid rgba(255, 255, 255, .28);
  color: #9DC7E0;
  font-size: 10.5px;
  font-weight: 800;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.ns-dz-crumb-check {
  display: none;
}

.ns-dz-crumb-label {
  font-size: 12px;
  font-weight: 600;
  color: #9DC7E0;
  white-space: nowrap;
}

.ns-dz-crumb.active {
  background: rgba(255, 255, 255, .12);
}

.ns-dz-crumb.active .ns-dz-crumb-dot {
  background: #fff;
  border-color: #fff;
  color: var(--ns-navy-900);
}

.ns-dz-crumb.active .ns-dz-crumb-label {
  color: #fff;
  font-weight: 800;
}

.ns-dz-crumb.done .ns-dz-crumb-dot {
  background: var(--ns-success);
  border-color: var(--ns-success);
  color: #fff;
}

.ns-dz-crumb.done .ns-dz-crumb-n {
  display: none;
}

.ns-dz-crumb.done .ns-dz-crumb-check {
  display: block;
}

.ns-dz-crumb.done .ns-dz-crumb-label {
  color: #B8D3E6;
}

.ns-dz-sep {
  flex: none;
  color: rgba(255, 255, 255, .3);
  font-size: 12px;
}

/* Up to a portrait tablet the bar is carrying crumbs, saves and Next in one row, and the
   full crumb labels are what tips it into a scroll. Only the current step keeps its name —
   the done/todo steps stay reachable as numbered dots, the standard compact stepper. A
   landscape tablet (≥992px) has the room for all three names. */
@media (max-width: 991.98px) {
  .ns-dz-crumb:not(.active) .ns-dz-crumb-label {
    display: none;
  }
}

/* On a phone the dots carry the whole stepper: every label goes (the numbered/checked dots
   remain, with their titles), and the separators between bare dots are noise too. */
@media (max-width: 575.98px) {
  .ns-dz-crumb .ns-dz-crumb-label,
  .ns-dz-sep {
    display: none;
  }
}

/* step panels - one visible at a time, driven by data-step on the designer root */

.ns-dz-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* the step slide animations translate sideways - never let that spawn a
     horizontal scrollbar that makes the whole page jitter */
  overflow-x: hidden;
}

.ns-dz-step {
  display: none;
  /* A flex child: without this, content wider than the viewport (a document page rendered
     wider than the canvas) sets the step's automatic minimum and the whole designer lays
     out at the page's width — clipped by .ns-dz-body's overflow-x with everything past the
     right edge (the zoom controls, half the tray) unreachable. Wide pages must scroll
     inside .ns-dz-canvas instead. */
  min-width: 0;
}

.ns-designer[data-step="docs"] .ns-dz-step[data-step-panel="docs"],
.ns-designer[data-step="recipients"] .ns-dz-step[data-step-panel="recipients"],
.ns-designer[data-step="review"] .ns-dz-step[data-step-panel="review"] {
  display: block;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-gutter: stable both-edges; /* the appearing scrollbar must not shift the content */
  padding: 20px 16px 28px;
}

.ns-dz-col {
  max-width: 460px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ns-dz-h {
  font-size: 18px;
  font-weight: 800;
  margin: 0;
}

.ns-dz-eyebrow {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ns-muted);
}

.ns-dz-hint {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ns-muted);
}

.ns-dz-cta {
  padding: 12px;
  font-weight: 800;
  border-radius: 12px;
  margin-top: 4px;
}

.ns-dz-add {
  border: 1.5px dashed var(--ns-border-strong);
  background: #fff;
  border-radius: 13px;
  padding: 12px;
  color: var(--ns-blue-600);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  font-family: inherit;
  transition: border-color .16s ease, background .16s ease;
}

.ns-dz-add:hover {
  border-color: var(--ns-blue-600);
  background: var(--ns-blue-50);
}

.ns-dz-disabled {
  pointer-events: none;
  opacity: .45;
}

/* documents step */

.ns-dz-upload {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  border: 2px dashed var(--ns-border-strong);
  background: #fff;
  border-radius: var(--ns-radius-panel);
  padding: 26px 16px;
  cursor: pointer;
  text-align: center;
  margin: 0;
  transition: border-color .16s ease, background .16s ease;
}

.ns-dz-upload:hover,
.ns-dz-upload.over {
  border-color: var(--ns-blue-600);
  background: var(--ns-blue-50);
}

.ns-dz-upload > b {
  font-size: 13.5px;
  font-weight: 800;
  color: var(--ns-blue-600);
}

.ns-dz-upload-sub {
  font-size: 11px;
  color: var(--ns-muted);
}

.ns-dz-upload-ic {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--ns-blue-600) 12%, #fff);
  color: var(--ns-blue-600);
  display: flex;
  align-items: center;
  justify-content: center;
}

.ns-dz-docrow {
  display: flex;
  align-items: center;
  gap: 11px;
  border: 1px solid var(--ns-border);
  background: #fff;
  border-radius: 13px;
  padding: 11px 12px;
  /* grid items default to min-width:auto, so an unbroken long filename would
     otherwise force this row (and its grid track) wider than the column. */
  min-width: 0;
  max-width: 100%;
}

.ns-dz-docrow-ic {
  flex: none;
  width: 36px;
  height: 44px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--ns-blue-600) 9%, #fff);
  border: 1px solid var(--ns-info-border-soft);
  color: var(--ns-blue-600);
  display: flex;
  align-items: center;
  justify-content: center;
}

.ns-dz-docrow-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.ns-dz-docrow-name {
  font-size: 13px;
  font-weight: 700;
}

.ns-dz-docrow-meta {
  font-size: 10.5px;
  color: var(--ns-muted);
}

.ns-dz-docrow-remove {
  flex: none;
  width: 30px;
  height: 30px;
  border: 0;
  background: transparent;
  color: var(--ns-muted);
  font-size: 18px;
  cursor: pointer;
  border-radius: 8px;
}

.ns-dz-docrow-remove:hover {
  color: var(--ns-maple);
}

/* Language variants: one card per form, its language copies listed inside. This step is where copies
   are uploaded, tagged and removed, so each needs its own row and controls; switching between them
   to mark them up happens on the Fields step's document bar instead. A form with no translation is a
   single row and looks exactly as it always did - the grouping only shows once there's a second
   language. */

.ns-dz-docgroup {
  display: grid;
  gap: 6px;
  min-width: 0;
}

/* Variants indent under the head row and lighten their border, so a translated form reads as one
   document with copies rather than two unrelated uploads. */
.ns-dz-docrow-variant {
  border-color: color-mix(in srgb, var(--ns-border) 70%, #fff);
}

.ns-dz-docgroup .ns-dz-docrow-variant + .ns-dz-docrow-variant {
  margin-left: 18px;
}

.ns-dz-doclang {
  flex: none;
  /* Wide enough for the longest option ("All languages") plus the caret - at 132px the label
     ran under the arrow. */
  max-width: 160px;
}

.ns-dz-doclang select {
  font-size: 11px;
  padding: 3px 26px 3px 8px;
  border-radius: 8px;
}

.ns-dz-addtranslation {
  justify-self: start;
  /* Lines up with the indented variant rows above it. */
  margin-left: 18px;
  border: 1px dashed var(--ns-border);
  background: transparent;
  color: var(--ns-blue-600);
  border-radius: 9px;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
}

.ns-dz-addtranslation:hover {
  border-style: solid;
  background: color-mix(in srgb, var(--ns-blue-600) 6%, #fff);
}

/* The language chip on a document-bar entry: a real button that swaps which language copy of that
   form the canvas shows. It names the copy you're LOOKING AT - while placing fields you have to be
   certain which copy you're marking up - and the swap glyph plus the tooltip carry "and this is how
   you change it".

   It has to look pressable AT REST, not only on hover: it sits in the shape of a tag, inside a
   clickable entry, so without a standing outline and the glyph it reads as a label. Touch has no
   hover to discover it with at all. */
.ns-dz-tab-lang {
  flex: none;
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  border: 1px solid color-mix(in srgb, var(--ns-blue-600) 45%, #fff);
  font-family: inherit;
  font-size: 9.5px;
  font-weight: 800;
  letter-spacing: .02em;
  padding: 2px 5px 2px 6px;
  border-radius: 999px;
  background: #fff;
  color: var(--ns-blue-600);
  white-space: nowrap;
  cursor: pointer;
  box-shadow: 0 1px 1.5px rgba(16, 42, 67, .10);
  transition: background-color .12s ease, border-color .12s ease, box-shadow .12s ease, transform .08s ease;
}

/* The glyph is the quiet half of the pair - the code is what you read. */
.ns-dz-tab-lang svg {
  opacity: .75;
}

.ns-dz-tab-lang:hover {
  background: color-mix(in srgb, var(--ns-blue-600) 12%, #fff);
  border-color: var(--ns-blue-600);
  box-shadow: 0 1px 3px rgba(16, 42, 67, .16);
}

.ns-dz-tab-lang:hover svg {
  opacity: 1;
}

/* A real press, so a tap on a phone confirms itself. */
.ns-dz-tab-lang:active {
  transform: translateY(1px);
  box-shadow: none;
  background: color-mix(in srgb, var(--ns-blue-600) 18%, #fff);
}

.ns-dz-tab-lang:focus-visible {
  outline: 2px solid var(--ns-blue-600);
  outline-offset: 1px;
}

@media (prefers-reduced-motion: reduce) {
  .ns-dz-tab-lang {
    transition: none;
  }

  .ns-dz-tab-lang:active {
    transform: none;
  }
}

/* Language choices in the in-house dialog (nsChoose) - stacked buttons, not a select, so each
   language is one tap on a phone. */
/* A choice dialog's options are stacked cards, each free to explain itself on a second
   line — the destructive one wears the maple accent so the two never read alike. */
.ns-dz-modal-choices {
  display: grid;
  gap: 8px;
}

.ns-dz-modal-choice {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--ns-border);
  border-radius: 10px;
  background: #fff;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color .13s ease, background .13s ease, box-shadow .13s ease;
}

.ns-dz-modal-choice:hover,
.ns-dz-modal-choice:focus-visible {
  border-color: var(--ns-blue-600);
  background: color-mix(in srgb, var(--ns-blue-600) 5%, #fff);
  outline: none;
  box-shadow: 0 2px 8px rgba(16, 40, 70, .1);
}

.ns-dz-modal-choice.is-danger:hover,
.ns-dz-modal-choice.is-danger:focus-visible {
  border-color: var(--ns-maple);
  background: rgba(197, 48, 48, .05);
}

.ns-dz-modal-choice-label {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-dz-modal-choice.is-danger .ns-dz-modal-choice-label {
  color: var(--ns-maple);
}

.ns-dz-modal-choice-desc {
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--ns-slate);
}

/* recipients step */

.ns-dz-seq {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding-left: 0;
  margin: 0;
}

.ns-dz-seq .form-check-input {
  flex: none;
  float: none;
  margin: 0;
}

.ns-dz-seq .form-check-label {
  line-height: 1.3;
  font-size: 12px;
}

/* assurance level - sits under the signer list, because Elevated's requirement is about them */

.ns-dz-assurance {
  border: 1px solid var(--ns-border);
  border-radius: 10px;
  background: var(--ns-surface);
  padding: .6rem .7rem;
  display: grid;
  gap: .5rem;
}

.ns-dz-assurance-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
  flex-wrap: wrap;
}

.ns-dz-assurance-title {
  font-size: 12px;
  font-weight: 600;
}

.ns-dz-assurance-frozen {
  font-size: 11px;
  color: var(--ns-muted);
}

.ns-dz-assurance-opts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .4rem;
}

@media (max-width: 480px) {
  .ns-dz-assurance-opts { grid-template-columns: 1fr; }
}

.ns-dz-assurance-opt {
  text-align: left;
  border: 1px solid var(--ns-border);
  border-radius: 8px;
  background: var(--ns-surface);
  padding: .45rem .55rem;
  display: grid;
  gap: .15rem;
  cursor: pointer;
}

.ns-dz-assurance-opt:hover:not(:disabled) { border-color: var(--ns-border-strong); }

.ns-dz-assurance-opt.is-selected {
  border-color: var(--ns-blue-600);
  box-shadow: inset 0 0 0 1px var(--ns-blue-600);
}

.ns-dz-assurance-opt:disabled {
  opacity: .6;
  cursor: not-allowed;
}

.ns-dz-assurance-opt-name {
  font-size: 12px;
  font-weight: 600;
}

.ns-dz-assurance-opt-hint {
  font-size: 11px;
  line-height: 1.35;
  color: var(--ns-muted);
}

/* The Elevated requirement, stated while editing rather than at send. */
.ns-dz-assurance-note {
  font-size: 11px;
  line-height: 1.4;
  border-radius: 8px;
  padding: .4rem .5rem;
  background: var(--ns-blue-50);
  color: var(--ns-ink);
}

.ns-dz-assurance-note.is-warning {
  background: var(--ns-warning-50);
  color: var(--ns-warning-text);
}

.ns-dz-rec {
  --ns-rc: var(--ns-blue-600);
  border: 1px solid var(--ns-border);
  border-left: 4px solid var(--ns-rc);
  background: #fff;
  border-radius: 13px;
  padding: 11px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  cursor: pointer;
}

.ns-dz-rec.active {
  box-shadow: 0 0 0 1px var(--ns-rc);
}

.ns-dz-rec-top {
  display: flex;
  align-items: center;
  gap: 9px;
}

.ns-dz-rec-badge {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--ns-rc);
  color: #fff;
  font-size: 12px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ns-dz-rec-name {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  color: var(--ns-ink);
  padding: 3px 4px;
  border-radius: 6px;
}

.ns-dz-rec-name:focus {
  outline: none;
  background: var(--ns-blue-50);
  box-shadow: var(--ns-focus-ring);
}

/* So the invalid ring (and the focus background) arrive rather than snap on. */
.ns-dz-rec-name {
  transition: background .14s ease, box-shadow .14s ease;
}

.ns-dz-rec-tools {
  flex: none;
  display: flex;
  gap: 1px;
}

.ns-dz-rec-tools button {
  width: 24px;
  height: 24px;
  border: 0;
  background: transparent;
  color: var(--ns-muted);
  cursor: pointer;
  font-size: 13px;
  border-radius: 6px;
  line-height: 1;
  padding: 0;
}

.ns-dz-rec-tools button:hover:not(:disabled) {
  background: var(--ns-divider);
  color: var(--ns-slate);
}

.ns-dz-rec-tools .ns-remove-signer {
  font-size: 16px;
}

.ns-dz-rec-tools .ns-remove-signer:hover:not(:disabled) {
  color: var(--ns-maple);
}

.ns-dz-rec-tools button:disabled {
  opacity: .35;
  cursor: default;
}

.ns-dz-rec-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.ns-dz-seg {
  display: flex;
  gap: 2px;
  background: var(--ns-divider);
  border-radius: 9px;
  padding: 3px;
}

.ns-dz-seg-btn {
  border: 0;
  border-radius: 7px;
  padding: 5px 12px;
  font-size: 11.5px;
  font-weight: 700;
  cursor: pointer;
  background: transparent;
  color: var(--ns-muted);
  font-family: inherit;
  transition: background .15s ease, color .15s ease;
}

.ns-dz-seg-btn:hover:not(.on) {
  color: var(--ns-text-700);
}

.ns-dz-seg-btn.on {
  background: #fff;
  box-shadow: 0 1px 3px rgba(16, 40, 70, .12);
}

/* colour-coded kinds: blue = signs and owns fields, amber = reviews & approves */
.ns-dz-seg-btn.ns-kind-signer.on {
  background: var(--ns-blue-50);
  color: var(--ns-blue-600);
}

.ns-dz-seg-btn.ns-kind-reviewer.on {
  background: var(--ns-warning-50);
  color: var(--ns-warning-text);
}

.ns-dz-pill-toggle {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--ns-border);
  background: #fff;
  border-radius: 20px;
  padding: 5px 8px 5px 12px;
  cursor: pointer;
  font-family: inherit;
  font-size: 11px;
  font-weight: 700;
  color: var(--ns-text-700);
}

.ns-dz-knob {
  position: relative;
  width: 32px;
  height: 18px;
  border-radius: 10px;
  background: var(--ns-border-strong);
  transition: background .16s ease;
  flex: none;
}

.ns-dz-knob::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #fff;
  transition: left .16s ease;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .2);
}

.ns-dz-pill-toggle.on .ns-dz-knob {
  background: var(--ns-success);
}

.ns-dz-pill-toggle.on .ns-dz-knob::after {
  left: 16px;
}

/* fields step - grid: tabs / canvas / tray (phone), tabs / tray+canvas+list (?768px) */

.ns-designer[data-step="fields"] .ns-dz-step-fields {
  display: grid;
  flex: 1;
  min-height: 0;
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas: "tabs" "canvas" "tray";
}

.ns-dz-tabs {
  grid-area: tabs;
  display: flex;
  align-items: stretch;
  background: #fff;
  border-bottom: 1px solid #E9EEF4;
  min-width: 0;
}

.ns-dz-tabstrip {
  flex: 1;
  min-width: 0;
  display: flex;
  overflow-x: auto;
  scrollbar-width: none;
  padding: 0 4px;
}

.ns-dz-tabstrip::-webkit-scrollbar {
  display: none;
}

.ns-dz-tab {
  flex: none;
  max-width: 170px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px 6px;
  border: 0;
  border-bottom: 2.5px solid transparent;
  background: transparent;
  cursor: pointer;
  font-family: inherit;
}

.ns-dz-tab.active {
  border-bottom-color: var(--ns-blue-600);
  background: color-mix(in srgb, var(--ns-blue-600) 5%, #fff);
}

/* Room for the language chip, so a translated form's filename isn't truncated to make space for it.
   Only these entries carry one, so monolingual tabs keep their narrower cap. */
.ns-dz-tab-haslang {
  max-width: 212px;
}

.ns-dz-tab-n {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--ns-divider);
  color: var(--ns-text-700);
  font-size: 10px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  transition: background .18s ease, color .18s ease;
}

.ns-dz-tab.active .ns-dz-tab-n {
  background: var(--ns-blue-600);
  color: #fff;
}

.ns-dz-tab-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.ns-dz-tab-name {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ns-slate);
  max-width: 130px;
  transition: color .18s ease;
}

.ns-dz-tab.active .ns-dz-tab-name {
  color: var(--ns-ink);
  font-weight: 800;
}

.ns-dz-tab-meta {
  font-size: 9.5px;
  color: var(--ns-muted);
  white-space: nowrap;
}

.ns-dz-tab-add {
  flex: none;
  width: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-left: 1px solid var(--ns-divider);
  color: var(--ns-blue-600);
  font-size: 19px;
  cursor: pointer;
  margin: 0;
}

.ns-dz-tab-add:hover {
  background: var(--ns-blue-50);
}

.ns-dz-canvaswrap {
  grid-area: canvas;
  position: relative;
  min-height: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.ns-dz-canvas {
  flex: 1;
  min-height: 0;
  overflow: auto;
  background: var(--ns-canvas-deep);
  padding: 14px 12px 20px;
}

/* The selection rectangle is positioned in this container's own space so it stays pinned to
   the paper across scrolls — and can therefore span pages. */
.ns-dz-canvas > #documents-container {
  position: relative;
}

.ns-dz-armed {
  position: absolute;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  max-width: calc(100% - 20px);
  background: var(--ns-navy-900);
  color: #fff;
  border-radius: 20px;
  padding: 6px 8px 6px 13px;
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 11.5px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .25);
}

.ns-dz-armed[hidden] {
  display: none;
}

.ns-dz-armed button {
  border: 0;
  background: rgba(255, 255, 255, .16);
  color: #fff;
  border-radius: 20px;
  padding: 3px 10px;
  font-size: 10.5px;
  font-weight: 700;
  cursor: pointer;
  flex: none;
  font-family: inherit;
}

.ns-dz-armed-on .ns-page {
  cursor: copy;
}

/* zoom glide: scoped to a transient class so drags and re-renders stay instant */
.ns-dz-zooming .ns-page {
  transition: width .22s cubic-bezier(.2, .7, .2, 1), height .22s cubic-bezier(.2, .7, .2, 1);
}

.ns-dz-zooming .ns-field {
  transition: left .22s cubic-bezier(.2, .7, .2, 1), top .22s cubic-bezier(.2, .7, .2, 1),
    width .22s cubic-bezier(.2, .7, .2, 1), height .22s cubic-bezier(.2, .7, .2, 1);
}

/* the tray: bottom bar on phones, left rail from 768px */

.ns-dz-tray {
  grid-area: tray;
  background: #fff;
  border-top: 1px solid #E9EEF4;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* The one grid item of the fields step without an explicit minimum: left at auto it is
     what let the single phone column grow to an over-wide page's width (see .ns-dz-step). */
  min-width: 0;
}

.ns-dz-tray-tab {
  border: 0;
  background: transparent;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 6px 10px 7px;
  cursor: pointer;
  border-bottom: 1px solid var(--ns-divider);
  font-family: inherit;
}

.ns-dz-grip {
  width: 34px;
  height: 4px;
  border-radius: 3px;
  background: var(--ns-border-strong);
}

.ns-dz-tray-tab-label {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ns-text-700);
}

.ns-dz-count {
  background: var(--ns-blue-600);
  color: #fff;
  border-radius: 20px;
  padding: 1px 7px;
  font-size: 10.5px;
  font-weight: 800;
}

.ns-dz-tray-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 12px calc(10px + env(safe-area-inset-bottom));
}

.ns-dz-tray-sec {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ns-dz-whostrip {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding-bottom: 2px;
  scrollbar-width: none;
}

.ns-dz-whostrip::-webkit-scrollbar {
  display: none;
}

.ns-dz-who {
  --ns-rc: var(--ns-blue-600);
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--ns-border);
  background: #fff;
  border-radius: 20px;
  padding: 3px 10px 3px 3px;
  cursor: pointer;
  font-family: inherit;
}

.ns-dz-who-dot {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--ns-rc);
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.ns-dz-who-name {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ns-slate);
  max-width: 110px;
}

.ns-dz-who.active {
  border-color: var(--ns-rc);
  background: color-mix(in srgb, var(--ns-rc) 10%, #fff);
}

.ns-dz-who.active .ns-dz-who-name {
  color: var(--ns-ink);
}

/* On phones the strip scrolls sideways — with a dozen types, squeezing them all into the
   viewport width would leave unreadable slivers. */
.ns-dz-types {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
}

/* The chips carry .ns-chip's touch-action: none for drag-to-place — but that also swallows
   the swipe that should scroll this strip, making every type past the fold unreachable on
   touch. pan-x hands the strip's own axis back to the browser: swiping along it scrolls,
   while a drag toward the page (the cross axis) still reaches the placement code. The ≥768px
   rail flips the axes — see the override in that block. */
.ns-dz-types .ns-chip {
  touch-action: pan-x;
}

.ns-dz-types::-webkit-scrollbar {
  display: none;
}

/* the shared .ns-chip is a row; the tray variant stacks icon over label on phones */
.ns-dz-type {
  flex: none;
  min-width: 64px;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  padding: 8px 6px;
  font-size: 10px;
  border-radius: 11px;
}

.ns-dz-type > span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* short labels below 768px, full labels beside the icon from 768px */
.ns-dz-type .ns-dz-type-full {
  display: none;
}

.ns-chip.armed {
  border-color: var(--ns-blue-600);
  background: color-mix(in srgb, var(--ns-blue-600) 10%, #fff);
  color: var(--ns-blue-600);
}

.ns-chip-disabled {
  pointer-events: none;
  opacity: .4;
}

/* placed-fields list: bottom sheet on phones, right panel from 768px */

.ns-dz-fieldlist {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1046;
  background: var(--ns-thead-bg);
  border-radius: 18px 18px 0 0;
  box-shadow: 0 -12px 40px rgba(16, 40, 70, .22);
  max-height: 72vh;
  max-height: 72dvh;
  display: flex;
  flex-direction: column;
  transform: translateY(105%);
  transition: transform .22s cubic-bezier(.2, .7, .2, 1);
}

.ns-designer.ns-dz-sheet-open .ns-dz-fieldlist {
  transform: translateY(0);
}

.ns-dz-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(7, 25, 40, .4);
  z-index: 1045;
  opacity: 0;
  transition: opacity .18s ease;
}

.ns-dz-backdrop.show {
  opacity: 1;
}

.ns-dz-fieldrows {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 10px 12px calc(14px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.ns-dz-collapse {
  width: 26px;
  height: 26px;
  border: 1px solid var(--ns-border-strong);
  border-radius: 7px;
  background: #fff;
  color: var(--ns-slate);
  cursor: pointer;
  font-size: 12px;
  align-items: center;
  justify-content: center;
  flex: none;
}

.ns-dz-fieldlist-rail,
.ns-dz-tray-rail {
  display: none;
}

.ns-dz-tray-secrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
}

/* Now that the per-field controls live in Properties, the card is a single row: grip, type
   icon, name over meta, and the ⚙ on the right. */
.ns-dz-frow {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--ns-border);
  background: #fff;
  border-radius: 10px;
  padding: 6px 8px;
  cursor: pointer;
  min-width: 0;
  transition: border-color .15s ease, box-shadow .15s ease;
}

.ns-dz-frow:hover {
  border-color: var(--ns-info-border);
  box-shadow: var(--ns-shadow-card-sm);
}

.ns-dz-frow-ic {
  --ns-rc: var(--ns-blue-600);
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: color-mix(in srgb, var(--ns-rc) 12%, #fff);
  color: var(--ns-rc);
  border: 1.5px solid color-mix(in srgb, var(--ns-rc) 45%, #fff);
  display: flex;
  align-items: center;
  justify-content: center;
}

.ns-dz-frow-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* The heading doubles as the field's name editor (see beginRenameField): the pencil is the
   discoverable way in, double-click and long-press are the shortcuts. */
.ns-dz-frow-titlerow {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}

.ns-dz-frow-type {
  font-size: 11.5px;
  font-weight: 700;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border-radius: 4px;
  /* a long press must not also start a text selection or fire the iOS callout */
  -webkit-touch-callout: none;
  user-select: none;
  transition: background .13s ease, box-shadow .13s ease;
}

/* An unnamed field shows its type label - same slot, but it reads as a placeholder, not
   as a name the sender chose. */
.ns-dz-frow-type.unnamed {
  color: var(--ns-text-700);
  font-weight: 600;
}

.ns-dz-frow-type[tabindex]:hover {
  background: var(--ns-divider);
  cursor: text;
}

.ns-dz-frow-type[tabindex]:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--ns-blue-600);
}

/* hover-reveal rename shortcut on a field card, mirroring .ns-dz-who-edit on a signer chip */
.ns-dz-frow-rename {
  flex: none;
  width: 18px;
  height: 18px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ns-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
  opacity: 0;
  pointer-events: none; /* invisible must also mean unclickable */
  transition: opacity .13s ease, background .13s ease, color .13s ease;
}

.ns-dz-frow:hover .ns-dz-frow-rename,
.ns-dz-frow.selected .ns-dz-frow-rename,
.ns-dz-frow-rename:focus-visible {
  opacity: 1;
  pointer-events: auto;
}

.ns-dz-frow-rename:hover {
  background: var(--ns-divider);
  color: var(--ns-blue-600);
}

/* No hover to reveal it on a touch device, and long-press is not a gesture anyone can be
   expected to guess - so the pencil is simply always there. */
@media (hover: none) {
  .ns-dz-frow-rename {
    opacity: 1;
    pointer-events: auto;
  }
}

/* The editor replaces the heading in place, so it has to sit on the same baseline without
   nudging the card's height. */
.ns-dz-frow-rename-input.form-control-sm {
  font-size: 11.5px;
  font-weight: 700;
  height: auto;
  padding: 1px 5px;
  margin: -1px 0;
  min-width: 0;
}

/* An unnamed field shows its type label — same slot, but it reads as a placeholder, not
   as a name the sender chose. */
.ns-dz-frow-type.unnamed {
  color: var(--ns-text-700);
  font-weight: 600;
}

.ns-dz-frow-meta {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 9.5px;
  color: var(--ns-muted);
  white-space: nowrap;
  overflow: hidden;
}

.ns-dz-frow-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex: none;
}

/* the per-field action bar inside a list row: assign · required · ↑ ↓ · delete */
.ns-dz-frow-actions {
  display: flex;
  align-items: center;
  gap: 3px;
}

.ns-dz-fa {
  --ns-rc: var(--ns-blue-600);
  flex: none;
  width: 24px;
  height: 24px;
  border: 1px solid var(--ns-border);
  background: #fff;
  color: var(--ns-slate);
  cursor: pointer;
  border-radius: 7px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 800;
  transition: background .13s ease, color .13s ease, border-color .13s ease;
}

.ns-dz-fa:hover:not(:disabled) {
  background: var(--ns-divider);
  border-color: var(--ns-border-strong);
}

.ns-dz-fa:disabled {
  opacity: .35;
  cursor: default;
}

/* review step */

.ns-dz-summary {
  border: 1px solid var(--ns-border);
  background: #fff;
  border-radius: 14px;
  overflow: hidden;
}

/* the review summary accordion: row heads expand to entries; warnings surface as amber
   icons on every level that contains one */

.ns-dz-acc + .ns-dz-acc {
  border-top: 1px solid var(--ns-divider);
}

.ns-dz-acc-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  border: 0;
  background: transparent;
  padding: 12px 14px;
  font-size: 12.5px;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
  transition: background .13s ease;
}

.ns-dz-acc-head:hover {
  background: var(--ns-thead-bg);
}

.ns-dz-acc-chev {
  flex: none;
  color: var(--ns-muted);
  transition: transform .18s ease;
}

.ns-dz-acc.open > .ns-dz-acc-head .ns-dz-acc-chev {
  transform: rotate(90deg);
}

.ns-dz-acc-label {
  flex: 1;
  min-width: 0;
  color: var(--ns-slate);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-dz-acc-warn {
  flex: none;
  display: inline-flex;
  color: var(--ns-warning);
}

/* "Sign in order" note pill - shown wherever the sequential flag matters, only when it's set */
.ns-dz-seqnote {
  display: none;
  align-items: center;
  gap: 5px;
  flex: none;
  border-radius: 20px;
  padding: 2px 9px 2px 7px;
  background: var(--ns-blue-50);
  color: var(--ns-blue-600);
  font-size: 10.5px;
  font-weight: 700;
  white-space: nowrap;
}

.ns-designer.ns-dz-sequential .ns-dz-seqnote {
  display: inline-flex;
}

/* the tray variant sits under the "Placing for" label as its own line */
.ns-dz-seqnote-tray {
  align-self: flex-start;
  margin-top: -2px;
}

/* on the review Signers head it sits between the label and the count */
.ns-dz-acc-head .ns-dz-seqnote {
  margin-right: 2px;
}

.ns-dz-acc-count {
  flex: none;
  font-weight: 800;
}

.ns-dz-acc-bodywrap {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows .2s ease, opacity .18s ease, visibility .2s;
}

.ns-dz-acc.open > .ns-dz-acc-bodywrap {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}

/* Vertical spacing lives on the children (clipped at 0fr), never on the sizer itself -
   a padded sizer would keep its padding when "collapsed" and read as a stray gap. */
.ns-dz-acc-body {
  overflow: hidden;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 0 12px;
}

.ns-dz-acc-body > :last-child {
  margin-bottom: 12px;
}

.ns-dz-acc-item {
  display: flex;
  align-items: center;
  gap: 9px;
  border: 1px solid var(--ns-border);
  background: #fff;
  border-radius: 10px;
  padding: 8px 10px;
  font-family: inherit;
  text-align: left;
  transition: border-color .13s ease, box-shadow .13s ease;
}

button.ns-dz-acc-item {
  cursor: pointer;
}

button.ns-dz-acc-item:hover {
  border-color: var(--ns-info-border);
  box-shadow: var(--ns-shadow-card-sm);
}

.ns-dz-acc-ic {
  flex: none;
  color: var(--ns-blue-600);
  display: inline-flex;
}

.ns-dz-acc-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ns-dz-acc-name {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ns-ink);
  min-width: 0;
}

.ns-dz-acc-meta {
  font-size: 10.5px;
  color: var(--ns-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-dz-acc-fix {
  margin-top: 6px;
}

.ns-dz-acc-static {
  --ns-rc: var(--ns-blue-600);
}

/* nested per-document groups inside the Fields row */
.ns-dz-acc-sub {
  border: 1px solid var(--ns-border);
  border-radius: 10px;
  background: var(--ns-thead-bg);
  overflow: hidden;
}

.ns-dz-acc-sub > .ns-dz-acc-head {
  padding: 9px 10px;
  font-size: 12px;
}

.ns-dz-acc-sub .ns-dz-acc-body {
  padding: 0 8px;
}

.ns-dz-acc-sub .ns-dz-acc-body > :last-child {
  margin-bottom: 8px;
}

/* a signer entry with warnings expands on click to reveal them */
.ns-dz-acc-signer {
  flex-direction: column;
  align-items: stretch;
  gap: 0;
}

.ns-dz-acc-itemhead {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}

.ns-dz-expandable .ns-dz-acc-itemhead {
  cursor: pointer;
}

.ns-dz-acc-itemhead .ns-dz-acc-chev {
  margin-left: auto;
}

.ns-dz-acc-signer.open .ns-dz-acc-chev {
  transform: rotate(90deg);
}

.ns-dz-acc-itemwrap {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows .2s ease, opacity .18s ease, visibility .2s;
}

.ns-dz-acc-signer.open .ns-dz-acc-itemwrap {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}

.ns-dz-acc-itembody {
  overflow: hidden;
  min-height: 0;
}

.ns-dz-acc-itembody > .ns-dz-warn {
  margin-top: 8px;
  width: 100%;
}

.ns-dz-warn {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  border: 1px solid color-mix(in srgb, var(--ns-warning) 35%, #fff);
  background: var(--ns-warning-50);
  border-radius: 12px;
  padding: 11px 12px;
  color: var(--ns-warning-text);
  font-size: 11.5px;
  line-height: 1.45;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}

.ns-dz-warn svg {
  flex: none;
  color: var(--ns-warning);
}

/* the status/validation toast floats above everything, centered near the bottom */

.ns-dz-status {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(14px + env(safe-area-inset-bottom));
  z-index: 1052;
  width: min(92vw, 460px);
  pointer-events: none;
}

.ns-dz-status .alert {
  pointer-events: auto;
  box-shadow: var(--ns-shadow-dropdown);
}

.ns-dz-toast {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  animation: ns-dz-toast-in .2s cubic-bezier(.2, .7, .2, 1) both;
}

.ns-dz-toast.leaving {
  animation: ns-dz-toast-out .18s ease both;
}

.ns-dz-toast-msg {
  flex: 1;
  min-width: 0;
}

.ns-dz-toast-close {
  flex: none;
  width: 22px;
  height: 22px;
  margin: -2px -4px 0 0;
  border: 0;
  background: transparent;
  color: inherit;
  opacity: .55;
  cursor: pointer;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: opacity .13s ease, background .13s ease;
}

.ns-dz-toast-close:hover {
  opacity: 1;
  background: rgba(0, 0, 0, .06);
}

@keyframes ns-dz-toast-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
}

@keyframes ns-dz-toast-out {
  to {
    opacity: 0;
    transform: translateY(8px);
  }
}

/* review step: the read-only, everything-visible document preview */

.ns-dz-review-docswrap {
  /* bleed to the panel's edges - the step padding would otherwise inset the deep canvas */
  margin: 18px -16px -28px;
  position: relative; /* anchors the zoom + page controls it hosts during review */
}

.ns-dz-review-docs {
  background: var(--ns-canvas-deep);
  border-top: 1px solid var(--ns-border);
  padding: 18px 12px calc(26px + env(safe-area-inset-bottom));
}

.ns-dz-review-docs:empty {
  display: none;
}

/* the floating canvas controls don't fit the stacked phone review - hide them there */
@media (max-width: 767px) {
  .ns-dz-review-mode .ns-dz-zoom,
  .ns-dz-review-mode .ns-dz-pagenav {
    display: none;
  }
}

.ns-dz-doc-cap {
  display: none;
}

.ns-dz-review-mode .ns-dz-doc-cap {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  max-width: 860px;
  margin: 18px auto 10px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ns-text-700);
}

.ns-dz-review-mode .ns-dz-doc:first-child .ns-dz-doc-cap {
  margin-top: 0;
}

/* the preview is look-don't-touch: no placement, no selection, no drags */
.ns-dz-review-mode .ns-overlay {
  pointer-events: none;
}

.ns-dz-review-mode .ns-field {
  cursor: default;
}

/* ---------- designer v2 motion: step slides, pop-ins, collapses ---------- */

/* Steps slide out the way you're heading and the next one slides in behind them;
   direction comes from whether the target step is ahead of or behind the current one. */
@keyframes ns-dz-in-fwd {
  from {
    opacity: 0;
    transform: translateX(26px);
  }
}

@keyframes ns-dz-in-back {
  from {
    opacity: 0;
    transform: translateX(-26px);
  }
}

@keyframes ns-dz-out-fwd {
  to {
    opacity: 0;
    transform: translateX(-26px);
  }
}

@keyframes ns-dz-out-back {
  to {
    opacity: 0;
    transform: translateX(26px);
  }
}

.ns-dz-enter-fwd {
  animation: ns-dz-in-fwd .2s cubic-bezier(.2, .7, .2, 1) both;
}

.ns-dz-enter-back {
  animation: ns-dz-in-back .2s cubic-bezier(.2, .7, .2, 1) both;
}

.ns-dz-leave-fwd {
  animation: ns-dz-out-fwd .15s ease both;
}

.ns-dz-leave-back {
  animation: ns-dz-out-back .15s ease both;
}

/* anything freshly created pops in; collapseOut() shrinks a row shut before removal */
@keyframes ns-dz-pop {
  from {
    opacity: 0;
    transform: scale(.92) translateY(4px);
  }
}

.ns-dz-pop-in {
  animation: ns-dz-pop .18s cubic-bezier(.2, .7, .2, 1) both;
}

.ns-dz-collapse-out {
  overflow: hidden;
  opacity: 0;
  margin-top: 0 !important;
  margin-bottom: 0 !important;
  padding-top: 0 !important;
  padding-bottom: 0 !important;
  border-width: 0 !important;
  /* !important: rows carry their own hover transitions (border/box-shadow) that would
     otherwise win the cascade and turn this collapse into an instant pop */
  transition: height .2s ease, opacity .15s ease, margin .2s ease, padding .2s ease !important;
}

.ns-dz-armed:not([hidden]) {
  animation: ns-dz-drop .18s cubic-bezier(.2, .7, .2, 1) both;
}

@keyframes ns-dz-drop {
  from {
    opacity: 0;
    transform: translate(-50%, -8px);
  }
  to {
    opacity: 1;
    transform: translate(-50%, 0);
  }
}

/* (Toolbar/handle visibility is transition-driven from .ns-field.selected - see the
   designer-fields section; nothing here animates on re-render.) */

/* switching documents from the tab strip eases the incoming pages in */
.ns-dz-doc-enter {
  animation: ns-dz-in-fwd .18s ease both;
}

/* drag-to-reorder: the grip is the only place a row can be grabbed */
.ns-dz-grab {
  flex: none;
  width: 18px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ns-border-strong);
  cursor: grab;
  touch-action: none;
  border-radius: 5px;
  transition: color .13s ease, background .13s ease;
}

.ns-dz-grab:hover {
  color: var(--ns-slate);
  background: var(--ns-divider);
}

.ns-dz-dragging {
  z-index: 20;
  position: relative;
  box-shadow: var(--ns-shadow-raised) !important;
  cursor: grabbing;
}

.ns-dz-dragging .ns-dz-grab {
  cursor: grabbing;
}

.ns-dz-drag-active {
  user-select: none;
}

/* the selected field's card in the list panel mirrors the on-page selection */
.ns-dz-frow.selected {
  border-color: var(--ns-blue-600);
  box-shadow: 0 0 0 2px rgba(9, 132, 192, .18);
}

/* --- field-type palette: search + collapsible categories (≥768px; phones keep the flat
   chip strip — the category wrappers render display:contents there) --- */

.ns-dz-search {
  position: relative;
  margin: 0;
}

/* Per-field options expand on the selected card (Text: placeholder + default; Checkbox:
   pre-tick - a field's name is edited on its heading instead, see .ns-dz-frow-type).
   Always in the DOM: the wrapper's grid-rows transition slides it open on select AND shut
   on deselect; visibility keeps the hidden inputs unfocusable. */
.ns-dz-frow-cfgwrap {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  /* the card's flex gap still surrounds a 0-height section - pull one gap back so a
     collapsed card is exactly as tall as one without a config section */
  margin-top: -6px;
  transition: grid-template-rows .2s ease, opacity .18s ease, visibility .2s, margin .2s ease;
}

.ns-dz-search svg {
  position: absolute;
  left: 9px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ns-muted);
  pointer-events: none;
}

.ns-dz-search input {
  width: 100%;
  background: #fff;
  border: 1px solid var(--ns-border-strong);
  border-radius: 10px;
  font-size: 12px;
  padding: 6px 10px 6px 28px;
  color: var(--ns-ink);
  font-family: inherit;
}

/* bare sizer - all visuals live on the inner box so 0fr really is 0px */
.ns-dz-frow-cfg {
  overflow: hidden;
  min-height: 0;
  cursor: default;
}

.ns-dz-search input:focus {
  outline: none;
  border-color: var(--ns-blue-600);
}

/* Below 768px the palette is one flat scrolling strip, so the category wrappers get out of
   the flex container's way entirely. */
.ns-dz-cat,
.ns-dz-cat-body,
.ns-dz-cat-list {
  display: contents;
}

.ns-dz-cat-head {
  width: 100%;
  align-items: center;
  gap: 7px;
  border: 0;
  background: transparent;
  padding: 7px 2px;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
}

.ns-dz-cat-chev {
  flex: none;
  color: var(--ns-muted);
  display: inline-flex;
  transition: transform .16s ease;
  transform: rotate(90deg);
}

.ns-dz-cat.closed .ns-dz-cat-chev {
  transform: rotate(0deg);
}

.ns-dz-cat-name {
  flex: 1;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--ns-slate);
}

.ns-dz-cat-count {
  font-size: 10px;
  font-weight: 800;
  color: var(--ns-muted);
}

/* --- inspector: the right panel's Properties / Outline segments --- */

/* The segmented control is the panel's whole top row — the collapse chevron (and the phone
   Done) ride along instead of a header of their own. */
.ns-dz-seg {
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 8px 10px;
  border-bottom: 1px solid #E9EEF4;
  background: var(--ns-thead-bg);
  flex: none;
  border-radius: inherit; /* the phone sheet's rounded top */
}

.ns-dz-seg-btn {
  flex: 1;
  border: 1px solid transparent;
  background: transparent;
  color: var(--ns-slate);
  border-radius: 8px;
  padding: 5px 8px;
  font-size: 11.5px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
}

.ns-dz-seg-btn.active {
  border-color: var(--ns-border-strong);
  background: #fff;
  color: var(--ns-ink);
  font-weight: 800;
}

.ns-dz-seg-count {
  opacity: .65;
}

.ns-dz-insp {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  /* the crossfade ghost (.ns-dz-insp-ghost) is laid out against this box */
  position: relative;
}

.ns-dz-insp-empty {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 28px 20px;
  text-align: center;
  font-size: 12px;
}

.ns-dz-insp-empty > span:last-child {
  color: var(--ns-muted);
  font-size: 11px;
  text-wrap: pretty;
}

.ns-dz-insp-empty-ic {
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--ns-blue-600) 10%, #fff);
  color: var(--ns-blue-600);
  display: flex;
  align-items: center;
  justify-content: center;
}

.ns-dz-insp-head {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 11px 10px;
  border-bottom: 1px solid var(--ns-divider);
  flex: none;
}

.ns-dz-insp-ic {
  --ns-rc: var(--ns-blue-600);
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--ns-rc) 12%, #fff);
  color: var(--ns-rc);
  border: 1.5px solid color-mix(in srgb, var(--ns-rc) 45%, #fff);
  display: flex;
  align-items: center;
  justify-content: center;
}

.ns-dz-insp-title {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ns-dz-insp-name {
  width: 100%;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 7px;
  font-size: 12.5px;
  font-weight: 800;
  padding: 1px 5px;
  margin-left: -5px;
  color: var(--ns-ink);
  font-family: inherit;
}

.ns-dz-insp-name:hover:not(:focus) {
  background: var(--ns-divider);
}

.ns-dz-insp-name:focus {
  outline: none;
  border-color: var(--ns-blue-600);
  background: #fff;
}

.ns-dz-insp-meta {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 10px;
  color: var(--ns-muted);
  white-space: nowrap;
  overflow: hidden;
}

.ns-dz-insp-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex: none;
}

.ns-dz-insp-close {
  flex: none;
  width: 24px;
  height: 24px;
  border: 1px solid var(--ns-border);
  background: #fff;
  color: var(--ns-slate);
  border-radius: 7px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.ns-dz-insp-close:hover {
  background: var(--ns-divider);
}

/* Content / Rules / Layout as one connected tab group — the same segmented-control recipe
   as the Properties/Outline switcher above it, not three floating pills. */
.ns-dz-insp-tabs {
  display: flex;
  gap: 2px;
  margin: 8px 10px;
  padding: 2px;
  background: var(--ns-canvas);
  border: 1px solid var(--ns-border);
  border-radius: 9px;
  flex: none;
}

.ns-dz-insp-tab {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  color: var(--ns-slate);
  border-radius: 7px;
  padding: 5px 8px;
  font-size: 11px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  /* the raised pill fades between tabs rather than jumping, so the indicator reads as
     moving with the pane that slides in beneath it */
  transition: background .17s ease, color .17s ease, box-shadow .17s ease;
}

.ns-dz-insp-tab:hover:not(.active) {
  color: var(--ns-ink);
}

.ns-dz-insp-tab.active {
  background: #fff;
  color: var(--ns-blue-600);
  font-weight: 800;
  box-shadow: 0 1px 2px rgba(16, 40, 70, .12);
}

.ns-dz-insp-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 10px 16px;
}

.ns-dz-insp-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

/* ---------- first-run card (dashboard, after redeeming an invitation) ----------
   A washed card header over a row of plain links, per
   docs/design/components/cards-and-surfaces.md. Sits above the band/rail columns and
   inherits their 22px gap from .ns-dash-columns. */

.ns-welcome {
  padding: 16px 18px;
  margin-bottom: 14px;
  border-left: 3px solid var(--ns-blue-600);
}

.ns-welcome-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.ns-welcome-title {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
}

.ns-welcome-dismiss {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  color: var(--ns-muted);
  transition: background .14s ease, color .14s ease;
}

.ns-welcome-dismiss:hover {
  background: var(--ns-blue-50);
  color: var(--ns-text-700);
}

.ns-welcome-dismiss:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
  color: var(--ns-text-700);
}

.ns-welcome-sub {
  margin: 4px 0 12px;
  font-size: 12.5px;
  color: var(--ns-muted);
}

.ns-welcome-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  font-size: 12.5px;
  font-weight: 600;
}

/* ---------- password strength meter ----------
   Advisory, never a gate — see wwwroot/js/password-strength.js. Four rungs, and the
   colour comes from the same severity ramp the rest of the app uses, so "weak" here
   reads as the same weak as everywhere else. */

/* Always laid out, faded rather than hidden. An untouched form should not already be
   telling somebody their empty password is too short — but toggling `hidden` would move
   the two fields under it the moment they started typing, and "nothing jumps" wins. */
.ns-strength {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 7px;
  opacity: 0;
  transition: opacity .16s ease;
}

.ns-strength[data-ns-strength-score] {
  opacity: 1;
}

.ns-strength-track {
  flex: 1;
  height: 4px;
  border-radius: 999px;
  background: var(--ns-border);
  overflow: hidden;
}

.ns-strength-fill {
  display: block;
  height: 100%;
  width: 25%;
  border-radius: 999px;
  background: var(--ns-danger-text);
  /* 160ms: the hover/colour rung of the motion vocabulary. The bar tracks typing, so
     anything slower lags behind the keystrokes that caused it. */
  transition: width .16s ease, background .16s ease;
}

.ns-strength-label {
  flex: none;
  font-size: 11px;
  font-weight: 700;
  color: var(--ns-muted);
}

.ns-strength[data-ns-strength-score="1"] .ns-strength-fill { background: var(--ns-warning); }
.ns-strength[data-ns-strength-score="2"] .ns-strength-fill { background: var(--ns-blue-600); }
.ns-strength[data-ns-strength-score="3"] .ns-strength-fill { background: var(--ns-success); }

/* ---------- choice rows with a dependent panel ----------
   The documented "checkbox with a description is a bordered row" anatomy
   (docs/design/components/forms-and-inputs.md), at form scale rather than the
   designer inspector's 11.5px, and as radios. Two or three mutually exclusive
   options, which is what the doc says radios are for. Named for its first use
   (the New account forms' setup-link / typed-password pair); /System/Tenants/New
   uses the same rows for its three-way first-admin choice, where one option
   deliberately has no panel of its own. */

.ns-invite-choice {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  margin: 0;
  border: 1px solid var(--ns-border);
  background: #fff;
  border-radius: 10px;
  padding: 11px 13px;
  cursor: pointer;
  transition: border-color .14s ease, background .14s ease;
}

.ns-invite-choice:hover {
  border-color: var(--ns-border-strong);
}

/* The checked row is the one the form will act on, so it says so without relying
   on the radio dot alone being spotted. */
.ns-invite-choice:has(input:checked) {
  border-color: var(--ns-blue-600);
  background: var(--ns-blue-50);
}

.ns-invite-choice:has(input:focus-visible) {
  outline: none;
  border-color: var(--ns-blue-600);
  box-shadow: var(--ns-focus-ring);
}

.ns-invite-choice input {
  flex: none;
  margin: 2px 0 0;
}

.ns-invite-choice > span {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ns-invite-choice-label {
  font-size: 13px;
  font-weight: 700;
  color: var(--ns-text-700);
}

.ns-invite-choice-help {
  font-size: 11.5px;
  color: var(--ns-muted);
}

/* Collapse/expand for whichever panel belongs to the checked option. 200ms is the
   collapse duration from docs/design/foundations/motion.md; the height itself is
   measured in admin-ui.js, which pins and then releases max-height. The global
   prefers-reduced-motion rule near the top of this file flattens it. */
.ns-invite-panel {
  transition: max-height .2s ease, opacity .2s ease;
}

.ns-invite-panel[style*="max-height: 0px"] {
  opacity: 0;
}

.ns-dz-insp-check {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  border: 1px solid var(--ns-border);
  background: #fff;
  border-radius: 9px;
  padding: 8px 10px;
  cursor: pointer;
  font-size: 11.5px;
  color: var(--ns-text-700);
}

.ns-dz-insp-check input {
  flex: none;
  margin: 0;
}

.ns-dz-insp-check span {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.ns-dz-insp-check b {
  font-size: 11.5px;
  font-weight: 700;
}

.ns-dz-insp-check em {
  font-style: normal;
  font-size: 10px;
  color: var(--ns-muted);
}

/* The frozen copy of the outgoing properties body, laid over the incoming one so departing
   controls fade out in place instead of blinking away. Inert by definition — it is a
   screenshot, not a panel. */
.ns-dz-insp-ghost {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 3;
  overflow: hidden;
  pointer-events: none;
}

/* A modifier welded to the bottom edge of the input it changes: the row shares the input's
   border (square corners where they meet, one overlapped pixel), so the two read as a
   single control instead of a checkbox that happens to sit nearby. */
.ns-dz-attach {
  display: flex;
  flex-direction: column;
  /* No gap: the label→control spacing lives inside .ns-dz-labeled, so the modifier can butt
     right up against the control's bottom edge and share its border. */
  gap: 0;
}

.ns-dz-attach > .ns-dz-labeled > .form-control {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.ns-dz-inputmod {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: -1px 0 0;     /* overlap the control's bottom border rather than doubling it */
  padding: 6px 9px;
  border: 1px solid var(--ns-border);
  border-radius: 0 0 8px 8px;
  background: var(--ns-canvas);
  cursor: pointer;
  transition: background .13s ease, border-color .13s ease;
}

.ns-dz-inputmod:hover {
  border-color: var(--ns-border-strong);
}

.ns-dz-inputmod input {
  flex: none;
  margin: 0;
}

.ns-dz-inputmod-ico {
  flex: none;
  display: inline-flex;
  color: var(--ns-muted);
  transition: color .13s ease;
}

.ns-dz-inputmod-ico svg {
  width: 13px;
  height: 13px;
}

.ns-dz-inputmod-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.ns-dz-inputmod-body b {
  font-size: 11px;
  font-weight: 700;
  color: var(--ns-text-700);
}

.ns-dz-inputmod-body em {
  font-style: normal;
  font-size: 10px;
  line-height: 1.35;
  color: var(--ns-muted);
}

/* Locked: the pair takes the accent together, so "this text is printed, not typed" reads
   from the shape of the control rather than from the checkbox alone. */
.ns-dz-attach.is-locked > .ns-dz-labeled > .form-control {
  border-color: var(--ns-blue-600);
  background: color-mix(in srgb, var(--ns-blue-600) 3%, #fff);
}

.ns-dz-inputmod.is-on {
  border-color: var(--ns-blue-600);
  background: color-mix(in srgb, var(--ns-blue-600) 8%, #fff);
}

.ns-dz-inputmod.is-on .ns-dz-inputmod-ico,
.ns-dz-inputmod.is-on .ns-dz-inputmod-body b {
  color: var(--ns-blue-600);
}

/* the Dropdown's options editor (Content tab) */
.ns-dz-insp-opts {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.ns-dz-insp-optshead {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.ns-dz-insp-optcount {
  font-size: 9.5px;
  font-weight: 700;
  color: var(--ns-muted);
}

.ns-dz-insp-opt {
  display: flex;
  align-items: center;
  gap: 5px;
}

.ns-dz-insp-optdel {
  flex: none;
  width: 26px;
  height: 26px;
  border: 1px solid var(--ns-border);
  background: #fff;
  color: var(--ns-muted);
  border-radius: 7px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.ns-dz-insp-optdel:hover {
  color: var(--ns-slate);
  background: var(--ns-divider);
}

.ns-dz-insp-optadd {
  border: 1.5px dashed var(--ns-border-strong);
  background: transparent;
  border-radius: 8px;
  padding: 6px;
  color: var(--ns-blue-600);
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
  font-family: inherit;
}

.ns-dz-insp-optadd:hover {
  border-color: var(--ns-blue-600);
}

/* The outline wrapper: the signer filter stays put while the rows swap out beneath it. */
.ns-dz-outwrap {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.ns-dz-outfilterwrap {
  flex: none;
}

.ns-dz-outfilter {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 0;
  padding: 10px 12px 0;
}

/* The custom signer picker: a native <select>'s options can't be styled reliably, so the
   colour coding (dot + tint, the same hue as the signer's fields) lives in our own
   button + listbox. */
.ns-dz-spick {
  --ns-rc: var(--ns-blue-600);
  position: relative;
}

.ns-dz-spick-btn {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--ns-border);
  border-left: 3px solid var(--ns-rc);
  background: #fff;
  color: var(--ns-rc);
  border-radius: 8px;
  padding: 5px 9px;
  font-size: 12px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
}

.ns-dz-spick-btn:disabled {
  opacity: .55;
  cursor: default;
}

.ns-dz-spick-btn > svg {
  margin-left: auto;
  color: var(--ns-slate);
  flex: none;
}

.ns-dz-spick-dot {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--ns-rc);
}

.ns-dz-spick-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-dz-spick-menu {
  position: absolute;
  z-index: 40;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 4px;
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: 10px;
  box-shadow: var(--ns-shadow-dropdown);
  max-height: 240px;
  overflow-y: auto;
}

.ns-dz-spick-opt {
  --ns-rc: var(--ns-blue-600);
  display: flex;
  align-items: center;
  gap: 7px;
  border: 0;
  background: transparent;
  color: var(--ns-rc);
  border-radius: 7px;
  padding: 6px 8px;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
}

.ns-dz-spick-opt:hover,
.ns-dz-spick-opt:focus-visible {
  background: color-mix(in srgb, var(--ns-rc) 10%, #fff);
  outline: none;
}

.ns-dz-spick-opt.active {
  background: color-mix(in srgb, var(--ns-rc) 14%, #fff);
  font-weight: 800;
}

.ns-dz-spick-opt span:last-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The sidebars scroll without showing a bar — the rails are narrow and the bar is clutter. */
.ns-dz-tray,
.ns-dz-tray-body,
.ns-dz-insp,
.ns-dz-fieldrows,
.ns-dz-outwrap {
  scrollbar-width: none;
}

.ns-dz-tray::-webkit-scrollbar,
.ns-dz-tray-body::-webkit-scrollbar,
.ns-dz-insp::-webkit-scrollbar,
.ns-dz-fieldrows::-webkit-scrollbar,
.ns-dz-outwrap::-webkit-scrollbar {
  display: none;
}

/* A table's cell map in the Properties panel: the grid as it sits on the page, each
   occupied slot wearing its field as a chip that drags between slots. */
.ns-dz-cellgrid {
  display: grid;
  gap: 3px;
}

.ns-dz-cellslot {
  min-height: 30px;
  min-width: 0;
  border: 1px dashed var(--ns-border);
  border-radius: 6px;
  background: var(--ns-canvas);
  display: flex;
  align-items: stretch;
  padding: 2px;
}

.ns-dz-cellslot.ns-dz-slot-over {
  border-style: solid;
  border-color: var(--ns-blue-600);
  background: color-mix(in srgb, var(--ns-blue-600) 10%, #fff);
}

.ns-dz-cellchip {
  --ns-rc: var(--ns-blue-600);
  flex: 1;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 3px 2px 5px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--ns-rc) 12%, #fff);
  border: 1px solid color-mix(in srgb, var(--ns-rc) 45%, transparent);
  color: var(--ns-rc);
  font-size: 10.5px;
  font-weight: 700;
  cursor: grab;
  user-select: none;
  touch-action: none;
}

.ns-dz-cellchip.dragging {
  opacity: .6;
  cursor: grabbing;
}

.ns-dz-cellchip svg {
  width: 11px;
  height: 11px;
  flex: none;
}

.ns-dz-cellchip-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ns-ink);
}

.ns-dz-cellchip-x {
  flex: none;
  border: 0;
  background: transparent;
  color: var(--ns-slate);
  padding: 0 2px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}

.ns-dz-cellchip-x:hover {
  color: var(--ns-maple);
}

.ns-dz-cellchip-x svg {
  width: 10px;
  height: 10px;
}

/* The ghost option input: dashed until it holds text — a placeholder slot, not an option.
   Type + Enter turns it into one and a fresh ghost takes its place. */
.insp-opt-ghost {
  border-style: dashed;
}

.insp-opt-ghost:focus {
  border-style: solid;
}

/* the tucked-away extras (a table's layout sections) live behind a disclosure */
.ns-dz-insp-details {
  border: 1px solid var(--ns-border);
  border-radius: 8px;
  padding: 6px 8px;
}

.ns-dz-insp-details summary {
  cursor: pointer;
  font-size: 11px;
  font-weight: 700;
  color: var(--ns-slate);
  user-select: none;
}

.ns-dz-insp-details[open] summary {
  margin-bottom: 6px;
  color: var(--ns-ink);
}

.ns-dz-insp-details .ns-dz-insp-opts {
  margin: 0;
}

/* Searching the palette rewrites which chips are on screen, so the surviving set fades in
   together — the same gesture the outline uses when its signer changes. */
.ns-dz-filter-swap {
  animation: ns-dz-filter-in .19s ease both;
}

@keyframes ns-dz-filter-in {
  from {
    transform: translateY(-4px);
    opacity: .25;
  }
}

/* the disclosure body is height-animated by bindDisclosure, so it must be able to clip */
.ns-dz-details-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* switching signers fade-slides the new rows in under the untouched select */
.ns-dz-rows-swap {
  animation: ns-dz-rows-swap-in .22s ease both;
}

@keyframes ns-dz-rows-swap-in {
  from {
    transform: translateY(8px);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ns-dz-rows-swap {
    animation: none;
  }
}

.ns-dz-frow-child {
  margin-left: 16px;
}

.ns-dz-frow-group {
  background: var(--ns-thead-bg);
}

/* a segmented picker inside the inspector (option layout) */
.ns-dz-insp-seg {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ns-dz-insp-segbtns {
  display: flex;
  gap: 4px;
}

.ns-dz-insp-segbtns button {
  flex: 1;
  border: 1px solid var(--ns-border);
  background: #fff;
  color: var(--ns-text-700);
  border-radius: 8px;
  padding: 6px 4px;
  font-size: 10.5px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
}

/* Hover on an inactive segment is mandatory and was missing here — see
   docs/design/components/tabs-and-segmented.md § States. Colour and border only, so the
   pressed/active treatment stays the louder of the two. */
.ns-dz-insp-segbtns button:hover:not(.active) {
  border-color: var(--ns-border-strong);
  color: var(--ns-ink);
}

/* Both of these arrived twice, from two branches at once: this control lived in the designer's
   inspector where it was only ever clicked, and it is now also the personal console's date-format
   row, which keyboard users tab through. Same recipe as .ns-row-link:focus-visible. */
.ns-dz-insp-segbtns button:focus-visible {
  outline: none;
  border-color: var(--ns-blue-600);
  box-shadow: var(--ns-focus-ring);
}

.ns-dz-insp-segbtns button.active {
  border-color: var(--ns-blue-600);
  background: color-mix(in srgb, var(--ns-blue-600) 8%, #fff);
  color: var(--ns-blue-600);
  font-weight: 800;
}

/* the Layout tab's order row (position within the document's list) */
.ns-dz-insp-orderrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.ns-dz-insp-orderbtns {
  display: inline-flex;
  gap: 4px;
}

/* the panel's one destructive act, kept at the very bottom away from everything else */
.ns-dz-insp-footer {
  flex: none;
  margin-top: auto;
  padding: 10px;
  border-top: 1px solid var(--ns-divider);
}

.ns-dz-insp-delete {
  width: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 1px solid var(--ns-border);
  background: #fff;
  color: var(--ns-maple);
  border-radius: 8px;
  padding: 7px;
  font-size: 11.5px;
  font-weight: 700;
  cursor: pointer;
  font-family: inherit;
}

.ns-dz-insp-delete:hover {
  border-color: var(--ns-danger-border);
  background: var(--ns-danger-50);
}

/* snap guides: the line a dragged/resized field just locked onto */
.ns-guide {
  position: absolute;
  background: var(--ns-maple);
  opacity: .75;
  pointer-events: none;
  z-index: 30;
}

.ns-guide-v {
  width: 1px;
  top: 0;
  bottom: 0;
}

.ns-guide-h {
  height: 1px;
  left: 0;
  right: 0;
}

/* switching inspector views slides the incoming one in from its side of the segmented
   control (Properties sits left of Outline) */
.ns-dz-insp-slide-left {
  animation: ns-dz-insp-in-left .18s ease both;
}

.ns-dz-insp-slide-right {
  animation: ns-dz-insp-in-right .18s ease both;
}

@keyframes ns-dz-insp-in-left {
  from {
    transform: translateX(-14px);
    opacity: 0;
  }
}

@keyframes ns-dz-insp-in-right {
  from {
    transform: translateX(14px);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ns-dz-insp-slide-left,
  .ns-dz-insp-slide-right {
    animation: none;
  }
}

/* the in-house confirm/prompt dialog */
.ns-dz-modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 2100;
  background: rgba(7, 25, 40, .44);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  opacity: 0;
  transition: opacity .16s ease;
}

.ns-dz-modal-backdrop.show {
  opacity: 1;
}

.ns-dz-modal {
  width: min(92vw, 400px);
  background: #fff;
  border-radius: var(--ns-radius-panel);
  box-shadow: var(--ns-shadow-modal);
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  transform: scale(.94) translateY(6px);
  transition: transform .16s cubic-bezier(.2, .7, .2, 1);
}

.ns-dz-modal-backdrop.show .ns-dz-modal {
  transform: none;
}

.ns-dz-modal-msg {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ns-ink);
  line-height: 1.5;
}

.ns-dz-modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

/* page navigation floating over the canvas corner, opposite the zoom control */
.ns-dz-pagenav {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 25;
  display: flex;
  align-items: center;
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: 10px;
  box-shadow: var(--ns-shadow-raised);
  overflow: hidden;
}

.ns-dz-pagenav[hidden] {
  display: none;
}

.ns-dz-pagenav button {
  border: 0;
  background: transparent;
  font-family: inherit;
  cursor: pointer;
  color: var(--ns-slate);
  width: 28px;
  height: 28px;
  font-size: 14px;
  line-height: 1;
  transition: background .13s ease, color .13s ease;
}

.ns-dz-pagenav button:hover {
  background: var(--ns-divider);
  color: var(--ns-ink);
}

.ns-dz-pagenav input {
  width: 34px;
  height: 28px;
  border: 0;
  border-left: 1px solid var(--ns-divider);
  text-align: center;
  font-family: inherit;
  font-size: 11px;
  font-weight: 800;
  color: var(--ns-ink);
  background: transparent;
}

.ns-dz-pagenav input:focus {
  outline: none;
  background: var(--ns-blue-50);
}

.ns-dz-pagenav span {
  font-size: 11px;
  font-weight: 700;
  color: var(--ns-muted);
  padding-right: 8px;
  border-right: 1px solid var(--ns-divider);
  line-height: 28px;
}

/* recipient-card inputs acknowledge the pointer */
.ns-dz-rec .form-control:hover:not(:focus):not(:disabled),
.ns-dz-rec .form-select:hover:not(:focus):not(:disabled) {
  border-color: var(--ns-border-strong);
}

.ns-dz-rec-name:hover:not(:focus) {
  background: var(--ns-divider);
}

/* labelled inputs on the recipient card */
.ns-dz-labeled {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
  margin: 0;
  cursor: text;
}

.ns-dz-lbl {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ns-muted);
}

/* A refused save or send outlines every box that has to change, all at once - three blank
   addresses are three red boxes, not three refusals one after another. The reason is said once,
   by the toast; the outline is what stops the toast being the only report of the failure
   (design/components/toasts-and-validation.md). The box keeps its size while it is wrong: the
   borderless name input takes an inset ring rather than a border, and the bordered ones only
   change colour. Hover and focus are spelled out because the card's own hover rule is more
   specific than a plain state class would be.

   Named ns-dz-invalid, not ns-invalid: the ceremony's .ns-invalid marks a field's CONTAINER
   (.ns-text-target and friends) while this one marks the control itself, and one class meaning
   two things is how a rule ends up matching nothing. */
.ns-dz-rec .ns-dz-rec-name.ns-dz-invalid,
.ns-dz-rec .ns-dz-rec-name.ns-dz-invalid:hover {
  background: var(--ns-danger-50);
  box-shadow: inset 0 0 0 1.5px var(--ns-maple);
}

.ns-dz-rec .ns-dz-rec-name.ns-dz-invalid:focus {
  box-shadow: inset 0 0 0 1.5px var(--ns-maple), var(--ns-focus-ring);
}

.ns-dz-rec .form-control.ns-dz-invalid,
.ns-dz-rec .form-select.ns-dz-invalid,
.ns-dz-rec .form-control.ns-dz-invalid:hover:not(:focus):not(:disabled),
.ns-dz-rec .form-select.ns-dz-invalid:hover:not(:focus):not(:disabled) {
  border-color: var(--ns-maple);
}

/* the tray's shortcut back to the recipients step */
.ns-dz-tray-add {
  border: 1.5px dashed var(--ns-border-strong);
  background: transparent;
  border-radius: 10px;
  padding: 6px 8px;
  color: var(--ns-blue-600);
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
  font-family: inherit;
  flex: none;
  transition: border-color .15s ease, background .15s ease;
}

.ns-dz-tray-add:hover {
  border-color: var(--ns-blue-600);
  background: var(--ns-blue-50);
}

/* The words at every width except the phone strip, where the + carries it alone. */
.ns-dz-tray-add-ic {
  display: none;
}

/* Phone: the Placing-for section tightens to one row — the chips shrink to their coloured
   dots (the role names live on the Roles step; the dot's letter and colour identify them
   here) and the add button rides at the strip's end as a 44px + instead of its own row. */
@media (max-width: 575.98px) {
  .ns-dz-tray-sec-who {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 8px;
  }

  .ns-dz-tray-sec-who > .ns-dz-tray-secrow,
  .ns-dz-tray-sec-who > .ns-dz-seqnote {
    grid-column: 1 / -1;
  }

  .ns-dz-who {
    padding: 3px;
  }

  .ns-dz-who-name,
  .ns-dz-who-edit,
  .ns-dz-who-grab {
    display: none;
  }

  .ns-dz-tray-add {
    width: 44px;
    height: 44px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
  }

  .ns-dz-tray-add-ic {
    display: inline-flex;
  }

  .ns-dz-tray-add-label {
    display: none;
  }
}

/* hover-reveal edit shortcut on a Placing-for chip */
.ns-dz-who-edit {
  flex: none;
  width: 20px;
  height: 20px;
  margin-left: auto; /* pinned to the chip's far edge, clear of the click target */
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ns-muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
  opacity: 0;
  pointer-events: none; /* invisible must also mean unclickable */
  transition: opacity .13s ease, background .13s ease, color .13s ease;
}

.ns-dz-who:hover .ns-dz-who-edit,
.ns-dz-who-edit:focus-visible {
  opacity: 1;
  pointer-events: auto;
}

.ns-dz-who-edit:hover {
  background: var(--ns-divider);
  color: var(--ns-blue-600);
}

.ns-dz-who-grab {
  width: 14px;
  height: 20px;
  opacity: 0;
  pointer-events: none;
}

.ns-dz-who:hover .ns-dz-who-grab {
  opacity: 1;
  pointer-events: auto;
}

/* zoom control floating over the canvas corner */
.ns-dz-zoom {
  position: absolute;
  right: 12px;
  bottom: 12px;
  z-index: 25;
  display: flex;
  align-items: center;
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: 10px;
  box-shadow: var(--ns-shadow-raised);
  overflow: hidden;
}

.ns-dz-zoom button {
  border: 0;
  background: transparent;
  font-family: inherit;
  cursor: pointer;
  color: var(--ns-slate);
  transition: background .13s ease, color .13s ease;
}

.ns-dz-zoom button:hover {
  background: var(--ns-divider);
  color: var(--ns-ink);
}

/* the edit bar: undo/redo/copy/paste, floating over the canvas's top-right corner —
   the same card recipe as the zoom control in the opposite corner */
.ns-dz-editbar {
  position: absolute;
  right: 12px;
  top: 12px;
  z-index: 25;
  display: flex;
  align-items: center;
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: 10px;
  box-shadow: var(--ns-shadow-raised);
  overflow: hidden;
}

.ns-dz-editbar button {
  width: 32px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  font-family: inherit;
  cursor: pointer;
  color: var(--ns-slate);
  transition: background .13s ease, color .13s ease;
}

.ns-dz-editbar button + button {
  border-left: 1px solid var(--ns-divider);
}

.ns-dz-editbar button:hover:not(:disabled) {
  background: var(--ns-divider);
  color: var(--ns-ink);
}

.ns-dz-editbar button:disabled {
  opacity: .35;
  cursor: default;
}

.ns-dz-editbar svg {
  width: 15px;
  height: 15px;
}

/* The two parts of the zoom control. The id selectors are the designer's original hooks; the
   class selectors are the contract for any new instance (the viewer uses them), so the control
   can be mounted anywhere without inheriting the designer's element ids. Prefer the classes. */
.ns-dz-zoom #dz-zoom-in,
.ns-dz-zoom #dz-zoom-out,
.ns-dz-zoom .ns-dz-zoom-step {
  width: 30px;
  height: 28px;
  font-size: 15px;
  line-height: 1;
}

.ns-dz-zoom #dz-zoom-reset,
.ns-dz-zoom .ns-dz-zoom-reset {
  min-width: 46px;
  height: 28px;
  font-size: 10.5px;
  font-weight: 800;
  border-left: 1px solid var(--ns-divider);
  border-right: 1px solid var(--ns-divider);
}

/* A step button at 50% or 200% is genuinely spent — say so rather than absorbing the click.
   The designer never disables its own pair, so this only shows where a caller sets it. */
.ns-dz-zoom button:disabled {
  opacity: .35;
  cursor: default;
}

.ns-dz-zoom button:disabled:hover {
  background: transparent;
  color: var(--ns-slate);
}

.ns-dz-zoom button:focus-visible,
.ns-dz-pagenav button:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

/* Touch: the floating canvas controls grow to a real tap target.
   At 28-30px these were comfortable with a mouse and a coin-toss with a thumb — under the 44px
   floor in docs/design/polish-pass.md §4. Keyed off `hover: none` rather than a width, because
   the thing that changes is the pointer, not the screen: a 1280px tablet needs this and a narrow
   desktop window does not.

   Both controls share the canvas's bottom line, zoom at the right edge and the page navigator at
   the left, so their combined width is the constraint that matters. At these sizes that is
   ~134px + ~162px = 296px, which still leaves a gap at 360px — the narrowest layout the polish
   pass asks for. Widening them further would have them meet in the middle.

   They float over .ns-dz-canvaswrap (grid-area: canvas). The designer's phone tray is its own
   grid row rather than an overlay, so a taller control eats into the canvas and cannot collide
   with the field-type strip below it; the 12px offset is left alone deliberately. */
@media (hover: none) {
  .ns-dz-zoom button,
  .ns-dz-pagenav button {
    min-height: 44px;
  }

  /* The bar's back chevron is the same story at 26px. Grown to a 44px target and then pulled
     back out of the layout by the 9px it gained on each side, so it contributes its original
     26px to the bar's height: the hit area grows, the navy bar does not. Without the negative
     margin the bar goes from 46px to 58px on every touch device — 12px of vertical space taken
     from a phone, on the designer and the viewer alike. */
  .ns-designer-back,
  .ns-designer-help {
    width: 44px;
    height: 44px;
    margin-block: -9px;
  }

  .ns-dz-zoom #dz-zoom-in,
  .ns-dz-zoom #dz-zoom-out,
  .ns-dz-zoom .ns-dz-zoom-step,
  .ns-dz-pagenav button {
    width: 44px;
    height: 44px;
  }

  /* Already 46px wide, so only its height is short of the floor. */
  .ns-dz-zoom #dz-zoom-reset,
  .ns-dz-zoom .ns-dz-zoom-reset {
    height: 44px;
  }

  /* The page number is typed into, so it is a tap target too — and the taller box needs its
     line-height to match or the digits sit high against the buttons beside them. */
  .ns-dz-pagenav input {
    width: 40px;
    height: 44px;
  }

  .ns-dz-pagenav span {
    line-height: 44px;
  }

  /* The edit bar is the zoom control's twin in the opposite corner but was added after the
     rules above and missed this pass — same floor, same reasoning: it floats over the canvas,
     so growing it eats page rather than colliding with anything. */
  .ns-dz-editbar button {
    width: 44px;
    height: 44px;
  }

  /* The armed pill's Cancel is the only way out of placement mode that doesn't place a
     field. The pill floats top-centre over the canvas, so it can afford the height. */
  .ns-dz-armed button {
    min-height: 44px;
    padding: 3px 16px;
  }

  /* Step crumbs and the bar's action buttons: same trick as the back chevron above —
     grown to the floor, then the gain handed back with negative block margins so the
     navy bar keeps its height. The strip carries the negative margins, not the crumb:
     it scrolls sideways, and overflow-x: auto clips a child that outgrows it, which
     would have thrown the gained height away (and left an 8px phantom scroll). */
  .ns-dz-crumbstrip {
    min-height: 44px;
    margin-block: -8px;
  }

  .ns-dz-crumb {
    min-height: 44px;
  }

  .ns-designer-bar .btn-sm {
    /* The padding route rather than min-height: the d-*-inline-block utilities on two of
       these buttons win the display fight with !important, and a min-height on an
       inline-block button leaves its label sitting high. 12px + 18px content + borders
       lands on the 44px floor with the text still optically centred. */
    --bs-btn-padding-y: 12px;
    margin-block: -6px;
  }

  .ns-dz-tab-add {
    width: 44px;
  }

  /* Tray and sheet controls that a finger actually works through. These live in their own
     scrolling column or row, so extra height just scrolls — nothing collides. The signer
     chips are deliberately absent: on the phone strip they read best at their compact size
     (they sit in a horizontal row the whole width of which is already a target), and the
     ≥768px rail variant gets its floor in the touch+rail block further down. */
  .ns-dz-type,
  .ns-dz-tray-add,
  .ns-dz-cat-head,
  .ns-dz-seg-btn,
  .ns-dz-seg .btn,
  .ns-dz-search input {
    min-height: 44px;
  }

  /* Dense in-row controls keep their visual size — the designer's density is deliberate
     (see the pointer:coarse note by .ns-panel-link) — but each grows an invisible hit area
     out to the 44px floor. The overlap only ever reaches a neighbour whose tap does the
     same or a containing action (the rename pencil sits on the name that also opens
     rename; the row's ⚙ sits on the row that selecting is a subset of), so a near-miss
     stays a hit. */
  .ns-dz-collapse,
  .ns-dz-frow-rename,
  .ns-dz-fa,
  .ns-dz-toast-close {
    position: relative;
  }

  .ns-dz-collapse::after,
  .ns-dz-frow-rename::after,
  .ns-dz-fa::after,
  .ns-dz-toast-close::after {
    content: "";
    position: absolute;
  }

  .ns-dz-collapse::after { inset: -9px; }
  .ns-dz-frow-rename::after { inset: -13px; }
  .ns-dz-fa::after { inset: -10px; }
  .ns-dz-toast-close::after { inset: -11px; }
}

/* hover sweep — every interactive designer control reacts to the pointer */

.ns-chip {
  transition: border-color .15s ease, background .15s ease, color .15s ease, box-shadow .15s ease;
}

/* the tray's --ns-rc tracks the signer being placed for, so hovering (or arming) a
   field type answers "who is this for?" at a glance */
.ns-dz-types {
  --ns-rc: var(--ns-blue-600);
}

.ns-dz-type:hover:not(.armed):not(.ns-chip-disabled) {
  border-color: color-mix(in srgb, var(--ns-rc) 45%, #fff);
  background: color-mix(in srgb, var(--ns-rc) 9%, #fff);
}

.ns-dz-type:hover:not(.ns-chip-disabled) svg {
  color: var(--ns-rc);
}

.ns-dz-type.armed {
  border-color: var(--ns-rc);
  background: color-mix(in srgb, var(--ns-rc) 12%, #fff);
  color: var(--ns-rc);
}

.ns-dz-type.armed svg {
  color: var(--ns-rc);
}

.ns-dz-who {
  transition: border-color .15s ease, background .15s ease;
}

.ns-dz-who:hover:not(.active):not(:disabled) {
  border-color: var(--ns-border-strong);
  background: var(--ns-thead-bg);
}

.ns-dz-tab {
  transition: background .15s ease, border-color .15s ease;
}

.ns-dz-tab:hover:not(.active) {
  background: var(--ns-thead-bg);
}

.ns-dz-tray-tab:hover {
  background: var(--ns-thead-bg);
}

.ns-dz-rec {
  transition: border-color .15s ease, box-shadow .15s ease;
}

.ns-dz-docrow {
  transition: border-color .15s ease, box-shadow .15s ease;
}

.ns-dz-docrow:hover {
  border-color: var(--ns-info-border);
  box-shadow: var(--ns-shadow-card-sm);
}

.ns-dz-collapse:hover,
.ns-dz-fieldlist-rail:hover {
  background: var(--ns-divider);
}

.ns-dz-tab-add {
  transition: background .15s ease;
}

.ns-dz-armed button:hover {
  background: rgba(255, 255, 255, .28);
}

.ns-field-tools button {
  transition: background .13s ease, color .13s ease, filter .13s ease;
}

.ns-field-tools .ns-ft-config:hover,
.ns-field-tools .ns-ft-require:hover {
  background: rgba(255, 255, 255, .26);
}

.ns-field-tools .ns-ft-assign:hover {
  filter: brightness(1.15);
}

.ns-field-tools .ns-ft-require.on {
  color: var(--ns-cyan);
  background: rgba(12, 180, 216, .18);
}

.ns-dz-warn {
  transition: border-color .15s ease, box-shadow .15s ease;
}

.ns-dz-warn:hover {
  border-color: var(--ns-warning);
  box-shadow: var(--ns-shadow-card-sm);
}

.ns-dz-rec-badge,
.ns-dz-rec {
  transition: border-color .18s ease, background .18s ease, box-shadow .15s ease;
}

/* the whole motion layer bows out for users who ask for less of it */
@media (prefers-reduced-motion: reduce) {
  .ns-dz-enter-fwd,
  .ns-dz-enter-back,
  .ns-dz-leave-fwd,
  .ns-dz-leave-back,
  .ns-dz-pop-in,
  .ns-stage-panel.is-entering,
  .ns-dz-armed:not([hidden]),
  .ns-field-tools.ns-anim,
  .ns-field-config,
  .ns-dz-doc-enter,
  .ns-viewer-doc-in,
  .ns-viewer-doc-out,
  .ns-dz-toast,
  .ns-dz-toast.leaving {
    animation: none;
  }

  .ns-dz-fieldlist,
  .ns-dz-tray,
  .ns-dz-backdrop,
  .ns-dz-modal-backdrop,
  .ns-dz-modal,
  .ns-dz-knob,
  .ns-dz-knob::after,
  .ns-dz-cat-chev,
  .ns-dz-acc-bodywrap,
  .ns-dz-acc-itemwrap,
  .ns-dz-acc-chev,
  .ns-field-tools,
  .ns-field .ns-resize {
    transition: none;
  }
}

/* ?768px: the tray becomes a left rail, the fields list a right panel */

@media (min-width: 768px) {
  .ns-dz-crumbs {
    padding: 9px 20px;
    gap: 3px;
  }

  .ns-designer[data-step="docs"] .ns-dz-step[data-step-panel="docs"],
  .ns-designer[data-step="recipients"] .ns-dz-step[data-step-panel="recipients"],
  .ns-designer[data-step="review"] .ns-dz-step[data-step-panel="review"] {
    padding: 30px 24px 40px;
  }

  .ns-dz-col {
    max-width: 520px;
    gap: 14px;
  }

  .ns-dz-h {
    font-size: 21px;
  }

  .ns-designer[data-step="fields"] .ns-dz-step-fields {
    /* both side columns size to their content, so a collapsed panel takes its column
       with it instead of leaving an empty track behind */
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas:
      "tabs tabs tabs"
      "tray canvas list";
  }

  .ns-dz-canvas {
    padding: 20px;
  }

  .ns-dz-tray {
    border-top: 0;
    border-right: 1px solid #E9EEF4;
    background: var(--ns-thead-bg);
    overflow-y: auto;
  }

  .ns-dz-tray-tab {
    display: none;
  }

  .ns-dz-tray-body {
    padding: 12px;
    gap: 15px;
  }

  .ns-dz-whostrip {
    flex-direction: column;
    overflow: visible;
    padding-bottom: 0;
  }

  .ns-dz-who {
    width: 100%;
    border-radius: 10px;
    padding: 5px 8px;
  }

  .ns-dz-who-name {
    max-width: none;
  }

  .ns-dz-types {
    flex-direction: column;
    gap: 2px;
    overflow: visible; /* the phone strip's sideways scroll doesn't apply to the column */
  }

  /* The rail scrolls vertically, so here the vertical swipe is the browser's and the
     horizontal drag (out onto the page) is the placement code's. */
  .ns-dz-types .ns-chip {
    touch-action: pan-y;
  }

  /* categories become real boxes: header row + a collapsible chip stack */
  .ns-dz-cat {
    display: block;
    border-bottom: 1px solid var(--ns-divider);
    padding-bottom: 4px;
  }

  .ns-dz-cat:last-child {
    border-bottom: 0;
    padding-bottom: 0;
  }

  /* Open/close animates the same way the review accordion does: a grid whose single row
     goes 1fr → 0fr, so the chips slide up behind the header instead of vanishing. The
     inner list carries the padding — the sizer must be able to reach a true 0px. */
  .ns-dz-cat-body {
    display: grid;
    grid-template-rows: 1fr;
    transition: grid-template-rows .2s cubic-bezier(.2, .7, .2, 1), opacity .16s ease;
  }

  .ns-dz-cat-body > * {
    grid-row: 1;
  }

  /* No vertical padding on the clipped element: padding is part of its own box, so it
     would survive at 0fr and read as a stray gap under a closed category. The breathing
     room goes on the last chip instead, which clips away with it. */
  .ns-dz-cat-list {
    display: flex;
    flex-direction: column;
    gap: 5px;
    min-height: 0;
    overflow: hidden;
  }

  .ns-dz-cat-list > :last-child {
    margin-bottom: 4px;
  }

  .ns-dz-cat.closed .ns-dz-cat-body {
    grid-template-rows: 0fr;
    opacity: 0;
  }

  .ns-dz-type {
    flex: none;
    flex-direction: row;
    justify-content: flex-start;
    gap: 9px;
    padding: 8px 10px;
    font-size: 12px;
    border-radius: 10px;
  }

  .ns-dz-type .ns-dz-type-full {
    display: inline;
  }

  .ns-dz-type .ns-dz-type-short {
    display: none;
  }

  /* The panel opens and closes by animating its width - its inner content keeps a fixed
     min-width so text doesn't reflow mid-slide. */
  .ns-dz-fieldlist {
    --dz-list-w: 240px;
    position: static;
    /* Reset the mobile bottom-sheet's z-index: 1046 back to auto. A CSS grid item's z-index
       creates a stacking context even at position:static, so left unset here it would still
       stack above Bootstrap's dropdown menu (z-index 1000) and everything else on the bar. */
    z-index: auto;
    grid-area: list;
    width: var(--dz-list-w);
    max-height: none;
    transform: none;
    border-radius: 0;
    box-shadow: none;
    border-left: 1px solid #E9EEF4;
    min-height: 0;
    overflow: hidden;
    transition: width .22s cubic-bezier(.2, .7, .2, 1), opacity .18s ease, border-color .22s ease;
  }

  .ns-dz-seg,
  .ns-dz-insp,
  .ns-dz-outwrap {
    min-width: var(--dz-list-w);
  }

  .ns-designer.ns-dz-list-closed .ns-dz-fieldlist {
    width: 0;
    opacity: 0;
    border-left-color: transparent;
  }

  .ns-dz-fieldlist-rail {
    grid-area: list;
    writing-mode: vertical-rl;
    align-items: center;
    justify-content: center;
    gap: 4px;
    width: 32px;
    border: 0;
    border-left: 1px solid #E9EEF4;
    background: #fff;
    color: var(--ns-slate);
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .05em;
    cursor: pointer;
    font-family: inherit;
  }

  .ns-designer.ns-dz-list-closed .ns-dz-fieldlist-rail {
    display: flex;
    animation: ns-dz-fade-in .18s ease .12s both;
  }

  .ns-dz-backdrop {
    display: none !important;
  }

  .ns-dz-status {
    left: auto;
    right: 18px;
    transform: none;
    bottom: 18px;
  }

  /* Review becomes two columns: the summary stays put on the left while the
     document preview scrolls on the right. */
  .ns-designer[data-step="review"] .ns-dz-step[data-step-panel="review"] {
    display: flex;
    align-items: stretch;
    padding: 0;
    overflow: hidden;
    scrollbar-gutter: auto;
  }

  .ns-designer[data-step="review"] .ns-dz-step[data-step-panel="review"] .ns-dz-col {
    flex: none;
    width: 380px;
    max-width: none;
    min-height: 0; /* a flex item's min-height:auto would outgrow the panel and never scroll */
    margin: 0;
    padding: 28px 24px 34px;
    overflow-y: auto;
    border-right: 1px solid var(--ns-border);
    background: var(--ns-surface);
  }

  /* the column is itself a flex stack - its children must keep their natural height
     (not shrink to fit) or the overflow scrollbar never engages */
  .ns-designer[data-step="review"] .ns-dz-step[data-step-panel="review"] .ns-dz-col > * {
    flex: none;
  }

  .ns-dz-review-docswrap {
    flex: 1;
    min-width: 0;
    min-height: 0;
    margin: 0;
    display: flex;
  }

  .ns-dz-review-docs {
    flex: 1;
    min-width: 0;
    border-top: 0;
    overflow-y: auto;
    padding: 22px 20px 34px;
  }

  /* the left tray mirrors the fields panel: width animates shut to a rail */
  .ns-dz-tray {
    --dz-tray-w: 200px;
    width: var(--dz-tray-w);
    overflow-x: hidden; /* clip during the width animation; vertical scroll stays */
    transition: width .22s cubic-bezier(.2, .7, .2, 1), opacity .18s ease, border-color .22s ease;
  }

  .ns-dz-tray .ns-dz-tray-body {
    min-width: var(--dz-tray-w);
  }

  .ns-designer.ns-dz-tray-closed .ns-dz-tray {
    width: 0;
    opacity: 0;
    border-right-color: transparent;
  }

  .ns-dz-tray-rail {
    display: none;
    grid-area: tray;
    writing-mode: vertical-rl;
    align-items: center;
    justify-content: center;
    gap: 4px;
    width: 32px;
    border: 0;
    border-right: 1px solid #E9EEF4;
    background: #fff;
    color: var(--ns-slate);
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .05em;
    cursor: pointer;
    font-family: inherit;
  }

  .ns-dz-tray-rail:hover {
    background: var(--ns-divider);
  }

  .ns-designer.ns-dz-tray-closed .ns-dz-tray-rail {
    display: flex;
    animation: ns-dz-fade-in .18s ease .12s both;
  }
}

@media (min-width: 768px) and (prefers-reduced-motion: reduce) {
  .ns-dz-fieldlist {
    transition: none;
  }

  .ns-designer.ns-dz-list-closed .ns-dz-fieldlist-rail {
    animation: none;
  }
}

@keyframes ns-dz-fade-in {
  from {
    opacity: 0;
  }
}

/* ?1100px: widen the rails to their desktop proportions */

@media (min-width: 1100px) {
  .ns-dz-tray {
    --dz-tray-w: 230px;
  }

  .ns-dz-fieldlist {
    --dz-list-w: 280px;
  }

  .ns-dz-tab {
    max-width: 200px;
  }

  .ns-dz-canvas {
    padding: 22px;
  }
}

/* Touch + rails: the collapsed-panel strips widen to the 44px floor, and the signer chips —
   full-width rows in the left rail from 768px — take the floor here, where the taller row
   just scrolls. After the ≥768px block because that is where their compact sizes are set. */
@media (hover: none) and (min-width: 768px) {
  .ns-dz-fieldlist-rail,
  .ns-dz-tray-rail {
    width: 44px;
  }

  .ns-dz-who {
    min-height: 44px;
  }
}

/* signer flow: navy step bar + centered cards + split ceremony (design 3o-3q) */

/* Fixed-height app shell, like .ns-designer: the ceremony's document column scrolls inside
   it so the rail stays a full-height panel. Steps that aren't the ceremony (.ns-sign-center)
   scroll on their own.

   The kiosk's shell is the same declaration and shares it from here rather than repeating it
   down in the kiosk runtime section, because that section is BELOW the max-width: 900px block
   that relaxes both shells for the stacked layout. A media query adds no specificity, so a
   duplicate down there silently won the cascade and the kiosk stayed clipped at every width -
   a signer on a kiosk tablet got the actions rail and could not scroll to the document at all.
   Declared once, above the reset, that cannot come back. */
.ns-sign-shell,
.ns-kiosk-body {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh; /* mobile browsers: track the visible viewport, not the largest one */
  overflow: hidden;
  background: var(--ns-canvas);
}

.ns-sign-bar {
  background: var(--ns-navy-900);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 18px;
  flex: none;
}

.ns-sign-bar-note {
  font-size: 11.5px;
  color: #B8D3E6;
}

.ns-sign-center {
  flex: 1;
  min-height: 0;
  overflow-y: auto; /* the shell no longer scrolls, so short viewports scroll here */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
  animation: ns-rise .18s ease both;
}

.ns-steps {
  display: flex;
  gap: 6px;
}

.ns-steps span {
  flex: 1;
  height: 4px;
  border-radius: 2px;
  background: var(--ns-canvas-deep);
}

.ns-steps span.active {
  background: var(--ns-blue-600);
}

.ns-otp-input {
  font-family: var(--ns-font-mono);
  font-size: 20px;
  text-align: center;
  letter-spacing: .55em;
  padding-left: calc(12px + .55em);
}

/* The shared partial's root sits between .ns-sign-shell (a flex column) and .ns-ceremony,
   so it has to relay the flex/height chain - otherwise .ns-ceremony's flex:1/min-height:0
   are inert and the sticky rail card can't size to its container. */
#ceremony {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.ns-sign-shell > #ceremony,
.ns-kiosk-main > #ceremony {
  flex: 1;
}

.ns-ceremony {
  display: flex;
  align-items: stretch;
  flex: 1;
  min-height: 0;
}

/* The document column is the scroller, not the page - that's what lets the rail beside it be
   a full-height panel rather than a card floating in an empty strip, and it keeps the rail
   on screen without sticky. Every ceremony shell is fixed-height for this reason
   (.ns-sign-shell, .ns-kiosk-body, .ns-designer). */
.ns-ceremony-doc {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  background: var(--ns-canvas-deep);
  padding: 14px 18px;
}

/* One element: the flex slot beside the document *is* the panel. It fills the row's height
   by stretching, so there's no sticky, no max-height and no gap to leave a strip showing. */
.ns-ceremony-rail {
  flex: none;
  width: 300px;
  display: flex;
  flex-direction: column;
  background: #fff;
  border-left: 1px solid var(--ns-border);
}

.ns-progress {
  height: 6px;
  border-radius: 3px;
  background: #E9EEF4;
  overflow: hidden;
}

.ns-progress > div {
  width: 0;
  height: 100%;
  background: var(--ns-blue-600);
  transition: width .2s ease, background-color .2s ease;
}

/* Every required field done - the bar and its label go green together, so "you can finish"
   is one signal rather than something to infer from a count. */
.ns-progress.is-ready > div {
  background: var(--ns-success);
}

.ns-cer-progress-label {
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ns-blue-600);
}

.ns-cer-progress-label.is-ready {
  color: var(--ns-success-text);
}

/* Optional work outstanding - named under the bar, deliberately quiet: it never blocks
   finishing, so it must not read as something the signer still owes. */
.ns-cer-optional {
  margin-top: 6px;
  font-size: 10.5px;
  color: var(--ns-muted);
}

/* Why the document didn't change language: an earlier signature settled which translation this
   envelope is executed in. Informational, not a warning - nothing is wrong. */
.ns-cer-langnote {
  margin-top: 6px;
  padding: 5px 7px;
  border-radius: 7px;
  border: 1px solid var(--ns-info-border-soft);
  background: color-mix(in srgb, var(--ns-blue-600) 6%, #fff);
  color: var(--ns-slate);
  font-size: 10px;
  line-height: 1.45;
}

/* ---------- ceremony actions rail: one card per actionable field (design 3p) ---------- */

.ns-ceremony-actions {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.ns-action-help {
  margin: 0 2px 4px;
  font-size: 10.5px;
  color: var(--ns-muted);
}

.ns-action-card {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  text-align: left;
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-control);
  cursor: pointer;
  transition: border-color .16s ease, box-shadow .16s ease, background .16s ease;
}

.ns-action-card:hover {
  border-color: var(--ns-info-border);
  box-shadow: var(--ns-shadow-card-sm);
}

.ns-action-card:focus-visible {
  outline: none;
  border-color: var(--ns-blue-600);
  box-shadow: var(--ns-focus-ring);
}

.ns-action-card.is-current {
  border-color: var(--ns-blue-600);
  background: rgba(var(--ns-primary-rgb),.06);
}

.ns-action-ico {
  flex: none;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  color: #fff;
}

.ns-action-ico svg {
  width: 17px;
  height: 17px;
}

/* per-type hues mirror the on-document field colors */
.ns-action-ico-Signature { background: #E8710A; }
.ns-action-ico-Initial   { background: #14A97C; }
.ns-action-ico-Checkbox,
.ns-action-ico-Text,
.ns-action-ico-DatePicker { background: var(--ns-blue-600); }

.ns-action-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.ns-action-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-action-sub {
  font-size: 10.5px;
  color: var(--ns-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-action-req {
  flex: none;
  padding: 1px 6px;
  border-radius: 999px;
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ns-maple);
  background: rgba(237, 28, 36, .09);
}

.ns-action-status {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  color: var(--ns-border-strong);
}

.ns-action-status svg {
  width: 18px;
  height: 18px;
}

.ns-action-card.done {
  border-color: var(--ns-success);
  background: rgba(20, 143, 91, .05);
}

.ns-action-card.done .ns-action-status { color: var(--ns-success); }
.ns-action-card.done .ns-action-title { color: var(--ns-slate); }

.ns-action-empty {
  padding: 12px 4px;
  font-size: 11px;
  color: var(--ns-muted);
  text-align: center;
}

/* the on-document field a rail card points at, briefly ringed after a jump */
.ns-field-flash {
  animation: ns-field-flash 1.2s ease;
}

@keyframes ns-field-flash {
  0%, 100% { box-shadow: 0 0 0 0 rgba(var(--ns-primary-rgb),0); }
  18% { box-shadow: 0 0 0 4px rgba(var(--ns-primary-rgb),.5); }
}

@media (max-width: 900px) {
  /* Stacked, so the two-column app shell relaxes: the page scrolls again rather than the
     document column, and the rail becomes a band above it instead of a side panel. */
  .ns-sign-shell,
  .ns-kiosk-body {
    height: auto;
    min-height: 100vh;
    overflow: visible;
  }

  /* The rail moves above the document with `order`, never `column-reverse`: a reversed flex
     container overflows *upward*, so on a short viewport the document's start laid out above
     the page's scroll origin — the signer landed on the end of the document and nothing could
     scroll up to the rest (scrollHeight never grows for upward overflow). */
  .ns-ceremony {
    flex-direction: column;
  }

  .ns-ceremony-rail {
    order: -1;
  }

  .ns-ceremony-doc {
    overflow-y: visible;
  }

  .ns-ceremony-actions {
    overflow-y: auto;
    max-height: 320px;
  }

  .ns-ceremony-rail {
    width: auto;
    border-left: 0;
    border-bottom: 1px solid var(--ns-border);
  }
}

/* Touch: the signer surfaces are touch-first — a lobby kiosk tablet has no mouse at all —
   so every button a signer can reach meets the 44px floor (docs/design/polish-pass.md §4).
   inline-flex so the label stays centred in the taller box; none of these carry a d-*
   display utility that would fight it (btn-next-field's d-none still wins, which is its job). */
@media (pointer: coarse) {
  .ns-sign-shell .btn,
  .ns-kiosk-body .btn,
  #ceremony .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
  }

  /* The navy top bar hands the growth back (same trick as .ns-designer-back), so the
     kiosk chrome keeps its height while the language switcher gains its target. */
  .ns-sign-bar .btn {
    margin-block: -7px;
  }
}

/* kiosk runtime (design 3t-3v) */

/* .ns-kiosk-body's own shell rule is NOT here - it is declared with .ns-sign-shell, above the
   max-width: 900px block that relaxes it. Putting it back here would re-break kiosk scrolling. */

.ns-kiosk-bar-side {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 8px; /* the right side carries the language switcher, and room for anything added beside it */
  min-width: 0; /* at 360px the switcher's language name gives way rather than overflowing the bar */
}

/* Launched from the home screen, the kiosk draws under the status bar (viewport-fit=cover in
   _KioskLayout, with apple-mobile-web-app-status-bar-style: black-translucent), so the bar pads
   itself clear of the clock rather than letting it sit on the logo. env() resolves to 0 in an
   ordinary browser tab and on a device with no inset, so this is inert everywhere but the
   standalone case it exists for. The base padding is restated from .ns-sign-bar above - keep the
   two in step. Scoped to the kiosk: /Sign shares that bar but is never installed to a home
   screen. */
.ns-kiosk-body .ns-sign-bar {
  padding-top: calc(11px + env(safe-area-inset-top));
  padding-left: calc(18px + env(safe-area-inset-left));
  padding-right: calc(18px + env(safe-area-inset-right));
}

.ns-kiosk-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.ns-kiosk-attract {
  position: relative;
  flex: 1;
  background: linear-gradient(160deg, var(--ns-navy-900), var(--ns-navy-800));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 6vh 34px;
  color: #fff;
  overflow: hidden;
}

/* Ambient moving background: two drifting glows, a sweeping aurora band, and a
   masked dot texture. All decorative (aria-hidden) and stilled under reduced motion. */
.ns-attract-glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(60px);
  will-change: transform;
  pointer-events: none;
}

.ns-attract-glow-a {
  width: 52vmax;
  height: 52vmax;
  top: -14vmax;
  left: -10vmax;
  background: radial-gradient(circle at 50% 50%, rgba(var(--ns-accent-rgb),.6), rgba(var(--ns-accent-rgb),0) 70%);
  animation: ns-drift-a 26s ease-in-out infinite;
}

.ns-attract-glow-b {
  width: 46vmax;
  height: 46vmax;
  bottom: -16vmax;
  right: -8vmax;
  background: radial-gradient(circle at 50% 50%, rgba(var(--ns-primary-rgb),.65), rgba(var(--ns-primary-rgb),0) 70%);
  animation: ns-drift-b 32s ease-in-out infinite;
}

.ns-attract-aurora {
  position: absolute;
  top: -30%;
  left: 15%;
  width: 55%;
  height: 160%;
  pointer-events: none;
  filter: blur(50px);
  background: linear-gradient(90deg, rgba(var(--ns-accent-rgb),0), rgba(var(--ns-accent-rgb),.16), rgba(var(--ns-accent-rgb),0));
  animation: ns-aurora 20s ease-in-out infinite;
}

.ns-attract-grid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(rgba(255, 255, 255, .05) 1px, transparent 1px);
  background-size: 26px 26px;
  opacity: .35;
  -webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 45%, #000 40%, transparent 78%);
  mask-image: radial-gradient(ellipse 70% 60% at 50% 45%, #000 40%, transparent 78%);
}

.ns-attract-content {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  animation: ns-rise .4s ease both;
}

.ns-kiosk-attract-eyebrow {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  /* The accent lightened toward white: the raw seed only just clears 3:1 on the nav
     gradient behind it, and this is 15px text. */
  color: color-mix(in srgb, var(--ns-accent) 70%, #fff);
  margin-bottom: 14px;
}

.ns-kiosk-attract h1 {
  margin: 0 0 16px;
  font-size: clamp(38px, 5.4vw, 58px);
  font-weight: 800;
  line-height: 1.05;
  color: #fff;
  max-width: 14ch;
  text-wrap: balance;
}

.ns-kiosk-attract p {
  margin: 0 0 44px;
  max-width: 30ch;
  font-size: clamp(17px, 1.9vw, 21px);
  color: rgba(255, 255, 255, .9);
  line-height: 1.55;
  text-wrap: pretty;
}

/* CTA: the wrapper's 2px padding is the "border"; a slow conic sheen rotates through it */
.ns-cta-wrap {
  position: relative;
  overflow: hidden;
  border-radius: 999px;
  padding: 2px;
  background: rgba(255, 255, 255, .14);
  box-shadow: 0 12px 34px rgba(var(--ns-accent-rgb),.34);
  transition: transform .16s ease;
}

.ns-cta-wrap:hover {
  transform: translateY(-1px);
}

/* The ring lands on the wrapper, not the link: .ns-cta's own box-shadow would be clipped by
   the overflow:hidden that contains the sheen. Whites rather than --ns-focus-ring, for the
   same reason .ns-designer-back uses them — a 15% tint of the primary seed is invisible over
   the nav gradient. The wrapper's resting glow is repeated so focus adds to it. */
.ns-cta-wrap:has(.ns-cta:focus-visible) {
  box-shadow: 0 0 0 3px rgba(255, 255, 255, .55), 0 12px 34px rgba(var(--ns-accent-rgb),.34);
}

.ns-cta-sheen {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 320%;
  aspect-ratio: 1;
  border-radius: 50%;
  pointer-events: none;
  z-index: 0;
  transform: translate(-50%, -50%);
  background: conic-gradient(from 0deg, rgba(255, 255, 255, 0) 0deg, rgba(255, 255, 255, .9) 45deg, rgba(255, 255, 255, 0) 95deg, rgba(255, 255, 255, 0) 360deg);
  animation: ns-cta-sheen 16s linear infinite;
}

.ns-cta {
  position: relative;
  z-index: 1;
  display: block;
  background: var(--ns-cyan);
  color: var(--ns-on-accent);
  border: none;
  border-radius: 999px;
  padding: 20px 58px;
  font-size: clamp(18px, 2vw, 23px);
  font-weight: 800;
}

.ns-cta:hover {
  color: var(--ns-on-accent);
  text-decoration: none;
}

.ns-kiosk-attract-trust {
  margin-top: 52px;
  font-size: 13px;
  color: rgba(255, 255, 255, .8);
  display: flex;
  align-items: center;
  gap: 7px;
}

@keyframes ns-drift-a {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(6%, 5%, 0) scale(1.12); }
  100% { transform: translate3d(0, 0, 0) scale(1); }
}

@keyframes ns-drift-b {
  0%   { transform: translate3d(0, 0, 0) scale(1.05); }
  50%  { transform: translate3d(-7%, -4%, 0) scale(1); }
  100% { transform: translate3d(0, 0, 0) scale(1.05); }
}

@keyframes ns-aurora {
  0%   { transform: translateX(-18%) rotate(-8deg); opacity: .5; }
  50%  { transform: translateX(12%) rotate(-8deg);  opacity: .9; }
  100% { transform: translateX(-18%) rotate(-8deg); opacity: .5; }
}

@keyframes ns-cta-sheen {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}

/* Reduced motion: still the ambient background and CTA sheen; keep the layout intact. */
@media (prefers-reduced-motion: reduce) {
  .ns-attract-glow-a,
  .ns-attract-glow-b,
  .ns-attract-content { animation: none; }
  .ns-attract-aurora { animation: none; opacity: .6; }
  .ns-cta-sheen { animation: none; opacity: 0; }
}

/* role dropdown (dot + label + description) */

.ns-role-trigger {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  background: #fff;
  border: 1px solid var(--ns-border-strong);
  border-radius: var(--ns-radius-control);
  padding: 8px 12px;
  text-align: left;
  transition: border-color .16s ease, box-shadow .16s ease;
}

.ns-role-trigger:hover:not(:disabled) {
  border-color: var(--ns-info-border);
}

.ns-role-trigger:focus-visible {
  outline: none;
  border-color: var(--ns-blue-600);
  box-shadow: var(--ns-focus-ring);
}

.ns-role-trigger:disabled {
  background: var(--ns-thead-bg);
  color: var(--ns-muted);
}

.ns-role-trigger > svg {
  color: var(--ns-muted);
  flex: none;
}

.ns-role-trigger-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.ns-role-trigger-text > span:first-child {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-role-trigger-text > span:nth-child(2) {
  font-size: 10.5px;
  color: var(--ns-muted);
}

.ns-role-item {
  display: flex;
  align-items: center;
  gap: 9px;
}

/* select drawer (plain select, any value) — same "native, enhanced" recipe as the role dropdown
   above, minus the dot/description: a trigger that reproduces .form-select's own box exactly (so
   nothing shifts when JS takes over) opening a .dropdown-menu-style panel instead of the browser's
   unstyled native popup, with the real <select> kept behind it as the posted field. Applies to
   every plain <select class="form-select"> site-wide. Because the chevron is a real flex child
   here (not an OS-drawn background-image like a native select's), the text-vs-chevron collision
   .form-select needs padding-right:2rem for never happens — ordinary symmetric padding is enough.
   docs/design/components/dropdowns-and-selects.md */

.ns-select-trigger {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background-color: var(--ns-surface);
  border: 1px solid var(--ns-border-strong);
  border-radius: var(--ns-radius-control);
  padding: 9px 12px;
  font-size: 13.5px;
  color: var(--ns-ink);
  text-align: left;
  transition: border-color .16s ease, box-shadow .16s ease;
}

.ns-select-sm .ns-select-trigger {
  border-radius: 8px;
  font-size: 12.5px;
  padding: 6px 10px;
}

/* Bootstrap's .w-auto shrinks a real <select> to its content because a form control has its own
   intrinsic auto-width; a plain <div> does not, so the shrink has to be spelled out for whichever
   wrapper inherits the class (System/Health's two filter selects, today). */
.ns-select.w-auto {
  display: inline-block;
}

.ns-select.w-auto .ns-select-trigger {
  width: auto;
}

.ns-select-trigger:hover:not(:disabled) {
  border-color: var(--ns-info-border);
}

.ns-select-trigger:focus-visible {
  outline: none;
  border-color: var(--ns-blue-600);
  box-shadow: var(--ns-focus-ring);
}

.ns-select-trigger[aria-expanded="true"] {
  border-color: var(--ns-blue-600);
}

.ns-select-trigger:disabled {
  background: var(--ns-thead-bg);
  color: var(--ns-muted);
  cursor: not-allowed;
}

.ns-select-trigger-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-select-trigger > svg {
  flex: none;
  color: var(--ns-muted);
  transition: transform .16s ease;
}

.ns-select-trigger[aria-expanded="true"] > svg {
  transform: rotate(180deg);
}

.ns-select-menu {
  max-height: 280px;
  overflow-y: auto;
}

/* Keyed off Bootstrap's own .show toggle (this menu opens via data-bs-toggle="dropdown", not the
   [hidden]-attribute pattern the designer's pickers use) but the same entrance every floating
   panel on the site uses. */
.ns-select-menu.show {
  animation: ns-dz-menu-in .16s cubic-bezier(.2, .7, .2, 1) both;
  transform-origin: top center;
}

@media (prefers-reduced-motion: reduce) {
  .ns-select-menu.show { animation: none; }
}

.ns-select-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
}

.ns-select-item > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-select-item > svg {
  flex: none;
}

.ns-select-item:disabled {
  color: var(--ns-muted);
  cursor: not-allowed;
}

/* operator pill (deployment-wide) */

.ns-pill-operator {
  background: var(--ns-navy-900);
  color: #fff;
}

/* tinted consent row (kiosk intro) */

.ns-consent {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  padding: 11px 12px;
  border: 1px solid var(--ns-info-border-soft);
  background: var(--ns-blue-50);
  border-radius: 11px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ns-navy-800);
  line-height: 1.45;
  cursor: pointer;
}

/* preview (dry-run) banner */

.ns-preview-banner {
  background: var(--ns-warning);
  color: #fff;
  border-radius: var(--ns-radius-card);
  padding: 9px 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 12px;
  font-weight: 700;
}

.ns-preview-banner .form-select {
  background-color: rgba(255, 255, 255, .2);
  border: none;
  color: #fff;
  font-weight: 600;
  font-size: 11.5px;
}

/* Preview lives in the designer chrome now: the banner becomes a full-width strip
   under the navy bar, and the ceremony partial inherits the shell's flex column. */
.ns-dz-preview-strip {
  border-radius: 0;
  flex: none;
}

.ns-designer > #ceremony {
  flex: 1;
  min-height: 0;
}

/* ---------- preview (dry-run) switcher: recipient chip + role dropdown ----------
   Mirrors the designer's recipient language - colour dot, signing-order number, and a
   Signer/Reviewer tag - so the sender recognises who they're previewing as. */

.ns-preview-note {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.ns-preview-view {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.ns-preview-view-lbl {
  font-size: 11.5px;
  font-weight: 600;
  opacity: .9;
}

.ns-preview-seq {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .18);
  font-size: 11px;
  font-weight: 700;
}

/* Language toggle on the preview strip - segmented, sitting on the amber banner like the
   sequential badge. Only rendered when the documents are actually translated. */
.ns-preview-langs {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .18);
}

.ns-preview-lang {
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  color: inherit;
  text-decoration: none;
  white-space: nowrap;
}

.ns-preview-lang:hover {
  background: rgba(255, 255, 255, .22);
  color: inherit;
}

.ns-preview-lang.active {
  background: #fff;
  /* Must name a real token: `--ns-navy` doesn't exist, so the declaration was dropped and the
     label inherited the banner's white - an invisible label on a white pill. */
  color: var(--ns-navy-900);
}

/* the "Viewing as" control: a recipient chip that opens the signer menu */
.ns-preview-switch {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  max-width: 230px;
  padding: 3px 9px 3px 4px;
  border: 1px solid rgba(255, 255, 255, .28);
  border-radius: 999px;
  background: rgba(255, 255, 255, .16);
  color: #fff;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}

.ns-preview-switch:hover,
.ns-preview-switch[aria-expanded="true"] {
  background: rgba(255, 255, 255, .26);
  border-color: rgba(255, 255, 255, .45);
}

.ns-preview-switch-static {
  cursor: default;
}

.ns-preview-switch-name {
  min-width: 0;
  max-width: 150px;
}

.ns-preview-caret {
  flex: none;
  opacity: .85;
}

/* the signer menu - white card, one row per recipient with colour dot + role tag */
.ns-preview-menu {
  --bs-dropdown-min-width: 15rem;
  padding: 6px;
  margin-top: 6px;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
  box-shadow: var(--ns-shadow-dropdown);
}

.ns-preview-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 9px;
  border-radius: var(--ns-radius-control);
}

.ns-preview-item.active,
.ns-preview-item.active:focus,
.ns-preview-item.active:hover {
  background: rgba(var(--ns-primary-rgb), .1);
  color: var(--ns-ink);
}

.ns-preview-item-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  line-height: 1.25;
}

.ns-preview-item-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-preview-item-role {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ns-muted);
  text-transform: uppercase;
  letter-spacing: .04em;
}

.ns-preview-check {
  flex: none;
  color: var(--ns-blue-600);
}

/* Preview keeps the document primary on small screens: the shared ceremony flips to
   column-reverse (actions rail above the doc) for real signing, but a sender previewing
   wants to see what actually goes out first - so the dry-run keeps the document on top. */
@media (max-width: 900px) {
  .ns-designer .ns-ceremony {
    flex-direction: column;
  }
}

/* On desktop the preview keeps its chrome (bar, strip, rail) fixed and scrolls only the
   document column - so a sender scrolling a long packet never loses the switcher or rail. */
@media (min-width: 901px) {
  .ns-designer .ns-ceremony-doc {
    overflow-y: auto;
    min-height: 0;
  }
}

/* Smoothly grow/shrink a document body when it collapses/expands - doc-nav.js drives the
   inline height, and falls back to the instant .ns-collapsed display:none below when the
   viewer prefers reduced motion. */
/* Matches the app's other collapse (.2s height / .18s opacity) rather than running slower
   than everything around it. Keep COLLAPSE_MS in doc-nav.js in step with the height value. */
.ns-doc-pages.ns-anim {
  overflow: hidden;
  transition: height .2s ease, opacity .18s ease;
}

/* the active "Viewing as" recipient shows the check (rows all carry it, hidden until active) */
.ns-preview-check { visibility: hidden; }
.ns-preview-item.active .ns-preview-check { visibility: visible; }

/* ---------- ceremony load state + field entrance ---------- */

/* full-cover spinner while the (slow) pdf.js render runs - never a blank canvas */
.ns-ceremony-doc { position: relative; }

.ns-cer-loading {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: var(--ns-canvas-deep);
  color: var(--ns-muted);
  font-size: 12.5px;
  font-weight: 600;
  transition: opacity .25s ease;
}

.ns-cer-loading-out { opacity: 0; }

.ns-spinner {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 3px solid var(--ns-border);
  border-top-color: var(--ns-blue-600);
  animation: ns-spin .7s linear infinite;
}

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

/* fields fade + rise in as they mount - on first load and on every signer switch */
.ns-field-in { animation: ns-field-in .28s ease both; }

@keyframes ns-field-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* the preview renders both rail variants; show only the active recipient's kind */
#ceremony[data-reviewer="true"] .ns-cer-signer { display: none; }
#ceremony[data-reviewer="false"] .ns-cer-reviewer { display: none; }

@media (prefers-reduced-motion: reduce) {
  .ns-spinner { animation-duration: 1.6s; }
  .ns-field-in { animation: none; }
}

/* ==========================================================================
   Functional styles: PDF viewer, document navigator, designer, ceremony.
   (Layout rules predate the design pass; colors mapped onto the tokens.)
   ========================================================================== */

/* ---------- PDF viewer ---------- */

#pdf-container,
.ns-doc-pages {
  display: flex;
  flex-direction: column;
  /* `safe`: a page wider than the column starts at its left edge and scrolls, instead of
     plain centering pushing its left half above the scroll origin where nothing can reach it. */
  align-items: safe center;
  gap: 1rem;
}

/* ---------- multi-document sections ---------- */

.ns-doc-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: .75rem;
  max-width: 860px;
  margin: 0 auto .5rem;
  padding: .35rem .75rem;
  border: 1px solid var(--ns-border);
  border-radius: .5rem;
  background: var(--ns-thead-bg);
  font-size: .85rem;
}

/* Filename grows so the collapse chevron sits left and any action (Remove) stays right. */
.ns-doc-header > .text-truncate {
  flex: 1 1 auto;
  min-width: 0;
}

.ns-doc-toggle {
  flex: 0 0 auto;
  width: 1.25rem;
  text-decoration: none;
  line-height: 1;
  font-size: .8rem;
}

.ns-doc-pages.ns-collapsed {
  display: none;
}

/* ---------- fixed document navigator ---------- */

.ns-docnav {
  position: fixed;
  top: 5rem;
  right: 1rem;
  z-index: 1030; /* below Bootstrap modals (1055) so signature capture covers it */
  width: 15rem;
  max-height: calc(100vh - 7rem);
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
  box-shadow: var(--ns-shadow-dropdown);
}

.ns-docnav.ns-docnav-hidden {
  display: none;
}

.ns-docnav-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: .5rem .75rem;
  border-bottom: 1px solid var(--ns-divider);
}

.ns-docnav-title {
  font-weight: 700;
  font-size: .85rem;
}

.ns-docnav-head .btn-close {
  font-size: .65rem;
}

.ns-docnav-list {
  list-style: none;
  margin: 0;
  padding: .25rem;
  overflow-y: auto;
}

.ns-docnav-item {
  display: flex;
  align-items: center;
  gap: .25rem;
  border-radius: .375rem;
}

.ns-docnav-item:hover {
  background: var(--ns-divider);
}

.ns-docnav-item.complete .ns-docnav-name {
  color: var(--ns-muted);
}

.ns-docnav-collapse {
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--ns-muted);
  width: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

.ns-docnav-link {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: .35rem;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: .3rem .25rem;
  text-align: left;
  cursor: pointer;
  font-size: .85rem;
}

.ns-docnav-num {
  flex: 0 0 auto;
  color: var(--ns-muted);
}

.ns-docnav-name {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-docnav-check {
  flex: 0 0 auto;
  color: var(--ns-success);
  font-weight: 700;
}

.ns-docnav-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: .4rem;
  border-top: 1px solid var(--ns-divider);
}

.ns-docnav-tab {
  position: fixed;
  top: 5rem;
  right: 0;
  z-index: 1030;
  border: 1px solid var(--ns-border);
  border-right: 0;
  border-radius: .5rem 0 0 .5rem;
  background: #fff;
  box-shadow: var(--ns-shadow-card-sm);
  padding: .4rem .55rem;
  cursor: pointer;
}

/* On narrow screens dock the navigator to the bottom instead of the side. */
@media (max-width: 767px) {
  .ns-docnav {
    top: auto;
    right: 0;
    left: 0;
    bottom: 0;
    width: auto;
    max-height: 45vh;
    border-radius: .5rem .5rem 0 0;
    /* Docked to the bottom edge now, so the home-indicator inset joins its padding. */
    padding-bottom: env(safe-area-inset-bottom);
  }

  .ns-docnav-tab {
    top: auto;
    bottom: calc(1rem + env(safe-area-inset-bottom));
    border-radius: .5rem 0 0 .5rem;
  }
}

/* Touch: the navigator is worked with a finger on the very tablets the ceremony ships to.
   Rows and pager grow to the 44px floor; the two icon-only atoms (the header's ✕, the
   per-document chevron in .ns-doc-header) keep their size and gain an invisible ::after
   hit area — anything they could overlap shares their subject. */
@media (pointer: coarse) {
  .ns-docnav-link,
  .ns-docnav-collapse {
    min-height: 44px;
  }

  .ns-docnav-collapse {
    width: 1.9rem;
  }

  .ns-docnav-pager .btn {
    min-width: 44px;
    min-height: 44px;
  }

  .ns-docnav-tab {
    padding: .8rem .9rem;
  }

  .ns-docnav-head .btn-close,
  .ns-doc-toggle {
    position: relative;
  }

  .ns-docnav-head .btn-close::after,
  .ns-doc-toggle::after {
    content: "";
    position: absolute;
    inset: -15px; /* both are ~14px glyphs — this reaches the 44px floor */
  }
}

.ns-page {
  position: relative;
  background: #fff;
  box-shadow: 0 1px 4px rgba(16, 40, 70, .3);
}

.ns-page canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.ns-overlay {
  position: absolute;
  inset: 0;
}

/* ---------- designer fields ---------- */

/* Each field wears its signer's color (--ns-rc, set inline by the designer);
   the field TYPE is told apart by border style and icon, not color. */
.ns-field {
  --ns-rc: var(--ns-blue-600);
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px dashed var(--ns-rc);
  background: color-mix(in srgb, var(--ns-rc) 10%, transparent);
  color: var(--ns-rc);
  font-size: .78rem;
  font-weight: 600;
  border-radius: .25rem;
  cursor: grab;
  user-select: none;
  touch-action: none;
}

/* The label clips itself (min-width:0 lets the flex item shrink) so the field
   doesn't need overflow:hidden - which would otherwise clip the resize handles
   and remove button that sit just outside the border. */
.ns-field .ns-field-label {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  padding: 0 .2rem;
}

.ns-field .ns-field-label svg {
  flex: none;
}

/* A group frame on the canvas: an outline with its name on the edge. It sits behind the
   fields it holds, so only its own border and legend catch the pointer. */
.ns-field.ns-field-frame {
  background: transparent;
  border-style: solid;
  border-width: 1.5px;
  z-index: 0;
}

.ns-field .ns-field-legend {
  position: absolute;
  top: -.6em;
  left: .35rem;
  padding: 0 .25rem;
  background: #fff;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Complex fields preview their real sub-layout on the canvas (display-only — the box
   itself still takes the pointer for move/select/resize). */
.ns-field .ns-field-choices {
  position: absolute;
  inset: 0;
  padding: .15rem .3rem;
  display: flex;
  flex-direction: column;
  gap: .1rem;
  overflow: hidden;
  pointer-events: none;
}

.ns-field .ns-field-choices.ns-choice-inline {
  flex-direction: row;
  align-items: center;
  justify-content: space-around;
  gap: .4rem;
}

.ns-field .ns-field-choices.ns-choice-twocolumn {
  flex-flow: row wrap;
  align-content: flex-start;
}

.ns-field .ns-field-choices.ns-choice-twocolumn .ns-field-choice {
  flex: 0 0 calc(50% - .2rem);
}

/* Matches the ceremony's .ns-choice-opt metrics EXACTLY — rem, not em, because the field
   chip's own font (.78rem) is smaller than the page font the ceremony inherits (1rem), so
   an em here would render every option smaller than the signer will see it. */
.ns-field-choice {
  display: flex;
  align-items: center;
  gap: .3rem;
  font-size: .8rem;
  font-weight: 400;
  line-height: 1.25;
  min-width: 0;
}

.ns-field-choicetext {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-field-mark {
  /* sized like the native radio/checkbox the ceremony renders in this slot (~13px
     regardless of font size, so fixed pixels — not em of the chip's smaller font) */
  flex: none;
  width: 13px;
  height: 13px;
  border: 1px solid currentColor;
  background: rgba(255, 255, 255, .8);
}

.ns-mark-radio {
  border-radius: 50%;
}

.ns-mark-check {
  border-radius: 2px;
}

.ns-field .ns-field-table {
  position: absolute;
  inset: 0;
  display: grid;
  pointer-events: none;
}

/* The table preview's bands, matching the ceremony grid: header/label cells are shaded
   full bands INSIDE the box (they take the same slice in the stamped PDF), data cells are
   hairlined slots. All tinted to the recipient via currentColor. */
.ns-tb-cell,
.ns-tb-head,
.ns-tb-corner {
  min-width: 0;
  border: 0 solid color-mix(in srgb, currentColor 45%, transparent);
  border-right-width: 1px;
  border-bottom-width: 1px;
}

.ns-tb-head {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  white-space: nowrap;
  /* rem to match the ceremony's .ns-table-head (.68em of the page font) */
  font-size: .68rem;
  font-weight: 700;
  padding: 0 .15rem;
  background: color-mix(in srgb, currentColor 12%, transparent);
}

/* The frozen copy of a chip's outgoing contents, laid over the new ones so a face that
   changes (locking a field, editing its options) fades instead of cutting. It mirrors the
   field's own centring so a label ghost sits exactly where the real one did, and it is inert
   — the live toolbar and handles underneath must keep taking the pointer. */
.ns-field .ns-field-facegh {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  z-index: 1;
}

/* the container a dragged field would join lights up */
.ns-field.ns-field-droptarget {
  border-style: solid;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ns-rc, var(--ns-blue-600)) 30%, transparent);
}

/* Static text shows its real content on the canvas rather than a type chip — what the
   sender types is what the page will read. */
.ns-field .ns-field-static {
  align-self: stretch;
  /* Fill the box: as a shrink-wrapped flex item it would be centred by the field's own
     justify-content, so left-aligned text would float in the middle — the ceremony renders
     printed text hard against the left edge. */
  flex: 1;
  min-width: 0;
  padding: 0 .2rem;
  /* rem to match the ceremony's .ns-static-target (.8em of the page font) */
  font-size: .8rem;
  font-weight: 400;
  line-height: 1.3;
  text-align: left;
  overflow: hidden;
  white-space: pre-wrap;
}

.ns-field .ns-field-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ns-field.dragging {
  opacity: .7;
  cursor: grabbing;
}

.ns-field-DateSigned {
  border-style: dotted;
  font-style: italic;
}

/* Required is told by the border: solid = the signer must complete it, dashed = optional.
   (DateSigned stays dotted - it auto-fills and has no required state.) */
.ns-field.ns-field-required {
  border-style: solid;
}

/* The no-input types read as required by nature — a signature is never optional-looking,
   a date stamp always fills, static text is already on the page — so they wear the solid
   border regardless of the Required flag (which the server ignores for them anyway). */
.ns-field.ns-field-Signature,
.ns-field.ns-field-Initial,
.ns-field.ns-field-StaticText {
  border-style: solid;
}

.ns-field.ns-field-DateSigned {
  border-style: solid;
  font-style: italic;
}

/* The typed-value family previews as the ceremony renders it: type icon, then the hint. */
.ns-field .ns-field-inputface {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  gap: .25rem;
  padding: 0 .15rem 0 .25rem;
  overflow: hidden;
  pointer-events: none;
}

.ns-field .ns-field-inputface svg {
  /* rem: the ceremony icon is .95em of the PAGE font, not of the chip's smaller font */
  width: .95rem;
  height: .95rem;
  flex: none;
}

.ns-field .ns-field-ph {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .8rem;
  font-weight: 400;
  opacity: .75;
}

/* An auto-filled field names the signer detail it draws from rather than a hint the signer
   would read, so it is set apart from a placeholder: italic and fully opaque, because it is
   a statement about the field rather than sample text standing in for a value. */
.ns-field .ns-field-ph.is-auto {
  font-style: italic;
  opacity: 1;
}

.ns-field.ns-field-Multiline .ns-field-inputface {
  align-items: flex-start;
  padding-top: .15rem;
}

/* marquee selection: drag on empty page space to select everything the rectangle touches */
.ns-marquee {
  position: absolute;
  z-index: 30;
  border: 1.5px solid var(--ns-blue-600);
  background: rgba(var(--ns-primary-rgb), .08);
  border-radius: 2px;
  pointer-events: none;
}

/* several fields selected: per-field toolbars/handles are clutter — bulk actions live in
   the inspector and on the keyboard instead */
.ns-dz-multi .ns-field-tools,
.ns-dz-multi .ns-resize {
  display: none !important;
}

/* a light lift under the pointer so placed fields read as grabbable */
.ns-field {
  transition: box-shadow .13s ease, transform .13s ease;
}

.ns-field:hover:not(.selected):not(.dragging):not(.resizing) {
  transform: scale(1.015);
  box-shadow: 0 3px 10px rgba(16, 40, 70, .18);
}

.ns-dz-review-mode .ns-field:hover {
  transform: none;
  box-shadow: none;
}

/* Selecting a field (a tap that doesn't drag) rings it and shows its toolbar +
   resize handles; a second tap, or a tap elsewhere on the page, deselects. */
.ns-field.selected {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ns-rc) 30%, transparent);
  z-index: 5;
}

/* The floating toolbar above the selected field: assign (cycles signers, wears the
   signer colour), required, configure (Checkbox/Text), delete. Always in the DOM -
   selection fades/scales it in and out, so nothing re-renders or jumps. */
.ns-field-tools {
  position: absolute;
  bottom: calc(100% + 5px);
  left: 50%;
  display: flex;
  gap: 2px;
  background: var(--ns-navy-900);
  border-radius: 8px;
  padding: 3px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, .25);
  z-index: 6;
  opacity: 0;
  pointer-events: none;
  transform: translateX(-50%) translateY(4px) scale(.92);
  transition: opacity .15s ease, transform .15s cubic-bezier(.2, .7, .2, 1);
}

.ns-field.selected .ns-field-tools {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(-50%);
}

.ns-field-tools button,
.ns-field-tools .ns-ft-static {
  width: 24px;
  height: 24px;
  border: 0;
  border-radius: 5px;
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  font-family: inherit;
  font-style: normal;
}

.ns-field-tools .ns-ft-config,
.ns-field-tools .ns-ft-require,
.ns-field-tools .ns-ft-delete {
  background: rgba(255, 255, 255, .14);
}

.ns-field-tools .ns-ft-delete {
  font-size: 14px;
}

.ns-field-tools .ns-ft-delete:hover {
  color: #ff9a9a;
}

.ns-field-tools .ns-ft-static {
  cursor: default;
}

/* (The required marker is the solid border above - no extra dot.) */

/* (Per-field options live in the inspector's Properties segment — see .ns-dz-insp.) */

/* Four corner handles for resizing a placed field. Each wears the signer's
   colour and shows the diagonal resize cursor for its corner. Always rendered;
   selection fades them in. */
.ns-field .ns-resize {
  position: absolute;
  width: 10px;
  height: 10px;
  background: #fff;
  border: 1px solid var(--ns-rc);
  border-radius: 2px;
  z-index: 2;
  touch-action: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s ease;
}

.ns-field.selected .ns-resize {
  opacity: 1;
  pointer-events: auto;
}

.ns-resize-nw { top: -5px; left: -5px; cursor: nwse-resize; }
.ns-resize-ne { top: -5px; right: -5px; cursor: nesw-resize; }
.ns-resize-sw { bottom: -5px; left: -5px; cursor: nesw-resize; }
.ns-resize-se { bottom: -5px; right: -5px; cursor: nwse-resize; }

.ns-field.resizing {
  opacity: .7;
  cursor: grabbing;
}

.ns-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--ns-border-strong);
  border-radius: 8px;
  padding: 7px 9px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ns-text-700);
  background: #fff;
  user-select: none;
  touch-action: none;
  cursor: grab;
  transition: border-color .16s ease;
}

.ns-chip:hover {
  border-color: var(--ns-info-border);
}

.ns-chip svg {
  color: var(--ns-blue-600);
  flex: none;
}

.ns-ghost {
  position: fixed;
  z-index: 2000;
  pointer-events: none;
  opacity: .85;
  margin: 0;
}

/* ---------- signing ceremony ---------- */

.ns-sig-target {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px dashed #fd7e14;
  background: rgba(253, 126, 20, .14);
  color: #b35900;
  font-size: .8rem;
  font-weight: 600;
  border-radius: .25rem;
  cursor: pointer;
}

.ns-sig-target:hover {
  background: rgba(253, 126, 20, .25);
}

.ns-sig-target.filled {
  border-style: solid;
  border-color: var(--ns-success);
  background: rgba(20, 143, 91, .06);
}

.ns-sig-target img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.ns-date-box {
  position: absolute;
  display: flex;
  align-items: center;
  padding-left: .35rem;
  border: 1px solid #6f42c1;
  background: rgba(111, 66, 193, .07);
  color: #3d2a63;
  font-size: .85rem;
  border-radius: .25rem;
}

/* Initials targets behave like signature targets, in their own hue. */
.ns-sig-target.ns-ini-target {
  border-color: #20c997;
  background: rgba(32, 201, 151, .14);
  color: #0c6e51;
}

.ns-sig-target.ns-ini-target:hover {
  background: rgba(32, 201, 151, .25);
}

.ns-sig-target.ns-ini-target.filled {
  border-color: var(--ns-success);
  background: rgba(20, 143, 91, .06);
}

.ns-check-target {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--ns-blue-600);
  background: rgba(var(--ns-primary-rgb),.08);
  color: var(--ns-navy-800);
  font-weight: 700;
  border-radius: .2rem;
  cursor: pointer;
  user-select: none;
}

.ns-check-target:hover {
  background: rgba(var(--ns-primary-rgb),.18);
}

.ns-text-target {
  position: absolute;
  border: 2px dashed var(--ns-blue-600);
  background: rgba(var(--ns-primary-rgb),.06);
  border-radius: .2rem;
  padding: 0 .25rem;
  font-size: inherit;
}

.ns-text-target:focus,
.ns-text-target:focus-within {
  outline: none;
  border-style: solid;
  background: #fff;
}

/* The wrapper carries the border and the type icon; the borderless inner input takes the
   typing. The icon (plus the per-type hint) is what tells the input types apart. */
.ns-input-wrap {
  display: flex;
  align-items: center;
  gap: .25rem;
}

.ns-input-ico {
  flex: none;
  display: inline-flex;
  color: var(--ns-blue-600);
  opacity: .65;
  pointer-events: none;
}

.ns-input-ico svg {
  width: .95em;
  height: .95em;
}

.ns-input-inner {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  color: inherit;
}

.ns-input-inner:focus {
  outline: none;
}

/* A field filled from the signer's own record: shown so they can check it, not theirs to edit.
   DOTTED deliberately — this ceremony already spends dashed on "your turn" and solid on "your
   turn, required" (.ns-text-target.ns-required), so a locked field takes a third border style
   rather than overloading either. Grey ink, a grey fill and a padlock carry it too, so the
   state never rests on border style alone. */
.ns-input-wrap.ns-input-locked {
  border-style: dotted;
  border-color: var(--ns-border-strong);
  background: var(--ns-divider);
  color: var(--ns-text-700);
  cursor: default;
}

.ns-input-wrap.ns-input-locked .ns-input-ico {
  color: var(--ns-muted);
}

/* Ellipsis rather than a silent clip: a truncated address the signer is being asked to check
   has to look truncated. (An input honours text-overflow only while unfocused, which is
   exactly when it matters here.) */
.ns-input-wrap.ns-input-locked .ns-input-inner {
  cursor: default;
  text-overflow: ellipsis;
}

/* Focusable (so a keyboard user and a screen reader still reach the value) but visibly
   inert — it must not borrow the "you can type here" white fill the editable inputs take. */
.ns-input-wrap.ns-input-locked:focus-within {
  background: var(--ns-divider);
  box-shadow: var(--ns-focus-ring);
}

.ns-input-lock {
  flex: none;
  display: inline-flex;
  color: var(--ns-muted);
  pointer-events: none;
}

.ns-input-lock svg {
  width: .8em;
  height: .8em;
}

.ns-input-wrap.ns-multiline-target {
  align-items: flex-start;
  padding: .15rem .25rem;
}

.ns-input-wrap.ns-multiline-target .ns-input-ico {
  margin-top: .1rem;
}

textarea.ns-input-inner {
  resize: none;
  line-height: 1.3;
  overflow-y: auto;
}

/* number inputs keep the browser's spinner but trimmed to the box */
.ns-input-inner[type="number"] {
  appearance: auto;
}

/* The live format message: a small bubble pinned to the box, shown only while the value
   doesn't fit its type — the field-level answer to "what's wrong with it?". It floats over
   the neighbouring fields rather than being covered by them (see the z-index lift below),
   and fades/slides in so it doesn't snap into existence on every keystroke. */
.ns-input-err {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 2;
  min-width: max-content;
  max-width: 240px;
  padding: 4px 9px;
  background: #fff;
  border: 1px solid var(--ns-maple);
  border-left: 3px solid var(--ns-maple);
  border-radius: 7px;
  box-shadow: 0 4px 14px rgba(16, 40, 70, .18);
  color: var(--ns-maple);
  font-size: .7rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: .002em;
  text-align: left;
  white-space: normal;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-3px);
  transition: opacity .15s ease, transform .15s ease, visibility .15s;
}

.ns-text-target.ns-invalid .ns-input-err {
  opacity: 1;
  visibility: visible;
  transform: none;
}

/* Near the bottom of a page there is no room underneath — the bubble sits above instead. */
.ns-text-target.ns-err-above .ns-input-err {
  top: auto;
  bottom: calc(100% + 6px);
  transform: translateY(3px);
}

.ns-text-target.ns-err-above.ns-invalid .ns-input-err {
  transform: none;
}

/* An invalid field (and above all the one being typed in) lifts over its neighbours, so its
   bubble is never half-hidden behind the next field down. */
.ns-text-target.ns-invalid {
  z-index: 20;
}

.ns-text-target:focus-within {
  z-index: 30;
}

@media (prefers-reduced-motion: reduce) {
  .ns-input-err {
    transition: none;
  }
}

/* A date picker is a signer-filled value field, same family as Text — unlike .ns-date-box
   (DateSigned), which is a frozen auto-filled display, never an input. */
.ns-date-target {
  position: absolute;
  border: 2px dashed var(--ns-blue-600);
  background: rgba(var(--ns-primary-rgb),.06);
  border-radius: .2rem;
  padding: 0 .25rem;
  font-size: inherit;
}

.ns-date-target:focus {
  outline: none;
  border-style: solid;
  background: #fff;
}

/* Radio / checkbox groups hold their options inside the field's own box, so the box
   scrolls if the sender sized it smaller than its list. */
.ns-choice-target {
  position: absolute;
  border: 2px dashed var(--ns-blue-600);
  background: rgba(var(--ns-primary-rgb), .06);
  border-radius: .2rem;
  padding: .15rem .3rem;
  display: flex;
  flex-direction: column;
  gap: .1rem;
  overflow-y: auto;
}

/* The option layouts the sender picks between. Inline exists for the common client-form
   shape: a Yes/No pair spanning two table columns, each option landing under its heading. */
.ns-choice-target.ns-choice-inline {
  flex-direction: row;
  align-items: center;
  justify-content: space-around;
  gap: .4rem;
  overflow: hidden;
}

.ns-choice-target.ns-choice-twocolumn {
  flex-flow: row wrap;
  align-content: flex-start;
}

.ns-choice-target.ns-choice-twocolumn .ns-choice-opt {
  flex: 0 0 calc(50% - .2rem);
}

/* A group frame is the sender's structure: it outlines its fields and names them, and it is
   never interactive — pointer-events off so the fields inside it stay clickable. */
.ns-group-target {
  position: absolute;
  border: 1px solid var(--ns-border-strong);
  border-radius: .2rem;
  pointer-events: none;
}

.ns-group-legend {
  position: absolute;
  top: -.55em;
  left: .4rem;
  padding: 0 .25rem;
  background: #fff;
  font-size: .62em;
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--ns-muted);
  white-space: nowrap;
}

/* A table's cells: a grid of inputs the signer fills, sized to sit over the table the
   document already draws. */
.ns-table-target {
  position: absolute;
  display: grid;
  gap: 1px;
  border: 2px dashed var(--ns-blue-600);
  background: rgba(var(--ns-primary-rgb), .06);
  border-radius: .2rem;
  overflow: hidden;
}

.ns-table-target.ns-required {
  border-style: solid;
}

.ns-table-target.ns-invalid {
  border-color: var(--ns-maple);
  background: rgba(197, 48, 48, .08);
}

.ns-table-cell {
  min-width: 0;
  border: 0;
  border-radius: 0;
  background: rgba(255, 255, 255, .75);
  font-size: .72em;
  padding: 0 .2rem;
}

.ns-table-cell:focus {
  outline: 2px solid var(--ns-blue-600);
  outline-offset: -2px;
  background: #fff;
}

/* A container table in the ceremony: just the grid, faint — the fields inside are the
   inputs, so the frame must neither compete nor catch the pointer. */
.ns-table-target.ns-table-passive {
  border-style: solid;
  border-width: 1px;
  background: transparent;
  pointer-events: none;
  opacity: .5;
}

.ns-table-cellframe {
  border: 0 solid var(--ns-border-strong);
  border-right-width: 1px;
  border-bottom-width: 1px;
}

/* The header/label bands inside a ceremony table — the same slice of the box the designer
   preview shades and the stamp draws into. */
.ns-table-head {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: .68em;
  font-weight: 700;
  padding: 0 .15rem;
  background: rgba(var(--ns-primary-rgb), .12);
  color: var(--ns-ink);
}

.ns-table-passive .ns-table-head {
  background: transparent;
  border: 0 solid var(--ns-border-strong);
  border-right-width: 1px;
  border-bottom-width: 1px;
}

/* The inspector's children list: icon + name tinted to the child's signer, and a button —
   each row is the way into that field's own properties. */
.ns-dz-insp-childname {
  --ns-rc: var(--ns-blue-600);
  flex: 1;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ns-rc);
  /* The same chip treatment the table's cell map uses: the signer's colour is carried by the
     row itself, not just by the icon, so a glance down the list says who each field is for. */
  border: 1px solid color-mix(in srgb, var(--ns-rc) 45%, transparent);
  border-radius: 7px;
  background: color-mix(in srgb, var(--ns-rc) 12%, #fff);
  padding: 4px 6px;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background .13s ease, border-color .13s ease;
}

.ns-dz-insp-childname:hover,
.ns-dz-insp-childname:focus-visible {
  background: color-mix(in srgb, var(--ns-rc) 20%, #fff);
  border-color: var(--ns-rc);
  outline: none;
}

.ns-dz-insp-childname > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ns-ink);
}

/* "Add a field to this container": a dashed add slot with its own drawer, because a native
   select can neither animate opening nor be trusted to colour its options — and each
   candidate has to read in its signer's colour, like the rows above it.
   NB: background-COLOR, never the `background` shorthand, on anything carrying an icon or a
   chevron — the shorthand drops background-image and then transitions background-position
   back from 0% 0%, which is how a select's arrow ends up flying in from the left. */
.ns-dz-addpick {
  position: relative;
}

.ns-dz-addpick-btn {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 6px;
  border: 1.5px dashed var(--ns-border-strong);
  border-radius: 9px;
  background-color: transparent;
  color: var(--ns-slate);
  padding: 6px 9px;
  font-size: 11.5px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: border-color .13s ease, color .13s ease, background-color .13s ease;
}

.ns-dz-addpick-btn:hover,
.ns-dz-addpick-btn:focus-visible,
.ns-dz-addpick-btn[aria-expanded="true"] {
  border-style: solid;
  border-color: var(--ns-blue-600);
  color: var(--ns-blue-600);
  background-color: color-mix(in srgb, var(--ns-blue-600) 6%, #fff);
  outline: none;
}

.ns-dz-addpick-btn > svg:first-child {
  flex: none;
}

.ns-dz-addpick-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* the chevron rides at the far end and turns over when the drawer is open */
.ns-dz-addpick-btn > svg:last-child {
  margin-left: auto;
  flex: none;
  transition: transform .16s ease;
}

.ns-dz-addpick-btn[aria-expanded="true"] > svg:last-child {
  transform: rotate(180deg);
}

.ns-dz-addpick-menu {
  position: absolute;
  z-index: 40;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 4px;
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: 10px;
  box-shadow: var(--ns-shadow-dropdown);
  max-height: 220px;
  overflow-y: auto;
}

/* Each candidate in its signer's colour, matching the child rows it will join. */
.ns-dz-addpick-opt {
  --ns-rc: var(--ns-blue-600);
  display: flex;
  align-items: center;
  gap: 6px;
  border: 0;
  border-radius: 7px;
  background-color: transparent;
  color: var(--ns-rc);
  padding: 6px 8px;
  font-size: 11.5px;
  font-weight: 600;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color .12s ease;
}

.ns-dz-addpick-opt:hover,
.ns-dz-addpick-opt:focus-visible {
  background-color: color-mix(in srgb, var(--ns-rc) 12%, #fff);
  outline: none;
}

.ns-dz-addpick-opt > svg {
  flex: none;
}

.ns-dz-addpick-opt > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ns-ink);
}

.ns-dz-addpick-opt > em {
  margin-left: auto;
  flex: none;
  font-style: normal;
  font-size: 9.5px;
  font-weight: 700;
  color: var(--ns-rc);
}

/* Every popup picker's drawer opens the same way. Keyed off the hidden attribute coming
   off — Bootstrap's reboot makes [hidden] display:none !important, so the rule starts
   matching exactly when the menu is shown and the keyframes run then. */
.ns-dz-spick-menu:not([hidden]),
.ns-dz-addpick-menu:not([hidden]) {
  animation: ns-dz-menu-in .16s cubic-bezier(.2, .7, .2, 1) both;
  transform-origin: top center;
}

@keyframes ns-dz-menu-in {
  from {
    transform: translateY(-5px) scale(.985);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ns-dz-spick-menu:not([hidden]),
  .ns-dz-addpick-menu:not([hidden]) {
    animation: none;
  }
}

/* the chevron only shows the row is a way in; it slides on hover */
.ns-dz-childarrow {
  margin-left: auto;
  flex: none;
  color: var(--ns-muted);
  opacity: 0;
  transform: translateX(-3px);
  transition: opacity .13s ease, transform .13s ease;
}

.ns-dz-insp-childname:hover .ns-dz-childarrow,
.ns-dz-insp-childname:focus-visible .ns-dz-childarrow {
  opacity: 1;
  transform: none;
}

/* The other direction: the container a field sits in, as a route back up. Reads as a
   breadcrumb above the field's own settings rather than as another setting. */
.ns-dz-insp-parent {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  padding: 6px 9px;
  border: 1px solid var(--ns-border);
  border-radius: 9px;
  background: var(--ns-canvas);
  color: var(--ns-slate);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background .13s ease, border-color .13s ease;
}

.ns-dz-insp-parent:hover,
.ns-dz-insp-parent:focus-visible {
  border-color: var(--ns-blue-600);
  background: color-mix(in srgb, var(--ns-blue-600) 6%, #fff);
  outline: none;
}

.ns-dz-insp-parent-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.ns-dz-insp-parent-body em {
  font-style: normal;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ns-muted);
}

.ns-dz-insp-parent-body b {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ns-ink);
  overflow: hidden;
  white-space: nowrap;
}

.ns-choice-target.ns-required {
  border-style: solid;
}

.ns-choice-target.ns-invalid {
  border-color: var(--ns-maple);
  background: rgba(197, 48, 48, .08);
}

.ns-choice-opt {
  display: flex;
  align-items: center;
  gap: .3rem;
  margin: 0;
  font-size: .8em;
  line-height: 1.25;
  cursor: pointer;
  min-width: 0;
}

.ns-choice-opt input {
  flex: none;
  margin: 0;
}

.ns-choice-opt span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Sender-authored display text: it is page content, not an input, so it gets no field
   chrome in the ceremony — it simply reads as part of the document. */
.ns-static-target {
  position: absolute;
  font-size: .8em;
  line-height: 1.3;
  color: #000;
  overflow: hidden;
  white-space: pre-wrap;
}

/* A multi-line box is the textarea flavour of .ns-text-target; the value wraps like the
   stamp will. */
.ns-multiline-target {
  resize: none;
  padding: .15rem .25rem;
  line-height: 1.3;
  overflow-y: auto;
}

/* A dropdown is a signer-picked value field, same family as Text. */
.ns-select-target {
  position: absolute;
  border: 2px dashed var(--ns-blue-600);
  background: rgba(var(--ns-primary-rgb), .06);
  border-radius: .2rem;
  padding: 0 .25rem;
  font-size: inherit;
}

.ns-select-target:focus {
  outline: none;
  border-style: solid;
  background: #fff;
}

/* Required ceremony fields wear a solid ring; an unmet required field on Finish flashes
   the maple accent until the signer completes it (client mirror of the server check). */
.ns-check-target.ns-required,
.ns-text-target.ns-required,
.ns-select-target.ns-required,
.ns-date-target.ns-required {
  border-style: solid;
}

.ns-check-target.ns-invalid,
.ns-text-target.ns-invalid,
.ns-select-target.ns-invalid,
.ns-date-target.ns-invalid {
  border-color: var(--ns-maple);
  background: rgba(197, 48, 48, .08);
}

/* The draw pad and typed preview inside a signature-capture dialog. Addressed by class rather
   than by id because four surfaces render this dialog now — the ceremony's signature and initials
   and the personal console's saved pair — and an id list would have to grow with every one. See
   _SignatureCapture.cshtml. */
/* ---------------------------------------------------------------------------
   ns-sig - the draw pad and typed preview inside a signature capture dialog.
   See docs/design/components/signature-capture.md.

   Both are literal white, not --ns-surface, and that is deliberate: this is
   paper. What gets drawn here becomes the image stamped into a legal document,
   and a branded deployment that retints its chrome does not get to retint the
   sheet somebody signs on - or the ink, which is the matching literal in
   signature-capture.js. The dashed border says "draw inside here" without
   looking like an input you should type in.

   Class-based rather than the #draw-pad / #ini-draw-pad ids they were until the
   personal console needed the same pad on a second page.
   --------------------------------------------------------------------------- */
.ns-sig-pad {
  width: 100%;
  border: 1px dashed var(--ns-border-strong);
  border-radius: .5rem;
  background: #fff;
  touch-action: none;
  cursor: crosshair;
}

.ns-sig-preview {
  width: 100%;
  border: 1px dashed var(--ns-border-strong);
  border-radius: .5rem;
  background: #fff;
}

/* ---------------------------------------------------------------------------
   ns-health - the export destination status strip on /Admin/Integrations.
   Source mock: "SmartCloud Status.dc.html" variant 1b ("health strip"), in the
   "SmartCloud status indicators redesign" design project.

   One verdict line that stays one line when everything passes, and expands to
   the individual checks when it doesn't. The loading state is the SAME strip
   with a sweeping progress line rather than a different shape, so the card does
   not jump when the probe resolves - that is the point of the design, and why
   the bar has a min-height instead of being sized by its content.
   --------------------------------------------------------------------------- */
.ns-health {
  margin-top: .75rem;
  border: 1px solid var(--ns-border);
  border-radius: 10px;
  background: var(--ns-thead-bg);
  overflow: hidden;
}

.ns-health-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 13px;
  /* Tall enough that the Re-check button does not drive it, so the resolved bar and the
     button-less loading bar are the same height. Together with the always-reserved track below,
     this is what keeps the card from jumping when the probe lands. */
  min-height: 48px;
}

.ns-health-icon {
  width: 20px;
  height: 20px;
  flex: none;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--ns-muted);
  color: #fff;
}

.ns-health-verdict {
  font-size: 13px;
  font-weight: 700;
  color: var(--ns-text-700);
}

.ns-health-ago,
.ns-health-hint {
  font-size: 12px;
  color: var(--ns-muted);
}

.ns-health-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

/* Both controls inherit the strip's tone via currentColor on the bar. */
.ns-health-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--ns-border);
  border-radius: 8px;
  background: var(--ns-surface);
  color: inherit;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.25;
  cursor: pointer;
}

.ns-health-toggle {
  border: 0;
  background: none;
  padding: 5px 2px;
  color: inherit;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.25;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.ns-health-btn:focus-visible,
.ns-health-toggle:focus-visible { outline: 0; box-shadow: var(--ns-focus-ring); }

/* The chevron points down when collapsed and up when open. */
.ns-health-toggle svg { transition: transform .15s ease; }
.ns-health-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }

/* --- the loading sweep ------------------------------------------------------ */
.ns-health-spinner {
  width: 18px;
  height: 18px;
  flex: none;
  border-radius: 50%;
  border: 2px solid var(--ns-info-border-soft);
  border-top-color: var(--ns-blue-600);
  animation: ns-spin .8s linear infinite;
}

.ns-health-track {
  height: 2px;
  background: transparent;
  overflow: hidden;
}

.ns-health-track > span { display: none; }

.ns-health.is-checking .ns-health-track { background: var(--ns-primary-tint); }

.ns-health.is-checking .ns-health-track > span {
  display: block;
  width: 30%;
  height: 100%;
  background: var(--ns-blue-600);
  animation: ns-sweep 1.15s cubic-bezier(.4, 0, .2, 1) infinite;
}

@keyframes ns-sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(320%); }
}

/* --- the expanded check list ------------------------------------------------ */
.ns-health-checks {
  background: var(--ns-surface);
  border-top: 1px solid var(--ns-border);
  padding: 4px 0;
}

.ns-health-check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 13px;
}

.ns-health-check-icon {
  width: 16px;
  height: 16px;
  flex: none;
  margin-top: 1px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
}

.ns-health-check-label {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-health-check-detail {
  font-size: 12px;
  color: var(--ns-text-700);
  margin-top: 2px;
  line-height: 1.45;
}

/* --- tones ------------------------------------------------------------------
   Set on the strip; the bar and its controls read them off currentColor so a
   new tone is one block rather than edits in five places. */
.ns-health.is-ok {
  border-color: #C6E7D5;
  background: var(--ns-success-50);
}
.ns-health.is-ok .ns-health-bar { color: var(--ns-success-text); }
.ns-health.is-ok .ns-health-verdict { color: var(--ns-success-text); }
.ns-health.is-ok .ns-health-ago { color: var(--ns-success-text); opacity: .75; }
.ns-health.is-ok .ns-health-icon { background: var(--ns-success); }
.ns-health.is-ok .ns-health-btn { border-color: #C6E7D5; }
.ns-health.is-ok .ns-health-checks { border-top-color: #C6E7D5; }

.ns-health.is-warning {
  border-color: #EFDCB4;
  background: var(--ns-warning-50);
}
.ns-health.is-warning .ns-health-bar { color: var(--ns-warning-text); }
.ns-health.is-warning .ns-health-verdict { color: var(--ns-warning-text); }
.ns-health.is-warning .ns-health-ago { color: var(--ns-warning-text); opacity: .75; }
.ns-health.is-warning .ns-health-icon { background: var(--ns-warning); }
.ns-health.is-warning .ns-health-btn { border-color: #EFDCB4; }
.ns-health.is-warning .ns-health-checks { border-top-color: #EFDCB4; }

.ns-health.is-error {
  border-color: var(--ns-danger-border);
  background: var(--ns-danger-50);
}
.ns-health.is-error .ns-health-bar { color: var(--ns-danger-text); }
.ns-health.is-error .ns-health-verdict { color: var(--ns-danger-text); }
.ns-health.is-error .ns-health-ago { color: var(--ns-danger-text); opacity: .75; }
.ns-health.is-error .ns-health-icon { background: var(--ns-maple); }
.ns-health.is-error .ns-health-btn { border-color: var(--ns-danger-border); }
.ns-health.is-error .ns-health-checks { border-top-color: var(--ns-danger-border); }

/* Unknown keeps the neutral surface - it is not a failure, and colouring it as
   one would tell the admin something is broken when nothing is known. */
.ns-health.is-unknown .ns-health-bar { color: var(--ns-slate); }
.ns-health.is-unknown .ns-health-verdict { color: var(--ns-slate); }
.ns-health.is-unknown .ns-health-icon { background: var(--ns-muted); }

/* Per-row icons, independent of the strip's own tone: in a red strip the rows
   that were simply never reached must not read as failures too. */
.ns-health-check.is-ok    .ns-health-check-icon { background: var(--ns-success); }
.ns-health-check.is-warning .ns-health-check-icon { background: var(--ns-warning); }
.ns-health-check.is-error .ns-health-check-icon { background: var(--ns-maple); }
.ns-health-check.is-unknown .ns-health-check-icon {
  background: none;
  border: 1.5px solid var(--ns-border-strong);
  color: var(--ns-muted);
}
.ns-health-check.is-unknown .ns-health-check-label { color: var(--ns-slate); }
.ns-health-check.is-unknown .ns-health-check-detail { color: var(--ns-muted); }

/* ---------- data table ----------
   The shared list-table system (Table System Spec, direction 1a), mounted by Envelopes, Templates
   and Walk-In kiosks. Appended as one block, prefixed .ns-dt-*, with no changes to any existing
   selector — and no colour, font, radius or shadow that is not already a token. */

.ns-dt {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Sorting, filtering, grouping and paging swap the table's markup in place rather than reloading.
   The swap is usually faster than the eye, so this is deliberately understated — enough to show the
   click landed on a slow connection, not a spinner that flashes on every interaction. The delay
   means a fast response never dims at all. */
.ns-dt.is-busy .ns-dt-card,
.ns-dt.is-busy .ns-dt-toolbar {
  pointer-events: none;
}

/* The outgoing rows fade before the swap lands, so a change of data cross-fades instead of cutting
   into a fade-in. Opacity only — see the note below on why nothing in the scroller may transform. */
.ns-dt-table tbody.is-fading,
.ns-dt-cards.is-fading {
  opacity: 0;
  transition: opacity .11s ease-in;
}

/* --- what a change of data looks like ---
   Two rules earn the restraint here.

   Only the rows move. The header and the footer are the table's frame, not its contents — a re-sort
   changes which rows are shown, and animating the chrome around them made the whole card look like it
   reloaded, which is the exact feel the in-place swap exists to remove.

   And the rows fade without moving. A translate or a scale overflows the scroll container for the
   length of the animation, which flashed a scrollbar in and then out on every sort — visible even on a
   three-row table, where nothing should be scrolling at all. Opacity alone costs no layout.

   No fill mode on any entrance animation, deliberately: with `both` the element holds the keyframe's
   transparent `from` state whenever the animation cannot run, so anything that stopped it would leave
   the rows invisible. Without it, an animation that never plays just shows the content. */
.ns-dt-table tbody {
  animation: ns-dt-rows-in .16s ease-out;
}

@keyframes ns-dt-rows-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* The Cards / Table switch swaps one whole body for the other, so that one *is* a change of frame and
   earns a little more — a cross-fade with a touch of scale. The scale sits on the cards grid and on the
   table's card, never on the scroller itself, for the same overflow reason as above. Applied only while
   the layout is actually changing, so an ordinary sort doesn't re-animate the card. */
.ns-dt-cards,
.ns-dt-layout-switch .ns-dt-card {
  animation: ns-dt-body-in .24s cubic-bezier(.2, .8, .3, 1);
}

@keyframes ns-dt-body-in {
  from { opacity: 0; transform: scale(.99); }
  to { opacity: 1; transform: none; }
}

/* A per-card stagger was tried and dropped: staggering needs a `backwards` fill so each card holds the
   transparent state through its delay, which is exactly the "invisible if the animation never runs"
   trap the note above avoids. The container's own entrance already reads as the list arriving. */

/* --- adding and removing a column ---
   The column slides as well as fading. A <col>'s width cannot be transitioned by CSS — it is not a
   rendered box — so data-table.js steps the width itself (stepWidth) and these rules fade the contents
   over the top. Timings match COL_FADE_MS. The cells clip while they narrow, so text does not spill
   into its neighbour on the way through. */
.ns-dt-table th.is-col-out,
.ns-dt-table td.is-col-out,
.ns-dt-table th.is-col-in,
.ns-dt-table td.is-col-in {
  overflow: hidden;
  white-space: nowrap;
}

.ns-dt-table th.is-col-out,
.ns-dt-table td.is-col-out {
  opacity: 0;
  transition: opacity .13s ease-in;
}

.ns-dt-table th.is-col-in,
.ns-dt-table td.is-col-in {
  animation: ns-dt-col-in .13s ease-out;
}

@keyframes ns-dt-col-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Motion here is decoration on top of a working table, so all of it comes off together. The busy dim
   stays as a plain opacity change: that one is feedback, not flourish. */
@media (prefers-reduced-motion: reduce) {
  .ns-dt-table tbody.is-fading,
  .ns-dt-cards.is-fading { transition: none; opacity: 1; }

  .ns-dt-pop,
  .ns-dt-pop.is-in { transition: none; transform: none; }

  .ns-dt-chip.is-new,
  .ns-dt-chip.is-leaving,
  .ns-dt-table tbody,
  .ns-dt-cards,
  .ns-dt-layout-switch .ns-dt-card { animation: none; }

  .ns-dt-table th,
  .ns-dt-table td { transition: none; animation: none; }
}

/* --- 2 · toolbar: one row, never wrapping above 1280px --- */

.ns-dt-toolbar {
  display: flex;
  gap: 8px;
  align-items: center;
  min-height: 32px;
}

.ns-dt-search-form { flex: none; }

.ns-dt-search { width: 250px; }

.ns-dt-spacer { flex: 1; }

/* Left of the spacer is what is being looked for; right of it is how the view is configured. */
.ns-dt-chips {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  min-width: 0;
}

/* --- filter chips --- */

.ns-dt-chip {
  display: inline-flex;
  align-items: center;
  background: var(--ns-primary-tint);
  border: 1px solid var(--ns-info-border);
  border-radius: 999px;
  max-width: 320px;
  /* Only chips whose column wasn't there before get .is-new — a swap re-renders them all, so
     animating on insert would flash the whole row on every sort. */
  transition: background .14s ease, border-color .14s ease;
}

.ns-dt-chip.is-new {
  animation: ns-dt-chip-in .2s cubic-bezier(.2, .8, .3, 1);
}

/* Collapses into the space it occupied rather than blinking out. data-table.js waits this long
   (CHIP_EXIT_MS) before swapping the row. */
.ns-dt-chip.is-leaving {
  animation: ns-dt-chip-out .14s ease-in both;
}

@keyframes ns-dt-chip-in {
  from { opacity: 0; transform: translateY(-3px) scale(.94); }
  to { opacity: 1; transform: none; }
}

@keyframes ns-dt-chip-out {
  from { opacity: 1; transform: none; }
  to { opacity: 0; transform: scale(.9); }
}

/* Nothing chosen yet: an outline, not a filled chip, because it is not filtering anything. It reads
   as a slot waiting to be filled rather than as an applied filter. */
.ns-dt-chip.is-pending {
  background: none;
  border-style: dashed;
  border-color: var(--ns-primary);
}

.ns-dt-chip-body {
  border: 0;
  background: none;
  padding: 5px 4px 5px 11px;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  color: var(--ns-primary-hover);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-height: 24px;
}

.ns-dt-chip-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  margin-right: 3px;
  border-radius: 50%;
  color: var(--ns-primary-hover);
  opacity: .7;
  flex: none;
}

.ns-dt-chip-x:hover {
  background: var(--ns-surface);
  opacity: 1;
  color: var(--ns-primary-hover);
}

/* Dashed and unfilled, so it reads as "add one" rather than as an applied filter. */
.ns-dt-add-filter {
  border: 1px dashed var(--ns-border-strong);
  background: none;
  border-radius: 999px;
  padding: 5px 11px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ns-slate);
  min-height: 32px;
  white-space: nowrap;
}

.ns-dt-add-filter:hover {
  border-color: var(--ns-primary);
  color: var(--ns-primary-hover);
}

.ns-dt-reset {
  border: 0;
  background: none;
  padding: 4px 2px;
  font-size: 12px;
  color: var(--ns-muted);
  text-decoration: none;
  white-space: nowrap;
}

.ns-dt-reset:hover { color: var(--ns-text-700); text-decoration: underline; }

/* --- toolbar buttons (Group, Columns) --- */

.ns-dt-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  white-space: nowrap;
}

.ns-dt-btn > svg {
  flex: none;
  color: var(--ns-muted);
}

.ns-dt-btn.is-on > svg,
.ns-dt-btn.is-open > svg {
  color: currentColor;
}

.ns-dt-btn.is-on,
.ns-dt-btn.is-open {
  background: var(--ns-primary-tint);
  border-color: var(--ns-info-border);
  color: var(--ns-primary-hover);
}

.ns-dt-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--ns-divider);
  color: var(--ns-slate);
  font-size: 10.5px;
  font-weight: 800;
}

.ns-dt-btn.is-open .ns-dt-count {
  background: var(--ns-primary);
  color: var(--ns-on-primary);
}

/* --- Cards / Table switch (Templates) --- */

.ns-dt-layout {
  display: inline-flex;
  border: 1px solid var(--ns-border-strong);
  border-radius: var(--ns-radius-control);
  overflow: hidden;
}

.ns-dt-layout-btn {
  padding: 6px 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ns-slate);
  text-decoration: none;
  background: var(--ns-surface);
  min-height: 32px;
  display: inline-flex;
  align-items: center;
}

.ns-dt-layout-btn + .ns-dt-layout-btn { border-left: 1px solid var(--ns-border-strong); }

.ns-dt-layout-btn.is-active {
  background: var(--ns-primary-tint);
  color: var(--ns-primary-hover);
}

/* --- popovers --- */

.ns-dt-pop-wrap { position: relative; }

.ns-dt-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 40;
  min-width: 200px;
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
  box-shadow: var(--ns-shadow-dropdown);
  padding: 10px;
  /* Drawers grow out of the control that opened them: a short rise and fade, anchored at the top edge
     so the motion reads as "this panel belongs to that button". `hidden` remains the source of truth
     for whether the panel exists; .is-in only drives the transition, so a closed drawer is still out
     of the tab order. */
  opacity: 0;
  transform: translateY(-4px) scale(.985);
  transform-origin: top right;
  transition: opacity .14s ease, transform .14s cubic-bezier(.2, .8, .3, 1);
}

.ns-dt-pop.is-in {
  opacity: 1;
  transform: none;
}

.ns-dt-pop.is-flipped,
.ns-dt-chip-wrap > .ns-dt-pop { transform-origin: top left; }

/* A chip's editor hangs off the chip's left edge, since chips sit at the row's start. */
.ns-dt-chip-wrap > .ns-dt-pop,
.ns-dt-pop.is-flipped { right: auto; left: 0; }

.ns-dt-pop-columns { width: 284px; }

.ns-dt-pop-filter { width: 260px; }

.ns-dt-pop-title {
  font-size: 12px;
  font-weight: 800;
  color: var(--ns-ink);
  margin-bottom: 8px;
}

.ns-dt-pop-group { border: 0; margin: 0 0 8px; padding: 0; }

.ns-dt-pop-legend {
  display: flex;
  align-items: center;
  gap: 6px;
  float: none;
  width: auto;
  margin: 0 0 5px;
  padding: 0;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ns-muted);
}

.ns-dt-pop-scroll { max-height: 208px; overflow-y: auto; }

.ns-dt-pop-find { margin-bottom: 8px; }

.ns-dt-pop-item,
.ns-dt-pop-radio,
.ns-dt-pop-check {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  border: 0;
  background: none;
  padding: 6px 7px;
  border-radius: 8px;
  font-size: 12.5px;
  color: var(--ns-text-700);
  text-align: left;
  text-decoration: none;
  min-height: 32px;
}

.ns-dt-pop-item:hover,
.ns-dt-pop-radio:hover,
.ns-dt-pop-check:hover { background: var(--ns-divider); }

.ns-dt-pop-item:disabled { color: var(--ns-ink-zero); background: none; }

.ns-dt-pop-radio.is-on,
.ns-dt-pop-radio-row.is-on { color: var(--ns-primary-hover); font-weight: 600; }

.ns-dt-radio {
  width: 13px;
  height: 13px;
  border: 1.5px solid var(--ns-border-strong);
  border-radius: 50%;
  flex: none;
}

.ns-dt-pop-radio.is-on .ns-dt-radio {
  border-color: var(--ns-primary);
  box-shadow: inset 0 0 0 3px var(--ns-surface), inset 0 0 0 7px var(--ns-primary);
}

.ns-dt-pop-ops {
  display: flex;
  gap: 4px;
  margin-bottom: 9px;
}

.ns-dt-op {
  flex: 1;
  text-align: center;
  border: 1px solid var(--ns-border-strong);
  border-radius: 8px;
  padding: 5px 6px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ns-slate);
  min-height: 28px;
}

.ns-dt-op input { position: absolute; opacity: 0; pointer-events: none; }

.ns-dt-op.is-on {
  background: var(--ns-primary-tint);
  border-color: var(--ns-info-border);
  color: var(--ns-primary-hover);
}

.ns-dt-pop-custom {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--ns-muted);
}

.ns-dt-pop-number { margin-bottom: 8px; }

.ns-dt-pop-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  border-top: 1px solid var(--ns-divider);
  margin-top: 4px;
  padding-top: 8px;
  font-size: 11px;
  color: var(--ns-muted);
}

/* --- 5.3 · the column rows inside the Columns popover --- */

.ns-dt-col-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 4px;
  border-radius: 8px;
  min-height: 32px;
}

.ns-dt-col-row:hover { background: var(--ns-divider); }

.ns-dt-col-row.is-dragging { opacity: .45; }

.ns-dt-col-row.is-lifted {
  background: var(--ns-primary-tint);
  box-shadow: inset 0 0 0 1px var(--ns-info-border);
}

.ns-dt-col-label {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  font-size: 12.5px;
  color: var(--ns-text-700);
  min-width: 0;
}

.ns-dt-col-label span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ns-dt-col-label input { accent-color: var(--ns-primary); flex: none; }

.ns-dt-grip {
  width: 12px;
  color: var(--ns-ink-zero);
  cursor: grab;
  line-height: 0;
  flex: none;
}

.ns-dt-grip.is-idle { cursor: default; }

.ns-dt-lock { width: 12px; color: var(--ns-muted); line-height: 0; flex: none; }

/* --- the table card --- */

.ns-dt-card { overflow: hidden; }

/* Same trap as .ns-set-card (see the comment by .ns-card:has(> .ns-panel-head)): the rows-per-page
   select's menu must not be clipped by the card that hosts it while it's open. */
.ns-dt-card:has(.dropdown-menu.show) {
  overflow: visible;
}

.ns-dt-scroll {
  overflow: auto;
  max-height: calc(100vh - 300px);
}

.ns-dt-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: 13px;
  table-layout: fixed;
}

.ns-dt-table.is-resizing { cursor: col-resize; user-select: none; }

/* --- 5.6 · sticky header and first column --- */

.ns-dt-table thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--ns-thead-bg);
  border-bottom: 1px solid var(--ns-border);
  padding: 0;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ns-muted);
  white-space: nowrap;
  text-align: left;
}

.ns-dt-table td {
  padding: 11px 14px;
  vertical-align: middle;
  border-bottom: 1px solid var(--ns-divider);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-dt-table tbody tr:hover td { background: var(--ns-thead-bg); }

/* The first column sticks left with an opaque fill and a shadow that makes the rest look like it
   slides underneath. Its own header cell needs both directions, and a higher z-index than either. */
.ns-dt-table th.is-pinned,
.ns-dt-table td.is-pinned {
  position: sticky;
  left: 0;
  z-index: 3;
  background: var(--ns-surface);
  box-shadow: 6px 0 8px -7px rgba(16, 40, 70, .10);
}

.ns-dt-table thead th.is-pinned { z-index: 4; background: var(--ns-thead-bg); }

.ns-dt-table tbody tr:hover td.is-pinned { background: var(--ns-thead-bg); }

/* --- 5.1 · sort --- */

.ns-dt-sort,
.ns-dt-label {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 9px 14px;
  color: inherit;
  text-decoration: none;
  min-height: 32px;
}

.ns-dt-sort:hover {
  background: var(--ns-divider);
  color: var(--ns-text-700);
}

.ns-dt-table th.is-sorted {
  background: var(--ns-primary-tint);
  color: var(--ns-primary-hover);
}

.ns-dt-table th.is-sorted .ns-dt-sort:hover { background: var(--ns-primary-tint); }

/* A faint tint down the sorted column, so the eye can follow it. */
.ns-dt-table td.is-sorted { background: var(--ns-surface-quiet); }

.ns-dt-table tbody tr:hover td.is-sorted { background: var(--ns-thead-bg); }

.ns-dt-caret { flex: none; transform: rotate(180deg); }

.ns-dt-caret.is-desc { transform: none; }

.ns-dt-table th.is-actions { text-align: right; }

.ns-dt-table td.is-actions { text-align: right; overflow: visible; }

/* --- 5.4 · resize --- */

.ns-dt-resize {
  position: absolute;
  top: 0;
  right: 0;
  width: 6px;
  height: 100%;
  cursor: col-resize;
  border-right: 1px solid var(--ns-border);
  /* A drag handle: without this a touch drag scrolls the table instead of resizing. */
  touch-action: none;
}

/* A 6px strip is a mouse affordance; a finger needs more to grab. Wider only on touch, and
   still far narrower than the sort link beside it. */
@media (pointer: coarse) {
  .ns-dt-resize {
    width: 16px;
  }
}

.ns-dt-table th { position: relative; }

.ns-dt-resize:hover,
.ns-dt-resize:focus-visible { border-right: 2px solid var(--ns-primary); outline: none; }

/* The full-height guide line, so a drag shows where the edge is landing. */
.ns-dt-resize.is-dragging {
  border-right: 2px solid var(--ns-primary);
}

.ns-dt-resize.is-dragging::after {
  content: "";
  position: absolute;
  top: 100%;
  right: -1px;
  width: 2px;
  height: 100vh;
  background: var(--ns-primary);
  opacity: .35;
}

/* --- 5.5 · group headers --- */

.ns-dt-group th {
  position: sticky;
  top: 33px;
  z-index: 1;
  background: var(--ns-thead-bg);
  border-bottom: 1px solid var(--ns-border);
  padding: 0;
  text-align: left;
}

.ns-dt-group-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  border: 0;
  background: none;
  padding: 7px 14px;
  font-size: 12px;
  color: var(--ns-text-700);
  min-height: 32px;
}

.ns-dt-group-btn:hover { background: var(--ns-divider); }

.ns-dt-group-caret { flex: none; transform: rotate(90deg); transition: transform .14s ease; }

.ns-dt-group.is-collapsed .ns-dt-group-caret { transform: none; }

.ns-dt-group-label { font-weight: 700; color: var(--ns-ink); }

.ns-dt-group-count { font-weight: 700; color: var(--ns-text-700); }

.ns-dt-group-cont { font-size: 11px; color: var(--ns-muted); font-weight: 500; }

/* --- 4 · cell types --- */

/* The first column's anchor fills its cell, so the whole cell is the click target rather than just the
   words in it. The cell gives up its padding to the link — same visual result, much bigger target.
   Safe to target the pinned cell directly: the pinned column is always the name column, and all three
   row partials render it as this link. */
/* height: 1px is the long-standing table trick, not a typo: a percentage height on a cell's child only
   resolves against a *definite* height, and a table cell has none by default. A cell's height is a
   minimum — it still stretches to the row — so this changes nothing visually and gives the link inside
   something to be 100% of. */
.ns-dt-table td.is-pinned { padding: 0; height: 1px; }

/* Flex column, centred, full height: the row's height is set by whichever cell is tallest (usually the
   progress segments), and a plain block link only covered its own text — leaving dead strips above and
   below inside a cell that looks clickable. */
.ns-dt-cell-link {
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: 100%;
  padding: 11px 14px;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}

.ns-dt-cell-link:hover { text-decoration: none; }

.ns-dt-cell-link:hover .ns-dt-name { color: var(--ns-primary-hover); }

/* Focus lands on the whole cell, which is what is clickable, rather than on a word inside it. */
.ns-dt-cell-link:focus-visible {
  outline: 2px solid var(--ns-primary);
  outline-offset: -2px;
  border-radius: 4px;
}

/* The cell link stacks its children in a COLUMN (name, then .ns-dt-sub), so anything meant to sit
   *beside* the name needs its own row — a bare pill dropped in there becomes a flex item and
   stretches to the full cell width, which is exactly how the "you" marker shipped wrong first time.
   min-width: 0 on both so the name ellipsises instead of shoving the marker out of the cell. */
.ns-dt-nameline {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.ns-dt-nameline > .ns-dt-name {
  min-width: 0;
}

.ns-dt-name {
  display: block;
  font-weight: 700;
  color: var(--ns-ink);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-dt-name:hover { color: var(--ns-primary-hover); }

.ns-dt-sub {
  display: block;
  font-size: 11.5px;
  color: var(--ns-muted);
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-dt-num { font-variant-numeric: tabular-nums; }

/* A number somebody can still act on: the documents-destroyed countdown inside its warning
   horizon. Amber ink plus the dot below - never the colour alone (polish-pass.md section 3). */
.ns-dt-num.is-warning {
  color: var(--ns-warning-text);
  font-weight: 700;
}

/* The severity dot sitting inline before a number, rather than in its own flex row. 6px here
   against the row's 8px: at 12px text an 8px dot reads as a bullet point. */
.ns-dt-dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 5px;
  vertical-align: 1px;
}

.ns-dt-num-strong { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ns-ink); }

.ns-dt-quiet { color: var(--ns-muted); }

.ns-dt-progress-ratio { font-size: 12px; font-weight: 600; color: var(--ns-text-700); }

.ns-dt-segments {
  display: flex;
  gap: 2px;
  margin-top: 4px;
}

.ns-dt-seg {
  width: 16px;
  height: 4px;
  border-radius: 2px;
  background: var(--ns-border);
}

.ns-dt-seg.is-signed { background: var(--ns-primary); }

.ns-dt-seg.is-complete { background: var(--ns-success); }

/* --- 5.7 · footer --- */

.ns-dt-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: var(--ns-thead-bg);
  border-top: 1px solid var(--ns-divider);
  padding: 8px 14px;
  font-size: 11.5px;
  color: var(--ns-muted);
}

.ns-dt-foot-controls { display: flex; align-items: center; gap: 8px; }

.ns-dt-page-size .form-select { width: auto; font-size: 11.5px; padding: 2px 22px 2px 8px; }

.ns-dt-page-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: 1px solid var(--ns-border-strong);
  border-radius: 7px;
  background: var(--ns-surface);
  color: var(--ns-text-700);
}

.ns-dt-page-btn:hover { border-color: var(--ns-primary); color: var(--ns-primary-hover); }

/* Disabled stays visible, so the control does not change shape as you page. */
.ns-dt-page-btn.is-disabled {
  color: var(--ns-border-strong);
  pointer-events: none;
}

/* --- 7 · states --- */

/* Shared by the Table layout's post-header empty state and the Cards layout's empty state alike
   (Table System Spec §7). Deliberately a sibling of the scroller/grid, never a row inside it — a
   table cell's width follows the full column set, which is usually wider than the viewport once
   there is horizontal scroll to be had, so a message centered inside one centers on the
   *scrollable table's* width instead of what is actually on screen. As a plain block it just
   centers in the space the list would otherwise occupy. */
.ns-dt-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(100vh - 300px);
}

.ns-dt-empty .ns-empty { padding: 40px 16px; }

.ns-dt-flash {
  background: var(--ns-warning-50);
  border: 1px solid var(--ns-warning-tint);
  border-radius: var(--ns-radius-control);
  padding: 8px 12px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ns-warning-text);
}

/* A shared backdrop for every .ns-dt-pop this table can open (Group, Columns, + Filter, a chip's
   own editor) — inert above the 1024px breakpoint, where those stay anchored dropdowns and never
   need one. Mirrors .ns-dz-backdrop (docs/design/components/panels-rails-inspectors.md — "The
   sheet"): same z-index scale, so a data-table sheet and the designer's field-list sheet never
   fight for a layer. */
.ns-dt-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1045;
  background: rgba(7, 25, 40, .4);
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s ease;
}

/* --- responsive: below 1024px, Group and Columns collapse into one sheet --- */

@media (max-width: 1024px) {
  .ns-dt-toolbar { flex-wrap: wrap; }

  .ns-dt-search { width: 100%; }

  .ns-dt-search-form { flex: 1 1 100%; }

  /* Chips scroll horizontally rather than stacking the toolbar into four rows. */
  .ns-dt-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    flex: 1 1 100%;
    padding-bottom: 2px;
  }

  .ns-dt-spacer { display: none; }

  .ns-dt-backdrop {
    display: block;
  }

  .ns-dt-backdrop.is-in {
    opacity: 1;
    pointer-events: auto;
  }

  /* A true bottom sheet, not just a repositioned dropdown: it slides up over the page rather than
     fading in place, sits above the nav rail and top bar (their own z-index tops out at 1035), and
     slides back down on close — same treatment, same z-index scale as .ns-dz-fieldlist. */
  .ns-dt-pop {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    width: auto;
    max-height: 72vh;
    max-height: 72dvh;
    overflow-y: auto;
    border-radius: var(--ns-radius-panel) var(--ns-radius-panel) 0 0;
    box-shadow: 0 -12px 40px rgba(16, 40, 70, .22);
    z-index: 1046;
    opacity: 1;
    transform: translateY(105%);
    transition: transform .22s cubic-bezier(.2, .7, .2, 1);
    /* Docked to the bottom edge now, so the home-indicator inset is part of its padding. */
    padding-bottom: calc(10px + env(safe-area-inset-bottom));
  }

  .ns-dt-pop.is-in {
    transform: translateY(0);
  }

  .ns-dt-pop-columns,
  .ns-dt-pop-filter { width: auto; }
}

/* Phone: the pinned name column keeps its saved desktop width, which can be most or all of
   a 375px screen — scrolling then just slides the other columns underneath an opaque wall.
   Below 576px the column unpins and scrolls with the rest; the row is still reachable, the
   wall is gone. */
@media (max-width: 575.98px) {
  .ns-dt-table td.is-pinned {
    position: static;
    box-shadow: none;
  }

  /* The header cell keeps its vertical stick with the rest of the thead — only the
     left-pinning releases. */
  .ns-dt-table thead th.is-pinned {
    left: auto;
    box-shadow: none;
  }
}

/* --- responsive: the touch pass ---
   The list table never got the (pointer: coarse) treatment the panel links have (see the note
   by .ns-panel-link): the toolbar's chips and buttons sat at 24–32px. Two treatments, same
   split as the designer's: controls with room to breathe grow to the 44px floor, and the
   dense in-row atoms keep their visual size but gain an invisible ::after hit area whose
   overlap only reaches ambiguous space or a same-subject neighbour. */
@media (pointer: coarse) {
  .ns-dt-search .form-control-sm,
  .ns-dt-btn,
  .ns-dt-add-filter,
  .ns-dt-reset,
  .ns-dt-layout-btn,
  .ns-dt-chip-body,
  .ns-dt-pop-item,
  .ns-dt-pop-radio,
  .ns-dt-pop-check,
  .ns-dt-sort,
  .ns-dt-label {
    min-height: 44px;
  }

  .ns-dt-page-btn {
    width: 44px;
    height: 44px;
  }

  .ns-kebab,
  .ns-dt-chip-x,
  .ns-dt-table .btn-sm,
  .ns-dt-cards .btn-sm {
    position: relative;
  }

  .ns-kebab::after,
  .ns-dt-chip-x::after,
  .ns-dt-table .btn-sm::after,
  .ns-dt-cards .btn-sm::after {
    content: "";
    position: absolute;
  }

  .ns-kebab::after { inset: -10px; }
  .ns-dt-chip-x::after { inset: -11px; }
  .ns-dt-table .btn-sm::after,
  .ns-dt-cards .btn-sm::after { inset: -6px; }
}

/* ==========================================================================
   The Features console — the hub (/Admin/Features) and the page per feature
   (/Admin/Features/{feature}). Source mock: "Feature Control.dc.html" frame 2a,
   in the "NimbleSign feature control" design project.

   Why a grid and not .ns-panel-row: a feature row answers three questions side
   by side — what it is, who reaches it, and what this organization has said —
   and the reading depends on those columns lining up down the list. A flex row
   lets each one size to its own content, which is what made the old grid hard
   to scan. Below 1100px the row stacks: same breakpoint, same reason as
   .ns-set-row.
   ========================================================================== */

.ns-feat-group {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 18px;
  border-top: 1px solid var(--ns-border);
  background: var(--ns-surface);
}

/* A group nothing is paying for yet: tinted so the eye separates "what you
   have" from "what you would have to buy" before reading either heading. */
.ns-feat-group.is-locked {
  background: var(--ns-blue-50);
}

.ns-feat-group-name {
  display: block;
  font-size: 12.5px;
  font-weight: 800;
  color: var(--ns-ink);
}

.ns-feat-group.is-locked .ns-feat-group-name {
  color: var(--ns-blue-hover);
}

.ns-feat-group-blurb {
  display: block;
  font-size: 11.5px;
  line-height: 1.45;
  margin-top: 1px;
  color: var(--ns-muted);
}

.ns-feat-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 170px 260px 34px;
  gap: 18px;
  align-items: center;
  padding: 13px 18px;
  border-top: 1px solid var(--ns-divider);
}

.ns-feat-row:hover {
  background: var(--ns-thead-bg);
}

/* Reaching nobody. A tint rather than opacity: the row still has to be
   readable, because an admin looking at an off feature is usually about to
   turn it on. */
.ns-feat-row.is-off {
  background: var(--ns-canvas);
}

.ns-feat-row.is-off:hover {
  background: var(--ns-thead-bg);
}

.ns-feat-name {
  display: flex;
  align-items: center;
  gap: 8px;
  /* A long French name plus a badge does not fit on one line — same rule, and
     the same reason, as the setting row's label. */
  flex-wrap: wrap;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--ns-ink);
  text-align: left;
}

.ns-feat-blurb {
  font-size: 12.25px;
  line-height: 1.5;
  color: var(--ns-muted);
  margin: 4px 0 0;
  max-width: 52ch;
}

.ns-feat-reach {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-feat-row.is-off .ns-feat-reach {
  color: var(--ns-muted);
}

.ns-feat-reason {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ns-muted);
  margin-top: 6px;
}

/* An escalation, not a preference: this reason names somebody else to go to. */
.ns-feat-reason.is-escalation {
  color: var(--ns-warning-text);
}

.ns-feat-open {
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--ns-muted);
}

.ns-feat-open:hover {
  color: var(--ns-primary);
  background: var(--ns-blue-50);
}

/* ---- the page per feature ---- */

.ns-feat-detail {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
  max-width: 1000px;
}

.ns-feat-detail > div {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

/* One audience choice. A bordered card rather than a bare radio row, so the
   chosen answer is legible at a glance from across the panel. */
.ns-feat-choice {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  width: 100%;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-control);
  padding: 11px 12px;
  margin: 4px 0;
  cursor: pointer;
  background: var(--ns-surface);
}

.ns-feat-choice:has(> input:checked) {
  border-color: var(--ns-info-border);
  background: var(--ns-blue-50);
}

.ns-feat-choice:has(> input:disabled) {
  cursor: not-allowed;
}

/* The whole row is the click target, so the whole row answers the pointer. Only the unpicked ones:
   hovering the current answer must not undo the three signals that say it is current. 140ms, the
   hover/colour band in docs/design/foundations/motion.md. */
.ns-feat-choice {
  transition: border-color .14s ease, background-color .14s ease;
}

.ns-feat-choice:hover:not(:has(> input:checked)):not(:has(> input:disabled)) {
  border-color: var(--ns-border-strong);
  background: var(--ns-canvas);
}

.ns-feat-choice-label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-feat-choice:has(> input:disabled) .ns-feat-choice-label {
  color: var(--ns-muted);
}

.ns-feat-choice-note {
  display: block;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ns-muted);
  margin-top: 2px;
}

.ns-feat-choice-count {
  flex: none;
  margin-top: 1px;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ns-muted);
}

.ns-feat-choice:has(> input:checked) .ns-feat-choice-count {
  color: var(--ns-blue-hover);
}

/* The roles governed by the choice above them: indented under it and hung off a
   rule, so it reads as belonging to that answer rather than as a fourth one. */
/* Indented under the audience choice it belongs to. It is a SIBLING of the radiogroup rather than a
   child (a radiogroup must contain only radios), so the left margin has to make up the 8px padding
   the radiogroup used to supply, and the bottom margin the 10px it used to close with. */
.ns-feat-roles {
  margin: 2px 16px 12px 42px;
  border-left: 2px solid var(--ns-border);
  padding-left: 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ns-feat-funnel {
  padding: 14px 18px 16px;
  display: flex;
  flex-direction: column;
  gap: 11px;
}

.ns-feat-step-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 12px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-feat-step.is-blocking .ns-feat-step-head,
.ns-feat-step.is-blocking .ns-feat-step-note {
  color: var(--ns-danger-text);
}

/* Taller than .ns-usage-bar: this bar is the panel's subject rather than a
   detail beside a number, and six 4px bars in a column read as hairlines. */
.ns-feat-step-bar {
  display: block;
  height: 9px;
  margin-top: 5px;
  border-radius: 3px;
  background: var(--ns-divider);
  overflow: hidden;
}

.ns-feat-step-fill {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--ns-primary);
}

.ns-feat-step.is-everyone .ns-feat-step-fill {
  background: var(--ns-success);
}

.ns-feat-step.is-blocking .ns-feat-step-fill {
  background: var(--ns-border-strong);
}

.ns-feat-step-note {
  font-size: 11px;
  line-height: 1.45;
  color: var(--ns-muted);
  margin-top: 4px;
}

.ns-feat-trace {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr);
  gap: 11px;
  align-items: start;
  padding: 11px 18px;
  border-top: 1px solid var(--ns-divider);
}

.ns-feat-trace-mark {
  width: 20px;
  height: 20px;
  margin-top: 1px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  background: var(--ns-success-50);
  color: var(--ns-success-text);
}

/* The layer that actually refuses. The ones after it are off too, but they are
   consequences — colouring them all red hides which one there is to act on. */
.ns-feat-trace.is-blocker .ns-feat-trace-mark {
  background: var(--ns-danger-50);
  color: var(--ns-danger-text);
}

.ns-feat-trace.is-downstream .ns-feat-trace-mark {
  background: var(--ns-divider);
  color: var(--ns-muted);
}

.ns-feat-trace-layer {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-feat-trace.is-blocker .ns-feat-trace-layer {
  color: var(--ns-danger-text);
}

.ns-feat-trace-verdict {
  font-size: 11px;
  font-weight: 700;
  color: var(--ns-success-text);
}

.ns-feat-trace:not(.is-allowing) .ns-feat-trace-verdict {
  color: var(--ns-danger-text);
}

.ns-feat-trace-detail {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ns-muted);
  margin-top: 2px;
}

.ns-feat-trace-owner {
  font-size: 11px;
  line-height: 1.4;
  color: var(--ns-slate);
  margin-top: 3px;
}

@media (max-width: 1100px) {
  .ns-feat-row {
    grid-template-columns: minmax(0, 1fr) 34px;
    row-gap: 10px;
  }

  /* The chevron stays pinned beside the name, so the tap target does not end up
     below two stacked cells. */
  .ns-feat-open {
    grid-row: 1;
    grid-column: 2;
  }

  .ns-feat-row > .ns-feat-cell {
    grid-column: 1 / -1;
  }

  .ns-feat-detail {
    grid-template-columns: minmax(0, 1fr);
  }

  .ns-feat-roles {
    margin-left: 16px;
  }
}

/* The unsaved-change amber reaches every savable row shape, not only .ns-set-row. settings.js puts
   is-dirty on whatever rowOf() returned, which is also a roster <tr> and any [data-settings-row] a
   page declared (a Features hub row, the audience question, the deployment switch). Without these
   the switch stayed blue and the only sign of a pending change was the bar's counter. */
[data-settings-row].is-dirty .ns-switch > input:checked,
tbody tr.is-dirty .ns-switch > input:checked {
  background: var(--ns-warning);
}

[data-settings-row].is-dirty .ns-switch > input:not(:checked),
tbody tr.is-dirty .ns-switch > input:not(:checked) {
  background: #D9C7A4;
}

[data-settings-row].is-dirty .form-check-input:checked {
  background-color: var(--ns-warning);
  border-color: var(--ns-warning);
}

/* A hub row has no control column to tint, so it takes the amber edge on the row itself. */
.ns-feat-row.is-dirty,
.ns-feat-detail [data-settings-row].is-dirty {
  box-shadow: inset 3px 0 0 var(--ns-warning);
}

[data-settings-row].is-saving,
tbody tr.is-saving {
  opacity: .55;
  pointer-events: none;
}

/* A switch label that names a thing rather than a state. Looks like .ns-switch-state but is
   deliberately not that class: settings.js rewrites .ns-switch-state to the on/off word when its
   switch is flipped, which would replace a role name and its headcount with "On". */
.ns-feat-role-name {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-ink);
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  cursor: pointer;
}

/* ==========================================================================
   Plans — the ladder, its marks, and the plan cards on Plans & features.
   Source mock: "Plans.dc.html" in the "NimbleSign feature control" design
   project, and the plan marks in "Feature Control.dc.html" frame 2a.

   One palette per tier, used wherever a plan's mark or name appears so a plan
   reads identically on its card, on a feature group header and on an
   organization's own Features page. Tier is an ordinal, so the palette is
   applied by a data attribute rather than a class per plan key — a
   deployment can rename its plans without touching CSS.
   ========================================================================== */

.ns-plan {
  --ns-plan-ink: var(--ns-slate);
  --ns-plan-text: var(--ns-slate);
  --ns-plan-tint: var(--ns-divider);
  --ns-plan-border: var(--ns-border-strong);
  /* The seed a chosen swatch overrides. Declared here, at the lowest specificity of anything that
     touches it, so a swatch rule below always wins — a fallback that outranks the real answer is
     the trap this avoids. It only reaches the four properties above via the derivation rule, so a
     plan with no swatch never picks it up. */
  --ns-plan-seed: var(--ns-primary);
}

.ns-plan[data-tier="1"] {
  --ns-plan-ink: var(--ns-primary);
  --ns-plan-text: var(--ns-primary-hover);
  --ns-plan-tint: var(--ns-primary-tint);
  --ns-plan-border: var(--ns-info-border);
}

/* Tier 2 and anything above it share the top tone: a deployment may add a
   fourth plan, and it should read as "the most", not as an unstyled outlier. */
.ns-plan[data-tier="2"],
.ns-plan[data-tier="3"],
.ns-plan[data-tier="4"] {
  --ns-plan-ink: var(--ns-nav);
  --ns-plan-text: var(--ns-nav);
  --ns-plan-tint: rgba(var(--ns-nav-rgb), .07);
  --ns-plan-border: rgba(var(--ns-nav-rgb), .24);
}

/* ---- a plan's own colour ----

   Tier says how *much* a plan sells, and three tiers share two tones, so an operator who has named
   their plans still cannot tell them apart at a glance. A plan may therefore be given a colour of
   its own, which overrides the tier tone wherever the plan appears. Declared after the tier rules
   above and matching them in specificity, so source order is what decides.

   One derivation and seven seeds, rather than seven copies of four properties — the rule for
   anything colour-coded by data (see design/components/pills-badges-chips.md). Everything already
   reading --ns-plan-ink / -text / -tint / -border retints for free: the mark, the plan name, the
   card border and its current-plan edge, the feature group header, the plan letter chips.

   A plan with no colour set carries no attribute and keeps its tier tone — an existing deployment
   looks exactly as it did until someone chooses otherwise. */
/* No swatch borrows a status colour. Green means finished and proven, amber that a human has to
   act, red that something failed — a plan is a tier, not a state, and a plan drawn in amber would
   claim something nobody intended while sitting in the same row as the pill that means it for
   real. The hues are the identity tokens defined at the top of this file, so a coloured plan looks
   like part of this app rather than a palette bolted on beside it. */
.ns-plan[data-swatch="blue"] { --ns-plan-seed: var(--ns-primary); }
.ns-plan[data-swatch="navy"] { --ns-plan-seed: var(--ns-nav); }
.ns-plan[data-swatch="teal"] { --ns-plan-seed: var(--ns-hue-teal); }
.ns-plan[data-swatch="indigo"] { --ns-plan-seed: var(--ns-hue-indigo); }
.ns-plan[data-swatch="violet"] { --ns-plan-seed: var(--ns-hue-violet); }
/* Plum sits far enough from --ns-maple that it cannot be taken for the danger red. */
.ns-plan[data-swatch="plum"] { --ns-plan-seed: var(--ns-hue-plum); }
.ns-plan[data-swatch="slate"] { --ns-plan-seed: var(--ns-slate); }

/* The derivation, last and deliberately the most specific selector here: it maps whichever seed
   won above onto the four properties everything else reads.

   :not([data-swatch=""]) is load-bearing. Razor renders a null attribute value as data-swatch=""
   rather than dropping the attribute, so a bare [data-swatch] would match every plan that has
   chosen no colour and repaint it in the fallback seed — which would quietly wipe the tier tone
   from every existing deployment, the one thing this feature must not do. */
.ns-plan[data-swatch]:not([data-swatch=""]) {
  --ns-plan-ink: var(--ns-plan-seed);
  --ns-plan-text: var(--ns-plan-seed);
  --ns-plan-tint: color-mix(in srgb, var(--ns-plan-seed) 8%, #fff);
  --ns-plan-border: color-mix(in srgb, var(--ns-plan-seed) 26%, #fff);
}

/* ---- how a plan is drawn: two treatments, and which one applies ----

   A plan label that has a SHAPE — a chip, a pill, a filled header — wears the pair: a light mix of
   the plan's own colour behind the plan's colour as text. A plan named as TEXT — a link, a heading,
   a name beside its mark — takes the colour alone, because giving running text a filled background
   makes it a chip, and then there are two kinds of chip.

   Both live here, together, so the pair cannot come apart. It already did once: the plan a tenant
   is on reused .ns-scope.is-tenant, which is a blue pill, so overriding only the text left violet
   letters on a blue tint — the plan's colour saying one thing and its background another.

   Guarded on :not([data-swatch=""]) throughout: a plan nobody has coloured keeps exactly the
   treatment it had before any of this existed. */

/* Shaped: the tint and the text, always together. */
.ns-scope.ns-plan[data-swatch]:not([data-swatch=""]) {
  background: var(--ns-plan-tint);
  color: var(--ns-plan-text);
}

/* Text: the colour alone. */
.ns-feat-name.ns-plan[data-swatch]:not([data-swatch=""]) {
  color: var(--ns-plan-text);
}

/* ---- the picker ----
   The palette as swatches rather than a dropdown of colour names: the question is which of these
   looks right, and that is answered by looking. Each is a label wrapping a real radio. */
.ns-plan-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.ns-plan-swatch {
  position: relative;
  display: inline-flex;
  cursor: pointer;
}

.ns-plan-swatch > input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.ns-plan-swatch > span[aria-hidden] {
  width: 26px;
  height: 26px;
  border-radius: 999px;
  background: var(--ns-plan-seed);
  /* Holds the edge of a pale swatch against a white card without adding a real border. */
  box-shadow: inset 0 0 0 1px rgba(16, 40, 70, .09);
  transition: box-shadow .15s ease, transform .15s ease;
}

.ns-plan-swatch:hover > span[aria-hidden] {
  transform: translateY(-1px);
}

/* Selected is a ring *outside* the circle, not a border on it: a border would eat into 26px and
   shrink the colour, which reads as the colour changing rather than as it being chosen. */
.ns-plan-swatch > input:checked + span {
  box-shadow: inset 0 0 0 1px rgba(16, 40, 70, .09),
              0 0 0 2px var(--ns-surface),
              0 0 0 4px var(--ns-plan-seed);
}

.ns-plan-swatch > input:focus-visible + span {
  box-shadow: inset 0 0 0 1px rgba(16, 40, 70, .09),
              0 0 0 2px var(--ns-surface),
              0 0 0 4px var(--ns-plan-seed),
              var(--ns-focus-ring);
}

/* The mark: a three-step stair filled up to this plan's tier. One inline SVG in
   currentColor, so the container's colour sets the tone. */
.ns-plan-mark {
  display: inline-flex;
  flex: none;
  color: var(--ns-plan-ink);
}

.ns-plan-name {
  font-weight: 800;
  color: var(--ns-plan-text);
}

/* ---- the plan cards ---- */

.ns-plan-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
  max-width: 1000px;
  margin-bottom: 16px;
  align-items: stretch;
}

.ns-plan-card {
  background: var(--ns-surface);
  border: 1px solid var(--ns-plan-border);
  border-radius: var(--ns-radius-card);
  box-shadow: var(--ns-shadow-card-sm);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 11px;
  min-width: 0;
}

/* The plan an organization is actually on, when the card strip is shown in a
   context that has one. A left edge rather than a full tint: the tier palette
   already owns the card's colour, and two colour signals on one card compete. */
.ns-plan-card.is-current {
  border-left-width: 3px;
  border-left-color: var(--ns-plan-ink);
}

.ns-plan-card-head {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}

.ns-plan-card-title {
  font-size: 16px;
  letter-spacing: -.01em;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ns-plan-card-sub {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ns-muted);
}

.ns-plan-card-grants {
  display: flex;
  flex-direction: column;
  gap: 5px;
  border-top: 1px solid var(--ns-divider);
  padding-top: 10px;
}

.ns-plan-grant {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ns-text-700);
}

.ns-plan-grant > svg {
  flex: none;
  margin-top: 2px;
  color: var(--ns-plan-ink);
}

/* Withheld, not granted — muted and crossed, so a scan of the card separates
   what the plan buys from what it does not without reading the words. */
.ns-plan-grant.is-off {
  color: var(--ns-muted);
}

.ns-plan-grant.is-off > svg {
  color: var(--ns-border-strong);
}

.ns-plan-card-foot {
  margin-top: auto;
  padding-top: 8px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 9px;
  border-top: 1px solid var(--ns-divider);
}

.ns-plan-key {
  font-family: var(--ns-font-mono);
  font-size: 11px;
  color: #B6C1CE;
}

/* ---- picking a plan while signing up ----
   A plan card narrow enough for the sign-in card's 410px column, with a real radio behind it so it
   posts, tabs and reads out without any JS — the same mechanic as the colour swatches in the plan
   editor. Every colour here is read from the four .ns-plan custom properties rather than chosen
   (plan-colour.md: add a consumer by reading one of these, never by adding a colour), which is what
   keeps a plan recognisable between this page and the operator console. */

.ns-signup-plan {
  display: block;
  position: relative;
  margin: 0;
  padding: 11px 13px;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-control);
  background: var(--ns-surface);
  cursor: pointer;
  transition: border-color .16s ease, background-color .16s ease, box-shadow .16s ease;
}

.ns-signup-plan + .ns-signup-plan {
  margin-top: 8px;
}

.ns-signup-plan:hover {
  border-color: var(--ns-plan-border);
}

/* The radio is the accessible control; the card is its label, so the ring goes on the card. */
.ns-signup-plan > input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.ns-signup-plan:has(> input:focus-visible) {
  box-shadow: var(--ns-focus-ring);
  border-color: var(--ns-plan-border);
}

.ns-signup-plan:has(> input:checked) {
  border-color: var(--ns-plan-border);
  background: var(--ns-plan-tint);
}

.ns-signup-plan-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* min-width:0 and the ellipsis together: an operator can name a plan anything, and without these a
   long name pushes the chosen tick off the right edge of a 410px card. */
.ns-signup-plan-head .ns-plan-name {
  min-width: 0;
  font-size: 13.5px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The amount, at the end of the name row. It takes the auto margin so the row reads
   name … amount ✓; deliberately NOT in the plan colour, because it states a fact about the price
   rather than identifying the plan (plan-colour.md). A plan whose amount the provider could not
   resolve has no element here at all, and the row is simply as it was before prices were shown. */
.ns-signup-plan-price {
  margin-left: auto;
  flex: none;
  font-size: 13.5px;
  font-weight: 700;
  white-space: nowrap;
}

.ns-signup-plan-price small {
  margin-left: 1px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--ns-muted);
}

/* A tick, revealed only on the chosen card: the tint alone is a weak signal at this size, and
   colour must not be the only thing carrying the state. */
.ns-signup-plan-tick {
  margin-left: auto;
  flex: none;
  color: var(--ns-plan-text);
  opacity: 0;
  transform: scale(.7);
  transition: opacity .16s ease, transform .16s cubic-bezier(.34, 1.4, .5, 1);
}

/* With a price present the tick sits beside it rather than claiming an auto margin of its own,
   which would otherwise split the free space and leave the amount stranded mid-row. */
.ns-signup-plan-head:has(.ns-signup-plan-price) .ns-signup-plan-tick {
  margin-left: 8px;
}

.ns-signup-plan:has(> input:checked) .ns-signup-plan-tick {
  opacity: 1;
  transform: none;
}

.ns-signup-plan-sub {
  margin-top: 3px;
  font-size: 12px;
  color: var(--ns-muted);
}

/* The monthly/annual choice — the small choice-segment form from
   design/components/tabs-and-segmented.md ("Small choice segments"), which is the value-picking one:
   three signals on active (border, tint, text colour), never colour alone. Two equal-width options,
   real radios behind them so the group posts, tabs and reads out without any JS. */

.ns-signup-seg {
  display: flex;
  gap: 6px;
}

.ns-signup-seg-opt {
  flex: 1;
  min-width: 0;
  position: relative;
  margin: 0;
  padding: 7px 10px;
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-control);
  background: var(--ns-surface);
  color: var(--ns-slate);
  font-size: 12.5px;
  font-weight: 700;
  text-align: center;
  cursor: pointer;
  transition: border-color .16s ease, background-color .16s ease, color .16s ease,
              opacity .16s ease;
}

.ns-signup-seg-opt > input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.ns-signup-seg-opt:hover:not(:has(> input:checked)):not(:has(> input:disabled)) {
  border-color: var(--ns-border-strong);
  color: var(--ns-ink);
}

.ns-signup-seg-opt:has(> input:focus-visible) {
  outline: none;
  border-color: var(--ns-blue-600);
  box-shadow: var(--ns-focus-ring);
}

/* The same three signals the designer's choice segments use — border, tint and text colour, never
   colour alone. Recipe copied from .ns-dz-insp-segbtns.active so the two instances cannot drift; the
   #fff in the mix is the white the tint is diluted into, not a chosen colour. */
.ns-signup-seg-opt:has(> input:checked) {
  border-color: var(--ns-blue-600);
  background: color-mix(in srgb, var(--ns-blue-600) 8%, #fff);
  color: var(--ns-blue-600);
  font-weight: 800;
}

/* A plan sold monthly only. Dimmed and inert, with the reason in the note below rather than left to
   be guessed — a disabled control that does not say why is the state the design rules call out. */
.ns-signup-seg-opt:has(> input:disabled) {
  opacity: .35;
  cursor: default;
}

.ns-signup-seg-note {
  margin-top: 5px;
  font-size: 12px;
  color: var(--ns-muted);
}

@media (prefers-reduced-motion: reduce) {
  .ns-signup-plan,
  .ns-signup-plan-tick,
  .ns-signup-seg-opt {
    transition: none;
  }
}

/* ---- the plan editor ---- */

.ns-plan-editor {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
  max-width: 1000px;
}

.ns-plan-editor > div {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

/* A cap row: the entitlement, its unit, and a number whose emptiness means
   unlimited. Deliberately the same two-column shape as a setting row, because
   that is what it is. */
.ns-plan-cap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 160px;
  gap: 14px;
  align-items: center;
  padding: 11px 18px;
  border-top: 1px solid var(--ns-divider);
}

.ns-plan-cap-name {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ns-ink);
}

.ns-plan-cap-note {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ns-muted);
  margin-top: 2px;
}

@media (max-width: 1100px) {
  .ns-plan-editor {
    grid-template-columns: minmax(0, 1fr);
  }

  .ns-plan-cap {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 8px;
  }
}

/* A feature group header carrying a plan: the tier tint replaces the flat blue the org console's
   "not in your plan" group uses, so the same header shape reads as "this tier" rather than as a
   single generic locked state. */
.ns-feat-group.ns-plan {
  background: var(--ns-plan-tint);
}

.ns-feat-group.ns-plan .ns-feat-group-name {
  color: var(--ns-plan-text);
}

/* Features no plan includes. Neutral rather than tinted: there is no tier to signal, and a plan
   change will not deliver them. */
.ns-feat-group.ns-plan.is-unplanned {
  background: var(--ns-thead-bg);
}

.ns-feat-group.ns-plan.is-unplanned .ns-feat-group-name {
  color: var(--ns-slate);
}

/* ==========================================================================
   The console two-column body — phase 2 of the Features console (design
   handoff "Features, plans & entitlements consoles"). Controls live in the
   left column; the right column is an information rail holding only things
   you read: the plan, the funnel, layer ownership, blast radius. Below
   1100px the grid collapses and the rail follows the controls — same
   breakpoint, same reason as .ns-set-row and .ns-feat-row.
   ========================================================================== */

.ns-console-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 20px;
  align-items: start;
  max-width: 1180px; /* .ns-set-stack's 1000px plus the 180px the rail adds */
}

.ns-console-main {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
}

.ns-inforail {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

/* Rail cards are 300px wide, so they trade the card's 14/18 padding for 13/15 —
   the same face, sized for the narrower column. */
.ns-inforail .ns-set-card-head { padding: 13px 15px; }
.ns-inforail .ns-panel-body { padding: 13px 15px; }

/* The rail's stat strip: label/value cells split by hairlines, inside a card. */
.ns-inforail-stats {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
}

.ns-inforail-stats > div {
  padding: 10px 12px;
  text-align: center;
}

.ns-inforail-stats > div + div {
  border-left: 1px solid var(--ns-divider);
}

.ns-inforail-stat-value {
  font-size: 19px;
  font-weight: 800;
  letter-spacing: -.01em;
}

.ns-inforail-stat-label {
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ns-muted);
}

/* The card that IS this instance: an ink edge, like the plan card's tier bar but in a token
   branding cannot reach — same rule as the .is-instance chip. It marks the deployment, so it must
   not change colour with whichever tenant the operator is acting as. */
.ns-inforail .ns-set-card.is-instance { border-left: 3px solid var(--ns-ink); }
.ns-inforail .ns-set-card.is-warning  { border-left: 3px solid var(--ns-warning); }

@media (max-width: 1100px) {
  .ns-console-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---- the scope chip ----
   Every console page header states which scope it writes in (design rule 1:
   scope is a destination). Same shape as .ns-pill; the tone is the scope. */

.ns-scope {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2.5px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
  vertical-align: 2px;
}

/* Instance scope is a fact about the DEPLOYMENT, so it is drawn in tokens branding cannot reach.
   --ns-nav and --ns-primary are both rewritten by BrandTheme for a branded org, which had every
   "Instance-wide" marker — and the Multi-tenant/Single-tenant chip on the System overview — wearing
   whichever tenant the operator happened to be acting as. A deployment has no brand; its customers
   do, which is why .is-tenant below still carries one. */
.ns-scope.is-instance {
  background: rgba(var(--ns-ink-rgb), .06);
  color: var(--ns-ink);
}

.ns-scope.is-tenant {
  background: var(--ns-primary-tint);
  color: var(--ns-primary-hover);
}

/* ---- the column-header strip ----
   Six lists share it, so it is one class: the same 10.5px uppercase row the
   tables use, above a run of .ns-feat-row/.ns-exc-row rows. */

.ns-feat-head {
  display: grid;
  gap: 12px;
  align-items: center;
  padding: 9px 18px;
  background: var(--ns-thead-bg);
  border-bottom: 1px solid var(--ns-border);
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ns-muted);
}

/* ---- the layered feature row (S1) ----
   Five columns: feature · reach | offered | sold in | exceptions | chevron.
   The header strip above it uses the same template, which is what keeps the
   columns lining up down the list. */

.ns-feat-row.is-layered,
.ns-feat-head.is-layered {
  grid-template-columns: minmax(0, 1fr) 118px 132px 96px 30px;
  gap: 12px;
}

.ns-feat-row.is-layered {
  display: grid;
  padding: 12px 18px;
  align-items: center;
}

/* The single-tenant variant (S5): switch | name + blurb | reach note. */
.ns-feat-row.is-simple,
.ns-feat-head.is-simple {
  grid-template-columns: 132px minmax(0, 1fr) 150px;
  gap: 14px;
}

.ns-feat-row.is-simple {
  display: grid;
  padding: 12px 18px;
  align-items: center;
}

@media (max-width: 1100px) {
  .ns-feat-row.is-layered,
  .ns-feat-row.is-simple {
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
  }

  /* A stacked row cannot line columns up, so the header strip has nothing to head. */
  .ns-feat-head.is-layered,
  .ns-feat-head.is-simple { display: none; }
}

/* ---- the plan chip (S1's "sold in" cell) ----
   One 24×22 letter per plan, in tier order. Included is the primary tint;
   not included keeps the letter legible but plainly out. */

.ns-plan-chips {
  display: flex;
  gap: 4px;
}

.ns-plan-chip {
  width: 24px;
  height: 22px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 800;
}

/* Shaped, so it wears the pair — the same pair as every other shaped plan label above. A row of
   letters then reads as a row of plans rather than four identical blue squares, which is the
   surface where one letter was doing all the work. The fallbacks are the flat blue this used
   before, for a chip whose plan has chosen no colour. */
.ns-plan-chip.is-in {
  background: var(--ns-plan-tint, var(--ns-primary-tint));
  color: var(--ns-plan-text, var(--ns-primary-hover));
}

/* Not a plan's colour at all: this letter marks a plan that does NOT include the feature, and the
   answer is an absence. Tinting it in the plan's own colour would say the opposite of what it
   means, so it stays neutral no matter which swatch the plan carries. */
.ns-plan-chip.is-out {
  background: var(--ns-divider);
  /* Slate lifted toward the canvas: a letter that reads as absent, not disabled. */
  color: oklch(from var(--ns-slate) calc(l + 0.18) calc(c * .6) h);
}

/* ---- the exception kind pill and the inert line (S3/S4) ---- */

.ns-exc-kind {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}

.ns-exc-kind.is-grant {
  background: var(--ns-success-50);
  color: var(--ns-success-text);
}

.ns-exc-kind.is-limit {
  background: var(--ns-primary-tint);
  color: var(--ns-primary-hover);
}

/* "Inert — off across the instance": an exception that currently delivers nothing. */
.ns-exc-inert {
  font-size: 11px;
  font-weight: 700;
  color: var(--ns-warning-text);
}

/* ---- the plan editor's feature rows (S2) ----
   Switch | name + blurb | note. The note column carries what the row cannot say
   itself: an instance switch holding it shut, or a tenant already holding it by
   exception. Inert rows keep their values legible — an operator about to reopen
   the instance switch needs to see what will come back. */

.ns-plan-feat-row {
  display: grid;
  grid-template-columns: 126px minmax(0, 1fr) 150px;
  gap: 14px;
  align-items: center;
  padding: 11px 18px;
  border-top: 1px solid var(--ns-divider);
}

.ns-plan-feat-row.is-inert {
  background: var(--ns-canvas);
}

.ns-plan-feat-row.is-inert .ns-panel-row-title {
  color: var(--ns-slate);
}

.ns-plan-feat-note {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ns-warning-text);
}

/* The cap row grown a third column for the same kind of note. */
.ns-plan-cap.is-noted {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 170px 130px;
  gap: 14px;
  align-items: center;
}

@media (max-width: 1100px) {
  .ns-plan-feat-row,
  .ns-plan-cap.is-noted {
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
  }
}

/* ---- the exceptions audit rows (S3) ----
   Tenant · plan | the exception | reason · granter | expires. Shares the header
   strip; the second column is the widest because the subject is the point. */

.ns-exc-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr) minmax(0, 1fr) 110px;
  gap: 14px;
  align-items: center;
  padding: 12px 18px;
  border-top: 1px solid var(--ns-divider);
}

.ns-exc-row:hover {
  background: var(--ns-thead-bg);
}

.ns-feat-head.is-audit {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr) minmax(0, 1fr) 110px;
  gap: 14px;
}

@media (max-width: 1100px) {
  .ns-exc-row { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .ns-feat-head.is-audit { display: none; }
}

/* ---- the tenant's exception rows and the add block (S4) ----
   What | reason · granter | expires | revoke. The add block sits on the head
   tint so appending reads as a different act from the list above it. */

.ns-exc-row.is-tenant-row {
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) 118px 78px;
}

.ns-exc-add {
  background: var(--ns-thead-bg);
  border-top: 1px solid var(--ns-border);
  padding: 14px 18px;
}

@media (max-width: 1100px) {
  .ns-exc-row.is-tenant-row { grid-template-columns: minmax(0, 1fr); }
}

/* ---- the admin hub rows (A1) ----
   Feature (with its reach folded under the name) | your answer | chevron. The
   locked run keeps the same grid on the canvas tint, name in slate: the list is
   the whole truth, and these rows read as facts rather than controls. */

.ns-feat-row.is-admin,
.ns-feat-head.is-admin {
  grid-template-columns: minmax(0, 1fr) 200px 30px;
  gap: 16px;
}

.ns-feat-row.is-admin {
  display: grid;
  padding: 12px 18px;
  align-items: center;
}

.ns-feat-row.is-admin.is-locked {
  background: var(--ns-canvas);
}

.ns-feat-row.is-admin.is-locked .ns-feat-name {
  color: var(--ns-slate);
}

/* The heading the locked run sits under: navy-tinted, padlock first. */
.ns-feat-group.is-locked-head {
  background: rgba(var(--ns-nav-rgb), .07);
  color: var(--ns-nav);
}

.ns-feat-group.is-locked-head > svg {
  flex: none;
  color: var(--ns-nav);
}

@media (max-width: 1100px) {
  .ns-feat-row.is-admin { grid-template-columns: minmax(0, 1fr); gap: 8px; }
  .ns-feat-head.is-admin { display: none; }
}

/* ---- the Roles page's tab strip (A3) ----
   Three roles across the card head. The active tab owns a 2px primary bottom
   edge and the tinted name; the counts stay muted either way. */

.ns-role-tabs {
  display: flex;
  border-bottom: 1px solid var(--ns-divider);
  background: var(--ns-thead-bg);
}

.ns-role-tab {
  flex: 1;
  min-width: 0;
  padding: 10px 16px 8px;
  text-align: left;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  color: var(--ns-text-700);
}

.ns-role-tab:hover {
  background: var(--ns-surface);
  color: var(--ns-text-700);
}

.ns-role-tab.active {
  border-bottom-color: var(--ns-primary);
  background: var(--ns-surface);
}

.ns-role-tab.active .ns-role-tab-name {
  color: var(--ns-primary-hover);
}

.ns-role-tab-name {
  display: block;
  font-size: 12.5px;
  font-weight: 700;
}

.ns-role-tab-counts {
  display: block;
  font-size: 11px;
  color: var(--ns-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- the per-person rows (A4) ----
   Feature | state | decided by | the switch, only where an exception is legal. */

.ns-exc-row.is-person-row,
.ns-feat-head.is-person {
  grid-template-columns: minmax(0, 1fr) 132px minmax(0, 1fr) 96px;
  gap: 14px;
}

@media (max-width: 1100px) {
  .ns-exc-row.is-person-row { grid-template-columns: minmax(0, 1fr); }
  .ns-feat-head.is-person { display: none; }
}

/* ---- console motion ----
   The same understated timings the rest of the app uses (.13-.15s ease): hover
   tints ease instead of snapping, the row chevron colours in, tabs slide their
   underline. The global prefers-reduced-motion rule at the top of this file
   flattens all of it. */

.ns-feat-row,
.ns-exc-row,
.ns-plan-feat-row {
  transition: background .15s ease;
}

.ns-feat-open {
  transition: background .13s ease, color .13s ease;
}

.ns-feat-name {
  transition: color .13s ease;
}

.ns-role-tab {
  transition: background .13s ease, color .13s ease, border-color .15s ease;
}

.ns-plan-chip {
  transition: background .15s ease, color .15s ease;
}

/* The warning tone the audit page's stat strip flips to when something expires soon. */
.ns-inforail-stat-value.warning {
  color: var(--ns-warning-text);
}

/* The layered row's off label carries the warning tone so an off instance switch
   reads as the consequential thing it is. */
.ns-switch-state.is-warning {
  color: var(--ns-warning-text);
}

/* ---- the console drill-down page ----
   Head, sub and body share one measure, so a right-hand item in .ns-page-head
   lands on the same edge as the information rail below it. .ns-page-head is
   space-between over exactly two children (docs/design/patterns/page-headers.md):
   a flat run of siblings there spreads across the whole window instead. */

.ns-console-page {
  max-width: 1180px;
}

/* ---- the audience-driven reveal (A2) ----
   The grid-template-rows recipe from docs/design/patterns/disclosures.md, driven
   straight off the radio: no JS, no height animation. Spacing lives on the card
   inside, never on the sizer — padding on a 0fr grid does not collapse and would
   leave a visible sliver. visibility is in the transition so the collapsed card
   is not tab-reachable. */

.ns-feat-modewrap {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  /* Same timings as .ns-dz-acc-bodywrap, the designer's proven instance of this recipe —
     matching it rather than picking my own is the point of having one. */
  transition: grid-template-rows .2s ease, opacity .18s ease, visibility .2s;
}

/* The clipping child. min-height: 0 is what actually lets the track reach zero — a grid item's
   automatic minimum is its content, so without this the collapsed row keeps the card's own height.
   The gap to the card above rides on the card INSIDE this clip: a margin on the grid item itself
   is outside the clip and leaves an 18px sliver when closed. */
.ns-feat-modewrap-inner {
  overflow: hidden;
  min-height: 0;
}

.ns-feat-modewrap-inner > .ns-set-card {
  margin-top: 16px; /* matches .ns-console-main's gap */
}

.ns-feat-modewrap.is-open {
  grid-template-rows: 1fr;
  opacity: 1;
  visibility: visible;
}

/* The audience card and its revealed group are ONE flex item of the left column, so the column's
   gap does not double up around a collapsed wrapper. */
.ns-feat-audience-group > .ns-set-card + .ns-feat-modewrap { margin: 0; }

/* ---- the instance-mode chip as a link (S1 rail) ---- */

a.ns-scope.is-link {
  text-decoration: none;
  transition: background .13s ease, color .13s ease;
}

/* Hover deepens the chip's OWN tone rather than a single shared navy — a tenant chip that hovered
   into the instance colour was already saying two things about its scope, and an instance chip
   that hovered into a brandable one undid the rule above. */
a.ns-scope.is-link.is-instance:hover {
  background: rgba(var(--ns-ink-rgb), .12);
  color: var(--ns-ink);
}

a.ns-scope.is-link.is-tenant:hover {
  background: rgba(var(--ns-primary-rgb), .14);
  color: var(--ns-primary-hover);
}

/* A chip carrying a plan hovers in that plan's colour, not the tenant tone underneath it. */
a.ns-scope.is-link.ns-plan[data-swatch]:not([data-swatch=""]):hover {
  background: color-mix(in srgb, var(--ns-plan-seed) 16%, #fff);
  color: var(--ns-plan-text);
}

a.ns-scope.is-link:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

/* The audit row grew a trailing action: the read-only list is where an operator
   arrives, so every row carries the way to the tenant that owns it. */
.ns-exc-row,
.ns-feat-head.is-audit {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr) minmax(0, 1fr) 110px 92px;
}

/* A byte cap is ten digits wide — the number must not truncate inside its field. */
.ns-plan-cap.is-noted {
  grid-template-columns: minmax(0, 1fr) 196px 130px;
}

@media (max-width: 1100px) {
  .ns-exc-row { grid-template-columns: minmax(0, 1fr); }
}

/* ---- the console state card ----
   A console front door's first answer: what this scope is, then one .ns-row-link per
   area with where it stands. The value leads the row — an operator scans the numbers
   down the left edge, and the label explains the one they stopped on. */

/* Deliberately untoned. A count of open exceptions is not a fault, and colouring some numbers
   and not others on the same card reads as a code the page never explains — the sub-line beside
   it ("Next expires 29 Sep 2026") is what carries the meaning. */
.ns-console-state-value {
  flex: none;
  min-width: 46px;
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -.01em;
  font-variant-numeric: tabular-nums; /* so the column of numbers lines up */
}

/* The chevron is the row's affordance, so it answers to the row's hover, not its own. */
.ns-console-state-row .ns-feat-open {
  transition: color .13s ease, transform .13s ease;
}

.ns-console-state-row:hover .ns-feat-open {
  color: var(--ns-primary);
  transform: translateX(1px);
}

.ns-console-state-row:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

/* ==========================================================================
   The in-app PDF viewer — Pages/Shared/_PdfViewer.cshtml + js/pdf-viewer.js.

   No design basis; built under docs/design/new-component.md Path 2. Composes the
   existing document surface wholesale rather than restyling it: .ns-designer for the
   shell and navy bar, .ns-doc / .ns-doc-pages / .ns-page for the documents,
   .ns-dz-zoom and .ns-dz-pagenav for the floating controls, .ns-docnav for the rail.
   What is below is only the stage those pieces sit in, plus the loading and error
   states. Contract: docs/design/components/pdf-viewer.md.
   ========================================================================== */

/* The stage is .ns-dz-canvaswrap — the designer's own wrapper, which is deliberately NOT the
   scroller. The scrolling happens in .ns-dz-canvas inside it, and the zoom / page controls are
   absolutely positioned against the wrapper as siblings of that scroller. That is the whole
   reason they stay pinned to the corner while the pages move: put them inside the scroller and
   `bottom: 12px` resolves against the scrollable content, so they sit below the last page and
   are invisible until you reach the end. Only the bits the viewer adds are below. */
.ns-viewer-stage {
  flex: 1;
  min-height: 0; /* or the flex child refuses to shrink and the whole page scrolls instead */
}

.ns-viewer-canvas {
  padding-bottom: calc(20px + env(safe-area-inset-bottom));
}

/* One document is shown at a time, switched by the tab strip; the rest are d-none.
   A grid with every document in the SAME cell, so that during a switch the outgoing and
   incoming documents overlap instead of stacking — which is what lets them cross-fade in
   place rather than fading out and then in, and means no layout shift while they trade. */
.ns-viewer-docs {
  display: grid;
}

.ns-viewer-doc {
  grid-area: 1 / 1;
  min-width: 0;
}

/* The switch. Both halves run for the same 190ms and land on the same frame — a fade-out
   followed by a fade-in reads as two events, and the document is the whole view here. */
.ns-viewer-doc-in {
  animation: ns-viewer-doc-in .19s cubic-bezier(.2, .7, .2, 1) both;
}

.ns-viewer-doc-out {
  animation: ns-viewer-doc-out .19s ease both;
  pointer-events: none; /* it is on its way out; clicks belong to the incoming document */
}

/* 6px, from the "content swap" row of the motion vocabulary — enough to read as movement,
   not enough to look like the page is travelling. */
@keyframes ns-viewer-doc-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

@keyframes ns-viewer-doc-out {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-4px); }
}

/* The strip sits under the navy bar rather than in the designer's grid, so it needs the
   flex sizing the grid was giving it. */
.ns-viewer-tabs {
  flex: none;
}

/* A viewer tab has no field count and no language chip, so it can afford a longer filename
   than the designer's — which is just as well, since these are whole stored filenames. */
.ns-viewer .ns-dz-tab-name {
  max-width: 190px;
}

.ns-viewer .ns-dz-tab {
  max-width: 240px;
}

/* One document failed while its siblings rendered: the note replaces that document's
   pages, so the rest of the envelope stays readable. */
.ns-viewer-doc-error {
  max-width: 860px;
  margin: 0 auto;
  width: 100%;
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
}

/* Loading. An overlay rather than a block in the flow, so the documents container keeps
   its real width while pdf.js works — renderPdf sizes pages from clientWidth, and a
   canvas that has not been laid out yet would render every page at the 860px fallback.
   It also means nothing shifts when the first page lands. */
.ns-viewer-loading {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 20px 16px;
  background: var(--ns-canvas-deep);
}

.ns-viewer-loading[hidden] {
  display: none;
}

/* A page-shaped skeleton, not a spinner in a void: same width and proportion as the real
   thing, so the wait previews the result. Letter aspect — the pages are usually letter or
   A4 and the difference at this size is a few pixels of height. */
.ns-viewer-skeleton {
  width: 100%;
  max-width: 860px;
  aspect-ratio: 8.5 / 11;
  border-radius: 2px;
  background: linear-gradient(100deg,
    var(--ns-surface) 30%,
    var(--ns-divider) 50%,
    var(--ns-surface) 70%);
  background-size: 240% 100%;
  /* The same ink as .ns-page's own shadow, at lower opacity — a placeholder should read as a
     page that has not arrived, not as a page. */
  box-shadow: 0 1px 4px rgba(16, 40, 70, .12);
  animation: ns-viewer-shimmer 1.4s ease infinite;
}

/* Ambient, and it displaces nothing — but the global reduced-motion reset clamps it
   anyway, which leaves a flat surface rather than a pulsing one. That is the intent. */
@keyframes ns-viewer-shimmer {
  from { background-position: 130% 0; }
  to   { background-position: -30% 0; }
}

.ns-viewer-loading-note {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  color: var(--ns-slate);
}

/* Every document failed. The stage-level state, with the download that still works. */
.ns-viewer-error {
  position: relative;
  z-index: 1;
}

@media (max-width: 575.98px) {
  .ns-viewer-canvas {
    padding: 12px 8px calc(20px + env(safe-area-inset-bottom));
  }

  /* Narrow tabs: the filename earns the space, the number circle does not. */
  .ns-viewer .ns-dz-tab {
    max-width: 62vw;
  }

  .ns-viewer .ns-dz-tab-name {
    max-width: 46vw;
  }
}

/* ==========================================================================
   Admin → Signer emails — Pages/Admin/EmailContent/Index.cshtml + js/email-content.js.

   Two pieces only: the placeholder reference above the cards, and the preview panel beside
   each card's fields. Everything else on the page is already-built: the console shell
   (.ns-set + _SettingsRail), a card, stock form controls, and the existing .ns-dz-insp-seg
   picker for the language switch — docs/design/components/tabs-and-segmented.md.
   ========================================================================== */

/* The placeholder reference. A quiet inset panel: it is documentation sitting beside the
   inputs it describes, so it has to read as reference and not as more fields to fill in. */
.ns-emailtok {
  background: var(--ns-thead-bg);
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-control);
  padding: 12px 14px;
}

.ns-emailtok-title {
  display: block;
  font-size: 12px;
  font-weight: 600;
  color: var(--ns-text-700);
  margin-bottom: 5px;
}

.ns-emailtok-list {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 4px 14px;
  margin: 0;
  font-size: 12px;
}

/* A <dt> here is a token, not a heading — the mono already carries the emphasis. */
.ns-emailtok-list dt {
  font-weight: 400;
}

.ns-emailtok-list dd {
  margin: 0;
  color: var(--ns-muted);
  min-width: 0;
}

/* The token column cannot survive a phone at 360px, so the pair stacks instead. */
@media (max-width: 575.98px) {
  .ns-emailtok-list {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .ns-emailtok-list dd {
    margin-bottom: 7px;
  }
}

/* The preview. Deliberately plainer than a card: what is inside is somebody else's design
   (the email itself), so the frame must not compete with it. */
.ns-emailprev {
  border: 1px solid var(--ns-border-strong);
  border-radius: var(--ns-radius-control);
  overflow: hidden;
  background: var(--ns-surface);
}

.ns-emailprev-subject {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--ns-border);
  background: var(--ns-thead-bg);
  font-size: 12.5px;
  /* Holds its height before the first compose lands, so nothing jumps when it does. */
  min-height: 38px;
}

.ns-emailprev-label {
  flex: none;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ns-muted);
}

.ns-emailprev-text {
  min-width: 0;
  color: var(--ns-ink);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-emailprev-frame {
  display: block;
  width: 100%;
  height: 30rem;
  border: 0;
  /* Not a token on purpose: this is the literal #EBEFF5 the email's own shell paints (an email
     cannot use CSS variables, so ResxEmailTemplates.Shell hard-codes it). Matching it keeps the
     frame from flashing white before the srcdoc lands. */
  background: #EBEFF5;
}

/* The language picker is a primary control on this page rather than a dense inspector row, so
   it takes a real touch target. Scoped to .ns-emaillang so the designer's inspector, where the
   same class is a small secondary control, keeps its density. */
@media (pointer: coarse) {
  .ns-emaillang {
    min-height: 44px;
  }
}

/* Stacked under the fields rather than beside them — shorter, so the fields it belongs to
   are still reachable without a long scroll past it. */
@media (max-width: 1199.98px) {
  .ns-emailprev-frame {
    height: 22rem;
  }
}

/* ==========================================================================
   Token fields — a text input whose value may contain {placeholder} tokens.
   Markup is a plain .form-control; admin-ui.js adds the Insert menu and the
   mirror layer that paints the chips. No design basis; built under
   docs/design/new-component.md Path 2. Contract:
   docs/design/components/token-field.md
   ========================================================================== */

/* The chip. One recipe, used in three places — inside the field, in the Insert menu, and in
   a reference list — so a token looks like the same object wherever it is named. */
.ns-tokenchip {
  display: inline-block;
  padding: 0 3px;
  border-radius: 5px;
  background: var(--ns-blue-50);
  box-shadow: inset 0 0 0 1px rgba(var(--ns-primary-rgb), .22);
  font-family: var(--ns-font-mono);
  font-size: .92em;
  color: var(--ns-blue-hover);
  white-space: nowrap;
}

/* A token the validator will refuse. The field previews the refusal rather than waiting for
   Save — and it is not colour alone: the dashed edge carries it too. */
.ns-tokenchip.is-unknown {
  background: var(--ns-danger-50);
  box-shadow: inset 0 0 0 1px var(--ns-danger-border);
  color: var(--ns-danger-text);
  text-decoration: underline dashed currentColor 1px;
  text-underline-offset: 2px;
}

.ns-tokenfield-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  /* The label owns the bottom margin in the stock recipe; the row takes it over so the
     trigger sits on the label's baseline rather than 5px below it. */
  margin-bottom: 5px;
}

.ns-tokenfield-head .form-label {
  margin-bottom: 0;
}

/* The Insert trigger. A quiet tertiary control: it is an affordance on a field, not an action
   on the page, so it stays lighter than any button in the footer. */
.ns-tokenfield-addbtn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  padding: 2px 7px;
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--ns-blue-600);
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}

.ns-tokenfield-addbtn:hover {
  background: var(--ns-blue-50);
  border-color: rgba(var(--ns-primary-rgb), .22);
}

.ns-tokenfield-addbtn:focus-visible {
  outline: none;
  border-color: var(--ns-blue-600);
  box-shadow: var(--ns-focus-ring);
}

.ns-tokenfield-addbtn[aria-expanded="true"] {
  background: var(--ns-blue-50);
  border-color: rgba(var(--ns-primary-rgb), .22);
}

/* Name over chip, so the menu reads as "Signer's name" first and the token second — the
   whole point being that nobody should need to know the token to use it. */
.ns-tokenfield-menu .dropdown-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.ns-tokenfield-optname {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- the field itself ---- */

.ns-tokenfield-box {
  position: relative;
}

/* Only once the mirror exists does the input go transparent — an un-enhanced field must keep
   the stock white surface rather than showing the page through it. */
.ns-tokenfield.is-enhanced .ns-tokenfield-input {
  position: relative;
  z-index: 1;
  background-color: transparent;
}

.ns-tokenfield-mirror {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  border-style: solid;
  border-color: transparent;
  border-radius: var(--ns-radius-control);
  /* The surface the input would have painted, moved down here so the chips sit on top of it
     and the input's text sits on top of them. */
  background-color: var(--ns-surface);
  /* Every glyph is invisible: the mirror exists to place the chip backgrounds under the real
     text, never to render text of its own. */
  color: transparent;
  overflow-wrap: break-word;
  pointer-events: none;
  /* Metrics (font, padding, border widths, line-height) are copied from the live input by
     admin-ui.js — see the comment there for why they are not restated here. */
}

/* Inside the mirror the chip is **layout-neutral**, and this is the whole trick: the real text
   sits on top of it, so any padding, border, font change or display change here would shift
   every glyph after the chip out from under the characters it is supposed to be behind. (The
   padded, mono version above is for the Insert menu and the reference list, where the chip is
   an object in its own right.) Measured: a 3px padding plus the mono face at .92em moved the
   following text 15.5px — visibly wrong after the first token. So: paint only.
   Background and box-shadow take no space; text-decoration takes no space; nothing else is
   allowed in here. */
.ns-tokenfield-mirror .ns-tokenchip {
  display: inline;
  padding: 0;
  font: inherit;
  letter-spacing: inherit;
  white-space: inherit;
  border-radius: 4px;
  color: transparent;
  /* The outward spread is the visual breathing room the padding would have given. */
  box-shadow: 0 0 0 2px var(--ns-blue-50);
}

/* A token the validator will refuse, in-field. Two signals, neither of which moves a glyph:
   the wash, and a dashed underline with its own explicit colour (the text itself is
   transparent here, so the decoration cannot inherit a visible one). */
.ns-tokenfield-mirror .ns-tokenchip.is-unknown {
  box-shadow: 0 0 0 2px var(--ns-danger-50);
  text-decoration: underline dashed var(--ns-danger-text) 1px;
  text-underline-offset: 2px;
}

@media (pointer: coarse) {
  .ns-tokenfield-addbtn {
    min-height: 32px;
    padding: 2px 10px;
  }
}

/* ---- the type-ahead list, opened by typing "{" in a token field ----
   Same recipe as .dropdown-menu (docs/design/components/dropdowns-and-selects.md): a floating
   surface, lighter than the card it floats over, 6px padding with 8px-radius inset items.
   Built and positioned by admin-ui.js. */
.ns-tokenfield-pop {
  position: absolute;
  left: 0;
  top: 100%;
  /* Above the input, which the enhanced field puts on z-index 1. */
  z-index: 5;
  width: min(24rem, 100%);
  max-height: 14rem;
  overflow-y: auto;
  margin-top: 4px;
  padding: 6px;
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
  box-shadow: var(--ns-shadow-dropdown);
  font-size: 13px;
}

/* Flipped above the field when there is no room below — set by JS from the field's own rect. */
.ns-tokenfield-pop.is-above {
  top: auto;
  bottom: 100%;
  margin: 0 0 4px;
}

.ns-tokenfield-sug {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 10px;
  border-radius: 8px;
  font-weight: 500;
  color: var(--ns-text-700);
  cursor: pointer;
}

/* Hover and keyboard-highlight are the same treatment on purpose: there is one highlighted row
   at a time and it should not matter how it got that way. */
.ns-tokenfield-sug.is-active,
.ns-tokenfield-sug:hover {
  background: var(--ns-divider);
  color: var(--ns-ink);
}

.ns-tokenfield-popempty {
  padding: 8px 10px;
  font-size: 12px;
  color: var(--ns-muted);
}

/* ---------- the autocomplete ----------
   A .form-control that suggests records from a server handler and turns the ones you pick into
   cards. The input is a real, posted field on its own
   (docs/design/components/autocomplete.md); admin-ui.js adds the list and the cards on top, and
   .is-enhanced is what says it did. No design basis; built under
   docs/design/new-component.md Path 2, borrowing the token field's suggestion-list recipe below
   so the two lists are the same object. */

.ns-autocomplete {
  /* The positioning context for the list. Set unconditionally, not on .is-enhanced: the list is
     absolutely positioned the moment JS appends it, and a late class would flash it page-wide. */
  position: relative;
}

.ns-autocomplete-pop {
  position: absolute;
  left: 0;
  top: 100%;
  z-index: 5;
  width: 100%;
  max-height: 15rem;
  overflow-y: auto;
  margin-top: 4px;
  padding: 6px;
  background: var(--ns-surface);
  border: 1px solid var(--ns-border);
  border-radius: var(--ns-radius-card);
  box-shadow: var(--ns-shadow-dropdown);
  font-size: 13px;
}

/* Flipped above the field when there is no room below — set by JS from the field's own rect,
   exactly as .ns-tokenfield-pop does. */
.ns-autocomplete-pop.is-above {
  top: auto;
  bottom: 100%;
  margin: 0 0 4px;
}

.ns-autocomplete-opt {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 7px 10px;
  border-radius: 8px;
  cursor: pointer;
}

/* Hover and keyboard-highlight are one treatment, for the token field's reason: there is one
   highlighted row and it should not matter how it got that way. */
.ns-autocomplete-opt.is-active,
.ns-autocomplete-opt:hover {
  background: var(--ns-divider);
}

/* An option already taken stays in the list rather than vanishing — a set that reshuffles as you
   pick is disorienting — but it reads as spent and does not respond to the pointer. */
.ns-autocomplete-opt.is-taken {
  opacity: .5;
  cursor: default;
}

.ns-autocomplete-opt.is-taken:hover {
  background: transparent;
}

.ns-autocomplete-optname {
  font-weight: 600;
  color: var(--ns-ink);
  /* Both lines truncate rather than wrap: a row is one record, and a wrapped 320-character
     address would make the list a paragraph. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-autocomplete-optmail {
  font-size: 11.5px;
  color: var(--ns-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Designed rather than an empty box, and it names the query — an empty popup reads as broken. */
.ns-autocomplete-empty {
  padding: 8px 10px;
  font-size: 12px;
  color: var(--ns-muted);
}

/* ---- the chosen records ----
   Each pick becomes a card in this stack, taking the input's place: same radius, same border
   weight, same vertical rhythm, so the field's slot does not move. Neutral rather than accented
   on purpose — a card *is* one of the field's values, not a selection chip, and the choice row
   above it on /System/Tenants/New already carries the blue. */

.ns-autocomplete-chosen {
  display: none;
  flex-direction: column;
  gap: 6px;
}

.ns-autocomplete.has-chosen .ns-autocomplete-chosen {
  display: flex;
}

/* Only the *full* field hides its box. Below the limit the box stays, so a multi-select keeps
   offering the next pick right where the last one landed. */
.ns-autocomplete.is-full .ns-autocomplete-input,
.ns-autocomplete.is-full .ns-autocomplete-help {
  display: none;
}

/* The stack sits above the box while both are on screen, so the cards read as what has been
   collected and the box as what is still being asked. */
.ns-autocomplete.has-chosen:not(.is-full) .ns-autocomplete-chosen {
  margin-bottom: 6px;
}

.ns-autocomplete-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 6px 6px 12px;
  background: var(--ns-surface);
  border: 1px solid var(--ns-border-strong);
  border-radius: var(--ns-radius-control);
}

.ns-autocomplete-card-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  /* Or a long address shoves the clear button off the card. */
  min-width: 0;
  flex: 1;
}

.ns-autocomplete-card-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ns-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-autocomplete-card-mail {
  font-size: 11.5px;
  color: var(--ns-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ns-autocomplete-clear {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ns-muted);
  cursor: pointer;
  transition: background .14s ease, color .14s ease;
}

.ns-autocomplete-clear:hover {
  background: var(--ns-divider);
  color: var(--ns-ink);
}

.ns-autocomplete-clear:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
  color: var(--ns-ink);
}

/* The only control on a card, and there is no hover to reveal it on a touch device — so it
   carries a real tap target there rather than the 30px the pointer gets. */
@media (hover: none) {
  .ns-autocomplete-clear {
    width: 44px;
    height: 44px;
  }
}

/* The running count, for a multi-select only: "2 of 5". Sits on the label's line so it does not
   push the field down, and goes quiet at zero rather than announcing an absence. */
.ns-autocomplete-count {
  margin-left: 6px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--ns-muted);
}

/* The intro row: explanation on the left, the placeholder reference on the right, so neither
   pushes the first field down the page. Both keep a reading measure once the row stacks. */
.ns-emailintro p,
.ns-emailintro .ns-emailtok {
  max-width: 46rem;
}
