/* ============================================================================
   21 · ASYNC FEEDBACK, FORMS & MARKDOWN EDITOR

   `az-*` — the visible half of runAction() / withBusy() / toast().
   `fm-*` — the Forms module page. `mde-*` — the Discord-markdown composer.

   Part of the dashboard stylesheet, split out of the former single style.css.
   LOAD ORDER IS LOAD-BEARING (later parts intentionally override earlier
   ones) — the canonical order is dashboard/views/partials/_app_styles.ejs.
   ========================================================================== */

/* ═══════════════════════════════════════════════════════════════════════════
   ASYNC ACTION FEEDBACK  —  `az-*`
   ═══════════════════════════════════════════════════════════════════════════
   The visible half of `runAction()` / `withBusy()` / `toast()` in components.js.
   Every asynchronous action in the dashboard (saving settings, deploying a panel
   to Discord, creating a channel, rendering a transcript, …) reports through
   these three surfaces, so "did that work?" is answered the same way everywhere:

     1. a BUTTON goes busy in place — its own spinner, its own label, disabled;
     2. a REGION dims behind a blurred overlay when the work owns more than one
        control (a modal body, a settings panel);
     3. a TOAST states the outcome, success or failure, and says why on failure.

   All three are built from the existing tokens (--crimson / --pistachio / --red,
   --ease, --spring) so they read as the same product as the rest of the theme,
   and all three stand down under prefers-reduced-motion.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. Button busy state ───────────────────────────────────────────────────
   Sized in `em`, so one rule serves .btn, .btn-sm and .btn-xs at the right
   scale. The spinner is a pseudo-free real element (.az-spin) because a
   ::before would be destroyed by the innerHTML swap that sets the label. */
.az-spin {
  display: inline-block;
  width: 1em; height: 1em;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: az-spin-kf .6s linear infinite;
  flex: none;
  vertical-align: -.125em;
}
.az-spin--lg { width: 1.9rem; height: 1.9rem; border-width: 2.5px; }
@keyframes az-spin-kf { to { transform: rotate(360deg); } }

/* `cursor:progress` beats .btn:disabled's not-allowed — a busy control is
   working, not refusing. `pointer-events` stays live so the cursor shows. */
.btn.is-busy, .is-busy {
  cursor: progress !important;
  opacity: .78;
}
.btn.is-busy:active { transform: none; }

/* A just-succeeded button flashes its confirmation in the pistachio accent
   before the original label comes back. */
.btn.is-done {
  border-color: rgba(var(--green-rgb), .45) !important;
  color: var(--pistachio-light) !important;
}
.btn.is-failed {
  border-color: rgba(var(--red-rgb), .5) !important;
  color: #FCA5A5 !important;
  animation: az-shake .34s var(--ease);
}
@keyframes az-shake {
  10%, 90% { transform: translateX(-1.5px); }
  30%, 70% { transform: translateX(2.5px); }
  50%      { transform: translateX(-2.5px); }
}

/* ── 2. Region overlay ──────────────────────────────────────────────────────
   Covers a panel/modal body while a multi-control action runs. The host is
   given `position:relative` by withBusy() when it is otherwise static. */
.az-busy {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .75rem;
  border-radius: inherit;
  background: rgba(10, 10, 15, .58);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  animation: az-fade .18s var(--ease);
  cursor: progress;
}
.az-busy__label {
  font-size: .84rem;
  font-weight: 600;
  color: var(--text-primary);
  letter-spacing: .01em;
  text-shadow: 0 1px 8px rgba(0,0,0,.6);
}
.az-busy__sub { font-size: .76rem; color: var(--text-muted); margin-top: -.45rem; }
.az-busy .az-spin { color: var(--crimson-glow); }
@keyframes az-fade { from { opacity: 0; } to { opacity: 1; } }

/* ── 3. Toast stack ─────────────────────────────────────────────────────────
   Bottom-centre, newest at the bottom, several at once. The container is
   pointer-events:none so it never blocks the page it is reporting on; each
   toast re-enables them for its own dismiss button. */
