/*
 * --------------------------------------------------------------
 * # Project overrides
 * #
 */


/*
 * --------------------------------------------------------------
 * # 1. Tokens
 * #
 */

:root {
  /* Surfaces, from furthest back to nearest front. */
  --sf-bg: #f6f9ff;              /* the page behind everything */
  --sf-surface: #ffffff;         /* cards, header, sidebar */
  --sf-surface-2: #eef3fd;       /* hover rows, active nav, subtle fills */
  --sf-border: #e2e8f4;          /* every hairline */

  /* Text, in decreasing emphasis. */
  --sf-text-strong: #012970;     /* headings and page titles */
  --sf-text: #3d4557;            /* body copy */
  --sf-text-muted: #5a6b8c;      /* labels, captions, table headers */

  /* Accent. */
  --sf-accent: #3b4fe0;
  --sf-accent-hover: #2e3fc4;
  --sf-accent-soft: #eceffe;     /* accent as a background tint */
  --sf-accent-contrast: #ffffff; /* text ON the accent */

  /* Status colours. */
  --sf-live-fg: #0f6b3f;
  --sf-live-bg: #dcf5e7;
  --sf-live-border: #a9e2c4;

  --sf-action-fg: #8a5300;
  --sf-action-bg: #fdf0d5;
  --sf-action-border: #f0d094;

  --sf-fail-fg: #a41c25;
  --sf-fail-bg: #fce1e3;
  --sf-fail-border: #f2b5ba;

  --sf-pending-fg: #1c4fa8;
  --sf-pending-bg: #e2ecfd;
  --sf-pending-border: #b6d0f7;

  --sf-idle-fg: #4f5a6e;
  --sf-idle-bg: #e9edf3;
  --sf-idle-border: #cdd5e1;

  /* Typography. */
  --sf-font-sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --sf-font-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", "Consolas", monospace;

  /*
   * Density. One token, shifted by [data-density="compact"] below, is the whole mechanism -- table
   * cells and card bodies read it, nothing else needs to know the setting exists.
   */
  --sf-cell-y: 0.7rem;
  --sf-cell-x: 0.75rem;
  --sf-card-pad: 1.25rem;

  /* Shadows are colour too: an indigo-tinted shadow on white becomes invisible mud on near-black. */
  --sf-shadow-card: 0 0 30px rgba(1, 41, 112, 0.08);
  --sf-shadow-bar: 0 2px 20px rgba(1, 41, 112, 0.08);
}

/*
 * AUTO. The default, and the reason data-theme is ABSENT rather than computed: this query stays
 * live, so the console follows the operating system as it changes -- including when it flips at
 * sunset -- with no request and no script.
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --sf-bg: #13151b;
    --sf-surface: #1b1e26;
    --sf-surface-2: #252a35;
    --sf-border: #2f3542;

    --sf-text-strong: #f1f4fa;
    --sf-text: #d5dae4;
    --sf-text-muted: #97a1b5;

    --sf-accent: #8b99ff;
    --sf-accent-hover: #a6b1ff;
    --sf-accent-soft: #232840;
    --sf-accent-contrast: #13151b;

    --sf-live-fg: #6cd39b;
    --sf-live-bg: #10291d;
    --sf-live-border: #22563b;

    --sf-action-fg: #f0b64c;
    --sf-action-bg: #2d2312;
    --sf-action-border: #5c4519;

    --sf-fail-fg: #f5868f;
    --sf-fail-bg: #2f1417;
    --sf-fail-border: #63262c;

    --sf-pending-fg: #7fb0f7;
    --sf-pending-bg: #131f33;
    --sf-pending-border: #274566;

    --sf-idle-fg: #a2acbd;
    --sf-idle-bg: #22262f;
    --sf-idle-border: #39404e;

    --sf-shadow-card: 0 0 30px rgba(0, 0, 0, 0.45);
    --sf-shadow-bar: 0 2px 20px rgba(0, 0, 0, 0.5);
  }
}

/*
 * EXPLICIT DARK. Repeated rather than shared with the block above because a media query and an
 * attribute selector cannot be combined in one rule -- and because this one must also win on a
 * machine whose OS is set to light.
 */
:root[data-theme="dark"] {
  --sf-bg: #13151b;
  --sf-surface: #1b1e26;
  --sf-surface-2: #252a35;
  --sf-border: #2f3542;

  --sf-text-strong: #f1f4fa;
  --sf-text: #d5dae4;
  --sf-text-muted: #97a1b5;

  --sf-accent: #8b99ff;
  --sf-accent-hover: #a6b1ff;
  --sf-accent-soft: #232840;
  --sf-accent-contrast: #13151b;

  --sf-live-fg: #6cd39b;
  --sf-live-bg: #10291d;
  --sf-live-border: #22563b;

  --sf-action-fg: #f0b64c;
  --sf-action-bg: #2d2312;
  --sf-action-border: #5c4519;

  --sf-fail-fg: #f5868f;
  --sf-fail-bg: #2f1417;
  --sf-fail-border: #63262c;

  --sf-pending-fg: #7fb0f7;
  --sf-pending-bg: #131f33;
  --sf-pending-border: #274566;

  --sf-idle-fg: #a2acbd;
  --sf-idle-bg: #22262f;
  --sf-idle-border: #39404e;

  --sf-shadow-card: 0 0 30px rgba(0, 0, 0, 0.45);
  --sf-shadow-bar: 0 2px 20px rgba(0, 0, 0, 0.5);
}

/* Compact is one line, because density is one token. */
:root[data-density="compact"] {
  --sf-cell-y: 0.3rem;
  --sf-cell-x: 0.55rem;
  --sf-card-pad: 0.75rem;
}

/*
 * Hand the accent to Bootstrap as well, so btn-primary, form focus rings, .text-primary, active
 * pagination and .link-primary all move with the theme instead of staying NiceAdmin indigo on a
 * dark ground. Bootstrap composes these from the -rgb pair, so both have to be set.
 */
:root,
:root[data-theme="dark"] {
  --bs-primary: var(--sf-accent);
  --bs-link-color: var(--sf-accent);
  --bs-link-hover-color: var(--sf-accent-hover);
  --bs-border-color: var(--sf-border);
}

:root { --bs-primary-rgb: 59, 79, 224; }
:root[data-theme="dark"] { --bs-primary-rgb: 139, 153, 255; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --bs-primary-rgb: 139, 153, 255; }
}

/* AND THE BUTTONS, SEPARATELY, BECAUSE --bs-primary DOES NOT REACH THEM. */
.btn-primary {
  --bs-btn-bg: var(--sf-accent);
  --bs-btn-border-color: var(--sf-accent);
  --bs-btn-color: var(--sf-accent-contrast);
  --bs-btn-hover-bg: var(--sf-accent-hover);
  --bs-btn-hover-border-color: var(--sf-accent-hover);
  --bs-btn-hover-color: var(--sf-accent-contrast);
  --bs-btn-active-bg: var(--sf-accent-hover);
  --bs-btn-active-border-color: var(--sf-accent-hover);
  --bs-btn-active-color: var(--sf-accent-contrast);
  --bs-btn-disabled-bg: var(--sf-accent);
  --bs-btn-disabled-border-color: var(--sf-accent);
  --bs-btn-disabled-color: var(--sf-accent-contrast);
}

.btn-outline-primary {
  --bs-btn-color: var(--sf-accent);
  --bs-btn-border-color: var(--sf-accent);
  --bs-btn-hover-bg: var(--sf-accent);
  --bs-btn-hover-border-color: var(--sf-accent);
  --bs-btn-hover-color: var(--sf-accent-contrast);
  --bs-btn-active-bg: var(--sf-accent);
  --bs-btn-active-border-color: var(--sf-accent);
  --bs-btn-active-color: var(--sf-accent-contrast);
  --bs-btn-disabled-color: var(--sf-accent);
  --bs-btn-disabled-border-color: var(--sf-accent);
}

.btn-link { --bs-btn-color: var(--sf-accent); --bs-btn-hover-color: var(--sf-accent-hover); }

/*
 * btn-outline-secondary is the quiet button -- Cancel, Refresh GAM Status, the row overflow -- and
 * Bootstrap gives it a LITERAL #6c757d.
 */
/*
 * btn-outline-success is Apply Changes on the campaign page and Resume on the channel form, and
 * Bootstrap gives it the literal #198754 in both themes.
 */
.btn-outline-success {
  --bs-btn-color: var(--sf-live-fg);
  --bs-btn-border-color: var(--sf-live-border);
  --bs-btn-hover-bg: var(--sf-live-bg);
  --bs-btn-hover-border-color: var(--sf-live-fg);
  --bs-btn-hover-color: var(--sf-live-fg);
  --bs-btn-active-bg: var(--sf-live-bg);
  --bs-btn-active-border-color: var(--sf-live-fg);
  --bs-btn-active-color: var(--sf-live-fg);
  --bs-btn-disabled-color: var(--sf-live-fg);
  --bs-btn-disabled-border-color: var(--sf-live-border);
}

/* The same for red. */
/* And amber. */
.text-warning { color: var(--sf-action-fg) !important; }
.btn-outline-warning {
  --bs-btn-color: var(--sf-action-fg);
  --bs-btn-border-color: var(--sf-action-border);
  --bs-btn-hover-bg: var(--sf-action-bg);
  --bs-btn-hover-border-color: var(--sf-action-fg);
  --bs-btn-hover-color: var(--sf-action-fg);
  --bs-btn-active-bg: var(--sf-action-bg);
  --bs-btn-active-border-color: var(--sf-action-fg);
  --bs-btn-active-color: var(--sf-action-fg);
  --bs-btn-disabled-color: var(--sf-action-fg);
  --bs-btn-disabled-border-color: var(--sf-action-border);
}

.text-danger { color: var(--sf-fail-fg) !important; }
.btn-outline-danger {
  --bs-btn-color: var(--sf-fail-fg);
  --bs-btn-border-color: var(--sf-fail-border);
  --bs-btn-hover-bg: var(--sf-fail-bg);
  --bs-btn-hover-border-color: var(--sf-fail-fg);
  --bs-btn-hover-color: var(--sf-fail-fg);
  --bs-btn-active-bg: var(--sf-fail-bg);
  --bs-btn-active-border-color: var(--sf-fail-fg);
  --bs-btn-active-color: var(--sf-fail-fg);
  --bs-btn-disabled-color: var(--sf-fail-fg);
  --bs-btn-disabled-border-color: var(--sf-fail-border);
}

.btn-outline-secondary {
  --bs-btn-color: var(--sf-text-muted);
  --bs-btn-border-color: var(--sf-border);
  --bs-btn-hover-bg: var(--sf-surface-2);
  --bs-btn-hover-border-color: var(--sf-text-muted);
  --bs-btn-hover-color: var(--sf-text-strong);
  --bs-btn-active-bg: var(--sf-surface-2);
  --bs-btn-active-border-color: var(--sf-text-muted);
  --bs-btn-active-color: var(--sf-text-strong);
  --bs-btn-disabled-color: var(--sf-text-muted);
  --bs-btn-disabled-border-color: var(--sf-border);
}

