/**
 * CubeWP Agency — layout on top of the platform design system.
 *
 * ===========================================================================
 * THIS FILE OWNS LAYOUT, NOT APPEARANCE
 *
 * Colour, type, spacing, radius, elevation and motion all come from
 * dashboard/assets/css/tokens.css, which index.php now loads first. That file
 * states the rule this one used to break:
 *
 *   > no module may use raw hex/px for themed properties ... visual drift
 *   > (the four-modal-dialects era) mechanically impossible
 *
 * Agency had 18 hardcoded hex values and its own accent (#4f46e5 against the
 * platform's #5641F3), so it was already a visibly different product - and it
 * had no dark mode while the rest of the console did. Every one of those values
 * is now a token, which is why dark mode works here without a line of dark-mode
 * CSS in this file.
 *
 * ===========================================================================
 * WHY `ag-` CLASSES STILL EXIST
 *
 * Most of them duplicate a `cw-` component. They are kept for now because
 * views.js references them, and swapping every class in the same change as the
 * theming would make a visual regression impossible to attribute. They are thin
 * aliases over the system; views migrate to `cw-` as each screen is rebuilt,
 * and what is left here at the end should be Agency-specific layout only.
 */

/* ⚠ THE COMMENT THAT USED TO BE HERE SAID THE OPPOSITE.
 *
 * It read: "Self-contained: /agency does not load the dashboard's stylesheet,
 * so the two products can look different without either constraining the
 * other." That was the bug, written down as a principle — it is exactly why the
 * agency console looked like older software than /dashboard.
 *
 * /agency now loads tokens + signup + dashboard + console, in dashboard order,
 * and renders the dashboard's own shell. This file is agency layout ONLY. */

:root {
}
* { box-sizing: border-box; }
/* BODY STYLING BELONGS TO THE SHELL. <body> now carries `dash-body` as well,
   and dashboard.css owns its canvas, type and colour — the same ones /dashboard
   uses. Setting them again here would be a second opinion about the same
   surface, and the reason the two products drifted apart in the first place. */
.ag-body { -webkit-font-smoothing: antialiased; }
/* An anchor dressed as a button must not keep its anchor underline — the "Open"
   buttons in the Sites table are <a class="cw-btn">, and console.css leaves
   text-decoration to the host page. Gap-fill, not an override. */
.ag-body a.cw-btn { text-decoration: none; }
/* !important is load-bearing: this is declared before .ag-app, which sets
   display:grid at the same specificity, so source order would otherwise show
   an app that is meant to be hidden. */
.hidden { display: none !important; }
a { color: var(--cw-accent); }
/* ── boot skeleton ─────────────────────────────────────────────────────────
   THE REAL SHELL SHOWS IMMEDIATELY; ONLY THE VIEW IS SKELETONED.

   The first version drew a SECOND sidebar and header in grey. That was both
   wrong and unnecessary. Unnecessary because the chrome is entirely STATIC —
   logo, nav labels, breadcrumb, footer link — and needs no data at all, so
   there was nothing to wait for. Wrong because the grey copy matched nothing
   that arrived: two wide blocks where four tiles and two panels were coming,
   so the "loading" picture and the loaded page were different layouts and the
   swap read as the page changing its mind.

   What is left waits only on data. The view skeleton is built from the
   OVERVIEW'S OWN classes (.ax-head, .ax-tiles, .ax-tile, .ax-cols, .ax-sec,
   .ax-card), so it inherits the real grid, gaps and card chrome and every
   placeholder sits where its content will land.

   The reveal itself is NOT here. .hidden is display:none !important, so no
   selector can beat it without a third !important, and that cap is a
   deliberate guard. index.php drops the class with one line instead, right
   after the shell markup, which also applies earlier than a rule would.    */

/* The two pieces of chrome that DO need data. Blanked rather than filled with
   a placeholder glyph — a lone "·" where an avatar will be reads as a
   rendering fault, not as loading. */
.cwa-booting #ag-avatar {
  color: transparent; background: var(--cw-surface-2); border-color: transparent;
}
.cwa-booting #ag-who::after {
  content: ""; display: inline-block; width: 84px; height: 12px;
  border-radius: var(--cw-r-sm); background: var(--cw-surface-2);
}

/* Sizes below stand in for the type they replace, taken from tokens.css:
   --cw-display 28px, the 30px tile value, --cw-body 14px, the 38px button.
   Keep them in step if those tokens move. */
/* GROW. As a bare flex item with min-width:0 and no intrinsic content this
   collapsed to zero, and the two width:100% bars inside it rendered 0px wide —
   a header skeleton with no header in it, which is exactly the "matches
   nothing" failure this block exists to avoid. */
.ag-vk-hb   { flex: 1 1 auto; min-width: 0; }
/* The margins are not decoration: .ax-head-t is a 34px line box and .ax-head-s
   a 22px one under a 6px margin, so the block measures 62px. A bare 28+8+14
   measured 50 and every tile below it started 12px high. */
.ag-vk-h    { height: 28px; width: 100%; max-width: 260px;
              border-radius: var(--cw-r-sm); margin: 3px 0; }
.ag-vk-sub  { height: 14px; width: 100%; max-width: 340px;
              border-radius: var(--cw-r-sm); margin: 10px 0 7px; }
.ag-vk-btn  { height: 38px; width: 96px; border-radius: var(--cw-r-md); }
.ag-vk-btn-w{ width: 112px; }

/* Inside a real .ax-tile: label, then the number, then the sub-line. */
/* Heights are the LINE BOXES they stand in for — 16px label, 36px value,
   16px sub — not the glyph sizes. .ax-tile is a flex column with its own 2px
   gap, so a tile lands on the same 118px the filled one does. */
.ag-vk-tl   { height: 16px; border-radius: var(--cw-r-sm); }
.ag-vk-tv   { height: 36px; width: 58px; border-radius: var(--cw-r-sm); margin-top: 4px; }
.ag-vk-ts   { height: 16px; width: 76px; border-radius: var(--cw-r-sm); margin-top: 2px; }

/* Panel heading + the capacity meter, which is a real 8px bar. */
.ag-vk-ct   { height: 16px; width: 100%; max-width: 150px; border-radius: var(--cw-r-sm); }
.ag-vk-meter{ height: 8px; border-radius: var(--cw-r-full); margin: var(--cw-s3) 0 var(--cw-s2); }

/* "Recent sites": a section heading, then table rows inside a card. The rows
   carry the card's own row rhythm — 12px/20px padding, a hairline between —
   so the table that replaces them does not shift the block. */
.ag-vk-st   { height: 16px; width: 118px; border-radius: var(--cw-r-sm); }
.ag-vk-rows { padding: var(--cw-s2) 0; }
.ag-vk-row  { height: 20px; margin: var(--cw-s3) var(--cw-s5); border-radius: var(--cw-r-sm); }

/* ── boot + gates: REMOVED ─────────────────────────────────────────────────
   The splash (.ag-boot, its wordmark and spinner) is gone — index.php resolves
   the marketing stylesheet during head parsing, so the signed-out page is
   styled at first paint and there is no swap for a spinner to cover.

   The .ag-gate card went with it: it had no markup left in the app, so its
   rules could only ever style nothing. Kept as a note rather than as dead CSS,
   because an unused class in a stylesheet reads like a component someone can
   still use.
   ────────────────────────────────────────────────────────────────────────── */

/* ── layout ───────────────────────────────────────────────────────────── */
/* ── banner ───────────────────────────────────────────────────────────── */
.ag-banner {
  border-radius: var(--cw-r-md); padding: var(--cw-s3) var(--cw-s4);
  margin-bottom: var(--cw-s5);
  font: var(--cw-small); border: 1px solid var(--cw-border);
}
.ag-banner-warn { background: var(--cw-warn-bg); border-color: var(--cw-warn-border); color: var(--cw-warn); }
.ag-banner-bad  { background: var(--cw-danger-bg);  border-color: var(--cw-danger-border); color: var(--cw-danger); }
/* ── cards ────────────────────────────────────────────────────────────── */
/* The pre-migration stat-card dialect (.ag-cards/.ag-card/.ag-card-n/.ag-bar/
   .ag-over) is GONE: .ag-stat over the platform's .cw-stat replaced it, and a
   styled selector nothing renders is where the next visual dialect starts.
   Only the footnote class below still has a caller. */
.ag-card-sub { font: var(--cw-micro); color: var(--cw-text-soft); }
/* ── sections + tables ────────────────────────────────────────────────── */
.ag-count {
  font: var(--cw-micro); color: var(--cw-text-soft);
  background: var(--cw-surface-2); border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-full); padding: 2px 9px;
}
.ag-empty { font: var(--cw-small); color: var(--cw-text-soft); margin: 0; }
/* Wide content scrolls inside its own container; the page never scrolls
   sideways on a phone. */
.ag-table {
  width: 100%; border-collapse: collapse; font: var(--cw-small);
  background: var(--cw-surface); border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-lg); overflow: hidden;
}
.ag-table th {
  text-align: left; font-weight: 600; font-size: 11px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--cw-text-soft);
  padding: 10px 16px; border-bottom: 1px solid var(--cw-border); white-space: nowrap;
}
.ag-table td { padding: 13px 16px; border-top: 1px solid var(--cw-surface-2); }
.ag-table tbody tr:first-child td { border-top: 0; }
.ag-table tbody tr:hover td { background: var(--cw-bg); }
.ag-strong { font-weight: 600; }
.ag-strong a { color: inherit; text-decoration: none; }
.ag-strong a:hover { text-decoration: underline; }
.ag-state { font-weight: 600; white-space: nowrap; }
.ag-tone-ok   { color: var(--cw-ok); }
.ag-tone-warn { color: var(--cw-warn); }
.ag-tone-bad  { color: var(--cw-danger); }
.ag-placeholder {
  background: var(--cw-surface); border: 1px dashed var(--cw-border-strong);
  border-radius: var(--cw-r-lg); padding: var(--cw-s8) var(--cw-s5); text-align: center;
}
.ag-placeholder h2 { margin: 0 0 var(--cw-s1); font: var(--cw-heading); color: var(--cw-ink); }
.ag-placeholder p  { margin: 0; color: var(--cw-text-soft); font: var(--cw-small); }
/* ── offer ────────────────────────────────────────────────────────────── */
.ag-offer-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--cw-s4);
}
.ag-offer-card {
  border: 1px solid var(--cw-border); border-radius: var(--cw-r-lg); padding: var(--cw-s5);
  display: flex; flex-direction: column; gap: var(--cw-s3);
  background: var(--cw-surface);
  transition: border-color var(--cw-t-micro), box-shadow var(--cw-t-micro);
}
.ag-offer-card:hover { border-color: var(--cw-border-strong); box-shadow: var(--cw-e1); }
.ag-offer-card h3 { margin: 0; font: var(--cw-heading); font-family: "Bricolage Grotesque", var(--cw-font); }
.ag-offer-price { margin: 0; font: var(--cw-small); color: var(--cw-text-soft); }
.ag-offer-price strong {
  font: 700 32px/38px "Bricolage Grotesque", var(--cw-font);
  color: var(--cw-ink); font-variant-numeric: tabular-nums; letter-spacing: -.02em;
  margin-right: var(--cw-s1);
}
.ag-offer-card ul { list-style: none; padding: 0; margin: 0 0 var(--cw-s3); font: var(--cw-small); line-height: 1.9; }
.ag-offer-card li { padding-left: var(--cw-s5); position: relative; }
.ag-offer-card li::before { content: "✓"; position: absolute; left: 0; color: var(--cw-ok); font-weight: 700; }
/* ── toast ────────────────────────────────────────────────────────────── */
/* `.ag-btn` is gone from here too: nothing emits the class any more, and a
   styled selector with zero users is where the next drift starts. */
