/* ============================================================
   AGENT FLOW — component layer v1.0
   Wajdan CRM AI · Powered by wijdan AI

   The Agent Flow Design Guide's component specs, expressed as
   ADDITIVE classes. Every selector here is a name the app did not
   already use, so loading this file changes nothing until a page
   opts in — except the two global rules in §0, which are
   deliberate app-wide corrections.

   Consumes only tokens from base.css (compiled into tailwind.css).
   No hex literals, and none of the four effects the opaque-only gate
   bans: backdrop blur, gradient-clipped text, theme-attribute selectors
   or OS colour-scheme queries. See unittesting/marketing-analyst/css/
   opaque-only.test.js for the exact patterns — do not spell them out
   here, the gate greps raw file text and a comment naming them fails it.

   Load order (partials/head-assets.ejs):
     studio-tokens-v2*.css -> tailwind.css -> ui-kit.css -> THIS FILE
     -> sidebar.css -> shared-pages.css -> component-aliases.css
     -> notifications-toast.css -> rtl.css
     -> per-page sheets -> role theme.
   This file is NOT last. Equal-specificity rules in any of those files
   win over it, so a component class here only lands where the page
   sheet does not redeclare the same properties.
   ============================================================ */

/* ============================================================
   0. Global corrections
   ============================================================ */

/* Focus ring — "apply everywhere. Never removed, never replaced by
   colour alone." (Design Guide 05.) Scoped to :focus-visible so it
   never fires on a mouse click.

   An outline, not a box-shadow: a box-shadow ring is defeated by any
   `box-shadow: ... !important` already in the cascade (sidebar.css:201,
   admin-theme.css:149, leads.css:1030), is clipped by an ancestor's
   overflow:hidden, and is not painted in forced-colors mode.
   A plain selector list, NOT :where(). At :where()'s zero specificity
   this ring lost to every `outline: none` suppression in the cascade
   — 116 of them, five carrying !important — so controls such as the
   .admin-role-page .btn-secondary pair on admin/lead-management were
   left with no focus indicator at all: a WCAG 2.4.11 failure, not a
   cosmetic one. Unwrapped and marked !important, the ring now survives
   all of them. The page rules that legitimately traded `outline: none`
   for their own box-shadow ring have been deleted alongside this
   change, so nothing stacks a second concentric ring. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
[role="button"]:focus-visible,
[role="tab"]:focus-visible,
[role="menuitem"]:focus-visible {
  outline: 2px solid var(--primary-600) !important;
  outline-offset: 2px !important;
}

/* LTR data inside Arabic text: phone numbers, emails, lead IDs,
   currency. "Never mirrors." (Design Guide 17.) */
.ltr-run {
  direction: ltr;
  unicode-bidi: isolate;
}

/* ============================================================
   1. Type roles (Design Guide 03)
   Body is 14/400/1.6. Negative tracking only at >=24px.
   ============================================================ */
.t-display {
  font-size: var(--fs-5xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--fg1);
}
.t-h1 {
  font-size: var(--fs-4xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--fg1);
}
.t-h2 {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--fg1);
}
.t-h3 {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  color: var(--fg1);
}
.t-card-title {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
  color: var(--fg1);
}
.t-section-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--fg1);
}
.t-body {
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--fg2);
}
.t-small {
  font-size: var(--fs-sm);
  color: var(--fg-muted);
}
.t-micro {
  font-size: var(--fs-xs);
  color: var(--fg-muted);
}
.t-eyebrow {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  color: var(--fg-muted);
}
.t-stat {
  font-size: var(--fs-5xl);
  font-weight: var(--fw-bold);
  line-height: 1;
  color: var(--fg1);
  font-variant-numeric: tabular-nums;
}
.t-mono {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}
.t-link {
  color: var(--primary-600);
  font-weight: var(--fw-medium);
  text-decoration: none;
}
.t-link:hover {
  color: var(--primary-700);
  text-decoration: underline;
}

/* Any figure that updates or sits in a column. Without this, live
   counters jitter. (Design Guide 03.) */
.t-num,
.num {
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   2. Buttons — the variants and sizes the app was missing (Design Guide 08)
   .btn / .btn-primary / -secondary / -ghost / -danger already ship in
   tailwind.css; the tonal variant and the size/shape classes are new.
   ============================================================ */
.btn-tonal {
  background: var(--primary-50);
  color: var(--primary-700);
  border: 1px solid var(--primary-100);
}
.btn-tonal:hover {
  background: var(--primary-100);
  border-color: var(--primary-200);
}

/* Sizes: 32 small · 40 default · 48 large (Design Guide 08). Large is for
   login and empty-state CTAs only. These live here rather than in the Tailwind
   @layer because component classes there are purged when unused, so a size
   class disappears from the build the moment its last usage does. */
.btn-sm {
  min-height: 32px;
  padding: 0 12px;
  font-size: var(--fs-sm);
  border-radius: var(--radius-md);
}
.btn-sm .material-symbols-outlined { font-size: 16px; }

.btn-lg {
  min-height: 48px;
  padding: 0 var(--spacing-lg);
  font-size: var(--fs-md);
}

/* Icon-only buttons are square and always carry an aria-label. */
.btn-icon {
  width: 40px;
  padding: 0;
  justify-content: center;
}
.btn-icon.btn-sm { width: 32px; }
.btn-icon.btn-lg { width: 48px; }

/* Touch target floor. */
@media (pointer: coarse) {
  .btn,
  .btn-icon {
    min-height: 44px;
  }
  .btn-icon { min-width: 44px; }
}

/* Dialog / form action rows: packed at the inline-end edge, primary
   last. Gap 8-12px. `justify-content` is flex-relative, not physical,
   so an RTL row already packs at the physical left with the primary
   furthest left — it needs no [dir] override, and row-reverse here
   would flip the axis a second time back to the LTR arrangement. */
.af-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  flex-wrap: wrap;
}

