/* ============================================================================
   Components: buttons, inputs, toolbar, chat input, auth card, lists.
   ========================================================================== */

/* ---- buttons ---------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: var(--control-height);
  padding: 0 var(--space-4);
  border-radius: var(--radius-pill);
  border: var(--border-hairline);
  background: var(--color-surface-raised);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
  user-select: none;
}

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

.btn--primary {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-bg);
}

.btn--primary:hover {
  background: var(--color-accent-hover);
  border-color: var(--color-accent-hover);
  color: var(--color-on-accent);
}

.btn--full {
  width: 100%;
}

.btn--quiet {
  border-color: transparent;
  background: transparent;
  color: var(--color-ink-secondary);
}

.btn--quiet:hover {
  background: var(--color-hover);
  color: var(--color-ink);
}

.btn--danger {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: var(--color-on-accent);
}

.btn--danger:hover {
  background: #99201a;
  border-color: #99201a;
}

.btn[disabled] {
  opacity: 0.5;
  pointer-events: none;
}

.btn--xs {
  height: 1.5rem;
  padding: 0 var(--space-2);
  font-size: var(--text-xs);
  gap: var(--space-1);
}

.icon-btn {
  display: grid;
  place-items: center;
  width: var(--control-height);
  height: var(--control-height);
  border-radius: var(--radius-round);
  color: var(--color-ink-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.icon-btn:hover {
  background: var(--color-hover);
  color: var(--color-ink);
}

/* ---- fields ------------------------------------------------------------------- */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.field__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-ink-secondary);
}

.input {
  height: var(--control-height);
  padding: 0 var(--space-3);
  border: var(--border-hairline);
  /* border-radius: var(--radius-sm); */
  background: var(--color-surface-raised);
  font-size: var(--text-sm);
  transition: border-color var(--dur-fast) var(--ease);
}

.input:hover {
  border-color: var(--color-border-strong);
}

.input:focus-visible {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 3px var(--color-focus-ring);
}

/* ---- toolbar --------------------------------------------------------------------- */

.toolbar__inner {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: var(--space-1);
  height: 100%;
  width: max-content;
  max-width: 100%;
  padding: 0 var(--space-2);
  background: var(--color-surface);
  border: var(--border-hairline);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-1);
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x;
}

.toolbar__inner::-webkit-scrollbar {
  display: none;
}

.toolbar__inner > * {
  flex-shrink: 0;
}