.ag-toast {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  background: var(--cw-ink); color: var(--cw-surface);
  padding: var(--cw-s3) var(--cw-s5); border-radius: var(--cw-r-md);
  font: var(--cw-small); max-width: min(560px, 92vw); z-index: 50;
  box-shadow: var(--cw-e2);
}
.ag-toast-error { background: var(--cw-danger); }
/* ── narrow screens ───────────────────────────────────────────────────── */
@media (max-width: 760px) {
  .ag-app { grid-template-columns: 1fr; }
}
/* ── view chrome (P6.8/P6.9) ──────────────────────────────────────────── */
.ag-view-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--cw-s4); flex-wrap: wrap; margin-bottom: var(--cw-s4);
}
.ag-link {
  appearance: none; border: 0; background: transparent; font: inherit;
  font: var(--cw-micro); color: var(--cw-accent); cursor: pointer; padding: 2px 6px;
  text-decoration: none; border-radius: var(--cw-r-sm);
}
.ag-link:hover { background: var(--cw-surface-2); text-decoration: underline; }
.ag-link.ag-danger { color: var(--cw-danger); }
.ag-link:focus-visible { outline: 2px solid var(--cw-accent); outline-offset: 1px; }
.ag-section-title .cw-btn { margin-left: auto; }
/* ── template catalog (P6.10) ─────────────────────────────────────────── */
.ag-tpl-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: var(--cw-s4);
}
.ag-tpl {
  background: var(--cw-surface); border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-lg); padding: var(--cw-s3);
  display: flex; flex-direction: column; gap: var(--cw-s2); min-height: 118px;
  transition: border-color var(--cw-t-micro), box-shadow var(--cw-t-micro),
              transform var(--cw-t-micro);
}
.ag-tpl:hover { border-color: var(--cw-border-strong); box-shadow: var(--cw-e1);
                transform: translateY(-1px); }
/* A stand-in for the screenshot the catalog does not carry — the template's
   initial on a brand-tinted plate. Data would beat decoration; this beats a
   name floating in a bare white box, and never pretends to be a preview. */
.ag-tpl-thumb {
  aspect-ratio: 16 / 9;
  border-radius: var(--cw-r-sm);
  background: linear-gradient(135deg, var(--cw-accent-subtle), var(--cw-surface-2));
  border: 1px solid var(--cw-border);
  display: grid; place-items: center;
}
.ag-tpl-thumb span {
  font: 700 24px/1 "Bricolage Grotesque", var(--cw-font);
  color: var(--cw-accent); opacity: .8;
}
.ag-tpl-locked .ag-tpl-thumb { background: var(--cw-surface-2); }
.ag-tpl-locked .ag-tpl-thumb span { color: var(--cw-text-faint); }
.ag-tpl-body { display: flex; flex-direction: column; gap: 3px; padding: var(--cw-s1) var(--cw-s1) 0; flex: 1; }
.ag-tpl-foot .cw-btn { width: 100%; }
/* Locked templates are shown, not hidden: hiding them makes a Core agency
   believe the premium catalog does not exist. Dimmed enough to read as
   unavailable, legible enough to read at all. */
.ag-tpl-locked { background: var(--cw-surface-2); border-style: dashed; }
.ag-tpl-locked .ag-tpl-name { color: var(--cw-text-soft); }
.ag-tpl-head { display: flex; align-items: flex-start; justify-content: space-between;
               gap: var(--cw-s2); flex-wrap: wrap; }
.ag-tpl-name { font: var(--cw-body); font-weight: 600; color: var(--cw-ink); }
.ag-tpl-lock {
  flex: none; font: var(--cw-micro); font-size: 11px; font-weight: 650; text-transform: uppercase;
  letter-spacing: .04em; color: var(--cw-accent); background: var(--cw-accent-subtle);
  border-radius: var(--cw-r-full); padding: 2px 7px;
}
.ag-tpl-cat { font: var(--cw-micro); font-weight: 450; color: var(--cw-text-soft);
               text-transform: capitalize; }
.ag-tpl-foot { margin-top: auto; }
.ag-tpl-note { font: var(--cw-micro); font-weight: 450; color: var(--cw-text-soft); }
/* ═══════════════════════════════════════════════════════════════════════════
   APP SHELL (P2)

   Sidebar + content on desktop; the SAME sidebar as an off-canvas drawer below
   900px. One navigation, moved — not a second mobile menu to keep in sync with
   the first.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════════════════
   THE SHELL LIVES IN dashboard.css NOW.

   This file used to re-implement it — .ag-app, .ag-sidebar, .ag-brand, .ag-nav,
   .ag-topbar, .ag-main — a second sidebar and header that had to be kept
   visually in step with /dashboard by hand, and never quite was. That is why
   the two products looked like different generations of the same software.
   
   The markup now uses .dash-shell / .dash-sidebar / .dash-sidenav-item /
   .cw-header / .dash-content, so there is ONE shell and both products render
   it. Anything below is agency-specific layout only.
   
   The drawer bits (.ag-scrim, .ag-menu-btn) stay: the dashboard sidebar has no
   off-canvas mode, so that behaviour is genuinely ours.
   ══════════════════════════════════════════════════════════════════════════ */

/* THE CONTENT COLUMN HAS TO CLAIM THE ROW.
 *
 * .dash-shell is a flex row and dashboard.css leaves .dash-content at the
 * default `flex: 0 1 auto`, so it is sized by its CONTENTS. A tab whose content
 * is narrow therefore collapses — measured at 436px on a 1920px screen — and
 * `.dash-tab-panel { max-width: 960px; margin: 0 auto }` can never centre a
 * 960px column inside a 436px parent, so the cap silently does nothing.
 *
 * The visible symptom is the container-width difference between this console
 * and /dashboard, whose tabs hold wide tables and so fill the row by accident
 * rather than by rule.
 *
 * Scoped here rather than patched into the shared sheet: this file loads after
 * dashboard.css and is read only by /agency, so the reference dashboard is
 * untouched. The same two lines belong in dashboard.css if that console should
 * centre its column too — a call worth making deliberately, not as a side
 * effect of this redesign.
 */
.dash-content { flex: 1; min-width: 0; }

.ag-account-who { font: var(--cw-small); color: var(--cw-text-soft); max-width: 18ch;
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ag-view {
  flex: 1;
  width: 100%;
  max-width: var(--cw-content-max);
  margin: 0 auto;
  padding: var(--cw-s7);
  min-width: 0;
}
.ag-view:focus { outline: none; }   /* focused only as a skip-link target */

/* The menu button and scrim exist only in drawer mode. */
.ag-menu-btn { display: none; }
.ag-scrim {
  position: fixed;
  inset: 0;
  background: rgba(22, 22, 31, .45);
  z-index: 40;
  opacity: 0;
  transition: opacity var(--cw-t-std);
}
.ag-scrim.open { opacity: 1; }
/* ── Drawer mode ─────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .dash-shell { display: block; }
  .ag-app .dash-sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    width: min(280px, 84vw);
    z-index: 50;
    transform: translateX(-100%);
    transition: transform var(--cw-t-panel);
    box-shadow: var(--cw-e2);
  }
  .ag-app .dash-sidebar.open { transform: translateX(0); }
  .ag-menu-btn { display: inline-flex; }


  /* The account email is the first thing worth dropping — the avatar still
     identifies the session, and the name is in the menu itself. */
  .ag-account-who, .ag-chevron { display: none; }
}
@media (max-width: 480px) {


}
/* A drawer that slid would be worse than one that appears, for anyone who has
   asked the OS not to animate. tokens.css already zeroes durations globally;
   this removes the transform so it does not jump. */
@media (prefers-reduced-motion: reduce) {
  .ag-app .dash-sidebar { transition: none; }
}
/* ═══════════════════════════════════════════════════════════════════════════
   FORMS IN PANELS (P3)

   The controls themselves are the platform's (cw-field / cw-label / cw-input /
   cw-help / cw-field-error). Only the arrangement is here.
   ═══════════════════════════════════════════════════════════════════════════ */

.ag-form { display: flex; flex-direction: column; gap: var(--cw-s5); }
/* A note is prose inside a form — a consequence someone should read before they
   submit, not a field they fill in. */
.ag-form-note {
  font: var(--cw-small);
  color: var(--cw-text);
  background: var(--cw-warn-bg);
  border: 1px solid var(--cw-warn-border);
  border-radius: var(--cw-r-md);
  padding: var(--cw-s3);
  margin: 0;
}
/* An address field shows the domain it will live under, so the person is
   choosing a hostname rather than a fragment. */
.ag-input-row { display: flex; align-items: stretch; min-width: 0; }
.ag-input-row .cw-input {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
  min-width: 0;
  flex: 1;
}
.ag-input-suffix {
  display: flex;
  align-items: center;
  padding: 0 var(--cw-s3);
  background: var(--cw-surface-2);
  border: 1px solid var(--cw-border);
  border-left: 0;
  border-radius: 0 var(--cw-r-sm) var(--cw-r-sm) 0;
  font: var(--cw-small);
  color: var(--cw-text-soft);
  white-space: nowrap;
  max-width: 45%;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* On a narrow screen the suffix goes under the field rather than squeezing it
   into a few characters. */
@media (max-width: 480px) {
  .ag-input-row { flex-direction: column; }
  .ag-input-row .cw-input { border-radius: var(--cw-r-sm) var(--cw-r-sm) 0 0; }
  .ag-input-suffix {
    max-width: none;
    border-left: 1px solid var(--cw-border);
    border-top: 0;
    border-radius: 0 0 var(--cw-r-sm) var(--cw-r-sm);
    padding: var(--cw-s2) var(--cw-s3);
  }
}
/* ═══════════════════════════════════════════════════════════════════════════
   DASHBOARD (P4)
   ═══════════════════════════════════════════════════════════════════════════ */

.ag-hero {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--cw-s4);
  margin-bottom: var(--cw-s7);
  flex-wrap: wrap;
}
/* Display type carries the console's voice: the dashboard sets its greetings in
   Bricolage Grotesque over Inter Tight body, and the agency should read as the
   same product. Family only — size and weight stay on the token. */
.ag-hero-title {
  font: var(--cw-display);
  font-family: "Bricolage Grotesque", var(--cw-font);
  letter-spacing: -.015em;
  color: var(--cw-ink); margin: 0; text-wrap: balance;
}
.ag-hero-sub   { font: var(--cw-body); color: var(--cw-text-soft); margin: var(--cw-s1) 0 0; }
.ag-hero-actions { display: flex; gap: var(--cw-s2); flex-shrink: 0; flex-wrap: wrap; }
.ag-section { margin-bottom: var(--cw-s7); }
/* A section that FOLLOWS other content needs air above it too. Without this the
   Clients screen ran "Client logins" straight into the bottom edge of the
   clients table, so two unrelated tables read as one. Adjacent margins collapse
   in this block container, so stacked sections still get one gap, not two. */
.ag-view > .ag-section:not(:first-child) { margin-top: var(--cw-s7); }
.ag-section-title {
  font: var(--cw-heading); color: var(--cw-ink); margin: 0 0 var(--cw-s3);
  /* flex + gap carried over from the rule this replaces: the count chip
     sits beside the title, and losing this would stack it underneath. */
  display: flex; align-items: center; gap: var(--cw-s2);
}
/* ── Needs your attention ────────────────────────────────────────────────── */

.ag-actions { display: flex; flex-direction: column; gap: var(--cw-s2); }
.ag-action {
  display: flex;
  align-items: center;
  gap: var(--cw-s4);
  padding: var(--cw-s4);
  border: 1px solid var(--cw-border);
  border-left-width: 3px;
  border-radius: var(--cw-r-lg);
  background: var(--cw-surface);
  /* An action card is text beside a button. At phone width the button drops
     below the text rather than squeezing it to one word per line. */
  flex-wrap: wrap;
}
/* The left rule carries the tone. A full tinted card for every notice turns the
   page into a warning; a rule marks severity without shouting. */
