/* ==========================================================================
   V2.0 — the interface layer
   --------------------------------------------------------------------------
   Every rule in this file is under body.ic-v2. With the toggle on Classic the
   class is absent and this stylesheet changes nothing, anywhere. That is the
   whole revert: one cookie, no second copy of any template.

   It works because the app already has a shared vocabulary -- .spx-card is in
   250 view files and .spx-table in 124 -- so restyling those reaches nearly
   every screen from one switch. Pages whose *structure* differs between
   versions (the dashboard) branch on UiVersion themselves instead; this file
   is for the ones where only the presentation changes.

   Colours resolve through the --t-* tokens with their literal as the var()
   fallback, the same contract as the rest of public/*.css: the tokens are
   defined only under [data-theme="dark"], so light renders the fallback and
   dark arrives for free.

   Raw-served from public/, so no sprockets and no precompile.

   ON SPECIFICITY, because it is not optional here. splynx-ui.css and
   ic-theme.css style the same components through long selectors and a great
   deal of !important -- for example

     body.spx-app .content-wrapper table.table > thead > tr > th { background: ... !important }
     .content table.table:not(.spx-form-table):not(.spx-items):not(.spx-kv) > thead > tr > th { ... !important }

   The first cut of this file used short, tidy selectors (body.ic-v2 table.dataTable
   thead th) and quietly lost every one of those fights -- the class was on the
   body, the file was loaded, the rule matched, and nothing changed. So the rule
   for this layer is: MIRROR THE SHAPE YOU ARE OVERRIDING and prefix it with
   body.ic-v2, which always adds a class and an element and therefore always
   wins; and where the rule you are overriding is !important, be !important too,
   because importance beats specificity outright.
   ========================================================================== */

/* ---------- page head ------------------------------------------------------
   The title was competing with the breadcrumb above it at nearly the same
   weight. V2 makes the title the loudest thing on the page and lets the crumb
   recede to what it is: a way back.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-record-head {
  align-items: flex-start;
  gap: 14px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--t-line-soft, #eef2f6);
  margin-bottom: 16px;
}
body.ic-v2 .spx-record-icon {
  display: inline-grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: var(--t-tint-purple, #f3eafb);
  color: var(--t-tint-purple-ink, #7b3fa8);
  font-size: 17px;
  flex: 0 0 40px;
}
body.ic-v2 .spx-crumb {
  font-size: 11.5px;
  letter-spacing: 0.2px;
  color: var(--t-faint, #8895a7);
  margin-bottom: 1px;
}
body.ic-v2 .spx-crumb a { color: var(--t-faint, #8895a7); }
body.ic-v2 .spx-crumb a:hover { color: var(--t-link, #1d6fa5); }
body.ic-v2 .spx-title {
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -0.4px;
  line-height: 1.15;
}
body.ic-v2 .spx-head-sub { color: var(--t-muted, #6b7789); font-size: 13px; margin: 3px 0 0; }
body.ic-v2 .spx-toolbar-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }

/* ---------- cards ------------------------------------------------------------
   AdminLTE's 3px coloured stripe along the top of every box is the single
   loudest thing on a page full of boxes, and it says nothing. Out, replaced by
   a real border and a shadow that reads as depth rather than decoration.
   -------------------------------------------------------------------------- */