/* ============================================================
   3. Icon tiles (Design Guide 07)
   44px, 12px radius, flat fill. Default slate; the indigo tint
   marks an AI surface; a semantic tint only for a tile that
   reports an actual state.
   ============================================================ */
.icon-tile {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-lg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--slate-100);
  color: var(--slate-600);
  flex: none;
}
.icon-tile .material-symbols-outlined { font-size: 24px; }
.icon-tile.is-ai { background: var(--primary-50); color: var(--primary-700); }
.icon-tile.is-warning { background: var(--warning-50); color: var(--warning-700); }
.icon-tile.is-error { background: var(--error-50); color: var(--error-700); }
.icon-tile.is-success { background: var(--success-50); color: var(--success-700); }
.icon-tile.is-sm { width: 32px; height: 32px; border-radius: var(--radius-md); }
.icon-tile.is-sm .material-symbols-outlined { font-size: 18px; }

/* ============================================================
   4. Status pills (Design Guide 10)
   22px tall, uppercase 11px, fully rounded, always the
   50-background + 600/700-text pairing. Never a saturated fill
   behind small text. A row carries at most one pill.
   ============================================================ */
.badge-new       { background: var(--primary-50); color: var(--primary-700); }
.badge-contacted { background: var(--info-50);    color: var(--info-600); }
.badge-qualified { background: var(--warning-50); color: var(--warning-600); }
.badge-converted { background: var(--success-50); color: var(--success-700); }
.badge-lost      { background: var(--gray-100);   color: var(--gray-600); }
.badge-hot       { background: var(--error-50);   color: var(--error-600); }

/* ============================================================
   5. Avatars (Design Guide 10)
   Initials on the role's indigo step, white text. Stacks overlap
   by -10px with a 2px white ring.
   ============================================================ */
.af-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  background: var(--primary-500);
  color: var(--white);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
  flex: none;
  text-transform: uppercase;
  overflow: hidden;
}
.af-avatar-24 { width: 24px; height: 24px; font-size: 10px; }
.af-avatar-40 { width: 40px; height: 40px; font-size: var(--fs-sm); }
.af-avatar-48 { width: 48px; height: 48px; font-size: var(--fs-base); }

/* Roles differ by value, not hue. */
.af-avatar.is-admin        { background: var(--role-admin); }
.af-avatar.is-sales-admin  { background: var(--role-sales-admin); }
.af-avatar.is-sales-rep    { background: var(--role-sales-rep); }
.af-avatar.is-marketing    { background: var(--role-marketing); }

.af-avatar-stack { display: inline-flex; align-items: center; }
.af-avatar-stack .af-avatar {
  box-shadow: 0 0 0 2px var(--white);
  margin-inline-start: -10px;
}
.af-avatar-stack .af-avatar:first-child { margin-inline-start: 0; }
.af-avatar-stack .af-avatar.is-overflow {
  background: var(--slate-200);
  color: var(--slate-600);
}

/* ============================================================
   6. Lead score meter (Design Guide 10)
   6px track, pill radius, colour from the temperature scale.
   Never a red-to-green gradient. The number is always shown —
   colour alone is not a signal.
   ============================================================ */
.af-score {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
}
.af-score-track {
  flex: 1 1 auto;
  min-width: 56px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--slate-200);
  overflow: hidden;
}
.af-score-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--slate-500);
  transition: width var(--dur-slow) var(--ease-out);
}
.af-score-value {
  font-variant-numeric: tabular-nums;
  font-weight: var(--fw-semibold);
  font-size: var(--fs-sm);
  color: var(--fg2);
  min-width: 2.25ch;
  text-align: end;
}
/* Hot 80-100 · Warm 60-79 · Cool 40-59 · Cold 0-39 */
.af-score.is-hot  .af-score-fill { background: var(--error-500); }
.af-score.is-warm .af-score-fill { background: var(--warning-500); }
.af-score.is-cool .af-score-fill { background: var(--info-500); }
.af-score.is-cold .af-score-fill { background: var(--slate-500); }

/* ============================================================
   7. Deltas & counters (Design Guide 10)
   A directional arrow so the signal survives greyscale and
   colour-blindness. Neutral until a threshold is crossed.
   ============================================================ */
.af-delta {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
}
.af-delta .material-symbols-outlined { font-size: 16px; }
.af-delta.is-good { color: var(--success-600); }
.af-delta.is-bad  { color: var(--error-600); }

/* ============================================================
   8. Analytics card, AI surface, empty state (Design Guide 11)
   ============================================================ */
.af-analytics-card {
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
  padding: var(--spacing-xl);
  box-shadow: var(--shadow-sm);
}

/* "AI surfaces are the only place the brand gradient touches a
   content surface." One per screen. */
.af-ai-card {
  position: relative;
  border-radius: var(--radius-lg);
  padding: var(--spacing-lg);
  background: var(--grad-brand-wave);
  color: var(--white);
  box-shadow: var(--shadow-md);
}
.af-ai-card .t-eyebrow,
.af-ai-card .t-small { color: var(--primary-100); }
.af-ai-card .icon-tile {
  background: var(--primary-400);
  color: var(--white);
}

/* Empty state — icon tile, one sentence of cause, one action.
   .campaign-empty-state (admin/dashboard, built by the campaign chart's
   own JS) and .no-data-message (marketing-analyst/dashboard) were two
   near-copies of this component; they are aliased here rather than kept
   as separate definitions. */
.af-empty,
.campaign-empty-state,
.no-data-message {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-md);
  padding: var(--spacing-2xl) var(--spacing-lg);
  text-align: center;
}
/* `hidden` still wins. tailwind.css's [hidden] rule loads BEFORE this
   file at equal specificity, so the display above would otherwise
   un-hide the two toggled empty states in marketing-analyst/dashboard. */