.ag-action-info   { border-left-color: var(--cw-info); }
.ag-action-warn   { border-left-color: var(--cw-warn); }
.ag-action-danger { border-left-color: var(--cw-danger); }
.ag-action-body   { flex: 1; min-width: 0; }
.ag-action-title  { font: var(--cw-heading); color: var(--cw-ink); }
.ag-action-text   { font: var(--cw-small); color: var(--cw-text-soft); margin: var(--cw-s1) 0 0; }
/* ── Stats ───────────────────────────────────────────────────────────────── */

.ag-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--cw-s3);
  margin-bottom: var(--cw-s7);
}
.ag-stats-sm { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.ag-stat {
  background: var(--cw-surface); border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-lg); padding: var(--cw-s5);
  transition: border-color var(--cw-t-micro), box-shadow var(--cw-t-micro);
}
.ag-stat:hover { border-color: var(--cw-border-strong); box-shadow: var(--cw-e1); }
.ag-stat-of  { font: var(--cw-title); color: var(--cw-text-faint); }
.ag-stat-sub { font: var(--cw-micro); color: var(--cw-text-soft); margin-top: var(--cw-s1); }
/* Overview panels that share a row. auto-fit keeps them side by side on a
   laptop and stacks them on a phone without a media query. */
.ag-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: var(--cw-s4);
  align-items: start;
  margin-bottom: var(--cw-s7);
}
/* ── Capacity ────────────────────────────────────────────────────────────── */

/* Inside .ag-cols the grid gap owns the spacing; the margin would double it. */
.ag-cols > .ag-capacity, .ag-cols > .ag-infra { margin-bottom: 0; }
.ag-capacity, .ag-infra { margin-bottom: var(--cw-s7); }
.ag-capacity-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--cw-s4); margin-bottom: var(--cw-s4); flex-wrap: wrap;
}
.ag-meter {
  height: 10px;
  border-radius: var(--cw-r-full);
  background: var(--cw-surface-2);
  overflow: hidden;
}
.ag-meter-fill { display: block; height: 100%; border-radius: var(--cw-r-full);
                 transition: width var(--cw-t-std); }
/* Tone is carried by the BADGE text as well as this colour — the bar is never
   the only thing saying how full it is. */
.ag-meter-ok     { background: var(--cw-ok); }
.ag-meter-warn   { background: var(--cw-warn); }
.ag-meter-danger { background: var(--cw-danger); }
.ag-kv th { text-align: left; font: var(--cw-small); color: var(--cw-text-soft);
            font-weight: 450; width: 40%; }
/* KEY/VALUE TABLES CANNOT OVERFLOW, by construction.
 *
 * The other tables here are lists with an unknown number of columns, so they
 * scroll inside `.ag-scroll`. A kv table has exactly two columns and one value
 * per row, and a horizontal scrollbar under two rows is worse than a wrapped
 * hostname — so these contain their content instead of scrolling it. Fixed
 * layout stops a long value from widening the column; `anywhere` lets a
 * hostname or an identifier break rather than push the page sideways. */
/* REASSURANCE, NOT A WARNING. `.ag-note` is warn-toned; using it to say "there
   are no nameservers to change" would colour a calming answer as an alarm. */
/* A CHOICE BETWEEN TWO REAL TRADE-OFFS, not a settings row. Each option is a
   full-width card so the consequence text is read before the radio is clicked —
   the whole point is that one of these moves the agency's email. */
.ag-choice { border: 0; margin: 0; padding: 0; }
.ag-choice legend { padding: 0; margin-bottom: var(--cw-s2); }
.ag-choice-opt {
  display: flex; gap: var(--cw-s3); align-items: flex-start;
  border: 1px solid var(--cw-border); border-radius: var(--cw-r-md);
  padding: var(--cw-s4); margin-bottom: var(--cw-s2); cursor: pointer;
}
.ag-choice-opt:hover { border-color: var(--cw-border-strong); }
/* :has() so the whole card reads as selected, with a plain border fallback for
   browsers without it — the radio itself is always the source of truth. */
.ag-choice-opt:has(input:checked) { border-color: var(--cw-accent); background: var(--cw-bg); }
.ag-choice-opt:has(input:disabled) { opacity: .6; cursor: not-allowed; }
.ag-choice-opt:focus-within { outline: 2px solid var(--cw-accent); outline-offset: 2px; }
.ag-choice-opt input { margin-top: 3px; flex: none; }
.ag-choice-body { display: flex; flex-direction: column; gap: var(--cw-s1); min-width: 0; }
.ag-choice-title { font: var(--cw-body); font-weight: 600; color: var(--cw-ink);
                   display: flex; align-items: center; gap: var(--cw-s2); flex-wrap: wrap; }
.ag-choice-note { font: var(--cw-small); color: var(--cw-text-soft); }
.ag-choice-why  { font: var(--cw-small); color: var(--cw-danger); }

.ag-explain {
  border: 1px solid var(--cw-border);
  background: var(--cw-bg);
  border-radius: var(--cw-r-md);
  padding: var(--cw-s4);
  display: flex; flex-direction: column; gap: var(--cw-s3);
}
.ag-explain p { margin: 0; }

.ag-kv { width: 100%; table-layout: fixed; }
.ag-kv th, .ag-kv td { overflow-wrap: anywhere; }
.ag-kv td { font: var(--cw-body); color: var(--cw-ink); }
/* The note beside a derived value names the screen that owns it. Its own line,
   because a value and its explanation read as one string otherwise. */
.ag-kv-note { display: block; font: var(--cw-small); color: var(--cw-text-soft);
              margin-top: 2px; }
/* !important kept from the rule this consolidates: `.ag-empty-state .cw-btn`
   is more specific and would otherwise win. Same 12px either way. */
.ag-spaced { margin-top: var(--cw-s3) !important; }
@media (max-width: 640px) {
  .ag-hero-title { font: var(--cw-title); }
  .ag-hero-actions { width: 100%; }
  .ag-hero-actions .cw-btn { flex: 1; }
  .ag-action { flex-direction: column; align-items: stretch; }
}
/* ═══════════════════════════════════════════════════════════════════════════
   LISTS (P5)
   ═══════════════════════════════════════════════════════════════════════════ */

.ag-lead { font: var(--cw-body); color: var(--cw-text-soft); margin: 0 0 var(--cw-s5); max-width: 68ch; }
.ag-search  { max-width: 320px; }
/* A wide table scrolls INSIDE its own container. The page body must never
   scroll sideways — a horizontal scrollbar on the document is how a layout
   tells you it has given up. */
.ag-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* A cell that behaves like a link but is a button, because it opens a panel
   rather than navigating. Semantics follow behaviour, not appearance. */
.ag-linkish {
  background: none; border: 0; padding: 0;
  font: inherit; color: var(--cw-accent);
  cursor: pointer; text-align: left;
}
.ag-linkish:hover { text-decoration: underline; }
.ag-linkish:focus-visible { outline: 2px solid var(--cw-accent); outline-offset: 2px; border-radius: 2px; }
.ag-dim { opacity: .6; }
.ag-row-actions { text-align: right; white-space: nowrap; }
.ag-muted { color: var(--cw-text-soft); }
/* `.cw-empty` already sets this exact padding and centring, at the same
   specificity and earlier in source order — so this restated it and changed
   nothing. The child rule below is kept: spacing a button inside the empty
   state is layout the component does not own. */
.ag-empty-state .cw-btn { margin-top: var(--cw-s4); }
.ag-panel-stack { display: flex; flex-direction: column; gap: var(--cw-s6); }
/* The custom-domain panel: short paragraphs, a records table, a button row. */
.ag-dom p { margin: 0 0 10px; }
.ag-dom-scroll { overflow-x: auto; margin: 4px 0 12px; }
.ag-dom-table td, .ag-dom-table th { white-space: nowrap; }
.ag-dom-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
/* The client card's website list: name over address, actions on the right.
   A list, not a table — the card is 400px wide and there are two actions. */
.ag-client-sites { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
.ag-client-site {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-rows: auto auto;
  column-gap: 12px; row-gap: 2px; align-items: center;
  padding: 10px 0; border-top: 1px solid var(--cw-border);
}
.ag-client-site:first-child { border-top: 0; padding-top: 0; }
.ag-client-site-n { grid-column: 1; grid-row: 1; font-weight: 600; overflow-wrap: anywhere; }
.ag-client-site-d { grid-column: 1; grid-row: 2; font: var(--cw-micro); color: var(--cw-text-soft); overflow-wrap: anywhere; }
.ag-client-site-a { grid-column: 2; grid-row: 1 / span 2; display: flex; gap: 6px; align-items: center; white-space: nowrap; }
.ag-plain-list { margin: 0; padding-left: var(--cw-s5); font: var(--cw-body); color: var(--cw-text); }
/* .cw-skip-label MOVED to console.css.
   It is a `cw-` utility — the shared vocabulary — but it was only ever defined
   here, so the client portal used it and got a plainly visible label instead of
   a hidden one. A class that exists in one app's sheet and is used from another
   fails silently; it belongs with the rest of the shared primitives. */
@media (max-width: 640px) {
  .ag-search { max-width: none; width: 100%; }
}
/* ═══════════════════════════════════════════════════════════════════════════
   SITES + GUIDED CREATION (P6)
   ═══════════════════════════════════════════════════════════════════════════ */

.ag-toolbar { display: flex; align-items: center; gap: var(--cw-s2);
              margin-bottom: var(--cw-s4); flex-wrap: wrap; }
.ag-filters { flex: 1; min-width: 0; overflow-x: auto; }
/* `display: block` carried over from the rule this replaces — .ag-sub is a
   sub-label inside a table cell and must sit on its own line. */
.ag-sub { display: block; font: var(--cw-micro); color: var(--cw-text-soft);
          margin-top: 2px; }
.ag-spaced-b { margin-bottom: var(--cw-s5); }
/* ── Wizard ──────────────────────────────────────────────────────────────── */

.ag-wizard { display: flex; flex-direction: column; gap: var(--cw-s6); }
.ag-steps {
  display: flex;
  gap: var(--cw-s2);
  list-style: none;
  margin: 0;
  padding: 0;
  flex-wrap: wrap;
}
.ag-step {
  display: flex; align-items: center; gap: var(--cw-s2);
  font: var(--cw-micro); color: var(--cw-text-faint);
}
.ag-step + .ag-step::before {
  content: ''; width: 16px; height: 1px; background: var(--cw-border); display: block;
}
.ag-step-n {
  display: grid; place-items: center;
  width: 20px; height: 20px; border-radius: var(--cw-r-full);
  background: var(--cw-surface-2); color: var(--cw-text-soft);
  font: var(--cw-micro);
}
/* Current and completed are distinguished by SHAPE and text, not colour alone:
   the tick is readable without colour vision. */
.ag-step.current { color: var(--cw-ink); font-weight: 600; }
.ag-step.current .ag-step-n { background: var(--cw-accent); color: var(--cw-accent-ink); }
.ag-step.done .ag-step-n    { background: var(--cw-ok-bg); color: var(--cw-ok); }
.ag-step-body { display: flex; flex-direction: column; gap: var(--cw-s5); }
.ag-step-error { margin: 0; }
.ag-progress { display: flex; flex-direction: column; gap: var(--cw-s3); }
.ag-progress p { margin: 0; }
@media (max-width: 640px) {
  .ag-steps { gap: var(--cw-s1); }
  .ag-step-l { display: none; }          /* numbers alone on a narrow screen */
  .ag-step.current .ag-step-l { display: inline; }
}
/* ═══════════════════════════════════════════════════════════════════════════
   BILLING (P7)

   Plan, Infrastructure and Capacity are separate CARDS, not rows of one table.
   The brief is explicit that the three concepts must not merge, and a shared
   table is exactly how they merge.
   ═══════════════════════════════════════════════════════════════════════════ */

.ag-bill-card { margin-bottom: var(--cw-s5); }
.ag-bill-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--cw-s4); flex-wrap: wrap; margin-bottom: var(--cw-s4);
}
.ag-bill-figure {
  display: flex; align-items: baseline; gap: var(--cw-s3);
  flex-wrap: wrap; margin-bottom: var(--cw-s4);
}
.ag-bill-tier  { font: var(--cw-title); color: var(--cw-ink); }
.ag-bill-price { font: var(--cw-body); color: var(--cw-text-soft); }
/* ═══════════════════════════════════════════════════════════════════════════
   SETUP SEQUENCE (P8)

   The wizard's step chrome, re-used vertically for onboarding. Infrastructure
   and domain are steps of ONE setup, not two unrelated errors, and reading them
   as a sequence is what answers "what is left?".
   ═══════════════════════════════════════════════════════════════════════════ */