.tool-btn {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: calc(var(--toolbar-height) - var(--space-2) * 2 + 4px);
  height: calc(var(--toolbar-height) - var(--space-2) * 2 + 4px);
  min-width: calc(var(--toolbar-height) - var(--space-2) * 2 + 4px);
  border-radius: var(--radius-pill);
  color: var(--color-ink-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.tool-btn:hover {
  background: var(--color-hover);
  color: var(--color-ink);
}

.tool-btn.is-active {
  background: var(--color-ink);
  color: var(--color-bg);
}

.tool-btn:disabled {
  opacity: 0.35;
  pointer-events: none;
}

.toolbar__sep {
  flex-shrink: 0;
  width: 1px;
  height: 20px;
  margin: 0 var(--space-1);
  background: var(--color-border);
}

/* presence: avatars of other members on the current canvas (realtime) */

.toolbar__presence {
  display: flex;
  align-items: center;
  padding: 0 var(--space-1);
}

.presence-avatar {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  margin-left: -7px;
  border: 2px solid var(--presence-color, var(--color-border));
  border-radius: var(--radius-round);
  background: var(--color-ink);
  color: var(--color-bg);
  font-size: 10px;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  overflow: hidden;
  transition: transform var(--dur-fast) var(--ease);
}

.presence-avatar:first-child {
  margin-left: 0;
}

.presence-avatar:hover {
  transform: translateY(-1px);
  z-index: 1;
}

.presence-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.presence-overflow {
  margin-left: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
}

/* note button slot: expands in place into Add note / View notes */

.toolbar__note {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: var(--space-1);
  max-width: calc(var(--toolbar-height) - var(--space-2) * 2 + 4px);
  overflow: hidden;
  border-radius: var(--radius-pill);
  transition: max-width var(--dur-med) var(--ease), background var(--dur-fast) var(--ease);
}

.toolbar__note.is-expanded {
  max-width: calc((var(--toolbar-height) - var(--space-2) * 2 + 4px) * 3 + var(--space-1) * 2);
  background: var(--color-hover);
}

.toolbar__note .tool-btn {
  flex-shrink: 0;
}

/* name pill under a hovered / focused toolbar button */
.toolbar__tip {
  position: fixed;
  z-index: var(--z-popover);
  padding: 2px 8px;
  background: var(--color-ink);
  color: var(--color-bg);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: 1.4;
  white-space: nowrap;
  pointer-events: none;
  translate: -50% 0;
  opacity: 0;
  box-shadow: var(--shadow-1);
  transition: opacity var(--dur-fast) var(--ease);
}

.toolbar__tip.is-visible {
  opacity: 1;
}

/* ---- chat input --------------------------------------------------------------------- */

.chat {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  height: auto;
  min-height: var(--chat-height);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  background: var(--color-surface);
  border: var(--border-hairline);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-2);
  transition: border-radius var(--dur-fast) var(--ease);
}

/* Multi-line composer: straighter sides, fixed corner radius (not stadium). */
.chat.is-multiline {
  border-radius: var(--radius-lg);
}

.chat__input {
  flex: 1;
  min-width: 0;
  min-height: 0;
  border: none;
  background: transparent;
  font-size: var(--text-sm);
}

.chat__input:focus-visible {
  box-shadow: none;
}

/* collapsed-only open control; hidden when chat column/sheet is open */
.chat__orb-btn {
  display: none;
  place-items: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: var(--radius-round);
  color: var(--color-ink-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.chat__orb-btn:hover {
  background: var(--color-hover);
  color: var(--color-ink);
}

.chat__attach {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: var(--radius-round);
  color: var(--color-ink-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.chat__attach:hover {
  background: var(--color-hover);
  color: var(--color-ink);
}

.chat__mic {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: var(--radius-round);
  color: var(--color-ink-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.chat__mic:hover {
  background: var(--color-hover);
  color: var(--color-ink);
}

.chat__mic.is-recording {
  color: var(--color-danger, #e11d48);
  background: color-mix(in srgb, var(--color-danger, #e11d48) 14%, transparent);
  animation: chat-mic-pulse 1.2s ease-in-out infinite;
}

.chat__mic.is-busy {
  pointer-events: none;
  color: var(--color-ink-secondary);
  cursor: wait;
}

.chat__mic-spinner {
  width: 16px;
  height: 16px;
  border: 2px solid var(--color-border-strong, currentColor);
  border-top-color: var(--color-ink);
  border-radius: 50%;
  animation: chat-mic-spin 0.7s linear infinite;
}

@keyframes chat-mic-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes chat-mic-pulse {
  0%, 100% {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-danger, #e11d48) 35%, transparent);
  }
  50% {
    box-shadow: 0 0 0 6px transparent;
  }
}

.chat__send {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  border-radius: var(--radius-round);
  background: var(--color-ink);
  color: var(--color-bg);
  transition: background var(--dur-fast) var(--ease);
}

.chat__send:hover {
  background: var(--color-accent-hover);
  color: var(--color-on-accent);
}

/* ---- verified-email banner (ui/emailVerifyBanner.js) ------------------------------------ *
 * Sits at --chrome-top, i.e. below the orbs and the toolbar, because the top edge
 * is already occupied by .toolbar. Centred like the toolbar and the toast stack so
 * it reads as part of the same chrome rather than as page content. --z-floating,
 * not --z-toast: it is persistent, and must never cover a transient message.
 * Hidden with the `hidden` attribute, so no display rule may override it. */

.verify-banner {
  position: fixed;
  top: var(--chrome-top);
  left: 50%;
  translate: -50% 0;
  z-index: var(--z-floating);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  max-width: calc(100vw - 2 * var(--space-panel-gap));
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-accent);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-2);
  font-size: var(--text-sm);
}

.verify-banner[hidden] {
  display: none;
}

.verify-banner__icon {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  color: var(--color-accent);
}

/* Truncates rather than wrapping: a pill that grows to two lines stops looking
   like chrome, and the address is decoration here — the buttons carry the task. */
.verify-banner__text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Narrow viewports: the address is the first thing worth losing. */
@media (max-width: 560px) {
  .verify-banner__text strong {
    display: none;
  }
}

/* ---- auth card ------------------------------------------------------------------------- */

.auth-card {
  width: var(--auth-card-width);
  max-width: calc(100vw - 2 * var(--space-panel-gap));
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  background: var(--color-surface);
  border: var(--border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3);
}

.auth-card__brand {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: var(--space-2);
}

.auth-card__logo {
  width: 100%;
  max-width: 220px;
  height: auto;
  display: block;
}

.auth-card__title {
  font-size: var(--text-xl);
  letter-spacing: -0.01em;
}

.auth-card__error {
  font-size: var(--text-sm);
  color: var(--color-danger);
  min-height: 1.2em;
}

.auth-card__notice {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--color-ink-secondary);
}

.auth-card__notice-meta {
  font-size: var(--text-xs);
  line-height: 1.4;
  color: var(--color-ink-secondary);
}

.auth-card__notice-meta:empty {
  display: none;
}

.auth-card__switch {
  font-size: var(--text-sm);
  color: var(--color-ink-secondary);
  text-align: center;
}

.auth-card__switch button {
  color: var(--color-accent);
  font-weight: var(--weight-medium);
}

.divider {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--color-ink-faint);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.divider::before,
.divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--color-border);
}

/* ---- tabs (createTabs: board main tabs + settings/community pill tabs) --------------------- */

.tabs {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
}

.tabs--main {
  border-bottom: var(--border-hairline);
}

.tabs--main .tabs__tab {
  padding: var(--space-2) var(--space-3);
  margin-bottom: -1px;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--color-ink-secondary);
  border-bottom: 2px solid transparent;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.tabs--main .tabs__tab:hover {
  color: var(--color-ink);
}

.tabs--main .tabs__tab.is-active {
  color: var(--color-ink);
  border-bottom-color: var(--color-ink);
}

.tabs--sub {
  flex-wrap: wrap;
}

.tabs--sub .tabs__tab {
  padding: var(--space-1) var(--space-3);
  border: var(--border-hairline);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--color-ink-secondary);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.tabs--sub .tabs__tab:hover {
  background: var(--color-hover);
  color: var(--color-ink);
}

.tabs--sub .tabs__tab.is-active {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-bg);
}

/* ---- settings panel: tab bar + scrolling tab body ----------------------------------------- */

/* Mirrors .panel__body--board: the shell owns scrolling, so the tab strip and
   the sign-out footer pin while only the tab body scrolls. Relies on
   .panel--left.is-expanded being a flex column capped at max-height (layout.css)
   so this chain gets a real height budget instead of clipping under overflow. */
.panel__body--settings {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.settings {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  flex: 1;
  min-height: 0;
}

.settings > .tabs {
  flex-shrink: 0;
}

.settings__body {
  flex: 1;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
}

.settings__footer {
  flex-shrink: 0;
  padding-top: var(--space-2);
  border-top: var(--border-hairline);
}

/* Unread count on a tab label. */
.tabs__count {
  display: inline-grid;
  place-items: center;
  min-width: 16px;
  height: 16px;
  margin-left: var(--space-1);
  padding: 0 4px;
  border-radius: var(--radius-pill);
  background: var(--color-ink);
  color: var(--color-bg);
  font-size: 10px;
  font-weight: var(--weight-semibold);
  line-height: 1;
}

.tabs__tab.is-active .tabs__count {
  background: var(--color-bg);
  color: var(--color-ink);
}

/* ---- panel content bits -------------------------------------------------------------------- */

.section-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-ink-secondary);
  margin-bottom: var(--space-2);
}

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

.stack > div {
  background-color: var(--color-bg);
    padding: 1rem;
    border-radius: var(--radius-lg);
}

/* Grouped fields that read as one control (e.g. new + confirm password). */
.stack--tight {
  gap: var(--space-2);
}

.list {
  display: flex;
  padding-left: var(--space-2);
  flex-direction: column;
}

.list__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0 var(--space-2) var(--space-2);
  font-size: var(--text-sm);
}

.list__item + .list__item {
  border-top: var(--border-hairline);
}

.list__item--tank {
  gap: var(--space-1);
  padding: var(--space-1);
  margin: 0 calc(-1 * var(--space-1));
  border-radius: var(--radius-sm);
}

.list__item--tank.is-active {
  background: var(--color-hover);
}

.list__item--tank.is-active .list__item-name {
  color: var(--color-ink);
  font-weight: var(--weight-medium);
}

.list__item-main {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: auto;
  min-height: var(--control-height);
  padding: var(--space-1) var(--space-2);
  text-align: left;
  border-radius: var(--radius-sm);
}

.list__item-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.list__meta {
  margin-left: auto;
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
  flex-shrink: 0;
}

.avatar {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-round);
  background: var(--color-ink);
  color: var(--color-bg);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  overflow: hidden;
}

.avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.empty-hint {
  font-size: var(--text-sm);
  color: var(--color-ink-faint);
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-5) 0;
}