/* Focus rings are the same accent, or a themed page grows a blue halo the moment anything is typed. */
.form-control:focus,
.form-select:focus,
.btn:focus-visible {
  border-color: var(--sf-accent);
  box-shadow: 0 0 0 0.2rem var(--sf-accent-soft);
}

/* Inputs and selects follow the surface, not Bootstrap's own white. */
.form-control,
.form-select {
  background-color: var(--sf-bg);
  border-color: var(--sf-border);
  color: var(--sf-text);
}

.form-control::placeholder { color: var(--sf-text-muted); }
.form-label { color: var(--sf-text-strong); }


/*
 * --------------------------------------------------------------
 * # 2. Re-pointing the vendored theme
 * #
 */

body {
  font-family: var(--sf-font-sans);
  background: var(--sf-bg);
  color: var(--sf-text);
  /* flow-root keeps #main's 60px top margin (style.css) INSIDE the body. */
  display: flow-root;
}

h1, h2, h3, h4, h5, h6,
.card-title,
.alert-heading,
.logo span,
.breadcrumb {
  font-family: var(--sf-font-sans);
}

a { color: var(--sf-accent); }
a:hover { color: var(--sf-accent-hover); }

.pagetitle h1 { color: var(--sf-text-strong); }

/* Header ------------------------------------------------------ */

.header {
  background-color: var(--sf-surface);
  box-shadow: var(--sf-shadow-bar);
}

.header .toggle-sidebar-btn,
.header .search-form button i,
.header-nav .nav-icon,
.header-nav .nav-profile {
  color: var(--sf-text-strong);
}

