/* Phase 8.1 — additive only, per docs/adr/0012-progressive-enhancement-ui.md
   decision 4: this file grows alongside whatever each UI-pass sub-phase
   actually adds (mobile nav, footer, theme toggle today; tooltips,
   onboarding, chat widget, animation in later sub-phases) — it is not a
   wholesale extraction of core/base.html's own inline <style> block.
   (Phase 9.3 retired core/base.html's own topbar/footer/theme-toggle rules
   that used to live here — see the "Public shell" and "Component system"
   sections below for their --color-*-token-based replacements.) */

/* -- Tooltips ------------------------------------------------------------ */
/* Pure CSS: the bubble is a real, always-present DOM node (not a ::after
   with attr()-sourced content), linked to its trigger by aria-describedby,
   so a screen reader announces it regardless of hover/focus state — only
   the *visual* reveal is :hover/:focus-driven. No JS. */
.tooltip-wrap { position: relative; display: inline-flex; vertical-align: middle; }
.tooltip-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-text-muted);
  font-size: 0.68rem;
  line-height: 1;
  padding: 0;
  cursor: help;
  margin-left: 4px;
}
.tooltip-trigger:hover { color: var(--color-text); border-color: var(--color-text-muted); }
.tooltip-bubble {
  position: absolute;
  bottom: 125%;
  left: 50%;
  transform: translate(-50%, 4px);
  background: var(--color-text);
  color: var(--color-bg);
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 0.78rem;
  line-height: 1.4;
  width: max-content;
  max-width: 220px;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.12s ease, transform 0.12s ease;
  z-index: 20;
}
.tooltip-trigger:hover + .tooltip-bubble,
.tooltip-trigger:focus-visible + .tooltip-bubble {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}

/* -- Onboarding checklist ------------------------------------------------- */
.onboarding-checklist { list-style: none; margin: 0; padding: 0; }
.onboarding-checklist li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--color-border);
}
.onboarding-checklist li:last-child { border-bottom: none; }
.onboarding-step-icon {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  margin-top: 1px;
}
.onboarding-step.is-done .onboarding-step-icon {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-accent-contrast);
}
.onboarding-step-body { flex: 1 1 auto; }
.onboarding-step.is-done .onboarding-step-label { color: var(--color-text-muted); text-decoration: line-through; }
.onboarding-step-actions { margin-top: 4px; display: flex; gap: 10px; font-size: 0.82rem; }
.onboarding-complete {
  color: var(--color-text-muted);
  font-size: 0.85rem;
}

@media (prefers-reduced-motion: reduce) {
  .tooltip-bubble { transition: none; }
}

/* -- Floating help button -------------------------------------------------
   Hidden by default (the `hidden` attribute in base.html's markup); app.js
   reveals it on DOMContentLoaded, so a JS-disabled visitor instead sees
   the plain <noscript> docs link right next to it — see ADR 0012
   decision 1. */
.help-widget {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 50;
}
.help-fab {
  width: 48px;
  height: 48px;
  border-radius: 999px;
  border: 1px solid var(--color-border);
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  font-size: 1.2rem;
  font-weight: 700;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.help-panel {
  position: absolute;
  right: 0;
  bottom: 60px;
  width: 300px;
  max-width: calc(100vw - 40px);
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  padding: 16px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
}
.help-panel[hidden] { display: none; }
.help-panel-body {
  max-height: 260px;
  overflow-y: auto;
  margin-bottom: 10px;
  font-size: 0.88rem;
  line-height: 1.5;
}
.help-panel-body p { margin: 0 0 8px; }
.help-form { display: flex; gap: 6px; }
.help-form input[type="text"] {
  flex: 1 1 auto;
  padding: 7px 9px;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: transparent;
  color: var(--color-text);
  font-size: 0.85rem;
}
.help-noscript-link {
  font-size: 0.8rem;
  color: var(--color-text-muted);
}

@media (max-width: 640px) {
  .help-widget { right: 16px; bottom: 16px; }
  .help-panel { right: -4px; }
}

/* -- 3D/animated accents (authenticated pages) ---------------------------
   See docs/adr/0012-progressive-enhancement-ui.md decision 3: real WebGL
   (three.js) is reserved for the public pricing page alone
   (billing/templates/billing/pricing.html); every authenticated page gets
   these lighter CSS-only accents instead — real 3D via CSS's own
   transform coordinate space (perspective + rotateX/rotateY), no canvas,
   no JS, nothing to load on a page where focus/performance matters. */
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: transform 0.18s ease, box-shadow 0.18s ease;
    transform-style: preserve-3d;
  }
  .card:hover {
    transform: perspective(800px) rotateX(1.5deg) rotateY(-1.5deg) translateY(-1px);
    box-shadow: var(--shadow-hover);
  }
}