.az-toasts {
  position: fixed;
  left: 50%;
  bottom: 1.25rem;
  transform: translateX(-50%);
  z-index: 10050;
  display: flex;
  flex-direction: column;
  gap: .5rem;
  align-items: center;
  pointer-events: none;
  width: min(92vw, 27rem);
}
.az-toast {
  pointer-events: auto;
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: .6rem;
  width: 100%;
  padding: .7rem .8rem .7rem .85rem;
  border-radius: .7rem;
  background: var(--glass-strong);
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  border: 1px solid var(--border-strong);
  box-shadow: 0 16px 40px -12px rgba(0,0,0,.7), inset 0 1px 0 rgba(255,255,255,.05);
  color: var(--text-primary);
  font-size: .84rem;
  line-height: 1.4;
  overflow: hidden;
  animation: az-toast-in .34s var(--spring);
}
.az-toast.az-out { animation: az-toast-out .22s var(--ease) forwards; }

@keyframes az-toast-in  { from { opacity: 0; transform: translateY(14px) scale(.97); } }
@keyframes az-toast-out { to   { opacity: 0; transform: translateY(8px)  scale(.98); } }

/* The accent rail is the whole colour story — one variable per kind. */
.az-toast::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--az-accent, var(--crimson));
}
.az-toast__icon {
  flex: none;
  display: grid; place-items: center;
  width: 1.35rem; height: 1.35rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--az-accent, var(--crimson)) 18%, transparent);
  color: var(--az-accent, var(--crimson));
  font-size: .95rem;
}
.az-toast__body  { flex: 1 1 auto; min-width: 0; }
.az-toast__title { font-weight: 700; }
.az-toast__msg   { color: var(--text); word-break: break-word; }
.az-toast__title + .az-toast__msg { margin-top: .1rem; font-size: .8rem; }
.az-toast__x {
  flex: none;
  background: none; border: 0; padding: 0 .1rem;
  color: var(--text-muted); font-size: 1.05rem; line-height: 1;
  cursor: pointer; border-radius: .3rem;
}
.az-toast__x:hover { color: var(--text-primary); }
.az-toast__x:focus-visible { outline: none; box-shadow: var(--ring-blue); }

/* Time-remaining hairline — the toast shows its own dismissal coming. */
.az-toast__bar {
  position: absolute;
  left: 0; bottom: 0; height: 2px;
  width: 100%;
  transform-origin: left;
  background: var(--az-accent, var(--crimson));
  opacity: .5;
  animation: az-toast-bar linear forwards;
}
@keyframes az-toast-bar { from { transform: scaleX(1); } to { transform: scaleX(0); } }

.az-toast--ok   { --az-accent: var(--pistachio); }
.az-toast--err  { --az-accent: var(--red); }
.az-toast--warn { --az-accent: var(--yellow); }
.az-toast--info { --az-accent: var(--crimson-glow); }

/* ── Inline status line (savebars, small forms) ─────────────────────────── */
.az-status { display: inline-flex; align-items: center; gap: .4rem; }
.az-status--ok   { color: var(--pistachio); }
.az-status--err  { color: #FCA5A5; }
.az-status--busy { color: var(--text-muted); }

@media (max-width: 560px) {
  .az-toasts { bottom: .75rem; width: 94vw; }
}

@media (prefers-reduced-motion: reduce) {
  .az-spin { animation-duration: 1.4s; }
  .az-toast, .az-busy { animation: none; }
  .az-toast.az-out { animation: none; opacity: 0; }
  .az-toast__bar { animation: none; }
  .btn.is-failed { animation: none; }
}

/* ─────────────────────────────────────────────────────────────────────────────
   FORMS — `fm-*`
   The Forms module's own page. It deliberately reuses the `tk-*` vocabulary for
   everything the two pages genuinely share (tabs, cards, chips, filters, pager,
   empty states) — those rules are unscoped and were always general, and forking
   them per page is how two screens in one product start looking like two products.
   Only what is NEW here gets an `fm-` rule.
   ───────────────────────────────────────────────────────────────────────────── */

/* A response's answers: a definition list, because that is what they are — the
   question and what was said. Two columns on desktop so a long form stays
   scannable, one column on narrow screens where the label would be squeezed. */
.fm-answers { display: grid; grid-template-columns: minmax(120px, 34%) 1fr;
  gap: .3rem .8rem; margin: .7rem 0 0; font-size: .88rem; }
.fm-answers dt { color: var(--text-muted); font-weight: 600; }
.fm-answers dd { margin: 0; color: var(--text); word-break: break-word; }
@media (max-width: 640px) {
  .fm-answers { grid-template-columns: 1fr; gap: .1rem .8rem; }
  .fm-answers dd { margin-bottom: .45rem; }
}

/* A bounded, scrollable checklist. Used for "forms on this panel" and "ping these
   roles" — both lists whose length is the SERVER's choice, so the height is capped
   here rather than letting a 60-role server push the modal's buttons off screen. */
.fm-checklist { display: flex; flex-direction: column; gap: .15rem;
  max-height: 220px; overflow-y: auto; padding: .45rem .55rem;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg-input, rgba(0,0,0,.16)); }