/* The header at phone width (SIM-56, found by SIM-57). */
@media (max-width: 575.98px) {
  /*
   * min-width: 0 is the whole fix: a flex item defaults to min-width: auto and refuses to shrink
   * below its content, which is what pushed the cluster past the viewport.
   */
  .header-nav { min-width: 0; }
  .header-nav > .pe-3 { padding-right: 0.5rem !important; }
  .header-nav > span {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

.sf-client-id { display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; }
.sf-client-id .sf-badge { min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.header .search-form input {
  color: var(--sf-text);
  background-color: var(--sf-bg);
  border: 1px solid var(--sf-border);
}

.header .search-form input::placeholder { color: var(--sf-text-muted); }

.header .search-form input:focus,
.header .search-form input:hover {
  border: 1px solid var(--sf-accent);
  box-shadow: 0 0 0 3px var(--sf-accent-soft);
}

/* The mobile search drawer hardcodes `background: white`, which is a white slab on a dark ground. */
@media (max-width: 1199px) {
  .header .search-bar {
    background: var(--sf-surface);
    box-shadow: var(--sf-shadow-bar);
  }
  .header .search-bar-show { transition: opacity 0.3s, top 0.3s; }
  .header .search-bar-show .search-form input { transition: border-color 0.3s, box-shadow 0.3s; }
}

.logo span { color: var(--sf-text-strong); }

/* Sidebar ----------------------------------------------------- */

.sidebar {
  background-color: var(--sf-surface);
  box-shadow: var(--sf-shadow-bar);
  scrollbar-color: var(--sf-border) transparent;
}

.sidebar::-webkit-scrollbar { background-color: var(--sf-surface); }
.sidebar::-webkit-scrollbar-thumb { background-color: var(--sf-border); }

.sidebar-nav .nav-heading { color: var(--sf-text-muted); }

/* Active entry: NiceAdmin marks the INACTIVE ones .collapsed, so the bare selector is the active one. */
.sidebar-nav .nav-link,
.sidebar-nav .nav-link:hover {
  color: var(--sf-accent);
  background: var(--sf-accent-soft);
}

.sidebar-nav .nav-link i,
.sidebar-nav .nav-link:hover i { color: var(--sf-accent); }

.sidebar-nav .nav-link.collapsed {
  color: var(--sf-text-strong);
  background: transparent;
}

.sidebar-nav .nav-link.collapsed i { color: var(--sf-text-muted); }

.sidebar-nav .nav-content a { color: var(--sf-text-strong); }
.sidebar-nav .nav-content a.active { color: var(--sf-accent); }
.sidebar-nav .nav-content a.active i { background-color: var(--sf-accent); }

/* Cards ------------------------------------------------------- */

.card {
  background-color: var(--sf-surface);
  box-shadow: var(--sf-shadow-card);
}

.card-header,
.card-footer {
  border-color: var(--sf-border);
  background-color: var(--sf-surface);
  color: var(--sf-text-muted);
}

.card-title { color: var(--sf-text-strong); }
.card-title span { color: var(--sf-text-muted); }
.card-body { padding: var(--sf-card-pad); }

/* Dashboard info cards. */
.dashboard .info-card h6 { color: var(--sf-text-strong); }
.dashboard .sales-card .card-icon,
.dashboard .revenue-card .card-icon,
.dashboard .customers-card .card-icon {
  color: var(--sf-accent);
  background: var(--sf-accent-soft);
}
.dashboard .filter .icon { color: var(--sf-text-muted); }
.dashboard .filter .icon:hover,
.dashboard .filter .icon:focus { color: var(--sf-accent); }
.dashboard .filter .dropdown-header h6 { color: var(--sf-text-muted); }

/* Dropdowns --------------------------------------------------- */

.dropdown-menu {
  background-color: var(--sf-surface);
  box-shadow: var(--sf-shadow-card);
  border: 1px solid var(--sf-border);
}

.dropdown-menu .dropdown-item { color: var(--sf-text); }
.dropdown-menu .dropdown-item:hover,
.dropdown-menu .dropdown-item:focus {
  background-color: var(--sf-surface-2);
  color: var(--sf-text-strong);
}
.dropdown-menu .dropdown-header { color: var(--sf-text-muted); }
.dropdown-menu .dropdown-footer a { color: var(--sf-text); }
.dropdown-menu .dropdown-divider { border-color: var(--sf-border); }
.dropdown-menu-arrow::before {
  background: var(--sf-surface);
  border-top: 1px solid var(--sf-border);
  border-left: 1px solid var(--sf-border);
}

/* Accordions and tabs ----------------------------------------- */

.accordion-item { border: 1px solid var(--sf-border); }
.accordion-button:not(.collapsed) {
  color: var(--sf-text-strong);
  background-color: var(--sf-surface-2);
}
.accordion-flush .accordion-button:not(.collapsed) { color: var(--sf-accent); }
.accordion-flush .accordion-body { color: var(--sf-text); }

.nav-tabs-bordered { border-bottom: 2px solid var(--sf-border); }
.nav-tabs-bordered .nav-link { color: var(--sf-text); }
.nav-tabs-bordered .nav-link:hover,
.nav-tabs-bordered .nav-link:focus { color: var(--sf-accent); }
.nav-tabs-bordered .nav-link.active {
  background-color: transparent;
  color: var(--sf-accent);
  border-bottom: 2px solid var(--sf-accent);
}

/* Breadcrumbs, back-to-top, footer ---------------------------- */

.breadcrumb,
.breadcrumb a,
.breadcrumb .breadcrumb-item::before { color: var(--sf-text-muted); }
.breadcrumb a:hover,
.breadcrumb .active { color: var(--sf-text-strong); }

.back-to-top {
  background: var(--sf-accent);
  box-shadow: var(--sf-shadow-card);
}
.back-to-top i,
.back-to-top:hover i { color: var(--sf-accent-contrast); }
.back-to-top:hover { background: var(--sf-accent-hover); }

/*
 * The footer is an EMPTY spacer since SIM-11 removed the template credit (partials/footer explains
 * why the element stays).
 */
.footer { border-top: 0; }

/*
 * NiceAdmin's soft badge backgrounds are six fixed pastels chosen for a white page. Re-point them at
 * the status tokens so they survive a dark ground; the meaning each one carries is SIM-9's business.
 */
.bg-primary-light  { background-color: var(--sf-pending-bg); border-color: var(--sf-pending-border); color: var(--sf-pending-fg); }
.bg-secondary-light,
.bg-dark-light     { background-color: var(--sf-idle-bg);    border-color: var(--sf-idle-border);    color: var(--sf-idle-fg); }
.bg-success-light  { background-color: var(--sf-live-bg);    border-color: var(--sf-live-border);    color: var(--sf-live-fg); }
.bg-danger-light   { background-color: var(--sf-fail-bg);    border-color: var(--sf-fail-border);    color: var(--sf-fail-fg); }
.bg-warning-light  { background-color: var(--sf-action-bg);  border-color: var(--sf-action-border);  color: var(--sf-action-fg); }
.bg-info-light     { background-color: var(--sf-pending-bg); border-color: var(--sf-pending-border); color: var(--sf-pending-fg); }


/*
 * --------------------------------------------------------------
 * # 3. Typography and density
 * --------------------------------------------------------------
 */

/* Mono for identifiers and figures. */
code, kbd, samp, pre,
.sf-mono {
  font-family: var(--sf-font-mono);
  font-variant-numeric: tabular-nums;
}

/* Bootstrap paints inline <code> in its pink (#d63384) whatever the ground. */
code, kbd, samp { color: var(--sf-text-strong); }

.table { font-variant-numeric: tabular-nums; }

.table { --bs-table-color: var(--sf-text); }
.table > :not(caption) > * > * {
  padding: var(--sf-cell-y) var(--sf-cell-x);
}

/* Forms follow the density setting too, or a Compact table sits inside a Comfortable form. */
:root[data-density="compact"] .form-control,
:root[data-density="compact"] .form-select,
:root[data-density="compact"] .btn {
  padding-top: 0.2rem;
  padding-bottom: 0.2rem;
}

.text-secondary,
.text-muted,
.form-text { color: var(--sf-text-muted) !important; }


/*
 * --------------------------------------------------------------
 * # 3b.
 */

.sf-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.18em 0.5em;
  border: 1px solid transparent;
  border-radius: 3px;
  font-family: var(--sf-font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

.sf-badge--live    { color: var(--sf-live-fg);    background: var(--sf-live-bg);    border-color: var(--sf-live-border); }
.sf-badge--action  { color: var(--sf-action-fg);  background: var(--sf-action-bg);  border-color: var(--sf-action-border); }
.sf-badge--fail    { color: var(--sf-fail-fg);    background: var(--sf-fail-bg);    border-color: var(--sf-fail-border); }
.sf-badge--pending { color: var(--sf-pending-fg); background: var(--sf-pending-bg); border-color: var(--sf-pending-border); }
.sf-badge--idle    { color: var(--sf-idle-fg);    background: var(--sf-idle-bg);    border-color: var(--sf-idle-border); }

/* Unknown is a SHAPE, not a colour. */
.sf-badge--unknown {
  color: var(--sf-text-muted);
  background: transparent;
  border: 1px dashed var(--sf-text-muted);
}

/* A badge that is also a button (the Unknown diagnosis popup) must not look like a form control. */
button.sf-badge {
  cursor: pointer;
  appearance: none;
}

button.sf-badge:hover { border-style: solid; color: var(--sf-text-strong); }

/*
 * A COUNT IS NOT A STATUS. The block member count was bg-secondary — the same grey as "Archived" —
 * so a block of eleven campaigns and a retired campaign were painted the same. Neutral, quiet, and
 * deliberately outside the grammar: nothing about a number is anyone's problem.
 */
.sf-count,
.sf-tag {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.15em 0.45em;
  border: 1px solid var(--sf-border);
  border-radius: 3px;
  background: var(--sf-surface-2);
  color: var(--sf-text-muted);
  font-family: var(--sf-font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  line-height: 1.35;
  white-space: nowrap;
}

.sf-count { font-variant-numeric: tabular-nums; }
.sf-tag { letter-spacing: 0.06em; text-transform: uppercase; }

/*
 * A DIFF IS A DIFFERENT VOCABULARY, and this is the one deliberate exception to "each colour has
 * exactly one meaning".
 */
.sf-diff--add    { color: var(--sf-live-fg);   background: var(--sf-live-bg);   border-color: var(--sf-live-border); }
.sf-diff--remove { color: var(--sf-fail-fg);   background: var(--sf-fail-bg);   border-color: var(--sf-fail-border); }
.sf-diff--change { color: var(--sf-action-fg); background: var(--sf-action-bg); border-color: var(--sf-action-border); }

/* The status cell: one primary badge, then a fixed-order strip of secondary signals. */
.sf-status {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.sf-status__primary {
  display: inline-flex;
  align-items: center;
  min-width: 5.5rem;
}

.sf-signals {
  display: grid;
  grid-template-columns: repeat(4, auto);
  align-items: center;
  gap: 0.25rem;
}

.sf-signal--pending   { grid-column: 1; }
.sf-signal--error     { grid-column: 2; }
.sf-signal--creatives { grid-column: 3; }
.sf-signal--remote    { grid-column: 4; }


/*
 * --------------------------------------------------------------
 * # 3c. Data tables and row actions (SIM-10)
 * --------------------------------------------------------------
 */

/* STICKY COLUMN HEADERS. */
.table > thead th {
  position: sticky;
  top: 60px;
  z-index: 2;
  background: var(--sf-surface);
  box-shadow: inset 0 -1px 0 var(--sf-border);

  /*
   * And the type. Column headers are labels, not prose: small, mono, and quiet enough to recede
   * behind the data they name. Kept in this one rule rather than a second .table > thead th block --
   * one selector with two homes is how a later edit lands in the block that does not win.
   */
  font-family: var(--sf-font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sf-text-muted);
}

/*
 * A sortable header is a .btn (partials/data-table) sitting inside the th, and Bootstrap's .btn
 * carries its own font-size (1rem) and text-transform (none).
 */
.table > thead th > .btn {
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
}

/* THE 60px IS THE TOP BAR'S, AND ONLY THE VIEWPORT HAS A TOP BAR. */
.modal .table > thead th,
.table-responsive .table > thead th { top: 0; }

/* The comparison tables in the Pending Changes popup. */
.sf-diff-table { table-layout: fixed; }
.sf-diff-table > :not(caption) > * > * { overflow-wrap: anywhere; }

/* A creative's full ad in the Pending Changes popup: every field it sends, the tag code as code. */
.sf-pc-sent { margin-top: .4rem; }
.sf-pc-sent > summary { cursor: pointer; width: fit-content; font-size: .82rem; color: var(--sf-accent); }
.sf-pc-sent > summary:hover, .sf-pc-sent > summary:focus-visible { text-decoration: underline; }
.sf-pc-fields { width: 100%; margin-top: .4rem; border-collapse: collapse; table-layout: fixed; font-size: .8rem; }
.sf-pc-fields th, .sf-pc-fields td { padding: .3rem .4rem; border-top: 1px solid var(--sf-border); vertical-align: top;
  text-align: left; overflow-wrap: anywhere; }
.sf-pc-fields th { width: 32%; font-weight: 500; color: var(--sf-text-muted); }
.sf-pc-fields th code { display: block; font-size: .72rem; font-weight: 400; color: var(--sf-text-muted); }
.sf-pc-fields pre { margin: 0; max-height: 11rem; overflow: auto; white-space: pre-wrap; word-break: break-all;
  font: .74rem/1.4 var(--sf-font-mono); padding: .35rem .5rem; border: 1px solid var(--sf-border); border-radius: 4px;
  background: var(--sf-surface-2); color: var(--sf-text); }
.sf-pc-thumb { display: block; width: 72px; height: 96px; margin-bottom: .25rem; object-fit: contain;
  border: 1px solid var(--sf-border); border-radius: 4px; background: var(--sf-surface-2); }
.sf-pc-was { display: block; margin-top: .15rem; color: var(--sf-text-muted); text-decoration: line-through; }
.sf-pc-flag { margin-left: .35rem; font-size: .7rem; }
.sf-pc-arrow { margin: 0 .3rem; color: var(--sf-text-muted); }
@media (max-width: 575.98px) {
  .sf-pc-fields, .sf-pc-fields tbody, .sf-pc-fields tr, .sf-pc-fields th, .sf-pc-fields td { display: block; width: 100%; }
  .sf-pc-fields td { border-top: 0; padding-top: 0; }
}

/* The block header rows on the campaigns list are .table-light and must not out-paint the header. */
.table > tbody > tr.table-light > * {
  --bs-table-bg: var(--sf-surface-2);
  --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075);
  --bs-table-hover-color: var(--bs-emphasis-color);
  --bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1);
  --bs-table-active-color: var(--bs-emphasis-color);
  color: var(--sf-text-strong);
}

/* The empty state. Every table gets one. */
.sf-empty {
  padding: 2rem 1rem !important;
  text-align: center;
  color: var(--sf-text-muted);
}

.sf-empty i { display: block; font-size: 1.5rem; margin-bottom: 0.35rem; opacity: 0.6; }

/* NO SCROLL WRAPPER AROUND THESE TABLES, and that is a decision rather than an omission. */

/* Row action buttons. */
.btn-row {
  --bs-btn-padding-y: 0.1rem;
  --bs-btn-padding-x: 0.5rem;
  --bs-btn-font-size: 0.75rem;
}

/* ONE CONTEXTUAL ACTION, THEN AN OVERFLOW. */
.sf-actions {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  justify-content: flex-end;
  flex-wrap: nowrap;
}

.sf-actions .dropdown-menu { min-width: 11rem; }

.sf-actions .dropdown-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8125rem;
}

.sf-actions .dropdown-item.text-danger:hover { color: var(--sf-fail-fg); background: var(--sf-fail-bg); }

/* The overflow trigger is an affordance, not a statement: quiet until reached for. */
.sf-actions__more {
  --bs-btn-padding-y: 0.1rem;
  --bs-btn-padding-x: 0.4rem;
  --bs-btn-font-size: 0.75rem;
  color: var(--sf-text-muted);
  border-color: var(--sf-border);
}

.sf-actions__more:hover,
.sf-actions__more:focus { color: var(--sf-text-strong); border-color: var(--sf-text-muted); }

/* A reading measure, for the one thing on a data page that gets worse with more room. */
.sf-prose { max-width: 78ch; }


/*
 * --------------------------------------------------------------
 * # 3d. Page header and sidebar (SIM-11)
 * --------------------------------------------------------------
 */

/* THE PAGE HEADER, IN ONE PLACE. */
.sf-page-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  /*
   * The title's own bottom margins (style.css 10px, Bootstrap's breadcrumb 1rem), moved onto the header (SIM-44). Inside
   * the title they sat above the actions whenever a long title pushed the actions onto a line of their own, which then
   * touched whatever followed. Padding rather than margin, so the page below is spaced exactly as it was.
   */
  padding-bottom: 26px;
}

.sf-page-header .pagetitle,
.sf-page-header .pagetitle .breadcrumb { margin-bottom: 0; }

.sf-page-header__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/*
 * The delivery-refresh control in a page header: the age of the stored snapshot, the Refresh button,
 * and the note the button answers with.
 */
.sf-refresh {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.sf-refresh__age {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.78rem;
  color: var(--sf-text-muted);
  white-space: nowrap;
}

/* The one-line answer to a click: started, or already running. Never both, never neither. */
.sf-refresh__note {
  font-size: 0.78rem;
  color: var(--sf-text-muted);
}

/* SIM-40 -- the campaigns list as the design reads it. */
.sf-page-header__lead { margin: 0.15rem 0 0; font-size: 0.85rem; color: var(--sf-text-muted); }

.sf-readonly-note {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0 0 1rem;
  font-size: 0.8rem;
  color: var(--sf-text-muted);
}

.sf-cell-title { font-weight: 600; }
.sf-cell-caption { margin-top: 0.15rem; font-size: 0.75rem; color: var(--sf-text-muted); overflow-wrap: anywhere; }

/* The Local config cell: a word or a badge, and the No ads chip beside it, on one line. */
.sf-local-config { white-space: nowrap; }
.sf-local-config > span + span { margin-left: 0.35rem; }

.sf-list-tools {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0 0 0.75rem;
  font-size: 0.8rem;
  color: var(--sf-text-muted);
}

.sf-list-tools__toggle { font-size: 0.8rem; }

/* The campaign group header (SIM-40), one full-width band matching the Variant 1 design. */
.sf-campaign-group {
  gap: 0.5rem 1rem;
  flex-wrap: wrap;
  padding: 0.55rem 0.9rem;
  background: var(--sf-surface-2);
  border: 0;
  text-align: left;
  color: var(--sf-text);
  cursor: pointer;
}
.sf-campaign-group:hover { background: var(--sf-surface-2); color: var(--sf-text); text-decoration: none; }
.sf-campaign-group:focus-visible { outline: 2px solid var(--sf-accent); outline-offset: -2px; }
.sf-campaign-group .campaign-block-caret { flex: none; color: var(--sf-accent); }
/* Caret state: points right when the block is closed, down when it is open. */
.campaign-block-toggle .campaign-block-caret { transition: transform .15s ease-in-out; }
.campaign-block-toggle[aria-expanded="true"] .campaign-block-caret { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .campaign-block-toggle .campaign-block-caret { transition: none; } }

.sf-campaign-group__id {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3rem 0.55rem;
  flex-wrap: wrap;
  min-width: 0;
}
.sf-campaign-group__summary {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--sf-text-muted);
}
/* In the header the aggregate badge sits among muted text, so it must not reserve a column's width. */
.sf-campaign-group__summary .sf-status__primary { min-width: 0; }

.sf-group-name { font-weight: 600; color: var(--sf-text-strong); overflow-wrap: anywhere; }
.sf-group-count { font-size: 0.78rem; color: var(--sf-text-muted); font-variant-numeric: tabular-nums; }
.sf-group-agency {
  font-size: 0.78rem;
  color: var(--sf-text-muted);
  border-left: 1px solid var(--sf-border);
  padding-left: 0.55rem;
}
.sf-group-breakdown { color: var(--sf-text-muted); overflow-wrap: anywhere; }

/*
 * Grouped campaign identities get a subtle token-coloured vertical guide and a controlled indent, so
 * a member reads as belonging to the band above it.
 */
.sf-member {
  border-left: 2px solid var(--sf-border);
  padding-left: 0.85rem;
  margin-left: 0.35rem;
}

/* The Campaigns table's own type scale (SIM-40). */
.campaigns .table > tbody > tr > td { font-size: 0.875rem; vertical-align: top; }
.campaigns .table > thead th { font-size: 0.75rem; }
.campaigns .sf-cell-title { font-size: 0.875rem; font-weight: 600; overflow-wrap: anywhere; }
.campaigns .sf-cell-caption { font-size: 0.75rem; overflow-wrap: anywhere; }

/* Numbers and dates: the sans face with tabular figures, so a column compares down the page. */
.sf-num { font-variant-numeric: tabular-nums; white-space: nowrap; }
/*
 * The value inside such a cell, when the deferred sweep replaces it out of band (SIM-40 item 8). The
 * swap is outerHTML, so the replacement span carries its own figure treatment rather than relying on
 * inheriting from the td it lands in.
 */
.sf-num__value { font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Identity gets the flexible space; the other columns hold their share and wrap rather than clip. */
.sf-campaign-cell { min-width: 12rem; }
/* A row that opens its name's link wherever it is clicked (SIM-108, main.js) says so under the pointer. */
tr:has(a[data-row-link]) { cursor: pointer; }
.sf-state-cell .sf-status__primary { min-width: 0; }
/* Local config wraps its signals instead of forcing a wide nowrap column. */
.sf-local-config { white-space: normal; }
.sf-local-config > span { display: inline-block; margin: 0 0.35rem 0.15rem 0; }

/* One control size in the row, so Check reads as a peer of the primary rather than a thin link. */
.campaigns .sf-actions .btn-row { min-height: 32px; display: inline-flex; align-items: center; }

.sf-list-footer {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  padding: 0.75rem 0.5rem 0;
  border-top: 1px solid var(--sf-border);
  font-size: 0.78rem;
  color: var(--sf-text-muted);
}

.sf-page-note {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin-top: 0.75rem;
  font-size: 0.78rem;
  color: var(--sf-text-muted);
}

.sf-page-note i { flex: none; margin-top: 0.1rem; }

/* The empty PAGE, as opposed to an empty tab: a heading, a sentence and the one useful control. */
.sf-empty--page h2 { margin: 0.25rem 0 0.35rem; font-size: 1.05rem; color: var(--sf-text-strong); }
.sf-empty--page p { margin: 0 0 0.9rem; }

/* TWO LOADING STATES, AND THEY MEAN DIFFERENT THINGS (SIM-40). */
.sf-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 2.25rem 1rem;
  color: var(--sf-text-muted);
  font-size: 0.85rem;
}

/* One item of a list loading on its own, under its own heading. */
.sf-loading--inline { justify-content: flex-start; padding: 0.25rem 0; }

.sf-refreshing {
  display: none;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
  font-size: 0.8rem;
  color: var(--sf-text-muted);
}

.sf-refreshable.htmx-request > .sf-refreshing { display: flex; }

/*
 * The dim applies to the CONTENT, never to the refreshing line -- an indicator that fades with the
 * thing it describes is the one element that must stay legible. Hence a child selector pair rather
 * than opacity on the container itself.
 */
.sf-refreshable.htmx-request > *:not(.sf-refreshing) {
  opacity: 0.5;
  pointer-events: none;
  transition: opacity 0.1s linear;
}

/* A button whose own request is in flight swaps its label: htmx puts htmx-request on the button. */
.sf-btn-busy { display: none; }
.htmx-request > .sf-btn-busy { display: inline-flex; align-items: center; gap: 0.4rem; }
.htmx-request > .sf-btn-idle { display: none; }

/* A THIRD STATE: A WRITE ON ITS WAY (SIM-106). */
.sf-line-busy {
  display: none;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--sf-pending-fg);
}
tr:has(.sf-line-pause.htmx-request) .sf-line-busy--pause,
tr:has(.sf-line-resume.htmx-request) .sf-line-busy--resume,
tr:has(.sf-line-archive.htmx-request) .sf-line-busy--archive,
.sf-line-controls:has(.sf-line-pause.htmx-request) .sf-line-busy--pause,
.sf-line-controls:has(.sf-line-resume.htmx-request) .sf-line-busy--resume {
  display: inline-flex;
}
tr:has(.sf-line-pause.htmx-request, .sf-line-resume.htmx-request, .sf-line-archive.htmx-request) .sf-delivery .sf-status,
tr:has(.sf-line-pause.htmx-request, .sf-line-resume.htmx-request, .sf-line-archive.htmx-request) .sf-delivery .sf-activation-note {
  display: none;
}

/*
 * Pause campaign and Resume campaign (SIM-194, option C): the row the run is asking SimpleFeed about right now says so in
 * its Delivery cell, with the same label a row's own Pause shows. Rendered by the server, not keyed on a request, because
 * the run belongs to a job and the table is redrawn as it moves; readers see it too.
 */
.sf-line-busy.sf-run-busy { display: inline-flex; }
tr:has(.sf-run-busy) .sf-delivery .sf-status,
tr:has(.sf-run-busy) .sf-delivery .sf-activation-note { display: none; }

/* The run's answer panel: its progress bar, and the Pause again button beside the link back to the campaign. */
.sf-run-progress { height: 0.375rem; margin-top: 0.5rem; }
.sf-run-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem; margin-top: 0.5rem; }
.sf-run-actions:not(:has(*)) { display: none; }
.sf-run-link { font-size: 0.8125rem; }