.ag-setup {
  flex-direction: column;
  align-items: stretch;
  gap: var(--cw-s3);
  padding: var(--cw-s5);
  background: var(--cw-surface);
  border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-lg);
  margin-bottom: var(--cw-s6);
}
.ag-setup .ag-step { align-items: flex-start; }
.ag-setup .ag-step + .ag-step::before { content: none; }   /* vertical: no connector rule */
.ag-setup-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ag-setup-note { font: var(--cw-micro); color: var(--cw-text-soft); }
/* A step still to do is dimmed but never hidden — the sequence is the point. */
.ag-setup .ag-step.todo { opacity: .65; }
/* Inline code, from the platform's own type scale. */
.cw-code {
  font: var(--cw-micro);
  font-family: var(--cw-font-mono);
  background: var(--cw-surface-2);
  border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-sm);
  padding: 1px 5px;
  /* A hostname is the longest unbreakable string either app renders, and it is
     rendered inside a table cell. `nowrap` made a long one push the whole page
     sideways at 375px — the nav went with it. Breaking mid-hostname is ugly;
     a horizontally scrolling app is broken. */
  overflow-wrap: anywhere;
}

/* The `ag-btn` rules are gone: every button in the agency app is now a
   `cw-btn`, so a second button dialect no longer exists to drift from the
   first. */

/* ══════════════════════════════════════════════════════════════════════════
   AGENCY UI — the product layer (redesign)

   The shell is /dashboard's (dash-shell / dash-sidebar / cw-header). What
   follows is the AGENCY's own information architecture on top of it: page
   headers, metric tiles, the capacity meter, data tables with real hierarchy,
   segmented filters, template cards, skeletons and empty states.

   Everything resolves against the console tokens (--cw-*) and the legacy
   dashboard vars, so this cannot drift into a fourth palette.

   The rule this layer exists to fix: a row is not a database record. Each row
   identifies a THING — a website, a client, a person — and the eye should find
   it without reading left to right.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── page header ───────────────────────────────────────────────────────── */
.ax-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--cw-s5); flex-wrap: wrap; margin-bottom: var(--cw-s6);
}
.ax-head-t {
  font: var(--cw-display); font-family: "Bricolage Grotesque", var(--cw-font);
  letter-spacing: -.02em; color: var(--cw-ink); margin: 0; text-wrap: balance;
}
.ax-head-s { font: var(--cw-body); color: var(--cw-text-soft); margin: 6px 0 0; max-width: 62ch; }
.ax-head-a { display: flex; gap: var(--cw-s2); flex-wrap: wrap; flex-shrink: 0; }

/* A compact page header for secondary screens — same family, less air. */
.ax-head-sm .ax-head-t { font: var(--cw-title); font-family: "Bricolage Grotesque", var(--cw-font); }

/* ── attention strip ───────────────────────────────────────────────────── */
/* One line when healthy, a card when not. Never a full-width green banner:
   an interface that congratulates you on normality trains you to skim the
   place a real warning will appear. */
.ax-calm {
  display: flex; align-items: center; gap: var(--cw-s2);
  font: var(--cw-small); color: var(--cw-text-soft); margin-bottom: var(--cw-s5);
}
.ax-calm svg { color: var(--cw-ok); flex: none; }
.ax-alerts { display: flex; flex-direction: column; gap: var(--cw-s2); margin-bottom: var(--cw-s5); }
.ax-alert {
  display: flex; align-items: center; gap: var(--cw-s4); padding: var(--cw-s4) var(--cw-s5);
  background: var(--cw-surface); border: 1px solid var(--cw-border);
  border-left: 3px solid var(--cw-border-strong); border-radius: var(--cw-r-lg); flex-wrap: wrap;
}
.ax-alert-warn   { border-left-color: var(--cw-warn); }
.ax-alert-danger { border-left-color: var(--cw-danger); }
.ax-alert-info   { border-left-color: var(--cw-info); }
.ax-alert-ico { flex: none; display: grid; place-items: center; width: 32px; height: 32px; border-radius: var(--cw-r-md); }
.ax-alert-warn   .ax-alert-ico { background: var(--cw-warn-bg);   color: var(--cw-warn); }
.ax-alert-danger .ax-alert-ico { background: var(--cw-danger-bg); color: var(--cw-danger); }
.ax-alert-info   .ax-alert-ico { background: var(--cw-info-bg);   color: var(--cw-info); }
.ax-alert-b { flex: 1; min-width: 0; }
.ax-alert-t { font: var(--cw-heading); color: var(--cw-ink); }
.ax-alert-p { font: var(--cw-small); color: var(--cw-text-soft); margin: 2px 0 0; }

/* ── metric tiles ──────────────────────────────────────────────────────── */
.ax-tiles {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--cw-s3); margin-bottom: var(--cw-s5);
}
.ax-tile {
  background: var(--cw-surface); border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-lg); padding: var(--cw-s5);
  display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.ax-tile-l {
  font: 600 11px/16px var(--cw-font); text-transform: uppercase; letter-spacing: .07em;
  color: var(--cw-text-soft);
}
.ax-tile-v {
  font: 700 30px/36px "Bricolage Grotesque", var(--cw-font); color: var(--cw-ink);
  font-variant-numeric: tabular-nums; letter-spacing: -.02em; margin-top: 4px;
}
.ax-tile-of { font: var(--cw-title); color: var(--cw-text-faint); }
.ax-tile-s  { font: var(--cw-micro); font-weight: 450; color: var(--cw-text-soft); margin-top: 2px; }
.ax-tile-s-ok   { color: var(--cw-ok); }
.ax-tile-s-warn { color: var(--cw-warn); }
.ax-tile-s-bad  { color: var(--cw-danger); }

/* ── section + card ────────────────────────────────────────────────────── */
.ax-sec { margin-bottom: var(--cw-s6); }
.ax-sec-h {
  display: flex; align-items: center; gap: var(--cw-s2);
  margin-bottom: var(--cw-s3); flex-wrap: wrap;
}
.ax-sec-t { font: var(--cw-heading); color: var(--cw-ink); }
.ax-sec-n {
  font: var(--cw-micro); color: var(--cw-text-soft); background: var(--cw-surface-2);
  border: 1px solid var(--cw-border); border-radius: var(--cw-r-full); padding: 1px 8px;
}
.ax-sec-a { margin-left: auto; display: flex; gap: var(--cw-s2); }
.ax-card {
  background: var(--cw-surface); border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-lg); overflow: hidden;
}
.ax-card-p { padding: var(--cw-s5); }
.ax-card-h {
  display: flex; align-items: center; gap: var(--cw-s2);
  padding: var(--cw-s4) var(--cw-s5); border-bottom: 1px solid var(--cw-border);
}
.ax-card-t { font: var(--cw-heading); color: var(--cw-ink); }
.ax-card-a { margin-left: auto; }
/* STRETCH, not start. Side-by-side cards that end at different heights read as
   two unrelated things that happen to be adjacent; a shared bottom edge is what
   makes them a pair. `align-items: start` was leaving the shorter card floating
   and a ragged gap under it — most visibly on Settings, where Agency name sat
   well short of Client portal beside it.

   A card that is now taller than its content uses .ax-card-fill to put its
   action on the bottom edge, so stretching adds purpose rather than padding. */
.ax-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--cw-s3); align-items: stretch; }

/* ── capacity ──────────────────────────────────────────────────────────── */
.ax-cap-top { display: flex; align-items: baseline; gap: var(--cw-s2); flex-wrap: wrap; }
.ax-cap-n { font: 700 26px/30px "Bricolage Grotesque", var(--cw-font); color: var(--cw-ink); font-variant-numeric: tabular-nums; }
.ax-cap-of { font: var(--cw-body); color: var(--cw-text-soft); }
.ax-cap-badge { margin-left: auto; }
.ax-meter { height: 8px; border-radius: var(--cw-r-full); background: var(--cw-surface-2); overflow: hidden; margin: var(--cw-s3) 0 var(--cw-s2); }
.ax-meter > span { display: block; height: 100%; border-radius: var(--cw-r-full); transition: width var(--cw-t-std); }
.ax-meter-ok { background: var(--cw-ok); }
.ax-meter-warn { background: var(--cw-warn); }
.ax-meter-danger { background: var(--cw-danger); }
/* Split fill: agency-owned vs client sites, so capacity reads as a portfolio
   rather than a storage quota.

   THE CHILD COMBINATOR IS LOAD-BEARING. `.ax-meter > span` scores 0,1,1 and
   this rule scored 0,1,0, so `display: block` won and the two segments stacked
   VERTICALLY inside an 8px bar instead of sitting side by side. An agency with
   no sites of its own got a zero-width first segment that still claimed the
   full height, pushing the client segment past `overflow: hidden` — a capacity
   bar that read as completely empty while 2 of 25 sites were in use. */
.ax-meter > .ax-meter-split { display: flex; height: 100%; }
.ax-meter-split i { display: block; height: 100%; }
.ax-meter-own { background: var(--cw-accent); }
/* The second chart step from tokens.css — not a new colour. */
.ax-meter-client { background: var(--cw-chart-2); }
.ax-legend { display: flex; gap: var(--cw-s4); flex-wrap: wrap; font: var(--cw-micro); font-weight: 450; color: var(--cw-text-soft); }
.ax-legend b { display: inline-flex; align-items: center; gap: 6px; font-weight: 450; }
.ax-legend i { width: 8px; height: 8px; border-radius: 3px; flex: none; }
.ax-note { font: var(--cw-micro); font-weight: 450; color: var(--cw-text-soft); margin-top: var(--cw-s3); }