.af-empty[hidden],
.campaign-empty-state[hidden],
.no-data-message[hidden] {
  display: none;
}
.af-empty-title {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
  color: var(--fg1);
}
.af-empty-body {
  font-size: var(--fs-base);
  color: var(--fg-muted);
  max-width: 56ch;
  line-height: var(--lh-relaxed);
}

/* Loading skeleton — mirrors the shape it replaces. Never a
   centred spinner over an empty card. */
.af-skeleton {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--slate-100);
}
.af-skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(var(--af-skeleton-from, -100%));
  background: linear-gradient(90deg, transparent 0%, var(--gray-200) 50%, transparent 100%);
  animation: af-skeleton-sweep 1.4s linear infinite;
}
/* The offset is a custom property the keyframes read off the element,
   not a [dir] override of `transform`. Overriding the cascaded
   transform alone would make the implicit 0% keyframe equal the 100%
   one in RTL, freezing the sweep. Same approach as
   property-inventory.css:1385. */
[dir="rtl"] .af-skeleton::after {
  --af-skeleton-from: 100%;
  --af-skeleton-to: -100%;
}
@keyframes af-skeleton-sweep {
  from { transform: translateX(var(--af-skeleton-from, -100%)); }
  to { transform: translateX(var(--af-skeleton-to, 100%)); }
}
.af-skeleton-line { height: 12px; margin-block-end: var(--spacing-sm); }
.af-skeleton-line:last-child { margin-block-end: 0; width: 60%; }

/* ============================================================
   9. Underline tabs (Design Guide 13)
   Underline tabs only — no pill tabs, no boxed tabs. Segmented
   controls are for filters, not navigation.
   ============================================================ */
.af-tabs {
  display: flex;
  align-items: center;
  gap: var(--spacing-lg);
  border-bottom: 1px solid var(--border-color);
}
.af-tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: 12px 0;
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--fg-muted);
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}
.af-tab:hover { color: var(--fg2); }
.af-tab.is-active,
.af-tab[aria-selected="true"] {
  color: var(--primary-700);
  font-weight: var(--fw-semibold);
}
.af-tab.is-active::after,
.af-tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: -1px;
  height: 2px;
  background: var(--primary-600);
  border-radius: var(--radius-pill);
}

/* ============================================================
   10. Filter chips (Design Guide 09)
   Active chips are indigo-50 with a 100-step border and carry a
   dismiss. Inactive are white with a neutral border and never do.
   ============================================================ */
.af-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-sm);
  height: 30px;
  padding: 0 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-color);
  background: var(--white);
  color: var(--fg2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.af-chip:hover { background: var(--gray-50); border-color: var(--gray-300); }
.af-chip.is-active,
.af-chip[aria-pressed="true"] {
  background: var(--primary-50);
  border-color: var(--primary-100);
  color: var(--primary-700);
}
.af-chip .material-symbols-outlined { font-size: 16px; }

/* ============================================================
   11. Overlays (Design Guide 14)
   Scrims are indigo-tinted, never neutral black. Modal headers
   are white with a 1px rule — never a coloured banner.
   ============================================================ */
/* stylelint-disable-next-line declaration-property-value-disallowed-list --
   a scrim is translucent by definition; the value is specified verbatim by
   the Design Guide (14) and is indigo-tinted, never neutral black. */
.af-scrim { background: rgba(16, 16, 79, 0.45); }

.af-modal {
  background: var(--bg-card);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-xl);
  overflow: hidden;
}
.af-modal-header {
  padding: var(--spacing-lg);
  background: var(--bg-card);
  border-bottom: 1px solid var(--border-color);
}
.af-modal-body { padding: var(--spacing-lg); }
.af-modal-footer {
  padding: var(--spacing-lg);
  border-top: 1px solid var(--border-color);
  background: var(--bg-card);
}

/* Inline alert — one line of cause, semantic tint, no shadow. */
.af-alert {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: var(--spacing-md) var(--spacing-md);
  border-radius: var(--radius-lg);
  border: 1px solid var(--info-200);
  background: var(--info-50);
  color: var(--fg2);
  font-size: var(--fs-base);
}
.af-alert.is-warning { background: var(--warning-50); border-color: var(--warning-200); }
.af-alert.is-error   { background: var(--error-50);   border-color: var(--error-200); }
.af-alert.is-success { background: var(--success-50); border-color: var(--success-200); }
.af-alert .material-symbols-outlined { font-size: 20px; flex: none; }

/* ============================================================
   12. Data-visualisation helpers (Design Guide 15)
   Series colours are assigned IN ORDER, never picked by feel.
   ============================================================ */
.af-series-1 { color: var(--chart-1); }
.af-series-2 { color: var(--chart-2); }
.af-series-3 { color: var(--chart-3); }
.af-series-4 { color: var(--chart-4); }
.af-series-5 { color: var(--chart-5); }
.af-series-6 { color: var(--chart-6); }
.af-series-good { color: var(--chart-7); }
.af-series-risk { color: var(--chart-8); }

.af-chart-caption {
  font-size: var(--fs-xs);
  color: var(--fg-muted);
}

/* ============================================================
   13. Motion utilities (Design Guide 06)
   Entrances only. Stagger lists by 40ms, cap the run at 6.
   ============================================================ */
@keyframes af-fade-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
.anim-fade-up {
  animation: af-fade-up var(--dur-slow) var(--ease-emphasized) both;
}
.anim-stagger > *:nth-child(1) { animation-delay: 0ms; }
.anim-stagger > *:nth-child(2) { animation-delay: 40ms; }
.anim-stagger > *:nth-child(3) { animation-delay: 80ms; }
.anim-stagger > *:nth-child(4) { animation-delay: 120ms; }
.anim-stagger > *:nth-child(5) { animation-delay: 160ms; }
.anim-stagger > *:nth-child(6) { animation-delay: 200ms; }