/* The workbook import's three steps, the current one in body ink; the rest in muted. */
.sf-stepper { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 1rem; font-size: 0.8rem; color: var(--sf-text-muted); }
.sf-stepper b { color: var(--sf-text-strong); font-weight: 600; }

/* The bottom jobs section: a title with its count under it, then the table and the footer line. */
.sf-jobs__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 0.75rem; }
.sf-jobs td { vertical-align: top; }

@media (max-width: 736px) {
  .sf-campaign-group__summary { width: 100%; margin-left: 0; padding-top: 0.25rem; }
}

/* The deployment version, at the end of the sidebar. */
.sidebar-footer {
  margin-top: 1.5rem;
  padding: 0.5rem 0 0 15px;
  border-top: 1px solid var(--sf-border);
  font-family: var(--sf-font-mono);
  font-size: 11px;
  color: var(--sf-text-muted);
}


/*
 * --------------------------------------------------------------
 * # 4. The appearance control
 * #
 */

.sf-appearance-toggle {
  border: 0;
  background: none;
  padding: 0 0.25rem;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--sf-text-strong);
}

.sf-appearance-toggle:hover,
.sf-appearance-toggle:focus { color: var(--sf-accent); }

.sf-appearance-menu { min-width: 12rem; }

.sf-appearance-menu .dropdown-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.875rem;
}

/* Which option is current is answered ENTIRELY by the root attribute, in CSS. */
.sf-appearance-check {
  margin-left: auto;
  visibility: hidden;
  color: var(--sf-accent);
}

:root:not([data-theme]) .sf-appearance-menu [data-set-theme="auto"] .sf-appearance-check,
:root[data-theme="light"] .sf-appearance-menu [data-set-theme="light"] .sf-appearance-check,
:root[data-theme="dark"] .sf-appearance-menu [data-set-theme="dark"] .sf-appearance-check,
:root:not([data-density="compact"]) .sf-appearance-menu [data-set-density="comfortable"] .sf-appearance-check,
:root[data-density="compact"] .sf-appearance-menu [data-set-density="compact"] .sf-appearance-check {
  visibility: visible;
}


/*
 * --------------------------------------------------------------
 * # 5. Fragments that only appear mid-action
 * #
 */

.swal2-popup {
  background: var(--sf-surface);
  color: var(--sf-text);
  font-family: var(--sf-font-sans);
}

.swal2-title { color: var(--sf-text-strong); }
.swal2-html-container { color: var(--sf-text); }

.swal2-container .swal2-confirm.btn,
.swal2-container .swal2-cancel.btn { font-family: var(--sf-font-sans); }

/*
 * THESE SELECTORS WERE DEAD, AND HAD BEEN FOR AS LONG AS THE CURRENT VENDOR BUNDLE (found while
 * bringing /publishers into the browser suite, SIM-47).
 */
.datatable-top,
.datatable-bottom { color: var(--sf-text-muted); font-size: 0.8rem; }

.datatable-input,
.datatable-selector {
  background-color: var(--sf-bg);
  border: 1px solid var(--sf-border);
  border-radius: 6px;
  color: var(--sf-text);
}

.datatable-pagination a,
.datatable-pagination button { color: var(--sf-accent); border-radius: 6px; }
.datatable-pagination a:hover,
.datatable-pagination button:hover { background-color: var(--sf-surface-2); }
/*
 * The :hover / :focus variants are restated because the vendor's own active-hover selectors are more
 * specific and put #d9d9d9 back under the text -- which left white on light grey the moment an
 * operator's pointer crossed the page they were already on.
 */
.datatable-pagination .datatable-active a,
.datatable-pagination .datatable-active a:hover,
.datatable-pagination .datatable-active a:focus,
.datatable-pagination .datatable-active button,
.datatable-pagination .datatable-active button:hover,
.datatable-pagination .datatable-active button:focus {
  background-color: var(--sf-accent);
  color: var(--sf-accent-contrast);
}

/* AND IT HAS TO FIT A PHONE (SIM-47, measured by the SIM-57 browser suite). */
.datatable-top,
.datatable-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.9rem;
  padding: 0.35rem 0;
}
.datatable-top > *,
.datatable-bottom > * { float: none !important; min-width: 0; }
.datatable-top > *:last-child,
.datatable-bottom > *:last-child { margin-left: auto; }
.datatable-input { max-width: 100%; }
/* The pager is a floated list inside that row; it wraps too, or it is the next thing to overflow. */
.datatable-pagination ul { display: flex; flex-wrap: wrap; }
.datatable-pagination li { float: none !important; }

@media (max-width: 575.98px) {
  /* Below this the two clusters stop competing for one line and simply stack. */
  .datatable-top > *:last-child,
  .datatable-bottom > *:last-child { margin-left: 0; }
}

/* THE SORT ARROWS NEED ROOM OF THEIR OWN (SIM-182). */
.datatable-sorter { padding-left: 0; padding-right: 16px; }
.datatable-sorter::before { border-top-color: currentColor; }
.datatable-sorter::after { border-bottom-color: currentColor; top: auto; bottom: 11px; }
.datatable-sorter::before,
.datatable-sorter::after { opacity: 0.35; }
.datatable-ascending .datatable-sorter::after,
.datatable-descending .datatable-sorter::before { opacity: 1; }

