/* ===== Design system: CSS custom properties =====
   Source of truth: docs/08-design-system.md (sections 2-7).
   Components must use these tokens, never raw hex.
   Legacy names at the end of each block are aliases kept alive during the
   migration; they are re-declared in every theme because a custom property is
   substituted on the element that declares it, so an alias declared only on
   :root would freeze the dark value for the whole light theme. */
:root {
  /* Native widgets (scrollbars, date pickers) follow the theme too. */
  color-scheme: dark;

  /* Surfaces */
  --bg: #0E1220;
  --surface: #151A2B;
  --surface-raised: #1B2136;
  --surface-sunken: #0B0F1B;

  /* Borders */
  --border: #262E45;
  --border-strong: #333D59;

  /* Text */
  --text: #E7ECF5;
  --text-muted: #98A3BC;
  --text-subtle: #6E7891;

  /* Accent (derived from the logo, #1AB6E2 -> #06A3DB) */
  --accent: #29ADE0;
  --accent-hover: #4CC0EC;
  --accent-soft: rgba(41, 173, 224, 0.14);
  --accent-contrast: #04121B;

  /* State */
  --ok: #3DD68C;
  --ok-soft: rgba(61, 214, 140, 0.13);
  --warn: #F5B544;
  --warn-soft: rgba(245, 181, 68, 0.13);
  --err: #F2617A;
  --err-soft: rgba(242, 97, 122, 0.13);

  /* Elevation */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.40);
  --shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.55);

  /* Radius */
  --radius-sm: 6px;
  --radius: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;
  --radius-full: 999px;

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

  /* Motion */
  --transition: 120ms ease;

  /* Legacy aliases — remove once the last usage is migrated */
  --cyan: var(--accent);
  --magenta: var(--accent);
  --violet: var(--accent);
  --panel: var(--surface);
  --panel-solid: var(--surface);
  --bg-elev: var(--surface-raised);
  --popup: var(--surface-raised);
  --input-bg: var(--surface-raised);
  --muted: var(--text-muted);
  --color-positive: var(--ok);
  --color-warning: var(--warn);
}

/* The theme flag lives on <body>, but the canvas is painted from <html>. If the
   light tokens were declared on the body alone, anything resolving a token above
   it (the canvas, the scrollbars) would keep the dark value and paint black bands
   around a scrolling page. Declaring the same block on the root element while the
   body carries the flag keeps one source of values for both. */
:root:has(body[data-theme="light"]),
body[data-theme="light"] {
  color-scheme: light;

  /* Surfaces */
  --bg: #F5F7FA;
  --surface: #FFFFFF;
  --surface-raised: #FFFFFF;
  --surface-sunken: #F0F3F8;

  /* Borders */
  --border: #E3E8EF;
  --border-strong: #CBD3E1;

  /* Text */
  --text: #101828;
  --text-muted: #5A6479;
  --text-subtle: #8A94A8;

  /* Accent */
  --accent: #0F92D0;
  --accent-hover: #0C7CB1;
  --accent-soft: rgba(15, 146, 208, 0.10);
  --accent-contrast: #FFFFFF;

  /* State */
  --ok: #12805C;
  --ok-soft: #E7F7F0;
  --warn: #B54708;
  --warn-soft: #FEF3E2;
  --err: #C0304A;
  --err-soft: #FDECEF;

  /* Elevation */
  --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06);
  --shadow: 0 2px 8px rgba(16, 24, 40, 0.08);
  --shadow-lg: 0 12px 32px rgba(16, 24, 40, 0.14);

  /* Legacy aliases — re-declared so they pick up the light values above */
  --cyan: var(--accent);
  --magenta: var(--accent);
  --violet: var(--accent);
  --panel: var(--surface);
  --panel-solid: var(--surface);
  --bg-elev: var(--surface-raised);
  --popup: var(--surface-raised);
  --input-bg: var(--surface-raised);
  --muted: var(--text-muted);
  --color-positive: var(--ok);
  --color-warning: var(--warn);
}

/* ===== Base ===== */
* { box-sizing: border-box; }

html, body {
  margin: 0;
  min-height: 100%;
  font-family: "Plus Jakarta Sans", Inter, "Segoe UI", sans-serif;
  font-size: 16px;
  color: var(--text);
}
/* Only the body is painted. An unpainted <html> hands its child's background to
   the canvas, so the whole viewport follows the themed body instead of resolving
   --bg a level above the element that declares the theme. */
body {
  background: var(--bg);
}

/* ===== Panel surface =====
   docs/08 §1: depth is opaque surfaces plus a soft shadow, never a blur.
   The class keeps the name `.glass` because it is applied in dozens of .razor
   files; there is nothing translucent left behind it. */
.glass {
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  background: var(--surface);
}
/* Dark mode separates the panel from the page by surface step alone. Light mode
   is white on grey, which is too weak a step, so it keeps a hairline (docs/08 §9). */
body[data-theme="light"] .glass {
  border: 1px solid var(--border);
}

/* ===== Layout ===== */
.layout {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 232px 1fr;
  gap: 14px;
  min-height: 100vh;
  padding: 14px;
  transition: grid-template-columns 0.2s ease;
}
.layout.sidebar-collapsed {
  grid-template-columns: 62px 1fr;
}

/* ===== Sidebar ===== */
.sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-3);
  position: sticky;
  top: 14px;
  height: calc(100vh - 28px);
  overflow: visible;
  z-index: 100;
}
.brand {
  display: flex;
  align-items: center;
  gap: 12px;
}
.brand-dot {
  width: 12px;
  height: 12px;
  border-radius: 999px;
  background: linear-gradient(130deg, var(--cyan), var(--magenta));
  box-shadow: 0 0 14px var(--cyan);
  flex-shrink: 0;
}
.brand-mark {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  object-fit: contain;
}
.brand h1 { margin: 0; font-size: 1rem; letter-spacing: 0.04em; outline: none; }
/* The brand block is a link wrapping a heading; a click must not ring it, but a
   keyboard visit still has to be visible (docs/08 §8). */
.brand:focus, .brand *:focus { outline: none; }
.brand:focus-visible, .brand *:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.brand p  { margin: 2px 0 0; font-size: 0.78rem; color: var(--muted); }

/* Brand header in website mode */
.brand-website { gap: 10px; }
.brand-website .brand-text { min-width: 0; flex: 1; }
.brand-name-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.brand-website-name {
  margin: 0;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  outline: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.brand-external-link {
  flex-shrink: 0;
  margin-left: auto;
  color: var(--muted);
  font-size: 0.8em;
  opacity: 0.7;
  transition: opacity var(--transition), color var(--transition);
  text-decoration: none;
}
.brand-external-link:hover {
  opacity: 1;
  color: var(--cyan);
}
.brand-external-link i.icon { margin: 0; }

/* Platform link in nav bottom */
.nav-platform-link {
  color: var(--muted) !important;
  font-size: 0.86rem;
  margin-bottom: 4px;
}
.nav-platform-link:hover {
  color: var(--text) !important;
}

/* ===== Sidebar collapse toggle =====
   It used to sit in the brand header, 22px from the website switcher's own ⌄
   caret: two chevrons side by side, one opening a menu and one folding a panel,
   and neither legible. It is now the last item at the foot of the rail — the
   width of the rail is a shell setting, not navigation — where it is the only
   chevron in view and 400px from the switcher. It borrows `.menu-item` wholesale
   so the collapsed rail treats it like every other item (centred icon, hidden
   label, title tooltip) with no extra rules. */
/* Collapse handle — on the rail's edge, not in its menu (docs/08 §9). It straddles
   the border at the vertical middle and is absolutely positioned, so it costs no
   layout space and sits nowhere near the switcher's caret. Revealed by hovering
   anywhere on the sidebar, which is where the pointer already is, and by keyboard
   focus so it is never unreachable. */
.sidebar-edge-toggle {
  position: absolute;
  top: 50%;
  right: -11px;
  transform: translateY(-50%);
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--text-subtle);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--transition), color var(--transition),
              border-color var(--transition), background var(--transition);
  z-index: 2;
}
.sidebar-edge-toggle i.icon {
  margin: 0;
  font-size: 0.7rem;
  line-height: 1;
}
.sidebar:hover .sidebar-edge-toggle,
.sidebar-edge-toggle:hover,
.sidebar-edge-toggle:focus-visible { opacity: 1; }
.sidebar-edge-toggle:hover {
  color: var(--text);
  border-color: var(--border-strong);
  background: var(--surface-raised);
}
.sidebar-edge-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.nav-scrollable {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0 -6px;
  padding: 0 6px;
}
.nav-scrollable::-webkit-scrollbar { width: 4px; }
.nav-scrollable::-webkit-scrollbar-track { background: transparent; }
.nav-scrollable::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }

/* The rail is a flex column with a --space-3 gap, so these margins are what the line
   adds on top of it: 4 + 12 = 16px of air on each side. Anything that puts extra
   margin on only one neighbour pushes the line off centre in its own gap. */
.nav-divider {
  height: 1px;
  background: var(--border);
  margin: var(--space-1) 0;
  flex-shrink: 0;
}

.menu { display: grid; gap: 2px; }
.menu-group-title {
  margin: var(--space-5) 0 var(--space-2) 10px;
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-subtle);
}
.menu-group-title:first-of-type { margin-top: var(--space-2); }
.sidebar-collapsed .menu-group-title { display: none; }
.menu-title {
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 4px 0 4px;
  padding: 0 4px;
}
.menu-item > i.icon,
.nav-bottom-trigger > i.icon:not(.flyout-chevron),
.flyout-logout-btn > i.icon {
  width: 1.4em;
  text-align: center;
  font-size: 0.95em;
  opacity: 0.7;
  margin: 0 2px 0 0;
}
/* docs/08 §9: 34px tall, no borders — hover is a surface step and the active
   item is an accent tint carrying a full-strength label and an accent icon. */
.menu-item {
  text-decoration: none;
  color: var(--text-muted);
  font-size: 0.88rem;
  border: none;
  border-radius: var(--radius);
  min-height: 34px;
  padding: 0 10px;
  display: flex;
  align-items: center;
  gap: 4px;
  font-weight: 500;
  transition: background var(--transition), color var(--transition);
}
.menu-item:hover {
  background: var(--surface-raised);
  color: var(--text);
  text-decoration: none;
}
/* --surface-raised is white in the light theme, i.e. invisible on the white
   sidebar, so a hovered item steps down to the sunken surface instead. */