.empty-state__icon {
  display: grid;
  place-items: center;
  color: var(--color-ink-faint);
}

/* ---- popover (anchored floating layer, appended to <body>) -------------------------------- */

.popover {
  position: fixed;
  z-index: var(--z-popover);
  min-width: 180px;
  max-width: min(320px, calc(100vw - 2 * var(--space-panel-gap)));
  max-height: min(320px, calc(100vh - 2 * var(--space-panel-gap)));
  overflow-y: auto;
  padding: var(--space-1);
  background: var(--color-surface-raised);
  border: var(--border-hairline);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-2);
}

/* subtle cue that more content is below when the menu overflows */
.popover--fade-bottom::after {
  content: "";
  position: sticky;
  bottom: -5px;
  display: block;
  height: 3rem;
  margin-top: calc(-1 * var(--space-5));
  background: linear-gradient(to bottom, transparent, var(--color-surface-raised));
  pointer-events: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
}

.popover .menu__label {
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-ink-faint);
}

.popover .menu__item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  text-align: left;
  font-size: var(--text-sm);
}

.popover .menu__item--danger {
  color: var(--color-danger);
}

.popover .menu__item:hover,
.popover .menu__item:focus-visible {
  background: var(--color-hover);
}

.popover .menu__item-hint {
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
  white-space: nowrap;
}

/* a menu with nothing to offer -- e.g. the blueprint picker inside a shared
   folder the sharer has not put anything in yet */
.popover .menu__empty {
  margin: 0;
  padding: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-ink-faint);
}

/* ---- canvas switcher (floating panel under the toolbar) ------------------------------------ */

.popover--switcher {
  width: 300px;
  max-width: min(300px, calc(100vw - 2 * var(--space-panel-gap)));
  max-height: min(440px, calc(100vh - var(--toolbar-height) - 3 * var(--space-panel-gap)));
  padding: var(--space-2);
}

.canvas-switcher {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.canvas-switcher__bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.canvas-switcher__slot:empty {
  display: none;
}

.canvas-switcher__sections {
  display: flex;
  flex-direction: column;
}

.canvas-switcher__row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding-right: var(--space-1);
  border-radius: var(--radius-sm);
}

.canvas-switcher__row:hover {
  background: var(--color-hover);
}

.canvas-switcher__select {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  font-size: var(--text-sm);
  text-align: left;
}

.canvas-switcher__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.canvas-switcher__dot {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-round);
  background: transparent;
}

.canvas-switcher__row.is-active .canvas-switcher__dot {
  background: var(--color-accent);
}

.canvas-switcher__row.is-active .canvas-switcher__name {
  font-weight: var(--weight-medium);
}

.canvas-switcher__actions {
  display: flex;
  gap: var(--space-1);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

.canvas-switcher__row:hover .canvas-switcher__actions,
.canvas-switcher__actions:focus-within {
  opacity: 1;
}

/* ---- dialog (styled confirm / notice) ------------------------------------------------------ */

.dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: grid;
  place-items: center;
  padding: var(--space-panel-gap);
  background: var(--color-scrim);
  /* solid scrim only — backdrop-filter blurs the full canvas underneath and stalls open */
  animation: dialog-fade var(--dur-fast) var(--ease);
  contain: layout style;
}

.dialog {
  width: 380px;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  background: var(--color-surface);
  border: var(--border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3);
  animation: dialog-rise var(--dur-med) var(--ease);
  contain: layout style;
}

.dialog--wide {
  width: 440px;
}

.dialog__title {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
}

.dialog__body {
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--color-ink-secondary);
}

.dialog__section {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.dialog__section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.dialog__section-label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-ink-secondary);
}

.dialog__plans {
  max-height: min(40vh, 280px);
  overflow: auto;
  margin: 0 calc(-1 * var(--space-1));
  padding: 0 var(--space-1);
}

.dialog__hint {
  font-size: var(--text-xs);
  line-height: var(--leading-normal);
  color: var(--color-ink-secondary);
}

.dialog__hint.is-hidden {
  display: none;
}

.dialog__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.dialog__field {
  margin-top: var(--space-1);
}

@keyframes dialog-fade {
  from {
    opacity: 0;
  }
}

/* ---- upload progress (bottom-right stacked transfer bars) -------------------------------- */

.upload-stack {
  position: fixed;
  right: var(--space-panel-gap);
  bottom: var(--space-panel-gap);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column-reverse;
  align-items: stretch;
  gap: var(--space-2);
  width: min(300px, calc(100vw - 2 * var(--space-panel-gap)));
  max-height: min(50vh, 360px);
  overflow: hidden;
  pointer-events: none;
}

.upload-stack__cancel-all {
  pointer-events: auto;
  align-self: flex-end;
  margin: 0;
  padding: var(--space-1) var(--space-2);
  border: var(--border-hairline);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
  color: var(--color-ink-secondary);
  font-size: var(--text-xs);
  box-shadow: var(--shadow-1);
  cursor: pointer;
}

.upload-stack__cancel-all:hover {
  color: var(--color-ink);
  background: var(--color-hover);
}

.upload-stack__cancel-all.is-hidden {
  display: none;
}

.upload-progress {
  padding: var(--space-3);
  background: var(--color-surface);
  color: var(--color-ink);
  border: var(--border-hairline);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-2);
  font-size: var(--text-sm);
  animation: toast-in var(--dur-med) var(--ease);
  pointer-events: none;
}

.upload-progress__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.upload-progress__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: var(--weight-medium);
}

.upload-progress__meta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex: 0 0 auto;
}

.upload-progress__pct {
  color: var(--color-ink-secondary);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}

.upload-progress__cancel {
  pointer-events: auto;
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-round);
  background: transparent;
  color: var(--color-ink-secondary);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}

.upload-progress__cancel:hover {
  color: var(--color-ink);
  background: var(--color-hover);
}

.upload-progress__cancel.is-hidden {
  display: none;
}

.upload-progress__track {
  height: 4px;
  border-radius: 2px;
  background: var(--color-border);
  overflow: hidden;
}

.upload-progress__bar {
  height: 100%;
  border-radius: inherit;
  background: var(--color-accent);
  transition: width 0.12s linear;
}

.upload-progress--indeterminate .upload-progress__bar {
  width: 35% !important;
  animation: upload-bar-pulse 1s ease-in-out infinite alternate;
}

.upload-progress--done .upload-progress__bar {
  background: var(--color-accent);
}

.upload-progress--error .upload-progress__bar {
  background: var(--color-danger);
  width: 100% !important;
}

.upload-progress--cancelled .upload-progress__bar {
  background: var(--color-ink-faint);
  width: 100% !important;
}