/* Bootstrap's own modal backdrop is fine; its header/footer rules are not, on a themed surface. */
.modal-content { background-color: var(--sf-surface); }
.modal-header,
.modal-footer { border-color: var(--sf-border); }
.modal-title { color: var(--sf-text-strong); }

/* A STALE ANSWER MUST NOT POSE AS THE NEW ONE. */
#pending-changes-body.htmx-request > .modal-footer,
#pending-changes-body.htmx-request > .modal-header small,
#pending-changes-body.htmx-request > .modal-body > *,
#gam-status-body.htmx-request > .modal-footer,
#gam-status-body.htmx-request > .modal-header small,
#gam-status-body.htmx-request > .modal-body > * { display: none; }

#pending-changes-body.htmx-request > .modal-body,
#gam-status-body.htmx-request > .modal-body {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--sf-text-muted);
}

#pending-changes-body.htmx-request > .modal-body::before,
#gam-status-body.htmx-request > .modal-body::before,
#agency-dialog-body.htmx-request::before,
#publisher-dialog-body.htmx-request::before,
#s2s-partner-dialog-body.htmx-request::before,
#account-dialog-body.htmx-request::before,
#report-dialog-body.htmx-request::before,
#forecast-dialog-body.htmx-request::before {
  content: "";
  flex: none;
  width: 1rem;
  height: 1rem;
  border: 0.2em solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spinner-border 0.75s linear infinite;
}

#pending-changes-body.htmx-request > .modal-body::after { content: "Reading this campaign from SimpleFeed..."; }
#gam-status-body.htmx-request > .modal-body::after { content: "Asking the publisher about this channel..."; }

/*
 * The agency (SIM-48), publisher (SIM-73), S2S partner (SIM-49), account (SIM-52) and report (SIM-43) dialogs have the same
 * shell problem in a sharper form: the previous content can be a FORM.
 */
#agency-dialog-body.htmx-request > *,
#publisher-dialog-body.htmx-request > *,
#s2s-partner-dialog-body.htmx-request > *,
#account-dialog-body.htmx-request > *,
#report-dialog-body.htmx-request > *,
#forecast-dialog-body.htmx-request > * { display: none; }

#agency-dialog-body.htmx-request,
#publisher-dialog-body.htmx-request,
#s2s-partner-dialog-body.htmx-request,
#account-dialog-body.htmx-request,
#report-dialog-body.htmx-request,
#forecast-dialog-body.htmx-request {
  flex-direction: row;
  align-items: center;
  gap: 0.6rem;
  padding: var(--sf-card-pad);
  color: var(--sf-text-muted);
}

#agency-dialog-body.htmx-request::after,
#publisher-dialog-body.htmx-request::after,
#s2s-partner-dialog-body.htmx-request::after,
#account-dialog-body.htmx-request::after,
#report-dialog-body.htmx-request::after,
#forecast-dialog-body.htmx-request::after { content: "Loading..."; }

/* A row's details: the full-width row a list opens from the row's own button. */
.sf-row-details {
  display: grid;
  gap: 1rem;
  padding: 0.9rem 1rem;
  background: var(--sf-surface-2);
  border-top: 1px solid var(--sf-border);
}
.sf-stack-table .sf-row-details { flex: 1 1 100%; text-align: left; }


/*
 * --------------------------------------------------------------
 * # 6. Long-standing project rules
 * --------------------------------------------------------------
 */

/* Sticky form cards must pin BELOW the top bar, never over it. */
.card.sticky-top {
  top: 60px;
  z-index: 996;
}

/* The image upload drop target. */
.upload-drop-zone {
  border: 2px dashed var(--sf-border);
  transition: background-color .15s ease-in-out, border-color .15s ease-in-out;
}

.upload-drop-zone-active {
  border-color: var(--sf-accent);
  background-color: var(--sf-accent-soft);
}

/*
 * --------------------------------------------------------------
 * # 7. The header activity indicator (SIM-15)
 * --------------------------------------------------------------
 */

/*
 * A link to the Activity page that lives beside the appearance switch. Quiet while nothing runs --
 * a muted glyph -- and carries the running count in the PENDING variant while something does, so
 * background progress is visible from every page instead of only the one that started it.
 */
.sf-activity-indicator {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  color: var(--sf-text-muted);
  text-decoration: none;
  font-size: 1.1rem;
}
.sf-activity-indicator:hover,
.sf-activity-indicator--busy { color: var(--sf-accent); }

/*
 * --------------------------------------------------------------
 * # 8. The campaign page (SIM-13): summary strip and the assets rail
 * --------------------------------------------------------------
 */

/*
 * The summary strip: the facts that used to be scattered across badges inside the table, in one line
 * under the title. Label over value, mono for identifiers and figures, and it wraps rather than
 * scrolls -- on a narrow window the items simply take a second line.
 */
.sf-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 2rem;
  padding: 0.75rem 1rem;
  margin-bottom: 1rem;
  background: var(--sf-surface);
  border: 1px solid var(--sf-border);
  border-radius: 0.5rem;
}
.sf-summary__item { display: flex; flex-direction: column; gap: 0.1rem; min-width: 6rem; }
.sf-summary__label {
  font-family: var(--sf-font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sf-text-muted);
}
.sf-summary__value { font-size: 0.9rem; color: var(--sf-text-strong); }

/*
 * Where the creatives are, said twice: the summary strip counts them and links to the library, and
 * the campaign edit form carries the same note.
 */
#creatives { scroll-margin-top: 76px; }

.sf-form-note {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--sf-border);
  border-radius: var(--bs-border-radius);
  background: var(--sf-surface-2);
  font-size: 0.9rem;
}

.sf-form-note i { color: var(--sf-text-muted); }

/*
 * --------------------------------------------------------------
 * # The sign-in page (SIM-38), and the system message page in the same frame (SIM-54)
 * #
 */
.sf-signin {
  min-height: 100vh;
  /*
   * Where supported, the dynamic viewport height: on a phone the toolbar-hidden 100vh overshoots
   * the visible screen and puts the centred card slightly low, plus a stray scroll.
   */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--sf-bg);
  color: var(--sf-text);
  font-family: var(--sf-font-sans);
}

.sf-signin__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1rem;
  flex-wrap: wrap;
  padding: 1rem 1.375rem;
  background: var(--sf-surface);
  border-bottom: 1px solid var(--sf-border);
}

.sf-signin__brand {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--sf-text-strong);
  text-decoration: none;
  white-space: nowrap;
}

/* The real asset at 26 CSS px, proportions kept; never redrawn or recoloured for a theme. */
.sf-signin__brand img { display: block; width: 26px; height: 26px; object-fit: contain; flex: none; }

.sf-signin__appearance {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: 0.8rem;
  color: var(--sf-text-muted);
}

.sf-signin__appearance .form-select { width: auto; min-height: 2.125rem; }

/* The card is centred in the space under the bar, both ways. */
.sf-signin__main {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 2.375rem 1.5rem 2.5rem;
}

.sf-signin__card {
  width: 100%;
  max-width: 420px;
  padding: 1.75rem 2rem;
  background: var(--sf-surface);
  border: 1px solid var(--sf-border);
  border-radius: 8px;
}

.sf-signin__eyebrow { margin: 0 0 0.5rem; font-size: 0.75rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--sf-text-muted); }
.sf-signin__title { margin: 0 0 0.5rem; font-size: 1.7rem; font-weight: 600; letter-spacing: -0.02em; line-height: 1.25; color: var(--sf-text-strong); }
.sf-signin__subtitle { margin: 0; font-size: 0.85rem; color: var(--sf-text-muted); }

/* The server's verdict, above both sign-in methods. Icon plus text, so colour is not the only cue. */
.sf-signin__message {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 1.25rem 0 0;
  padding: 0.75rem;
  border: 1px solid var(--sf-fail-border);
  border-left: 3px solid var(--sf-fail-fg);
  border-radius: 5px;
  background: var(--sf-fail-bg);
  color: var(--sf-text-strong);
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}
.sf-signin__message i { flex: none; margin-top: 1px; color: var(--sf-fail-fg); }
.sf-signin__message p { margin: 0; }

.sf-signin__sso { margin-top: 1.45rem; }

.sf-signin__google,
.sf-signin__submit,
.sf-signin__action {
  width: 100%;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-weight: 500;
}

.sf-signin__note { margin: 0.55rem 0 0; font-size: 0.78rem; color: var(--sf-text-muted); }

/* Google not configured: the reason sits in a quiet box under the disabled control, in body ink. */
.sf-signin__note--unavailable {
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--sf-border);
  border-radius: 5px;
  background: var(--sf-surface-2);
  color: var(--sf-text);
}

/*
 * The username and password sit behind one link: the page offers Google as the one obvious method,
 * and the credentials slide down only when asked for. Bootstrap's collapse does the sliding from its
 * data attributes; the chevron turns on aria-expanded, which Bootstrap keeps current on the link.
 */
.sf-signin__alt { margin-top: 1.2rem; text-align: center; }
.sf-signin__more {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--sf-accent);
  text-decoration: none;
}
.sf-signin__more:hover,
.sf-signin__more:focus { color: var(--sf-accent-hover); text-decoration: underline; }
.sf-signin__more .bi { font-size: 0.7rem; transition: transform 0.2s ease; }
.sf-signin__more[aria-expanded="true"] .bi { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .sf-signin__more .bi { transition: none; } }

.sf-signin__form { margin-top: 1.1rem; padding-top: 1.25rem; border-top: 1px solid var(--sf-border); }
.sf-signin__section { margin: 0 0 0.25rem; font-size: 0.9rem; font-weight: 500; color: var(--sf-text-strong); }
.sf-signin__form .sf-signin__note { margin: 0 0 1.05rem; }
.sf-signin__field { margin-bottom: 0.95rem; }
.sf-signin__field .form-label { margin-bottom: 0.4rem; font-size: 0.82rem; font-weight: 500; color: var(--sf-text); }
.sf-signin__field .form-control { min-height: 43px; }

.sf-signin__label-row { display: flex; justify-content: space-between; align-items: center; gap: 0.6rem; min-height: 1.5rem; margin-bottom: 0.4rem; }
.sf-signin__label-row .form-label { margin-bottom: 0; }

.sf-signin__reveal {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.15rem 0;
  font-size: 0.78rem;
  color: var(--sf-accent);
  text-decoration: none;
}
.sf-signin__reveal:hover,
.sf-signin__reveal:focus { color: var(--sf-accent-hover); text-decoration: underline; }

.sf-signin__submit { margin-top: 0.45rem; }

.sf-signin__help { margin-top: 1.5rem; padding-top: 1.2rem; border-top: 1px solid var(--sf-border); text-align: center; font-size: 0.78rem; color: var(--sf-text-muted); }
.sf-signin__help p { margin: 0; }
.sf-signin__help p + p { margin-top: 0.2rem; color: var(--sf-text); }

/*
 * The system message page (text-page, SIM-54) in this frame: the message in body ink rather than the
 * subtitle's muted grey, since it is the whole page, and one link out, as tall as the buttons above.
 * A long version string or an unbroken code wraps inside the card instead of widening it.
 */