body[data-theme="light"] .menu-item:hover {
  background: var(--surface-sunken);
}
.menu-item.active,
body[data-theme="light"] .menu-item.active {
  background: var(--accent-soft);
  color: var(--text);
  text-decoration: none;
}
.menu-item.active > i.icon {
  color: var(--accent);
  opacity: 1;
}
.menu-item:hover > i.icon {
  opacity: 1;
}
.menu-item.disabled-item {
  color: var(--muted);
  opacity: 0.45;
  cursor: default;
  pointer-events: none;
}
/* Draft status banner in sidebar */
.draft-status-banner {
  margin-top: 4px;
  padding: 8px 10px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 0.82rem;
  margin-bottom: 0;
}
/* The label yields before the Publish button does. Today's longest state that
   renders *with* the button ("Draft changes", "Not published") fits the 232px rail
   exactly, so a longer one added later would push the button out of the chip
   rather than truncate. min-width:0 lets the flex item shrink; the ellipsis makes
   the shrink readable, and the full text is already in the title attribute. */
.draft-status-indicator {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}
.draft-status-indicator:hover { text-decoration: underline; }
.draft-status-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
}
.draft-status-text {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The publishing state is the one place green is allowed (docs/08 §9); the
   Publish button beside it stays the accent. */
.draft-status-published {
  background: var(--ok-soft);
}
.draft-status-published .draft-status-dot {
  background: var(--ok);
}
.draft-status-published .draft-status-text {
  color: var(--ok);
}
.draft-status-draft {
  background: var(--warn-soft);
}
.draft-status-draft .draft-status-dot {
  background: var(--warn);
}
.draft-status-draft .draft-status-text {
  color: var(--warn);
}
.draft-status-never {
  background: var(--surface-sunken);
}
.draft-status-never .draft-status-dot {
  background: var(--text-subtle);
}
.draft-status-never .draft-status-text {
  color: var(--text-muted);
}
.draft-publish-btn.ui.button {
  flex-shrink: 0;
}
/* ===== Sidebar collapsed state ===== */
.sidebar-collapsed .sidebar {
  padding: 12px 8px;
}
.sidebar-collapsed .nav-label,
.sidebar-collapsed .brand-text,
.sidebar-collapsed .brand-subtitle,
.sidebar-collapsed .flyout-chevron,
.sidebar-collapsed .brand-external-link,
.sidebar-collapsed .brand-name-row {
  display: none;
}
.sidebar-collapsed .brand {
  justify-content: center;
  padding-top: 8px;
  flex-wrap: wrap;
}
.sidebar-collapsed .brand-dot,
.sidebar-collapsed .brand-mark {
  width: 18px;
  height: 18px;
  margin-bottom: 4px;
}
.sidebar-collapsed .menu-item {
  justify-content: center;
  padding: 0 6px;
}
.sidebar-collapsed .menu-item > i.icon,
.sidebar-collapsed .nav-bottom-trigger > i.icon:not(.flyout-chevron) {
  margin: 0;
  font-size: 1.1em;
}
.sidebar-collapsed .nav-bottom-trigger {
  justify-content: center;
}
.sidebar-collapsed .nav-platform-link {
  justify-content: center;
}
.sidebar-collapsed .draft-status-banner {
  background: transparent;
  border: none;
  justify-content: center;
  padding: 7px 0;
  gap: 0;
}
/* The collapsed rail is 62px and every other item in it is icon-only, so the chip
   shows the *state* and not the action: just the dot, still linking to Revisions &
   publish, which is where Publish lives anyway. The `.ui.button` part is load
   bearing — Fomantic's own `.ui.mini.primary.button` outranked the bare
   `.draft-publish-btn`, so the button stayed laid out and hung 8px off the rail. */
.sidebar-collapsed .draft-status-text,
.sidebar-collapsed .draft-publish-btn,
.sidebar-collapsed .draft-publish-btn.ui.button,
.sidebar-collapsed .draft-status-indicator > *:not(.draft-status-dot) {
  display: none;
}
/* Nothing may spill out of the rail even if a future child forgets the rule. */
.sidebar-collapsed .draft-status-banner { overflow: hidden; }
/* Tooltips in collapsed mode — CSS-only via title attribute */
.sidebar-collapsed .menu-item,
.sidebar-collapsed .nav-bottom-trigger,
.sidebar-collapsed .nav-platform-link {
  position: relative;
}
.sidebar-collapsed .menu-item:hover::after,
.sidebar-collapsed .nav-bottom-trigger:hover::after,
.sidebar-collapsed .nav-platform-link:hover::after {
  content: attr(title);
  position: absolute;
  left: calc(100% + 8px);
  top: 50%;
  transform: translateY(-50%);
  background: var(--popup);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 0.82rem;
  white-space: nowrap;
  z-index: 1000;
  pointer-events: none;
  box-shadow: var(--shadow);
}

/* Collapsing is meaningless below 768px — there the sidebar is off-canvas, so the
   control goes away and the collapsed state is ignored. The old rule named
   `.sidebar-collapse-toggle`, a class that exists nowhere, so it never hid
   anything. */
@media (max-width: 768px) {
  .sidebar-edge-toggle { display: none; }
  .sidebar-collapsed .nav-label,
  .sidebar-collapsed .brand-text,
  .sidebar-collapsed .brand-subtitle { display: revert; }
}

/* ===== Deploy status card (Revisions page) =====
   The card sits directly inside a panel, so the surface step carries it and the
   border goes away (docs/08 §9). Light mode keeps a hairline because there both
   surfaces are white. */
.deploy-card {
  border-radius: var(--radius);
  background: var(--surface-raised);
  overflow: hidden;
}
body[data-theme="light"] .deploy-card {
  border: 1px solid var(--border);
}
.deploy-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  gap: 12px;
  flex-wrap: wrap;
}
.deploy-card-status {
  display: flex;
  align-items: center;
  gap: 10px;
}
.deploy-card-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  flex-shrink: 0;
}
.deploy-card-label {
  font-weight: 700;
  font-size: 1rem;
}
.deploy-card-actions {
  display: flex;
  gap: 8px;
}
.deploy-card-changes {
  display: flex;
  gap: 16px;
  padding: 12px 20px;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.deploy-change-item {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 0.85rem;
  color: var(--muted);
}
.deploy-change-item i.icon {
  font-size: 0.9em;
  margin: 0;
  opacity: 0.7;
}
.deploy-card-note {
  padding: 10px 20px;
  border-top: 1px solid var(--border);
  font-size: 0.82rem;
  color: var(--muted);
}
/* States — no glow (docs/08 §1.4): the dot carries the colour, nothing else. */
.deploy-card--live .deploy-card-dot { background: var(--ok); }
.deploy-card--live .deploy-card-label { color: var(--ok); }
.deploy-card--pending .deploy-card-dot { background: var(--warn); }
.deploy-card--pending .deploy-card-label { color: var(--warn); }
.deploy-card--never .deploy-card-dot { background: var(--text-subtle); }
.deploy-card--never .deploy-card-label { color: var(--muted); }
.deploy-card--loading .deploy-card-dot { background: var(--muted); opacity: 0.4; }
.draft-status-toast {
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  font-size: 0.8rem;
  margin-bottom: 4px;
  text-align: center;
}
.draft-status-toast.success {
  background: var(--ok-soft);
  color: var(--ok);
}
.draft-status-toast.error {
  background: var(--err-soft);
  color: var(--err);
}

/* ===== Sidebar bottom triggers & flyout menus ===== */
/* The user block is the last thing in a tall column; it gets its own air above
   the divider instead of being crammed against the final menu item. */
/* No margin-top: it sat under .nav-divider and made the line 16px from what is above
   it and 24px from this, which read as the line belonging to the section above. */
.nav-bottom {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.flyout-wrapper {
  position: relative;
}

.nav-bottom-trigger {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-height: 34px;
  padding: 0 10px;
  border-radius: var(--radius);
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 0.86rem;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
  text-align: left;
}
.nav-bottom-trigger:hover {
  background: var(--surface-raised);
  color: var(--text);
}
body[data-theme="light"] .nav-bottom-trigger:hover {
  background: var(--surface-sunken);
}
.nav-bottom-username {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
i.icon.flyout-chevron {
  margin-left: auto;
  margin-right: 0;
  font-size: 0.65em;
  opacity: 0.5;
  width: auto;
  transition: opacity 0.15s ease;
}
/* Flyout panel — hidden by default, shown on hover */
.flyout-panel {
  position: absolute;
  left: calc(100% + 8px);
  bottom: 0;
  min-width: 220px;
  max-width: 280px;
  padding: 12px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--popup);
  box-shadow: var(--shadow);
  z-index: 10;
  opacity: 0;
  pointer-events: none;
  transform: translateX(-6px);
  transition: opacity 0.15s ease-out, transform 0.15s ease-out;
}
/* Bridge gap between trigger and panel so hover doesn't break */
.flyout-panel::before {
  content: '';
  position: absolute;
  right: 100%;
  top: 0;
  bottom: 0;
  width: 12px;
}
.flyout-wrapper:hover > .flyout-panel {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(0);
}
.flyout-wrapper:hover > .nav-bottom-trigger,
body[data-theme="light"] .flyout-wrapper:hover > .nav-bottom-trigger {
  background: var(--accent-soft);
  color: var(--text);
}
.flyout-wrapper:hover > .nav-bottom-trigger .flyout-chevron {
  opacity: 1;
}
.flyout-panel .menu-title {
  margin: 0 0 6px;
}
.flyout-panel .menu {
  display: grid;
  gap: 2px;
}

/* Theme choice — three ordinary menu rows under their own heading, not a segmented
   control: this menu is a list of rows, so a widget of another kind reads as foreign
   no matter how it is styled. The chosen row is marked the way an active nav item is
   (docs/08 §9) and carries a check, since one tint alone was too quiet to find. */
.menu-theme-group {
  display: grid;
  gap: 2px;
}
/* Which row is lit follows body[data-theme-pref], written by theme.js next to the flag
   that paints the theme — so the mark and the theme cannot tell different stories.
   The chosen row says it three ways: the accent tint an active nav item carries, a
   heavier label, and the check. Every row carries a check; only one is shown. */
.theme-option-row > i.menu-item-check {
  display: none;
}
body[data-theme-pref="light"] .theme-option-row[data-theme-option="light"],
body[data-theme-pref="dark"] .theme-option-row[data-theme-option="dark"],
body[data-theme-pref="auto"] .theme-option-row[data-theme-option="auto"] {
  background: var(--accent-soft);
  color: var(--text);
}
body[data-theme-pref="light"] .theme-option-row[data-theme-option="light"] > i.icon,
body[data-theme-pref="dark"] .theme-option-row[data-theme-option="dark"] > i.icon,
body[data-theme-pref="auto"] .theme-option-row[data-theme-option="auto"] > i.icon {
  color: var(--accent);
  opacity: 1;
}
body[data-theme-pref="light"] .theme-option-row[data-theme-option="light"] > span:first-of-type,
body[data-theme-pref="dark"] .theme-option-row[data-theme-option="dark"] > span:first-of-type,
body[data-theme-pref="auto"] .theme-option-row[data-theme-option="auto"] > span:first-of-type {
  font-weight: 600;
}
body[data-theme-pref="light"] .theme-option-row[data-theme-option="light"] > i.menu-item-check,
body[data-theme-pref="dark"] .theme-option-row[data-theme-option="dark"] > i.menu-item-check,
body[data-theme-pref="auto"] .theme-option-row[data-theme-option="auto"] > i.menu-item-check {
  display: inline-block;
}
/* Same weight as the .flyout-panel .menu-title rule above, so ordering decides:
   the heading of a group inside the menu needs air above it, the panel's own
   first heading does not. */
.flyout-panel .menu-title-section {
  margin-top: var(--space-2);
}
/* Beats .menu-item > i.icon, which would dim it and pull it in beside the label. */
.menu-item > i.menu-item-check {
  margin-left: auto;
  color: var(--accent);
  opacity: 1;
  font-size: 0.8em;
}
/* Sits at the right of its row, ahead of the check when there is one. */
.menu-item-note {
  margin-left: auto;
  font-size: 0.76rem;
  color: var(--text-subtle);
}
.menu-item-note + i.menu-item-check {
  margin-left: 6px;
}
body[data-theme="dark"] .theme-only-light,
body[data-theme="light"] .theme-only-dark {
  display: none;
}

.flyout-logout-form {
  margin: 0;
}
.flyout-logout-btn {
  width: 100%;
  background: transparent;
  font-family: inherit;
  font-size: 0.88rem;
  cursor: pointer;
  color: var(--err);
}
/* The button also carries .menu-item, whose light-theme hover is more specific,
   so the destructive tint has to be stated for that theme as well. */
.flyout-logout-btn:hover,
body[data-theme="light"] .flyout-logout-btn:hover {
  background: var(--err-soft);
  color: var(--err);
}
.flyout-logout-btn > i.icon {
  opacity: 0.9;
}

/* Nav footer buttons (reused in flyout) */
.nav-footer-btn {
  display: block;
  width: 100%;
  min-height: 34px;
  padding: 0 10px;
  border-radius: var(--radius);
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  color: var(--text-muted);
  font-size: 0.8rem;
  font-family: inherit;
  cursor: pointer;
  text-align: center;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}
.nav-footer-btn:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--text);
}
.nav-footer-btn--active {
  border-color: var(--ok);
  color: var(--ok);
  background: var(--ok-soft);
}
.nav-footer-btn--active:hover {
  border-color: var(--ok);
  background: var(--ok-soft);
  color: var(--ok);
}
.nav-footer-btn--logout {
  color: var(--text-muted);
  border-color: transparent;
}
.nav-footer-btn--logout:hover {
  color: var(--err);
  border-color: var(--err);
  background: var(--err-soft);
}