@media (prefers-reduced-motion: reduce) {
  .anim-fade-up,
  .af-skeleton::after {
    animation: none;
  }
}

/* ============================================================
   14. Page header — retired as a card
   Every page used to open with a white .modern-page-header card holding the
   page title, a description and that page's actions. The title now lives in
   the topbar (partials/topbar-page-title.ejs) and js/topbar-page-actions.js
   moves the actions out — to the topbar when there are one or two, or to
   their own row at the top of the content column when there are more. So
   nothing is left to show. The 36 call sites are untouched — this collapses
   the row instead.

   These rules live here because this file is loaded on every page, including
   the five that build their own <head>. (modern-page-header.css was retired:
   it defined nothing and only 18 of the 36 call sites ever linked it.)
   !important is
   required: several later-loading page stylesheets style the card that way.
   ============================================================ */
.modern-page-header {
  display: none !important;
}

/* Page actions, placed by js/topbar-page-actions.js.

   Preferred: merged onto the page's first section heading, so the primary
   action sits beside the label it belongs to and costs no vertical band —
   "Performance Overview  ......  [Add Lead]".
     .af-section-head    a heading the script paired with the actions
     .af-actions-host    a heading row that already existed; actions joined it
     .af-section-actions the actions themselves, in either case
   Fallback: .af-page-actionbar, a row of its own, for the few pages that
   have no section heading at all (lead-distribution, all-campaigns).

   `margin-inline-start: auto` and `justify-content` are flex-relative, so
   RTL packs at the physical left with no [dir] override. */

/* The paired heading row. The heading keeps its own type styles; only its
   block margins are neutralised, because the row owns the spacing now. */
.af-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--spacing-md);
  margin-block-end: var(--spacing-md);
}

.af-section-head > :first-child {
  margin-block: 0;
  flex: 1 1 auto;
  min-width: 0;
}

/* An existing heading row only needs to not fight the actions' auto margin. */
.af-actions-host {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  flex-wrap: wrap;
}

.af-section-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-inline-start: auto;
  /* It sat inside a hidden .modern-page-header until the script moved it,
     so it must not carry that card's padding or borders. */
  padding: 0;
  border: 0;
  background: none;
}

/* Action buttons are the guide's DEFAULT size — 40px tall, 10px radius.
   Several pages had grown their own 44px/12px variants (.add-lead-btn used
   0.75rem block padding and --radius-lg), which read as oversized next to a
   section title. Block padding is zeroed so min-height alone sets the box. */
.af-section-actions > *,
.af-page-actionbar > * {
  flex: none;
  white-space: nowrap;
  min-height: 40px;
  padding-block: 0;
  border-radius: var(--radius-button);
}

/* Fallback row — no section heading to merge into. */
.af-page-actionbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  /* 12px — the guide's action-row gap, and what lead-management already
     used inside the old header card. */
  gap: 12px;
  margin-block-end: var(--spacing-lg);
  /* It sits inside a hidden .modern-page-header until the script moves it,
     so it must not inherit that card's padding or borders. */
  padding: 0;
  border: 0;
  background: none;
}

@media (max-width: 640px) {
  /* One column on a phone: half-width buttons read as a broken grid. The
     merged row stacks the heading above the actions rather than squeezing
     both onto one line. */
  .af-section-head {
    align-items: stretch;
  }
  .af-section-actions,
  .af-page-actionbar {
    justify-content: stretch;
    margin-inline-start: 0;
    width: 100%;
  }
  .af-section-actions > *,
  .af-page-actionbar > * {
    flex: 1 1 100%;
    justify-content: center;
  }
}

/* Titles rendered outside a page header keep the type scale. */
.modern-page-title,
.page-title {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  color: var(--fg1);
  margin: 0;
}

.modern-page-description,
.page-description {
  font-size: var(--fs-base);
  color: var(--fg-muted);
  margin: 0;
}

/* .btn-ref-* predate the shared button layer and are still the markup used by
   every page header, so they alias the Agent Flow button spec. They now render
   in the topbar, which is why they must be defined app-wide. */
.btn-ref-secondary,
.btn-ref-primary {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-sm);
  min-height: 40px;
  padding: 0 var(--spacing-md);
  border-radius: var(--radius-button);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: all var(--dur-base) var(--ease-out);
}

.btn-ref-secondary {
  background: var(--white);
  color: var(--fg2);
  border: 1px solid var(--border-color);
  box-shadow: var(--shadow-sm);
}

.btn-ref-secondary:hover {
  background: var(--gray-50);
  border-color: var(--gray-400);
}

.btn-ref-primary {
  background: var(--grad-primary-strong);
  color: var(--white);
  border: 1px solid transparent;
  box-shadow: var(--glow-primary);
}

.btn-ref-primary:hover {
  background: var(--grad-primary-hover);
  box-shadow: var(--glow-primary-hover);
}

.btn-ref-secondary .material-symbols-outlined,
.btn-ref-primary .material-symbols-outlined {
  font-size: 1.125rem;
}