.badge-running {
  animation: badge-pulse 1.4s ease-in-out infinite;
}
@keyframes badge-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.1); opacity: 0.7; }
}
@media (prefers-reduced-motion: reduce) {
  .badge-running { animation: none; }
}

.badge-passed { display: inline-flex; align-items: center; gap: 4px; }
.badge-check {
  flex: 0 0 auto;
  color: currentColor;
}
.badge-check path {
  stroke-dasharray: 20;
  stroke-dashoffset: 20;
  animation: badge-check-draw 0.4s ease-out 0.1s forwards;
}
@media (prefers-reduced-motion: reduce) {
  .badge-check path { stroke-dashoffset: 0; animation: none; }
}
@keyframes badge-check-draw {
  to { stroke-dashoffset: 0; }
}

/* -- Phase 9.2: app shell (sidebar + topbar) -----------------------------
   Ported from the MVP's own static/css/base.css #sidebar/#main-col/#topbar
   rules (see docs/adr/0013-tailwind-and-the-mvp-design-system.md) — same
   selectors, same breakpoints (900px icon-rail, 720px off-canvas), written
   against the new --color- and --radius- and --shadow- prefixed tokens
   input.css already defines rather than the old short aliases, since this
   is new code, not a migrated old rule. Used by
   core/templates/core/app_base.html, the new shell for authenticated
   org-context pages — core/base.html (the plain centered-column shell)
   still serves the public/auth-flow pages until sub-phase 9.3 migrates
   them. */
.skip-link {
  position: fixed;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 200;
  background: var(--color-bg-raised);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 0.5rem 0.9rem;
  font-size: var(--text-sm);
  font-weight: 600;
  box-shadow: var(--shadow-popover);
  transform: translateY(-150%);
  transition: transform var(--duration-fast) var(--ease-kiwami);
}
.skip-link:focus-visible {
  transform: translateY(0);
  text-decoration: none;
}

body:has(#sidebar) {
  margin: 0;
  display: flex;
  min-height: 100vh;
}