.mobile-sidebar-toggle {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 8;
  display: none !important;
}

.sidebar-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(2, 7, 20, 0.6);
  border: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
  z-index: 5;
}

/* ===== Website switcher in the sidebar brand header (audit A17) =====
   The trigger looks like the site name, because that is what people click. It
   only shows it is a control on hover and focus — a permanent button frame at the
   top of the sidebar would compete with the navigation below it. */
.site-switcher { position: relative; min-width: 0; }
.site-switcher-trigger {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-width: 0;
  padding: 2px 6px;
  margin-left: -6px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: left;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.site-switcher-trigger:hover,
.site-switcher.is-open .site-switcher-trigger {
  background: var(--accent-soft);
}
.site-switcher-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
i.icon.site-switcher-caret {
  margin: 0 0 0 auto;
  flex-shrink: 0;
  font-size: 0.62em;
  opacity: 0.55;
}
.site-switcher-trigger:hover .site-switcher-caret,
.site-switcher.is-open .site-switcher-caret { opacity: 1; }
/* Catches the click that closes the popup. Transparent, above the page, below
   the panel itself. */
.site-switcher-backdrop {
  position: fixed;
  inset: 0;
  border: none;
  background: transparent;
  padding: 0;
  cursor: default;
  z-index: 200;
}
.site-switcher-panel {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: -6px;
  z-index: 201;
  width: max-content;
  min-width: 232px;
  max-width: 320px;
  padding: var(--space-2);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-raised);
  box-shadow: var(--shadow);
}
.site-switcher-panel .menu-title { margin: 2px 0 6px 6px; }
.site-switcher-list {
  display: grid;
  gap: 2px;
  max-height: 320px;
  overflow-y: auto;
}
.site-switcher-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 32px;
  padding: 0 8px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 0.86rem;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.site-switcher-item:hover { background: var(--surface-sunken); color: var(--text); }
.site-switcher-item.is-current { background: var(--accent-soft); color: var(--text); }
.site-switcher-item.is-current > i.icon { color: var(--accent); margin: 0 0 0 auto; }
.site-switcher-item-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.site-switcher-empty {
  margin: 0;
  padding: var(--space-2) 8px;
  font-size: 0.82rem;
  color: var(--text-subtle);
}
.site-switcher-all { font-size: 0.84rem; }
/* Collapsed sidebar hides .brand-name-row, so the popup goes with it. */
.sidebar-collapsed .site-switcher-panel { display: none; }

/* ===== Website switcher (legacy dropdown form) ===== */
.website-switcher {
  padding: 0;
}
.website-switcher label {
  display: block;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
}
.website-switcher .ui.dropdown {
  width: 100%;
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  color: var(--text);
  border-radius: var(--radius);
  padding: 0;
  font-size: 0.88rem;
  font-family: inherit;
}
.website-switcher .ui.selection.dropdown > .divider.text {
  color: var(--text);
}
body[data-theme="light"] .website-switcher .ui.selection.dropdown > .divider.text {
  color: var(--text);
}

/* ===== Content area ===== */
.admin-content {
  display: flex;
  flex-direction: column;
}
.admin-content > * + * {
  margin-top: 14px;
}
.admin-content > .content-header + * {
  margin-top: 0;
}

/* ===== Content header (merged with content box below) ===== */
.content-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 16px;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  background: var(--surface);
}
body[data-theme="light"] .content-header {
  border: 1px solid var(--border);
}
/* When a panel.glass or file-manager-layout directly follows content-header, merge them */
.content-header:has(+ .panel.glass),
.content-header:has(+ .file-manager-layout) {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom: none;
  box-shadow: none;
}
.content-header + .panel.glass {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
.content-header + .file-manager-layout > .panel.glass:first-child {
  border-top-left-radius: 0;
}
/* Merge tabs / deploy-card flush with header inside panel.glass */
.panel.glass > .design-tabs:first-child {
  margin: -8px -16px 0;
  padding: 8px 16px;
}
.panel.glass > .deploy-card:first-child {
  padding: 0;
}
.eyebrow {
  margin: 0;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--cyan);
}
h2 { margin: 4px 0 0; font-size: 1.3rem; }
/* One breadcrumb convention (docs/08 §9): "Admin › area › object", built by
   PageHeader from its Breadcrumbs/Crumbs parameters. The separator is a styled
   chevron, not a literal ">" typed into the label. */
.crumbs {
  margin: 6px 0 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 0.78rem;
  color: var(--text-muted);
}
.crumbs a { color: var(--text-muted); text-decoration: none; }
.crumbs a:hover { color: var(--text); opacity: 1; }
.crumbs .crumb-current { color: var(--text); font-weight: 500; }
.crumbs .crumb-sep {
  color: var(--text-subtle);
  user-select: none;
  line-height: 1;
}
.topbar-actions { display: flex; gap: 8px; align-items: center; }
/* The object being edited, next to its type, in a detail page title. */
.page-title-object { font-weight: 600; }
.page-title-type {
  margin-left: var(--space-2);
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--text-subtle);
}

/* ===== Fix double borders/padding on Fomantic .ui.input wrapper ===== */
.ui.input {
  border: none !important;
  box-shadow: none !important;
  padding: 0 !important;
}

/* ===== Unified sizing for inputs & dropdowns =====
   docs/08 §9: every text control is 34px tall with --radius. */
.ui.input > input,
.ui.form input[type="text"],
.ui.form input[type="email"],
.ui.form input[type="password"],
.ui.form input[type="number"],
.ui.form input[type="url"],
.ui.form input[type="date"],
.ui.selection.dropdown {
  min-height: 34px !important;
}
/* An icon input has to leave room for its icon; the padding rule below is !important,
   so restoring the space has to be too, or the icon sits on top of the text. */
.ui.left.icon.input > input {
  padding-left: 2.4em !important;
}
/* Textareas keep Fomantic's own height but lose its 8em floor, as before. */
.ui.form textarea {
  min-height: 0 !important;
}
.ui.input > input,
.ui.form input[type="text"],
.ui.form input[type="email"],
.ui.form input[type="password"],
.ui.form input[type="number"],
.ui.form input[type="url"],
.ui.form input[type="date"],
.ui.form textarea,
.ui.selection.dropdown {
  font-size: 0.88rem;
  padding: 0.52em 0.8em !important;
  line-height: 1.21em;
  box-sizing: border-box;
  border-radius: var(--radius);
}

.ui.selection.dropdown {
  padding-right: 2.1em !important;
}
.ui.selection.dropdown > .text,
.ui.selection.dropdown > .default.text {
  line-height: 1.21em;
}

.ui.selection.dropdown .menu > .item {
  padding: 0.52em 0.8em !important;
  font-size: 0.88rem;
  line-height: 1.21em;
}

.ui.selection.dropdown > .dropdown.icon {
  top: 50% !important;
  transform: translateY(-50%);
  margin-top: 0 !important;
  padding: 0 0.8em !important;
}

.ui.form .field > label,
.ui.form label {
  font-size: 0.82rem;
  color: var(--muted) !important;
}

/* ===== Fomantic UI button overrides =====
   docs/08 §9: 34px tall, --radius, secondary = raised surface with a strong
   outline, primary = accent fill, destructive = --err. The `body` prefix outranks
   Fomantic's own .ui.button rules without reaching for !important. */
body .ui.button {
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  min-height: 34px;
  padding: 0 1.1em;
  font-size: 0.88rem;
  line-height: 1.2;
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  color: var(--text);
  border-radius: var(--radius);
  font-weight: 500;
  transition: background var(--transition), border-color var(--transition), color var(--transition);
}
body .ui.button:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--text);
}
/* The flex `gap` above already spaces an icon from its label; Fomantic's own
   negative left margin on button icons would double the gap on one side. */
body .ui.button > .icon:not(.button):not(.dropdown) {
  margin: 0;
}
body .ui.primary.button {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}
body .ui.primary.button:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--accent-contrast);
}
body .ui.negative.button,
body .ui.red.button,
body .ui.negative.button.basic {
  background: var(--err-soft);
  border-color: var(--err);
  color: var(--err);
}
/* --accent-contrast is the "text on a solid fill" colour in both themes: near
   black on the light dark-theme fills, white on the deeper light-theme ones. */