.upload-progress__status {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-ink-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.upload-progress__status.is-hidden {
  display: none;
}

.upload-progress--error .upload-progress__status {
  color: var(--color-danger);
  white-space: normal;
}

.upload-progress--cancelled .upload-progress__status {
  color: var(--color-ink-secondary);
}

.upload-progress.is-leaving {
  opacity: 0;
  translate: 0 4px;
  transition: opacity var(--dur-med) var(--ease), translate var(--dur-med) var(--ease);
}

@keyframes upload-bar-pulse {
  from {
    translate: -40% 0;
  }
  to {
    translate: 180% 0;
  }
}

/* ---- toast (transient status pill above the chat dock) ------------------------------------- */

.toast-stack {
  position: fixed;
  bottom: calc(var(--chat-height) + 2 * var(--space-panel-gap));
  left: 50%;
  translate: -50% 0;
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  pointer-events: none;
}

.toast {
  padding: var(--space-2) var(--space-4);
  background: var(--color-ink);
  color: var(--color-bg);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  box-shadow: var(--shadow-2);
  animation: toast-in var(--dur-med) var(--ease);
}

.toast--sticky {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.toast--sticky::before {
  content: "";
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  border: 2px solid color-mix(in srgb, var(--color-bg) 35%, transparent);
  border-top-color: var(--color-bg);
  border-radius: 50%;
  animation: toast-spin 0.8s linear infinite;
}

@keyframes toast-spin {
  to {
    rotate: 360deg;
  }
}

.toast.is-leaving {
  opacity: 0;
  translate: 0 4px;
  transition: opacity var(--dur-med) var(--ease), translate var(--dur-med) var(--ease);
}

@keyframes toast-in {
  from {
    opacity: 0;
    translate: 0 6px;
  }
}

@keyframes dialog-rise {
  from {
    opacity: 0;
    translate: 0 6px;
  }
}

/* ---- publish dialog (public collections / forms) --------------------------------------------- */

.dialog--publish {
  width: 460px;
}

.publish__toggle {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.publish__toggle input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.publish__toggle-track {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  width: 34px;
  height: 20px;
  padding: 2px;
  border-radius: var(--radius-pill);
  background: var(--color-active);
  transition: background var(--dur-fast) var(--ease);
}

.publish__toggle-thumb {
  width: 16px;
  height: 16px;
  border-radius: var(--radius-round);
  background: var(--color-surface-raised);
  box-shadow: var(--shadow-1);
  transition: translate var(--dur-fast) var(--ease);
}

.publish__toggle input:checked + .publish__toggle-track {
  background: var(--color-accent);
}

.publish__toggle input:checked + .publish__toggle-track .publish__toggle-thumb {
  translate: 14px 0;
}

.publish__toggle input:focus-visible + .publish__toggle-track {
  outline: 2px solid var(--color-focus-ring);
  outline-offset: 1px;
}

.publish__link {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  transition: opacity var(--dur-fast) var(--ease);
}

.publish__link.is-disabled {
  opacity: 0.45;
  pointer-events: none;
}

.publish__url {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.publish__url-part {
  color: var(--color-ink-secondary);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
}

.publish__slug {
  flex: 1;
  min-width: 90px;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}

.publish__slug[readonly] {
  color: var(--color-ink-secondary);
}

.publish__copy {
  align-self: flex-start;
}

.publish__error {
  font-size: var(--text-sm);
  color: var(--color-danger);
}

/* ---- members / invites / tank dialog --------------------------------------------------------- */

.section-label--row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.section-label__actions {
  display: inline-flex;
  gap: var(--space-1);
}

.member__actions {
  display: inline-flex;
  gap: var(--space-1);
  margin-left: var(--space-2);
}

.avatar-btn {
  display: inline-flex;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  border-radius: var(--radius-round);
}

.avatar-btn:hover .avatar {
  outline: 2px solid var(--color-focus-ring);
}

/* ---- settings → profile tab ----------------------------------------------------------------- */

.profile__avatar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Read-only value shown where a field would otherwise sit (e.g. email). */
.profile__static {
  font-size: var(--text-sm);
  color: var(--color-ink-secondary);
}

/* A sign-in method: provider mark, its state, and the button that changes it.
   The label takes the slack so the button stays flush right on any width. */
.profile__identity {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.profile__identity .profile__static {
  flex: 1;
  min-width: 0;
}

/* Account deletion. Separated by a rule and pushed down rather than merely
   coloured red: the point is that it does not read as part of the form above it,
   so nobody arrives at it while tabbing through their display name. */
.profile__danger {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: var(--border-hairline);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: flex-start;
}

/* ---- settings → notifications tab ------------------------------------------------------------ */

/* Activity rows are buttons (the whole row navigates), so the button defaults
   .list__item never had to undo are reset here. */
.notif {
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  padding: var(--space-2) var(--space-2);
  margin: 0 calc(-1 * var(--space-2));
}

.notif:hover {
  background: var(--color-hover);
}

.notif__text {
  flex: 1;
  min-width: 0;
  color: var(--color-ink-secondary);
}

.notif.is-unread .notif__text {
  color: var(--color-ink);
  font-weight: var(--weight-medium);
}

/* Reserved on every row so read/unread rows align. */
.notif__dot {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-round);
  background: transparent;
}

.notif.is-unread .notif__dot {
  background: var(--color-accent);
}

/* Icon stand-in where a row has no actor (e.g. an anonymous form submission). */
.avatar--muted {
  background: var(--color-active);
  color: var(--color-ink-secondary);
}

.notif-prefs__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) 0;
}

.notif-prefs__row + .notif-prefs__row {
  border-top: var(--border-hairline);
}

.notif-prefs__copy {
  flex: 1;
  min-width: 140px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.notif-prefs__name {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.notif-prefs__desc {
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
}

.notif-prefs__check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
  font-size: var(--text-xs);
  color: var(--color-ink-secondary);
  cursor: pointer;
}

.dialog--invite {
  width: 460px;
}

.dialog--join {
  width: 380px;
}

.invite__controls {
  display: flex;
  gap: var(--space-2);
}

.invite__controls .input {
  flex: 1;
  min-width: 0;
}

.invite__controls .btn {
  flex-shrink: 0;
}

.invite__list {
  max-height: 240px;
  overflow-y: auto;
}

.invite__code {
  font-family: var(--font-mono, monospace);
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
}

.invite__actions {
  margin-left: auto;
  display: inline-flex;
  gap: var(--space-1);
}

.invite__row .list__meta {
  margin-left: var(--space-2);
}

/* Sub-label under a field. Pre-existing markup in publishDialog/blueprintEditor
   used this class with no rule behind it; the share dialogs need it too. */
.field__hint {
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
  line-height: 1.4;
}

/* ---- notification action rows ----------------------------------------------------------------- */

/* An actionable row is a div (nested buttons are invalid), so the hover and
   padding .notif gets for free as a button are restated. */
.notif--action {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2);
  cursor: default;
}

.notif--action:hover {
  background: none;
}

.notif__line {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.notif__actions {
  display: flex;
  gap: var(--space-2);
  padding-left: calc(var(--space-2) + 28px);
}

/* Resolved state on an answered action row. */
.notif__state {
  flex-shrink: 0;
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
}

/* ---- share folder dialog ---------------------------------------------------------------------- */

.dialog--share {
  width: 480px;
}

.share__find {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.share__results {
  max-height: 160px;
  overflow-y: auto;
}

/* A result is a button; same reset as .notif. */
.share__result {
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.share__result:hover {
  background: var(--color-hover);
}

.share__grant {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border: var(--border-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-hover);
}

.share__grant-who {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

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

.share__grant-controls .input {
  flex-shrink: 0;
  width: auto;
}

.share__notify {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-ink-secondary);
  cursor: pointer;
  margin-right: auto;
}

.share__existing {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.share__row-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.share__row-name {
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.share__row-actions {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
}

.input--xs {
  padding: 2px var(--space-1);
  font-size: var(--text-xs);
  height: auto;
}

/* ---- share preview dialog --------------------------------------------------------------------- */

.dialog--share-preview {
  width: 460px;
}

.share-preview__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.share-preview__badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-round);
  background: var(--color-active);
  font-size: var(--text-xs);
  color: var(--color-ink-secondary);
}

.share-preview__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.share-preview__list {
  max-height: 220px;
  overflow-y: auto;
}

.share-preview__glyph {
  flex-shrink: 0;
  display: inline-flex;
  color: var(--color-ink-faint);
}

.share-preview__row .list__meta {
  margin-left: auto;
}

.share-preview__dest {
  padding-top: var(--space-2);
  border-top: var(--border-hairline);
}

.dialog--tank {
  width: 420px;
}

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

.tank-form__avatar {
  align-self: center;
}

.avatar--upload {
  width: 56px;
  height: 56px;
  border: var(--border-hairline);
  background: var(--color-surface-raised);
  color: var(--color-ink-secondary);
  font-size: var(--text-lg);
  cursor: pointer;
}

.avatar--upload:hover {
  border-color: var(--color-border-strong);
}

.tank-form textarea.input {
  height: auto;
  padding: var(--space-2) var(--space-3);
  resize: vertical;
}

/* ---- card lightbox (public collection full-card overlay) ------------------------------------- */

.card-lightbox-overlay {
  padding: var(--space-6) var(--space-4);
}

.card-lightbox {
  width: min(1400px, 100%);
  max-height: calc(100vh - 2 * var(--space-6));
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  border: var(--border-hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-3);
  animation: dialog-rise var(--dur-med) var(--ease);
  overflow: hidden;
  contain: layout style;
}

.card-lightbox--view {
  width: min(1100px, 100%);
}

.card-lightbox__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5) 0;
}

.card-lightbox__title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.card-lightbox__title.is-untitled {
  color: var(--color-ink-faint);
  font-weight: var(--weight-regular);
}

.card-lightbox__body {
  padding: var(--space-4) var(--space-5) var(--space-5);
  overflow-y: auto;
}

/* ---- blueprint draft → new-card preview (assistant latents) --------------------------------- */

.blueprint-card-preview__head-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.blueprint-card-preview__eyebrow {
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-ink-faint);
}

.blueprint-card-preview__body {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.blueprint-card-preview__create {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* ---- styled select -------------------------------------------------------------------------- */

.select {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-width: 0;
  text-align: left;
  font-size: var(--text-sm);
  color: var(--color-ink);
}

.select > svg {
  flex-shrink: 0;
  color: var(--color-ink-faint);
}

.select--field {
  height: var(--control-height);
  padding: 0 var(--space-3);
  border: var(--border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-surface-raised);
  transition: border-color var(--dur-fast) var(--ease);
}

.select--field:hover {
  border-color: var(--color-border-strong);
}

.select--field:focus-visible {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 1px var(--color-focus-ring);
  outline: none;
}

.select--cell {
  width: 100%;
  height: 32px;
  padding: 0;
  background: transparent;
  border: none;
}

.select--cell > svg {
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

.select--cell:hover > svg,
.select--cell:focus-visible > svg {
  opacity: 1;
}

.select[disabled] {
  opacity: 0.5;
  pointer-events: none;
}

.select__value {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.select__value.is-placeholder {
  color: var(--color-ink-faint);
}

.select__menu {
  display: flex;
  flex-direction: column;
}

.select__option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  text-align: left;
  font-size: var(--text-sm);
}

.select__option:hover,
.select__option:focus-visible {
  background: var(--color-hover);
  outline: none;
}

.select__option.is-active {
  font-weight: var(--weight-medium);
}

.select__option-label {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.select__option-hint {
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
}

.select__option-check {
  display: grid;
  place-items: center;
  color: var(--color-accent);
}

/* ---- search input ---------------------------------------------------------------------------- */

.search {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
}

.search > svg {
  position: absolute;
  left: var(--space-2);
  color: var(--color-ink-faint);
  pointer-events: none;
}

.search__input {
  width: 100%;
  height: var(--control-height);
  padding: 0 var(--space-6) 0 calc(var(--space-2) + 20px);
  border: var(--border-hairline);
  border-radius: var(--radius-lg);
  background: var(--color-surface-raised);
  font-size: var(--text-sm);
  transition: border-color var(--dur-fast) var(--ease);
  margin-right: var(--space-1);
}

.search__input:hover {
  border-color: var(--color-border-strong);
}

.search__input:focus-visible {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 1px var(--color-focus-ring);
  outline: none;
}

.search__clear {
  position: absolute;
  right: var(--space-1);
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-round);
  color: var(--color-ink-faint);
}

.search__clear:hover {
  background: var(--color-hover);
  color: var(--color-ink);
}

/* ---- admin (/admin route; docs/saas-plan.md §1.5) --------------------------
 *
 * Appended here rather than in an eighth stylesheet: index.html has no bundler
 * and links each file explicitly, so a new file means a new <link> that every
 * page pays for to serve one route. The rules below lean on the existing
 * .list / .tabs / .field / .btn / .chip / .publish__error / .empty-hint
 * vocabulary and only add the page frame.
 *
 * The admin page replaces the workspace. It is NOT a normal scrolling document,
 * even though an earlier version of this comment claimed it was: base.css:28 sets
 * `body { overflow: hidden }` and layout.css:15 pins `#app` to `position: fixed;
 * inset: 0` with no overflow of its own, so a `min-height: 100vh` child here just
 * grew past the viewport and got clipped — every tab was unreachable below the
 * fold. So .admin is a fixed-height flex column that owns its own scroller, the
 * same shape .panel__body--settings / .settings__body use at :544 below. Header
 * and tabs pin; only .admin__body scrolls.
 */

.admin {
  /* The page measure. 940px gave each card in the 2-up .admin__grid about 440px,
     which is too narrow for a chart with a legend and left the Tanks/Services
     rows fighting over space. Widened here rather than per-tab so every band
     stays aligned. */
  --admin-measure: 1280px;

  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  /* Opaque and full-bleed: the canvas is appended unconditionally at
     main.js:120, so without this its dot grid frames the centred column and the
     page reads as another floating panel rather than a replacement surface. */
  background: var(--color-bg);
  /* The scroller is .admin__body. Anything that escapes this box is a bug, and
     clipping it here is how that bug stays visible instead of breaking layout. */
  overflow: hidden;
}

/* Three bands — header, tab strip, scrolling body — that must share one content
 * edge, while only the body scrolls and its scrollbar sits at the viewport edge
 * rather than floating beside a 1280px column.
 *
 * So the gutter is paid twice, in two different places, and the max-widths differ by
 * exactly that gutter: the header and the tab strip are themselves centred and
 * carry their own padding (hence measure + 2 gutters, border-box), whereas the
 * body is full-bleed with the padding on the scroller and the max-width on its
 * children (hence measure alone). Both arrangements put the content edge at the
 * same x. Changing one without the other visibly misaligns the page.
 */
.admin__header,
.admin__tabs {
  width: 100%;
  max-width: calc(var(--admin-measure) + 2 * var(--space-panel-gap));
  margin-inline: auto;
  box-sizing: border-box;
}

.admin__tabs > .tabs,
.admin__body > * {
  width: 100%;
  max-width: var(--admin-measure);
  margin-inline: auto;
}

.admin__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-shrink: 0;
  padding: var(--space-panel-gap) var(--space-panel-gap) var(--space-3);
}

.admin__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-ink);
  font-size: var(--text-md);
  font-weight: 600;
  text-decoration: none;
}

.admin__logo {
  width: 24px;
  height: 24px;
}

/* Seven tabs at the `main` variant overflow below ~700px, and .tabs sets no
   flex-wrap (only .tabs--sub does, :515), so they were silently clipped and
   unreachable. Scroll rather than wrap: wrapping makes the strip two rows tall
   and shifts the whole body down at exactly the width where space is scarcest. */
.admin__tabs {
  flex-shrink: 0;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 0 var(--space-panel-gap);
  border-bottom: var(--border-hairline);
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.admin__tabs::-webkit-scrollbar {
  display: none;
}

/* The strip's own bottom border would double up with the one above. */
.admin__tabs > .tabs--main {
  border-bottom: 0;
}

.admin__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  /* min-height:0 is load-bearing: a flex child's default min-height is auto,
     which refuses to shrink below its content and so never overflows — meaning
     overflow-y would never engage. */
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* both-edges, not plain stable: the gutter is reserved symmetrically, so a
     centred child stays on the same axis as the pinned header above it. With a
     one-sided gutter the body's content drifts left of the header by half a
     scrollbar the moment the page is long enough to scroll — which is always. */
  scrollbar-gutter: stable both-edges;
  padding: var(--space-4) var(--space-panel-gap) var(--space-7);
}

/* Wraps so a row of selects plus a text input degrades on a narrow window
   instead of overflowing. */
.admin__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.admin__controls .input {
  flex: 1 1 180px;
  min-width: 0;
}

/* A <select> has an intrinsic width that already fits its longest option, so
   letting it flex-grow like a text input stretches it to no purpose and steals
   room from the free-text field beside it (the Invites tab's two dropdowns plus
   its label input). */
.admin__controls select.input {
  flex: 0 0 auto;
  width: auto;
}

.admin__controls .search {
  flex: 1 1 240px;
  min-width: 0;
}

.admin__readonly {
  padding: var(--space-3);
  border: var(--border-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
}

/* Monospace and resizable: it is a JSON document, and 24 rows is a guess. */
.admin__json {
  min-height: 320px;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: 1.5;
  resize: vertical;
  white-space: pre;
}

/* ---- billing (Settings -> Billing & Storage; docs/saas-plan.md §2.4) --------
   Was .dialog--billing. The dialog is gone: balance, storage, plans, per-service
   and per-member usage and a paged ledger is a page's worth of content, and the
   modal was already at max-height with a separately scrolling ledger inside it.
   Nothing here sets a width or a max-height any more -- .settings__body owns the
   scrolling (see .panel__body--settings), so a tab that manages its own would
   produce a second scrollbar inside the first.
   -------------------------------------------------------------------------- */

/* The two credit buckets — plan (this month's allowance) and top-up (never
   expire). Each is a self-contained widget: title + value on one line, then the
   meter (or, for top-ups, its rule), then a caption. The old screen showed the
   combined balance as one big number, which hid that the two buckets have
   opposite expiry rules. */
.billing__bucket {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border: var(--border-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-surface-raised);
}

.billing__bucket + .billing__bucket {
  margin-top: var(--space-2);
}

.billing__bucket-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.billing__bucket-title {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.billing__bucket-value {
  margin-left: auto;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  color: var(--color-ink-secondary);
  white-space: nowrap;
}

.billing__bucket-value.is-over {
  color: var(--color-danger);
}

.billing__bucket-meta {
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
  line-height: 1.45;
}

/* The Add-credits control lives with the top-up bucket, not as its own section:
   the presets stay collapsed until asked for, so the credits section reads as
   two meters and a button rather than a price ladder. */
.billing__add {
  margin-top: var(--space-2);
  width: 100%;
}

.billing__topup-note {
  margin-top: var(--space-2);
}

.billing__runway {
  margin-bottom: var(--space-3);
}

.billing__plan {
  align-items: flex-start;
}

.billing__plan-main {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-height: 0;
}

.billing__plan-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  width: 100%;
}

.billing__plan-name {
  color: var(--color-ink);
  font-weight: var(--weight-medium);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.billing__plan-price {
  margin-left: auto;
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.billing__plan-meta {
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
  line-height: 1.45;
}

/* A stated consequence, not a decoration: "out of credit" and "out of storage"
   both mean something has stopped working, so they read at full ink weight
   rather than as secondary hint text. */
.billing__warn {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--color-ink);
}

.billing__error {
  font-size: var(--text-sm);
  color: var(--color-danger);
}

.billing__error[hidden] {
  display: none;
}

.billing__storage {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.billing__bar {
  height: 6px;
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: var(--color-hover);
}

.billing__bar-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
  transition: width var(--dur-fast) var(--ease);
}

.billing__bar-fill.is-over {
  background: var(--color-danger);
}

/* Tabular so a column of amounts lines up on the decimal point. */
.billing__delta {
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--color-ink-secondary);
  white-space: nowrap;
}

.billing__delta.is-credit {
  color: var(--color-accent);
}

/* Wraps at narrow widths rather than shrinking: "$5" and "$50" should stay the
   same size so the row does not read as a price ladder. Hidden until the Add
   credits button asks for them. */
.billing__presets {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-2);
  margin-bottom: var(--space-2);
}

/* ---- usage ---------------------------------------------------------------- */

.billing__range {
  display: flex;
  gap: var(--space-1);
}

.billing__stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.billing__stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-2);
  border: var(--border-hairline);
  border-radius: var(--radius-sm);
  background: var(--color-surface-raised);
}