/* ============================================================
   15. PAGE FRAME (Agent Flow Geometry — canonical, v1)
   ------------------------------------------------------------
   The app had 8 content caps (none/600/1100/1200/1280/1360/1400/1600),
   13 wrappers whose padding lived in an inline style="" attribute, and
   17 max-width declarations inside the layout frame alone. This is the
   one frame. Every value below is a token from the six-step 4pt scale
   or --content-max; there are no literals except the two grid track
   floors the guide names by hand (240px KPI, 320px detail rail).

   --content-max (85rem) and --page-pad-x (2rem) are both already
   defined in tailwind.css and were referenced ZERO times before this
   block. --page-pad-x duplicates --spacing-xl and is deliberately not
   used here — one name per value.

   CASCADE NOTE. This file loads 5th (head-assets.ejs:25), BEFORE
   sidebar/shared-pages/per-page sheets/role themes. Every selector in
   this section is (0,1,0) with no !important, so a per-page sheet that
   also declares the frame would win. That is why the frame is declared
   ONLY here: every competing max-width / page padding / centring
   declaration has been deleted from the per-page sheets rather than
   out-specified. One owner per property. `scripts/design/frame-audit.js`
   measures all 49 page views and is the regression test for this.
   ============================================================ */

/* One wrapper. Centred, capped, 32px page padding on both axes.

   The list is every class that appears as a page wrapper in a view.
   It is not open-ended: a new page uses .af-page. */
.af-page,
.sa-page-wrapper,
.sr-page-shell,
.dashboard-content,
.ma-page-content,
.scoring-container {
  width: 100%;
  max-width: var(--content-max);
  margin-inline: auto;
  padding-block: var(--spacing-xl);
  padding-inline: var(--spacing-xl);
}

/* App-shell exception. Chat and the WhatsApp consoles are not documents
   in a content column — they are two-pane apps that own the viewport
   below the topbar, size their own panes and scroll internally. Capping
   and padding them pushes their fixed-height panes past the fold. The
   exception is declared here so it reads as a decision, not a page that
   escaped the frame. */
.af-shell,
.af-shell.ma-page-content,
.af-shell.sa-page-wrapper,
.af-shell.sr-page-shell,
.af-shell.af-page {
  max-width: none;
  margin-inline: 0;
  padding-block: 0;
  padding-inline: 0;
}

/* Transitional compound — (0,2,0), the minimum specificity that works.
   During migration a view may carry BOTH the canonical class and its
   legacy one (a main element with class "sa-page-wrapper af-page").
   At (0,1,0) the later-loading legacy rule would win and the opt-in
   would silently do nothing, so this compound re-asserts only the
   three properties a legacy wrapper overrides. Delete this block once
   the legacy declarations are gone. */
.af-page.sa-page-wrapper,
.af-page.sr-page-shell,
.af-page.dashboard-content,
.af-page.ma-page-content,
.af-page.calendar-page-shell,
.af-page.user-management-content {
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: var(--spacing-xl);
}

/* One mobile step, at the app's dominant breakpoint (768px carries 92
   of 258 media queries). This replaces the 0.875/0.75/0.625rem tail in
   admin-responsive.css and team-performance.css. */
@media (max-width: 768px) {
  .af-page,
  .sa-page-wrapper,
  .sr-page-shell,
  .dashboard-content,
  .ma-page-content,
  .scoring-container {
    padding-block: var(--spacing-md);
    padding-inline: var(--spacing-md);
  }

  .af-shell {
    padding-block: 0;
    padding-inline: 0;
  }
}

/* Section rhythm — 48px between major page sections, nothing after the
   last one. Replaces the 3rem/2rem/1.5rem mix on the top grids. */
.af-page-section {
  margin-block-end: var(--spacing-2xl);
}
.af-page-section:last-child {
  margin-block-end: 0;
}

/* ------------------------------------------------------------
   Grids. Gap is --spacing-lg (24px) everywhere — the measured
   majority (4 of 6 top grids already sit at 1.5rem).
   ------------------------------------------------------------ */
.af-grid {
  display: grid;
  gap: var(--spacing-lg);
}

/* KPI row. minmax(240px, 1fr) is the figure the guide names. At the
   1360px cap minus 64px of page padding a four-item row resolves to
   four ~306px tracks; auto-fit collapses empty tracks, so a row of 3
   stays 3-up rather than stretching. */
