/**
 * NOW Agent Portal — main.css
 * Shared vocabulary ONLY: tokens, reset, typography, and shared
 * components (Style Guide v1.6 §9).
 *
 * Layers
 *  reset      — user agent style reset
 *  base       — tokens (:root) + typographic defaults
 *  layout     — declared here for cascade ordering; populated by the
 *               layout sheets in assets/css/layouts/{blank,admin,application}.css,
 *               which own ALL page chrome (templates A/B/C)
 *  components — buttons, cards, tables, menus, forms, badges, avatars, flash
 *  utilities  — small composable helpers
 *
 * Rules (Style Guide §9):
 *  - Tokens and shared components live ONLY in this file.
 *  - Layout chrome lives ONLY in the mirrored layout sheets under
 *    assets/css/layouts/.
 *  - Page CSS mirrors its view (assets/css/{controller}/{action}.css) and
 *    composes/arranges only — it never redefines tokens or restyles
 *    shared components.
 *  - Never hard-code a hex value, or a size a token already covers.
 *  - No !important. No inline styles except server-computed per-row values.
 **/

@layer reset, base, layout, components, utilities;

/* ============================================================
   RESET
   ============================================================ */
@layer reset {
  * {
    margin: 0;
    padding: 0;
  }

  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  img,
  picture,
  svg,
  video {
    display: block;
    max-width: 100%;
  }

  button {
    cursor: pointer;
  }

  button,
  input,
  select,
  textarea {
    font: inherit;
    max-width: 100%;
    min-width: 0;
  }

  ul[class],
  ol[class] {
    list-style: none;
  }

  a {
    color: inherit;
  }

  /* Without this iOS Safari auto-inflates text in landscape, silently breaking
     sized layouts (Style Guide §8). Not a zoom lock — pinch-zoom still works. */
  html {
    -webkit-text-size-adjust: 100%;
  }
}

/* ============================================================
   BASE — design tokens + typographic defaults
   ============================================================ */
@layer base {
  :root {
    /* --- Color tokens (Style Guide §2) --- */
    --color-black:       #111111; /* logo mark, primary buttons, icon tiles, headings */
    --color-text:        #1a1a1a; /* body text */
    --color-text-muted:  #6b6b6b; /* secondary text, table headers, metadata */
    --color-text-faint:  #9a9a9a; /* placeholders, disabled text ONLY */
    --color-border:      #e5e5e5; /* card borders, dividers, input borders */
    --color-bg:          #ffffff; /* page + card background */
    --color-bg-subtle:   #f5f5f5; /* hover rows, active nav, avatars, AI bubbles */

    /* The one accent — destructive actions + form errors ONLY */
    --color-danger:      #dc2626;
    --color-danger-bg:   #fef2f2;

    /* Status text (plain text at MVP, no colored badges) */
    --color-status-ok:   var(--color-text);

    /* --- Type scale (Style Guide §3) --- */
    /* Mobile-first: this is the mobile scale. The single media query at the
       bottom of this file restores the desktop values. */
    --text-page-title:   24px;  /* 28px at >1024px */
    --text-section:      17px;
    --text-body:         15px;
    --text-small:        13px;
    --text-stat:         32px;
    /* ALL form controls. 16px on mobile because iOS Safari zooms the viewport
       whenever a focused control is under 16px — including readonly ones.
       Never "simplify" this back to --text-body (§3). */
    --text-input:        16px;  /* --text-body at >1024px */

    /* --- Spacing (Style Guide §4) — base unit 8px --- */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --space-7: 48px;

    /* --- Radii --- */
    --radius-sm: 6px;   /* buttons, inputs, badges, menu items */
    --radius-md: 10px;  /* cards, tables, dropdowns, chat bubbles */
    --radius-lg: 14px;  /* logo mark, icon tiles, auth card */

    /* --- The one shadow (floating elements only) --- */
    --shadow-menu: 0 4px 16px rgba(0, 0, 0, 0.08);

    /* --- Layout dimensions (Style Guide §4) --- */
    /* These NEVER change at a breakpoint (§9). A layout sheet changes which
       properties consume them; the values keep one meaning at every width. */
    --sidebar-width: 280px;   /* admin template */
    --rail-width:    64px;    /* workspace icon rail */
    --topbar-height: 64px;
    --ai-panel-width: 380px;  /* AI drawer overlay width at >1024px */
    --ai-launcher-size: 56px; /* floating AI bubble on mobile */
    --content-max-width: 1280px;
    --auth-max-width: 640px;

    scrollbar-gutter: stable;
  }

  html {
    scroll-behavior: smooth;
  }

  body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                 "Helvetica Neue", Arial, sans-serif;
    font-size: var(--text-body);
    line-height: 1.5;
    color: var(--color-text);
    background-color: var(--color-bg);
    isolation: isolate;
  }

  /* One per page, always followed by a muted one-line description */
  .page-title {
    font-size: var(--text-page-title);
    font-weight: 700;
    color: var(--color-black);
  }

  .page-desc {
    font-size: var(--text-small);
    color: var(--color-text-muted);
    margin-block-start: var(--space-1);
  }

  .section-title {
    font-size: var(--text-section);
    font-weight: 600;
    color: var(--color-black);
  }

  .text-muted { color: var(--color-text-muted); }
  .text-small { font-size: var(--text-small); }
  .text-strong { font-weight: 600; }

  ::placeholder {
    color: var(--color-text-faint);
  }

  /* Visible focus on all interactive elements (Style Guide §8) */
  :focus-visible {
    outline: 2px solid var(--color-black);
    outline-offset: 2px;
  }
}