#sidebar {
  width: 232px;
  flex-shrink: 0;
  background: var(--color-bg-sidebar);
  border-right: 1px solid var(--color-border-hairline);
  display: flex;
  flex-direction: column;
  padding: 0.9rem 0.7rem;
  position: sticky;
  top: 0;
  height: 100vh;
  z-index: 20;
}
#sidebar .brand {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.3rem 0.5rem 1rem;
  color: var(--color-text);
  text-decoration: none;
}
#sidebar .brand:hover { text-decoration: none; }
#sidebar .brand-mark { flex-shrink: 0; }
#sidebar .brand-name {
  font-weight: 680;
  font-size: var(--text-md);
  letter-spacing: -0.01em;
}
#sidebar nav {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
#sidebar nav a {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.45rem 0.6rem;
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text);
  text-decoration: none;
}
#sidebar nav a svg { flex-shrink: 0; opacity: 0.75; }
#sidebar nav a:hover { background: var(--color-bg-hover); text-decoration: none; }
#sidebar nav a.active {
  background: var(--color-bg-active);
  color: var(--color-accent);
  font-weight: 600;
}
#sidebar nav a.active svg { opacity: 1; }
#sidebar .footer {
  margin-top: auto;
  padding-top: 1rem;
  border-top: 1px solid var(--color-border-hairline);
  font-size: 0.78rem;
  color: var(--color-text-muted);
}
#sidebar .footer .who-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 -0.4rem 0.4rem;
  padding: 0.35rem 0.4rem;
  min-width: 0;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
  transition: background 0.1s ease;
}
#sidebar .footer .who-row:hover { background: var(--color-bg-active); }
#sidebar .footer .who {
  color: var(--color-text);
  line-height: 1.3;
  min-width: 0;
  flex: 1;
  overflow: hidden;
}
#sidebar .footer .who .name,
#sidebar .footer .who .email {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#sidebar .footer .who .email { color: var(--color-text-muted); font-size: var(--text-xs); }
#sidebar .footer-link { display: block; color: var(--color-text-muted); margin: 0 0 0.6rem; }
#sidebar .theme-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  border-radius: var(--radius-md);
  padding: 0.4rem 0.6rem;
  font-family: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  margin-bottom: 0.7rem;
}
#sidebar .theme-toggle:hover { border-color: var(--color-text-dim); }
/* Shows the icon of the mode a click switches *to* — moon while light, sun
   while dark. Covers all three states: an explicit [data-theme], and the
   un-stamped "follow the OS" default via prefers-color-scheme. Not scoped
   to #sidebar: core/templates/core/public_base.html's topbar toggle uses
   the same .icon-sun/.icon-moon markup (see "Public shell" below), and
   app.js's own click handler never touches these — it only ever flips
   [data-theme], this is the only place that decides which icon shows. */
.theme-toggle .icon-sun { display: none; }
.theme-toggle .icon-moon { display: inline-flex; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: inline-flex; }
  :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
}
:root[data-theme="dark"] .theme-toggle .icon-sun { display: inline-flex; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
:root[data-theme="light"] .theme-toggle .icon-sun { display: none; }
:root[data-theme="light"] .theme-toggle .icon-moon { display: inline-flex; }

#main-col { flex: 1; min-width: 0; display: flex; flex-direction: column; }
#topbar {
  position: sticky;
  top: 0;
  z-index: 15;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  height: 52px;
  padding: 0 1.5rem;
  background: color-mix(in srgb, var(--color-bg) 90%, transparent);
  backdrop-filter: saturate(1.4) blur(8px);
  border-bottom: 1px solid var(--color-border-hairline);
}
#topbar-context {
  flex: 1;
  min-width: 0;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#topbar #sidebar-toggle {
  display: none;
  background: transparent;
  border: none;
  color: var(--color-text);
  cursor: pointer;
  padding: 0.3rem;
  border-radius: var(--radius-md);
}
#topbar #sidebar-toggle:hover { background: var(--color-bg-hover); }
.topbar-links { display: flex; align-items: center; gap: 0.4rem; }
.topbar-links > a {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--text-sm);
  font-weight: 550;
  color: var(--color-text-muted);
  padding: 0.35rem 0.6rem;
  border-radius: var(--radius-md);
  text-decoration: none;
}
.topbar-links > a:hover { background: var(--color-bg-hover); color: var(--color-text); text-decoration: none; }

/* max-width/margin explicitly reset: _legacy_styles.html's shared bare
   `main { max-width: 760px; margin: 0 auto; }` rule (for the old
   centered-column shell) would otherwise also constrain this #main,
   since an ID selector only wins on the properties it actually sets. */
#main { flex: 1; min-width: 0; max-width: none; margin: 0; padding: 2.25rem 3rem 4rem; }

/* Colored-initials avatar (core/templatetags/avatars.py). */
.avatar {
  --avatar-size: 28px;
  --avatar-bg: var(--color-bg-active);
  width: var(--avatar-size);
  height: var(--avatar-size);
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--avatar-bg);
  color: var(--color-on-solid);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: calc(var(--avatar-size) * 0.4);
  font-weight: 600;
  line-height: 1;
  text-transform: uppercase;
  user-select: none;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}