.af-grid-kpi {
  display: grid;
  gap: var(--spacing-lg);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

/* Chart + list. */
.af-grid-2-1 {
  display: grid;
  gap: var(--spacing-lg);
  grid-template-columns: 2fr 1fr;
}

/* Record + detail rail. */
.af-grid-record {
  display: grid;
  gap: var(--spacing-lg);
  grid-template-columns: 1fr 320px;
}

/* Both two-column patterns collapse at the guide's 1024 breakpoint. */
@media (max-width: 1024px) {
  .af-grid-2-1,
  .af-grid-record {
    grid-template-columns: 1fr;
  }
}

/* Legacy KPI grid names, pointed at the canonical row. Inert until the
   page sheet's own declaration is deleted (see cascade note above).
   .kpi-grid is included because both of its definitions are dead
   (0 markup uses) and the name should resolve to something correct if
   it is ever emitted. */
.quick-stats-grid,
.stat-cards-grid,
.kpi-cards-grid,
.kpi-grid,
.lead-counts-grid {
  display: grid;
  gap: var(--spacing-lg);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

/* ============================================================
   16. THE STAT CARD (canonical)
   ------------------------------------------------------------
   CHOICE OF NAME. .stat-card is the app's most-used stat container —
   44 class tokens across 6 views (admin/dashboard, admin/weekly-reports,
   admin/campaign-management, sales-admin/dashboard,
   sales-representative/dashboard, and one partial). .af-stat-card is
   the canonical name, but the geometry is defined on BOTH so those 6
   views need no markup change at all. ui-kit.css's .stat-tile — written
   as "one canonical replacement" — has 0 markup uses and is not revived.

   ANATOMY: icon tile -> label -> figure -> delta -> caption, siblings
   in one flex column. The card audit found two live shapes:
     A  .stat-card > .stat-card-header(.stat-icon + .stat-info(
          h3.stat-title + p.stat-subtitle)) + .stat-value-container(
          p.stat-value)                        — admin & sales-rep
     C  .stat-card > span.stat-card-label + .stat-card-body(
          span.stat-card-value)                — sales-admin
   Rather than rewrite six string/DOM builders, the wrapper divs are
   flattened with display:contents and the caption is moved after the
   figure with flex `order`. Both shapes then render the canonical
   anatomy with ZERO markup edits — migrating markup is riskier than
   redefining a class.

   No accent bar. The 4px ::before is retired app-wide and is not
   reintroduced here; the last live one (admin-dashboard's .funnel-card)
   and the dead paint rules stranded behind it are now gone too.
   ============================================================ */
/* The alias list is every page-sheet stat container whose own recipe the
   stat-card migration deleted: twelve sheets each hand-rolled the same
   white box. Naming them here collapses all of them onto this one
   surface with no markup edit, the same trade section 16 already makes
   for .stat-card. */
.af-stat-card,
.stat-card,
.kpi-card,
.ca-summary-card,
.metric-card,
.ld-summary-card,
.status-card,
.summary-card,
.pi-stat-card,
.lead-count-stat-card,
.member-stat-card,
.validation-stat,
.tp-preview-tile {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  padding: var(--spacing-lg);
  box-shadow: var(--shadow-sm);
  color: inherit;
  text-decoration: none;
}

/* Flatten the legacy wrapper divs so their children become direct flex
   items of the card. display:contents removes the box, not the
   element, so ids, handlers and JS lookups all survive. */
.af-stat-card .stat-info,
.stat-card .stat-info,
.af-stat-card .stat-value-container,
.stat-card .stat-value-container,
.af-stat-card .stat-card-body,
.stat-card .stat-card-body {
  display: contents;
}

/* Shape A's header keeps a box only because it holds the icon; with the
   info block flattened it is a plain icon row. */
.af-stat-card .stat-card-header,
.stat-card .stat-card-header {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: 0;
  margin: 0;
  border: 0;
}

/* Icon tile — 44px, --radius-lg, flat --slate-100 / --slate-600.
   .stat-icon is the legacy name; the geometry is the section 3
   .icon-tile recipe. */
.af-stat-icon,
.af-stat-card .stat-icon,
.stat-card .stat-icon {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-lg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--slate-100);
  color: var(--slate-600);
  flex: none;
}
.af-stat-icon .material-symbols-outlined,
.af-stat-card .stat-icon .material-symbols-outlined,
.stat-card .stat-icon .material-symbols-outlined {
  font-size: 24px;
}

/* Label — one line, 14px semibold, muted. */
.af-stat-label,
.af-stat-card .stat-card-label,
.stat-card .stat-card-label,
.af-stat-card .stat-title,
.stat-card .stat-title,
.ld-summary-label,
.status-card-label,
.tp-preview-tile-label,
.member-stat-card .stat-label,
.validation-stat .stat-label {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  color: var(--fg-muted);
  margin: 0;
}

/* Figure — 30px bold, tabular, tight. One emphasis per surface. */
.af-stat-figure,
.af-stat-card .stat-value,
.stat-card .stat-value,
.af-stat-card .stat-card-value,
.stat-card .stat-card-value,
.ld-summary-value,
.status-card-value,
.tp-preview-tile-value,
.member-stat-card .stat-value,
.validation-stat .stat-value {
  font-size: var(--fs-4xl);
  font-weight: var(--fw-bold);
  line-height: 1;
  color: var(--fg1);
  font-variant-numeric: tabular-nums;
  margin: 0;
}

/* Caption — 12px muted, and it belongs AFTER the figure. Shape A emits
   the subtitle before the value, so flex order moves it without
   touching the EJS. `order` is flex-relative and needs no [dir] rule. */
.af-stat-caption,
.af-stat-card .stat-subtitle,
.stat-card .stat-subtitle,
.status-card-sub,
.tp-preview-tile-note {
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
  color: var(--fg-muted);
  margin: 0;
  order: 1;
}

/* Delta — the section 7 .af-delta semantics: neutral text plus a
   directional arrow, green or red ONLY past a threshold.
   .stat-card-trend is the legacy name and is normalised to the same
   neutral default; the sheets that colour it unconditionally are
   deleted by the migration. */
.af-stat-delta,
.af-stat-card .stat-card-trend,
.stat-card .stat-card-trend,
.lead-count-card-trend {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--fg-muted);
  font-variant-numeric: tabular-nums;
  order: 1;
}
.af-stat-delta.is-good { color: var(--success-600); }
.af-stat-delta.is-bad  { color: var(--error-600); }

/* ---- modifiers ---- */

/* Interactive. Hover is the only place --shadow-md touches a card, and
   there is no translate — a lifting KPI row is ornament, not data.
   Focus is already covered by the section 0 :focus-visible ring. */