body .ui.negative.button:hover,
body .ui.red.button:hover,
body .ui.negative.button.basic:hover {
  background: var(--err);
  border-color: var(--err);
  color: var(--accent-contrast);
}
/* There is no green button (docs/08 §9). Green is the *state* — "Published" —
   and the accent is the primary action, so Publish/Apply render as the accent
   wherever they were marked `positive`. */
body .ui.positive.button {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}
body .ui.positive.button:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--accent-contrast);
}
/* Fomantic makes a fluid button `display: block`, which would leave its label
   sitting at the top of the 34px box; keep it a flex row that fills the width. */
body .ui.fluid.button {
  display: flex;
  width: 100%;
}
/* Icon-only and tiny buttons keep the radius but not the 34px floor. */
body .ui.button.icon:not(.labeled),
body .ui.mini.button,
body .ui.tiny.button {
  min-height: 0;
  padding: 0.5em 0.7em;
}
/* docs/08 §9: inputs and selects are a raised surface behind a --border-strong
   outline in both themes, so these rules are no longer dark-only. */
.ui.input > input,
.ui.input > textarea,
.ui.form input,
.ui.form input[type="text"],
.ui.form input[type="email"],
.ui.form input[type="password"],
.ui.form input[type="number"],
.ui.form input[type="url"],
.ui.form input[type="date"],
.ui.form textarea,
.ui.form .field > .selection.dropdown,
.ui.selection.dropdown {
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  color: var(--text);
  font-family: inherit;
  border-radius: var(--radius);
}
.ui.input > input::placeholder,
.ui.form input::placeholder,
.ui.form textarea::placeholder {
  color: var(--text-subtle);
}
/* The focus ring is the shared one from §8 — an accent outline, not a border
   colour change, so the control does not shift by a pixel when focused. */
.ui.input > input:focus,
.ui.input > textarea:focus,
.ui.form input:focus,
.ui.form textarea:focus,
.ui.selection.dropdown:focus,
.ui.selection.active.dropdown {
  border-color: var(--accent);
  background: var(--surface-raised);
  color: var(--text);
}
.ui.selection.dropdown:hover {
  border-color: var(--border-strong);
}
.ui.selection.dropdown .menu {
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
  box-shadow: var(--shadow);
}
.ui.selection.dropdown .menu > .item {
  color: var(--text);
  border-top: 1px solid var(--border);
  background: transparent;
}
.ui.selection.dropdown .menu > .item:hover,
.ui.selection.dropdown .menu > .item.active {
  background: var(--accent-soft);
  color: var(--text);
}
.ui.selection.dropdown .menu > .item.active {
  font-weight: 600;
}
.ui.selection.dropdown > .dropdown.icon,
.ui.selection.dropdown > .delete.icon,
.ui.selection.dropdown > .search.icon {
  color: var(--text-muted);
}
.ui.selection.dropdown > .default.text {
  color: var(--text-subtle);
}
.ui.selection.dropdown > .divider.text,
.ui.selection.visible.dropdown > .text:not(.default) {
  color: var(--text);
}
.ui.checkbox label,
.ui.checkbox + label {
  color: var(--text);
}
.ui.checkbox label::before {
  background: var(--surface-raised);
  border-color: var(--border-strong);
}
.ui.checkbox input:checked ~ label::before {
  background: var(--accent);
  border-color: var(--accent);
}
.ui.checkbox input:checked ~ label::after {
  color: var(--accent-contrast);
}
body[data-theme="dark"] .ui.form .field > label,
body[data-theme="dark"] .ui.form .fields .field > label,
body[data-theme="dark"] .ui.form .field.required > label,
body[data-theme="dark"] .ui.form label {
  color: var(--muted);
  font-size: 0.82rem;
}
/* ----- Date picker (Fomantic calendar) -----
   The one popup in the admin that used to ignore the theme: it arrives wearing
   Fomantic's own light palette. Every rule below is token-driven, so light and dark
   are the same code, and each is prefixed with `body` only to outrank Fomantic's
   .ui.popup / .ui.table defaults. The picker writes dd.MM.yyyy; the C# side has to
   agree, hence Culture="cs-CZ" on every SemDate* input (docs/05 §3.13). */
/* Shown from `active`, which the plugin toggles while the field has focus — Fomantic's
   popup module never shows it (see wwwroot/js/datePicker.js for the whole story). That
   also means the position has to be stated here rather than computed by the module. */
body .ui.calendar .ui.popup.active {
  display: block;
}
body .ui.calendar .ui.popup {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: auto;
  bottom: auto;
  margin: 0;
  background: var(--popup);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  color: var(--text);
  padding: 6px;
  /* The invoice form is itself a modal, and a picker behind the scrim is unusable. */
  z-index: 2100;
}
/* Fomantic draws the little pointer as a rotated square with its own background. */
body .ui.calendar .ui.popup::before {
  background: var(--popup);
  box-shadow: -1px -1px 0 0 var(--border);
}
body .ui.calendar .ui.popup > table {
  background: transparent;
  border: 0;
  color: inherit;
}
/* Month + arrows */
body .ui.calendar .ui.popup table thead th {
  background: transparent;
  color: var(--text);
  font-weight: 600;
  border: 0;
}
/* Day-of-week row reads as a label, not as data. */
body .ui.calendar .ui.popup table thead tr:last-child th {
  color: var(--text-subtle);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
body .ui.calendar .ui.popup table td {
  background: transparent;
  color: var(--text);
  border: 0;
  border-radius: var(--radius-sm);
}
body .ui.calendar .ui.popup table td.link:hover,
body .ui.calendar .ui.popup table th.link:hover {
  background: var(--accent-soft);
  color: var(--text);
}
/* Today is marked by the accent on the number; the filled cell is reserved for the
   selected day, so the two never compete. */
body .ui.calendar .ui.popup table td.today {
  color: var(--accent);
  font-weight: 700;
}
body .ui.calendar .ui.popup table td.active,
body .ui.calendar .ui.popup table td.active:hover,
body .ui.calendar .ui.popup table td.range {
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
}
body .ui.calendar .ui.popup table td.disabled,
body .ui.calendar .ui.popup table td.adjacent {
  color: var(--text-subtle);
  opacity: 0.55;
}
/* The "Today" shortcut in the footer row. */
body .ui.calendar .ui.popup table tfoot td,
body .ui.calendar .ui.popup table tr.today td {
  color: var(--accent);
  font-weight: 600;
}
/* The input's own calendar icon — quiet, it labels the field rather than shouting. */
body .ui.input > i.calendar.icon {
  color: var(--text-subtle);
  opacity: 1;
}
/* Modals and their scrim are theme-agnostic: an opaque raised surface lifted by
   --shadow-lg over a plain black scrim, no blur (docs/08 §1, §3). The `body`
   prefix only exists to outrank Fomantic's own .ui.modal rules. */
body .ui.dimmer {
  background: rgba(0, 0, 0, 0.55);
}
body .ui.modal {
  background: var(--surface-raised);
  color: var(--text);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
}
body .ui.modal > .header {
  background: var(--surface-raised);
  color: var(--text);
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}
body .ui.modal > .content {
  background: var(--surface-raised);
  color: var(--text);
}
body .ui.modal > .actions {
  background: var(--surface-raised);
  border-top: 1px solid var(--border);
  border-radius: 0 0 var(--radius-xl) var(--radius-xl);
}
/* Whatever ends up first and last inside the modal has to follow the container radius,
   in the same shape Fomantic uses, or its own rule wins on specificity. */
body .ui.modal > :first-child:not(.icon):not(.dimmer) {
  border-top-left-radius: var(--radius-xl);
  border-top-right-radius: var(--radius-xl);
}
body .ui.modal > :last-child:not(.icon):not(.dimmer) {
  border-bottom-left-radius: var(--radius-xl);
  border-bottom-right-radius: var(--radius-xl);
}
/* Fomantic centres a modal by absolute positioning plus a negative margin it
   computes in JS; when the dialog is taller or shorter than that guess it ends
   up anchored high on the page. The dimmer is already a flex box, so the dialog
   only has to stop positioning itself and let the box centre it. */
body .ui.dimmer.modals {
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  overflow: auto;
}
body .ui.dimmer.modals > .ui.modal {
  position: relative !important;
  top: auto !important;
  left: auto !important;
  margin: 0 !important;
  transform: none !important;
  max-height: calc(100vh - var(--space-6));
}
/* docs/08 §9: the close control belongs to the dialog, not to the scrim. Pulled
   inside the top-right corner, it reads as part of the modal and stays visible
   in both themes (Fomantic's own colour is white, invisible on a light dialog). */
body .ui.modal > .close,
body .ui.modal > i.close.icon {
  top: var(--space-3);
  right: var(--space-3);
  left: auto;
  width: 28px;
  height: 28px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  opacity: 1;
  transition: background var(--transition), color var(--transition);
}
/* A close button that is a *direct child* of .ui.modal costs the dialog its rounded
   top corners — they paint square while the bottom two stay round (#248). Reparenting
   it into the header is the only thing that fixes it: neither z-index:auto nor
   overflow:hidden on the dialog helps, and the button does not overlap the corner arc
   at all. Put the close inside .header, as FileManagerPage does. */
body .ui.modal > .close:hover,
body .ui.modal > i.close.icon:hover {
  background: var(--surface-sunken);
  color: var(--text);
}
/* The header has to leave room for it. */
body .ui.modal > .header { padding-right: calc(var(--space-6) + var(--space-3)); }

body[data-theme="dark"] .ui.warning.message {
  background: rgba(255, 217, 135, 0.1);
  border: 1px solid rgba(255, 217, 135, 0.4);
  color: var(--warn);
  box-shadow: none;
}
body[data-theme="dark"] .ui.message.info {
  background: rgba(66, 233, 255, 0.08);
  border: 1px solid rgba(66, 233, 255, 0.3);
  color: var(--text);
  box-shadow: none;
}

/* ===== Form controls (legacy: used by HtmlCodeEditor/TinyMceEditor) ===== */
.input {
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  color: var(--text);
  border-radius: var(--radius);
  min-height: 34px;
  padding: 7px 12px;
  font-size: 0.88rem;
  font-family: inherit;
  width: 100%;
}
.input:focus {
  border-color: var(--accent);
  color: var(--text);
  background: var(--surface-raised);
}
.input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.code-editor {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  min-height: 180px;
}

/* ===== CodeMirror 6 editor ===== */
.code-editor-wrap {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-sunken);
  /* overflow:auto so a long unbroken line scrolls inside the editor instead of
     stretching the page layout (see #205). min-width:0 allows the wrap to shrink
     inside grid/flex parents that would otherwise blow out their track width. */
  overflow: auto;
  min-width: 0;
  max-width: 100%;
}
/* Keep this in step with the EditorView theme in js/codemirrorInterop.js, which sets
   the same size — two declarations of one value, and whichever wins is source order. */