.avatar-system {
  background: var(--color-bg-active);
  color: var(--color-text-muted);
  font-size: calc(var(--avatar-size) * 0.3);
  font-weight: 550;
  text-shadow: none;
}

/* Sidebar collapses to an icon-only rail once the viewport can't spare the
   label column — labels/footer text hide via CSS only. */
@media (max-width: 900px) {
  #sidebar { width: 60px; padding: 0.9rem 0.45rem; }
  #sidebar .brand-name,
  #sidebar nav a span,
  #sidebar .footer .who,
  #sidebar .footer-link,
  #sidebar .theme-toggle span:not(.icon-sun):not(.icon-moon) {
    display: none;
  }
  #sidebar nav a { justify-content: center; }
  #sidebar .theme-toggle { justify-content: center; }
  #sidebar .footer .who-row { justify-content: center; }
  #main { padding: 2rem 1.5rem 3rem; }
  #topbar { padding: 0 1rem; }
}

/* Below 720px the rail slides fully off-canvas; #sidebar-toggle in the top
   bar (core/static/core/js/shell.js) brings it back. */
@media (max-width: 720px) {
  #topbar #sidebar-toggle { display: inline-flex; }
  #sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    width: 232px;
    padding: 0.9rem 0.7rem;
    transform: translateX(-105%);
    transition: transform var(--duration-med) var(--ease-kiwami);
    box-shadow: var(--shadow-modal);
  }
  body.nav-open #sidebar { transform: translateX(0); }
  #sidebar .brand-name,
  #sidebar nav a span,
  #sidebar .footer .who,
  #sidebar .footer-link { display: revert; }
  #sidebar nav a { justify-content: flex-start; }
}
@media (prefers-reduced-motion: reduce) {
  .skip-link, #sidebar { transition: none; }
}

/* ── Phase 9.3: base element defaults ──────────────────────────────────
   Ported from the MVP's own static/css/base.css (same rules, same
   --color-*/--text-* tokens) — bare-element styling every page gets
   regardless of which shell it uses, replacing core/_legacy_styles.html's
   cruder reset now that every template has migrated off it (see
   docs/adr/0013-tailwind-and-the-mvp-design-system.md). */
a {
  color: var(--color-accent);
  text-decoration: none;
}
a:hover { text-decoration: underline; }
/* A link sitting inside running text needs a non-colour cue (WCAG 1.4.1)
   — underline it. Links used as nav/buttons/cards aren't in text blocks
   and keep the clean look. */
p a, li a:not(.btn), td a:not(.btn) {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
h1, h2, h3, h4 {
  font-weight: 600;
  line-height: var(--leading-tight);
  letter-spacing: -0.011em;
}
h1 { font-size: var(--text-xl); margin: 0 0 0.35rem; }
h2 { font-size: var(--text-lg); margin: 1.75rem 0 0.6rem; }
h2:first-child { margin-top: 0; }
h3 { font-size: var(--text-md); margin: 1.5rem 0 0.5rem; }
h4 { font-size: var(--text-sm); margin: 0 0 0.35rem; }
table { width: 100%; border-collapse: collapse; margin-top: 0.5rem; }
th, td {
  text-align: left;
  padding: 0.5rem 0.6rem;
  border-bottom: 1px solid var(--color-border-hairline);
  font-size: var(--text-sm);
}
th {
  font-weight: 600;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
code {
  font-family: var(--font-mono);
  font-size: 0.85em;
  background: var(--color-bg-sunken);
  padding: 0.05em 0.35em;
  border-radius: var(--radius-sm);
}
pre code { background: none; padding: 0; }
:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ── Buttons ────────────────────────────────────────────────────────────
   .btn alone is the neutral/secondary look (a raised, bordered surface);
   .btn-primary is the filled, accent CTA; .btn-danger the filled, red
   destructive action; .btn-secondary an explicit fully-transparent
   variant. Same four-way split as the MVP's own components.css. */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  border: 1px solid var(--color-border);
  background: var(--color-bg-raised);
  color: var(--color-text);
  border-radius: var(--radius-md);
  padding: 0.45rem 0.9rem;
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  font-family: inherit;
  transition: border-color var(--duration-fast) var(--ease-kiwami),
    opacity var(--duration-fast) var(--ease-kiwami);
}
.btn:hover { border-color: var(--color-text-dim); text-decoration: none; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-secondary { background: transparent; }
.btn-primary {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  border-color: var(--color-accent);
}
.btn-primary:disabled {
  background: var(--color-border);
  border-color: var(--color-border);
  color: var(--color-text-dim);
}
.btn-danger {
  background: var(--color-red);
  color: var(--color-red-contrast);
  border-color: var(--color-red);
}
.btn-sm { padding: 0.3rem 0.65rem; font-size: 0.78rem; }

/* ── Cards ──────────────────────────────────────────────────────────── */
.card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-raised);
  padding: 1.25rem 1.4rem;
  box-shadow: var(--shadow-card);
  margin-bottom: 1rem;
}
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 0.9rem;
}