.af-stat-card.is-linked,
.stat-card.is-linked,
a.af-stat-card,
a.stat-card,
.af-stat-card[role="button"],
.stat-card[role="button"] {
  cursor: pointer;
  transition: box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.af-stat-card.is-linked:hover,
.stat-card.is-linked:hover,
a.af-stat-card:hover,
a.stat-card:hover,
.af-stat-card[role="button"]:hover,
.stat-card[role="button"]:hover {
  border-color: var(--gray-300);
  box-shadow: var(--shadow-md);
}

/* Dense rows of 8+ tiles. */
.af-stat-card.is-compact,
.stat-card.is-compact {
  padding: var(--spacing-md);
  gap: var(--spacing-xs);
}
.af-stat-card.is-compact .af-stat-figure,
.stat-card.is-compact .stat-value,
.stat-card.is-compact .stat-card-value {
  font-size: var(--fs-3xl);
}

/* Icon and text on one row — the .pi-stat-card shape. */
.af-stat-card.has-icon-inline,
.stat-card.has-icon-inline {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: var(--spacing-md);
  row-gap: var(--spacing-xs);
}
.af-stat-card.has-icon-inline .af-stat-icon,
.stat-card.has-icon-inline .stat-icon {
  grid-row: 1 / span 2;
}

/* Segmented strip — cells inside ONE surface, divided by a rule, not
   individually bordered cards. This is what .wa-monitor-summary-item,
   .team-performance-kpi-item and .summary-item actually are; forcing
   them into a card would give each a border and break the rhythm. */
.af-stat-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-lg);
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  padding: var(--spacing-lg);
}
.af-stat-strip-item {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
  flex: 1 1 0;
  min-width: 0;
  padding-inline-start: var(--spacing-lg);
  border-inline-start: 1px solid var(--border-color);
}
.af-stat-strip-item:first-child {
  padding-inline-start: 0;
  border-inline-start: 0;
}

/* ============================================================
   17. THE CHART PANEL (canonical)
   ------------------------------------------------------------
   CHOICE OF NAME. .af-chart-card is canonical. Its geometry is the
   .af-analytics-card recipe that already sat unused in section 8, and
   it is shared with the three existing panel classes that carry real
   markup and are already the same component: .analytics-card (9 uses),
   .md-modern-card (10) and .ca-analytics-panel (2). The last two ship
   HAND-COPIED clones of the same header rule — collapsing them here
   removes one of the pair outright.

   DELIBERATELY EXCLUDED:
     .adsm-chart-card — marketing-analyst/ads-manager.ejs:10-12 re-links
       studio-tokens-v2*.css AFTER head-assets, so --radius-xl resolves
       to 12px on that ONE page instead of 16px. Aliasing it before that
       duplicate link is removed would make one class render two
       different radii. Migration Group 0 removes the link first.
     .chart-card — its only live definition (ai-reports.css:849, built by
       ai-reports.js) is a --gray-50 inset tile on a white report page,
       where a white panel would have no edge. It stays different.

   Analytics geometry: 16px radius, 32px padding — the guide's analytics
   block, and already exactly what admin-dashboard.css's
   .analytics-section uses.
   ============================================================ */
.af-chart-card,
.analytics-card,
.md-modern-card,
.ca-analytics-panel {
  --af-chart-h: 280px;
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-xl);
  padding: var(--spacing-xl);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

/* Transitional compound — (0,2,0), the same device section 15 uses for
   the page wrapper. Every marketing-analyst panel is written
   `md-modern-card modern-card` (and `ca-analytics-panel modern-card` on
   campaign-analytics), and shared-pages.css still declares .modern-card
   AFTER this file. Without this, deleting the per-page panel rule would
   hand these three properties to that legacy declaration — a gradient
   fill on a 20px radius — instead of to the canonical panel. It
   re-asserts only what .modern-card overrides. Delete it with
   .modern-card. */
.af-chart-card.modern-card,
.md-modern-card.modern-card,
.ca-analytics-panel.modern-card {
  background: var(--bg-card);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
}

/* Header. No bottom border: the title is already the one emphasis on
   this surface. The two clone rules it replaces each add a --gray-100
   rule AND a 17px bold title; both are dropped. `padding: 0` is
   required because those clones put the panel's inset on the header
   (1.125rem 1.25rem 0.875rem) — the panel owns it now. */
.af-chart-card-header,
.md-modern-card-header,
.ca-panel-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-md);
  margin-block-end: var(--spacing-lg);
  padding: 0;
  border: 0;
  background: none;
}

/* Same transitional compound, for the header. The marketing-analyst
   headers are written `md-modern-card-header modern-card-header` and
   shared-pages.css's .modern-card-header — a tinted gradient band with
   a 1.5rem/2rem inset — loads after this file. Delete with
   .modern-card-header. */
.af-chart-card-header.modern-card-header,
.md-modern-card-header.modern-card-header {
  align-items: flex-start;
  padding: 0;
  border: 0;
  background: none;
}

/* Title — 16px semibold, the section 1 .t-section-title role. Replaces
   the 1.0625rem/17px off-scale title in both clones and the two
   conflicting .chart-title definitions (1.125rem vs 1rem). */
.af-chart-card-title,
.af-chart-card-header h3,
.md-modern-card-header h3,
.ca-panel-header h3,
.chart-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  color: var(--fg1);
  margin: 0;
}

/* Caption — extends the section 12 .af-chart-caption with its position
   in the header. One sentence, under the title, never beside it. */
.af-chart-card-header .af-chart-caption,
.af-chart-card-caption {
  display: block;
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
  color: var(--fg-muted);
  margin: 0;
  margin-block-start: var(--spacing-xs);
}

/* Body fills the panel; the panel already supplies the inset. */
.af-chart-card-body,
.md-modern-card-body,
.ca-panel-body {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
}

/* The chart box itself. This is what .chart-container ALWAYS meant in
   3 of its 4 conflicting definitions (admin-dashboard.css:667,
   marketing-dashboard.css:794, sales-admin-dashboard.css:266) — a
   positioned canvas box, not a panel. Height is a local custom property
   so a page can size one chart without a new class, and it replaces the
   inline style="position:relative;height:280px" in
   marketing-analyst/dashboard.ejs:346,366. */
.af-chart {
  position: relative;
  width: 100%;
  height: var(--af-chart-h, 280px);
}
.af-chart > canvas {
  display: block;
  max-width: 100%;
}

/* Legend — promotes .ca-chart-legend, the app's only real one, onto the
   spacing scale (it used gap:18px / margin-top:12px). */