.sf-signin__text { margin: 0.75rem 0 0; color: var(--sf-text); overflow-wrap: anywhere; }
.sf-signin__action { margin-top: 1.5rem; }

@media (max-width: 560px) {
  .sf-signin__bar { padding: 0.875rem 1rem; }
  .sf-signin__brand { font-size: 1.05rem; }
  .sf-signin__main { padding: 2rem 1rem 2.375rem; }
  .sf-signin__card { padding: 1.5rem 1.375rem; }
  /* 16px inputs keep iOS from zooming the page on focus. */
  .sf-signin__field .form-control { font-size: 1rem; }
}

@media (max-width: 400px) {
  /* The select is labelled for assistive technology; only the visible word gives way. */
  .sf-signin__appearance > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
}

@media (pointer: coarse) {
  .sf-signin__reveal { min-height: 44px; padding: 0.5rem 0; }
  .sf-signin__appearance .form-select { min-height: 44px; }
}

/* The persistent apply error: the FAIL pair, complete text, one line per channel. */
.sf-summary__error {
  margin-bottom: 1rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--sf-fail-border);
  border-left-width: 4px;
  border-radius: 0.5rem;
  background: var(--sf-fail-bg);
  color: var(--sf-fail-fg);
}
.sf-summary__error-text { white-space: pre-wrap; font-size: 0.85rem; margin-top: 0.25rem; }

/* The summary strip's health half is set off from its identity half. */
.sf-summary__item--divider { border-left: 1px solid var(--sf-border); padding-left: 2rem; }

/* The apply-result panel: who failed and why, one line each, the diagnostics under a fold. */
.sf-summary__error-lead { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; }
.sf-summary__failures { list-style: none; margin: 0.5rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 0.35rem; }
.sf-summary__failures li { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.sf-summary__failure-who { font-weight: 600; min-width: 7rem; }
.sf-summary__failure-why { color: var(--sf-text-strong); }
.sf-summary__details { margin-top: 0.5rem; }
.sf-summary__details summary { cursor: pointer; font-size: 0.8rem; color: var(--sf-text-muted); }

/* The channel table's filter strip and the detail row's labelled facts. */
.sf-filters { display: flex; flex-wrap: wrap; gap: 0.35rem; }
.sf-detail { line-height: 1.9; }
.sf-detail__part {
  display: inline-block; margin-right: 0.35rem; padding: 0 0.45rem;
  border: 1px solid var(--sf-border); border-radius: 3px;
  background: var(--sf-surface-2); font-family: var(--sf-font-mono); font-size: 0.72rem;
}
.sf-detail__k { color: var(--sf-text-muted); font-family: var(--sf-font-sans); margin-right: 0.2rem; }
.sf-sans { font-family: var(--sf-font-sans); }

/*
 * The creative library: products as groups, sizes as tiles, coverage as a table. Full width under
 * the channels -- the rail that held 24 tall cards beside an empty column is gone.
 */
.sf-assets__group { margin-bottom: 1.25rem; }
.sf-assets__group-title { margin-bottom: 0.4rem; }
.sf-asset-row {
  display: grid; grid-template-columns: 9rem minmax(12rem, 16rem) minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: 0.75rem 1rem; align-items: center;
  padding: 0.6rem 0.75rem; margin-bottom: 0.5rem;
  border: 1px solid var(--sf-border); border-radius: 0.5rem; background: var(--sf-surface);
}
@media (max-width: 1199px) {
  .sf-asset-row { grid-template-columns: 9rem minmax(0, 1fr) auto; }
  .sf-asset-row__field { grid-column: 1 / -1; }
}
.sf-asset-row__thumb {
  display: flex; align-items: center; justify-content: center; height: 6rem;
  border-radius: 0.35rem; background: var(--sf-surface-2); overflow: hidden;
}
.sf-asset-row__thumb img { max-width: 100%; max-height: 100%; object-fit: contain; }
.sf-asset-row__thumb--source { flex-direction: column; }
.sf-asset-row__meta { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.sf-asset-row__name { font-family: var(--sf-font-mono); font-size: 0.75rem; color: var(--sf-text-strong); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sf-asset-row__source { font-family: var(--sf-font-mono); font-size: 0.7rem; color: var(--sf-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sf-asset-row__field { min-width: 0; }
.sf-asset-row__actions { display: flex; gap: 0.25rem; }
.sf-coverage th { font-family: var(--sf-font-mono); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--sf-text-muted); }
/*
 * SIM-169: the coverage matrix scrolls inside .table-responsive (one of the documented exceptions in the NO SCROLL
 * WRAPPER note above), and its publisher column stays put while the products scroll past, so a scrolled row still
 * says whose it is.
 */
.sf-coverage-scroll .sf-coverage th:first-child,
.sf-coverage-scroll .sf-coverage td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--sf-surface);
}
.sf-coverage-scroll .sf-coverage thead th:first-child { z-index: 3; }

/*
 * --------------------------------------------------------------
 * # 9. The channel form (SIM-14): inherited-value chips and section counts
 * --------------------------------------------------------------
 */

/* A value the channel inherits reads as a stated fact with a source, not as an empty field. */
.sf-inherit {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem;
  padding: 0.6rem 0.8rem;
  border: 1px dashed var(--sf-pending-border);
  border-radius: 0.5rem;
  background: var(--sf-pending-bg);
}
.sf-inherit__title { font-size: 0.75rem; font-weight: 600; color: var(--sf-pending-fg); margin-right: 0.4rem; }
.sf-inherit__chip {
  display: inline-flex; align-items: center; gap: 0.3rem;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--sf-surface);
  border: 1px solid var(--sf-pending-border);
  color: var(--sf-text-strong);
  font-family: var(--sf-font-mono); font-size: 0.75rem;
}
.sf-inherit__label { color: var(--sf-text-muted); font-family: var(--sf-font-sans); }
.sf-inherit__source { color: var(--sf-pending-fg); font-family: var(--sf-font-sans); font-size: 0.7rem; }

/* "3 set" / "nothing set" beside a collapsed section's title. */
.sf-section-count {
  font-family: var(--sf-font-mono); font-size: 0.7rem; font-weight: 500;
  letter-spacing: 0.03em; text-transform: uppercase; color: var(--sf-text-muted);
}

/*
 * --------------------------------------------------------------
 * # 10. The operations board (SIM-17)
 * --------------------------------------------------------------
 */
.sf-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 1rem; }
.sf-tile {
  display: flex; flex-direction: column; gap: 0.35rem;
  padding: 1rem 1.1rem;
  background: var(--sf-surface); border: 1px solid var(--sf-border); border-radius: 0.6rem;
  text-decoration: none; color: var(--sf-text-strong);
}
.sf-tile:hover { border-color: var(--sf-accent); color: var(--sf-text-strong); }
.sf-tile__label {
  font-family: var(--sf-font-mono); font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--sf-text-muted);
}
.sf-tile__value { font-family: var(--sf-font-mono); font-size: 1.6rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.sf-tile__meta { font-family: var(--sf-font-sans); font-size: 0.8rem; font-weight: 400; color: var(--sf-text-muted); }

/*
 * Delivery against a goal (SIM-95): the bar fills to the delivered share and a mark stands where even delivery would be.
 * The words under it say the same, so neither the fill nor the colour is the only cue.
 */
.sf-meter { min-width: 9rem; }
.sf-meter__track {
  position: relative; height: 6px; border-radius: 3px;
  background: var(--sf-surface-2); box-shadow: inset 0 0 0 1px var(--sf-border);
}
.sf-meter__fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 3px; background: var(--sf-accent); }
.sf-meter__mark { position: absolute; top: -4px; height: 14px; border-left: 2px solid var(--sf-text-strong); }
.sf-meter__text {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.2rem 0.45rem; margin-top: 0.35rem;
  font-size: 0.8rem; color: var(--sf-text-muted);
}
.sf-meter__text strong { color: var(--sf-text-strong); font-weight: 600; font-variant-numeric: tabular-nums; }

/*
 * Delivered impressions per day (SIM-95). The drawing stretches to its panel (preserveAspectRatio none, strokes that do
 * not scale) and its scale and days are HTML beside it, so the labels keep their size on a phone. An SVG text label
 * would shrink with the drawing to a few pixels at 320px.
 */