/* ============================================================
   COMPONENTS (Style Guide §5)
   ============================================================ */
@layer components {
  /* ----------------------------------------------------------
     5.1 Buttons — three variants, no others
     ---------------------------------------------------------- */
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: 10px 18px;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-weight: 600;
    font-size: var(--text-body);
    text-decoration: none;
    transition: background-color 120ms ease, border-color 120ms ease;
  }

  /* Max ONE primary per screen region */
  .btn--primary {
    background-color: var(--color-black);
    color: var(--color-bg);
  }

  .btn--primary:hover {
    background-color: #000000;
  }

  .btn--secondary {
    background-color: var(--color-bg);
    border-color: var(--color-border);
    color: var(--color-black);
  }

  .btn--secondary:hover {
    background-color: var(--color-bg-subtle);
  }

  /* Danger = red TEXT, not a filled red button. Menus + confirm forms only. */
  .btn--danger {
    background: none;
    border: none;
    color: var(--color-danger);
    padding: 10px 18px;
  }

  .btn--danger:hover {
    background-color: var(--color-danger-bg);
  }

  .btn[disabled] {
    color: var(--color-text-faint);
    cursor: not-allowed;
  }

  /* Compact size — for inline actions (e.g. tile edit/delete). Mobile gets a
     36px floor rather than the usual 44px: these live in dense table action
     cells, and that exception is documented in §5.1. */
  .btn--sm {
    padding: var(--space-2) var(--space-3);
    min-height: 36px;
    font-size: var(--text-small);
  }
  .btn--sm.btn--danger { padding: var(--space-2) var(--space-3); }

  /* Square icon-only button — the §5.9 chat send button */
  .btn--icon {
    padding: 10px;
  }

  /* A dimmed black button fails contrast, so a disabled primary goes subtle */
  .btn--primary[disabled] {
    background-color: var(--color-bg-subtle);
    color: var(--color-text-faint);
  }
  .btn--primary[disabled]:hover {
    background-color: var(--color-bg-subtle);
  }

  /* ----------------------------------------------------------
     5.2 Cards — one recipe; variants by content only
     ---------------------------------------------------------- */
  .card {
    background-color: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-5);
  }

  /* Stat card: muted label + icon, big number, muted delta line */
  .stat-card .stat-label {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: var(--text-small);
    color: var(--color-text-muted);
  }

  .stat-card .stat-value {
    font-size: var(--text-stat);
    font-weight: 700;
    color: var(--color-black);
    margin-block: var(--space-2);
  }

  .stat-card .stat-delta {
    font-size: var(--text-small);
    color: var(--color-text-muted);
  }

  /* Link / workspace tile: whole tile is the click target */
  .tile {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    background-color: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-4);
    text-decoration: none;
    font-weight: 600;
    transition: background-color 120ms ease, border-color 120ms ease;
  }

  .tile:hover {
    background-color: var(--color-bg-subtle);
  }

  .icon-tile {
    flex-shrink: 0;
    width: 44px;
    height: 44px;
    background-color: var(--color-black);
    border-radius: var(--radius-lg);
    color: var(--color-bg);
    display: grid;
    place-items: center;
    font-weight: 700;
  }

  /* Auth card: larger padding, centered. NOTE: sessions/new.css and
     invitations/show.css override text-align to `start` — do not touch that
     line here or the login and invite forms silently re-center. */
  .card--auth {
    padding: var(--space-5);
    text-align: center;
    width: 100%;
  }

  /* Grid for stat cards / link tiles (Style Guide §4 layout rules) */
  .card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
    gap: var(--space-4);
  }

  /* ----------------------------------------------------------
     5.3 Tables — wrapped in a card
     ---------------------------------------------------------- */
  .table-wrap {
    background-color: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    /* Scroll a too-wide table instead of clipping its last column. */
    overflow-x: auto;
  }

  .table-wrap table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-body);
  }

  .table-wrap th {
    font-size: var(--text-small);
    font-weight: 400;
    color: var(--color-text-muted);
    text-align: start;
    padding: var(--space-2) var(--space-3);
    border-block-end: 1px solid var(--color-border);
  }

  .table-wrap td {
    padding: var(--space-3);
    border-block-end: 1px solid var(--color-border);
    vertical-align: top;
    /* Break long unbroken strings (emails, tokens) so they wrap instead of
       forcing the column wide. */
    overflow-wrap: anywhere;
  }

  .table-wrap tbody tr:last-child td {
    border-block-end: none;
  }

  .table-wrap tbody tr:hover {
    background-color: var(--color-bg-subtle);
  }

  /* First column bold when it's the row's identity */
  .table-wrap td.cell-identity {
    font-weight: 600;
  }

  /* Last column: status / actions, right-aligned. Keep the buttons on one
     line — they have their own flex row and shouldn't wrap when squeezed. */
  .table-wrap th.cell-actions,
  .table-wrap td.cell-actions {
    text-align: end;
    white-space: nowrap;
  }

  /* Right-aligned numeric column (counts, money) */
  .table-wrap th.cell-num,
  .table-wrap td.cell-num {
    text-align: end;
    white-space: nowrap;
  }

  /* Tables whose row actions are a kebab menu (§5.3/§5.4). The menu's panel is
     absolutely positioned inside a <td>, and .table-wrap sets overflow-x:auto —
     which per CSS makes BOTH axes a scroll container, so the panel would be
     clipped or would summon a scrollbar. These tables therefore opt out of the
     scroll fallback.

     Safe to opt out: every table that carries this is also --stack (it becomes
     cards below 1025px), and collapsing a pair of buttons into one 44px trigger
     is what makes the remaining columns fit at >1024px.

     The wrap no longer clips, so the card's rounded corners have to be restored
     on the corner cells themselves. */
  .table-wrap--menus {
    overflow: visible;
  }

  .table-wrap--menus tbody tr:first-child td:first-child { border-start-start-radius: var(--radius-md); }
  .table-wrap--menus tbody tr:first-child td:last-child  { border-start-end-radius: var(--radius-md); }
  .table-wrap--menus tbody tr:last-child  td:first-child { border-end-start-radius: var(--radius-md); }
  .table-wrap--menus tbody tr:last-child  td:last-child  { border-end-end-radius: var(--radius-md); }

  /* ----------------------------------------------------------
     5.3b Stacked tables (mobile) — .table-wrap--stack renders each row as a
     label/value card instead of scrolling sideways (§5.3). Opt-in per table:
     numeric report tables deliberately keep the scroll fallback, because
     column-to-column comparison is the point of them.

     Labels come from data-label on each <td>, so the markup carries them once
     and the header row is simply dropped. The desktop block at the bottom of
     this file reverts every rule here.
     ---------------------------------------------------------- */
  .table-wrap--stack thead {
    /* Not `display: none` on the <tr>: hiding the group keeps the a11y tree
       tidy while the cells' data-label carries the same information. */
    display: none;
  }

  .table-wrap--stack tbody tr {
    display: block;
    padding: var(--space-4);
    border-block-end: 1px solid var(--color-border);
  }

  .table-wrap--stack tbody tr:last-child {
    border-block-end: none;
  }

  .table-wrap--stack td {
    display: flex;
    gap: var(--space-3);
    align-items: baseline;
    padding: var(--space-1) 0;
    border-block-end: none;
    /* The scroll-fallback nowrap would force these cards wide. */
    white-space: normal;
    text-align: start;
  }

  /* .table-wrap td.cell-num / td.cell-actions carry text-align:end and beat the
     bare `td` selector above on specificity, so the alignment has to be undone
     at matching weight — otherwise values drift right inside the cards. */
  .table-wrap--stack td.cell-num,
  .table-wrap--stack td.cell-actions {
    text-align: start;
  }

  .table-wrap--stack td::before {
    content: attr(data-label);
    flex: 0 0 38%;
    font-size: var(--text-small);
    color: var(--color-text-muted);
  }

  /* The identity cell is the card's heading, and the actions row its footer —
     neither wants a label column. */
  .table-wrap--stack td.cell-identity {
    display: block;
    font-size: var(--text-section);
    padding-block-end: var(--space-2);
  }

  .table-wrap--stack td.cell-actions {
    padding-block-start: var(--space-3);
  }

  /* In stacked mode the actions cell is the card FOOTER, and the rule above
     resets it to text-align:start — which parks a lone kebab at the card's
     left edge, reading as a stray glyph. A trailing trigger is the convention
     and matches where it sits in the desktop table. */
  .table-wrap--menus.table-wrap--stack td.cell-actions {
    justify-content: flex-end;
  }

  .table-wrap--stack td.cell-identity::before,
  .table-wrap--stack td.cell-actions::before,
  .table-wrap--stack td.table-empty::before {
    content: none;
  }

  /* A colspan empty-state row is one message, not a label/value pair. */
  .table-wrap--stack td.table-empty {
    display: block;
    text-align: center;
  }

  .table-empty {
    padding: var(--space-6);
    text-align: center;
    color: var(--color-text-muted);
  }

  /* Chip list — multiple small labels (e.g. a member's roles) that wrap
     gracefully in a table cell instead of running as one long string. */
  .chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
  }

  /* Reporting date-range control — preset chips + a custom from/to form.
     Stacked on mobile: `justify-content: space-between` on a wrapping row
     produces a bizarre split at narrow widths, so the desktop block restores
     the row rather than the base declaring it. */
  .range-control {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-4);
    margin-block: var(--space-4);
  }

  .range-presets {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  .range-chip {
    display: inline-block;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    font-size: var(--text-small);
    color: var(--color-text-muted);
    text-decoration: none;
  }

  .range-chip:hover { background-color: var(--color-bg-subtle); }

  .range-chip.is-active {
    background-color: var(--color-black);
    border-color: var(--color-black);
    color: var(--color-bg);
  }

  .range-custom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
  }

  .range-custom input[type="date"] {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: var(--space-1) var(--space-2);
    font-size: var(--text-input);
  }

  /* Pagination: plain Prev/Next links, server-rendered */
  .pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-4);
    font-size: var(--text-small);
    color: var(--color-text-muted);
    padding-block: var(--space-4);
  }

  /* ----------------------------------------------------------
     5.4 Dropdown menus — native <details>/<summary>, JS-free
     ---------------------------------------------------------- */
  .menu {
    position: relative;
  }

  /* 44px touch target on mobile; the desktop block below restores 32px. */
  .menu > summary {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-sm);
    transition: background-color 120ms ease;
  }

  .menu > summary::-webkit-details-marker {
    display: none;
  }

  .menu > summary:hover {
    background-color: var(--color-bg-subtle);
  }

  .menu-panel {
    position: absolute;
    inset-block-start: calc(100% + var(--space-1));
    inset-inline-end: 0;
    min-width: 180px;
    /* Anchored to the inline end, so without a cap a 240px panel overflows a
       narrow viewport. Unconditional — no breakpoint needed (§5.4). */
    max-width: calc(100vw - 2 * var(--space-4));
    background-color: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-menu);
    padding: var(--space-1);
    z-index: 20;
  }

  .menu-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    padding: var(--space-3);
    border: none;
    background: none;
    border-radius: var(--radius-sm);
    font-size: var(--text-body);
    text-align: start;
    text-decoration: none;
    color: var(--color-text);
    transition: background-color 120ms ease;
  }

  .menu-item:hover {
    background-color: var(--color-bg-subtle);
  }

  /* Secondary item (e.g. "See all workspaces") — muted, no leading icon */
  .menu-item--muted {
    color: var(--color-text-muted);
    font-size: var(--text-small);
  }

  /* Destructive item: always last, red text, after a divider */
  .menu-item--danger {
    color: var(--color-danger);
  }

  .menu-item--danger:hover {
    background-color: var(--color-danger-bg);
  }

  .menu-divider {
    border: none;
    border-block-start: 1px solid var(--color-border);
    margin-block: var(--space-1);
  }

  /* Avatar-triggered variant — the top-bar user-context menu. The summary
     hosts the round avatar (no kebab box), so the hover/focus ring is round. */
  /* The avatar must STAY 32px (§5.7 specs it), so the touch target grows
     invisibly instead of the visual. The top bar's --space-4 gap means a -6px
     expansion can't overlap a neighbour. */
  .user-menu > summary {
    position: relative;
    width: 32px;
    height: 32px;
    padding: 0;
    border-radius: 50%;
  }

  .user-menu > summary::after {
    content: '';
    position: absolute;
    inset: -6px;
  }

  .user-menu .menu-panel {
    min-width: 240px;
  }

  /* Muted section label inside a menu (e.g. "Your workspaces") */
  .menu-label {
    padding: var(--space-2) var(--space-3) var(--space-1);
    font-size: var(--text-small);
    color: var(--color-text-muted);
  }

  /* The workspace the user is currently in */
  .menu-item[aria-current="page"] {
    background-color: var(--color-bg-subtle);
  }

  /* Kebab-triggered variant — the standard row-actions trigger in a table
     (§5.3). Shares every .menu / .menu-panel / .menu-item rule above; only the
     stacking and wrapping differ from the avatar menu.

     z-index: the base panel sits at 20 to clear page content, but .topbar is
     position:sticky at z-index 10 and .content sets none — so a row panel would
     paint straight over the top bar as the page scrolls. The avatar menu never
     hit this because its panel is INSIDE .topbar. */
  .kebab-menu .menu-panel {
    z-index: 5;
    /* .cell-actions carries white-space:nowrap, which is load-bearing for the
       scroll fallback (§5.3) and must not be removed — but it would inherit
       into the panel and run every label onto one line. */
    white-space: normal;
  }

  /* A menu item is a real <button type="submit"> inside a real <form> for
     destructive actions, so the feature survives JS being off. Browsers give
     both a default margin that would break the panel's rhythm. */
  .kebab-menu form {
    margin: 0;
  }

  /* ----------------------------------------------------------
     5.4b Mobile nav drawer (shared/nav_drawer.html.php)

     A shared component rather than layout chrome, because BOTH the admin and
     application top bars render it — the same reasoning that puts .user-menu
     here. Each template's docked nav (.sidebar / .rail) stays in its own layout
     sheet. Style guide §4B/§4C.

     Native <details>, so no JS is needed to open it. Hidden entirely at
     >1024px by the media block at the bottom of this file.
     ---------------------------------------------------------- */
  .nav-drawer {
    /* Static in the top-bar flex row: the toggle is a normal 44px flex item,
       and only the panel and scrim escape to the viewport. */
    flex-shrink: 0;
  }

  .nav-toggle {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-sm);
    transition: background-color 120ms ease;
  }

  .nav-toggle::-webkit-details-marker {
    display: none;
  }

  .nav-toggle:hover {
    background-color: var(--color-bg-subtle);
  }

  .nav-drawer[open] .nav-toggle {
    background-color: var(--color-bg-subtle);
  }

  /* Dims the page behind the open drawer. --color-black at low opacity keeps
     this tokenised, so no new colour enters the system (§2). */
  .nav-drawer-scrim {
    position: fixed;
    inset: 0;
    z-index: 21;
    background-color: var(--color-black);
    opacity: 0.32;
  }

  .nav-drawer-panel {
    position: fixed;
    z-index: 22;
    inset-block-start: var(--topbar-height);
    inset-block-end: 0;
    inset-inline-start: 0;
    width: min(280px, 80vw);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--space-3);
    background-color: var(--color-bg);
    border-inline-end: 1px solid var(--color-border);
    box-shadow: var(--shadow-menu);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
  }

  /* Icon + text label, unlike the desktop rail's icon-only buttons — seven
     labelled rows read comfortably at 375px, seven bare icons do not. */
  .nav-drawer-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: 44px;
    padding-inline: var(--space-3);
    border-radius: var(--radius-sm);
    color: var(--color-text);
    text-decoration: none;
    transition: background-color 120ms ease;
  }

  .nav-drawer-item:hover,
  .nav-drawer-item[aria-current="page"] {
    background-color: var(--color-bg-subtle);
  }

  .nav-drawer-item[aria-current="page"] {
    font-weight: 600;
  }

  /* Identity header block for the avatar menu */
  .menu-identity {
    padding: var(--space-3);
  }

  .menu-identity .identity-name {
    font-weight: 600;
  }

  .menu-identity .identity-email {
    font-size: var(--text-small);
    color: var(--color-text-muted);
  }

  .menu-identity .identity-role {
    margin-block-start: var(--space-2);
    display: inline-block;
    padding: 2px var(--space-2);
    border-radius: var(--radius-sm);
    background-color: var(--color-bg-subtle);
    font-size: var(--text-small);
    font-weight: 600;
    color: var(--color-text-muted);
  }

  /* ----------------------------------------------------------
     5.5 Forms & inputs — every form: hidden _csrf, POST, PRG
     ---------------------------------------------------------- */
  .field-group {
    display: grid;
    gap: var(--space-2);
  }

  .field-group > label {
    font-size: var(--text-small);
    font-weight: 500;
  }

  .field-group > input,
  .field-group > select,
  .field-group > textarea {
    width: 100%;
    background-color: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    padding: 10px 12px;
    /* The reset's `font: inherit` would give these body 15px, which triggers
       iOS focus-zoom. Form controls always use --text-input (§3). */
    font-size: var(--text-input);
    transition: border-color 120ms ease;
  }

  .field-group > input:focus,
  .field-group > select:focus,
  .field-group > textarea:focus {
    border-color: var(--color-black);
  }

  .field-help {
    font-size: var(--text-small);
    color: var(--color-text-muted);
  }

  /* Validation errors — the only non-destructive use of --color-danger */
  .field-group.has-error > input,
  .field-group.has-error > select,
  .field-group.has-error > textarea {
    border-color: var(--color-danger);
  }

  .field-error {
    font-size: var(--text-small);
    color: var(--color-danger);
  }

  /* The list that holds .field-error items above a form. Fourteen views use it;
     it was defined in SEVEN page sheets in four different variants, and the
     other seven views loaded no definition at all. The colour lives on
     .field-error, so this is only the list's layout — flex column also
     suppresses the <ul> markers without a reset. */
  .form-errors {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin-block-end: var(--space-4);
  }

  /* Inline row of report links (CINC connection page, dashboard snapshot). */
  .cinc-report-links {
    display: inline-flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    align-items: baseline;
  }

  /* Footnote under a stats table ("figures exclude ..."). Four CINC views use
     it; only one of them loaded the sheet that defined it. */
  .cinc-stats-note {
    margin-block-start: var(--space-4);
    line-height: 1.5;
  }

  .form-stack {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }

  .form-actions {
    display: flex;
    gap: var(--space-3);
    justify-content: flex-end;
    flex-wrap: wrap;
  }

  .form-actions form {
    margin: 0;
  }

  /* Start-aligned action row for page-level controls (report toolbars, "Add"
     buttons above a table) — the same component as .form-actions with the
     default inline alignment. Shared here rather than per page because eight
     views were already using one copy of it that seven of them never loaded. */
  .action-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    /* The fix every one of those copies needed: a toolbar of three controls
       overflows a 375px viewport otherwise. */
    flex-wrap: wrap;
  }

  .action-row form {
    margin: 0;
  }

  /* "← Back to X" link above a page title. Twelve views use it; it lived in a
     single page sheet that eleven of them never loaded. */
  .link-back {
    color: var(--color-text-muted);
    text-decoration: none;
    font-size: var(--text-small);
  }

  .link-back:hover {
    color: var(--color-text);
  }

  /* ----------------------------------------------------------
     5.6 Badges & pills — grayscale only at MVP
     ---------------------------------------------------------- */
  .badge {
    display: inline-block;
    background-color: var(--color-bg-subtle);
    color: var(--color-text-muted);
    border-radius: var(--radius-sm);
    padding: 2px 8px;
    font-size: var(--text-small);
  }

  /* Attention variant — a status that should stand out (e.g. a suspended member) */
  .badge--warn {
    background-color: var(--color-danger-bg);
    color: var(--color-danger);
  }

  /* Muted variant — a de-emphasised state (e.g. an archived post) */
  .badge--muted {
    background-color: transparent;
    color: var(--color-text-muted);
    border: 1px solid var(--color-border);
  }

  /* ----------------------------------------------------------
     Audience picker — shared "Who can see this?" grouped checkboxes
     (shared/audience_picker.html.php), used by board posts, links, and
     future audience-targeted forms.
     ---------------------------------------------------------- */
  .audience-picker {
    display: grid;
    /* min() guard: an unguarded 200px floor overflows below 200px + gap
       instead of collapsing to one column (Style Guide §4). */
    grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
    gap: var(--space-4);
    margin-block-start: var(--space-4);
  }

  .audience-group {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-4);
    min-width: 0;
    max-height: 260px;
    overflow-y: auto;
  }

  .audience-group legend {
    padding-inline: var(--space-2);
    font-weight: 600;
    font-size: var(--text-small);
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }

  /* Checkbox rows are things people actually tap — 44px floor on mobile. */
  .audience-check {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding-block: var(--space-2);
    min-height: 44px;
    cursor: pointer;
  }

  /* ----------------------------------------------------------
     Launch tile — shared quick-link tile used by the Links page and
     the dashboard quick-links section.
     ---------------------------------------------------------- */
  .link-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(180px, 100%), 1fr));
    gap: var(--space-4);
    margin-block-start: var(--space-4);
  }

  .link-card {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background-color: var(--color-bg);
    color: var(--color-text);
    text-decoration: none;
    transition: border-color 0.12s ease, background-color 0.12s ease;
  }

  .link-card:hover {
    background-color: var(--color-bg-subtle);
    border-color: var(--color-text-muted);
  }

  .link-card-icon {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    background-color: var(--color-bg-subtle);
    overflow: hidden;
  }

  .link-card-icon img {
    width: 100%;
    height: 100%;
    object-fit: contain;
  }

  .link-card-initial {
    font-weight: 600;
    color: var(--color-text-muted);
  }

  .link-card-label {
    font-weight: 600;
    overflow-wrap: anywhere;
  }

  /* ----------------------------------------------------------
     5.7 Avatar — initials only, no images at MVP
     ---------------------------------------------------------- */
  .avatar {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background-color: var(--color-bg-subtle);
    color: var(--color-text);
    display: grid;
    place-items: center;
    font-size: var(--text-small);
    font-weight: 600;
    text-transform: uppercase;
  }

  .avatar--sm {
    width: 24px;
    height: 24px;
  }

  .avatar--lg {
    width: 40px;
    height: 40px;
  }

  .avatar--inverse {
    background-color: var(--color-black);
    color: var(--color-bg);
  }

  /* ----------------------------------------------------------
     Flash messages (post-redirect) — Style Guide §7
     Fixed toast stack: overlays the viewport (no layout shift,
     scroll-independent) and each message auto-dismisses via the
     flash-out keyframe. Rendered by app/views/shared/flashes.html.php.
     ---------------------------------------------------------- */
  .flash-stack {
    position: fixed;
    top: var(--space-4);
    right: var(--space-4);
    z-index: 30; /* above dropdown menus (20) and page chrome (10) */
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    width: min(360px, calc(100vw - 2 * var(--space-4)));
    pointer-events: none; /* the stack must not block clicks; messages re-enable */
  }

  .flash {
    background-color: var(--color-bg-subtle);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-3) var(--space-4);
    font-size: var(--text-body);
    box-shadow: var(--shadow-menu);
    pointer-events: auto;
    animation: flash-in 200ms ease-out,
               flash-out 400ms ease-in 5s forwards;
  }

  .flash--error {
    background-color: var(--color-bg);
    border-color: var(--color-danger);
    color: var(--color-danger);
    animation-delay: 0ms, 8s; /* errors get more reading time before dismissing */
  }

  @keyframes flash-in {
    from {
      opacity: 0;
      transform: translateY(calc(-1 * var(--space-2)));
    }
  }

  @keyframes flash-out {
    to {
      opacity: 0;
      visibility: hidden; /* ends hidden so a faded toast can't catch clicks */
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .flash {
      animation: flash-out 400ms ease-in 5s forwards; /* fade only, no slide-in */
    }

    .flash--error {
      animation-delay: 8s;
    }
  }

  /* ----------------------------------------------------------
     5.9 AI chat bubbles (component; panel layout is in @layer layout)
     ---------------------------------------------------------- */
  .chat-bubble {
    display: flex;
    gap: var(--space-2);
    align-items: flex-end;
    max-width: 85%;
  }

  .chat-bubble .bubble-body {
    border-radius: var(--radius-md);
    padding: var(--space-3);
  }

  .chat-bubble--assistant {
    align-self: flex-start;
  }

  .chat-bubble--assistant .bubble-body {
    background-color: var(--color-bg-subtle);
  }

  .chat-bubble--user {
    align-self: flex-end;
    flex-direction: row-reverse;
  }

  .chat-bubble--user .bubble-body {
    background-color: var(--color-black);
    color: var(--color-bg);
  }

  /* ----------------------------------------------------------
     5.10 Skeleton placeholders + deferred panels (style guide §7)

     CINC data is fetched after the shell paints (MVP doc §7.6/§9), so these
     stand in for a panel that is still in flight. Two rules matter:

       - The blocks are SIZED LIKE the thing they replace (a stat row, a table
       row, a funnel bar), so the panel landing does not shove the page.
       - The pulse is the SECOND sanctioned motion exception after the flash
       toast stack (§7). A cold CINC pull can run 10-20s, and a motionless
       gray block held that long reads as a hung page rather than a loading
       one. It is opacity only - no slide, no scale - and it is switched off
       entirely under prefers-reduced-motion.
     ---------------------------------------------------------- */
  .skeleton {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  .skeleton-line,
  .skeleton-stat,
  .skeleton-row {
    background-color: var(--color-bg-subtle);
    border-radius: var(--radius-sm);
    animation: skeleton-pulse 1600ms ease-in-out infinite;
  }

  .skeleton-line {
    block-size: 12px;
  }

  /* Widths vary so the block reads as text, not as a progress bar. */
  .skeleton-line--title { block-size: 16px; inline-size: 40%; }
  .skeleton-line--wide  { inline-size: 100%; }
  .skeleton-line--mid   { inline-size: 70%; }
  .skeleton-line--short { inline-size: 45%; }

  /* Mirrors .card-grid + .stat-card, so the swap is the same height. */
  .skeleton-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--space-4);
  }

  .skeleton-stat {
    block-size: 88px;
  }

  .skeleton-row {
    block-size: 40px;
  }

  /* A deferred panel is the container the loader swaps markup into. It carries
     data-state, so the CSS keys off the same attribute idiom the record
     counters and inline transaction saves already use. */
  [data-cinc-panel][data-state="error"] .skeleton {
    display: none;
  }

  .cinc-panel-error {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-3);
  }

  @keyframes skeleton-pulse {
    50% {
      opacity: 0.55;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .skeleton-line,
    .skeleton-stat,
    .skeleton-row {
      animation: none;
    }
  }
}