.af-chart-legend,
.ca-chart-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-md);
  margin-block-start: var(--spacing-sm);
  margin-block-end: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-xs);
  color: var(--fg-muted);
}
.af-chart-legend > li {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
}
.af-chart-legend-swatch {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-sm);
  background: currentcolor;
  flex: none;
}

/* Empty state — the section 8 .af-empty, sized to the chart box so
   swapping the canvas out does not collapse the panel. The two legacy
   names aliased onto .af-empty in section 8 are chart empty states and
   nothing else, so they take the chart height unqualified. */
.af-chart-card .af-empty,
.analytics-card .af-empty,
.md-modern-card .af-empty,
.ca-analytics-panel .af-empty,
.campaign-empty-state,
.no-data-message {
  min-height: var(--af-chart-h, 280px);
  padding-block: var(--spacing-xl);
  padding-inline: var(--spacing-lg);
}

/* Loading — the skeleton REPLACES the chart, it never scrims it.
   "Mirrors the shape it replaces, never a centred spinner over an empty
   card." Put .af-skeleton and .af-chart on the same element. */
.af-skeleton.af-chart {
  border-radius: var(--radius-lg);
}
.af-chart-card.is-loading .af-chart-card-body > :not(.af-skeleton) {
  display: none;
}

/* ============================================================
   18. SHARED SURFACE GEOMETRY — modal, table, input
   ------------------------------------------------------------
   These three complete the canonical set. Each is (0,1,0) with no
   !important, so it lands only where the page sheet's own declaration
   has been removed.
   ============================================================ */

/* Modal — 16px radius (the value --modal-radius already resolves to and
   ui-kit.css:181 already consumes) and --shadow-xl. Padding is 24px
   block / 32px inline, the exact token spelling of the `1.5rem 2rem`
   that 20 existing rules already use. */
.af-modal-panel {
  background: var(--bg-card);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-xl);
  overflow: hidden;
}
.af-modal-panel .af-modal-header,
.af-modal-panel .af-modal-body,
.af-modal-panel .af-modal-footer {
  padding-block: var(--spacing-lg);
  padding-inline: var(--spacing-xl);
}

/* Table — 16px block / 24px inline cell padding and a 56px row floor:
   16 + (14px x 1.6 body line) + 16 = 54.4, rounded up onto the grid.
   This is already what modern-user-table.css:309 uses; it retires the
   off-scale 0.75rem/12px majority and the 0.625/0.5/0.375rem tail. */
.af-table {
  width: 100%;
  border-collapse: collapse;
}
.af-table th,
.af-table td {
  padding-block: var(--spacing-md);
  padding-inline: var(--spacing-lg);
  text-align: start;
  border-block-end: 1px solid var(--border-color);
}
.af-table tbody tr {
  height: 56px;
}
.af-table thead th {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wider);
  color: var(--fg-muted);
}
.af-table tbody tr:hover {
  background: var(--slate-50);
}
@media (max-width: 768px) {
  .af-table th,
  .af-table td {
    padding-block: var(--spacing-sm);
    padding-inline: var(--spacing-md);
  }
}

/* Input — 40px tall, 12px radius. The guide puts INPUTS on --radius-lg
   alongside cards; ui-kit.css:134 currently resolves --input-radius to
   8px, 4px off spec. Padding is 8px block / 16px inline, the on-scale
   reading of the app's 0.5rem/0.75rem. */
.af-input {
  display: block;
  width: 100%;
  min-height: 40px;
  padding-block: var(--spacing-sm);
  padding-inline: var(--spacing-md);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  background: var(--bg-card);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
  color: var(--fg1);
}
@media (pointer: coarse) {
  .af-input { min-height: 44px; }
}


/* ============================================================
   19. Page transition

   This is a server-rendered multi-page app: every navigation is a full
   document load, so the "transition" is really an entrance on the new
   page. Two mechanisms, both pure CSS, no navigation interception —
   nothing here can swallow a click, a download or a form submit.

   1. Cross-document View Transitions, where the browser supports them.
      The old page cross-fades into the new one natively. Browsers
      without support simply ignore the at-rule.
   2. A content fade-up on load, which every browser gets. It runs on the
      main content only — the shell (sidebar, topbar) stays put, so the
      page reads as content changing inside a stable frame rather than
      the whole window blinking.

   Durations follow the guide: entrances use --ease-emphasized, and the
   shell itself never animates. Honours prefers-reduced-motion.
   ============================================================ */

@view-transition {
  navigation: auto;
}

/* The shell is the same element on both pages, so hold it still and let
   only the content cross-fade. */
::view-transition-group(root) {
  animation-duration: var(--dur-slow);
  animation-timing-function: var(--ease-out);
}

@keyframes af-page-enter {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* Applied to the content column, not <body>: the sidebar and topbar must
   not fade, or the shell flickers on every navigation. The selector list
   is the set of page wrappers the app actually renders. */
.af-page,
.sa-page-wrapper,
.sr-page-shell,
.dashboard-content,
.ma-page-content,
.calendar-page-shell,
.wa-monitor-shell {
  /* `backwards`, never `both`/`forwards`: a filling animation stays "in
     effect" after it ends, and an animation never yields the KEYWORD `none` —
     the `to` keyframe resolves to the identity matrix. A wrapper with any
     transform value is a stacking context AND the containing block for every
     position: fixed descendant, which traps modal overlays inside the content
     column (they size to the wrapper, and the topbar/sidebar paint over the
     scrim). `backwards` still fills the before-phase, so the no-flash start
     this was buying is preserved. */
  animation: af-page-enter var(--dur-slow) var(--ease-emphasized) backwards;
}

@media (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: none;
  }

  .af-page,
  .sa-page-wrapper,
  .sr-page-shell,
  .dashboard-content,
  .ma-page-content,
  .calendar-page-shell,
  .wa-monitor-shell {
    animation: none;
  }
}