.fm-check { display: flex; align-items: center; gap: .5rem; padding: .22rem .1rem;
  font-size: .88rem; cursor: pointer; border-radius: 5px; }
.fm-check:hover { background: rgba(255,255,255,.04); }
.fm-check input { accent-color: var(--primary); flex: 0 0 auto; }

/* The overview's status roll-up: the same chip, one size up, because it is the
   headline on that panel rather than metadata under a card. */
.tk-chips--lg .tk-chip { font-size: .92rem; padding: .4rem .7rem; }

/* ═══════════════════════════════════════════════════════════════════════════════
   `mde-*` — THE DISCORD-MARKDOWN COMPOSER
   ═══════════════════════════════════════════════════════════════════════════════
   Styles for dashboard/client/js/markdownEditor.js: a toolbar, a live preview and
   the emoji trigger, wrapped around a plain <textarea> that stays the value source.

   Deliberately NOT scoped under a page class — the same call embedBuilder's `wz-*`
   rules make. The composer mounts inside a ticket modal today and will mount
   somewhere else tomorrow, and a rule that only worked in one of them would be a
   second composer waiting to happen.
   ───────────────────────────────────────────────────────────────────────────── */

.mde {
  border: 1px solid var(--border);
  border-radius: 0.75rem;
  background: rgba(255,255,255,.035);
  overflow: hidden;
  transition: border-color .15s, box-shadow .15s;
}
.mde:focus-within { border-color: var(--crimson); box-shadow: 0 0 0 3px rgba(var(--blue-rgb),0.16); }

/* The bar wraps rather than scrolls: a hidden Bold button on a narrow modal is
   worse than two rows of them. */
.mde-toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .15rem;
  padding: .35rem .4rem;
  border-bottom: 1px solid var(--border);
  background: rgba(0,0,0,.18);
}
.mde-group { display: flex; align-items: center; gap: .1rem; }
.mde-sep { width: 1px; height: 18px; margin: 0 .25rem; background: var(--border); }
/* Pushes the emoji + preview controls to the far end — they are not formatting. */
.mde-tail { margin-left: auto; gap: .25rem; }