/* ── Page header: title block + a primary action, top of most pages ──── */
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 1.25rem;
}
.page-header h1 { margin-bottom: 0; }
.page-sub { color: var(--color-text-muted); margin: 0.15rem 0 0; font-size: var(--text-sm); }

/* ── Form fields — one shared control style, ported from the MVP's own
   audit CO-1 fix. `.field` wraps label + control + hint/error; the bare
   input/select/textarea selector (rather than a dedicated `.input` class)
   keeps every already-migrated template's markup unchanged. ──────────── */
.field { margin-bottom: 0.9rem; }
.field > label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 550;
  color: var(--color-text);
  margin-bottom: 0.3rem;
}
.field input[type="text"],
.field select,
.field textarea {
  width: 100%;
  box-sizing: border-box;
  min-height: 38px;
  padding: 0.45rem 0.6rem;
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text);
  font-family: inherit;
  font-size: var(--text-base);
}
.field input[type="text"]:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-accent) 20%, transparent);
}
.field textarea { resize: vertical; line-height: 1.5; }
.hint { color: var(--color-text-muted); font-size: var(--text-xs); margin: 0.3rem 0 0; line-height: 1.45; }
.error { color: var(--color-red); font-size: 0.85rem; margin-bottom: 0.75rem; }

/* ── Simple link lists (teams, tokens, org picker) ────────────────────── */
ul.plain { list-style: none; margin: 0; padding: 0; }
ul.plain li {
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--color-border-hairline);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
ul.plain li:last-child { border-bottom: none; }
ul.plain a { color: var(--color-text); text-decoration: none; font-weight: 600; }
ul.plain a:hover { text-decoration: underline; }

/* ── Status pills (core/_status_badge.html, core/templatetags/
   status_badges.py) — every Run/TestResult/BuildImage status gets a
   color, not just "running" (pulse) and "passed" (checkmark draw, both
   defined above in the Phase 8.6 section this extends). ──────────────── */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 0.08rem 0.45rem;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--color-text-muted);
  white-space: nowrap;
}
.badge-passed { background: var(--color-green-bg); color: var(--color-green); border-color: transparent; }
.badge-failed { background: var(--color-red-bg); color: var(--color-red); border-color: transparent; }
.badge-running { background: var(--color-blue-bg); color: var(--color-blue); border-color: transparent; }
.badge-pending { background: var(--color-gray-bg); color: var(--color-gray); border-color: transparent; }

/* ── Tables (a light wrapper over the bare table rules above) ─────────── */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}
.table-wrap table { margin-top: 0; }
.table-wrap th:first-child, .table-wrap td:first-child { padding-left: 1rem; }
.table-wrap th:last-child, .table-wrap td:last-child { padding-right: 1rem; }
.table-wrap tr:last-child td { border-bottom: none; }
.table-wrap tbody tr[data-href] { cursor: pointer; }
.table-wrap tbody tr[data-href]:hover { background: var(--color-bg-hover); }