/* ── toolbar: search + segmented filters ───────────────────────────────── */
.ax-bar { display: flex; gap: var(--cw-s2); align-items: center; flex-wrap: wrap; margin-bottom: var(--cw-s4); }
.ax-search { position: relative; flex: 1 1 260px; min-width: 200px; max-width: 380px; }
.ax-search svg { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--cw-text-faint); pointer-events: none; }
.ax-search input {
  width: 100%; height: 38px; padding: 0 12px 0 34px;
  border: 1px solid var(--cw-border-strong); border-radius: var(--cw-r-md);
  background: var(--cw-surface); color: var(--cw-ink); font: var(--cw-small); font-family: inherit;
}
.ax-search input::placeholder { color: var(--cw-text-faint); }
.ax-search input:focus { outline: 2px solid var(--cw-accent); outline-offset: 1px; border-color: var(--cw-accent); }
/* A segmented control, not a <select>. The options are few and worth seeing. */
.ax-seg {
  display: inline-flex; gap: 2px; padding: 3px; background: var(--cw-surface-2);
  border: 1px solid var(--cw-border); border-radius: var(--cw-r-md); overflow-x: auto; max-width: 100%;
}
.ax-seg button {
  appearance: none; border: 0; background: transparent; cursor: pointer; white-space: nowrap;
  font: var(--cw-small); font-weight: 500; font-family: inherit; color: var(--cw-text-soft);
  padding: 6px 12px; border-radius: var(--cw-r-sm); transition: background var(--cw-t-micro), color var(--cw-t-micro);
}
.ax-seg button:hover { color: var(--cw-ink); }
.ax-seg button.on { background: var(--cw-surface); color: var(--cw-ink); font-weight: 600; box-shadow: var(--cw-e1); }
.ax-seg button:focus-visible { outline: 2px solid var(--cw-accent); outline-offset: 1px; }
.ax-bar-end { margin-left: auto; display: flex; gap: var(--cw-s2); align-items: center; }

/* ══ SITES WORKSPACE ═══════════════════════════════════════════════════════
   The agency's website list. A management screen, so density is the point:
   25 sites should be scannable without scrolling past card after card.
   ══════════════════════════════════════════════════════════════════════════ */

/* Capacity: one line above the list it constrains, not a panel competing with
   it. Said once — the number used to appear three times on this screen. */
.ax-capbar {
  padding: var(--cw-s4) var(--cw-s5);
  background: var(--cw-surface); border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-lg); margin-bottom: var(--cw-s4);
}
.ax-capbar-h { display: flex; align-items: baseline; gap: var(--cw-s2); flex-wrap: wrap; }
.ax-capbar-n {
  font: 700 22px/26px "Bricolage Grotesque", var(--cw-font);
  color: var(--cw-ink); font-variant-numeric: tabular-nums;
}
.ax-capbar-of { font-weight: 500; color: var(--cw-text-soft); margin-left: 4px; }
.ax-capbar-l  { font: var(--cw-small); color: var(--cw-text-soft); }
.ax-capbar-end { margin-left: auto; }
.ax-capbar .ax-meter  { margin: var(--cw-s3) 0 var(--cw-s2); }
/* Used capacity this screen cannot attribute to agency or client. Neutral on
   purpose: it is a gap in what we can classify, not a third kind of site. */
.ax-meter-rest { background: var(--cw-border-strong); }
.ax-legend-mute { color: var(--cw-text-faint); }

/* Filter pills — product controls, not browser tabs. The count is part of the
   control: an agency wants to know how many are offline before clicking. */
.ax-pills { display: flex; gap: var(--cw-s2); flex-wrap: wrap; }
.ax-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px var(--cw-s3); border-radius: var(--cw-r-full);
  background: var(--cw-surface); border: 1px solid var(--cw-border);
  font: var(--cw-small); font-weight: 500; color: var(--cw-text-soft);
  cursor: pointer; transition: background var(--cw-t-fast), border-color var(--cw-t-fast);
}
.ax-pill:hover { background: var(--cw-surface-2); color: var(--cw-ink); }
.ax-pill.on {
  background: var(--cw-accent-subtle); border-color: var(--cw-accent);
  color: var(--cw-accent); font-weight: 600;
}
.ax-pill:focus-visible { outline: 2px solid var(--cw-accent); outline-offset: 1px; }
.ax-pill-n {
  font: var(--cw-micro); font-weight: 600; font-variant-numeric: tabular-nums;
  padding: 1px 6px; border-radius: var(--cw-r-full);
  background: var(--cw-surface-2); color: var(--cw-text-soft);
}
.ax-pill.on .ax-pill-n { background: var(--cw-surface); color: var(--cw-accent); }

/* Filters and search share a line; search takes the slack rather than a fixed
   width, so the row neither squeezes at 1366 nor strands space at 1920. */
.ax-bar-sites { align-items: center; gap: var(--cw-s3); }
.ax-bar-sites .ax-bar-end { flex: 1; justify-content: flex-end; min-width: 200px; }
.ax-bar-sites .ax-search  { width: 100%; max-width: 320px; }

/* ── the site list ─────────────────────────────────────────────────────────
   A resource list, not a spreadsheet. The column headers stay for orientation
   and screen readers but recede — they were the loudest thing on the old
   screen, which is most of why it read as an admin table. A deliberate column
   system gives the SITE the space it needs and stops the row ending in a wide
   empty gap.                                                                */
.ax-t-sites { table-layout: fixed; }
.ax-t-sites thead th {
  font-size: 10px; letter-spacing: .08em; color: var(--cw-text-faint);
  font-weight: 600; padding-bottom: var(--cw-s2);
}
/* The action column is sized to its CONTENT, measured: Open + its arrow + the
   overflow button come to 116px, and a 14% column left a 94px content box —
   so the row overflowed by 2px and `.ax-tw` showed a horizontal scrollbar.
   Reserving the arrow's space (rather than animating it in) is what made the
   button permanently wider, so the column has to account for it. */
/* THE ACTIONS COLUMN IS NOT A PROPORTION OF ANYTHING.
   It was 16%, which is fine at 960px and wrong at 679: the cell was handed
   109px to hold an Open button, a 6px gap, a 36px overflow button and 20px
   of padding — 135px of content. table-layout:fixed honoured the percentage,
   the buttons overflowed the cell, and .ax-tw grew a horizontal scrollbar in
   a band roughly 940–1040px wide (below 900 the sidebar becomes a drawer and
   the content is roomy again, which is why it was invisible at both the
   desktop and the mobile sizes anyone would test).

   Its contents do not scale with the viewport, so neither should it. Fixed
   px for the actions, and the SITE column takes the remainder — the name is
   the thing worth giving slack to, and it is the one that truncates. Owner
   and status keep their proportions. */
.ax-t-sites .ax-c-site { width: auto; }
.ax-t-sites .ax-c-own  { width: 22%; }
.ax-t-sites .ax-c-st   { width: 18%; }
.ax-t-sites .ax-c-act  { width: 136px; }
/* No left padding on the action cell: it is right-aligned, so the gap belongs
   between the status and the buttons, not inside the cell. */
.ax-t-sites tbody td.ax-c-act, .ax-t-sites thead th.ax-c-act { padding-left: 0; }

/* Rows feel interactive without pretending to be buttons. */
.ax-t-sites tbody tr { transition: background var(--cw-t-fast); }
.ax-t-sites tbody tr:hover { background: var(--cw-surface-2); }
.ax-t-sites tbody tr:hover .ax-sid-n { color: var(--cw-accent); }
.ax-t-sites tbody tr:hover .ax-sid-m {
  background: var(--cw-accent); color: var(--cw-accent-ink);
}

/* The primary action carries a direction, revealed on hover so a column of
   arrows does not compete with the site names for attention. */
.ax-open { display: inline-flex; align-items: center; gap: 4px; }
/* OPACITY ONLY — never layout.
   This animated `margin-left` from -4px to 0, so hovering a row made it 4px
   wider. Past the fixed column width that overflowed `.ax-tw`, and a horizontal
   scrollbar appeared under the table for as long as the pointer was over a row.
   The arrow now always occupies its space and only fades, so hover changes
   nothing about the layout. */
.ax-open-i { opacity: 0; transition: opacity var(--cw-t-fast); }
.ax-t-sites tbody tr:hover .ax-open-i { opacity: 1; }

/* Ownership: relationship first in a quiet label, then WHO in the line that
   carries the weight. A column of these scans as a list of clients. */