.code-editor-wrap .cm-editor {
  font-size: 0.8rem;
  min-height: var(--cm-min-height, 200px);
  max-width: 100%;
}
.code-editor-wrap .cm-editor .cm-scroller {
  min-height: var(--cm-min-height, 200px);
  overflow: auto;
}
/* Soft wrap is on (EditorView.lineWrapping), so lines break at the editor's width
   instead of running off to the right. This only catches what wrapping cannot: a
   single unbroken token such as a long URL or a base64 data URI. */
.code-editor-wrap .cm-line {
  overflow-wrap: anywhere;
}
.code-editor-wrap .cm-editor.cm-focused {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
body[data-theme="light"] .code-editor-wrap .cm-editor .cm-gutters {
  background: var(--surface-sunken);
  border-right: 1px solid var(--border);
}
body[data-theme="light"] .code-editor-wrap .cm-editor {
  background: var(--surface-sunken);
}

.drag-handle {
  width: 44px;
  color: var(--muted);
  cursor: grab;
  user-select: none;
}

.section-dnd {
  border: 1px dashed rgba(155, 176, 230, 0.35);
  border-radius: 12px;
  padding: 10px;
}

.section-editor {
  background: transparent;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.form-grid label {
  display: grid;
  gap: 6px;
  font-size: 0.82rem;
  color: var(--muted);
}
.form-grid .full { grid-column: 1 / -1; }

/* ===== Appearance → Branding rows (URL input + picker + preview) =====
   Fixed column track so every row lines up and nothing reflows when a
   preview appears or disappears. */
.branding-field { display: grid; gap: 6px; }
.branding-field > label {
  font-size: 0.82rem;
  color: var(--muted);
}
.branding-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto 56px;
  gap: 8px;
  align-items: center;
}
.branding-row .ui.input { width: 100%; }
.branding-preview {
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--surface-sunken);
}
.branding-preview img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
@media (max-width: 900px) {
  .branding-row { grid-template-columns: minmax(0, 1fr) 56px; }
  .branding-row .ui.button { grid-column: 1; }
  .branding-preview { grid-row: 1 / span 2; grid-column: 2; }
}

/* ===== Secondary tab strip (inside a settings section) ===== */
.subtabs { display: flex; gap: 4px; flex-wrap: wrap; }
.subtabs button {
  border: none;
  border-radius: var(--radius);
  padding: 5px 12px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 500;
  transition: background var(--transition), color var(--transition);
}
.subtabs button:hover { color: var(--text); }
.subtabs button.active {
  background: var(--accent-soft);
  color: var(--text);
}

/* ===== Post editor two-column layout ===== */
.post-editor {
  display: grid;
  grid-template-columns: 1fr 520px;
  gap: 20px;
  align-items: start;
}
.post-editor-main {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* The post editor's right rail is now five cards laid out by
   PostDetailPage.razor.css; the old .post-editor-sidebar / .post-sidebar-*
   / .post-status-badge rules that described the two-column pile went with it. */
@media (max-width: 1120px) {
  .post-editor { grid-template-columns: 1fr; }
}

/* ===== Dashboard ===== */
/* Stat tiles are one band split by hairlines, not four bordered cards
   (docs/08 §9). The negative margins pull the band out to the edges of the
   enclosing .panel so the dividers run its full width. */
.dashboard-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  margin: calc(var(--space-4) * -1) calc(var(--space-4) * -1) 0;
  border-bottom: 1px solid var(--border);
}
.dashboard-stat-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  padding: var(--space-4);
  border-left: 1px solid var(--border);
  text-decoration: none;
  color: var(--text);
  transition: background var(--transition);
}
.dashboard-stat-card:first-child { border-left: none; }
a.dashboard-stat-card:hover {
  background: var(--surface-raised);
  color: var(--text);
  opacity: 1;
}
/* --surface-raised is white in the light theme, i.e. invisible on a card, so
   the hover step there goes the other way, to the sunken surface. */
