/* =========================================================================
   components.css — reusable UI primitives: surfaces, buttons/forms,
   badges/status/spinner, chips, tables/data, toggle switches, and the
   small utility classes JS-rendered fragments reach for instead of
   inline style="" (blocked by the CSP).

   LOAD ORDER: after tokens.css, base.css, layout.css; before header.css,
   pages.css, legacy.css.
     - The Utilities section's `[hidden] { display: none !important; }`
       must load after layout.css's `.row { display: flex }` — it also
       carries `!important` so it wins unconditionally, but the load order
       is kept correct regardless per the project's ordering convention.
     - `.row > select, .row > input, .row > button, .row > .btn { width:
       auto }` (Controls, below) overrides the `width: 100%` given to
       `input[type=text]` etc. earlier in THIS SAME file — both rules have
       equal specificity (0,1,1), so the override only wins because it is
       physically later in this file. Keep it below the base control rule
       if this file is ever re-ordered.
   ========================================================================= */

/* -------------------------------------------------------------------------
   Surfaces
   ------------------------------------------------------------------------- */
.card {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  margin-top: var(--space-3);
  box-shadow: var(--shadow-card);
}
.card--flush { padding: 0; overflow: hidden; }
.card__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
}
.card__title { font-size: var(--text-md); font-weight: var(--weight-semibold); color: var(--text-heading); }
.card__body { padding: var(--space-5); }

.empty {
  text-align: center; color: var(--text-muted);
  padding: var(--space-10) var(--space-4);
  font-size: var(--text-sm);
}

.note {
  max-width: 560px;
}
.note p { margin: 0 0 var(--space-2); }
.note p:last-child { margin: 0; }

/* -------------------------------------------------------------------------
   Controls — buttons, inputs, forms
   ------------------------------------------------------------------------- */
button, .btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-2);
  height: var(--control-h-md);
  padding: 0 var(--space-5);
  font: inherit; font-size: var(--text-base); font-weight: var(--weight-semibold);
  color: var(--text-on-brand);
  background: var(--brand-fill);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
button:hover, .btn:hover { background: var(--brand-fill-hover); }
button:disabled, .btn:disabled { opacity: .5; cursor: wait; }

.btn--ghost {
  background: transparent; color: var(--text-body);
  border: 1px solid var(--border-default);
}
.btn--ghost:hover { background: var(--surface-raised); border-color: var(--border-strong); }

.btn--danger {
  background: var(--danger-soft); color: var(--danger);
  border: 1px solid var(--danger-soft);
}
.btn--danger:hover { background: var(--danger-soft); border-color: var(--danger); }

.btn--sm { height: var(--control-h-sm); padding: 0 var(--space-3); font-size: var(--text-sm); }

form { display: flex; gap: var(--space-3); flex-wrap: wrap; }

label {
  display: block; font-size: var(--text-sm); color: var(--text-muted);
  margin: var(--space-4) 0 var(--space-1);
}
.field { display: flex; flex-direction: column; }
.field label { margin-top: 0; }

input[type=checkbox], input[type=radio] { accent-color: var(--brand-fill); }
/* a bare checkbox+text label (e.g. admin.html's "Make owner") rather than
   a caption-above-control label — progressive enhancement, degrades to the
   plain block label above in browsers without :has() */
label:has(> input[type=checkbox]) {
  display: inline-flex; align-items: center; gap: var(--space-2);
  margin: var(--space-2) 0; color: var(--text-body); font-size: var(--text-sm);
  cursor: pointer;
}

input[type=text], input[type=email], input[type=password], input[type=number],
input[type=file], select, textarea {
  width: 100%;
  padding: 0 var(--space-4);
  height: var(--control-h-md);
  font: inherit; font-size: var(--text-base);
  color: var(--text-body);
  background: var(--surface-raised);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-md);
  outline: none;
}
textarea { height: auto; padding: var(--space-3) var(--space-4); min-height: 140px; resize: vertical; font-family: inherit; }
input[type=file] { padding: var(--space-2) var(--space-4); }
select { padding: 0 var(--space-3); }

/* `width:100%` above is right for a stacked form (login, invite, change
   password) where every control owns its own line. Inside a horizontal
   `.row` it is wrong -- it makes a county dropdown span the full content
   width next to a 100px button. Controls placed directly in a row size to
   their content instead, and still wrap to full width on narrow screens. */
.row > select, .row > input, .row > button, .row > .btn { width: auto; }
/* Fixed basis, not auto: a <select> sizes to its WIDEST option, and the
   jurisdiction picker holds 64 county names, so `width:auto` let it eat
   ~630px and squeeze the address field -- the primary input -- down to
   394px. Pin it and let .addr-wrap's flex-grow take the remaining space. */
.row > select { flex: 0 0 auto; width: 240px; max-width: 100%; min-width: 0; }

input:focus-visible, select:focus-visible, textarea:focus-visible {
  box-shadow: var(--focus-ring);
  border-color: var(--brand);
}