.billing__stat-val {
  font-size: var(--text-md);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}

.billing__stat-label {
  font-size: var(--text-xs);
  color: var(--color-ink-secondary);
}

/* Inline SVG, sized by CSS and drawn with preserveAspectRatio="none": the bars
   are a shape, not a measurement, so non-uniform scaling is correct here and it
   keeps the markup free of pixel arithmetic. No Chart.js -- /admin loads it
   dynamically for real charts, and ~200 KB to draw N rectangles in a settings
   panel would be absurd. */
.billing__chart {
  display: block;
  width: 100%;
  height: 56px;
  margin-bottom: var(--space-3);
  fill: var(--color-accent);
}

.billing__chart-axis {
  stroke: var(--color-border);
  stroke-width: 0.5;
}

.billing__spark {
  display: block;
  width: 100%;
  height: 28px;
  margin-top: var(--space-2);
  fill: none;
  stroke: var(--color-accent);
  stroke-width: 1;
  /* Non-uniform scaling would otherwise thin the stroke horizontally. */
  vector-effect: non-scaling-stroke;
}

.billing__usage {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-bottom: 2rem;
}

/* Three columns: label, bar, amount. The bar takes the slack so amounts stay
   right-aligned in a column that lines up. */
.billing__usage-row {
  display: grid;
  grid-template-columns: minmax(0, 8em) 1fr auto;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.billing__usage-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--color-ink-secondary);
}