body[data-theme="light"] a.dashboard-stat-card:hover {
  background: var(--surface-sunken);
}
.dashboard-stat-value {
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1.15;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.dashboard-stat-value-sm {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.dashboard-stat-label {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.dashboard-stat-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}
.dashboard-stat-period {
  font-size: 0.75rem;
  color: var(--text-subtle);
}
.dashboard-trend {
  font-size: 0.72rem;
  font-weight: 600;
  margin-left: var(--space-1);
}
.dashboard-trend.trend-up { color: var(--ok); }
.dashboard-trend.trend-down { color: var(--err); }
@media (max-width: 900px) {
  .dashboard-stats { grid-template-columns: repeat(2, 1fr); }
  .dashboard-stat-card:nth-child(odd) { border-left: none; }
  .dashboard-stat-card:nth-child(n + 3) { border-top: 1px solid var(--border); }
}
.dashboard-layout {
  display: grid;
  grid-template-columns: 7fr 3fr;
  gap: var(--space-5);
  align-items: start;
}
.dashboard-main { min-width: 0; }
/* The side column is secondary content. It carries no boxes of its own — one
   hairline splits it off the main column and spacing separates its sections
   (docs/08 §9). */
.dashboard-sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  border-left: 1px solid var(--border);
  padding-left: var(--space-5);
}
/* Publish status widget — the tint is the message, so it keeps a soft fill but
   loses the border and the glow. */
.dashboard-publish-status {
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.dashboard-publish-indicator {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.dashboard-publish-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  flex-shrink: 0;
}
.dashboard-publish-text {
  font-weight: 600;
  font-size: 0.875rem;
}
.dashboard-publish-date {
  font-size: 0.8rem;
  color: var(--text-muted);
  padding-left: var(--space-4);
}
/* Publish status states */
.dashboard-publish-published { background: var(--ok-soft); }
.dashboard-publish-published .dashboard-publish-dot { background: var(--ok); }
.dashboard-publish-published .dashboard-publish-text { color: var(--ok); }
.dashboard-publish-draft { background: var(--warn-soft); }
.dashboard-publish-draft .dashboard-publish-dot { background: var(--warn); }
.dashboard-publish-draft .dashboard-publish-text { color: var(--warn); }
/* "Never published" is not a state worth colouring; the sunken surface reads as
   a quiet well in both themes, unlike --surface-raised which is white in light. */
.dashboard-publish-never { background: var(--surface-sunken); }
.dashboard-publish-never .dashboard-publish-dot { background: var(--text-subtle); }
.dashboard-publish-never .dashboard-publish-text { color: var(--text-muted); }
.dashboard-publish-unknown .dashboard-publish-dot { background: var(--text-subtle); opacity: 0.4; }
.dashboard-publish-unknown .dashboard-publish-text { color: var(--text-subtle); }
/* Website info panel — a section inside a panel, not a box inside a box: a
   quiet uppercase heading, no border, no fill. */
.dashboard-info-panel { padding: 0; }
.dashboard-info-panel h4 {
  margin: 0 0 var(--space-2);
  font-size: 0.82rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.dashboard-info-panel .design-table { min-width: auto; }
/* A key/value list, not a data table: no row rules, no hover, tighter rows. */
.dashboard-info-panel table.design-table tbody td {
  height: auto;
  padding: var(--space-1) var(--space-3) var(--space-1) 0;
  font-size: 0.82rem;
  border-bottom: none;
}
.dashboard-info-panel table.design-table tbody td:last-child { padding-right: 0; }
.dashboard-info-panel .design-table tbody tr:hover { background: none; }
@media (max-width: 1120px) {
  .dashboard-layout { grid-template-columns: 1fr; }
  .dashboard-sidebar {
    border-left: none;
    padding-left: 0;
    border-top: 1px solid var(--border);
    padding-top: var(--space-5);
  }
}
/* Post list in sidebar panels */
.dashboard-post-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.dashboard-post-list li {
  padding: 6px 0;
  border-bottom: 1px solid var(--border);
}
.dashboard-post-list li:last-child { border-bottom: none; }
.dashboard-post-list a {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.88rem;
}
.dashboard-post-list.stacked li {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.dashboard-post-meta {
  font-size: 0.72rem;
  color: var(--muted);
}
.dashboard-panel-footer {
  margin-top: var(--space-3);
  font-size: 0.82rem;
}

/* ===== Panels ===== */
.panel { padding: 16px; }
.stack { display: grid; gap: 12px; }

.panel-inline {
  border: 1px solid rgba(155, 176, 230, 0.24);
  border-radius: var(--radius);
  padding: 10px;
  background: var(--surface-raised);
}
.panel-inline h4 { margin: 0 0 8px; }

/* ===== Tables =====
   No outer frame (docs/08 §9): one hairline under the header, one between rows,
   and nothing around the outside. The first and last columns lose their side
   padding so the table lines up with the text around it. */
.table-wrap { overflow: auto; }
table.design-table { width: 100%; border-collapse: collapse; min-width: 500px; }
table.design-table th,
table.design-table td {
  text-align: left;
  padding: var(--space-2);
  font-size: 0.85rem;
}
table.design-table th:first-child,
table.design-table td:first-child { padding-left: 0; }
table.design-table th:last-child,
table.design-table td:last-child { padding-right: 0; }
table.design-table thead th {
  height: 32px;
  color: var(--text-muted);
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
  border-bottom: 1px solid var(--border);
}
table.design-table tbody td {
  height: 40px;
  border-bottom: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}
table.design-table tbody tr:last-child td { border-bottom: none; }
/* Row feedback is opt-in via `.clickable`: a highlight that follows the cursor
   promises the row does something, so a read-only table (API keys, revision
   history, a key/value list) must not have one. */
table.design-table.clickable tbody tr { cursor: pointer; }
table.design-table.clickable tbody tr:hover { background: var(--surface-raised); }
/* --surface-raised is white in the light theme, so a hovered row there steps
   down to the sunken surface instead. */
body[data-theme="light"] table.design-table.clickable tbody tr:hover { background: var(--surface-sunken); }
/* The hover band is bled out to the panel's edges rather than inset. The first and
   last cells carry `padding-left/right: 0` so their text lines up with the page
   title and everything else in the panel, which meant the fill started and ended
   exactly on that text and read as clipped. Insetting the band would have bought
   the breathing room by pushing every column inward and losing that alignment.
   Same idiom as .dashboard-stats: pull out by the panel's own padding, then give
   it back as cell padding so no text moves. The pull is on the scroll wrapper, not
   on the table, precisely so the table still measures 100% of its container and
   `.table-wrap { overflow: auto }` gains no scroll width from this (Users/Roles).
   The cell padding is scoped to the same two contexts that get the pull, so a
   design-table somewhere else is untouched. */
.panel > table.design-table,
.panel .table-wrap {
  margin-left: calc(var(--space-4) * -1);
  margin-right: calc(var(--space-4) * -1);
}
.panel > table.design-table > * > tr > :first-child,
.panel .table-wrap > table.design-table > * > tr > :first-child {
  padding-left: var(--space-4);
}
.panel > table.design-table > * > tr > :last-child,
.panel .table-wrap > table.design-table > * > tr > :last-child {
  padding-right: var(--space-4);
}
/* The keyboard equivalent of that hover. The ring is inset because an outline on
   a <tr> is drawn outside the row box and would otherwise sit under the
   neighbouring rows. :focus-within covers the "real <a> inside the first cell"
   pattern; :focus-visible covers a tr made focusable with tabindex, where it also
   has to restate the offset against the global focus rule's +2px. */
table.design-table.clickable tbody tr:focus-visible,
table.design-table.clickable tbody tr:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
/* With a nested anchor the row ring is the one that shows; a second ring around
   the link inside it is noise. */
table.design-table.clickable tbody tr:focus-within a:focus-visible { outline: none; }
/* Numeric columns line up on the right (docs/08 §6). */
table.design-table th.num,
table.design-table td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ===== Links ===== */
a { color: var(--cyan); }
a:hover { color: var(--cyan); opacity: 0.85; }

/* ===== Badges =====
   docs/08 §9: a soft fill in the state colour with the solid colour as text —
   no outline, --radius-sm because a badge is the smallest thing on the page. */
.ui.label {
  display: inline-flex;
  padding: 3px 9px;
  border-radius: var(--radius-sm);
  font-size: 0.76rem;
  border: none;
  background: var(--surface-raised);
  color: var(--text-muted);
  font-weight: 500;
}
body[data-theme="light"] .ui.label {
  background: var(--surface-sunken);
}
.ui.label.green  { color: var(--ok)   !important; background: var(--ok-soft)     !important; }
.ui.label.yellow { color: var(--warn) !important; background: var(--warn-soft)   !important; }
.ui.label.red    { color: var(--err)  !important; background: var(--err-soft)    !important; }
.ui.label.teal   { color: var(--accent) !important; background: var(--accent-soft) !important; }
.ui.label.orange { color: var(--warn) !important; background: var(--warn-soft)   !important; }

/* ==========================================================================
   SHARED CONVENTIONS — class names page authors should reach for
   --------------------------------------------------------------------------
   Status                                                        (docs/08 §9)
     .status-badge                     base pill, use with one modifier below
     .status-badge--published          green   — live / published / active
     .status-badge--draft              amber   — draft changes / scheduled
     .status-badge--error              red     — failed / blocked / inactive
     .status-badge--neutral            grey    — not published / none / n-a
     .status-badge--info               accent  — informational only, no state
     .status-badge--dot                adds a leading state dot
     Vocabulary is fixed: "Published" / "Not published" / "Draft changes".
     Never "Live", never "Yes/No".

   Header & toolbar actions                                      (docs/08 §9)
     .topbar-actions                   the flex row in a PageHeader
     .action-danger                    put on the destructive button (or on a
                                       wrapper holding several of them). It is
                                       pushed to the far left of the group and
                                       fenced off with a hairline, so Delete is
                                       never adjacent to Save.
     .action-sep                       an explicit hairline between two groups

   Per-row table actions                                         (docs/08 §9)
     td.row-actions-cell               last cell: shrink-to-fit, right aligned
     .row-actions                      flex wrapper, revealed on row hover or
                                       keyboard focus (always shown on touch)
     .row-action                       quiet icon button, 28px square
     .row-action--danger               destructive tint on hover/focus only

   Link that must read as a control                           (docs/08 §9)
     .link-action                      put on the <a>, nothing else. Gives a
                                       card-footer / panel link a quiet ghost
                                       button shape and a CSS-drawn chevron, so
                                       it is recognisable as a control without a
                                       second accent colour or a real button.
                                       Colour alone (an <a> that only underlines)
                                       is not an affordance.

   List toolbars                                              (docs/08 §9)
     .toolbar-search                   the list's search box — 280px
     .toolbar-filter                   a filter selector — 200px
     Two widths per toolbar, never four.

   Numbers and dates                                          (docs/08 §6, §9)
     th.num / td.num                   right aligned, tabular figures
     .tabular                          tabular figures anywhere else
     Format dates through AdminFormat (Services/AdminFormat.cs): Day() in
     lists, Minute() only where the time of day carries meaning.
   ========================================================================== */

/* ----- Status badges ----- */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 3px 9px;
  border-radius: var(--radius-sm);
  font-size: 0.76rem;
  font-weight: 500;
  line-height: 1.35;
  white-space: nowrap;
  background: var(--surface-raised);
  color: var(--text-muted);
}
/* --surface-raised is white in the light theme, i.e. invisible on a white panel,
   so an unmodified badge steps down to the sunken surface instead. Every coloured
   modifier below has to restate itself for that theme: this rule carries a
   `body[data-theme=...]` prefix and would otherwise outweigh them. */
body[data-theme="light"] .status-badge { background: var(--surface-sunken); }
.status-badge--dot::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: currentColor;
  flex-shrink: 0;
}
.status-badge--published,
body[data-theme="light"] .status-badge--published {
  background: var(--ok-soft);
  color: var(--ok);
}
.status-badge--draft,
body[data-theme="light"] .status-badge--draft {
  background: var(--warn-soft);
  color: var(--warn);
}
.status-badge--error,
body[data-theme="light"] .status-badge--error {
  background: var(--err-soft);
  color: var(--err);
}
.status-badge--info,
body[data-theme="light"] .status-badge--info {
  background: var(--accent-soft);
  color: var(--accent);
}
.status-badge--neutral,
body[data-theme="light"] .status-badge--neutral {
  background: var(--surface-sunken);
  color: var(--text-muted);
}

/* ----- Destructive actions are fenced off from the primary one ----- */
/* The hairline is a pseudo-element, not a border: the element carrying the class
   is usually a .ui.button whose own border rule would win. */
.topbar-actions > .action-danger,
.toolbar > .action-danger {
  order: -1;
  position: relative;
  margin-right: var(--space-4);
}
.topbar-actions > .action-danger::after,
.toolbar > .action-danger::after {
  content: "";
  position: absolute;
  top: 3px;
  bottom: 3px;
  right: calc(var(--space-3) * -1);
  width: 1px;
  background: var(--border);
}
/* When it is the only thing in the group there is nothing to fence it from. */
.topbar-actions > .action-danger:only-child,
.toolbar > .action-danger:only-child { margin-right: 0; }
.topbar-actions > .action-danger:only-child::after,
.toolbar > .action-danger:only-child::after { content: none; }
.action-sep {
  width: 1px;
  align-self: stretch;
  min-height: 24px;
  background: var(--border);
  flex-shrink: 0;
}

/* ----- Per-row actions ----- */
table.design-table th.row-actions-cell,
table.design-table td.row-actions-cell {
  width: 1%;
  white-space: nowrap;
  text-align: right;
}
.row-actions {
  display: inline-flex;
  gap: var(--space-1);
  justify-content: flex-end;
  opacity: 0;
  transition: opacity var(--transition);
}
/* Revealed by hovering the row or by tabbing into one of its buttons, so the
   actions stay reachable from the keyboard while the table reads quiet. */
tr:hover .row-actions,
tr:focus-within .row-actions,
.row-actions:focus-within { opacity: 1; }
/* Hover does not exist on a touch screen — there the actions are always shown. */
@media (hover: none) {
  .row-actions { opacity: 1; }
}
.row-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.row-action > i.icon { margin: 0; }
.row-action:hover {
  background: var(--accent-soft);
  color: var(--text);
}
.row-action--danger:hover {
  background: var(--err-soft);
  color: var(--err);
}

/* ----- Copy button ----- */
/* The copy button answers for itself: the icon swaps to a tick when the browser took the text
   and to a cross when it refused, then goes back. The state is set by `stranioCopyRow` inside
   the click, not by the server, because that is where the copy happens — Safari refuses a
   clipboard write once the gesture is over (#265). Global, because three screens copy this way. */
.copy-button .check.icon,
.copy-button .times.icon {
  display: none;
}
.copy-button.is-copied .copy.icon,
.copy-button.is-copy-failed .copy.icon {
  display: none;
}
.copy-button.is-copied .check.icon {
  display: inline-block;
  color: var(--ok);
}
.copy-button.is-copy-failed .times.icon {
  display: inline-block;
  color: var(--err);
}

/* ----- Inline link action ----- */
/* A navigation link that has to read as something you can press: a card footer,
   a panel's "see all", the publishing chip. Colour and an underline on hover are
   not enough — people scanning a card do not register a coloured word as a
   control (docs/08 §9). This gives the link a control *shape* (a quiet ghost
   button, the same 8px radius and height as the real ones) and a chevron, so the
   affordance survives without a second accent colour or a real button.
   The chevron is drawn in CSS from `currentColor`, so adopting the convention is
   one class on the existing anchor — no icon markup, no token for it. */
.link-action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 28px;
  padding: 0 var(--space-2);
  margin-left: calc(var(--space-2) * -1); /* keeps the label optically flush with the text above it */
  border-radius: var(--radius);
  color: var(--accent);
  font-size: 0.8rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--transition), color var(--transition);
}
.link-action::after {
  content: "";
  width: 6px;
  height: 6px;
  border-top: 1.5px solid currentColor;
  border-right: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform 160ms ease;
}
.link-action:hover,
.link-action:focus-visible {
  background: var(--accent-soft);
  color: var(--accent-hover);
  text-decoration: none;
}
.link-action:hover::after { transform: rotate(45deg) translate(1px, -1px); }
.link-action:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Not for a chip that is already a control. The sidebar publishing chip has its
   own background, state colour and full-width hit area; adding the chevron there
   bought nothing and cost the ~10px that pushed Publish out of the 232px rail. */

/* ----- Numbers ----- */
.tabular { font-variant-numeric: tabular-nums; }

/* ===== Tabs ===== */
.design-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-bottom: 0;
}
.design-tabs a { text-decoration: none; }
/* docs/08 §9: a tab strip is navigation, not a row of buttons — no outlines, the
   selected tab is an accent tint and the rest are quiet labels. */
.design-tabs button,
.design-tabs a {
  border: none;
  border-radius: var(--radius);
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 0 14px;
  /* Buttons get a normalised line-height, anchors inherit the body's — without this the
     link tabs render ~7px taller than the button tabs. */
  line-height: 1.15;
  text-decoration: none;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  transition: background var(--transition), color var(--transition);
}
.design-tabs button:hover,
.design-tabs a:hover {
  background: var(--surface-raised);
  color: var(--text);
}
body[data-theme="light"] .design-tabs button:hover,
body[data-theme="light"] .design-tabs a:hover {
  background: var(--surface-sunken);
}
.design-tabs button.active,
.design-tabs a.active,
body[data-theme="light"] .design-tabs button.active,
body[data-theme="light"] .design-tabs a.active {
  background: var(--accent-soft);
  color: var(--text);
}

/* ===== Toolbar ===== */
.toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* Two widths for a list toolbar, never four (audit B3, F4): a search box and a
   filter selector. Put the class on the control (on SemInput it lands on the
   .ui.input wrapper) instead of an inline max-width, so they cannot drift apart
   again. A filter is narrower than a search — it holds a label, not a sentence. */