/* ============================================================
   UTILITIES
   ============================================================ */
@layer utilities {
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  .icon {
    display: inline-block;
    width: 18px;
    height: 18px;
    stroke: currentColor;   /* inline SVG inherits text color (§6) */
    stroke-width: 1.5px;
    fill: none;
  }

  .stack-sm { display: flex; flex-direction: column; gap: var(--space-2); }
  .stack    { display: flex; flex-direction: column; gap: var(--space-4); }
  .row      { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
  .row-between {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
  }

  /* Reduced motion (quality floor) */
  @media (prefers-reduced-motion: reduce) {
    html {
      scroll-behavior: auto;
    }
    *,
    *::before,
    *::after {
      transition-duration: 0.01ms;
    }
  }
}

/* ============================================================
   NON-MOBILE (viewport width > 1024px) — Style Guide §4/§9
   ============================================================
   The ONE breakpoint in this project. Everything above is the mobile base.

   Two rules that are easy to break and fail silently:
     1. This must stay the LAST block in the file, and there must be exactly
        one of it. Enforced by tests/css_breakpoint_audit.php.
     2. The media block is OUTER and the layers are re-opened INSIDE it. An
        unlayered @media here would outrank every @layer — including all the
        layout chrome in layouts/*.css — and silently change which rules win.

   Never write `@media (min-width: var(--something))`: custom properties do not
   substitute in media conditions, so the condition fails to parse and the
   whole block is discarded with no error. That is why the value is a literal.
   ============================================================ */
@media (min-width: 1025px) {
  @layer base {
    :root {
      --text-page-title: 28px;
      --text-input: var(--text-body);
    }
  }

  @layer components {
    .card--auth {
      padding: var(--space-6);
    }

    /* Roomier cells once there's width for them */
    .table-wrap th {
      padding: var(--space-3) var(--space-4);
    }

    .table-wrap td {
      padding: var(--space-4);
    }

    .btn--sm {
      padding: 4px 10px;
      min-height: auto;
    }
    .btn--sm.btn--danger { padding: 4px 10px; }

    .range-control {
      flex-direction: row;
      flex-wrap: wrap;
      align-items: center;
      justify-content: space-between;
    }

    .range-chip {
      padding: var(--space-1) var(--space-3);
    }

    .audience-check {
      padding-block: var(--space-1);
      min-height: auto;
    }

    /* Mouse targets don't need the 44px floor */
    .menu > summary {
      width: 32px;
      height: 32px;
    }

    .user-menu > summary::after {
      content: none;
    }

    /* The docked .sidebar / .rail take over, so the drawer disappears whole.
       display:none (not visibility/opacity) is load-bearing: it drops the
       duplicate nav links from the tab order and the accessibility tree. */
    .nav-drawer {
      display: none;
    }

    /* Stacked tables revert to real tables (§5.3b) */
    .table-wrap--stack thead {
      display: table-header-group;
    }

    .table-wrap--stack tbody tr {
      display: table-row;
      padding: 0;
      border-block-end: none;
    }

    .table-wrap--stack td,
    .table-wrap--stack td.cell-identity,
    .table-wrap--stack td.table-empty {
      display: table-cell;
      gap: normal;
      padding: var(--space-4);
      border-block-end: 1px solid var(--color-border);
    }

    .table-wrap--stack tbody tr:last-child td {
      border-block-end: none;
    }

    .table-wrap--stack td::before {
      content: none;
    }

    .table-wrap--stack td.cell-identity {
      font-size: var(--text-body);
    }

    .table-wrap--stack td.cell-actions {
      padding: var(--space-4);
      text-align: end;
      white-space: nowrap;
    }

    .table-wrap--stack td.table-empty {
      text-align: center;
    }
  }
}