.ax-ob { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.ax-ob-l {
  font: 600 10px/14px var(--cw-font); letter-spacing: .07em; text-transform: uppercase;
  color: var(--cw-text-faint);
}
.ax-ob-l-agency { color: var(--cw-accent); }
.ax-ob-n {
  font: var(--cw-small); color: var(--cw-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ax-ob-mute .ax-ob-n { color: var(--cw-text-soft); }

/* Site identity: the SUBJECT of the row. Name leads, address supports,
   ownership sits with it so the relationship survives when the Belongs-to
   column drops on narrow screens. */
.ax-t-sites tbody td { padding-top: var(--cw-s3); padding-bottom: var(--cw-s3); }
/* CONSTANT ROW RHYTHM, whatever the data says.
   The identity block is two lines (name + host) for most rows and ONE for a
   site created before the wizard asked for a name — the host is promoted to
   the title and there is nothing to print underneath. Those rows measured
   61px against everyone else s 68, so the list rippled wherever an older site
   sat. min-height is the two-line block (22 + 1 + 21), which the monogram s
   36px does not reach on its own; align-items keeps the short row centred in
   it rather than top-aligned in a taller box. */
.ax-sid { display: flex; align-items: center; gap: var(--cw-s3); min-width: 0;
          min-height: 44px; }
.ax-sid-m {
  flex: none; width: 36px; height: 36px; border-radius: var(--cw-r-md);
  display: grid; place-items: center;
  background: var(--cw-accent-subtle); color: var(--cw-accent);
  font: 600 12px/1 var(--cw-font); letter-spacing: .02em;
}
.ax-sid-b { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.ax-sid-n {
  font: var(--cw-body); font-weight: 600; color: var(--cw-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
a.ax-sid-n { text-decoration: none; color: var(--cw-ink); }
.ax-sid-n:hover { color: var(--cw-accent); text-decoration: underline; }
.ax-sid-d {
  font: var(--cw-small); color: var(--cw-text-soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* HIDDEN WHILE THE "Belongs to" COLUMN IS THERE.
   Ownership belongs in one place per row: printing it in the identity cell AND
   in its own column said the same thing twice and cost every row a third line.
   This compact form appears only when that column is dropped (below 860px),
   which is exactly where the agency's most important distinction would
   otherwise disappear. */
.ax-sid-o { display: none; }
@media (max-width: 860px) {
  .ax-sid-o {
    display: flex; align-items: center; gap: 6px;
    font: var(--cw-micro); color: var(--cw-text-soft); margin-top: 2px;
  }
  /* Three lines here, not two: the owner line rejoins the identity block
     once its column is gone, so the floor that keeps rows level has to rise
     with it. Without this the same one-line rows that rippled the desktop
     list rippled this one, 20px short instead of 7. */
  .ax-sid { min-height: 64px; }
}
/* The ownership tag reads as a word first; colour only reinforces it. */
.ax-sid-tag {
  font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  padding: 1px 5px; border-radius: var(--cw-r-sm);
  background: var(--cw-surface-2); color: var(--cw-text-soft);
}
.ax-sid-tag-agency { background: var(--cw-accent-subtle); color: var(--cw-accent); }
.ax-sid-tag-mute   { background: var(--cw-surface-2); color: var(--cw-text-faint); }

/* A removed site stays listed but stops competing for attention. */
.ax-row-mute .ax-sid-n, .ax-row-mute .ax-sid-d { color: var(--cw-text-soft); }
.ax-row-mute .ax-sid-m { background: var(--cw-surface-2); color: var(--cw-text-faint); }

/* The overflow control is a target, not a decoration. */
.ax-more { min-width: 32px; }

@media (max-width: 640px) {
  /* Rows are already cards at this width (see the table rules below); the
     identity block keeps its shape so the site is still the subject. */
  .ax-bar-sites .ax-bar-end { min-width: 0; }
  .ax-bar-sites .ax-search  { max-width: none; }
  .ax-capbar-end { margin-left: 0; width: 100%; }
}

/* ── stacked cards ─────────────────────────────────────────────────────────
   Two cards in a row need air between them. `.ax-sec` only ever set
   margin-BOTTOM, so a card that followed another had nothing above it: on the
   Clients screen the table card and the client-logins card met with a 0px gap,
   and because `.ax-card` clips its overflow the last table row landed flush on
   that seam and read as cut in half.

   An adjacent-sibling rule fixes every such pair at once rather than each
   screen remembering a margin. Views that set their own inline margin-top still
   win, so nothing doubles up.                                                */
.ax-card + .ax-card { margin-top: var(--cw-s4); }
/* ...but NOT side by side in a grid, where `gap` already does the spacing.
   Two cards in .ax-cols are adjacent siblings too, so the rule above was
   pushing the right-hand card down by 16px — the pair ended level only because
   stretch made up the difference, leaving their tops misaligned instead. */
.ax-cols > .ax-card + .ax-card { margin-top: 0; }

/* ── data table ────────────────────────────────────────────────────────── */
.ax-tw { overflow-x: auto; -webkit-overflow-scrolling: touch; }
/* A table that ENDS a card keeps its last row off the clipped edge. */
.ax-card > .ax-tw:last-child { padding-bottom: var(--cw-s2); }
.ax-t { width: 100%; border-collapse: collapse; font: var(--cw-small); }
.ax-t thead th {
  text-align: left; font: 600 11px/16px var(--cw-font); letter-spacing: .06em;
  /* TOP PADDING TO MATCH THE BODY. This was `0`, so the header label sat flush
     against the card's top edge while every row below it had breathing room —
     the header read as cramped and slightly detached from its own table. Shared
     by Sites, Clients, Team and Recent sites, which all showed it. */
  text-transform: uppercase; color: var(--cw-text-soft);
  padding: var(--cw-s3) var(--cw-s5) var(--cw-s2);
  white-space: nowrap; border-bottom: 1px solid var(--cw-border);
}
.ax-t tbody td { padding: var(--cw-s3) var(--cw-s5); border-bottom: 1px solid var(--cw-surface-2); vertical-align: middle; }
.ax-t tbody tr:last-child td { border-bottom: 0; }
.ax-t tbody tr { transition: background var(--cw-t-micro); }
.ax-t tbody tr:hover { background: var(--cw-bg); }
/* The actions column takes only the width its buttons need.
   `width: 1%` is the table idiom for "shrink to content": without it the column
   claimed an equal share of the table and, on a Team screen holding just the
   owner — who has no Remove button — that was ~200px of empty cells sitting
   beside the data, reading as a broken or unfinished column. */
.ax-t-end { text-align: right; white-space: nowrap; width: 1%; }

/* The identity cell — an avatar/mark plus two lines. This is what stops a row
   reading as a spreadsheet record. */
.ax-id { display: flex; align-items: center; gap: var(--cw-s3); min-width: 0; }
.ax-mark {
  flex: none; width: 34px; height: 34px; border-radius: var(--cw-r-md);
  display: grid; place-items: center; font: 600 13px/1 var(--cw-font);
  background: var(--cw-accent-subtle); color: var(--cw-accent);
  border: 1px solid var(--cw-accent-border); text-transform: uppercase;
}
.ax-mark-n { background: var(--cw-surface-2); color: var(--cw-text-soft); border-color: var(--cw-border); }
.ax-id-b { min-width: 0; }
.ax-id-t { font: var(--cw-body); font-weight: 600; color: var(--cw-ink); overflow-wrap: anywhere; }
.ax-id-t a { color: inherit; text-decoration: none; }
.ax-id-t a:hover { color: var(--cw-accent); }
.ax-id-s { font: var(--cw-micro); font-weight: 450; color: var(--cw-text-soft); margin-top: 1px; overflow-wrap: anywhere; }

/* Ownership, said not implied. */
.ax-own { display: inline-flex; align-items: center; gap: 6px; font: var(--cw-small); color: var(--cw-text); }
.ax-own-s { display: block; font: var(--cw-micro); font-weight: 450; color: var(--cw-text-soft); margin-top: 1px; }

/* Status: dot + word. Never colour alone. */
.ax-st { display: inline-flex; align-items: center; gap: 7px; font: var(--cw-small); font-weight: 600; white-space: nowrap; }
.ax-st i { width: 7px; height: 7px; border-radius: 50%; flex: none; background: currentColor; }
.ax-st-ok   { color: var(--cw-ok); }
.ax-st-warn { color: var(--cw-warn); }
.ax-st-bad  { color: var(--cw-danger); }
.ax-st-mute { color: var(--cw-text-soft); }
.ax-st-info { color: var(--cw-accent); }
.ax-st-why  { display: block; font: var(--cw-micro); font-weight: 450; color: var(--cw-text-soft); margin-top: 1px; }
/* A breathing dot for work in progress — reduced-motion aware via tokens.css. */
.ax-st-live i { animation: ax-pulse 2s ease-in-out infinite; }
@keyframes ax-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

.ax-row-a { display: inline-flex; gap: 6px; align-items: center; justify-content: flex-end; }

/* ── template browser (matches /dashboard's Launch a site) ─────────────── */
.ax-tpl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(232px, 1fr)); gap: var(--cw-s4); }
.ax-tpl {
  display: flex; flex-direction: column; text-align: left; padding: 0; cursor: pointer;
  background: var(--cw-surface); border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-lg); overflow: hidden; font-family: inherit;
  transition: border-color var(--cw-t-micro), box-shadow var(--cw-t-micro), transform var(--cw-t-micro);
}
.ax-tpl:hover { border-color: var(--cw-accent-border); box-shadow: var(--cw-e1); transform: translateY(-2px); }
.ax-tpl:focus-visible { outline: 2px solid var(--cw-accent); outline-offset: 2px; }
.ax-tpl-shot { position: relative; aspect-ratio: 16 / 10; background: var(--cw-surface-2); overflow: hidden; }
.ax-tpl-shot img { width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }
/* No screenshot in the catalogue → the template's initial, never a fake image. */
.ax-tpl-blank { width: 100%; height: 100%; display: grid; place-items: center;
                font: 700 26px/1 "Bricolage Grotesque", var(--cw-font); color: var(--cw-text-faint); }
/* "Coming soon" must not look like "Premium". One is a plan boundary the agency
   can cross by paying; the other is our build queue, and dressing it in the
   same badge would read as an upsell for something nobody can buy. */
.ax-tpl-flag-soon { background: var(--cw-surface-2); color: var(--cw-text-soft); }
.ax-tpl-flag {
  position: absolute; top: 8px; right: 8px;
  font: 600 10px/1 var(--cw-font); letter-spacing: .05em; text-transform: uppercase;
  background: var(--cw-ink); color: var(--cw-surface); padding: 5px 8px; border-radius: var(--cw-r-full);
  backdrop-filter: saturate(140%) blur(2px);
}
.ax-tpl-b { padding: var(--cw-s3) var(--cw-s4) var(--cw-s4); display: flex; flex-direction: column; gap: 3px; flex: 1; }
.ax-tpl-n { font: var(--cw-body); font-weight: 600; color: var(--cw-ink); }
.ax-tpl-c { font: var(--cw-micro); font-weight: 450; color: var(--cw-text-soft); text-transform: capitalize; }
.ax-tpl-f { margin-top: auto; padding-top: var(--cw-s3); display: flex; gap: 6px; align-items: center; }
.ax-tpl-locked { opacity: .72; }
.ax-tpl-locked:hover { transform: none; }
.ax-cats { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: var(--cw-s4); }
.ax-cat {
  appearance: none; cursor: pointer; font: var(--cw-small); font-family: inherit; font-weight: 500;
  padding: 6px 13px; border-radius: var(--cw-r-full);
  background: var(--cw-surface); border: 1px solid var(--cw-border); color: var(--cw-text-soft);
  transition: border-color var(--cw-t-micro), color var(--cw-t-micro), background var(--cw-t-micro);
}
.ax-cat:hover { color: var(--cw-ink); border-color: var(--cw-border-strong); }
.ax-cat.on { background: var(--cw-accent); border-color: var(--cw-accent); color: var(--cw-accent-ink); font-weight: 600; }
.ax-cat:focus-visible { outline: 2px solid var(--cw-accent); outline-offset: 2px; }

/* ── empty + error ─────────────────────────────────────────────────────── */
.ax-empty { text-align: center; padding: var(--cw-s9) var(--cw-s5); }
.ax-empty-i {
  width: 46px; height: 46px; margin: 0 auto var(--cw-s4); border-radius: var(--cw-r-lg);
  display: grid; place-items: center; background: var(--cw-surface-2); color: var(--cw-text-faint);
}
.ax-empty-t { font: var(--cw-heading); color: var(--cw-ink); }
.ax-empty-s { font: var(--cw-small); color: var(--cw-text-soft); max-width: 44ch; margin: 6px auto 0; }
.ax-empty-a { margin-top: var(--cw-s5); display: flex; gap: var(--cw-s2); justify-content: center; flex-wrap: wrap; }
.ax-empty-sm { padding: var(--cw-s7) var(--cw-s5); }

/* ── skeletons ─────────────────────────────────────────────────────────── */
.ax-sk {
  border-radius: var(--cw-r-sm); background: var(--cw-surface-2); position: relative; overflow: hidden;
}
.ax-sk::after {
  content: ''; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.55), transparent);
  animation: ax-shim 1.4s infinite;
}
@keyframes ax-shim { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .ax-sk::after { animation: none; } }
.ax-sk-line { height: 12px; margin: 9px 0; }
.ax-sk-tile { height: 96px; border-radius: var(--cw-r-lg); }
.ax-sk-row  { height: 58px; border-radius: var(--cw-r-md); margin-bottom: 8px; }
.ax-sk-tpl  { aspect-ratio: 16/10; border-radius: var(--cw-r-lg); }

/* ── DNS values ────────────────────────────────────────────────────────────
   A value someone retypes into a registrar's panel. It is the whole point of
   the screen, so it gets its own line at full width rather than a table cell
   that truncates — a half-shown nameserver is a support ticket.
   Stacked, not columnar, so nothing has to scroll sideways on a phone.        */
.ax-dns-set { display: flex; flex-direction: column; gap: var(--cw-s2); margin: var(--cw-s4) 0; }
.ax-dns {
  padding: var(--cw-s3) var(--cw-s4); background: var(--cw-surface-2);
  border: 1px solid var(--cw-border); border-radius: var(--cw-r-md);
}
.ax-dns-l {
  font: 600 11px/16px var(--cw-font); text-transform: uppercase; letter-spacing: .07em;
  color: var(--cw-text-soft);
}
.ax-dns-v { display: flex; align-items: center; gap: var(--cw-s2); margin-top: 3px; flex-wrap: wrap; }
/* The value may be a long nameserver; let it wrap rather than push the card. */
.ax-dns-v .cw-code { word-break: break-all; }
.ax-dns-n { font: var(--cw-small); color: var(--cw-text-soft); margin-top: 4px; }

/* A card that fills its grid track and keeps its action on the bottom edge.
   Grid siblings already stretch to equal height; without this the shorter
   card's content floats at the top and the two read as mismatched even though
   the boxes are the same size. */
.ax-card-fill { display: flex; flex-direction: column; }
.ax-card-foot { margin-top: auto; padding-top: var(--cw-s5); }

/* The template a wizard step is starting from — small, so it identifies the
   choice without turning a form step into a gallery. */
.ax-pick {
  display: flex; align-items: center; gap: var(--cw-s3);
  padding: var(--cw-s3); margin-bottom: var(--cw-s4);
  background: var(--cw-surface-2); border: 1px solid var(--cw-border);
  border-radius: var(--cw-r-md);
}
.ax-pick-shot, .ax-pick-blank {
  flex: none; width: 64px; height: 44px; border-radius: var(--cw-r-sm);
  object-fit: cover; object-position: top center;
  border: 1px solid var(--cw-border); background: var(--cw-surface);
}
.ax-pick-blank {
  display: grid; place-items: center;
  font: 600 13px/1 var(--cw-font); color: var(--cw-text-soft);
}
.ax-pick-b { display: flex; flex-direction: column; min-width: 0; }
.ax-pick-n { font: var(--cw-heading); color: var(--cw-ink); }
.ax-pick-c { font: var(--cw-micro); color: var(--cw-text-soft); text-transform: capitalize; }

/* Key/value table: the row header is a label, not a column heading. */
.ax-t-kv tbody th {
  text-align: left; font: var(--cw-small); font-weight: 500; color: var(--cw-text-soft);
  width: 42%; padding: var(--cw-s3) var(--cw-s3) var(--cw-s3) 0; vertical-align: top;
}

/* ── laptop-first responsive ───────────────────────────────────────────── */
@media (max-width: 1180px) {
  .ax-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ax-cols  { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 860px) {
  .ax-head-t { font: var(--cw-title); font-family: "Bricolage Grotesque", var(--cw-font); }
  .ax-head-a { width: 100%; }
  .ax-head-a .cw-btn { flex: 1; }
  /* Low-priority columns move to the detail rather than making the page
     scroll sideways. Marked in markup so the choice is explicit, not incidental. */
  .ax-col-lo { display: none; }
  .ax-search { max-width: none; }
}
@media (max-width: 640px) {
  .ax-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--cw-s2); }
  .ax-tile { padding: var(--cw-s4); }
  .ax-tile-v { font-size: 24px; line-height: 30px; }
  /* ONE column on a phone. A template is chosen by looking at its picture, and
     two 165px thumbnails side by side show neither — the narrower card was
     making the browser harder to use, not more compact. */
  .ax-tpl-grid { grid-template-columns: minmax(0, 1fr); gap: var(--cw-s3); }
  .ax-t thead { display: none; }
  /* Rows become cards on a phone. The identity leads, everything else stacks
     under it — a 5-column table at 375px is unreadable however it is styled. */
  .ax-t, .ax-t tbody, .ax-t tr, .ax-t td { display: block; width: 100%; }
  /* SPECIFICITY, not intent: the rule above scores 0,1,2 and `.ax-col-lo`
     scores 0,1,0, so turning rows into cards silently un-hid every column that
     had been dropped for narrow screens. On Sites that printed ownership twice
     — once in the identity block, once from the column that was supposed to be
     gone — and the long client name wrapped over four lines. */
  .ax-t tbody td.ax-col-lo { display: none; }
  /* Fixed column widths are meaningless once cells are blocks, and they were
     squeezing the name to ~97px inside a 375px card. */
  .ax-t-sites { table-layout: auto; }
  .ax-t-sites tbody td.ax-c-site,
  .ax-t-sites tbody td.ax-c-st,
  .ax-t-sites tbody td.ax-c-act { width: 100%; }
  /* One long client name must not push the card taller than the site itself. */
  .ax-sid-o { flex-wrap: nowrap; overflow: hidden; }
  .ax-sid-ow { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ax-t tbody tr {
    border: 1px solid var(--cw-border); border-radius: var(--cw-r-lg);
    padding: var(--cw-s3); margin-bottom: var(--cw-s2); background: var(--cw-surface);
  }
  .ax-t tbody td { border: 0; padding: 4px 0; }
  .ax-t tbody td.ax-t-end { text-align: left; padding-top: var(--cw-s3); }
  .ax-row-a { justify-content: flex-start; }
  .ax-row-a .cw-btn { flex: 1; }
}

/* ══ SUPPORT — the agency side of the client conversation ══════════════════
   A CONVERSATION IS ONE OBJECT. The first version drew three stacked cards —
   header, messages, composer — which read as three thin boxes on a page
   rather than a thing you are reading. One card, three regions, divided by
   the same hairline the tables use.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── the nav badge ─────────────────────────────────────────────────────────
   Unread has to be visible from ANYWHERE, or the agency finds out a client
   wrote to them by happening to open the tab. */
.ag-nav-badge {
  margin-left: auto; flex: none;
  min-width: 18px; height: 18px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--cw-r-full);
  background: var(--cw-accent); color: var(--cw-accent-ink);
  font: 700 11px/1 var(--cw-font); font-variant-numeric: tabular-nums;
}

/* ── thread list ───────────────────────────────────────────────────────── */
.ax-threads { list-style: none; margin: 0; padding: 0; }
.ax-thread + .ax-thread { border-top: 1px solid var(--cw-surface-2); }
/* Grid, so the client and the time hold their columns and only the subject
   and preview truncate. The avatar and the unread dot are fixed rails. */
.ax-thread-btn {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr) auto 8px;
  align-items: center; gap: var(--cw-s4);
  width: 100%; text-align: left; font: inherit; cursor: pointer;
  background: none; border: 0; padding: var(--cw-s4) var(--cw-s5);
  transition: background var(--cw-t-micro);
}
.ax-thread-btn:hover { background: var(--cw-bg); }
.ax-thread-btn:focus-visible { outline: 2px solid var(--cw-accent); outline-offset: -2px; }

.ax-thread-av {
  width: 36px; height: 36px; border-radius: var(--cw-r-md); flex: none;
  display: grid; place-items: center;
  background: var(--cw-surface-2); color: var(--cw-text-soft);
  font: 600 12px/1 var(--cw-font); letter-spacing: .02em;
}
/* An unread conversation earns the accent; a read one stays quiet. */
.ax-thread.is-unread .ax-thread-av { background: var(--cw-accent-subtle); color: var(--cw-accent); }
.ax-thread.is-closed .ax-thread-btn { opacity: .62; }

.ax-thread-main { min-width: 0; display: grid; gap: 3px; }
.ax-thread-top { display: flex; align-items: center; gap: var(--cw-s2); min-width: 0; }
.ax-thread-subject {
  font: var(--cw-body); font-weight: 600; color: var(--cw-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ax-thread.is-unread .ax-thread-subject { font-weight: 700; }
.ax-thread-tag {
  flex: none; font: var(--cw-micro); font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; padding: 1px 6px; border-radius: var(--cw-r-sm);
  background: var(--cw-surface-2); color: var(--cw-text-faint);
}
.ax-thread-preview {
  font: var(--cw-small); color: var(--cw-text-soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* "You" as a quiet chip rather than a prefix that reads as part of the
   message — the preview is the client's words, not ours. */
.ax-thread-you {
  color: var(--cw-text-faint); font-weight: 600;
}
.ax-thread-you::after { content: ' · '; font-weight: 400; }

.ax-thread-meta { display: grid; justify-items: end; gap: 3px; min-width: 0; }
.ax-thread-who {
  font: var(--cw-small); font-weight: 500; color: var(--cw-text);
  max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ax-thread-when { font: var(--cw-micro); color: var(--cw-text-faint); }

/* Always present, so a read row and an unread one are the same width and the
   list does not shuffle sideways as things are read. */
.ax-thread-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--cw-accent);
}
.ax-thread-dot.is-empty { background: transparent; }

/* ── the conversation ──────────────────────────────────────────────────── */
.ax-conv { overflow: hidden; }
.ax-conv-head {
  display: flex; align-items: center; gap: var(--cw-s3);
  padding: var(--cw-s4) var(--cw-s5);
  border-bottom: 1px solid var(--cw-border);
}
.ax-conv-av {
  width: 38px; height: 38px; border-radius: var(--cw-r-md); flex: none;
  display: grid; place-items: center;
  background: var(--cw-accent-subtle); color: var(--cw-accent);
  font: 600 13px/1 var(--cw-font);
}
.ax-conv-id { flex: 1; min-width: 0; display: grid; gap: 2px; }
.ax-conv-subject {
  font: var(--cw-heading); color: var(--cw-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ax-conv-who { font: var(--cw-small); color: var(--cw-text-soft); }
.ax-conv-state { font-weight: 600; color: var(--cw-text-faint); }

.ax-msgs {
  display: flex; flex-direction: column; gap: var(--cw-s4);
  padding: var(--cw-s5);
  /* A long thread scrolls INSIDE the card, so the composer stays reachable
     without paging to the bottom of the document. */
  max-height: 52vh; overflow-y: auto;
}
.ax-msg { max-width: 76%; }
/* Alignment carries who spoke, so neither side needs colour to be legible. */
.ax-msg.is-mine { margin-left: auto; }
.ax-msg-who {
  font: var(--cw-micro); font-weight: 600; color: var(--cw-text-soft);
  margin-bottom: 4px;
}
.ax-msg-at { font-weight: 400; color: var(--cw-text-faint); }
.ax-msg.is-mine .ax-msg-who { text-align: right; }
.ax-msg-body {
  font: var(--cw-body); color: var(--cw-ink);
  background: var(--cw-surface-2);
  border-radius: var(--cw-r-lg) var(--cw-r-lg) var(--cw-r-lg) var(--cw-r-sm);
  padding: var(--cw-s3) var(--cw-s4);
  /* Typed by a person: keep their line breaks, and break a pasted URL rather
     than letting it widen the card. */
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.ax-msg.is-mine .ax-msg-body {
  background: var(--cw-accent-subtle);
  border-radius: var(--cw-r-lg) var(--cw-r-lg) var(--cw-r-sm) var(--cw-r-lg);
}

/* ── composer ──────────────────────────────────────────────────────────── */
.ax-composer { border-top: 1px solid var(--cw-border); padding: var(--cw-s4) var(--cw-s5); }
.ax-textarea {
  width: 100%; min-height: 76px; resize: vertical; line-height: 1.5;
  font: var(--cw-body); font-family: inherit; color: var(--cw-ink);
  background: var(--cw-surface); border: 1px solid var(--cw-border-strong);
  border-radius: var(--cw-r-md); padding: var(--cw-s3) var(--cw-s4);
  transition: border-color var(--cw-t-micro);
}
.ax-textarea:focus { outline: none; border-color: var(--cw-accent); }
.ax-textarea:focus-visible { outline: 2px solid var(--cw-accent); outline-offset: 1px; }
.ax-composer-foot {
  display: flex; align-items: center; gap: var(--cw-s3);
  margin-top: var(--cw-s3);
}
/* Holds its place empty, so an error does not shove the button sideways. */
.ax-composer-hint {
  flex: 1; min-width: 0; font: var(--cw-small); color: transparent;
}
.ax-composer-hint.is-error { color: var(--cw-danger); }

@media (max-width: 760px) {
  .ax-thread-btn { grid-template-columns: 32px minmax(0, 1fr) 8px; row-gap: 2px; }
  .ax-thread-meta {
    grid-column: 2; justify-items: start;
    grid-auto-flow: column; gap: var(--cw-s2); align-items: baseline;
  }
  .ax-thread-who { max-width: 46vw; }
  .ax-msg { max-width: 90%; }
  .ax-msgs { max-height: none; }
}

/* ══ THE GATE — sign in / sign up on /agency ═══════════════════════════════
   The card, tabs, inputs and OTP boxes are the platform's (.auth-card and
   friends, from dashboard.css); only the full-page frame around it is here.
   ══════════════════════════════════════════════════════════════════════════ */
.ag-gate {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: var(--cw-s6); background: var(--cw-bg);
}
/* A flex item shrinks to its content, so without this the card lands at ~386px
   while /signin's block-centred one fills its 400px max-width — the same 14px
   divergence the client portal's gate had. */
.ag-gate .auth-card { width: 100%; }
/* The secondary ways out sit under the primary action, never beside it. */
.agg-alt { display: block; width: 100%; margin-top: var(--cw-s3); }
/* The message line holds its place, so an error does not shift the card. */
#agg-msg { min-height: 20px; margin-top: var(--cw-s3); }
#agg-msg.is-error { color: var(--cw-danger); }

/* ══ CODE — the agency's plugin and theme library ═══════════════════════════
   Cards, not rows: a package is a THING the agency owns, and the grid reads
   as a shelf. Same card token set as everything else; the only new colour is
   the rejected tint, which is the danger token at low alpha. */
.ax-code-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--cw-s4);
}
.ax-code { display: flex; flex-direction: column; min-width: 0; }
.ax-code.is-rejected { border-color: color-mix(in srgb, var(--cw-danger) 35%, var(--cw-border)); }
.ax-code-hit {
  display: flex; align-items: center; gap: var(--cw-s3); width: 100%; min-width: 0;
  padding: var(--cw-s4) var(--cw-s5); border: 0; background: none; text-align: left;
  font: inherit; color: inherit; cursor: pointer; border-radius: var(--cw-r-lg) var(--cw-r-lg) 0 0;
  transition: background var(--cw-t-micro);
}
.ax-code-hit:hover { background: var(--cw-surface-2); }
.ax-code-hit:focus-visible { outline: 2px solid var(--cw-accent); outline-offset: -2px; }
.ax-code-mark {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: var(--cw-r-md);
  display: grid; place-items: center; font: var(--cw-heading); font-size: 15px;
  background: var(--cw-accent-subtle); color: var(--cw-accent);
}
.ax-code.is-rejected .ax-code-mark { background: color-mix(in srgb, var(--cw-danger) 12%, transparent); color: var(--cw-danger); }
.ax-code-id { display: flex; flex-direction: column; min-width: 0; gap: 2px; flex: 1 1 auto; }
.ax-code-name { font: var(--cw-heading); color: var(--cw-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ax-code-slug { font: var(--cw-small); color: var(--cw-text-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.ax-code-ver { flex: 0 0 auto; font: var(--cw-small); color: var(--cw-text-soft); font-variant-numeric: tabular-nums; }
.ax-code-foot {
  display: flex; align-items: center; gap: var(--cw-s3); min-width: 0;
  padding: var(--cw-s3) var(--cw-s5); border-top: 1px solid var(--cw-border); margin-top: auto;
}
.ax-code-uses, .ax-code-why { font: var(--cw-small); color: var(--cw-text-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.ax-code-why { color: var(--cw-danger); }
.ax-code-why-cell { font: var(--cw-small); color: var(--cw-danger); max-width: 360px; }

/* The upload panel body. One drop target, one meter, one line of status. */
.ax-up { display: flex; flex-direction: column; gap: var(--cw-s4); }
.ax-up-drop {
  display: flex; flex-direction: column; align-items: center; gap: var(--cw-s2); text-align: center;
  padding: var(--cw-s6) var(--cw-s5); border: 1px dashed var(--cw-border-strong); border-radius: var(--cw-r-lg);
  background: var(--cw-surface-2); cursor: pointer; transition: border-color var(--cw-t-micro), background var(--cw-t-micro);
}
.ax-up-drop:hover { border-color: var(--cw-accent); background: var(--cw-accent-subtle); }
.ax-up-drop:focus-within { outline: 2px solid var(--cw-accent); outline-offset: 2px; }
.ax-up-ico { color: var(--cw-accent); display: grid; place-items: center; }
.ax-up-t { font: var(--cw-heading); color: var(--cw-ink); }
.ax-up-s { font: var(--cw-small); color: var(--cw-text-soft); word-break: break-word; }
.ax-up-prog .ax-meter { margin: 0 0 var(--cw-s2); }
.ax-up-prog .ax-meter > span { background: var(--cw-accent); }
.ax-up-msg { font: var(--cw-small); color: var(--cw-text-soft); }
.ax-up-err { color: var(--cw-danger); margin: 0; }

@media (max-width: 640px) {
  .ax-code-grid { grid-template-columns: 1fr; }
  .ax-code-why-cell { max-width: 200px; }
}

/* ── Code: deploy panel + per-site rows ─────────────────────────────────── */
.ax-dep-list { display: flex; flex-direction: column; gap: var(--cw-s2); max-height: 44vh; overflow: auto; padding-right: 2px; }
.ax-dep {
  display: flex; align-items: flex-start; gap: var(--cw-s3); padding: var(--cw-s3) var(--cw-s4);
  border: 1px solid var(--cw-border); border-radius: var(--cw-r-md); background: var(--cw-surface); cursor: pointer;
  transition: border-color var(--cw-t-micro), background var(--cw-t-micro);
}
.ax-dep:hover { border-color: var(--cw-border-strong); }
.ax-dep:has(input:checked) { border-color: var(--cw-accent); background: var(--cw-accent-subtle); }
.ax-dep:focus-within { outline: 2px solid var(--cw-accent); outline-offset: 1px; }
.ax-dep.is-off { opacity: .55; cursor: default; }
.ax-dep input { margin: 3px 0 0; accent-color: var(--cw-accent); flex: 0 0 auto; }
.ax-dep-b { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ax-dep-t { font: var(--cw-heading); color: var(--cw-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ax-dep-s { font: var(--cw-small); color: var(--cw-text-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ax-dep-e { font: var(--cw-small); color: var(--cw-danger); }
.ax-dep-opt { margin-top: var(--cw-s4); }
.ax-col-act { width: 48px; text-align: right; }
.ax-row-bad td:first-child { box-shadow: inset 3px 0 0 var(--cw-danger); }
.ax-code-dom { display: block; font: var(--cw-small); color: var(--cw-text-soft); }
.ax-code-err { display: block; font: var(--cw-small); color: var(--cw-danger); max-width: 360px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ax-dep-opt .ax-dep-s { white-space: normal; }

/* Code: version picker in the deploy panel + per-site outcome notes */
.ax-dep-ver { display: flex; flex-direction: column; gap: var(--cw-s1); margin-bottom: var(--cw-s4); }
.ax-dep-ver-t { font: var(--cw-small); color: var(--cw-text-soft); }
.ax-dep-ver select { max-width: 320px; }
.ax-code-oc { display: block; font: var(--cw-small); max-width: 420px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
.ax-code-oc-warn { color: var(--cw-warn); }
@media (max-width: 640px) { .ax-code-oc { max-width: 220px; } }
@media (max-width: 640px) { .ax-dep-s { white-space: normal; } }

/* ── Import a site: wizard ──────────────────────────────────────────────── */
#ag-topbar-right .cw-btn + .cw-btn { margin-left: var(--cw-s2); }
.ax-imp-steps { display: flex; gap: var(--cw-s2); margin-bottom: var(--cw-s4); font: var(--cw-small); color: var(--cw-text-faint); }
.ax-imp-steps span { padding: 4px 10px; border-radius: var(--cw-r-full); background: var(--cw-surface-2); }
.ax-imp-steps span.on { background: var(--cw-accent-subtle); color: var(--cw-accent); font-weight: 600; }
.ax-up-drop + .ax-up-drop { margin-top: var(--cw-s2); }
.ax-up-t em { font-style: normal; font-weight: 400; color: var(--cw-text-soft); }
.ax-field { display: flex; flex-direction: column; gap: 4px; margin-top: var(--cw-s3); }
.ax-field-l { font: var(--cw-small); color: var(--cw-text-soft); }
.ax-field-l em { font-style: normal; color: var(--cw-text-faint); }
.ax-field-h { font: var(--cw-micro); color: var(--cw-text-faint); }
.ax-imp-wait { display: flex; flex-direction: column; gap: var(--cw-s3); align-items: flex-start; padding: var(--cw-s4) 0; }
.ax-imp-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--cw-s3); margin-bottom: var(--cw-s4); }
.ax-imp-facts > div { display: flex; flex-direction: column; gap: 2px; padding: var(--cw-s3) var(--cw-s4); border: 1px solid var(--cw-border); border-radius: var(--cw-r-md); background: var(--cw-surface); min-width: 0; }
.ax-imp-k { font: var(--cw-micro); color: var(--cw-text-faint); text-transform: uppercase; letter-spacing: .04em; }
.ax-imp-v { font: var(--cw-heading); color: var(--cw-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ax-imp-form { display: flex; flex-direction: column; }
.ax-imp-form .ax-field:first-child { margin-top: 0; }
.ax-imp-done { display: flex; flex-direction: column; gap: var(--cw-s3); }
.ax-imp-sum { display: grid; grid-template-columns: auto 1fr; gap: 4px var(--cw-s4); margin: var(--cw-s2) 0 0; font: var(--cw-small); }
.ax-imp-sum dt { color: var(--cw-text-soft); } .ax-imp-sum dd { margin: 0; color: var(--cw-ink); }
.ax-imp-list { margin: 0; padding-left: 18px; font: var(--cw-small); color: var(--cw-text-soft); }
.ax-alert-a { margin-left: auto; flex: 0 0 auto; }
.cw-input-sm { padding-top: 4px; padding-bottom: 4px; font: var(--cw-small); }
@media (max-width: 640px) { .ax-imp-facts { grid-template-columns: 1fr; } }
.ax-imp-cta { display: none; margin: 0 0 var(--cw-s3); }
@media (max-width: 640px) { .ax-imp-cta { display: block; } #ag-topbar-right [data-do="site-import"] { display: none; } }

/* Template → code */
.ax-tpl-gear { margin-left: auto; }
.ax-dep-opt-inline { display: inline-flex; align-items: center; gap: 6px; margin-top: 4px; font: var(--cw-small); color: var(--cw-text-soft); cursor: pointer; }
.ax-dep-opt-inline input { margin: 0; accent-color: var(--cw-accent); }

/* Billing → Change plan: one row per tier the agency may move to. */
.ax-plan-opt { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 12px 0; border-top: 1px solid var(--cw-border); }
.ax-plan-opt:first-of-type { border-top: 0; }
.ax-plan-opt .cw-btn { flex: 0 0 auto; white-space: nowrap; }
@media (max-width: 640px) { .ax-plan-opt { flex-direction: column; } .ax-plan-opt .cw-btn { width: 100%; } }

/* Settings → Branding */
.ax-brand-row { display: flex; gap: 20px; align-items: flex-start; margin-top: 10px; }
.ax-brand-logo { flex: 0 0 auto; min-width: 120px; }
.ax-brand-logo img { display: block; max-height: 48px; max-width: 200px; width: auto; height: auto; }
.ax-brand-kv { margin: 0; }
.ax-brand-kv dt { font: var(--cw-small); color: var(--cw-text-soft); }
.ax-brand-kv dd { margin: 2px 0 0; }
@media (max-width: 640px) { .ax-brand-row { flex-direction: column; gap: 10px; } }

/* Static HTML sites */
.ax-kind { display: inline-block; margin-left: 6px; padding: 1px 6px; border-radius: 4px; font: var(--cw-small); font-size: 10.5px; letter-spacing: .04em; color: var(--cw-text-soft); border: 1px solid var(--cw-border); vertical-align: middle; }
.ax-file { display: flex; align-items: center; gap: 10px; margin: 8px 0 4px; flex-wrap: wrap; }
.ax-file input[type=file] { font: var(--cw-body); }
.ax-file-n { font: var(--cw-small); color: var(--cw-text-soft); }

/* Import a site → what is being imported */
.ax-imp-kind { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; }
.ax-imp-kind-on, .ax-imp-kind-alt { display: inline-flex; align-items: center; gap: 6px; padding: 8px 12px; border-radius: 999px; font: var(--cw-body); border: 1px solid var(--cw-border); background: var(--cw-surface); color: var(--cw-text); }
.ax-imp-kind-on { border-color: var(--cw-accent); color: var(--cw-accent); font-weight: 600; }
.ax-imp-kind-alt { cursor: pointer; }
.ax-imp-kind-alt:hover, .ax-imp-kind-alt:focus-visible { border-color: var(--cw-border-strong); }