.billing__usage-bar {
  height: 6px;
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: var(--color-hover);
}

.billing__usage-bar i {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
}

.billing__usage-amt {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* An empty balance or an over-quota tank, wherever a chip reports one. */
.chip--danger {
  color: var(--color-danger);
  border-color: var(--color-danger);
}

/* /admin -> Tanks: the per-row credit adjustment form, revealed under its row. */
.admin__adjust {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3) var(--space-3);
  border-bottom: var(--border-hairline);
}

.admin__adjust .input {
  flex: 1 1 200px;
  min-width: 0;
}

/* /admin -> dashboards (docs/saas-plan.md §4.3) --------------------------------
 *
 * Still in this file rather than a new stylesheet, for the reason given at the
 * top of the admin section: index.html has no bundler and links every file
 * explicitly, so an eighth <link> would be paid for on every page to serve one
 * route. Chart.js draws into a canvas and brings no CSS of its own, so there is
 * nothing to vendor here either.
 */

/* The KPI strip. auto-fit rather than a fixed column count so the number of
 * cards can differ by deploy mode — self-hosted renders four fewer, and a fixed
 * grid would leave holes. */
.admin__kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.admin__kpi {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--space-3);
  border: var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.admin__kpi-label {
  font-size: var(--text-xs);
  color: var(--color-ink-secondary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.admin__kpi-value {
  font-size: var(--text-lg);
  /* Tabular figures: these sit in a grid and are read by comparison, so digits
   * shifting width between refreshes makes the column look unstable. */
  font-variant-numeric: tabular-nums;
}

.admin__kpi-note {
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
}

/* Email delivery: a failed count or a stale backlog is the one thing on this page
   that wants the reader to stop. Border rather than a red number, so it reads as a
   state of the card and not as a magnitude. */
.admin__kpi.is-warn {
  border-color: var(--color-danger);
}

.admin__kpi.is-warn .admin__kpi-value {
  color: var(--color-danger);
}

/* Two-up above 720px, stacked below. */
.admin__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.admin__card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border: var(--border-hairline);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  min-width: 0;
}

/* Chart.js sizes to its container, and a flex/grid child has no intrinsic
 * height, so without an explicit one the canvas collapses to zero and the chart
 * silently does not appear. */
.admin__chart {
  position: relative;
  height: 240px;
  min-width: 0;
}

.admin__chart--short {
  height: 120px;
}

.admin__chart canvas {
  width: 100% !important;
  height: 100% !important;
}

/* The range picker. */
.admin__range {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.admin__range-presets {
  display: flex;
  gap: 2px;
}

.admin__range-presets .btn.is-active {
  color: var(--color-ink);
  border-color: var(--color-ink);
}

.admin__range-custom {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.admin__range-custom .input {
  flex: 0 0 auto;
  width: auto;
}

.admin__range-dash,
.admin__range-grain {
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
}

/* A row's trailing cluster of figures and buttons, shared by the Services,
 * Users and Tanks lists. */
.admin__service-figures {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-wrap: wrap;
  justify-content: flex-end;
}

.admin__service-figures .chip {
  font-variant-numeric: tabular-nums;
}

/* A disclosure control that reads as text, not as a button: it sits inside a
 * list item's name and a button chrome there would compete with the row. */
.admin__expand {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
}

.admin__expand:hover {
  color: var(--color-accent);
}

/* Drill-down panels: Tanks -> usage -> run, and Services -> operations. Inset
 * and tinted so nesting is legible two levels deep.
 *
 * Both are SIBLINGS of their .list__item, inside the .list flex column — not
 * children of the row. A `grid-column: 1 / -1` used to sit here, which did nothing
 * (the parents are flex, not grid) and masked the fact that services.js was
 * nesting its panel inside the row and getting a horizontal layout for it. */
.admin__drawer,
.admin__service-detail {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3);
  border-bottom: var(--border-hairline);
  background: var(--color-bg);
}

.admin__service {
  flex-wrap: wrap;
}

/* Inline sparkline in a list row. Fixed size: it is a shape, not a measurement,
 * and letting it flex would make two rows' shapes non-comparable. */
.admin__spark {
  position: relative;
  display: block;
  width: 90px;
  height: 28px;
  flex: 0 0 auto;
}

.admin__spark canvas {
  width: 100% !important;
  height: 100% !important;
}

/* Settings: the form layer above the raw document. */
.admin__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-3);
}