body.ic-v2 .box,
body.ic-v2 .spx-card,
body.ic-v2 .vhost-data-box {
  border-radius: 14px;
  border: 1px solid var(--t-line, #e2e8f0);
  border-top: 1px solid var(--t-line, #e2e8f0);
  background: var(--t-surface, #fff);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05), 0 1px 3px rgba(16, 24, 40, 0.08);
}
/* splynx-ui pins this at 8px !important through a longer selector, so the
   plain rule above never reaches the cards that matter. Two classes on <body>
   puts this one class ahead of it. */
body.ic-v2.spx-app .content-wrapper .box.spx-card,
body.ic-v2.spx-app .content-wrapper .box.vhost-data-box,
body.ic-v2.spx-app .content-wrapper .vhost-data-box {
  border-radius: 14px !important;
  border-top: 1px solid var(--t-line, #e2e8f0) !important;
}
:root[data-theme="dark"] body.ic-v2 .box,
:root[data-theme="dark"] body.ic-v2 .spx-card,
:root[data-theme="dark"] body.ic-v2 .vhost-data-box {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}
body.ic-v2 .box-header,
body.ic-v2 .box-header.with-border {
  border-bottom: 1px solid var(--t-line-soft, #eef2f6);
  padding: 13px 16px;
}
body.ic-v2 .box-header .box-title {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.1px;
}
body.ic-v2 .box-header .box-title i { color: var(--t-tint-purple-ink, #7b3fa8); margin-right: 7px; }
/* A row, not a float. The actions in these headers are .pull-right inside an
   un-cleared block, so the header collapses to the title's height and the
   button lands on top of whatever comes next -- which is exactly what it did
   over the invoice panel's summary band. */
body.ic-v2 .box-header.with-border,
body.ic-v2 .box-header {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
body.ic-v2 .box-header > .box-title { margin: 0; }
body.ic-v2 .box-header > .pull-right,
body.ic-v2 .box-header > .box-tools { margin-left: auto; float: none !important; position: static; }

/* ---------- tables -----------------------------------------------------------
   The Internet list is twelve columns and twenty-five rows. The two things
   that actually make that scannable are a header that stays put and a row you
   can follow across -- everything else here is in service of those.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-table,
body.ic-v2 table.dataTable {
  font-variant-numeric: tabular-nums;
}

/* The :not() chain is ic-theme's own -- mirrored so this out-specifies it, and
   a happy accident besides: form and key/value tables are not data tables and
   have no business wearing a data table's header. */
body.ic-v2 .content-wrapper table.table:not(.spx-form-table):not(.spx-items):not(.spx-kv) > thead > tr > th,
body.ic-v2 .content table.table:not(.spx-form-table):not(.spx-items):not(.spx-kv) > thead > tr > th {
  background: var(--t-surface-2, #f6f8fb) !important;
  border-bottom: 1px solid var(--t-line, #e2e8f0) !important;
  border-top: 0 !important;
  padding: 9px 12px !important;
  font-size: 10.5px !important;
  font-weight: 700 !important;
  letter-spacing: 0.7px !important;
  text-transform: uppercase !important;
  color: var(--t-muted, #6b7789) !important;
  white-space: nowrap;
}

/* A header that scrolls away is the main reason a wide table is hard to read.
   :has() scopes this to the DataTables list pages -- the big ones -- so a
   three-row table inside a record card is not turned into a scroll box. On a
   browser without :has() the rule is simply dropped and the header styling
   above still applies. */
body.ic-v2 .table-responsive:has(table.dataTable) {
  max-height: calc(100vh - 250px);
  overflow: auto;
}
body.ic-v2 .table-responsive:has(table.dataTable) table.dataTable thead th {
  position: sticky;
  top: 0;
  z-index: 3;
}

/* Same mirrored shape as the header above, and for the same reason: the
   short form lost to body.spx-app .content-wrapper table.table > tbody > tr > td
   on every table that is not a DataTable -- which includes the invoice panel. */
body.ic-v2 .content-wrapper table.table:not(.spx-form-table):not(.spx-items):not(.spx-kv) > tbody > tr > td,
body.ic-v2 .content table.table:not(.spx-form-table):not(.spx-items):not(.spx-kv) > tbody > tr > td {
  padding: 8px 12px !important;
  border-top: 1px solid var(--t-line-soft, #eef2f6);
  vertical-align: middle;
}

/* The row you are on, marked at its edge rather than by a wash that fights
   with the status colours already in the row. */
body.ic-v2 .content-wrapper table.table > tbody > tr,
body.ic-v2 .content table.table > tbody > tr {
  box-shadow: inset 2px 0 0 transparent;
  transition: background-color 90ms linear, box-shadow 90ms linear;
}
body.ic-v2 .content-wrapper table.table:not(.spx-form-table):not(.spx-items):not(.spx-kv) > tbody > tr:hover,
body.ic-v2 .content table.table:not(.spx-form-table):not(.spx-items):not(.spx-kv) > tbody > tr:hover {
  background: var(--t-surface-2, #f6f8fb) !important;
  box-shadow: inset 2px 0 0 var(--t-brand-2, #8b5cc4);
}

/* Empty cells were shouting. A column of em-dashes at full contrast reads as
   content; faded, the real values in the column come forward. */
body.ic-v2 .ic-na,
body.ic-v2 .ic-na td { color: var(--t-faint, #b6c0cc); }

/* Addresses and identifiers line up only in a monospaced face, and lining up
   is the entire point of putting them in a column. */
body.ic-v2 .ic-ip-cell,
body.ic-v2 .ic-ip-cell code {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
}

/* ---------- pills ------------------------------------------------------------- */
body.ic-v2 .spx-pill {
  border-radius: 999px;
  padding: 2px 9px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.2px;
  line-height: 1.5;
}

/* ---------- the list toolbar ---------------------------------------------------
   column_visibility.js injects its own stylesheet with literal light colours
   (#fff, #f4f7fa, #3c8dbc). These out-specify it through body.ic-v2 rather
   than editing that file, so Classic keeps exactly what it has today.
   -------------------------------------------------------------------------- */
body.ic-v2 .status-filter-group .status-chip {
  min-height: 34px !important;
  padding: 6px 15px;
  font-size: 12.5px !important;
  border-radius: 999px;
  background: var(--t-surface, #fff);
  border: 1px solid var(--t-line, #d0d7de);
  color: var(--t-ink-2, #1b2433);
}
body.ic-v2 .status-filter-group .status-chip:hover {
  background: var(--t-surface-2, #f4f7fa);
  border-color: var(--t-brand, #5b2c86);
  color: var(--t-brand-text, #5b2c86);
}
body.ic-v2 .status-filter-group .status-chip.status-filter-selected {
  background: var(--t-brand, #5b2c86);
  border-color: var(--t-brand, #5b2c86);
  color: #fff;
}

body.ic-v2 .dataTables_filter input,
body.ic-v2 .vhost-table-toolbar .dataTables_filter input {
  height: 34px;
  border-radius: 9px;
  border: 1px solid var(--t-line, #d0d7de);
  background: var(--t-surface, #fff);
  color: var(--t-ink, #1b2433);
  padding: 4px 12px;
}
body.ic-v2 .dataTables_length select,
body.ic-v2 .vhost-table-toolbar .dataTables_length select {
  height: 34px;
  border-radius: 9px;
  border: 1px solid var(--t-line, #d0d7de);
  background: var(--t-surface, #fff);
  color: var(--t-ink, #1b2433);
}
body.ic-v2 .dataTables_filter input:focus,
body.ic-v2 .dataTables_length select:focus {
  outline: 2px solid var(--t-accent, #2f80ed);
  outline-offset: -1px;
  border-color: var(--t-accent, #2f80ed);
}

/* ---------- buttons -------------------------------------------------------------- */
body.ic-v2 .btn {
  border-radius: 9px;
  font-weight: 600;
  padding: 6px 14px;
}
body.ic-v2 .btn-default {
  background: var(--t-surface, #fff);
  border-color: var(--t-line, #d0d7de);
  color: var(--t-ink-2, #334155);
}
body.ic-v2 .btn-default:hover {
  border-color: var(--t-brand, #5b2c86);
  color: var(--t-brand-text, #5b2c86);
  background: var(--t-surface-2, #f6f8fb);
}
body.ic-v2 .btn.spx-btn-primary {
  background: var(--t-brand, #5b2c86);
  border-color: var(--t-brand, #5b2c86);
  color: #fff;
  box-shadow: 0 1px 2px rgba(91, 44, 134, 0.25);
}

/* ---------- pagination ------------------------------------------------------------ */
body.ic-v2 .dataTables_paginate .paginate_button {
  border-radius: 9px !important;
  border: 1px solid var(--t-line, #d0d7de) !important;
  margin-left: 4px;
}
body.ic-v2 .dataTables_paginate .paginate_button.current {
  background: var(--t-brand, #5b2c86) !important;
  border-color: var(--t-brand, #5b2c86) !important;
  color: #fff !important;
}

/* ---------- numeric columns ---------------------------------------------------------
   The invoice panel is the same card and the same table as everything else, so
   it is already covered above. What it needs on top is what any money column
   needs: the digits have to line up, or the column cannot be scanned for the
   big number. .text-right is how this app already marks those cells -- Qty,
   Amount and Invoice total on the invoice panel all carry it -- so keying off
   it picks up every other numeric column in the app for free.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-table td.text-right,
body.ic-v2 table.dataTable tbody td.text-right {
  font-variant-numeric: tabular-nums;
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
}
body.ic-v2 .ic-service-invoices .spx-sub { color: var(--t-muted, #6b7789); font-size: 12.5px; }

/* Print takes the page as it is; none of the above is worth carrying to paper
   and the sticky header actively breaks a printed table. */
@media print {
  body.ic-v2 .table-responsive:has(table.dataTable) { max-height: none; overflow: visible; }
  body.ic-v2 table.dataTable thead th { position: static; }
  body.ic-v2 .box, body.ic-v2 .spx-card, body.ic-v2 .vhost-data-box { box-shadow: none; }
}

/* ==========================================================================
   V2.0 — the bento list
   --------------------------------------------------------------------------
   New classes, so no specificity fight: nothing else in the app styles a
   .v2-*. They are only ever emitted by a _v2 template, which only renders
   when UiVersion says so, so Classic never sees any of this either.
   ========================================================================== */

/* ---------- the summary band ---------------------------------------------- */
body.ic-v2 .v2-band {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
  gap: 12px;
  margin: 0 0 16px;
}
body.ic-v2 .v2-stat {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 12px 15px 13px;
  border-radius: 14px;
  border: 1px solid var(--t-line, #e2e8f0);
  background: var(--t-surface, #fff);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
  /* It is a <button> when it filters, so it needs the reset. */
  font: inherit;
  text-align: left;
  color: var(--t-ink, #2c3340);
  width: 100%;
}
:root[data-theme="dark"] body.ic-v2 .v2-stat { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }
body.ic-v2 .v2-stat:before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--v2-bar, var(--t-line, #e2e8f0));
}
body.ic-v2 a.v2-stat:hover { text-decoration: none; color: var(--t-ink, #2c3340); }
body.ic-v2 .v2-stat.is-clickable { cursor: pointer; }
body.ic-v2 .v2-stat.is-clickable:hover {
  border-color: var(--t-brand, #5b2c86);
  box-shadow: 0 4px 16px rgba(16, 24, 40, 0.10);
}
body.ic-v2 .v2-stat:focus-visible { outline: 2px solid var(--t-accent, #2f80ed); outline-offset: 2px; }

body.ic-v2 .v2-stat-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
}
body.ic-v2 .v2-stat-value {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.6px;
  font-variant-numeric: tabular-nums;
}
body.ic-v2 .v2-stat-meta { font-size: 11px; color: var(--t-faint, #8895a7); }
body.ic-v2 .v2-stat-bar {
  display: block;
  height: 4px;
  margin: 5px 0 1px;
  border-radius: 999px;
  background: var(--t-surface-3, #eef1f6);
  overflow: hidden;
}
body.ic-v2 .v2-stat-bar i { display: block; height: 100%; border-radius: 999px; background: currentColor; }

body.ic-v2 .v2-stat-ok    .v2-stat-value, body.ic-v2 .v2-stat-ok    .v2-stat-bar i { color: var(--t-success-text, #27ae60); }
body.ic-v2 .v2-stat-warn  .v2-stat-value, body.ic-v2 .v2-stat-warn  .v2-stat-bar i { color: var(--t-tint-amber-ink, #e67e22); }
body.ic-v2 .v2-stat-bad   .v2-stat-value, body.ic-v2 .v2-stat-bad   .v2-stat-bar i { color: var(--t-danger-text, #c0392b); }
body.ic-v2 .v2-stat-info  .v2-stat-value, body.ic-v2 .v2-stat-info  .v2-stat-bar i { color: var(--t-accent-text, #2f80ed); }
body.ic-v2 .v2-stat-lead  .v2-stat-value, body.ic-v2 .v2-stat-lead  .v2-stat-bar i { color: var(--t-tint-purple-ink, #8e44ad); }
body.ic-v2 .v2-stat-ok:before    { background: linear-gradient(90deg, var(--t-success-text, #27ae60), transparent); }
body.ic-v2 .v2-stat-warn:before  { background: linear-gradient(90deg, var(--t-tint-amber-ink, #e67e22), transparent); }
body.ic-v2 .v2-stat-bad:before   { background: linear-gradient(90deg, var(--t-danger-text, #c0392b), transparent); }
body.ic-v2 .v2-stat-info:before  { background: linear-gradient(90deg, var(--t-accent-text, #2f80ed), transparent); }
body.ic-v2 .v2-stat-lead:before  { background: linear-gradient(90deg, var(--t-tint-purple-ink, #8e44ad), transparent); }

/* ---------- composite rows -------------------------------------------------
   Two lines per cell: the thing itself, then the context that makes it mean
   something. Five of these carry what twelve columns used to.
   -------------------------------------------------------------------------- */
/* NOT display:flex on the <td>. A table cell that is turned into a flex
   container stops being a table-cell, drops out of the table's column model,
   and every cell in the row piles into the first column -- which is exactly
   what the first cut did. Leave the cell alone and stack its children. */
body.ic-v2 .v2-cell { min-width: 0; }
body.ic-v2 .v2-cell > .v2-primary,
body.ic-v2 .v2-cell > .v2-secondary,
body.ic-v2 .v2-cell > .v2-chips { display: block; }
body.ic-v2 .v2-cell > .v2-secondary,
body.ic-v2 .v2-cell > .v2-chips ~ .v2-secondary { margin-top: 3px; }
body.ic-v2 .v2-primary {
  font-size: 13px;
  font-weight: 600;
  color: var(--t-ink, #2c3340);
  line-height: 1.25;
  min-width: 0;
}
body.ic-v2 .v2-secondary {
  font-size: 11.5px;
  color: var(--t-muted, #6b7789);
  line-height: 1.25;
  min-width: 0;
}
body.ic-v2 .v2-secondary a { color: var(--t-muted, #6b7789); }
body.ic-v2 .v2-secondary a:hover { color: var(--t-link, #1d6fa5); }
body.ic-v2 .v2-cell > .v2-chips,
body.ic-v2 .v2-chips { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; }
body.ic-v2 .v2-clip {
  display: block;
  max-width: 34ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The login is what people search for and paste, so it gets the face that
   makes one character differ visibly from another. */
body.ic-v2 .v2-cell-service .v2-primary,
body.ic-v2 .v2-ip {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
body.ic-v2 .v2-cell-service .v2-primary { font-size: 12.5px; }
body.ic-v2 .v2-ip { font-size: 11px; }
body.ic-v2 .v2-cell-network { min-width: 170px; }
body.ic-v2 .v2-empty { padding: 22px 14px; color: var(--t-muted, #6b7789); }

/* Two-line rows are taller, so the breathing room the single-line table
   needed would make this one enormous. */
body.ic-v2 .content-wrapper table.v2-list > tbody > tr > td,
body.ic-v2 .content table.v2-list > tbody > tr > td {
  padding: 7px 12px !important;
}
body.ic-v2 .v2-list-card { overflow: hidden; }
/* DataTables sets column widths at runtime; without an explicit alignment the
   header text drifts to the middle of whatever width it lands on. */
body.ic-v2 .content-wrapper table.v2-list > thead > tr > th,
body.ic-v2 .content table.v2-list > thead > tr > th { text-align: left !important; }

/* ---------- the invoice panel ----------------------------------------------- */
body.ic-v2 .v2-invoices .v2-band { margin-bottom: 14px; }
body.ic-v2 .v2-invoices .v2-stat-value { font-size: 22px; }
body.ic-v2 .v2-amount {
  font-weight: 600;
  color: var(--t-ink, #2c3340);
}
/* Provenance, not preamble. It explains how these rows were matched, which is
   worth keeping and not worth reading twice, so it sits under the table. */
body.ic-v2 .v2-provenance {
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--t-line-soft, #eef2f6);
  font-size: 11.5px;
  color: var(--t-faint, #8895a7);
}
body.ic-v2 .v2-provenance strong { color: var(--t-muted, #6b7789); font-weight: 600; }
body.ic-v2 .v2-provenance-warn { color: var(--t-tint-amber-ink, #9f5f00); }

/* ==========================================================================
   V2.0 — the record shell (customer, and anything else built the same way)
   ========================================================================== */
body.ic-v2 .v2-record-crumb { margin: 0 0 12px; font-size: 11.5px; }

/* ---------- hero ------------------------------------------------------------
   Who this is, and the two things you do from anywhere on the record. The
   classic shell said the name three times and put the balance in a grey bar
   at 13px; this says it once and lets the band carry the numbers.
   -------------------------------------------------------------------------- */
body.ic-v2 .v2-hero {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 16px 18px;
  margin-bottom: 14px;
}
body.ic-v2 .v2-hero-id { display: flex; align-items: center; gap: 14px; min-width: 0; flex: 1 1 320px; }
body.ic-v2 .v2-hero-avatar .spx-avatar,
body.ic-v2 .v2-hero-avatar > span {
  width: 46px;
  height: 46px;
  font-size: 15px;
  flex: 0 0 46px;
}
body.ic-v2 .v2-hero-name { min-width: 0; }
body.ic-v2 .v2-hero .spx-title { font-size: 24px; margin: 0; line-height: 1.15; }
body.ic-v2 .v2-hero-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
  margin-top: 5px;
  font-size: 12px;
  color: var(--t-muted, #6b7789);
}
body.ic-v2 .v2-hero-ref {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  padding: 1px 7px;
  border-radius: 6px;
  background: var(--t-surface-3, #eef1f6);
  color: var(--t-ink-2, #334155);
}
body.ic-v2 .v2-hero-sep { color: var(--t-line, #cbd5e1); }
body.ic-v2 .v2-hero-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
body.ic-v2 .v2-hero-step { display: inline-flex; gap: 4px; }

/* ---------- the tab strip ----------------------------------------------------
   A segmented control, not Bootstrap's nav-tabs, and on the same line as the
   record's own controls -- in the classic shell those are two separate strips
   and the second one repeats the customer's name.
   -------------------------------------------------------------------------- */
body.ic-v2 .v2-record-nav {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin: 0 0 14px;
}
body.ic-v2 .v2-tabs {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 2px;
  padding: 3px;
  border-radius: 12px;
  background: var(--t-surface-2, #f1f4f8);
  border: 1px solid var(--t-line, #e2e8f0);
}
body.ic-v2 .v2-tabs a {
  padding: 6px 13px;
  border-radius: 9px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--t-muted, #6b7789);
  white-space: nowrap;
}
body.ic-v2 .v2-tabs a:hover { color: var(--t-ink, #2c3340); text-decoration: none; }
body.ic-v2 .v2-tabs a.is-on {
  background: var(--t-surface, #fff);
  color: var(--t-brand-text, #5b2c86);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.10);
}
:root[data-theme="dark"] body.ic-v2 .v2-tabs a.is-on { background: var(--t-surface-3, #1e1e1e); }
body.ic-v2 .v2-record-tools { display: flex; align-items: center; gap: 8px; margin-left: auto; }

/* The line's share leads; the invoice it sits on stays underneath rather than
   moving into a tooltip, because it was a column of its own before. */
body.ic-v2 .v2-amount-cell .v2-amount { display: block; font-weight: 600; color: var(--t-ink, #2c3340); }
body.ic-v2 .v2-amount-cell .v2-secondary { display: block; margin-top: 2px; }
body.ic-v2 .v2-th-sub { font-weight: 400; opacity: .65; }

/* ---------- the ticket list --------------------------------------------------
   Only the composed cell and the new Age column need rules. The five inline
   menus keep their own columns and their own styling, deliberately -- see the
   note in _ticket_head_v2.
   -------------------------------------------------------------------------- */
body.ic-v2 .v2-cell-ticket .v2-primary a { color: var(--t-ink, #2c3340); font-weight: 600; }
body.ic-v2 .v2-cell-ticket .v2-primary a:hover { color: var(--t-link, #1d6fa5); }
body.ic-v2 .v2-ticket-ref {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  padding: 0 5px;
  border-radius: 5px;
  background: var(--t-surface-3, #eef1f6);
  color: var(--t-muted, #6b7789);
}
body.ic-v2 .v2-ticket-ref:hover { color: var(--t-brand-text, #5b2c86); text-decoration: none; }
body.ic-v2 .v2-cell-ticket .spx-labels { margin-left: 4px; }

/* How long it has been open. The count rail has always bucketed by this and
   the list never said, so the colour uses the rail's own thresholds. */
body.ic-v2 .v2-age { font-variant-numeric: tabular-nums; font-weight: 600; }
body.ic-v2 .v2-age-warm { color: var(--t-tint-amber-ink, #9f5f00); }
body.ic-v2 .v2-age-hot {
  color: var(--t-tint-red-ink, #b42318);
  background: var(--t-tint-red, #fdeeee);
  border-radius: 6px;
  text-align: center;
}

/* ---------- the list fits the page ------------------------------------------
   No sideways scrollbar, at any window width, with any column set.

   Auto layout sizes a column to its widest cell, and this list has several
   whose widest cell is long: "Service Cancellations" in Group, "Process
   Cancellation" in Status, "Waiting on customer", on top of a subject capped
   at 420px. Measured against the live list at a 1889px window, the table
   wanted 1417px and the results pane had 1338 -- so 79px fell off the right,
   the Watch column was cut in half, and spx-tickets-fit.js correctly turned
   the pane into a horizontal scroller. Capping the subject harder does not
   fix it: the overflow moves with the data and with the Columns toggle, so
   the width at which it stops fitting is never the same twice.

   table-layout:fixed inverts the question. The columns are told what to take,
   the table is exactly as wide as the pane, and anything longer truncates
   inside its own cell instead of pushing the table wider. The widths are
   percentages so they hold at every width: hide a column and the rest share
   what it gave up; show all five optional ones and the total goes past 100%,
   which the browser scales back rather than overflowing. Verified at 1889,
   1600, 1440, 1280, 1100 and 900px with the optional columns off and on --
   the table's scrollWidth equalled the pane's clientWidth in all twelve, so
   .is-cramped never fires and the sticky header keeps working.

   The percentages are measured, not guessed. Each pill was rendered off
   screen with every label it can hold and the widest taken: Status 147px,
   Group 151px, Priority 78px, Assigned to 97px. At a 1889px window every
   column gets what it needs and nothing truncates at all. Narrower than that
   they shrink together and the long labels ellipsis -- which is the trade the
   scrollbar was already making, except now it is visible and the last column
   is not hidden behind it.

   Scoped to .v2-list. The classic head is a different set of columns and
   mostly carries no data-col to hang a width on. ---------------------------- */
body.ic-v2 .spx-tickets-table.v2-list { table-layout: fixed; width: 100%; }

/* Fixed layout hands a cell its width; without this, content longer than the
   cell paints over the column beside it instead of being cut.

   The padding selector is written the long way, and it has to be. Three rules
   higher up this file already set it !important -- 9px 12px on a V2 table
   header, 8px 12px on the :not() chain that mirrors ic-theme, 7px 12px on a
   .v2-list cell -- and an important declaration only loses to a heavier
   important one. This took two passes to get right: .spx-tickets-table
   .v2-list > tbody > tr > td is 0,3,4 and lost to the 0,3,5 .v2-list rule,
   then 0,5,5 still lost to the :not() chain, whose three :not()s count as
   three classes and make it 0,6,5. Naming every class on the table matches
   that and wins on source order. Measure paddingLeft after changing it --
   the widths below apply either way, so a padding rule that silently loses
   looks exactly like one that worked. */
body.ic-v2 .content-wrapper table.table.spx-table.spx-tickets-table.v2-list > thead > tr > th,
body.ic-v2 .content table.table.spx-table.spx-tickets-table.v2-list > thead > tr > th,
body.ic-v2 .content-wrapper table.table.spx-table.spx-tickets-table.v2-list > tbody > tr > td,
body.ic-v2 .content table.table.spx-table.spx-tickets-table.v2-list > tbody > tr > td {
  overflow: hidden;
  padding-left: 8px !important;
  padding-right: 8px !important;
}
/* 12px a side across eleven columns is 264px of table spent on gaps. Eight
   gives 88px of it back -- more than the 79 that was overflowing -- and it is
   why the subject still gets ~343px here instead of ~255px. The checkbox and
   the expander hold one small control each and need less again. */
body.ic-v2 .content-wrapper table.table.spx-tickets-table.v2-list > thead > tr > th.spx-check,
body.ic-v2 .content-wrapper table.table.spx-tickets-table.v2-list > tbody > tr > td.spx-check,
body.ic-v2 .content-wrapper table.table.spx-tickets-table.v2-list > thead > tr > th.spx-col-x,
body.ic-v2 .content-wrapper table.table.spx-tickets-table.v2-list > tbody > tr > td.spx-col-x,
body.ic-v2 .content table.table.spx-tickets-table.v2-list > thead > tr > th.spx-check,
body.ic-v2 .content table.table.spx-tickets-table.v2-list > tbody > tr > td.spx-check,
body.ic-v2 .content table.table.spx-tickets-table.v2-list > thead > tr > th.spx-col-x,
body.ic-v2 .content table.table.spx-tickets-table.v2-list > tbody > tr > td.spx-col-x {
  padding-left: 5px !important;
  padding-right: 5px !important;
}

/* The eleven the list opens with sum to 100. The five optional ones carry a
   width too, so turning one on gives it a share rather than a sliver. */
body.ic-v2 .spx-tickets-table.v2-list th[data-col="check"]    { width: 2.2%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="expander"] { width: 1.9%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="ticket"]   { width: 26.5%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="id"]       { width: 5.0%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="customer"] { width: 11.0%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="priority"] { width: 7.0%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="status"]   { width: 12.2%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="group"]    { width: 12.5%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="type"]     { width: 11.0%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="assignee"] { width: 8.8%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="from"]     { width: 13.0%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="inbox"]    { width: 13.0%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="created"]  { width: 8.7%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="age"]      { width: 4.4%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="updated"]  { width: 8.7%; }
body.ic-v2 .spx-tickets-table.v2-list th[data-col="watch"]    { width: 4.8%; }

/* The px caps auto layout needed are wrong now -- the column decides the
   width, and a 420px cap inside a 343px column only clips 77px early. */
body.ic-v2 .spx-tickets-table.v2-list td.spx-subject,
body.ic-v2 .spx-tickets-table.v2-list td.spx-customer-cell,
body.ic-v2 .spx-tickets-table.v2-list td.spx-email-cell { max-width: none; }
body.ic-v2 .spx-tickets-table.v2-list .spx-customer-name { max-width: 100%; }

/* The five menu columns are the exception to the clipping above. Their
   dropdown is positioned inside the cell by Bootstrap and hangs well outside
   it -- measured at 99px below and 139px past the right edge -- so a cell that
   clips would cut the menu down to a sliver. Nothing is lost by letting them
   overflow: the pill below caps itself at the cell width, so the only thing
   that can escape is the open menu, which is the point. */
body.ic-v2 .content-wrapper table.table.spx-table.spx-tickets-table.v2-list > tbody > tr > td.spx-menu-cell,
body.ic-v2 .content table.table.spx-table.spx-tickets-table.v2-list > tbody > tr > td.spx-menu-cell {
  overflow: visible;
}

/* A pill is one line of text and a caret. Given less room than its label it
   ellipsises rather than spilling into the next column. */
body.ic-v2 .spx-tickets-table.v2-list .spx-inline { max-width: 100%; }
body.ic-v2 .spx-tickets-table.v2-list .spx-inline > .spx-pill {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The subject line. The anchor already truncated, but its max-width:100% was
   measured against .v2-primary, and the preview badge sits inside that same
   block -- so the anchor was allowed the full cell width *plus* the badge,
   and overflowed by exactly the badge. A flex row, badge fixed and anchor
   min-width:0, is what puts the ellipsis on the cell edge. The row underneath
   gets the same treatment: three labels on one ticket measured 375px of pills
   in a 269px cell. */
body.ic-v2 .spx-tickets-table.v2-list td.spx-subject .v2-primary {
  display: flex;
  align-items: baseline;
  gap: 5px;
  min-width: 0;
}
body.ic-v2 .spx-tickets-table.v2-list td.spx-subject .v2-primary > .spx-preview-badge { flex: 0 0 auto; }
body.ic-v2 .spx-tickets-table.v2-list td.spx-subject .v2-primary > a {
  flex: 0 1 auto;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.ic-v2 .spx-tickets-table.v2-list td.spx-subject .v2-secondary {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  overflow: hidden;
}
body.ic-v2 .spx-tickets-table.v2-list td.spx-subject .v2-secondary > .v2-ticket-ref { flex: 0 0 auto; }
body.ic-v2 .spx-tickets-table.v2-list td.spx-subject .spx-labels {
  min-width: 0;
  margin-left: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A header narrower than its own label should read "Last upda..." rather than
   being sliced mid-glyph. The sort arrow is what goes first when it is really
   tight, which is the right thing to lose -- the link's title still names the
   column and the sort. */
body.ic-v2 .spx-tickets-table.v2-list > thead > tr > th > a {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ==========================================================================
   V2.0 — the shared layer, part two
   --------------------------------------------------------------------------
   Everything above styles components one or two screens use. This part covers
   the ones that are everywhere, so a screen nobody has hand-designed still
   comes out looking like V2.0 rather than like Classic with rounder cards.

   The counts that decided what is in here: .spx-kpi appears in 47 view files,
   .spx-card in 388, .spx-table in 162, .spx-pill in 100, .spx-sub in 101.
   Restyling those reaches more of the app than any number of per-page
   templates would, with no template churn and no second copy to keep in step.

   Every rule is still under body.ic-v2, so Classic sees none of it.
   ========================================================================== */

/* ---------- KPI strips are bento tiles now ---------------------------------
   Same markup, same helpers, same colours: .spx-kpi already carries its tone
   in a modifier class. What changes is the shape. The flat 4px box with a
   34px number becomes the tile the dashboard and the Internet band use, so a
   KPI strip on the helpdesk, the leads board, billing and the IPv4 pages all
   read as one object rather than four near-misses.

   The number drops from 34px to 26px deliberately. At 34 a row of seven tiles
   is a row of shouting; the tone colour and the caption do the work, and 26px
   with tabular figures still carries across a desk. The caption becomes the
   band micro-label, uppercase and tracked, which is what tells you the number
   belongs to a heading rather than being one.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}
/* The grid sizes the tracks now, so the flex basis that used to do it would
   only fight it. A lone tile keeps its own width for the reason recorded in
   splynx-ui.css: a page-wide card holding one number is not a card. */
body.ic-v2 .spx-kpi-row > .spx-kpi { flex: none; }
@media (min-width: 768px) {
  body.ic-v2 .spx-kpi-row:has(> .spx-kpi:only-child) { grid-template-columns: 260px; }
}

body.ic-v2 .spx-kpi {
  position: relative;
  overflow: hidden;
  padding: 12px 15px 13px;
  border-radius: 14px;
  border: 1px solid var(--t-line, #e2e8f0);
  background: var(--t-surface, #fff);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
}
:root[data-theme="dark"] body.ic-v2 .spx-kpi { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }

/* The same top hairline the band tiles carry, in the tile own tone. It is the
   cheapest way to make a strip scannable: you find the red one without
   reading any of them. */
body.ic-v2 .spx-kpi:before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--v2-bar, var(--t-line, #e2e8f0));
}
body.ic-v2 .spx-kpi-ok:before,
body.ic-v2 .spx-kpi-resolved:before { background: linear-gradient(90deg, var(--t-tint-green-ink, #27ae60), transparent); }
body.ic-v2 .spx-kpi-info:before,
body.ic-v2 .spx-kpi-new:before { background: linear-gradient(90deg, var(--t-accent-text, #2f80ed), transparent); }
body.ic-v2 .spx-kpi-warn:before,
body.ic-v2 .spx-kpi-work_in_progress:before,
body.ic-v2 .spx-kpi-unlinked:before { background: linear-gradient(90deg, var(--t-tint-amber-ink, #e67e22), transparent); }
body.ic-v2 .spx-kpi-bad:before,
body.ic-v2 .spx-kpi-attention:before,
body.ic-v2 .spx-kpi-age-hot:before { background: linear-gradient(90deg, var(--t-danger-text, #c0392b), transparent); }
body.ic-v2 .spx-kpi-lead:before,
body.ic-v2 .spx-kpi-me:before,
body.ic-v2 .spx-kpi-vhost:before,
body.ic-v2 .spx-kpi-waiting_customer:before,
body.ic-v2 .spx-kpi-waiting_agent:before { background: linear-gradient(90deg, var(--t-tint-purple-ink, #8e44ad), transparent); }

/* Only the ones that go somewhere lift. A tile that is a number and not a
   link should not pretend to be pressable. */
body.ic-v2 a.spx-kpi:hover,
body.ic-v2 button.spx-kpi:hover {
  border-color: var(--t-brand, #5b2c86);
  box-shadow: 0 4px 16px rgba(16, 24, 40, 0.10);
}
body.ic-v2 .spx-kpi:focus-visible { outline: 2px solid var(--t-accent, #2f80ed); outline-offset: 2px; }

/* Mirrors the `span.` shape splynx-ui uses, so this out-specifies it rather
   than tying and depending on which file the browser read last. */
body.ic-v2 .spx-kpi span.spx-kpi-label,
body.ic-v2 .spx-kpi span.spx-kpi-head,
body.ic-v2 .spx-kpi > span:first-child {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
  margin-bottom: 2px;
}
body.ic-v2 .spx-kpi span.spx-kpi-value,
body.ic-v2 .spx-kpi b {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.6px;
  font-variant-numeric: tabular-nums;
}
body.ic-v2 .spx-kpi em {
  margin-top: 6px;
  font-size: 11.5px;
}
/* The icon was 18px at 0.85, which put it in competition with the number it
   labels. It is a category mark, not a second reading. */
body.ic-v2 .spx-kpi i {
  top: 11px;
  font-size: 14px;
  opacity: 0.5;
}
body.ic-v2 .spx-age-row span.spx-kpi-value,
body.ic-v2 .spx-status-row span.spx-kpi-value { font-size: 22px; }

/* ---------- report and hub tiles --------------------------------------------
   The reports index is 19 of these. Same card, but they are a title and a
   button rather than a number, so no tone hairline: a stripe on a tile with
   nothing to be alarmed about is decoration.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-report-tile,
body.ic-v2 .spx-hub-tile {
  border-radius: 14px;
  border-color: var(--t-line, #e2e8f0);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
body.ic-v2 .spx-report-tile:hover,
body.ic-v2 .spx-hub-tile:hover {
  border-color: var(--t-brand, #5b2c86);
  box-shadow: 0 4px 16px rgba(16, 24, 40, 0.10);
}
body.ic-v2 .spx-report-tile-title { font-size: 14px; letter-spacing: -0.1px; }

/* ---------- record tabs are a segmented control ------------------------------
   The object .v2-tabs draws on the customer record, applied to the Bootstrap
   nav-tabs markup every other record page already uses. Doing it here rather
   than by rewriting 100 templates is the whole point of V2.0 being a mode.

   !important throughout, and not for fun: splynx-ui pins
   `.spx-record-tabs > li > a { border: none !important; background:
   transparent !important }`, and a segmented control IS a background on the
   selected item, so it cannot be drawn without out-shouting that.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-record-tabs {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 2px;
  padding: 3px;
  margin-bottom: 14px;
  border: 1px solid var(--t-line, #e2e8f0);
  border-radius: 12px;
  background: var(--t-surface-2, #f1f4f8);
}
body.ic-v2 .spx-record-tabs > li > a {
  padding: 6px 13px !important;
  border-radius: 9px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--t-muted, #6b7789);
  white-space: nowrap;
}
body.ic-v2 .spx-record-tabs > li > a:hover {
  color: var(--t-ink, #2c3340);
  background: transparent !important;
}
body.ic-v2 .spx-record-tabs > li.active > a {
  background: var(--t-surface, #fff) !important;
  color: var(--t-brand-text, #5b2c86);
  border-bottom: 0 !important;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.10);
}
:root[data-theme="dark"] body.ic-v2 .spx-record-tabs > li.active > a { background: var(--t-surface-3, #1e1e1e) !important; }
/* The strip used to sit ON the card it introduced, which is why it had two
   square bottom corners. As a floating pill it does not, so the card under it
   gets its corners back. */
body.ic-v2 .spx-record-tabs + .spx-record-body,
body.ic-v2 .spx-record-tabs + .box { border-radius: 14px; }

/* The count beside a tab label. Solid purple at 11px was the loudest thing on
   the strip; it is a quantity, not an alert. */
body.ic-v2 .spx-count {
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--t-surface-3, #eef1f6);
  color: var(--t-ink-2, #334155);
  font-size: 10.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
body.ic-v2 .spx-record-tabs > li.active .spx-count {
  background: var(--t-brand, #5b2c86);
  color: #fff;
}

/* ---------- key/value tables ------------------------------------------------
   Every record page is built out of these. The label column was the same
   weight and colour as the value, so a .spx-kv read as a two-column table of
   equals rather than as a list of facts about one thing.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-kv td { padding: 7px 10px; border-color: var(--t-line-soft, #eef2f6); }
body.ic-v2 .spx-kv td:first-child {
  width: 40%;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
  vertical-align: top;
  padding-top: 9px;
}
body.ic-v2 .spx-kv td:last-child { color: var(--t-ink, #2c3340); }
body.ic-v2 .spx-kv tr:hover td { background: var(--t-surface-2, #f7f9fc); }

/* ---------- form controls ----------------------------------------------------
   A Bootstrap 3 input at 4px radius beside a 14px card is the clearest single
   tell that a screen has not been touched. The focus ring matters more than
   the corner: the default is a pale blue glow that both themes wash out.
   -------------------------------------------------------------------------- */
body.ic-v2 .form-control,
body.ic-v2 select.form-control,
body.ic-v2 textarea.form-control {
  border-radius: 9px;
  border-color: var(--t-line, #d9e0e9);
  box-shadow: none;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
body.ic-v2 .form-control:focus {
  border-color: var(--t-brand, #5b2c86);
  box-shadow: 0 0 0 3px rgba(91, 44, 134, 0.14);
}
body.ic-v2 .input-group-addon { border-radius: 9px; border-color: var(--t-line, #d9e0e9); }
body.ic-v2 .spx-form-table > tbody > tr > td { padding: 8px 10px !important; }
body.ic-v2 .spx-form-table label,
body.ic-v2 .control-label {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: var(--t-muted, #6b7789);
}
/* A 34px control is under the 44px touch target, and these pages get used on
   phones in vans. Raising the minimum only where the pointer is coarse keeps
   the desktop density everyone is used to. */
@media (pointer: coarse) {
  body.ic-v2 .form-control,
  body.ic-v2 .btn { min-height: 44px; }
}

/* ---------- list chrome --------------------------------------------------------
   The strip above a table: page size, search, the Columns menu, and on the
   helpdesk the bulk-action panels. Six screens build this out of the same
   pieces, so it is worth having them agree on the spacing.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-list-controls,
body.ic-v2 .spx-table-tools {
  padding-bottom: 11px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--t-line-soft, #eef2f6);
}
body.ic-v2 .spx-col-toggle-menu {
  border-radius: 12px;
  border-color: var(--t-line, #e2e8f0);
  box-shadow: 0 12px 32px rgba(16, 24, 40, 0.14);
  padding: 6px;
}
body.ic-v2 .spx-col-toggle-item {
  border-radius: 7px;
  padding: 5px 8px;
  font-size: 12.5px;
}
body.ic-v2 .spx-col-toggle-item:hover { background: var(--t-surface-2, #f1f4f8); }
body.ic-v2 .spx-bulk-panel,
body.ic-v2 .spx-filter-panel {
  border-radius: 12px;
  border: 1px solid var(--t-line, #e2e8f0);
  background: var(--t-surface-2, #f7f9fc);
}
body.ic-v2 .spx-bulk-panel label,
body.ic-v2 .spx-filter-panel label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
}

/* ---------- the pager ------------------------------------------------------- */
body.ic-v2 .spx-pager {
  margin-top: 14px;
  padding-top: 11px;
  border-top: 1px solid var(--t-line-soft, #eef2f6);
  font-size: 12px;
}
body.ic-v2 .spx-pager-count { font-variant-numeric: tabular-nums; }
body.ic-v2 .spx-pager-btns .btn { border-radius: 9px; }

/* ---------- sortable headers ---------------------------------------------------
   The header is 10.5px uppercase under the table rules above, so the sort
   arrow at its default size stood taller than the text it belongs to.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-sort-icon { font-size: 9px; opacity: 0.55; }
body.ic-v2 .spx-sortable.is-sorted .spx-sort-icon { opacity: 1; }
body.ic-v2 .spx-sortable a:hover { color: var(--t-brand-text, #5b2c86); }
/* A composite header carries the sort of the column it absorbed. That is a
   second control in one cell, so it has to read as secondary without becoming
   unreadable: muted, not faint. */
body.ic-v2 .v2-th-alt { margin-left: 7px; font-weight: 600; }
body.ic-v2 .v2-th-alt:before { content: "/"; margin-right: 7px; color: var(--t-line, #cbd5e1); }
body.ic-v2 .v2-th-alt a { color: var(--t-muted, #6b7789); }
body.ic-v2 .v2-th-alt a.is-sorted { color: var(--t-brand-text, #5b2c86); }

/* ---------- hints, callouts and empty states -------------------------------- */
body.ic-v2 .spx-hint {
  padding: 9px 12px;
  border-radius: 10px;
  background: var(--t-surface-2, #f7f9fc);
  border: 1px solid var(--t-line-soft, #eef2f6);
  font-size: 12.5px;
}
body.ic-v2 .alert { border-radius: 12px; border-width: 1px; }
body.ic-v2 .spx-sub { font-size: 12px; color: var(--t-muted, #6b7789); }

/* ---------- the row detail ------------------------------------------------------
   What the expander opens. It is a panel inside a row, so it has to read as
   inset rather than as another row that happens to be very wide.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-row-detail-body {
  border-radius: 12px;
  background: var(--t-surface-2, #f7f9fc);
  border: 1px solid var(--t-line-soft, #eef2f6);
  padding: 12px 14px;
}
body.ic-v2 .spx-row-detail-body strong {
  display: block;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
}
body.ic-v2 .spx-mini-services > li {
  border-radius: 9px;
  border: 1px solid var(--t-line, #e2e8f0);
  background: var(--t-surface, #fff);
}
body.ic-v2 .spx-mini-user {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
}

/* ---------- composite-cell extras ------------------------------------------------
   New with the customer list. .v2-primary, .v2-secondary and .v2-clip come
   from the Internet list above; these are the pieces that list did not need.
   -------------------------------------------------------------------------- */
body.ic-v2 .v2-ref {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 10.5px;
  padding: 0 5px;
  margin-right: 5px;
  border-radius: 5px;
  background: var(--t-surface-3, #eef1f6);
  color: var(--t-ink-2, #334155);
  flex: 0 0 auto;
}
body.ic-v2 .v2-logins {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
}
/* An absent value, said once and quietly. Not the same grey as a value that
   is present but unremarkable. */
body.ic-v2 .v2-muted { color: var(--t-faint, #8895a7); font-weight: 400; }
/* The secondary line in these two cells puts a chip beside clipped text, so
   it is a flex row rather than the plain block the Internet list uses. */
body.ic-v2 .v2-cell-customer > .v2-secondary,
body.ic-v2 .v2-cell-contact > .v2-secondary {
  display: flex;
  align-items: center;
  min-width: 0;
}
body.ic-v2 .v2-cell-customer { min-width: 210px; }
body.ic-v2 .v2-cell-services { min-width: 180px; }
body.ic-v2 .v2-cell-seen { white-space: nowrap; }

/* ---------- the modal record panel --------------------------------------------
   Rows open in a panel rather than navigating, all over this app, so it is
   a card like any other and should not be the one square-cornered surface
   left standing.
   -------------------------------------------------------------------------- */
body.ic-v2 .spx-record-modal-panel,
body.ic-v2 .spx-ticket-modal { border-radius: 16px; }

/* ---------- motion --------------------------------------------------------------
   Two transitions, both under 130ms, both on properties that do not reflow.
   Off entirely for anyone who has asked for that.
   -------------------------------------------------------------------------- */
body.ic-v2 .v2-stat,
body.ic-v2 .spx-kpi {
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
@media (prefers-reduced-motion: reduce) {
  body.ic-v2 * { transition: none !important; animation: none !important; }
}

/* Paper takes the page as it is. A tile with a shadow prints as a grey smear,
   and a 3px gradient hairline prints as a smudge. */
@media print {
  body.ic-v2 .spx-kpi,
  body.ic-v2 .spx-report-tile,
  body.ic-v2 .spx-row-detail-body { box-shadow: none; }
  body.ic-v2 .spx-kpi:before { display: none; }
}

/* ==========================================================================
   V2.0 — winning against body.spx-app
   --------------------------------------------------------------------------
   `.ic-v2` and `.spx-app` are both classes on <body>, so a rule written
   `body.ic-v2 .thing` does NOT beat splynx-ui's `body.spx-app .nav-tabs.thing`
   -- the second chains more classes and wins on specificity, `!important` or
   not. Every screen in the app carries .spx-app, so "the rule I wrote is
   simply ignored, everywhere" is the failure mode, and it is silent: the
   properties nothing else sets DO apply, so the component comes out half
   restyled and looks like a design decision.

   Four of them were exactly that, measured in a browser against the real
   stylesheets rather than read off the source:

     .spx-record-tabs   background dropped, the segmented control never drawn
     li.active > a      still Bootstrap's underline, 3px, not a pill
     .spx-kv td         6px 10px !important, not the 7px 10px above
     .form-control      6px !important, not the 9px above

   The rule to follow when adding to the layers above: if splynx-ui or
   ic-theme pins it under `body.spx-app`, mirror that whole chain here with
   `body.ic-v2.spx-app` in front. One extra class is all it takes, and the
   file already does this for .spx-card for the same reason.
   ========================================================================== */

/* ---------- the segmented tab strip, for real this time -------------------- */
body.ic-v2.spx-app .nav-tabs.spx-record-tabs {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 2px;
  padding: 3px;
  margin-bottom: 14px;
  background: var(--t-surface-2, #f1f4f8);
  border: 1px solid var(--t-line, #e2e8f0);
  border-radius: 12px;
}
body.ic-v2.spx-app .nav-tabs.spx-record-tabs > li > a {
  padding: 6px 13px !important;
  border-radius: 9px !important;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--t-muted, #6b7789);
  white-space: nowrap;
}
body.ic-v2.spx-app .nav-tabs.spx-record-tabs > li.active > a,
body.ic-v2.spx-app .nav-tabs.spx-record-tabs > li.active > a:hover,
body.ic-v2.spx-app .nav-tabs.spx-record-tabs > li.active > a:focus {
  background: var(--t-surface, #fff) !important;
  color: var(--t-brand-text, #5b2c86) !important;
  border: 0 !important;
  border-radius: 9px !important;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.10);
}
:root[data-theme="dark"] body.ic-v2.spx-app .nav-tabs.spx-record-tabs > li.active > a {
  background: var(--t-surface-3, #1e1e1e) !important;
}

/* ---------- key/value rows ---------------------------------------------------
   splynx-ui pins every table cell under .content-wrapper at 6px 10px
   !important and 13px, and unlike ic-theme's version that chain does not
   exclude .spx-kv -- so the label column kept the body size and the rows kept
   the list density. Mirrored, with the same !important it has to beat.
   -------------------------------------------------------------------------- */
body.ic-v2.spx-app .content-wrapper table.spx-kv > tbody > tr > td,
body.ic-v2.spx-app .content table.spx-kv > tbody > tr > td {
  padding: 7px 10px !important;
}
body.ic-v2.spx-app .content-wrapper table.spx-kv > tbody > tr > td:first-child,
body.ic-v2.spx-app .content table.spx-kv > tbody > tr > td:first-child {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
}

/* ---------- form controls ------------------------------------------------------ */
body.ic-v2.spx-app .form-control,
body.ic-v2.spx-app select.form-control,
body.ic-v2.spx-app textarea.form-control {
  border-radius: 9px !important;
}
body.ic-v2.spx-app .form-control:focus {
  border-color: var(--t-brand, #5b2c86) !important;
  box-shadow: 0 0 0 3px rgba(91, 44, 134, 0.14) !important;
}

/* The mobile strip stays one scrolling row, which the rules above had taken
   away. splynx-ui pins `flex-wrap: nowrap; overflow-x: auto` on
   .spx-record-tabs under (max-width: 767px) at one class of specificity, and
   the segmented rules above out-specify it -- so three tabs wrapped onto two
   lines inside the pill on a 375px screen. Measured, not guessed.

   The original reason for nowrap was that a wrapped nav-tabs draws its bottom
   border per line and stranded the active underline on the first one. The
   pill has no underline to strand, so this is no longer a broken frame -- but
   it is still two rows of chrome where one scrolls, and the app already made
   that call everywhere else. Keep it. */
@media (max-width: 767px) {
  body.ic-v2.spx-app .nav-tabs.spx-record-tabs,
  body.ic-v2 .spx-record-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  body.ic-v2.spx-app .nav-tabs.spx-record-tabs > li,
  body.ic-v2 .spx-record-tabs > li { flex: 0 0 auto; }
}

/* ---------- the ticket popup -------------------------------------------------
   1120px on a 1900px screen left a third of the width empty on either side,
   which is a lot of nothing next to a conversation you are reading.
   -------------------------------------------------------------------------- */
/* Desktop only. ic-theme.css widens this dialog to calc(100% - 16px) under
   @media (max-width: 991px), and a media query adds no specificity -- an
   unscoped rule here would beat that and make the popup NARROWER on a phone
   than it is today. */
@media (min-width: 992px) {
  body.ic-v2 .spx-ticket-modal-dialog {
    width: calc(100vw - 80px);
    max-width: 1720px;
    margin: 20px auto;
  }
  body.ic-v2 .spx-ticket-modal-body { max-height: calc(100vh - 104px); }
}
body.ic-v2 .spx-ticket-modal-content { border-radius: 14px; }

/* ---------- the ticket identity ----------------------------------------------
   State first, because the page's job is deciding what to do next and the
   classic block kept status and priority in the sidebar dropdowns.
   -------------------------------------------------------------------------- */
body.ic-v2 .v2-ticket-origin { min-width: 0; }
body.ic-v2 .v2-ticket-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 7px;
}
body.ic-v2 .v2-ticket-origin .v2-ticket-ref {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--t-surface-3, #eef1f6);
  color: var(--t-ink-2, #334155);
}
body.ic-v2 .v2-ticket-subject {
  margin: 0 0 6px;
  font-size: 19px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.2px;
  color: var(--t-ink, #2c3340);
}
body.ic-v2 .v2-ticket-provenance {
  font-size: 12px;
  color: var(--t-muted, #6b7789);
}
body.ic-v2 .v2-chip-warm { background: var(--t-tint-amber, #fff4e5); color: var(--t-tint-amber-ink, #9f5f00); }
body.ic-v2 .v2-chip-hot  { background: var(--t-tint-red, #fdeeee); color: var(--t-tint-red-ink, #b42318); }
body.ic-v2 .v2-chip-plain { background: var(--t-tint-slate, #f3f7fb); color: var(--t-tint-slate-ink, #5b6472); }

/* The thread is the reading column; at 1720px the classic 8/4 split leaves the
   sidebar wider than it needs and the conversation narrower than it wants. */
@media (min-width: 1500px) {
  body.ic-v2 .spx-ticket-modal-panel > .row > .col-md-8 { width: 70%; }
  body.ic-v2 .spx-ticket-modal-panel > .row > .col-md-4 { width: 30%; }
}
/* ==========================================================================
   V2.0 — the helpdesk count rail
   --------------------------------------------------------------------------
   The ticket list got its composed cell and its Age column in #557, but its
   SUMMARY did not get anything, because the rail is not a .spx-kpi strip and
   so the shared layer never reached it. On the most-used page in the app that
   left the V2 table sitting next to a Classic sidebar.

   No band was added here, deliberately. Every other list got one because it
   had no summary at all; this page has twenty-one counts already, and the
   rail's own notes record a round of deleting rows that repeated a control
   two inches above them. A second summary would be that mistake again. The
   rail IS the band — so it is built out of the same parts.

   Its rules live in a <style> block inside crm_tickets/index.html.erb rather
   than in a stylesheet, which does not change the arithmetic: specificity
   decides first, and every selector here carries one class more than the one
   it overrides. Source order never comes into it.
   ========================================================================== */
body.ic-v2 .spx-count-rail {
  border-radius: 14px;
  border-color: var(--t-line, #e2e8f0);
  padding: 6px;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
}
:root[data-theme="dark"] body.ic-v2 .spx-count-rail { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }

body.ic-v2 .spx-count-rail-group {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  color: var(--t-muted, #6b7789);
  padding: 11px 8px 4px;
}
body.ic-v2 .spx-count-rail-group:first-child { padding-top: 5px; }

/* A row is a tile laid on its side: label, then the number, right-aligned and
   in tabular figures so a column of them lines up on the digit rather than
   drifting with the glyph widths. The 3px transparent left border went with
   the square corners -- an indent that only appears on the selected row moves
   every other row sideways when you click. */
body.ic-v2 a.spx-count-rail-row {
  padding: 5px 9px;
  border-left: 0;
  border-radius: 9px;
  font-size: 12.5px;
  color: var(--t-ink-2, #334155);
}
body.ic-v2 a.spx-count-rail-row:hover,
body.ic-v2 a.spx-count-rail-row:focus {
  background: var(--t-surface-2, #f1f4f8);
  color: var(--t-ink, #2c3340);
}
body.ic-v2 .spx-count-rail-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
body.ic-v2 .spx-count-rail-value {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 12px;
  flex: 0 0 auto;
}

/* The selected row. The old one carried a tint, a left bar, a weight change
   and an inset ring -- four signals for one bit of state, which is what you
   reach for when none of them is working on its own. One filled chip and the
   brand tint is enough, and it does not move the rows around it. */
body.ic-v2 a.spx-count-rail-row.is-active {
  background: var(--t-tint-purple, #f3e8fb);
  border-left: 0;
  border-radius: 9px;
  box-shadow: none;
  color: var(--t-tint-purple-ink, #3d1d5c);
}
:root[data-theme="dark"] body.ic-v2 a.spx-count-rail-row.is-active { background: var(--t-surface-3, #1e1e1e); }
body.ic-v2 a.spx-count-rail-row.is-active .spx-count-rail-value {
  background: var(--t-brand, #5b2c86);
  color: #fff;
  border-radius: 999px;
  padding: 0 7px;
}

/* Tone stays on the number, as it was. It is the only coloured thing in a
   rail of twenty-one rows, which is what makes "Needs attention" findable
   without reading down the list. */
body.ic-v2 .spx-count-rail-hot .spx-count-rail-value { color: var(--t-danger-text, #c0392b); }
body.ic-v2 .spx-count-rail-good .spx-count-rail-value { color: var(--t-tint-green-ink, #22a45d); }
body.ic-v2 .spx-count-rail-blue .spx-count-rail-value { color: var(--t-accent-text, #2f80ed); }
body.ic-v2 a.spx-count-rail-row.is-active.spx-count-rail-hot .spx-count-rail-value,
body.ic-v2 a.spx-count-rail-row.is-active.spx-count-rail-good .spx-count-rail-value,
body.ic-v2 a.spx-count-rail-row.is-active.spx-count-rail-blue .spx-count-rail-value { color: #fff; }

/* ==========================================================================
   V2.0 — the hosting list
   ========================================================================== */
/* A domain is an identifier: it gets read character by character when
   somebody is checking whether it is the right one. */
body.ic-v2 .v2-domain,
body.ic-v2 .v2-domain a {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
}
body.ic-v2 .v2-cell-site { min-width: 220px; }
/* Bootstrap's .table-bordered draws a line round every cell, which turns a
   two-line composed cell into a box inside a box. The row rule is enough. */
body.ic-v2 table.table-bordered.v2-list > tbody > tr > td,
body.ic-v2 table.table-bordered.v2-list > thead > tr > th { border-left: 0; border-right: 0; }

/* The line that explains why the three presence numbers differ. Quiet: it is
   there for the person who noticed, not for everyone, every time. */
body.ic-v2 .icb-presence-note { font-size: 11px; color: var(--t-faint, #8895a7); margin-top: 1px; }
/* ==========================================================================
   V2.0 — the Voice and Equipment lists
   ========================================================================== */
/* Numbers, MAC addresses and logins are all read one character at a time,
   usually against something printed on a box or read down a phone. The
   monospaced face is what makes 0 and O, 1 and l differ on sight. */
body.ic-v2 .v2-did,
body.ic-v2 .v2-did a,
body.ic-v2 .v2-mac,
body.ic-v2 .v2-mac a {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
}
body.ic-v2 .v2-cell-line { min-width: 160px; }
body.ic-v2 .v2-cell-device { min-width: 180px; }
body.ic-v2 .v2-cell-stock { min-width: 140px; }
body.ic-v2 .v2-cell-assigned { min-width: 180px; }

/* The band's own provenance line. .v2-provenance already exists for the
   invoice panel; on Voice it carries the audit's age and the fact that
   reading it writes nothing, which used to live inside the audit bar. */
body.ic-v2 .v2-band + .v2-provenance { margin-top: -4px; border-top: 0; padding-top: 0; }

/* The heading above each equipment band. It was a full-size h3 saying
   "Total ONT Equipment : 412" -- the count is a tile now, so the heading
   only has to name the group. */
body.ic-v2 .spx-section-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
  margin: 22px 0 10px;
}
body.ic-v2 .spx-section-title:first-of-type { margin-top: 6px; }

/* ==========================================================================
   V2.0 — the dashboard stat cards
   --------------------------------------------------------------------------
   `dashboard_stat_card` renders 56 cards across 10 views: the reseller home
   (orders#index, which is `root`), the customer services hub, monthly ops and
   seven sections of the home dashboard. None of them had been touched, so
   they were still AdminLTE small-boxes -- a saturated block of colour with
   white text on it -- sitting under V2 chrome.

   This is the same trade the KPI strip made, and the bigger one: no template
   changes at all, 56 cards, 10 views.

   White-on-colour is also the accessibility problem in the set. White on
   AdminLTE's bg-yellow (#f39c12) is 2.15:1 and on bg-aqua (#00c0ef) 2.36:1 --
   both far under 4.5. Putting the colour in a hairline and the number, and
   the text on a white surface, is what fixes that rather than hunting for
   darker yellows.

   THE ONE EXCEPTION IS DELIBERATE. A card may carry `hue:` instead of a
   state tone -- Sean asked for the Monthly run cards to be a different
   colour each, and _stat_card.html.erb emits `ic-hue-*` for exactly that.
   Those keep their identity colour: every rule here is scoped
   :not([class*="ic-hue-"]) so the two systems never meet.

   Specificity: ic-theme pins these through `.content-wrapper a.ic-stat-card`
   with `!important` on the colours, so that whole chain is mirrored with
   body.ic-v2 in front.
   ========================================================================== */
body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]),
body.ic-v2 a.ic-stat-card:not([class*="ic-hue-"]),
body.ic-v2 a.ic-stat-card:not([class*="ic-hue-"]):visited,
body.ic-v2 .content-wrapper a.ic-stat-card:not([class*="ic-hue-"]),
body.ic-v2 .content-wrapper a.ic-stat-card:not([class*="ic-hue-"]):visited {
  background: var(--t-surface, #fff) !important;
  border: 1px solid var(--t-line, #e2e8f0);
  /* !important: the layout's own <style> pins border-radius 4px important on
     .skin-blue .content-wrapper .small-box, and measuring showed every V2
     stat card in the app rendering at 4px rather than 14px.
     NO BRACES IN THIS COMMENT ON PURPOSE -- three specs parse this file by
     scanning to the next closing brace, and one inside a comment ends the
     block early. The first draft of this very comment did exactly that and
     turned two unrelated specs red. */
  border-radius: 14px !important;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
  color: var(--t-ink, #2c3340) !important;
  min-height: 0;
  padding-bottom: 0;
  /* Both are needed here rather than inherited: ic-theme sets position and
     overflow on `a.ic-stat-card` only, and _stat_card.html.erb renders a
     plain <div> when the card has no path -- so the hairline below would
     otherwise be placed against some ancestor and square off the corner. */
  position: relative;
  overflow: hidden;
}
:root[data-theme="dark"] body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]) { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }

/* A lift, not a jump. The 2px translate moved a whole grid of cards when you
   ran the mouse across them. */
body.ic-v2 a.ic-stat-card:not([class*="ic-hue-"]):hover,
body.ic-v2 a.ic-stat-card:not([class*="ic-hue-"]):focus,
body.ic-v2 .content-wrapper a.ic-stat-card:not([class*="ic-hue-"]):hover,
body.ic-v2 .content-wrapper a.ic-stat-card:not([class*="ic-hue-"]):focus {
  transform: none;
  border-color: var(--t-brand, #5b2c86);
  box-shadow: 0 4px 16px rgba(16, 24, 40, 0.10);
  color: var(--t-ink, #2c3340) !important;
}
body.ic-v2 a.ic-stat-card:focus-visible { outline: 2px solid var(--t-accent, #2f80ed); outline-offset: 2px; }

body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]) .inner { padding: 12px 15px 13px; }

/* Caption under the number in the markup, so it reads as the number's name
   rather than as a heading above it -- same as every other V2 tile. */
body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]) .inner h3,
body.ic-v2 .content-wrapper .ic-stat-card:not([class*="ic-hue-"]) .inner h3 {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.6px;
  font-variant-numeric: tabular-nums;
  margin: 0;
  color: var(--t-ink, #2c3340) !important;
}
body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]) .inner p,
body.ic-v2 .content-wrapper .ic-stat-card:not([class*="ic-hue-"]) .inner p,
body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]) .inner a,
body.ic-v2 .content-wrapper .ic-stat-card:not([class*="ic-hue-"]) .inner a {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789) !important;
  margin: 2px 0 0;
}

/* AdminLTE's icon is 70px at 0.15 alpha in the corner. On a white tile that
   is a grey smudge behind the number rather than a mark beside it. */
body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]) .icon,
body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]) .icon > i {
  font-size: 15px !important;
  top: 12px !important;
  right: 13px !important;
  color: var(--t-faint, #b6c0cc) !important;
  opacity: 1;
}

/* "View list" was a dark bar across the bottom of a coloured block. The whole
   tile is the link, so it only has to say so quietly. */
body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]) .small-box-footer,
body.ic-v2 .content-wrapper a.ic-stat-card:not([class*="ic-hue-"]) .small-box-footer {
  position: static;
  background: transparent;
  border-top: 1px solid var(--t-line-soft, #eef2f6);
  color: var(--t-muted, #6b7789) !important;
  font-size: 10.5px;
  font-weight: 600;
  text-align: left;
  padding: 7px 15px;
}

/* The "needs you" flag. It was white on a colour; on a white tile it is the
   one thing allowed to be loud. */
body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]) .ic-stat-flag {
  display: inline-block;
  margin-bottom: 4px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--t-tint-red, #fdeeee);
  color: var(--t-tint-red-ink, #b42318) !important;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}

/* The tone, as a hairline and on the number. Both the semantic class the
   helper emits (.ic-stat-active) and the AdminLTE colour beside it (.bg-green)
   are matched, because the helper emits the pair and either could be the one
   a future card carries. */
body.ic-v2 .ic-stat-card:not([class*="ic-hue-"]):before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--v2-bar, var(--t-line, #e2e8f0));
}
/* The tone, as a custom property rather than a background on the
   pseudo-element. `body.ic-v2 .bg-aqua:before` is (0,2,2) and the default
   bar rule on the card is (0,3,2), so the gradient lost every time and
   the hairline has been rendering flat grey since it was written --
   measured in a browser, rgb(226,232,240) on every card. A property is
   set on the element, where nothing competes for it, and the :before
   reads it. */
body.ic-v2 .ic-stat-active,
body.ic-v2 .bg-green { --v2-bar: linear-gradient(90deg, var(--t-tint-green-ink, #27ae60), transparent); }
body.ic-v2 .ic-stat-pending,
body.ic-v2 .bg-aqua { --v2-bar: linear-gradient(90deg, var(--t-accent-text, #2f80ed), transparent); }
body.ic-v2 .ic-stat-suspended,
body.ic-v2 .bg-yellow { --v2-bar: linear-gradient(90deg, var(--t-tint-amber-ink, #e67e22), transparent); }
body.ic-v2 .ic-stat-expired,
body.ic-v2 .bg-orange { --v2-bar: linear-gradient(90deg, var(--t-tint-amber-ink, #d35400), transparent); }
body.ic-v2 .ic-stat-deleted,
body.ic-v2 .bg-red { --v2-bar: linear-gradient(90deg, var(--t-danger-text, #c0392b), transparent); }
body.ic-v2 .ic-stat-prepaid,
body.ic-v2 .bg-teal { --v2-bar: linear-gradient(90deg, var(--t-tint-blue-ink, #2b8fb3), transparent); }
body.ic-v2 .ic-stat-mandate,
body.ic-v2 .bg-purple { --v2-bar: linear-gradient(90deg, var(--t-tint-purple-ink, #8e44ad), transparent); }

/* THE TONE USED TO BE ON THE NUMBER HERE, AND IT WAS A BUG.
   Seven rules set `.ic-stat-active .inner h3 { color: green }` and so on,
   keyed on the tone class alone. On a real .ic-stat-card they never applied
   -- the ink rule above is (0,5,2) against their (0,3,2) and wins -- so they
   were dead everywhere they were meant to work. Where they DID apply was the
   one place the white surface does not reach: a .small-box carrying a tone
   class without .ic-stat-card. /staff_presences has three. Measured in a
   browser on production: a green number on the green block at 1.11:1, and a
   blue one on aqua at 1.80:1, against Classic's 3.18 and 2.15. V2 had made
   them worse than the thing it was fixing.
   The tone lives on the 3px bar, which is what the rest of V2 does, and the
   number is ink. The surface block below is what makes that true for a
   small-box that is not a stat card. */

/* A plain small-box that is not a stat card at all. This used to set the
   radius and nothing else -- "the shape without pretending to know its tone"
   -- which left the saturated AdminLTE block and its white text in place.
   Measured on production: /reports/commission_agents at 2.15, 2.19 and 3.18,
   /reports/clarens_subcontractor the same plus 4.09, and /staff_presences
   worse still once the tone rules above got at the number.

   It gets the same surface as a stat card instead. The layout's own <style>
   block pins `.skin-blue .content-wrapper .small-box { color: #fff
   !important }` and a background per colour, so these carry .content-wrapper
   to outspecify it -- (0,4,1) against the layout's (0,3,0), and (0,6,2) for
   the text against its (0,4,1). */
body.ic-v2 .small-box:not(.ic-stat-card):not([class*="ic-hue-"]),
body.ic-v2 .content-wrapper .small-box:not(.ic-stat-card):not([class*="ic-hue-"]) {
  background: var(--t-surface, #fff) !important;
  border: 1px solid var(--t-line, #e2e8f0);
  /* !important: the layout's own <style> pins border-radius 4px important on
     .skin-blue .content-wrapper .small-box, and measuring showed every V2
     stat card in the app rendering at 4px rather than 14px.
     NO BRACES IN THIS COMMENT ON PURPOSE -- three specs parse this file by
     scanning to the next closing brace, and one inside a comment ends the
     block early. The first draft of this very comment did exactly that and
     turned two unrelated specs red. */
  border-radius: 14px !important;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
  color: var(--t-ink, #2c3340) !important;
  position: relative;
  overflow: hidden;
  min-height: 0;
}
/* The tone bar. The gradient rules above already key on both the semantic
   class and the AdminLTE colour, so this only has to supply the default and
   let them paint it. */
body.ic-v2 .small-box:not(.ic-stat-card):not([class*="ic-hue-"]):before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--v2-bar, var(--t-line, #e2e8f0));
}
body.ic-v2 .content-wrapper .small-box:not(.ic-stat-card):not([class*="ic-hue-"]) .inner h3 {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.6px;
  font-variant-numeric: tabular-nums;
  margin: 0;
  color: var(--t-ink, #2c3340) !important;
}
body.ic-v2 .content-wrapper .small-box:not(.ic-stat-card):not([class*="ic-hue-"]) .inner p,
body.ic-v2 .content-wrapper .small-box:not(.ic-stat-card):not([class*="ic-hue-"]) .inner a,
body.ic-v2 .content-wrapper .small-box:not(.ic-stat-card):not([class*="ic-hue-"]) p,
body.ic-v2 .content-wrapper .small-box:not(.ic-stat-card):not([class*="ic-hue-"]) p a {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789) !important;
  margin: 2px 0 0;
}
body.ic-v2 .content-wrapper .small-box:not(.ic-stat-card):not([class*="ic-hue-"]) .icon,
body.ic-v2 .content-wrapper .small-box:not(.ic-stat-card):not([class*="ic-hue-"]) .icon > i {
  font-size: 15px !important;
  top: 12px !important;
  right: 13px !important;
  color: var(--t-faint, #b6c0cc) !important;
  opacity: 1;
}
body.ic-v2 .content-wrapper .small-box:not(.ic-stat-card):not([class*="ic-hue-"]) .small-box-footer {
  position: static;
  background: transparent;
  border-top: 1px solid var(--t-line-soft, #eef2f6);
  color: var(--t-muted, #6b7789) !important;
  font-size: 10.5px;
  font-weight: 600;
  text-align: left;
  padding: 7px 15px;
}

/* ==========================================================================
   V2.0 — the product list
   ========================================================================== */
body.ic-v2 .v2-cell-product { min-width: 260px; }
/* The bulk bar above the admin list. It is a toolbar that appears when
   something is selected, so it should read as one. */
body.ic-v2 .ic-bulk-bar {
  border-radius: 12px;
  border: 1px solid var(--t-line, #e2e8f0);
  background: var(--t-surface-2, #f7f9fc);
}
body.ic-v2 .ic-bulk-count {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
}
body.ic-v2 .ic-table-shell { border-radius: 14px; }

/* ==========================================================================
   V2.0 — the reports hub
   --------------------------------------------------------------------------
   Nineteen reports, grouped. The card is the link: Classic put a View button
   inside each tile, so the title beside it was dead and there were nineteen
   small targets instead of nineteen large ones.
   ========================================================================== */
body.ic-v2 .v2-report-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 10px;
  margin: 0 0 6px;
}
body.ic-v2 a.v2-report-card,
body.ic-v2 .content-wrapper a.v2-report-card {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 13px 15px;
  border: 1px solid var(--t-line, #e2e8f0);
  border-radius: 14px;
  background: var(--t-surface, #fff);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
  color: var(--t-ink, #2c3340);
  text-decoration: none;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
:root[data-theme="dark"] body.ic-v2 a.v2-report-card { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }
body.ic-v2 a.v2-report-card:hover,
body.ic-v2 a.v2-report-card:focus,
body.ic-v2 .content-wrapper a.v2-report-card:hover,
body.ic-v2 .content-wrapper a.v2-report-card:focus {
  border-color: var(--t-brand, #5b2c86);
  box-shadow: 0 4px 16px rgba(16, 24, 40, 0.10);
  color: var(--t-brand-text, #5b2c86);
  text-decoration: none;
}
body.ic-v2 a.v2-report-card:focus-visible { outline: 2px solid var(--t-accent, #2f80ed); outline-offset: 2px; }
body.ic-v2 .v2-report-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.3;
}
/* The arrow is decoration -- aria-hidden in the markup -- so it must not be
   the thing that says where the card goes. It is a hint that it goes
   somewhere, and it leans in on hover. */
body.ic-v2 .v2-report-go {
  flex: 0 0 auto;
  font-size: 15px;
  color: var(--t-faint, #b6c0cc);
  transition: transform 0.12s ease, color 0.12s ease;
}
body.ic-v2 a.v2-report-card:hover .v2-report-go {
  color: var(--t-brand, #5b2c86);
  transform: translateX(2px);
}

/* ==========================================================================
   V2.0 — the schedule lists
   ========================================================================== */
/* A ticket number is quoted down a phone and pasted into a search. */
body.ic-v2 .v2-ticket,
body.ic-v2 .v2-ticket a {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
}
body.ic-v2 .v2-cell-job { min-width: 170px; }
body.ic-v2 .v2-cell-who { min-width: 160px; }
body.ic-v2 .v2-cell-when { min-width: 150px; white-space: nowrap; }

/* ==========================================================================
   V2.0 — the mailbox surfaces
   --------------------------------------------------------------------------
   Two screens the shared layer could not reach, for the same reason the
   helpdesk count rail could not: they are built from bespoke components
   declared in a <style> block inside the template, not from .spx-table and
   .spx-card. So they sat in Classic under V2 chrome.

     .ic-mail    the Plesk mailbox panel on a hosting or SpamTitan record
     .ic-mbrev   the mailbox billing review

   Both declare `.mtab` -- a table class of their own -- plus panels, pills
   and a mono face. Every selector here mirrors theirs with body.ic-v2 in
   front, which is one class more, so source order never comes into it.

   The mailbox panel gets no band, deliberately. Its own comments record why
   at length: on a reseller whose bill covers a whole Plesk estate, the
   domain's mailbox count and the record's billed quantity are not the same
   kind of number, and a band that put them side by side was R448.35 a month
   behind one reasonable-looking dialog. The billing review gets one, because
   there every figure is one domain compared with itself.
   ========================================================================== */
body.ic-v2 .ic-mail .mtab th,
body.ic-v2 .ic-mbrev .mtab th {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
  opacity: 1;
  padding: 8px 10px;
  border-bottom: 1px solid var(--t-line, #e2e8f0);
}
body.ic-v2 .ic-mail .mtab td,
body.ic-v2 .ic-mbrev .mtab td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--t-line-soft, #eef2f6);
}
body.ic-v2 .ic-mail .mtab tbody tr:hover > td,
body.ic-v2 .ic-mbrev .mtab tbody tr:hover > td { background: var(--t-surface-2, #f7f9fc); }
body.ic-v2 .ic-mail tr.open > td { background: var(--t-tint-purple, #f3e8fb); }
:root[data-theme="dark"] body.ic-v2 .ic-mail tr.open > td { background: var(--t-surface-3, #1e1e1e); }

/* An address is read one character at a time when somebody is checking it
   against what a customer spelled out. */
body.ic-v2 .ic-mail .mono,
body.ic-v2 .ic-mbrev .mono {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* The drift columns. Up is money not being billed, down is money being
   billed for mail that is gone -- neither is an error, so neither shouts. */
body.ic-v2 .ic-mbrev .up { color: var(--t-danger-text, #c0392b); font-weight: 700; }
body.ic-v2 .ic-mbrev .dn { color: var(--t-tint-green-ink, #22a45d); font-weight: 700; }

/* Panels: the drawer a mailbox opens into, the two outcome boxes, the
   nothing-to-review state and the expected-mismatch bar. All the same
   object at V2 radii. */
body.ic-v2 .ic-mail .drawer,
body.ic-v2 .ic-mail .okbox,
body.ic-v2 .ic-mail .errbox,
body.ic-v2 .ic-mail .syncbar,
body.ic-v2 .ic-mbrev .none {
  border-radius: 12px;
}
body.ic-v2 .ic-mail .drawer {
  border-color: var(--t-line, #e2e8f0);
  background: var(--t-surface-2, #f7f9fc);
  padding: 15px 17px;
}
body.ic-v2 .ic-mail .drawer h4 {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
}
body.ic-v2 .ic-mail .fld label {
  font-size: 10.5px;
  letter-spacing: 0.7px;
  opacity: 1;
  color: var(--t-muted, #6b7789);
}

/* Pills, on the V2 shape. .on and .ooo keep their own colours -- they are
   the only state this table shows. */
body.ic-v2 .ic-mail .pill {
  border-radius: 999px;
  font-size: 11px;
  padding: 1px 9px;
}

body.ic-v2 .ic-mbrev .acts input[type="text"] { border-radius: 9px; }

/* ==========================================================================
   V2.0 — the Monthly run
   --------------------------------------------------------------------------
   The twelve cards on this page are the ones the stat-card block above
   deliberately does NOT touch: they carry `hue:` instead of a state tone,
   because Sean asked for each to be a different colour, and ic-theme paints
   them a saturated block with white text.

   That carve-out was about not REPAINTING them. It was never a reason to
   leave them square-cornered and differently spaced from every other tile in
   the app. So this section gives them the V2 shape and nothing else: radius,
   shadow, padding, the icon, the footer and the flag. Not one declaration
   here sets a background or a colour, which is also what keeps
   spec/views/shared/v2_stat_cards_spec.rb passing -- it fails any rule that
   paints a hue card.

   THE LABELS NOW MATCH THE REST OF V2, and they did not always. Six of the
   twelve hues used to be too light to carry white text -- lime 3.09, amber
   3.19, orange 3.56, cyan 3.68, teal 3.74, emerald 3.77, against a 4.5:1
   requirement -- so these labels were held at 13px while every other V2 tile
   used the 10.5px uppercase micro-label. Shrinking text on a background it
   already failed against would only have made it worse.

   Those six were darkened in ic-theme.css instead, by 10-20% lightness with
   hue and saturation untouched, so the twelve are still twelve
   distinguishable colours. The worst of them is now 4.53:1 and the exception
   is over, so the label joins the rest.

   spec/views/shared/v2_monthly_run_spec.rb computes those ratios from
   ic-theme rather than trusting this paragraph: it REQUIRES the 10.5px label
   while every hue clears 4.5:1, and demands 13px back the moment one does
   not. Darkening the hues is what flipped it, and the spec said so.
   ========================================================================== */
body.ic-v2 .ic-stat-card[class*="ic-hue-"],
body.ic-v2 a.ic-stat-card[class*="ic-hue-"],
body.ic-v2 .content-wrapper a.ic-stat-card[class*="ic-hue-"] {
  /* !important for the same reason as every other card here: a hue card is
     still a .small-box, and the layout pins 4px on those. The Monthly run
     has been rendering at 4px while the tiles beside it were at 14px, which
     is exactly the mismatch the shape-only carve-out was meant to remove. */
  border-radius: 14px !important;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.10);
  position: relative;
  overflow: hidden;
}
body.ic-v2 a.ic-stat-card[class*="ic-hue-"]:hover,
body.ic-v2 .content-wrapper a.ic-stat-card[class*="ic-hue-"]:hover {
  transform: none;
  box-shadow: 0 6px 18px rgba(16, 24, 40, 0.22);
}
body.ic-v2 .ic-stat-card[class*="ic-hue-"] .inner { padding: 14px 16px 12px; }
body.ic-v2 .ic-stat-card[class*="ic-hue-"] .inner h3 {
  font-size: 26px;
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.6px;
  font-variant-numeric: tabular-nums;
  margin: 0 0 3px;
}
/* The same micro-label every other V2 tile carries. Safe now: see above. */
body.ic-v2 .ic-stat-card[class*="ic-hue-"] .inner p {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  line-height: 1.35;
}

body.ic-v2 .ic-stat-card[class*="ic-hue-"] .icon,
body.ic-v2 .ic-stat-card[class*="ic-hue-"] .icon > i {
  font-size: 16px !important;
  top: 13px !important;
  right: 14px !important;
  opacity: 0.45;
}
body.ic-v2 .ic-stat-card[class*="ic-hue-"] .small-box-footer {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-align: left;
  padding: 7px 16px;
}
body.ic-v2 .ic-stat-card[class*="ic-hue-"] .ic-stat-flag {
  display: inline-block;
  margin-bottom: 5px;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

/* ---------- the run's own tables ---------------------------------------------
   Three of them: the calendar of what runs when, the queue tables shared with
   the product-change and promo sections, and the LTE reconciliation. All
   plain .table with an ic-ops-* class, so the shared .spx-table rules never
   reached them.
   -------------------------------------------------------------------------- */
body.ic-v2 .ic-ops-calendar > thead > tr > th,
body.ic-v2 .ic-ops-table > thead > tr > th,
body.ic-v2 .content-wrapper .ic-ops-calendar > thead > tr > th,
body.ic-v2 .content-wrapper .ic-ops-table > thead > tr > th {
  font-size: 10.5px !important;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
  border-bottom: 1px solid var(--t-line, #e2e8f0);
}
body.ic-v2 .content-wrapper .ic-ops-calendar > tbody > tr > td,
body.ic-v2 .content-wrapper .ic-ops-table > tbody > tr > td {
  border-top: 0;
  border-bottom: 1px solid var(--t-line-soft, #eef2f6);
}
/* When a task runs is the column you scan down, so it lines up on the digit. */
body.ic-v2 .ic-ops-when {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--t-ink-2, #334155);
}
body.ic-v2 .ic-ops-col-user,
body.ic-v2 .ic-ops-col-user a {
  font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
}
/* Bootstrap's .label is a 75%-size inline block with square corners; every
   other status marker in V2 is a pill. */
body.ic-v2 .ic-ops-table .label {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
}

/* ==========================================================================
   V2.0 — the typeface
   --------------------------------------------------------------------------
   V2 is set in IBM Plex Sans. Classic keeps Source Sans 3, so the toggle puts
   the old face back like everything else in this file.

   Why this one rather than a more neutral pick: V2 already sets every
   identifier in IBM Plex MONO -- logins, MAC addresses, domains, DID
   numbers, ticket references, IP addresses. Plex Sans is the same family, so
   a mono chip sitting inside a row of text stops reading as a foreign object
   dropped into it, and the two share digit shapes. Plex Sans is also a
   little wider than Source Sans with a larger x-height, which is what makes
   11.5px secondary text hold together instead of going thin and grey at the
   same time.

   AND IT MAKES THE MONO REAL. ic-theme.css names IBM Plex Mono in three
   places and ic-v2.css in a dozen, and until this change the layout only
   ever requested Source Sans 3 -- so every one of those identifiers has been
   rendering in whatever the machine had, Consolas on Windows and Menlo on a
   Mac. The pairing above was not a pairing at all. The <head> now asks for
   both Plex families.

   SPECIFICITY. splynx-ui pins the face in six places, five of them under
   body.spx-app, which every signed-in page carries. Two body classes do not
   stack, so each of those is mirrored here with body.ic-v2.spx-app in front
   -- one class more. The sixth is the login shell, which V2 never paints
   (ui_v2_surface? excludes pre-login), so it is left alone.
   ========================================================================== */
body.ic-v2,
body.ic-v2.spx-app,
body.ic-v2.skin-blue {
  font-family: "IBM Plex Sans", "Segoe UI", Helvetica, Arial, sans-serif;
}

/* AdminLTE sets these at one element of specificity and splynx-ui mirrors it;
   either way a body class in front is enough. */
body.ic-v2 h1, body.ic-v2 h2, body.ic-v2 h3,
body.ic-v2 h4, body.ic-v2 h5, body.ic-v2 h6,
body.ic-v2 .h1, body.ic-v2 .h2, body.ic-v2 .h3,
body.ic-v2 .h4, body.ic-v2 .h5, body.ic-v2 .h6 {
  font-family: "IBM Plex Sans", "Segoe UI", Helvetica, Arial, sans-serif;
}

body.ic-v2.spx-app .spx-title,
body.ic-v2.spx-app .content-header h1 {
  font-family: "IBM Plex Sans", "Segoe UI", Helvetica, Arial, sans-serif;
}

body.ic-v2.spx-app .btn-xs {
  font-family: "IBM Plex Sans", "Segoe UI", Helvetica, Arial, sans-serif;
}

body.ic-v2.spx-app .content-wrapper .small-box .inner h3,
body.ic-v2.spx-app .content-wrapper a.ic-stat-card .inner h3 {
  font-family: "IBM Plex Sans", "Segoe UI", Helvetica, Arial, sans-serif;
}

/* Plex Sans has real tabular figures and V2 is mostly numbers in columns:
   counts in tiles, money in tables, dates down a list. Lining them up on the
   digit rather than letting the glyph widths drift is most of what makes a
   column of numbers readable. The places that need it already ask for it --
   this is the base so a new one does not have to remember. */
body.ic-v2 .v2-stat-value,
body.ic-v2 .spx-kpi span.spx-kpi-value,
body.ic-v2 .spx-kpi b,
body.ic-v2 .spx-count,
body.ic-v2 .spx-pager-count,
body.ic-v2 .spx-count-rail-value {
  font-variant-numeric: tabular-nums;
}

/* ==========================================================================
   V2.0 — forms
   --------------------------------------------------------------------------
   Twenty-five _form.html.erb partials, every new and edit screen in the app,
   and until now the only thing V2 reached on any of them was the radius on a
   text input. They share one idiom, so this is a shared-layer job like the
   KPI strip and the stat cards -- no template touched, all twenty-five
   covered:

     .ic-form-shell     the card
     .box-header        its heading
     .ic-form-grid      a two-column grid of fields
     .form-group        label over control
     .box-footer        the save/cancel bar
     #error_explanation what you get when the save fails

   FIRST, A BUG OF MINE. splynx-ui pins `.form-control` AND `.selectize-input`
   together at 6px; the V2 rule written earlier covered only `.form-control`,
   so on the thirty-one views that use selectize the dropdowns rendered at 6px
   beside 9px text inputs. Both are covered here.
   ========================================================================== */
body.ic-v2 .selectize-input,
body.ic-v2.spx-app .selectize-input {
  border-radius: 9px !important;
  border-color: var(--t-line, #d9e0e9) !important;
  box-shadow: none !important;
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
body.ic-v2 .selectize-input.focus,
body.ic-v2.spx-app .selectize-input.focus {
  border-color: var(--t-brand, #5b2c86) !important;
  box-shadow: 0 0 0 3px rgba(91, 44, 134, 0.14) !important;
}
/* The dropdown is a menu, so it gets the menu treatment the Columns menu has. */
body.ic-v2 .selectize-dropdown {
  border-radius: 12px;
  border-color: var(--t-line, #e2e8f0);
  box-shadow: 0 12px 32px rgba(16, 24, 40, 0.14);
  overflow: hidden;
}
body.ic-v2 .selectize-dropdown .active { background: var(--t-surface-2, #f1f4f8); color: var(--t-ink, #2c3340); }
/* A chosen item in a multi-select is a pill everywhere else in V2. */
body.ic-v2 .selectize-input > .item { border-radius: 999px; }

/* ---------- what you see when the save fails -------------------------------
   #error_explanation had no visual styling at all -- two rules setting its
   width, and otherwise a bare <h2> and <ul> at browser defaults, which is a
   serif-ish heading and a bulleted list sitting on the page. It is the one
   part of a form nobody looks at until something has gone wrong, which is
   the worst moment for it to look unfinished.
   -------------------------------------------------------------------------- */
body.ic-v2 #error_explanation {
  margin: 0 0 16px;
  padding: 14px 16px;
  border: 1px solid var(--t-tint-red-ink, #b42318);
  border-radius: 12px;
  background: var(--t-tint-red, #fdeeee);
  color: var(--t-tint-red-ink, #b42318);
}
/* !important, and not for tidiness: ic-theme sets
   `.skin-blue .content-wrapper h2 { color: var(--t-ink) !important }`, and
   importance beats specificity outright -- an id selector loses to it. The
   heading rendered in ink inside a red panel until this was added. */
body.ic-v2 #error_explanation h2 {
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.2px;
  color: var(--t-tint-red-ink, #b42318) !important;
}
body.ic-v2 #error_explanation ul {
  margin: 0;
  padding-left: 18px;
}
body.ic-v2 #error_explanation li {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--t-tint-red-ink, #b42318);
}
/* The field that caused it, where Rails marks one. */
body.ic-v2 .field_with_errors .form-control,
body.ic-v2 .field_with_errors .selectize-input,
body.ic-v2 .has-error .form-control {
  border-color: var(--t-tint-red-ink, #b42318) !important;
}
body.ic-v2 .field_with_errors { display: contents; }

/* ---------- the card and its heading ---------------------------------------- */
body.ic-v2 .ic-form-shell,
body.ic-v2 .ic-table-shell {
  border-color: var(--t-line, #e2e8f0);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
}
:root[data-theme="dark"] body.ic-v2 .ic-form-shell { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }
/* 18px bold was a page title sitting inside a card. Every other section
   heading in V2 is the micro-label; this is a section heading. */
body.ic-v2 .ic-form-shell > .box-header .box-title {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
}
body.ic-v2 .ic-form-shell > .box-header {
  padding: 13px 20px 11px;
  border-bottom-color: var(--t-line-soft, #eef2f6);
}

/* ---------- fields ------------------------------------------------------------
   Not the 10.5px uppercase micro-label used on tiles and table headers. A
   field label is read once, deliberately, before typing into the thing under
   it -- and several here are long ("Additional Product", "SA DID Number").
   Uppercase at 10.5px makes a long label slower to read, not faster, so
   these match the .spx-form-table label instead: small and heavy, still
   sentence case.
   -------------------------------------------------------------------------- */
body.ic-v2 .ic-form-grid .form-group > label {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: var(--t-muted, #6b7789);
  margin-bottom: 5px;
}
body.ic-v2 .ic-form-grid .form-group .help-block {
  font-size: 11.5px;
  color: var(--t-faint, #8895a7);
  margin-top: 5px;
}
body.ic-v2 .ic-form-grid { gap: 16px 22px; }

/* A control nobody can edit should not look like one that invites it. */
body.ic-v2 .form-control[readonly],
body.ic-v2 .form-control[disabled],
body.ic-v2 .form-control:disabled {
  background: var(--t-surface-2, #f1f4f8);
  color: var(--t-muted, #6b7789);
  cursor: default;
}

/* ---------- the action bar ------------------------------------------------------
   AdminLTE leaves this a plain strip. It holds Save, so it should read as
   the end of the form rather than as more of it.
   -------------------------------------------------------------------------- */
body.ic-v2 .ic-form-shell > .box-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 13px 20px;
  background: var(--t-surface-2, #f7f9fc);
  border-top: 1px solid var(--t-line-soft, #eef2f6);
  border-radius: 0 0 14px 14px;
}
/* Bootstrap floats these; a float in a flex row lands in the wrong place. */
body.ic-v2 .ic-form-shell > .box-footer .pull-right { float: none !important; margin-left: auto; }
body.ic-v2 .ic-form-shell > .box-footer .pull-left { float: none !important; }

/* ==========================================================================
   V2.0 — forms, the other thirteen
   --------------------------------------------------------------------------
   The block above scopes the label and the action bar inside .ic-form-grid
   and .ic-form-shell. Measured after shipping it: only 12 of the 25 form
   partials use the grid and 9 use the shell, so thirteen forms -- members
   (the largest at 261 lines), crm_leads, crm_quotes, equipment, schedules,
   custom_prices, fibre_details, lte_details, monthly_payments,
   notifications, orders, topups and wireless_details -- got the control
   styling and nothing else. Their labels were untouched.

   Rendered on production to confirm it rather than inferred: equipment's
   edit form came back with nine .form-group and no .ic-form-shell or
   .ic-form-grid at all, while voips, ip_services, products and hostings had
   all three.

   So the label and the footer are keyed on what the markup actually has in
   common -- .form-group and .box-footer -- rather than on the shell that
   only a third of them use.
   ========================================================================== */
body.ic-v2 .form-group > label {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: var(--t-muted, #6b7789);
  margin-bottom: 5px;
}
body.ic-v2 .form-group .help-block {
  font-size: 11.5px;
  color: var(--t-faint, #8895a7);
  margin-top: 5px;
}

/* .box-footer is in 104 files beyond the form partials, and nearly all of
   them are the same thing under another name: add_router, change_password,
   add_special_price and the rest are forms that never used _form.html.erb.
   A handful are card footers -- the POPI notice on the reseller home -- and
   an action row is the right reading for those too.
   
   float: none plus margin-left: auto reproduces exactly what .pull-right did
   before, so nothing moves except the strip it sits on. */
body.ic-v2 .box > .box-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  background: var(--t-surface-2, #f7f9fc);
  border-top: 1px solid var(--t-line-soft, #eef2f6);
}
body.ic-v2 .box > .box-footer .pull-right { float: none !important; margin-left: auto; }
body.ic-v2 .box > .box-footer .pull-left { float: none !important; }

/* ==========================================================================
   The member portal's chrome row
   --------------------------------------------------------------------------
   NOT under body.ic-v2. This row carries the switch INTO and OUT OF V2.0, so
   it has to be visible in both -- a toggle that only renders in the version
   you are already in is a one-way door, which is the exact fault this row
   exists to fix.

   It sits at the top right of the content area, where the staff bar puts the
   same control. .icb-vtoggle itself is styled in dashboard-bento.css and is
   shared with that bar, so nothing about the switch is duplicated here.
   ========================================================================== */
.ic-member-chrome {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
  padding: 10px 16px 0;
}

@media (max-width: 767px) {
  .ic-member-chrome { padding: 8px 12px 0; }
}

@media print {
  .ic-member-chrome { display: none; }
}

/* ==========================================================================
   V2.0 — the technician portal
   --------------------------------------------------------------------------
   Technicians already had V2.0 and already had the toggle: current_user is
   overridden to return the seat's parent User, so ui_v2_surface? has let
   them through all along and the staff bar renders for them. Their portal is
   .spx-card, .spx-table and .box-body, all of which the shared layer covers.

   What it did not cover is the job card on the dashboard, and the four
   status boxes on the schedules page.

   THE SCHEDULES PAGE IS THE ACCESSIBILITY ONE. Its four small-boxes put both
   the count and the link in white on a saturated block -- the link with an
   inline `style="color: #fff"` -- and measured in a browser against the real
   AdminLTE, every one fails:

     Pending      bg-aqua    #00c0ef  2.15
     In Progress  bg-yellow  #f39c12  2.19
     Completed    bg-green   #00a65a  3.18
     Closed       bg-red     #dd4b39  4.09

   A band replaces them, which is handled in the template rather than here.

   THE JOB CARD IS NOT AN ACCESSIBILITY FIX, and the first draft of this
   comment wrongly said it was. AdminLTE forces `color: #fff !important` on
   .bg-*, but ic-theme sets `.skin-blue .content-wrapper h3 { color: ink
   !important }` and wins, so the card's title has been INK on the colour all
   along -- 7.11:1 on bg-yellow, which passes. Measured, after assuming
   otherwise.

   What this is, then, is consistency: a saturated block at the top of a
   white card is the one thing left in the portal that does not look like
   V2.0, and the tone is doing real work -- a technician scans a grid of
   cards by colour -- so it moves to the 3px bar every other V2 tile carries
   rather than being dropped. Contrast goes 7.11 to 12.69 as a side effect,
   not as the point.

   Scoped to `.box > .box-header`, which is the only place in the app that
   puts a bg-* class on a header -- technician_portal/dashboard.html.erb,
   twice. Alerts, labels and the stat cards use the same classes elsewhere
   and must not be touched.
   ========================================================================== */
body.ic-v2 .box > .box-header.bg-aqua,
body.ic-v2 .box > .box-header.bg-yellow,
body.ic-v2 .box > .box-header.bg-green,
body.ic-v2 .box > .box-header.bg-red {
  background: var(--t-surface, #fff) !important;
  color: var(--t-ink, #2c3340) !important;
  border-top: 3px solid var(--t-line, #e2e8f0);
  border-bottom: 1px solid var(--t-line-soft, #eef2f6);
  border-radius: 14px 14px 0 0;
  padding: 11px 15px 10px;
}
body.ic-v2 .box > .box-header.bg-aqua .box-title,
body.ic-v2 .box > .box-header.bg-yellow .box-title,
body.ic-v2 .box > .box-header.bg-green .box-title,
body.ic-v2 .box > .box-header.bg-red .box-title,
body.ic-v2 .box > .box-header.bg-aqua a,
body.ic-v2 .box > .box-header.bg-yellow a,
body.ic-v2 .box > .box-header.bg-green a,
body.ic-v2 .box > .box-header.bg-red a {
  color: var(--t-ink, #2c3340) !important;
  font-size: 13.5px;
  font-weight: 700;
}
/* The tone, on the bar rather than under the words. */
body.ic-v2 .box > .box-header.bg-aqua   { border-top-color: var(--t-accent-text, #2f80ed); }
body.ic-v2 .box > .box-header.bg-yellow { border-top-color: var(--t-tint-amber-ink, #e67e22); }
body.ic-v2 .box > .box-header.bg-green  { border-top-color: var(--t-tint-green-ink, #27ae60); }
body.ic-v2 .box > .box-header.bg-red    { border-top-color: var(--t-danger-text, #c0392b); }

/* So the 3px bar follows the card's corner instead of squaring it off. */
body.ic-v2 .box:has(> .box-header.bg-aqua),
body.ic-v2 .box:has(> .box-header.bg-yellow),
body.ic-v2 .box:has(> .box-header.bg-green),
body.ic-v2 .box:has(> .box-header.bg-red) { overflow: hidden; }

/* ---------- the job card's details -------------------------------------------
   Five label/value pairs per card, marked up as .form-group with a <label>.
   They are not a form -- nothing here is editable -- so they should read as
   a list of facts about the job. The general V2 form-label rule already
   sizes the label; this puts the value beside it rather than under it, which
   is what makes a card of five pairs fit on a phone without scrolling.
   -------------------------------------------------------------------------- */
body.ic-v2 .box > .box-body > .form-group {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  margin-bottom: 7px;
}
body.ic-v2 .box > .box-body > .form-group > label {
  flex: 0 0 auto;
  margin-bottom: 0;
  min-width: 96px;
}

/* ==========================================================================
   V2.0 — the member portal
   --------------------------------------------------------------------------
   The portal got the class and the toggle in the change that gave it
   shared/_member_chrome, but measuring it afterwards showed how little that
   actually bought. Rendered in both versions and compared in a browser, a
   member saw the typeface (IBM Plex Sans against Source Sans 3), a 27px page
   title against 22px, a 999px status pill against 3px, and 14px .spx-card
   corners on three of the four pages. Everything they actually look at was
   untouched:

     .ic-account-card   8px radius, no shadow -- identical in both
     .ic-record-hero    identical
     .ic-record-kicker  identical
     .ic-price          identical

   Not one of the portal's own classes appeared in this file. The home page
   -- the one a customer lands on -- was the worst of it: no .spx-card at
   all, so nothing the shared layer covers.

   EVERYTHING HERE IS SCOPED THROUGH .ic-portal, and that is not tidiness.
   .ic-record is in 45 view files, .ic-record-kicker in 31, .ic-price in 23
   and .ic-record-hero in 22 -- the staff account pages share this whole
   idiom. A rule on `body.ic-v2 .ic-price` would repaint the customer record
   and every account show page in the app. .ic-portal is a class the four
   signed-in website/ templates carry and nothing else does, so the blast
   radius is exactly the member portal.
   ========================================================================== */

/* The account card. The one thing on the portal a customer reads.

   !important on four properties, and it is not laziness. splynx-ui pins
   `body.spx-app .ic-record` twice -- border, border-radius and box-shadow at
   line 1640, then border-radius and border-color again at 2255 -- all marked
   important. Importance beats specificity, so this rule is MORE specific
   (0,3,1 against 0,2,1), loads later, and still lost: measured in a browser,
   the card came out at splynx's 8px with no shadow while every other rule in
   this block applied. Only the pinned properties are marked; the rest are
   left normal so the next person can override them the ordinary way. */
body.ic-v2 .ic-portal .ic-account-card {
  border-radius: 14px !important;
  border: 1px solid var(--t-line, #e2e8f0) !important;
  border-top: 3px solid var(--t-line, #e2e8f0) !important;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05) !important;
  overflow: hidden;
  transition: box-shadow 0.15s ease, border-top-color 0.15s ease;
}
body.ic-v2 .ic-portal .ic-account-card:hover {
  box-shadow: 0 4px 14px rgba(16, 24, 40, 0.09) !important;
}
/* Pay-now earns the only strong colour on the page, because it is the only
   thing here that costs the customer something if ignored. */
body.ic-v2 .ic-portal .ic-account-card.is-pay-now {
  border-top-color: var(--t-danger-text, #c0392b) !important;
}

body.ic-v2 .ic-portal .ic-account-list {
  display: grid;
  gap: 14px;
}

/* The label above the service name, matched to every other V2 tile label. */
body.ic-v2 .ic-record-kicker,
body.ic-v2.spx-app .ic-record-kicker {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
  margin-bottom: 2px;
}
body.ic-v2 .ic-record-hero h2,
body.ic-v2.spx-app .ic-record-hero h2 {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.2px;
  margin: 0 0 2px;
}
body.ic-v2 .ic-record-sub,
body.ic-v2.spx-app .ic-record-sub {
  font-size: 13px;
  color: var(--t-ink-2, #4a5a6a);
}

/* Tabular figures so a column of prices lines up on the decimal. */
body.ic-v2 .ic-price,
body.ic-v2.spx-app .ic-price {
  font-size: 26px;
  letter-spacing: -0.5px;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}
body.ic-v2 .ic-price-label,
body.ic-v2.spx-app .ic-price-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
}

body.ic-v2 .ic-portal .ic-empty {
  padding: 26px 8px;
  text-align: center;
  color: var(--t-muted, #6b7789);
  font-size: 14px;
}
body.ic-v2 .ic-portal .ic-portal-actions {
  margin-top: 16px;
}

/* The key/value grid on My profile and the service page. Same treatment the
   staff record already has, but reached through .ic-portal rather than by
   styling .ic-kv globally. */
body.ic-v2 .ic-record-section,
body.ic-v2.spx-app .ic-record-section {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
}

/* ==========================================================================
   V2.0 — the record idiom, everywhere rather than only the member portal
   --------------------------------------------------------------------------
   These rules were written scoped to .ic-portal, on the grounds that
   .ic-record is in 45 view files and repainting the staff pages unmeasured
   was not part of a member-portal change. The six staff record pages have
   now been measured, in both versions, rendered off production:

     members      font, title, card, table head changed -- idiom untouched
     crm_tickets  font, title, card              -- idiom untouched
     users        font, title, card, table head  -- idiom untouched
     schedules    font, title, card              -- idiom untouched
     accounts     font, title, card              -- 40 key/value rows untouched
     hostings     font and title ONLY            -- no card at all

   So the idiom is the whole of what was missing, and the scope comes off.
   crm_customers already has a V2 record shell of its own and uses none of
   these classes, so it is unaffected -- checked, not assumed.

   Mirrored with body.ic-v2.spx-app because splynx-ui pins the same elements
   through body.spx-app. Equal specificity would fall to source order, which
   ic-v2.css happens to win; the mirror means it does not have to.
   ========================================================================== */

/* The record container itself. hostings/show looked like it had no card at
   all -- zero .spx-card on the page -- but .ic-record IS its card: splynx
   styles it, at 8px with no shadow. So this is not a template change, it is
   the same surface every other V2 card has.

   !important on the three properties splynx pins on body.spx-app .ic-record
   (border, border-radius, box-shadow), for the reason recorded on the member
   portal card: importance beats specificity, and without it this rule loses
   while every rule around it applies. */
body.ic-v2 .ic-record,
body.ic-v2.spx-app .ic-record {
  border: 1px solid var(--t-line, #e2e8f0) !important;
  border-radius: 14px !important;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05) !important;
  overflow: hidden;
}

/* The key/value rows on a record. splynx makes these a 150px/1fr grid with a
   right-aligned 13px label; V2 keeps the grid and gives the label the same
   treatment every other V2 caption has. */
body.ic-v2 .ic-kv dt,
body.ic-v2.spx-app .ic-kv dt {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.7px;
  text-transform: uppercase;
  color: var(--t-muted, #6b7789);
}
body.ic-v2 .ic-kv dd,
body.ic-v2.spx-app .ic-kv dd {
  font-size: 13px;
  color: var(--t-ink, #2c3340);
}
body.ic-v2 .ic-kv,
body.ic-v2.spx-app .ic-kv {
  padding: 9px 14px;
  border-bottom: 1px solid var(--t-line-soft, #eef2f6);
}
/* The hero above them: a header, so it reads as one. */
body.ic-v2 .ic-record-hero,
body.ic-v2.spx-app .ic-record-hero {
  border-bottom: 1px solid var(--t-line, #e2e8f0);
}

/* The band sits above the notices, so it needs the same gap they have. */
body.ic-v2 .ic-portal .v2-band { margin-bottom: 14px; }

/* One column below the phone breakpoint: the hero is a flex row and at
   375px the price was being squeezed onto three lines beside the name. */
@media (max-width: 560px) {
  body.ic-v2 .ic-record-hero {
    flex-direction: column;
    gap: 12px;
  }
  body.ic-v2 .ic-price { font-size: 22px; }
}