.sf-trend { margin: 0; }
.sf-trend__chart { position: relative; height: 11rem; margin: 0.5rem 0 0 3rem; }
.sf-trend__plot { display: block; width: 100%; height: 100%; overflow: visible; }
.sf-trend__grid { stroke: var(--sf-border); stroke-width: 1; }
.sf-trend__bar { fill: var(--sf-accent); opacity: 0.4; }
.sf-trend__bar--recent { opacity: 1; }
.sf-trend__scale span,
.sf-trend__days span {
  position: absolute; font-size: 0.72rem; color: var(--sf-text-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.sf-trend__scale span { right: calc(100% + 0.5rem); transform: translateY(50%); }
.sf-trend__days { position: relative; height: 1.1rem; margin: 0.4rem 0 0 3rem; }
.sf-trend__days span { transform: translateX(-50%); }
.sf-trend__days span:first-child { transform: none; }
.sf-trend__days span:last-child { transform: translateX(-100%); }
@media (max-width: 575.98px) {
  .sf-trend__days span:nth-child(2), .sf-trend__days span:nth-child(4) { display: none; }
}

/*
 * ------------------------------------------------------------
 * 11. Geo field widget (partials/geo-field)
 * Chips read the field's text back as places; the search and the
 */
.sf-geo__chips { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-bottom: 0.4rem; }
.sf-geo__chip {
  display: inline-flex; align-items: center; gap: 0.25rem;
  padding: 0.1rem 0.25rem 0.1rem 0.6rem;
  border-radius: 999px;
  background: var(--sf-surface-2);
  border: 1px solid var(--sf-border);
  color: var(--sf-text-strong);
  font-size: 0.8rem;
}
.sf-geo__remove {
  border: 0; background: transparent; color: var(--sf-text-muted);
  padding: 0 0.3rem; line-height: 1; font-size: 1.05rem; cursor: pointer;
}
.sf-geo__remove:hover { color: var(--sf-fail-fg); }
.sf-geo__inherited { font-size: 0.8rem; color: var(--sf-text-muted); }
.sf-geo__list { max-height: 260px; overflow-y: auto; margin-top: 0.3rem; }
.sf-geo__raw summary { font-size: 0.8rem; color: var(--sf-text-muted); cursor: pointer; margin-top: 0.4rem; }
.sf-geo__raw textarea { font-family: var(--sf-font-mono); font-size: 0.8rem; margin-top: 0.3rem; }

/*
 * ------------------------------------------------------------
 * 12. Global search results (search.mustache, SIM-53)
 * One panel per type, and each result ONE link whose last words
 */
.sf-search-summary { margin: 0 0 0.75rem; color: var(--sf-text-muted); }
.sf-search-summary strong { color: var(--sf-text-strong); }
.sf-results { list-style: none; margin: 0; padding: 0; }
.sf-results li + li { border-top: 1px solid var(--sf-border); }
.sf-result {
  display: flex; align-items: center; gap: 0.75rem;
  padding: 0.7rem var(--sf-card-pad);
  color: var(--sf-text); text-decoration: none;
}
.sf-result:hover { background: var(--sf-surface-2); color: var(--sf-text); }
.sf-result:focus-visible { outline: 2px solid var(--sf-accent); outline-offset: -2px; }
.sf-result__main { flex: 1 1 auto; min-width: 0; }
.sf-result__label { display: block; font-weight: 600; color: var(--sf-text-strong); overflow-wrap: anywhere; }
.sf-result__meta {
  display: block; margin-top: 0.1rem;
  font-size: 0.8rem; color: var(--sf-text-muted); overflow-wrap: anywhere;
}
.sf-result__action {
  flex: none; display: inline-flex; align-items: center; gap: 0.2rem;
  font-size: 0.8rem; font-weight: 500; color: var(--sf-accent); white-space: nowrap;
}
.sf-result .sf-badge { flex: none; }

/*
 * --------------------------------------------------------------
 * # The campaign detail page (SIM-41)
 * #
 */

/*
 * A titled full-width section. The campaign page needs one for the publisher channels and one for
 * the creative library, each with a count beside the title and its own action cluster. Same card
 * surface as .card -- this adds the header row that both were building ad-hoc.
 */
.sf-panel {
  background: var(--sf-surface);
  border: 1px solid var(--sf-border);
  border-radius: 12px;
  box-shadow: var(--sf-shadow-card);
  margin-bottom: 1.5rem;
}
.sf-panel__head {
  display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center;
  padding: 1rem var(--sf-card-pad);
  border-bottom: 1px solid var(--sf-border);
}
.sf-panel__title {
  display: flex; align-items: center; gap: 0.5rem; margin: 0;
  font-family: var(--sf-font-sans); font-size: 1rem; font-weight: 600; color: var(--sf-text-strong);
}
.sf-panel__count {
  font-family: var(--sf-font-mono); font-size: 0.75rem; font-weight: 500;
  color: var(--sf-text-muted);
  background: var(--sf-idle-bg); border: 1px solid var(--sf-idle-border);
  border-radius: 999px; padding: 0.05rem 0.45rem;
}
/* Full-basis so it drops to its own line UNDER the title and the actions, never between them. */
.sf-panel__lead { flex-basis: 100%; margin: 0; font-size: 0.8125rem; color: var(--sf-text-muted); }
.sf-panel__actions { margin-left: auto; display: flex; gap: 0.5rem; flex-wrap: wrap; }
.sf-panel__body { padding: var(--sf-card-pad); }
.sf-panel__body--flush { padding: 0; }
.sf-panel__foot {
  padding: 0.75rem var(--sf-card-pad); border-top: 1px solid var(--sf-border);
  font-size: 0.75rem; color: var(--sf-text-muted);
}

/*
 * Summary strip. .sf-summary and its __item / __label / __value are unchanged and keep their other
 * consumers; this modifier only lays the SAME items out as equal cells divided by hairlines, so the
 * campaign's six facts read as one scannable strip instead of a ragged flex row.
 */
.sf-summary--strip {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 1px; padding: 0;
  background: var(--sf-surface);
  overflow: hidden;
}
/*
 * Each cell draws its own hairline into the 1px gap around it, and the container's overflow clips the lines at its edges.
 * The gaps used to be the container's border-coloured background showing through, which also filled every cell a last
 * row leaves empty with a block of border colour: half a row on a report's six facts at desktop width (SIM-44).
 */
.sf-summary--strip .sf-summary__item { background: var(--sf-surface); padding: 0.8rem 1rem; min-width: 0; outline: 1px solid var(--sf-border); }
.sf-summary--strip .sf-summary__value { font-weight: 600; overflow-wrap: anywhere; }
/* The qualifier under a value: "read 12m ago", "not applied yet". */
.sf-summary__meta { display: block; font-size: 0.75rem; color: var(--sf-text-muted); margin-top: 0.1rem; }

/* Identifiers break at EVERY width, not only narrow ones (SIM-56, found by the SIM-57 browser suite). */
.sf-stack-table td,
.sf-stack-table th {
  overflow-wrap: anywhere;
}

/* A CONTROL'S LABEL IS NOT AN IDENTIFIER, and anywhere must not reach it (SIM-47). */
.sf-stack-table td .btn,
.sf-stack-table th .btn {
  overflow-wrap: normal;
}

/* A COLUMN HEADING IS NOT AN IDENTIFIER EITHER (SIM-98). */
.sf-stack-table th {
  overflow-wrap: normal;
}

/* A number that is read as language rather than compared down a column. */
.sf-num--wrap {
  white-space: normal;
  /*
   * ...but a DATE is not a breakable token (SIM-98). With `anywhere` in force, "2026-10-01 to
   * 2026-12-31" broke at every hyphen -- "2026- / 10-01 / to / 2026- / 12-31" -- which reads as a
   * rendering fault. `normal` puts the break opportunities back where the language has them.
   */
  overflow-wrap: normal;
}

/* Narrow viewports (SIM-56 designed in, not retrofitted). */
@media (max-width: 991.98px) {
  .sf-stack-table thead {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
  }
  .sf-stack-table tbody tr {
    display: block; padding: 0.6rem 0.75rem; margin: 0 0 0.6rem;
    border: 1px solid var(--sf-border); border-radius: 10px;
  }
  .sf-stack-table tbody td {
    display: flex; gap: 0.75rem; align-items: baseline; justify-content: space-between;
    border: 0 !important; padding: 0.2rem 0 !important; text-align: left !important;
  }
  .sf-stack-table tbody td::before {
    content: attr(data-label); flex: 0 0 auto;
    font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.04em;
    color: var(--sf-text-muted);
  }
  /* An empty data-label is the deliberate opt-out for the actions cell and the detail row. */
  .sf-stack-table tbody td[data-label=""]::before { content: none; }
  /* A STACKED TABLE MAY ALSO BE A .datatable, and this one rule is what lets it (SIM-47). */
  .sf-stack-table tbody td { width: auto !important; }
  /*
   * min-width: 0 and overflow-wrap are what make the stacking actually hold (SIM-56, found by the
   * SIM-57 browser suite).
   */
  .sf-stack-table tbody td > * {
    text-align: right;
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .sf-panel__actions { margin-left: 0; width: 100%; }
}

/*
 * --------------------------------------------------------------
 * # The apply-feedback family (SIM-58)
 * #
 */
.sf-feedback {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  padding: 0.85rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--sf-border);
  border-radius: 10px;
  background: var(--sf-surface);
}
.sf-feedback__icon { flex: 0 0 auto; font-size: 1.05rem; line-height: 1.2; }
/* min-width: 0 is load-bearing, not tidiness. */
.sf-feedback__body { flex: 1 1 auto; min-width: 0; }
.sf-feedback__title {
  margin: 0 0 0.15rem;
  font-family: var(--sf-font-sans);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--sf-text-strong);
}
.sf-feedback__text { margin: 0; font-size: 0.8125rem; color: var(--sf-text); }
/* The raw remote failure text. */
.sf-feedback__detail {
  margin: 0.5rem 0 0;
  font-family: var(--sf-font-mono);
  font-size: 0.75rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--sf-text);
}
/*
 * A long job message folds (SIM-45): past four lines, partials/job-row keeps the first two on screen and the rest
 * opens with the browser's own details element. The failure and its first lines never leave the screen.
 */
.sf-fold summary {
  margin-top: 0.35rem;
  font-size: 0.75rem;
  color: var(--sf-accent);
  cursor: pointer;
}
/*
 * The recorded phase, as discrete facts rather than one hyphenated string. Wrapping is the point:
 * the channel code alone can be 40 characters, and at 320px a single line either overflows or has
 * to be truncated, and truncating it hides the one fact that says WHICH channel is being worked on.
 */
.sf-feedback__stage {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.75rem;
  margin-top: 0.4rem;
  font-family: var(--sf-font-mono);
  font-size: 0.75rem;
  color: var(--sf-text-muted);
}
.sf-feedback__stage > span { min-width: 0; overflow-wrap: anywhere; }
/*
 * The dismiss button sits IN the flex flow rather than absolutely positioned. Bootstrap's
 * .alert-dismissible reserves right padding for an absolute .btn-close, and at 320px a wrapped
 * title runs underneath it. As a flex item it simply takes its own column at every width.
 */
.sf-feedback__close {
  flex: 0 0 auto;
  box-sizing: content-box;
  width: 1em;
  height: 1em;
  padding: 0.7rem;
  margin: -0.45rem -0.6rem 0 0;
  opacity: 0.65;
}
.sf-feedback__close:hover,
.sf-feedback__close:focus-visible { opacity: 1; }

/* Four tones. The icon and the title carry the -fg; the panel carries the -bg and -border. */
/* Request Apply: the answers to a waiting request, under its banner. */
.sf-feedback__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }

.sf-feedback--info { background: var(--sf-pending-bg); border-color: var(--sf-pending-border); }
.sf-feedback--info .sf-feedback__icon,
.sf-feedback--info .sf-feedback__title { color: var(--sf-pending-fg); }

.sf-feedback--warn { background: var(--sf-action-bg); border-color: var(--sf-action-border); }
.sf-feedback--warn .sf-feedback__icon,
.sf-feedback--warn .sf-feedback__title { color: var(--sf-action-fg); }

.sf-feedback--good { background: var(--sf-live-bg); border-color: var(--sf-live-border); }
.sf-feedback--good .sf-feedback__icon,
.sf-feedback--good .sf-feedback__title { color: var(--sf-live-fg); }

.sf-feedback--error { background: var(--sf-fail-bg); border-color: var(--sf-fail-border); }
.sf-feedback--error .sf-feedback__icon,
.sf-feedback--error .sf-feedback__title { color: var(--sf-fail-fg); }

/*
 * Tone is never the only cue (SIM-56). Every state also carries its own Bootstrap icon, and the
 * three that are not plain information carry role="alert" or role="status" so a screen reader
 * announces them; the four tones differ in lightness as well as hue in both themes.
 */

/*
 * -------------------------------------------------------------
 * # The channel answer is pinned to the viewport (SIM-76, the user picked option C on 2026-09-21)
 * --------------------------------------------------------------
 */
.section.campaign #formplace:has(> *),
#channel-feedback:has(> *),
#line-action-status:has(> *) {
  position: fixed;
  right: 1.5rem;
  left: auto;
  /*
   * Lifted clear of .back-to-top, which is 40px square at bottom:15px with z-index 99999 -- five orders
   * above anything this file sets, so it draws OVER the panel's corner and can cover the dismiss button.
   * 4.75rem puts the panel's floor at 76px, above that button's 55px, with a gap between them.
   */
  bottom: calc(4.75rem + env(safe-area-inset-bottom, 0px));
  z-index: 1040;
  width: min(34rem, calc(100vw - 3rem));
  /* A long SimpleFeed error scrolls inside the panel rather than growing up the whole screen. */
  max-height: min(60vh, 32rem);
  overflow-y: auto;
  box-shadow: 0 0.75rem 2rem rgba(0, 0, 0, 0.28);
}
/* The margin belonged to a panel in the page flow; pinned, it would be dead space under the shadow. */
.section.campaign #formplace:has(> *) > *,
#channel-feedback:has(> *) > *,
#line-action-status:has(> *) > * { margin-bottom: 0; }