/* A boolean setting. The label wraps the box so the text is a hit target too. */
.field__check {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  font-size: var(--text-sm);
  /* Matches .field__label's spacing, so a checkbox row lines up with the number
     fields it sits beside in the grid. */
  min-height: 1.9em;
}

.field__check > input {
  flex: 0 0 auto;
  margin: 0;
  cursor: pointer;
}

/* .admin__collapse is the neutral name; the other two are the historical ones for
   the price tables and the raw document. Same card, three callers. */
.admin__collapse,
.admin__price,
.admin__raw {
  border: var(--border-hairline);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface);
}

.admin__price + .admin__price {
  margin-top: var(--space-2);
}

.admin__collapse > summary,
.admin__price > summary,
.admin__raw > summary {
  cursor: pointer;
  font-size: var(--text-sm);
}

.admin__collapse[open] > summary,
.admin__price[open] > summary,
.admin__raw[open] > summary {
  margin-bottom: var(--space-2);
}

/* ---- admin: inline row editor (Tags) --------------------------------------- */

/* Expands under the row it edits rather than opening a dialog: editing a tag is
   usually one field, and a modal per tweak makes curating a vocabulary of
   sixty tags unbearable. */
.admin__editor {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0 0 var(--space-2);
  padding: var(--space-3);
  border: var(--border-hairline);
  border-top: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  background: var(--color-surface);
}

