.input, .textarea, .select { width:100%; padding:10px 12px; background:#fff;
  border:1px solid var(--hair-2); border-radius:var(--r-sm);
  font-family:var(--f-body); font-size:14px; color:var(--ink); outline:none;
  transition:border-color .15s ease, box-shadow .15s ease; }
.input:focus, .textarea:focus, .select:focus { border-color:var(--accent); box-shadow:0 0 0 4px var(--accent-soft); }
.textarea { resize:vertical; min-height:90px; font-family:var(--f-mono); }
/* Strip the native browser dropdown caret and replace it with a
   hand-drawn SVG chevron so every <select class="select"> across
   management + admin shows the same caret in the same spot. The
   `:not([multiple])` carve-out leaves multi-line list pickers (e.g.
   the Discord channel selector) on their default rendering. */
.select:not([multiple]) {
  appearance:none; -webkit-appearance:none; -moz-appearance:none;
  padding-right:36px;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'><path d='M1 1.5L6 6.5L11 1.5' stroke='%231B1814' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat:no-repeat;
  background-position:right 12px center;
}
.chip { display:inline-flex; align-items:center; gap:6px; padding:4px 10px;
  border-radius:999px; background:var(--cream-2); border:1px solid var(--hair); font-size:12px; color:var(--ink-2); }
.chip.ok { background:#E8F0DE; border-color:#B5C99B; color:#3E5A26; }
.chip.warn { background:#FCE8D5; border-color:#E9BC8D; color:#8A4B14; }
.chip.error { background:var(--danger-soft); border-color:#E0A097; color:var(--danger-2); }
.dot { width:8px; height:8px; border-radius:50%; background:var(--muted); display:inline-block; }
.dot.ok { background:var(--ok); box-shadow:0 0 0 3px rgba(62,127,74,.18); }
.dot.warn { background:var(--warn); box-shadow:0 0 0 3px rgba(196,107,26,.18); }
.dot.error { background:var(--danger); box-shadow:0 0 0 3px rgba(178,58,42,.18); }
.toggle { position:relative; display:inline-block; width:34px; height:20px; }
.toggle input { opacity:0; width:0; height:0; }
.toggle span { position:absolute; inset:0; cursor:pointer; background:var(--hair-2); border-radius:999px; transition:background .2s ease; }
.toggle span::before { content:''; position:absolute; left:2px; top:2px; width:16px; height:16px;
  background:#fff; border-radius:50%; transition:transform .2s ease; }
.toggle input:checked + span { background:var(--sage); }
.toggle input:checked + span::before { transform:translateX(14px); }
.indicator { display:none; width:10px; height:10px; border-radius:50%; background:currentColor; margin-left:6px; animation:pulse 1s infinite; }
.htmx-request .indicator, .indicator.htmx-request { display:inline-block; }
@keyframes fadeUp { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:none} }
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.35} }
.fade-up { animation:fadeUp .4s ease both; }

/* Form elements */
.form-group { margin-bottom: 1.25rem; }
.form-group label {
  display: block;
  margin-bottom: 0.35rem;
  font-weight: 500;
  font-size: 0.9rem;
}
.form-group input,
.form-group select,
.form-group textarea {
  width: 100%;
  padding: 0.6rem 0.75rem;
  min-height: 44px;
  border: 1px solid var(--hair-2);
  border-radius: var(--r-sm);
  font-size: 1rem;
  font-family: inherit;
  background: #fff;
  color: var(--ink);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.form-group input:focus-visible,
.form-group select:focus-visible,
.form-group textarea:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.form-group input[type="color"] {
  padding: 0.25rem;
  height: 44px;
  cursor: pointer;
}
/* Input width utilities — replaces inline style="max-width:160px" etc. */
.w-input-xs { max-width:120px; }
.w-input-sm { max-width:160px; }
.w-input-md { max-width:200px; }
.w-input-lg { max-width:260px; }

/* Inputs that fail HTML5 validity (min, max, step, required, …) get
   a danger border so the operator sees the value is unacceptable
   before they tab away. Scoped to .input.cell-save so we don't tag
   every form field the moment it's empty — :invalid would otherwise
   light up cells the user just hasn't filled in yet. */
.input.cell-save:invalid {
  border-color:var(--danger);
  box-shadow:0 0 0 4px var(--danger-soft);
}
/* Chip-input — Alpine component used to edit list-of-strings fields
   (catch-phrases, off-topics, handoff conditions). The visible UI is a
   row of removable chips plus an inline text input; a hidden textarea
   carries the newline-joined value so the existing form handler keeps
   working. */
.chip-input { background:#fff; border:1px solid var(--hair-2);
  border-radius:var(--r-sm); padding:8px; transition:border-color .15s ease, box-shadow .15s ease; }
.chip-input:focus-within { border-color:var(--accent);
  box-shadow:0 0 0 4px var(--accent-soft); }
.chip-input .chips { display:flex; flex-wrap:wrap; gap:6px; }
.chip-input .chips:not(:empty) { margin-bottom:6px; }
.chip-input .chips .pill { display:inline-flex; align-items:center; gap:6px;
  padding:4px 6px 4px 10px; background:var(--cream-2);
  border:1px solid var(--hair); border-radius:999px;
  font-size:13px; color:var(--ink-2); }
.chip-input .chips .pill button { background:none; border:none;
  color:var(--muted); cursor:pointer; padding:0 4px;
  font-size:14px; line-height:1; border-radius:50%; }
.chip-input .chips .pill button:hover { background:rgba(27,24,20,.08); color:var(--ink); }
.chip-input .draft { width:100%; border:none; outline:none;
  padding:6px 4px; font-family:var(--f-body); font-size:14px;
  color:var(--ink); background:transparent; }
.chip-input .draft::placeholder { color:var(--muted); }
.chip-input .hint { color:var(--muted); font-size:11px;
  margin-top:6px; padding-left:4px; }

/* JSON-validation feedback for textareas. The error chip lives below
   the textarea and shows the parser message inline. */
.json-field { position:relative; }
.json-field .json-error { color:var(--danger-2); font-size:12px;
  margin-top:6px; font-family:var(--f-mono); white-space:pre-wrap;
  word-break:break-word; }
.json-field textarea.invalid { border-color:var(--danger);
  box-shadow:0 0 0 4px var(--danger-soft); }