.form-error { color: var(--danger); font-size: var(--text-sm); margin: var(--space-2) 0 0; }

/* -------------------------------------------------------------------------
   Feedback — badges, status, errors, spinner
   ------------------------------------------------------------------------- */
.badge {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs); font-weight: var(--weight-semibold);
  background: var(--surface-raised); border: 1px solid var(--border-default);
  color: var(--text-muted);
  white-space: nowrap;
}
.badge--ok { background: var(--success-soft); border-color: var(--success-soft); color: var(--success); }
.badge--warn, .badge.yb-src   { background: var(--warning-soft); border-color: var(--warning-soft); color: var(--warning); }
.badge--bad                    { background: var(--danger-soft);  border-color: var(--danger-soft);  color: var(--danger); }
.badge--info                   { background: var(--info-soft);    border-color: var(--info-soft);    color: var(--info); }
.badge--neutral                { background: var(--surface-raised); border-color: var(--border-default); color: var(--text-muted); }
.badge.solar { background: var(--warning-soft); border-color: var(--warning-soft); color: var(--warning); }

.status-line { margin: var(--space-4) 2px; color: var(--text-muted); min-height: 22px; font-size: var(--text-sm); }
.error { color: var(--danger); }

.spinner {
  display: inline-block; width: 16px; height: 16px;
  border: 2px solid var(--border-default); border-top-color: var(--brand);
  border-radius: 50%; animation: spin var(--dur-med) linear infinite;
  animation-duration: 800ms;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; border-top-color: var(--border-default); border-right-color: var(--brand); }
}

/* -------------------------------------------------------------------------
   Chip
   ------------------------------------------------------------------------- */
.chip {
  display: inline-flex; align-items: center; gap: var(--space-1);
  background: var(--surface-raised); border: 1px solid var(--border-default);
  border-radius: var(--radius-pill); padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs); color: var(--text-muted); white-space: nowrap;
}
.chip--credits { color: var(--text-body); font-variant-numeric: tabular-nums; }

/* -------------------------------------------------------------------------
   Data — tables, mono, numerals
   ------------------------------------------------------------------------- */
.tablebox { overflow-x: auto; margin-top: var(--space-3); }
table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
th {
  text-align: left; color: var(--text-muted); font-weight: var(--weight-semibold);
  font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-caps);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border-default);
  white-space: nowrap;
}
td {
  padding: var(--space-3); border-bottom: 1px solid var(--border-subtle);
  vertical-align: top; color: var(--text-body);
}
tr:last-child td { border-bottom: 0; }
tr:hover td { background: var(--surface-raised); }
td a { color: var(--brand-text); }

.mono { font-family: var(--font-mono); font-size: var(--text-xs); overflow-wrap: anywhere; }
td.mono, th.mono, .profile__id .mono, .chip .mono { white-space: nowrap; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
/* `min-width: 0`, NOT `max-width: 1px` -- these sit inside flex rows, where a
   flex item's default `min-width:auto` refuses to shrink below its content and
   defeats the ellipsis. min-width:0 is the fix for that; max-width:1px just
   collapses the element (it made every email in the admin user list invisible). */
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* -------------------------------------------------------------------------
   Toggle (admin.html's per-user switches)
   ------------------------------------------------------------------------- */
.toggle { position: relative; display: inline-flex; align-items: center; width: 40px; height: 22px; }
.toggle input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; margin: 0; cursor: pointer;
}
.toggle span {
  position: absolute; inset: 0; background: var(--surface-raised);
  border: 1px solid var(--border-default); border-radius: var(--radius-pill);
  transition: background var(--dur-fast) var(--ease-out);
}
.toggle span::before {
  content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px;
  border-radius: 50%; background: var(--text-muted);
  transition: transform var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.toggle input:checked + span { background: var(--success-soft); border-color: var(--success); }
.toggle input:checked + span::before { transform: translateX(18px); background: var(--success); }
.toggle input:focus-visible + span { box-shadow: var(--focus-ring); }
.toggle input:disabled + span { opacity: .5; cursor: not-allowed; }

/* -------------------------------------------------------------------------
   Small utilities — for JS-rendered fragments that would otherwise need
   inline style="" (blocked by the CSP). Owning pages should reach for
   these instead of setting .style.* directly.
   ------------------------------------------------------------------------- */
.hidden { display: none !important; }

/* MUST come with `!important`, and must live after every layout rule. The
   browser's built-in `[hidden]{display:none}` lives in the UA stylesheet, which
   loses to ANY author declaration -- so `.row{display:flex}` on a `<div class="row"
   hidden>` silently un-hides it. Every progressive-disclosure panel in the app
   (the invite form, the per-user grant-credits panels, the one-time secret
   reveal) uses the native `hidden` attribute, and without this they all render
   expanded at once. */
[hidden] { display: none !important; }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mt-6 { margin-top: var(--space-6); }