@media (max-width: 575.98px) {
  /*
   * A phone has no room for a 34rem card in a corner, so it spans the width less a gutter on each
   * side. Still bottom-anchored: that is the half of the screen a thumb reaches, and it leaves the
   * row the operator just used visible above it.
   */
  .section.campaign #formplace:has(> *),
  #channel-feedback:has(> *),
  #line-action-status:has(> *) {
    right: 0.75rem;
    left: 0.75rem;
    bottom: calc(4rem + env(safe-area-inset-bottom, 0px));
    width: auto;
    max-height: 50vh;
  }
}

/* A control locked while Apply Changes runs (SIM-230): it looks disabled, but a press answers why in the pinned panel. */
.btn[aria-disabled="true"]:not(.disabled),
.dropdown-item[aria-disabled="true"]:not(.disabled) { opacity: 0.65; }

/* Compact density: the one token this component reads, same as .card-body and the tables. */
:root[data-density="compact"] .sf-feedback { padding: 0.6rem 0.75rem; }

@media (max-width: 575.98px) {
  /*
   * At phone width the row keeps its shape and only gives back padding: the icon column is 1.05rem
   * and earns it as the non-colour state cue, and the body already carries min-width: 0 so the
   * title, the text, the stage facts and the detail all wrap instead of overflowing.
   */
  .sf-feedback { gap: 0.5rem; padding: 0.7rem 0.75rem; }
  .sf-feedback__stage { gap: 0.25rem 0.6rem; }
}

/*
 * --------------------------------------------------------------
 * # The publishers page (SIM-47)
 * #
 */

/*
 * The freshness line under a delivery badge, when it is explaining a reading we could NOT make
 * rather than dating one we could.
 */
.sf-cell-caption--qualified { color: var(--sf-action-fg); }

/* A blocked publisher's reason: body ink, so it reads above the muted who-and-when line under it. */
.sf-block-reason { color: var(--sf-text); }

/*
 * The delivery cell holds a badge and a caption, so the badge's slot stops reserving a width the
 * caption below it has already established. Same escape hatch .sf-state-cell uses on the campaigns
 * row, for the same reason.
 */
.sf-delivery-cell .sf-status__primary { min-width: 0; }

/*
 * The dialog's four headline figures. The strip modifier draws them as equal cells divided by
 * hairlines; this only gives it the border the panel version gets from its container.
 */
.sf-delivery-strip {
  border: 1px solid var(--sf-border);
  border-radius: 8px;
  margin-bottom: 1.25rem;
}

/*
 * Section headings inside the delivery dialog. Small, muted and lettered like a table header, so
 * they separate the three parts of the reading without competing with the modal title.
 */
.sf-delivery-heading {
  margin: 1.25rem 0 0.5rem;
  font-family: var(--sf-font-sans);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sf-text-muted);
}

/*
 * The delivered-lines table is inside a modal, where the sticky offset is already 0 (see the sticky
 * rules above). It stacks with everything else below 992px; the only thing it needs of its own is a
 * smaller measure, because a modal body is narrower than a page.
 */
.sf-delivery-lines { font-size: 0.8125rem; margin-bottom: 0; }
.sf-delivery-lines > thead th { font-size: 0.6875rem; }

@media (max-width: 991.98px) {
  /*
   * Stacked, the comment cell has the whole card width, so truncating it hides content for nothing
   * (SIM-47, measured by the SIM-57 browser suite at 320px).
   */
  .sf-stack-table tbody td[data-label="Comment"] .text-truncate {
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
    max-width: none !important;
  }
}


/*
 * -------------------------------------------------------------
 * # A panel that shows its first rows and folds the rest (SIM-134)
 * --------------------------------------------------------------
 */
.sf-more { display: none; }
.sf-panel:has(.sf-more-toggle[open]) .sf-more { display: table-row-group; }
.sf-more-toggle > summary {
  cursor: pointer;
  color: var(--sf-accent, #4154f1);
  font-size: 0.875rem;
  padding: 0.25rem 0;
}
.sf-panel:has(.sf-more-toggle[open]) .sf-more-toggle > summary { visibility: hidden; height: 0; padding: 0; }

/* What an action takes and what it leaves (Copy to draft, Block), each line marked in text as well as colour. */
.sf-copy-form { max-width: 40rem; }
.sf-copy-list { list-style: none; padding: 0; display: grid; gap: 0.15rem; font-size: 0.875rem; }
.sf-copy-list li::before { content: "\2713"; color: var(--sf-live-fg); margin-right: 0.4rem; }
.sf-copy-list .sf-copy-list__not { color: var(--sf-text-muted); }
.sf-copy-list .sf-copy-list__not::before { content: "\2717"; color: var(--sf-text-muted); }

/* Live creatives dialog: GAM's creatives on a channel's line as cards in GAM's order; the dashed card is one GAM lacks. */
.sf-live-toolbar { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.sf-live-head { display: flex; flex-wrap: wrap; gap: 2px 12px; align-items: baseline; min-width: 0; }
.sf-live-line { font-weight: 600; overflow-wrap: anywhere; }
.sf-live-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
.sf-live-card { border: 1px solid var(--bs-border-color); border-radius: var(--bs-border-radius-lg); padding: 10px; display: grid; gap: 6px; align-content: start; min-width: 0; }
.sf-live-card--missing { border-style: dashed; }
.sf-live-thumb { height: 140px; border-radius: var(--bs-border-radius); border: 1px solid var(--bs-border-color); background: var(--bs-tertiary-bg);
  display: flex; align-items: center; justify-content: center; overflow: hidden; color: var(--bs-secondary-color); font-size: 2rem; }
.sf-live-thumb img { display: block; width: 100%; height: 100%; object-fit: contain; }
.sf-live-name { font-weight: 600; overflow-wrap: anywhere; }
.sf-live-code { font-family: var(--bs-font-monospace); font-size: .78rem; color: var(--bs-secondary-color); overflow-wrap: anywhere; }
.sf-live-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.sf-live-stats { font-variant-numeric: tabular-nums; }
.sf-live-url { font-size: .8rem; overflow-wrap: anywhere; }
.sf-live-chips .sf-badge { white-space: normal; overflow-wrap: anywhere; }

/* The Accounts form's agency records: a bounded scroll list whose every row is a full-width touch target. */
.sf-records { border: 1px solid var(--bs-border-color); border-radius: var(--bs-border-radius); max-height: 240px; overflow-y: auto; }
.sf-records__item { display: flex; gap: 8px; align-items: baseline; padding: 10px; margin: 0; border-top: 1px solid var(--bs-border-color); cursor: pointer; }
.sf-records__item:first-child { border-top: 0; }
.sf-records__name { min-width: 0; overflow-wrap: anywhere; }
.sf-records__count { margin-left: auto; color: var(--bs-secondary-color); font-size: .8rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
.sf-records__empty { margin: 0; padding: 10px; color: var(--bs-secondary-color); font-size: .875rem; }

/*
 * Administration > Actions' schedule: a month grid on a desktop, the same tasks as a list by day below 768 px.
 * The daily triggers sit once in the dashed strip above both.
 */
.sf-cal-nav { display: flex; align-items: center; gap: 6px; }
.sf-cal-month { font-weight: 600; min-width: 9.5em; text-align: center; }
.sf-cal-daily { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: baseline; padding: 10px 12px; margin-bottom: 14px;
  border: 1px dashed var(--sf-border); border-radius: 8px; font-size: .875rem; }
.sf-cal-daily b { color: var(--sf-text-strong); }
.sf-cal-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); border: 1px solid var(--sf-border); border-radius: 8px; overflow: hidden; }
.sf-cal-head { padding: 6px; font-weight: 600; text-align: center; background: var(--sf-surface-2); color: var(--sf-text-strong);
  border-bottom: 1px solid var(--sf-border); font-size: .8rem; }
.sf-cal-cell { min-height: 92px; padding: 6px; border-right: 1px solid var(--sf-border); border-bottom: 1px solid var(--sf-border);
  min-width: 0; font-size: .8rem; display: flex; flex-direction: column; gap: 4px; }
.sf-cal-cell:nth-child(7n) { border-right: 0; }
.sf-cal-out { color: var(--sf-text-muted); background: var(--sf-bg); }
.sf-cal-today { box-shadow: inset 0 0 0 2px var(--bs-primary); }
.sf-cal-daynum { color: var(--sf-text-muted); }
.sf-cal-chip { display: block; padding: 2px 6px; border-radius: 4px; border-left: 3px solid var(--sf-border);
  background: var(--sf-surface-2); color: inherit; text-decoration: none; overflow-wrap: anywhere; line-height: 1.3; }
a.sf-cal-chip:hover { text-decoration: underline; }
.sf-cal-chip--planned, .sf-cal-chip--cancelled { border-left-color: var(--bs-secondary); }
.sf-cal-chip--cancelled { text-decoration: line-through; color: var(--sf-text-muted); }
.sf-cal-chip--queued { border-left-color: var(--bs-primary); }
.sf-cal-chip--done { border-left-color: var(--bs-success); }
.sf-cal-chip--late { border-left-color: var(--bs-warning); }
.sf-cal-chip--failed { border-left-color: var(--bs-danger); background: var(--sf-fail-bg); }
.sf-cal-more { color: var(--sf-text-muted); }
.sf-cal-phone { display: none; }
.sf-cal-day { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr); gap: 6px 14px; padding: 10px 0; border-top: 1px solid var(--sf-border); }
.sf-cal-day:first-child { border-top: 0; }
.sf-cal-date { font-weight: 600; color: var(--sf-text-strong); }
.sf-cal-date small { display: block; font-weight: 400; color: var(--sf-text-muted); }
.sf-cal-items { display: grid; gap: 6px; min-width: 0; }
.sf-cal-item { display: grid; grid-template-columns: 3.4rem minmax(0, 1fr) auto; gap: 8px; align-items: baseline; min-width: 0; }
.sf-cal-item__time { font-size: .85rem; color: var(--sf-text-muted); }
.sf-cal-item__what { min-width: 0; overflow-wrap: anywhere; }
.sf-cal-item__what small { display: block; color: var(--sf-text-muted); }
@media (max-width: 767.98px) {
  .sf-cal-desktop { display: none; }
  .sf-cal-phone { display: block; }
}
@media (max-width: 575.98px) {
  .sf-cal-day { grid-template-columns: minmax(0, 1fr); }
  .sf-cal-item { grid-template-columns: 3.4rem minmax(0, 1fr); }
  .sf-cal-item .sf-badge { grid-column: 2; justify-self: start; }
}