.toolbar-search,
.toolbar-search > input {
  width: 100%;
  max-width: 280px;
}
/* The second selector is load-bearing, not redundant: `Class` on a
   SemDropdownSelection lands on the .ui.selection.dropdown root itself, whose own
   Fomantic width rule outweighs a bare class. Do not "simplify" it away. */
.toolbar-filter,
.toolbar-filter.ui.selection.dropdown {
  width: 100%;
  max-width: 200px;
  min-width: 0;
}

/* A panel whose toolbar is a heading plus one action — the billing screens stack
   several such tables on one page, and each needs its own title without becoming a
   second PageHeader. Kept here rather than in three scoped stylesheets that would
   drift apart. */
.panel-section-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.toolbar > .toolbar-end { margin-left: auto; }

/* ===== Filter bar ===== */
.filter-bar {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

/* ===== Callout ===== */
.callout {
  border: 1px solid rgba(255, 217, 135, 0.4);
  color: var(--warn);
  background: rgba(255, 217, 135, 0.1);
  border-radius: 10px;
  padding: 10px;
  font-size: 0.85rem;
}
.callout-error {
  border: 1px solid rgba(255, 149, 179, 0.4);
  color: var(--err);
  background: rgba(255, 149, 179, 0.1);
  border-radius: 10px;
  padding: 10px;
  font-size: 0.85rem;
}

/* ===== Split layouts ===== */
.split-3 {
  display: grid;
  grid-template-columns: 220px 1fr 280px;
  gap: 10px;
}
/* ===== Help text (docs/08 §10) =====
   One muted sentence, and it always sits --space-1 from the thing it belongs to,
   with nothing in between:
     - a sentence about a whole group (a card, a settings section, a set of
       controls) goes under the heading that names the group, above the controls —
       you read it before you act;
     - a hint about one control goes under that control — you read it while you
       check what you typed. A checkbox is the same case: there the label is the
       control, so the sentence goes underneath it.
   What made the old placement read as "too low" was not only the order but the
   distance: the hint was a plain item in a 12px or 16px stack, so it drifted away
   from the control and floated between two blocks. It is never allowed to sit on
   the container's own rhythm — where a stack's gap is larger than --space-1 the
   hint pulls the difference back. Each of those stacks is listed below; the pull
   uses the child combinator on purpose, so a nested field wrapper with its own
   4px gap is not caught by its parent's rule. */
.hint,
.field-hint,
.check-help,
.field-note {
  margin: var(--space-1) 0 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--text-muted);
}

/* Inside a gapped stack the gap is the rhythm, so the margin above steps out of
   the way and the stack decides. */
.stack > .hint,
.stack > .field-hint,
.rail-card > .field-hint,
.rail-field > .field-hint,
.post-write > .field-hint,
.post-slug-field > .field-hint,
.form-grid > .hint,
.form-grid > .field-note,
.checkbox-row > .hint,
.permission > .hint,
.appearance-field > .hint,
.ei-field > .hint,
.branding-field > .hint { margin-top: 0; }

/* …and then, wherever the hint really does explain the thing immediately above
   it, it closes the stack's gap back down to --space-1. A hint that only leads
   into what comes *below* — a sentence introducing a table, say — is not in this
   list and keeps the stack's own rhythm, because pulling it up would attach it to
   whatever happens to precede it. Stacks whose gap already is --space-1
   (.rail-field, .checkbox-row, .permission, .post-slug-field) need no pull. */
.stack > :is(h1, h2, h3, h4, h5, h6) + .hint,
.stack > :is(h1, h2, h3, h4, h5, h6) + .field-hint,
.rail-card > :is(h1, h2, h3, h4, h5, h6) + .field-hint {
  margin-top: calc(var(--space-1) - var(--space-3));
}
.post-write > .field-hint { margin-top: calc(var(--space-1) - var(--space-4)); }
.appearance-field > .hint,
.ei-field > .hint { margin-top: calc(var(--space-1) - var(--space-2)); }
.branding-field > .hint { margin-top: calc(var(--space-1) - 6px); }

/* Fomantic hands every `.field` a 1em bottom margin. Where the next thing is the
   sentence explaining that field, the margin turns the intended 4px into 20px and
   the hint reads as the start of the next block — the exact drift this section
   exists to stop. The class is doubled because Fomantic states it as
   `.ui.form .field`, which outranks a single class; app.css loads after
   semantic.min.css, so an equal-specificity rule here wins. */
.field.field:has(+ .hint),
.field.field:has(+ .field-hint),
.field.field:has(+ .check-help),
.field.field:has(+ .field-note) { margin-bottom: 0; }
/* And the same for a field's own label, which Fomantic also gives a bottom
   margin, for the case where the sentence explains the field before the control
   rather than after it. */
.ui.form .field > label:has(+ .hint),
.ui.form .field > label:has(+ .field-hint),
.ui.form .field > label:has(+ .field-note) { margin-bottom: 0; }

/* A Fomantic checkbox is inline-block, so in plain block flow it drags a text
   line box along and the sentence under it lands ~3px lower than the same
   sentence in a grid row. Making the wrapper a grid removes the line box, and
   every checkbox in the app then explains itself at exactly --space-1. */
.form-grid > div:has(> .ui.checkbox + .hint) {
  display: grid;
  justify-items: start;
  align-content: start;
}

/* A note that qualifies a checkbox takes a line of its own under it rather than
   trailing off its right-hand side, so every checkbox in the app explains itself
   the same way. */
.check-row > .check-help {
  flex-basis: 100%;
  margin-top: calc(var(--space-1) - var(--space-2));
}

/* ===== Misc ===== */
.hidden { display: none !important; }
h3 { font-size: 1.05rem; margin: 0 0 4px; }
h6 { font-size: 0.9rem; margin: 12px 0 6px; color: var(--text); }
hr { border-color: var(--border); opacity: 0.5; }

/* ===== Login page ===== */
.login-page {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 20px;
}
.login-card {
  width: 100%;
  max-width: 400px;
  padding: 32px;
  display: grid;
  gap: 20px;
}
.login-brand {
  display: grid;
  justify-items: center;
  gap: 6px;
  text-align: center;
}
.login-brand p { margin: 0; font-size: 0.8rem; color: var(--muted); }
.brand-logo { height: 36px; width: auto; }
.brand-logo-on-light { display: none; }
body[data-theme="light"] .brand-logo-on-dark { display: none; }
body[data-theme="light"] .brand-logo-on-light { display: block; }

.login-card form {
  display: grid;
  gap: 14px;
}
.login-card label {
  display: grid;
  gap: 6px;
  font-size: 0.82rem;
  color: var(--muted);
}
.login-aside {
  margin: 0;
  text-align: center;
  font-size: 0.8rem;
  color: var(--muted);
}

/* ===== Settings page sections ===== */
.settings-section {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 24px;
  padding: 20px 0;
  border-bottom: 1px solid var(--border);
}
.settings-section:last-child { border-bottom: none; }

/* The section's own name and the sentence explaining it are the group case of
   docs/08 §10: the sentence sits --space-1 under the heading. */
.settings-info h6 {
  margin: 0 0 var(--space-1);
  font-size: 0.92rem;
  font-weight: 600;
}
.settings-info p {
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--text-muted);
  max-width: 280px;
}

.settings-fields { min-width: 0; }
.settings-info { min-width: 0; }
/* Prevent grid blowout in nested form grids (long unbroken content in inputs
   or editors must not stretch the column track — see #205). */
.settings-fields .form-grid > * { min-width: 0; }

.settings-section.danger .settings-info h6 { color: var(--err); }
.settings-section.danger { border-bottom: none; }

@media (max-width: 900px) {
  .settings-section { grid-template-columns: 1fr; gap: 12px; }
}

/* ===== Section headings (used in website detail) ===== */
.section-heading {
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--cyan);
  margin: 8px 0 4px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
}

/* ===== Addresses list ===== */
.address-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid rgba(155, 176, 230, 0.12);
  font-size: 0.88rem;
}
.address-item b { color: var(--text); }
.address-primary { color: var(--ok); font-size: 0.78rem; }

/* ===== File manager layout ===== */
.file-manager-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}
.file-manager-layout.has-details {
  grid-template-columns: 1fr 320px;
}
.file-manager-files { }
.file-manager-details { align-self: start; overflow-wrap: break-word; min-width: 0; }

/* The file manager owns its own breadcrumb, folder cards and upload area in
   FileManagerPage.razor.css — one grid and one card shape for folders and files
   (docs/09 C5), with the actions in a footer strip instead of overlaying the
   icon (C1). The global .fm-breadcrumb / .folder-card / .file-upload-* rules
   that described the old split layout are gone.

   .file-card, .file-grid and .file-picker-* below stay: FilePicker and
   PostImageGallery still render against them. */

/* File cards */
.file-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 8px;
  background: var(--surface-raised);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--transition), box-shadow var(--transition);
  display: flex;
  flex-direction: column;
}
.file-card:hover { border-color: var(--cyan); }
.file-card.selected { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.file-card .file-thumb {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  margin-bottom: 6px;
}
.file-card .file-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 6px;
}
.file-card .file-name { font-size: 0.82rem; word-break: break-all; margin-top: auto; }
.file-card .file-size { font-size: 0.76rem; color: var(--muted); }
.file-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 10px;
}

/* ===== File picker modal ===== */
.file-picker.ui.modal {
  position: relative !important;
  top: auto !important;
  left: auto !important;
  margin: auto !important;
  transform: none !important;
  width: min(1080px, calc(100vw - 48px)) !important;
  max-height: calc(100vh - 64px);
  display: flex !important;
  flex-direction: column;
}
.file-picker.ui.modal > .content {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  padding: 0 !important;
}
.file-picker-layout {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  min-height: 380px;
  height: 100%;
}
.file-picker-folders {
  border-right: 1px solid var(--border);
  padding: 14px 10px;
  overflow-y: auto;
}
.file-picker-side-title {
  margin: 0 0 8px 10px;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.file-picker-folders .folder-list button {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.file-picker-folders .folder-list button i.icon { color: var(--muted); margin: 0; flex: none; }
.file-picker-folders .folder-list button.active {
  background: var(--accent-soft);
  color: var(--text);
  font-weight: 500;
}
.file-picker-folders .folder-list button.active i.icon { color: var(--cyan); }
.file-picker-main { display: flex; flex-direction: column; min-width: 0; }
.file-picker-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
}
.file-picker-files {
  padding: 14px 16px;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}
/* Logos and icons are usually padded artwork — show the whole image, don't crop it. */
.file-picker .file-card .file-thumb { position: relative; }
.file-picker .file-card .file-thumb img { object-fit: contain; }
.file-picker .file-card .file-thumb i.icon {
  font-size: 2.2em;
  margin: 0;
  color: var(--muted);
}
/* Two lines max, so one long filename can't stretch its whole grid row. */
.file-picker .file-card .file-name {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
  line-height: 1.25;
}
.file-picker .file-check {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--cyan);
  color: var(--accent-contrast);
  display: flex;
  align-items: center;
  justify-content: center;
}
.file-picker .file-check i.icon { font-size: 0.72em; margin: 0; color: inherit; }
@media (max-width: 700px) {
  .file-picker-layout { grid-template-columns: 1fr; }
  .file-picker-folders { border-right: none; border-bottom: 1px solid var(--border); max-height: 160px; }
}