/* ── Empty states ──────────────────────────────────────────────────────── */
.empty-state {
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-lg);
  padding: 2.2rem 1.5rem;
  text-align: center;
  color: var(--color-text-muted);
}
.empty-state h3 { margin: 0 0 0.3rem; color: var(--color-text); font-size: var(--text-md); }
.empty-state p { margin: 0; font-size: var(--text-sm); }
.empty-state .btn { margin-top: 1rem; }

/* ── Stat tiles (billing usage summary) ────────────────────────────────── */
.stat-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.9rem;
  margin: 1rem 0 1.6rem;
}
.stat-tile {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-bg-raised);
  padding: 0.85rem 1rem;
}
.stat-tile .num { font-size: 1.5rem; font-weight: 700; line-height: 1.2; font-variant-numeric: tabular-nums; }
.stat-tile .lbl { color: var(--color-text-muted); font-size: 0.78rem; margin-top: 0.15rem; }

/* ── Public shell (core/templates/core/public_base.html) ───────────────
   A simple centered-column shell for pre-auth/public pages — brand +
   sign-in-or-dashboard link, no sidebar: an anonymous visitor has no
   organization context, and core/templatetags/avatars.py's avatar() tag
   assumes a real user, so this shell never tries to render one. Replaces
   core/base.html's own topbar/footer (removed this sub-phase — see
   docs/adr/0013-tailwind-and-the-mvp-design-system.md). */
.site-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.9rem 1.5rem;
  border-bottom: 1px solid var(--color-border-hairline);
}
.site-topbar .brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 680;
  font-size: var(--text-md);
  color: var(--color-text);
  text-decoration: none;
}
.site-topbar .brand:hover { text-decoration: none; }
.site-topbar nav { display: flex; align-items: center; gap: 1.1rem; font-size: var(--text-sm); }
.site-topbar nav a { color: var(--color-text-muted); text-decoration: none; }
.site-topbar nav a:hover { color: var(--color-text); }
.site-topbar .theme-toggle {
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
  padding: 0.3rem 0.5rem;
  cursor: pointer;
}
.site-topbar .theme-toggle:hover { color: var(--color-text); border-color: var(--color-text-dim); }

.site-main { max-width: 760px; margin: 0 auto; padding: 2.25rem 1.25rem 4rem; }

.site-footer {
  max-width: 760px;
  margin: 0 auto;
  padding: 1.5rem 1.25rem 2.5rem;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.site-footer a { color: var(--color-text-muted); }
.site-footer a:hover { color: var(--color-text); }

@media (max-width: 640px) {
  .site-topbar { padding: 0.8rem 1rem; }
  .site-main { padding: 1.5rem 1rem 3rem; }
  .site-footer { padding: 1.25rem 1rem 2rem; }
}

/* ── Phase 9.4: toasts ──────────────────────────────────────────────────
   Server-rendered from Django's messages framework (both shells'
   `{% if messages %}` block) — present and readable with no JS at all;
   app.js only adds the auto-dismiss/slide-out behavior on top. Ported
   from the MVP's own #toast-stack/.toast. */
#toast-stack {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  z-index: 80;
  max-width: calc(100vw - 2.5rem);
}
.toast {
  min-width: 220px;
  max-width: 340px;
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  border-left: 3px solid var(--color-accent);
  border-radius: var(--radius-md);
  padding: 0.6rem 0.85rem;
  font-size: 0.82rem;
  box-shadow: var(--shadow-overlay);
  animation: toast-in var(--duration-med) var(--ease-kiwami);
}
.toast-success { border-left-color: var(--color-green); }
.toast-error { border-left-color: var(--color-red); }
.toast-warning { border-left-color: var(--color-amber); }
.toast.toast-out { animation: toast-out var(--duration-med) var(--ease-kiwami) forwards; }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}
@keyframes toast-out {
  to { opacity: 0; transform: translateY(6px); }
}
@media (prefers-reduced-motion: reduce) {
  .toast, .toast.toast-out { animation: none; }
}