.admin__editor .admin__grid {
  margin-bottom: 0;
}

.admin__section {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

.admin__section:first-child {
  margin-top: 0;
}

.admin__chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}

.admin__check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-xs);
  color: var(--color-ink-secondary);
  white-space: nowrap;
}

.admin__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
}

/* ---- admin: moderation queue --------------------------------------------- */

/* A report row is taller than a list row because it carries the reported
 * content itself — the point of the queue is that a decision needs no
 * navigation. So it stacks instead of sitting on one line. */
.admin__report {
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2);
}

.admin__report .admin__actions {
  justify-content: flex-start;
  flex-wrap: wrap;
}

/* Reported text is somebody else's words, quoted. It is escaped on the way in;
 * this only makes it look quoted. */
.admin__quote {
  margin: var(--space-1) 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--color-border);
  font-size: var(--text-sm);
  color: var(--color-ink-secondary);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ---- admin: KPI hierarchy, deltas, bars, skeletons ------------------------- */

/* The KPI strip was one flat auto-fit grid of up to ten identical cards, mixing
 * money (revenue, cost, margin) with counts (calls, runs, signups). Everything
 * had the same weight, so nothing read as the answer. Split in two: three
 * primary cards at display size, the rest compact underneath.
 */
.admin__kpis--primary {
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

.admin__kpis--primary .admin__kpi-value {
  font-size: var(--text-xl);
}

.admin__kpis--secondary {
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}

.admin__kpis--secondary .admin__kpi {
  padding: var(--space-2) var(--space-3);
}

.admin__kpis--secondary .admin__kpi-value {
  font-size: var(--text-md);
}

/* Value and its period-over-period delta share a baseline: the delta is an
   annotation on the number, not a second number. */
.admin__kpi-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Direction is coloured only where direction has a meaning. A metric declared
 * neutral (calls, storage, wallet float) renders faint and uncoloured — painting
 * "calls up 12%" green would assert a judgement the number does not support. */
.admin__delta {
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--color-ink-faint);
}

.admin__delta--good {
  color: var(--color-accent);
}

.admin__delta--bad {
  color: var(--color-danger);
}

/* States the resolved window, grain and comparison basis, so a screenshot of
   this page is self-describing. */
.admin__caption {
  font-size: var(--text-xs);
  color: var(--color-ink-faint);
}

/* Top-N rows: a proportional fill behind the row, so relative magnitude reads at
 * a glance. Ten currency figures in a column require ten comparisons; ten bars
 * require none. Painted as a background layer rather than a child element so it
 * cannot affect the row's own layout. */
.admin__barrow {
  position: relative;
  isolation: isolate;
}

.admin__barrow::before {
  content: "";
  position: absolute;
  inset: 1px 0;
  z-index: -1;
  width: var(--bar, 0%);
  border-radius: var(--radius-sm);
  background: var(--color-hover);
}

/* Tab-switch placeholder. The shell used to blank the body to "Loading…" on
 * every tab click, which flashed on a fast connection and read as a fault. */
.admin__skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.admin__skeleton-bar {
  height: 44px;
  border-radius: var(--radius-sm);
  background: var(--color-hover);
  animation: admin-pulse 1.4s ease-in-out infinite;
}

.admin__skeleton-bar:nth-child(2) {
  animation-delay: 0.15s;
}

.admin__skeleton-bar:nth-child(3) {
  animation-delay: 0.3s;
}

.admin__skeleton-bar--tall {
  height: 180px;
}

@keyframes admin-pulse {
  0%,
  100% {
    opacity: 0.45;
  }
  50% {
    opacity: 0.9;
  }
}

@media (prefers-reduced-motion: reduce) {
  .admin__skeleton-bar {
    animation: none;
  }
}

/* ---- admin: responsive -----------------------------------------------------
 *
 * This section previously had no @media queries at all, so every grid held its
 * desktop column count until its own minmax() floor forced a reflow, and the
 * control rows overflowed rather than stacking.
 */

/* Below the 2-up chart grid's comfortable width, one column. Relying on
   auto-fit's 300px floor alone gave two 300px charts on a 700px screen, which is
   narrower than their own legends. */
@media (max-width: 900px) {
  .admin__grid {
    grid-template-columns: 1fr;
  }

  .admin__kpis--primary,
  .admin__kpis--secondary {
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  }

  .admin__kpis--primary .admin__kpi-value {
    font-size: var(--text-lg);
  }
}

@media (max-width: 600px) {
  .admin__header {
    padding: var(--space-3) var(--space-3) var(--space-2);
  }

  .admin__tabs {
    padding: 0 var(--space-3);
  }

  .admin__body {
    padding: var(--space-3) var(--space-3) var(--space-6);
    gap: var(--space-3);
  }

  /* Full-width stacked controls: a search field and two buttons sharing a
     375px row leaves each too narrow to read its own label. */
  .admin__controls {
    flex-direction: column;
    align-items: stretch;
  }

  .admin__controls .btn,
  .admin__controls .input,
  .admin__controls select.input,
  .admin__controls .search {
    width: 100%;
    flex: 0 0 auto;
  }

  /* A list row's name and its trailing figures stop being a single line. */
  .list__item {
    flex-wrap: wrap;
  }

  .admin__service-figures {
    justify-content: flex-start;
  }

  /* A shape this small is not comparable across rows on a phone, and it costs a
     Chart.js instance per row to draw. */
  .admin__spark {
    display: none;
  }

  .admin__range-custom {
    flex-wrap: wrap;
  }

  .admin__chart {
    height: 200px;
  }
}