/* ===== Folder list ===== */
.folder-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 4px; }
.folder-list a, .folder-list button {
  display: block;
  padding: 8px 10px;
  border-radius: 8px;
  text-decoration: none;
  color: var(--text);
  font-size: 0.88rem;
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}
.folder-list a:hover, .folder-list button:hover,
.folder-list a.active, .folder-list button.active {
  background: var(--accent-soft);
}


/* ===== Blazor essentials ===== */
.valid.modified:not([type=checkbox]) { outline: 1px solid var(--ok); }
.invalid { outline: 1px solid var(--err); }
.validation-message { color: var(--err); font-size: 0.82rem; }

.blazor-error-boundary {
  background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
  padding: 1rem 1rem 1rem 3.7rem;
  color: white;
}
.blazor-error-boundary::after { content: "An error has occurred."; }

#blazor-error-ui {
  color-scheme: light only;
  background: var(--panel-solid);
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
  color: var(--text);
  border-top: 1px solid var(--border);
}
#blazor-error-ui .dismiss {
  cursor: pointer;
  position: absolute;
  right: 0.75rem;
  top: 0.5rem;
}

/* ===== Responsive ===== */
@media (max-width: 1120px) {
  .layout { grid-template-columns: 1fr; }
  .mobile-sidebar-toggle { display: inline-block !important; }
  .sidebar {
    position: fixed;
    top: 14px;
    left: 14px;
    width: min(320px, calc(100vw - 28px));
    height: calc(100vh - 28px);
    transform: translateX(-120%);
    transition: transform 0.2s ease;
    z-index: 6;
    background: var(--panel-solid);
  }
  .layout.sidebar-open .sidebar { transform: translateX(0); }
  .layout.sidebar-open .sidebar-backdrop { opacity: 1; pointer-events: auto; }
  .admin-content { margin-top: 44px; }
  .split-3, .form-grid, .file-manager-layout, .file-manager-layout.has-details { grid-template-columns: 1fr; }
}

.progress { height: 6px; background: var(--surface-sunken); border-radius: var(--radius-full); overflow: hidden; margin: 8px 0; }
.progress .bar { width: 100%; height: 100%; background: var(--accent); animation: pulse 1.2s infinite; }
@keyframes pulse { 0% { opacity: .5;} 50% {opacity:1;} 100% {opacity:.5;} }

/* ===== Title image picker ===== */
.title-image-picker {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.title-image-preview {
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  max-width: 300px;
}
.title-image-preview img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 8px;
}
.title-image-placeholder {
  border: 2px dashed var(--border);
  border-radius: 8px;
  padding: 20px;
  text-align: center;
  cursor: pointer;
  color: var(--muted);
  transition: border-color var(--transition);
  max-width: 300px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.title-image-placeholder:hover {
  border-color: var(--cyan);
  color: var(--text);
}
.title-image-actions {
  display: flex;
  gap: 6px;
}

/* ===== Post image gallery ===== */
.post-image-gallery {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  grid-column: 1 / -1;
}
.post-gallery-card {
  position: relative;
}
.post-gallery-actions {
  display: flex;
  gap: 4px;
  justify-content: center;
  margin-top: 6px;
}
.post-gallery-actions .ui.button {
  padding: 4px 8px;
  font-size: 0.78rem;
}

/* ===== Floating toast notifications =====
   Anchored bottom-right: the top-right corner belongs to the page header's
   actions, and a toast landing on top of the button that triggered it hides the
   thing the reader just used. Newest toast sits closest to the corner. */
.toast-container {
  position: fixed;
  bottom: var(--space-5);
  right: var(--space-5);
  z-index: 10000;
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-end;
  gap: var(--space-2);
  pointer-events: none;
  width: max-content;
  max-width: min(420px, calc(100vw - var(--space-6)));
}
/* Page detail's sticky save bar lands on exactly these pixels, and because the
   toast is fixed at z-index 10000 while the bar is trapped inside .layout's
   stacking context, the toast wins and covers Discard/Save — the two controls the
   error is telling you to use. The toast is the transient one, so it steps above
   the bar instead: the bar's own height (a 34px control, --space-3 padding twice,
   a 1px border twice) plus the --space-3 it holds off the viewport, plus one more
   --space-3 of air. Moving the toast back to the top-right is not an option — that
   corner belongs to the page header's actions (docs/08 §9). */
body:has(.save-bar) .toast-container {
  bottom: calc(34px + var(--space-3) * 4 + 2px);
}
.toast-item {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  border-left: 3px solid;
  box-shadow: var(--shadow);
  font-size: 0.88rem;
  line-height: 1.4;
  max-width: 100%;
  animation: toast-in 0.35s ease-out;
}
.toast-success {
  background: var(--surface-raised);
  border-left-color: var(--ok);
  color: var(--text);
}
.toast-error {
  background: var(--surface-raised);
  border-left-color: var(--err);
  color: var(--text);
}
/* The message wraps instead of being clipped — a publish result is often a full
   sentence and half of it is worse than none. */
.toast-msg {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
}
body[data-theme="light"] .toast-item { border: 1px solid var(--border); border-left-width: 3px; }
@media (max-width: 700px) {
  .toast-container {
    left: var(--space-3);
    right: var(--space-3);
    bottom: var(--space-3);
    width: auto;
    max-width: none;
  }
  .toast-item { width: 100%; }
}
.toast-close {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 1.2rem;
  line-height: 1;
  padding: 0 2px;
  transition: color var(--transition);
}
.toast-close:hover { color: var(--text); }
.toast-exit { animation: toast-out 0.4s ease-in forwards; }

@keyframes toast-in {
  from { opacity: 0; transform: translateX(40px); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes toast-out {
  from { opacity: 1; transform: translateX(0); }
  to   { opacity: 0; transform: translateX(40px); }
}

/* ===== TinyMCE dark mode overrides ===== */
body[data-theme="dark"] .tox.tox-tinymce {
  border: 1px solid rgba(150, 178, 255, 0.18);
  border-radius: 8px;
}
body[data-theme="dark"] .tox .tox-edit-area__iframe {
  background: var(--input-bg);
}
body[data-theme="dark"] .tox .tox-editor-header,
body[data-theme="dark"] .tox .tox-toolbar__primary,
body[data-theme="dark"] .tox .tox-toolbar__overflow,
body[data-theme="dark"] .tox .tox-toolbar__group,
body[data-theme="dark"] .tox:not(.tox-tinymce-inline) .tox-editor-header {
  background: var(--panel-solid) !important;
  border-bottom: 1px solid rgba(150, 178, 255, 0.12) !important;
}
body[data-theme="dark"] .tox .tox-statusbar {
  background: var(--panel-solid) !important;
  border-top: 1px solid rgba(150, 178, 255, 0.12) !important;
  color: var(--muted);
}
body[data-theme="dark"] .tox .tox-statusbar__text-container,
body[data-theme="dark"] .tox .tox-statusbar__path-item,
body[data-theme="dark"] .tox .tox-statusbar__wordcount,
body[data-theme="dark"] .tox .tox-statusbar__branding a {
  color: var(--muted) !important;
}
body[data-theme="dark"] .tox .tox-tbtn {
  color: var(--muted);
}
body[data-theme="dark"] .tox .tox-tbtn svg {
  fill: var(--muted) !important;
}
body[data-theme="dark"] .tox .tox-tbtn:hover {
  background: rgba(150, 178, 255, 0.1) !important;
  color: var(--text);
}
body[data-theme="dark"] .tox .tox-tbtn:hover svg {
  fill: var(--text) !important;
}
body[data-theme="light"] .tox.tox-tinymce {
  border: 1px solid var(--border);
  border-radius: 8px;
}

/* ===== Empty states (docs/08 §9) =====
   Centred column with generous breathing room, capped at a comfortable measure so
   the help sentence does not stretch across a wide table panel. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-2);
  max-width: 380px;
  margin: 0 auto;
  padding: var(--space-7) var(--space-4);
}
.empty-state-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  margin-bottom: var(--space-1);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  color: var(--text-subtle);
  font-size: 1.35rem;
}
/* Fomantic gives icons their own box metrics; centre the glyph in the circle. */
.empty-state-icon i.icon { margin: 0; width: auto; height: auto; line-height: 1; }
.empty-state-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text);
}
.empty-state-text {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-muted);
}
.empty-state-action { margin-top: var(--space-3); }
/* Inline variant for a small panel — a dashboard side card, the "Website roles"
   box on user detail, an API-keys panel. Same component, less air. */
.empty-state--inline {
  padding: var(--space-5) var(--space-3);
  gap: var(--space-1);
}
.empty-state--inline .empty-state-icon {
  width: 40px;
  height: 40px;
  font-size: 1rem;
  margin-bottom: 0;
}
.empty-state--inline .empty-state-title { font-size: 0.88rem; }
.empty-state--inline .empty-state-text { font-size: 0.8rem; }
.empty-state--inline .empty-state-action { margin-top: var(--space-2); }
/* --surface-raised is white in the light theme, so the circle would vanish on a
   white panel; a hairline gives it the same definition light-mode cards get. */
body[data-theme="light"] .empty-state-icon { border: 1px solid var(--border); }

/* Views trend component */
.views-trend { white-space: nowrap; font-size: 0.9em; }
.views-trend.up { color: var(--ok); }
.views-trend.down { color: var(--err); }
.views-trend.muted { color: var(--muted); }

/* ===== Focus (docs/08 §8) =====
   One ring for everything that can be reached from the keyboard. It lives at the
   end of the file so it outranks any component rule that clears the outline, and
   it is :focus-visible only, so a mouse click never leaves a ring behind.
   Fomantic sets `outline: none` on its buttons and dropdowns, which is why they
   are listed by name. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
[contenteditable]:focus-visible,
.ui.button:focus-visible,
.ui.dropdown:focus-visible,
.ui.selection.dropdown:focus-visible,
.ui.checkbox input:focus-visible ~ label::before,
.menu-item:focus-visible,
.nav-bottom-trigger:focus-visible,
.design-tabs button:focus-visible,
.design-tabs a:focus-visible,
.subtabs button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ===== Reduced motion (docs/08 §7) =====
   Nothing here is meaning-carrying animation, so a reader who asks for stillness
   gets it: transitions collapse to an instant swap and looping animations stop
   after the frame they are on. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}