/* ── Modal chrome — shared overlay/dialog shell (api/tokens.html's "New
   token" modal today) ───────────────────────────────────────────────── */
.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: var(--color-overlay);
  align-items: center;
  justify-content: center;
  z-index: 60;
  padding: 1rem;
}
.modal-overlay.open { display: flex; }
.modal {
  width: 440px;
  max-width: 100%;
  max-height: 88vh;
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-modal);
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--color-border-hairline);
}
.modal-header h2 { margin: 0; }
.modal-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: var(--radius-md);
  background: transparent;
  cursor: pointer;
  color: var(--color-text-muted);
  font-size: 1.35rem;
  line-height: 1;
}
.modal-close:hover { background: var(--color-bg-hover); color: var(--color-text); }
.modal-body { padding: 1.25rem; overflow-y: auto; }
.modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.6rem;
  padding: 1rem 1.25rem;
  border-top: 1px solid var(--color-border-hairline);
}

/* ── Command palette (Cmd/Ctrl+K) ──────────────────────────────────────
   Nav destinations + the active organization's projects
   (core/templates/core/app_base.html's json_script data island),
   fuzzy-matched client-side — core/static/core/js/command-palette.js. */
#command-palette {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  position: fixed;
  inset: 0;
  background: var(--color-overlay);
  padding-top: 12vh;
  z-index: 90;
}
#command-palette[hidden] { display: none; }
.cp-box {
  width: 480px;
  max-width: calc(100vw - 2rem);
  max-height: 70vh;
  background: var(--color-bg-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-modal);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.cp-input-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.9rem 1rem;
  border-bottom: 1px solid var(--color-border-hairline);
  color: var(--color-text-dim);
}
.cp-input-row input {
  flex: 1;
  border: 0;
  background: transparent;
  color: var(--color-text);
  font-size: var(--text-md);
  font-family: inherit;
}
.cp-input-row input:focus { outline: none; }
.cp-results {
  list-style: none;
  margin: 0;
  padding: 0.4rem;
  overflow-y: auto;
}
.cp-results li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 0.7rem;
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  color: var(--color-text);
  cursor: pointer;
}
.cp-results li .cp-group {
  margin-left: auto;
  color: var(--color-text-dim);
  font-size: var(--text-xs);
}
.cp-results li.active,
.cp-results li:hover { background: var(--color-bg-active); }
.cp-empty { padding: 1.25rem; text-align: center; color: var(--color-text-muted); font-size: var(--text-sm); }
.cp-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: var(--text-sm);
  font-weight: 550;
  color: var(--color-text-muted);
  padding: 0.35rem 0.6rem;
  border-radius: var(--radius-md);
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  font-family: inherit;
}
.cp-trigger:hover { background: var(--color-bg-hover); color: var(--color-text); }
.cp-trigger kbd {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 0.05rem 0.3rem;
  color: var(--color-text-dim);
}

/* ── Phase 9.5: usage meter (billing/templates/billing/billing.html) ────
   An honest current-period snapshot, not a fabricated time series —
   billing.usage.compute_usage() only ever returns one period's totals,
   never history, so this is the one chart form that data actually
   supports: a single meter, capped at 100%, that changes color once
   usage reaches the plan's included allowance. The exact numbers
   (including the real, uncapped overage) are always given as text next
   to it, never implied by the bar alone. */
.meter-block { margin: 0.9rem 0 1.1rem; }
.meter-block-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: var(--text-sm);
  font-weight: 550;
  margin-bottom: 0.4rem;
}
.meter-track {
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--color-bg-sunken);
  overflow: hidden;
}
.meter-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  transition: width var(--duration-med) var(--ease-kiwami);
}
.meter-fill.meter-fill-over { background: var(--color-red); }
@media (prefers-reduced-motion: reduce) {
  .meter-fill { transition: none; }
}
.meter-caption {
  display: flex;
  justify-content: space-between;
  margin-top: 0.4rem;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.meter-over-label { color: var(--color-red); font-weight: 600; }