.mde-btn {
  min-width: 1.85rem; height: 1.85rem; padding: 0 .35rem;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .8rem; font-weight: 700; line-height: 1;
  color: var(--text-muted);
  background: none; border: 1px solid transparent; border-radius: 7px;
  cursor: pointer;
  transition: background .12s, color .12s, border-color .12s;
}
.mde-btn:hover { background: var(--surface-hover); color: var(--text-primary); border-color: var(--border); }
.mde-btn--wide { min-width: auto; padding: 0 .6rem; font-size: .72rem; letter-spacing: .02em; }
.mde-btn--wide.is-on { background: var(--blurple); color: #fff; border-color: var(--blurple); }
/* The emoji picker mounts its trigger inside a wrapper of its own, so the class is
   carried by that button and the wrapper needs no layout of its own. */
.mde-btn--emoji { font-size: 1rem; font-weight: 400; }

.mde-body { position: relative; }
/* The textarea keeps its own `.input` sizing from the page, minus the frame the
   wrapper now draws. `resize: vertical` is the point of a composer for long text. */
.mde-input.input {
  border: none; border-radius: 0; background: transparent; box-shadow: none;
  resize: vertical; min-height: 8.5rem; display: block;
  font-family: inherit; line-height: 1.55;
}
.mde-input.input:focus { border: none; box-shadow: none; background: transparent; }

.mde-foot {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  padding: .3rem .5rem; border-top: 1px solid var(--border);
  background: rgba(0,0,0,.12);
}
.mde-tokens { display: flex; align-items: center; gap: .25rem; flex-wrap: wrap; }
.mde-tokens-label { font-size: .7rem; color: var(--text-muted); }
.mde-token {
  padding: .1rem .4rem; font-size: .7rem; font-family: var(--font-mono);
  color: var(--text-secondary); background: rgba(255,255,255,.05);
  border: 1px solid var(--border); border-radius: 5px; cursor: pointer;
  transition: background .12s, color .12s;
}
.mde-token:hover { background: var(--blurple); color: #fff; border-color: var(--blurple); }
.mde-count { margin-left: auto; font-size: .7rem; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.mde-count.is-near { color: var(--warning, #F59E0B); }

/* ── The preview ─────────────────────────────────────────────────────────────
   Sits under the box rather than beside it: these fields live inside a modal that
   is already narrow, and a side-by-side split would leave both halves too thin to
   judge line breaks in — which is most of what this preview is FOR. */
.mde-preview {
  padding: .7rem .85rem;
  border-top: 1px dashed var(--border-strong);
  background: rgba(0,0,0,.22);
  font-size: .875rem; line-height: 1.45; color: var(--text-primary);
  max-height: 320px; overflow-y: auto;
  word-break: break-word;
}
.mde-preview-empty, .mde-preview-note { margin: 0; font-size: .75rem; color: var(--text-muted); }
.mde-preview-note { margin-top: .6rem; padding-top: .5rem; border-top: 1px solid var(--border); }

.mde-line { margin: 0; }
.mde-blank { height: .65rem; }
.mde-h { font-weight: 700; line-height: 1.3; margin: .35rem 0 .15rem; }
.mde-h1 { font-size: 1.3rem; }
.mde-h2 { font-size: 1.12rem; }
.mde-h3 { font-size: 1rem; }
.mde-subtext { font-size: .78rem; color: var(--text-muted); }
.mde-list { margin: .1rem 0 .1rem 1.15rem; padding: 0; }
.mde-list li { margin: .05rem 0; }
.mde-quote {
  margin: .15rem 0; padding: .05rem 0 .05rem .7rem;
  border-left: 4px solid var(--border-strong); border-radius: 2px;
}
.mde-code {
  padding: .1rem .3rem; font-family: var(--font-mono); font-size: .82em;
  background: rgba(0,0,0,.45); border: 1px solid var(--border); border-radius: 4px;
}
.mde-codeblock {
  margin: .3rem 0; padding: .5rem .65rem;
  font-family: var(--font-mono); font-size: .8rem; line-height: 1.4;
  background: rgba(0,0,0,.45); border: 1px solid var(--border); border-radius: 6px;
  white-space: pre-wrap; word-break: break-word; overflow-x: auto;
}
.mde-codeblock code { font: inherit; background: none; border: none; padding: 0; }
.mde-link { color: #00A8FC; cursor: help; }
.mde-mention {
  padding: 0 .15rem; border-radius: 3px; font-weight: 500;
  color: #C9CDFB; background: rgba(88,101,242,.3);
}
.mde-emoji { width: 1.375em; height: 1.375em; object-fit: contain; vertical-align: -0.3em; }
/* Discord hides a spoiler until it is clicked; the preview shows the same blackout
   and reveals on hover, because the point here is "is it marked", not the secret. */
.mde-spoiler { background: #232428; color: transparent; border-radius: 4px; }
.mde-spoiler:hover { background: rgba(255,255,255,.1); color: inherit; }

/* The emoji popup's search box + its empty state (insert mode only). */
.emoji-popup-search { padding: 6px 6px 0; }
.emoji-popup-search-input {
  width: 100%; padding: .35rem .5rem; font-size: .8rem;
  color: var(--text-primary); background: rgba(0,0,0,.3);
  border: 1px solid var(--border); border-radius: 7px; outline: none;
}
.emoji-popup-search-input:focus { border-color: var(--blurple); }
.emoji-popup-empty { grid-column: 1 / -1; margin: .5rem 0; text-align: center;
  font-size: .78rem; color: var(--text-muted); }
