/* ============================================================================
   16 · WELCOME / GOODBYE DESIGNER

   `wz-*` — the page shell: numbered step panels, form atoms, token chips,
   auto-roles picker, repeatable rows, the sticky preview column and save bar.
   `wd-*` — the layered canvas designer inside the "Image" style.

   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.
   ========================================================================== */

/* ============================================================================
   WELCOME / GOODBYE DESIGNER  (.wz-* page shell, .wd-* canvas designer)
   Numbered step panels + a sticky Discord preview + a layered image designer.
   Scoped to this page and built only from the shared tokens, so it inherits the
   app theme rather than restating it.
   ========================================================================== */

.wz-eyebrow {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  color: var(--crimson-glow);
  margin-bottom: 0.375rem;
}

.wz-title-controls {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

/* ── hero header: the Welcome/Goodbye toggle IS the page title ──────────────
   Top row keeps only the Enabled switch (+ the injected Commands button); the
   toggle sits centred as the heading with the subtitle under it. */
.wz-hero-top {
  justify-content: flex-end;
  align-items: center;
  margin-bottom: 0.25rem;
}
.wz-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin: 0.25rem 0 0.875rem;
}
.wz-hero-title { margin: 0; }
.wz-hero .wz-title-controls { justify-content: center; }
.wz-hero .wz-seg { padding: 4px; border-radius: 12px; }
.wz-hero .wz-seg label { font-size: 1.0625rem; }
.wz-hero .wz-seg span { padding: 0.5rem 1.5rem; border-radius: 9px; }
.wz-hero .page-subtitle { margin-top: 0.625rem; max-width: 44rem; }

.wz-lock {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1rem;
  margin-bottom: 1.25rem;
  background: rgba(239, 68, 68, 0.08);
  border: 1px solid rgba(239, 68, 68, 0.25);
  border-radius: 8px;
  font-size: 0.8125rem;
  color: var(--text-muted);
}

/* ── segmented control ───────────────────────────────────────────────────── */
.wz-seg {
  display: inline-flex;
  padding: 3px;
  background: var(--bg-tertiary);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.wz-seg label {
  position: relative;
  cursor: pointer;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--text-muted);
}
.wz-seg input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.wz-seg span {
  display: block;
  padding: 0.4375rem 0.875rem;
  border-radius: 7px;
  transition: background 0.15s, color 0.15s;
}
.wz-seg label.active span {
  background: var(--crimson);
  color: #fff;
  box-shadow: 0 2px 10px rgba(220, 38, 38, 0.3);
}

/* ── switch ──────────────────────────────────────────────────────────────── */
.wz-switch {
  width: 38px;
  height: 22px;
  flex: none;
  border-radius: 999px;
  background: var(--surface-hover);
  border: 1px solid var(--border-strong);
  position: relative;
  cursor: pointer;
  transition: background 0.18s, border-color 0.18s;
}
.wz-switch i {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--text-muted);
  transition: transform 0.18s, background 0.18s;
}
.wz-switch.on { background: rgba(220, 38, 38, 0.25); border-color: var(--crimson); }
.wz-switch.on i { transform: translateX(16px); background: var(--crimson-glow); }

.wz-enabled { display: flex; align-items: center; gap: 0.5rem; font-size: 0.8125rem; }

.wz-switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.875rem 0;
  border-top: 1px solid var(--border);
  margin-top: 0.875rem;
}
.wz-switch-row b { font-size: 0.8125rem; color: var(--text-primary); font-weight: 600; }
.wz-switch-row small {
  display: block;
  margin-top: 0.1875rem;
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--text-muted);
}

/* ── layout ──────────────────────────────────────────────────────────────── */
.wz-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(340px, 0.85fr);
  gap: 1.25rem;
  align-items: start;
  padding-bottom: 1.5rem;
}
.wz-builder { display: grid; gap: 1.25rem; min-width: 0; }

.wz-panel {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1.5rem;
}
.wz-panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.wz-panel-title { display: flex; gap: 0.75rem; }
.wz-panel-title h2 { font-size: 0.9375rem; margin: 0 0 0.1875rem; color: var(--text-primary); }
.wz-panel-title p  { font-size: 0.75rem; margin: 0; color: var(--text-muted); max-width: 46ch; text-wrap: pretty; }
.wz-num {
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 8px;
  display: grid;
  place-items: center;
  background: rgba(220, 38, 38, 0.15);
  border: 1px solid rgba(220, 38, 38, 0.35);
  color: var(--crimson-glow);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
}
.wz-panel-body { margin-top: 0.5rem; }

/* ── form atoms ──────────────────────────────────────────────────────────── */
.wz-label,
.wd-label {
  display: block;
  margin: 1rem 0 0.375rem;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.09em;
  color: var(--text-muted);
}
.wz-label small, .wd-label small { opacity: 0.6; letter-spacing: 0; }

/* Help tooltips ride inline after a field label, a switch title or a panel heading. */
.wz-label .tooltip-wrap,
.wd-label .tooltip-wrap,
.wz-switch-row .tooltip-wrap,
.wz-panel-title .tooltip-wrap,
.wz-preview-head .tooltip-wrap,
.wd-props-head .tooltip-wrap,
.wd-check .tooltip-wrap { margin-left: 0.35rem; }

.wz-row,
.wd-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.wz-row > div, .wd-row > div { min-width: 0; }

.wz-textarea, .wd-textarea { min-height: 78px; resize: vertical; line-height: 1.55; }

.wz-hint, .wd-hint {
  margin: 0.5rem 0 0;
  font-size: 0.75rem;
  color: var(--text-muted);
  line-height: 1.5;
}
.wz-hint code {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  background: var(--bg-tertiary);
  border-radius: 4px;
  padding: 1px 5px;
}

/* "Available variables" chip strip — every {placeholder}, shown under the
   welcome/goodbye text fields. A chip writes its token into the field at the caret
   (falling back to copying it when the strip names no field and nothing was focused).
   Rendered by tokenChips() in pages/welcome.js; the behaviour is a delegated listener
   on the builder root. `.copied` is the acknowledgement flash for BOTH outcomes. */
.wz-tokens {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  margin: 0.5rem 0 0;
}
.wz-tokens-label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-right: 0.125rem;
}
.wz-token-chip {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  line-height: 1.5;
  color: var(--text-primary);
  background: var(--bg-tertiary);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 2px 7px;
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s, transform .12s;
}
.wz-token-chip:hover {
  border-color: var(--crimson);
  color: #fff;
  background: rgba(220, 38, 38, .14);
}
/* A PICKER chip. It does not write a literal the way every other chip does — it opens a
   channel picker and inserts whatever comes back — so it wears Discord's own mention
   blurple (the colour of the pill it is about to produce) and a trailing caret, the
   standard "this opens something" cue. Same size and shape otherwise: it belongs to the
   same strip and must not read as a different kind of control. */
.wz-token-chip--pick {
  border-color: rgba(88, 101, 242, .45);
  background: rgba(88, 101, 242, .14);
  color: #c9cdfb;
}
.wz-token-chip--pick::after {
  content: '▾';
  margin-left: 0.3em;
  opacity: .7;
}
.wz-token-chip--pick:hover {
  border-color: rgba(88, 101, 242, .85);
  background: rgba(88, 101, 242, .26);
  color: #fff;
}
/* The picker is open — the chip is latched against a second click stacking a second
   dialog, so it must not keep offering itself as clickable. */
.wz-token-chip--pick[data-picking] {
  opacity: .6;
  cursor: default;
}
.wz-token-chip:active { transform: translateY(1px); }
.wz-token-chip:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px rgba(220, 38, 38, .45);
}
.wz-token-chip.copied {
  border-color: rgba(var(--green-rgb), .55);
  background: rgba(var(--green-rgb), .16);
  color: var(--text-primary);
}
/* The write was refused (the field is already at its maxlength). Deliberately NOT the
   success green — a green flash for a click that changed nothing is worse than none. */
.wz-token-chip.refused {
  border-color: rgba(245, 158, 11, .55);   /* --yellow, the page's own warn tone */
  background: rgba(245, 158, 11, .16);
  color: var(--text-primary);
}
@media (prefers-reduced-motion: reduce) {
  .wz-token-chip { transition: none; }
}

.wz-color, .wd-color {
  display: flex;
  height: 38px;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg-tertiary);
}
.wz-color input[type="color"], .wd-color input[type="color"] {
  width: 40px;
  border: 0;
  padding: 3px;
  background: none;
  cursor: pointer;
}
.wz-color input[type="text"], .wd-color input[type="text"] {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: none;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  padding: 0 0.5rem;
}

.wz-upload-field { display: flex; gap: 0.375rem; }
.wz-upload-field .input { flex: 1; min-width: 0; }

/* What the picture measures, and what Discord will scale it to. Filled in by
   bindEmbedFields once the browser has loaded it; `--wait` is the moment before that
   and `--warn` is a link that did not load at all. Tabular figures so the numbers do
   not jitter sideways while a size is being re-measured. */
.wz-img-size {
  margin-top: 0.375rem;
  font-size: 0.6875rem;
  line-height: 1.4;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.wz-img-size--wait { opacity: 0.65; }
.wz-img-size--warn { color: #F59E0B; }

/* ── auto-roles picker ───────────────────────────────────────────────────────
   Replaces a native <select multiple>, whose Ctrl/Cmd-click requirement made
   picking a second role look broken. Checkboxes have no hidden modifier. */
.wz-rolepick {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-tertiary);
  padding: 0.375rem;
}

/* The roles actually in use. This list IS the answer to "what does a new member
   get?", so it is never a scroll box of every role in the server. */
.wz-role-chips { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.25rem; }
.wz-role-chips:empty { display: none; }

.wz-role-chip {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.375rem 0.5rem;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.04);
  font-size: 0.8125rem;
  color: var(--text);
}
.wz-role-chip.blocked .wz-role-name { color: var(--text-muted); }

.wz-role-x {
  flex: none;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.75rem;
  line-height: 1;
}
.wz-role-x:hover { background: rgba(220, 38, 38, 0.18); color: var(--red); }

.wz-rolepick-none {
  margin: 0;
  padding: 0.625rem 0.5rem;
  font-size: 0.75rem;
  color: var(--text-muted);
}

/* The search box that adds one. Only ever lists roles that can actually be added —
   an Administrator role or one above Azra is not offered at all. */
.wz-roleadd { position: relative; margin-top: 0.375rem; }
.wz-roleadd-search { width: 100%; height: 32px; font-size: 0.8125rem; }

.wz-roleadd-results {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 20;
  list-style: none;
  margin: 0;
  padding: 0.25rem;
  max-height: 216px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-secondary);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}
.wz-roleadd-results[hidden] { display: none; }

.wz-roleadd-opt {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.375rem 0.5rem;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font-size: 0.8125rem;
  text-align: left;
  cursor: pointer;
}
.wz-roleadd-opt:hover { background: rgba(255, 255, 255, 0.06); }

.wz-roleadd-empty {
  padding: 0.5rem;
  font-size: 0.75rem;
  color: var(--text-muted);
  line-height: 1.5;
}

.wz-role-dot {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
}
.wz-role-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Why a role on the list will be skipped on join: it outranks Azra, or it is an
   administrator role Azra refuses to hand out. Flagged rather than hidden — it is
   already saved, and it silently does nothing on every join until it is removed. */
.wz-role-flag {
  flex: none;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(245, 158, 11, 0.12);
  border: 1px solid rgba(245, 158, 11, 0.3);
  color: #F59E0B;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.wz-warn {
  margin-top: 0.625rem;
  padding: 0.625rem 0.75rem;
  background: rgba(245, 158, 11, 0.08);
  border: 1px solid rgba(245, 158, 11, 0.28);
  border-radius: 8px;
  font-size: 0.75rem;
  color: var(--text-muted);
  line-height: 1.5;
}

.wz-sub {
  margin: 0.25rem 0 0.5rem;
  padding: 0 0 0.25rem 0.875rem;
  border-left: 2px solid var(--border-strong);
}

/* ── repeatable rows (embed fields, link buttons) ────────────────────────── */
.wz-fields-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1.25rem;
}
.wz-fields-head .wz-label { margin: 0; }
.wz-fields-head small { display: block; margin-top: 0.25rem; font-size: 0.6875rem; color: var(--text-muted); }

.wz-field {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.625rem;
  margin-top: 0.5rem;
  background: var(--bg-tertiary);
}
/* Inline sits at the RIGHT, next to the delete button, rather than floating alone on
   the far left away from the inputs it applies to. */
.wz-field-top { display: flex; align-items: center; justify-content: flex-end; gap: 0.625rem; margin-bottom: 0.5rem; }
.wz-field .input + .input { margin-top: 0.375rem; }

.wz-field-del {
  border: 0;
  background: rgba(239, 68, 68, 0.12);
  color: var(--red);
  border-radius: 6px;
  width: 26px;
  height: 24px;
  font-size: 1rem;
  line-height: 1;
}
.wz-field-del:hover { background: rgba(239, 68, 68, 0.22); }

.wz-btn-row { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.5rem; }
.wz-btn-row .input:first-child { width: 140px; flex: none; }
.wz-btn-row .input { flex: 1; min-width: 0; }

/* checkbox used by "Inline" + the designer's option toggles.
   The box itself is the dashboard-wide input[type="checkbox"] theme — do not
   add a decorative <span> here, it renders a second box beside the real one. */
.wz-check, .wd-check {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
}

/* ── preview column ──────────────────────────────────────────────────────── */
/* A sticky element can only travel inside its own containing block, and the
   `align-items: start` above sizes this column to its content — which left the
   rail no room to move, so it scrolled away with the page. Stretching the column
   to the row height (i.e. the taller builder column) gives the rail the whole
   page to stick against. */
.wz-preview-col { min-width: 0; align-self: stretch; }
.wz-preview {
  position: sticky;
  top: 1.25rem;
  /* The scroll port is .app-main = viewport minus the 56px top navbar. Capping the
     rail to it keeps a tall preview (big embed image, designed card + buttons)
     reachable — the overflow scrolls inside the mock instead of off-screen. */
  max-height: calc(100vh - 56px - 2.5rem);
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
}
.wz-preview-head {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--border);
}
.wz-live {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  color: var(--text-muted);
}
.wz-live::first-letter { color: var(--pistachio); }
.wz-prev-actions { display: inline-flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; justify-content: flex-end; }

/* Channel / DM preview switch — sits under the head, shown only when the welcome DM
   is on (both are sent; this only picks which one the mock shows). */
.wz-prev-target {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.625rem 1rem 0;
}
.wz-prev-target .wz-seg { flex: 1; }

.wz-discord {
  margin: 1rem;
  /* Takes the leftover height of the pinned rail and scrolls its own content, so
     the head and the note below stay visible no matter how tall the message is. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
  background: #313338;
  border-radius: 10px;
  color: #dbdee1;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}
.wz-discord::-webkit-scrollbar { width: 6px; }
.wz-discord::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
.wz-discord-top {
  position: sticky;   /* the mock's channel bar stays put while its chat scrolls */
  top: 0;
  z-index: 1;
  padding: 0.75rem 0.875rem;
  background: #2b2d31;
  font-size: 0.8125rem;
  color: #949ba4;
}
.wz-discord-top b { color: #f2f3f5; }
.wz-chat { padding: 0.875rem; min-height: 260px; }
.wz-date { text-align: center; font-size: 0.6875rem; color: #80848e; margin: 0 0 0.75rem; }

.wz-msg { display: flex; gap: 0.625rem; }
.wz-avatar { width: 34px; height: 34px; flex: none; border-radius: 50%; overflow: hidden; background: var(--crimson); }
.wz-avatar img { width: 100%; height: 100%; object-fit: cover; }
.wz-msg-body { min-width: 0; flex: 1; }
.wz-meta { display: flex; align-items: center; gap: 0.375rem; margin-bottom: 0.25rem; }
.wz-meta b { font-size: 0.8125rem; color: #f2f3f5; }
.wz-meta em {
  font-style: normal;
  font-size: 0.5625rem;
  font-weight: 700;
  background: var(--crimson);
  color: #fff;
  border-radius: 3px;
  padding: 1px 3px;
}
.wz-meta small { font-size: 0.625rem; color: #949ba4; }

.wz-mention-line:not(:empty) { margin-bottom: 0.25rem; }
/* The mention pill: a user ping on the line above the message, or a {rules} channel
   link inside it — Discord paints both the same blurple. */
.wz-mention {
  font-size: 0.8125rem;
  color: #c9cdfb;
  background: rgba(88, 101, 242, 0.3);
  border-radius: 3px;
  padding: 0 2px;
}
/* On its own line the chip sets its own size (nothing around it to match), but inside a
   message it sits mid-sentence — so there it takes the size of the text it interrupts. */
.wz-plain .wz-mention,
.wz-embed .wz-mention { font-size: inherit; }
.wz-plain { font-size: 0.8125rem; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }

.wz-embed {
  border-left: 4px solid var(--crimson);
  background: #2b2d31;
  border-radius: 4px;
  padding: 0.625rem 0.75rem;
  max-width: 100%;
}
.wz-embed-author { display: flex; align-items: center; gap: 0.375rem; font-size: 0.75rem; font-weight: 600; margin-bottom: 0.5rem; color: #f2f3f5; }
.wz-embed-author img { width: 18px; height: 18px; border-radius: 50%; object-fit: cover; }
.wz-embed-main { display: flex; gap: 0.625rem; }
.wz-embed-text { min-width: 0; flex: 1; }
.wz-embed-title { font-weight: 700; font-size: 0.875rem; color: #f2f3f5; margin-bottom: 0.25rem; }
.wz-embed-desc  { font-size: 0.8125rem; line-height: 1.4; color: #dbdee1; word-break: break-word; }
.wz-embed-desc code { font-family: var(--font-mono); background: #1e1f22; border-radius: 3px; padding: 0 3px; }
.wz-embed-thumb { width: 56px; height: 56px; flex: none; border-radius: 6px; object-fit: cover; }
/* Discord FITS an embed image into 400 × 300 (embedBuilder.EMBED_IMAGE_BOX): the whole
   picture survives, a small one is not blown up, and nothing is ever cropped. So the mock
   is bounded on BOTH axes with auto width/height, which is exactly that behaviour — this
   used to be `width:100%; max-height:150px; object-fit:cover`, which cropped a tall
   picture and contradicted the "shown at W × H px" readout under the URL box. `min()`
   keeps it inside a preview rail narrower than 400px. */
.wz-embed-image {
  display: block;
  width: auto;
  height: auto;
  max-width: min(100%, 400px);
  max-height: 300px;
  margin-top: 0.5rem;
  border-radius: 6px;
}
/* Discord lays fields out as a grid, not a wrap: embedBuilder.fieldGrid decides which
   fields share a row (a run of inline fields, up to 3), and each row stretches to fill
   the width — so a lone inline field spans it, exactly as Discord renders it. Sizing
   with `flex: 1 1 0` keeps that free of percentage-vs-gap maths. */
.wz-embed-fields { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.5rem; }
.wz-embed-field-row { display: flex; gap: 0.5rem; }
.wz-embed-field-row > div { flex: 1 1 0; min-width: 0; }
.wz-embed-fields b { display: block; font-size: 0.75rem; color: #f2f3f5; }
.wz-embed-fields span { display: block; font-size: 0.75rem; color: #b5bac1; margin-top: 0.125rem; }
.wz-embed-footer { font-size: 0.6875rem; color: #949ba4; margin-top: 0.5rem; }

.wz-embed-buttons { display: flex; flex-wrap: wrap; gap: 0.375rem; margin-top: 0.5rem; }
.wz-embed-button {
  font-size: 0.75rem;
  padding: 0.25rem 0.625rem;
  border-radius: 4px;
  background: #4e5058;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.08);
}
/* How Discord renders a button it will not let you press — used for the welcome DM's
   server-name chip, which is a label rather than an action. */
.wz-embed-button.disabled { opacity: 0.5; cursor: not-allowed; }

.wz-prev-canvas { display: block; width: 100%; height: auto; border-radius: 6px; margin-top: 0.25rem; }

.wz-note { flex: none; margin: 0 1rem 1rem; font-size: 0.75rem; color: var(--text-muted); text-align: center; }
.wz-note-warn { color: #F59E0B; }

/* ── save bar ────────────────────────────────────────────────────────────── */
.wz-savebar {
  position: sticky;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.875rem 1.25rem;
  margin-top: 1.25rem;
  background: var(--glass-strong);
  backdrop-filter: blur(12px);
  border: 1px solid var(--border);
  border-radius: 14px;
}
/* Three actions now (Restore defaults / Reset changes / Save), each with a tooltip
   glyph beside it — so the row centres its items and is allowed to wrap on narrow
   screens rather than pushing Save off the edge of the bar. */
.wz-savebar > div { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; }
.wz-savebar #wz-defaults ion-icon { font-size: 1rem; margin-right: 0.15rem; vertical-align: -0.15em; }
.wz-savebar #wz-state { font-size: 0.8125rem; color: var(--text-muted); }
.wz-savebar #wz-state.dirty { color: #F59E0B; }

/* Generic savebar (dashboard/client/js/savebar.js) — same look as .wz-savebar above
   (welcome.js's original, page-scoped implementation) but usable by any config page.
   Keep the two in visual sync. */
.dash-savebar {
  position: sticky;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.875rem 1.25rem;
  margin-top: 1.25rem;
  background: var(--glass-strong);
  backdrop-filter: blur(12px);
  border: 1px solid var(--border);
  border-radius: 14px;
}
.dash-savebar > div { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; }
.dash-savebar [data-sb-restore] ion-icon { font-size: 1rem; margin-right: 0.15rem; vertical-align: -0.15em; }
.dash-savebar [data-sb-state] { font-size: 0.8125rem; color: var(--text-muted); }
.dash-savebar [data-sb-state].dirty { color: #F59E0B; }
@media (max-width: 900px) {
  .dash-savebar { flex-direction: column; align-items: stretch; }
  .dash-savebar > div { justify-content: flex-end; }
}

/* A savebar mounted in its OWN wrapper — a direct child of #app-main-content, so a
   Reset/Restore repaint of the page body cannot destroy the bar (leveling, punishments,
   automod). `.dash-savebar`'s own `position: sticky` does NOTHING there: a sticky element
   can never travel outside its PARENT's box, and that wrapper is exactly bar-height, so
   the bar simply scrolled away with the page. The WRAPPER is what has to stick — its
   containing block is #app-main-content, which spans the whole page, and the scrollport
   it sticks to is .app-main. Pages that render `savebarHtml()` inline inside a tall
   `.page-content` (moderation, security, logging) already stick and need none of this.
   Vertical padding is zeroed so the bar sits flush at the viewport bottom like
   .wz-savebar does; the wrapper's horizontal padding (if any) is kept. */
.savebar-dock {
  position: sticky;
  bottom: 0;
  z-index: 5;
  padding-top: 0;
  padding-bottom: 0;
}

/* ============================================================================
   .wd-* — the layered canvas designer inside the "Image" style
   ========================================================================== */
/* ONE size scale for every control the designer draws. Each family used to size itself
   from padding + whatever line-height its font resolved to, so boxes that sit side by
   side landed on heights nobody chose: a .wd-row put a 34px input next to a 37px
   <select> next to a 38px colour pair, and the four small-button families measured
   25.8 / 27 / 27.8px. Height is therefore set explicitly from these tokens and the
   padding follows, never the reverse — a control's box is a number we picked, not a
   by-product of the font it carries. Note form controls do NOT inherit body's
   line-height: 1.5 (they use `normal`), which is why sizing by padding was so hard to
   predict here. `mountDesigner` puts the .wd class on its root. */
.wd {
  --wd-ctl-h: 34px;   /* full-width control: input, <select> trigger, colour pair */
  --wd-btn-h: 32px;   /* small button: tools, presets, fonts, align, z-order */
  --wd-chip-h: 23px;  /* {token} pill */
  --wd-icon-h: 22px;  /* square icon button in the layers list */
  --wd-ctl-r: 8px;
  --wd-btn-r: 7px;
  --wd-ruler: 22px;   /* ruler gutter: a 9px label + an 8px major tick + breathing room */
}

.wd-bg-row { margin-bottom: 0.5rem; }
/* stretch (the default) is load-bearing: it is what gives Upload the input's height. */
.wd-bg-inputs { display: flex; align-items: stretch; gap: 0.5rem; }
.wd-bg-inputs .input { flex: 1; min-width: 0; }
.wd-bg-inputs .btn { flex: none; }

/* Says what a pasted picture link is doing — foreign links are copied onto Discord's
   CDN so the bot can fetch them, and until that lands (or if it fails) the canvas
   deliberately shows no picture, which needs explaining. */
.wd-bg-state {
  margin-top: 0.375rem;
  font-size: 0.75rem;
  line-height: 1.45;
}
.wd-bg-state.warn { color: var(--yellow, #FAA61A); }
.wd-bg-state.ok   { color: var(--green, #3BA55D); }
.wd-bg-state.busy { color: var(--text-muted); }

.wd-editor {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 272px;
  gap: 1rem;
  align-items: start;
  margin-top: 0.75rem;
}
.wd-canvas-col { min-width: 0; }

.wd-tools { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.5rem; }
.wd-tools .btn { height: var(--wd-btn-h); padding-block: 0; }
.wd-tools .wd-hint { margin: 0; flex: 1; min-width: 160px; font-size: 0.6875rem; }

/* undo / redo + the keyboard-shortcut legend */
.wd-undo { display: inline-flex; gap: 0.25rem; }
/* Square, and exactly as tall as +Text/+Image beside them: the glyph is the content,
   so the box is --wd-btn-h on both axes rather than min-width + padding + line-height. */
.wd-undo .btn { width: var(--wd-btn-h); min-width: 0; padding-inline: 0; font-size: 0.9rem; line-height: 1; }
.wd-undo .btn:disabled { opacity: 0.4; cursor: default; }

.wd-kbd-hint {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  font-size: 0.6875rem;
  color: var(--text-muted);
  white-space: nowrap;
}
/* Transient confirmation for an action with no visible result of its own (copy/cut). */
.wd-kbd-note {
  font-size: 0.6875rem;
  font-weight: 600;
  color: var(--green, #93C572);
  opacity: 0;
  transition: opacity 160ms ease;
  white-space: nowrap;
}
.wd-kbd-note.on { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .wd-kbd-note { transition: none; }
}

/* Rulers in the gutters above and left of the design canvas, measured from the CENTRE
   of the card — 0 in the middle, negative left/up, positive right/down — so a tick reads
   the same as the X/Y boxes in the properties panel (offX/offY in pages/welcome-design.js).
   The ticks themselves are drawn in JS: the design canvas is laid out fluid, so where a
   tick lands is only knowable from the measured rect. */
.wd-stage {
  display: grid;
  grid-template-columns: var(--wd-ruler) minmax(0, 1fr);
  grid-template-rows: var(--wd-ruler) auto;
}
.wd-ruler-corner {
  display: flex;
  align-items: center;
  justify-content: center;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.wd-ruler { display: block; }
.wd-ruler-x {
  width: 100%;
  height: var(--wd-ruler);
  border-bottom: 1px solid var(--border);
}
/* The vertical ruler is absolutely positioned inside its own cell rather than sized
   directly. Its grid row is `auto` — the design canvas decides how tall it is — and a
   canvas carries an intrinsic height (its `height` attribute, 150px until the JS sizes
   it), so as a plain grid item it would contribute that 150px to the row's own height:
   a size cycle, and a floor under the row. Taken out of flow it contributes nothing, and
   `inset: 0` stretches it to whatever the canvas beside it settled on. */
.wd-ruler-y-cell { position: relative; border-right: 1px solid var(--border); }
.wd-ruler-y { position: absolute; inset: 0; width: 100%; height: 100%; }

.wd-canvas-wrap {
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  background:
    linear-gradient(45deg, rgba(255,255,255,.02) 25%, transparent 25%, transparent 75%, rgba(255,255,255,.02) 75%) 0 0 / 16px 16px,
    var(--bg-tertiary);
}
.wd-canvas-wrap canvas {
  display: block;
  width: 100%;
  height: auto;
  cursor: crosshair;
  touch-action: none;
}

/* layers list — reads front-to-back, top-to-bottom (see renderLayers in
   welcome-design.js): the top row is the layer on top of the card. Rows drag to
   restack; the background row is pinned to the bottom and has no grip. */
.wd-layers-block { margin-top: 0.625rem; }
.wd-layers-count { color: var(--text-muted); font-weight: 600; }
.wd-layers {
  margin-top: 0.375rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  max-height: 220px;
  overflow-y: auto;
}
.wd-layer {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.625rem;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  font-size: 0.75rem;
}
.wd-layer-grip {
  width: 10px;
  flex: none;
  display: grid;
  place-items: center;
  color: var(--text-muted);
  font-size: 0.75rem;
  line-height: 1;
  cursor: grab;
  /* A touch drag on the grip reorders; anywhere else on the row it still scrolls the
     list, which is why welcome-design.js only starts a touch drag from this element. */
  touch-action: none;
}
.wd-layer:hover .wd-layer-grip { color: var(--crimson-glow); }
.wd-layer.pinned .wd-layer-grip { cursor: default; }
.wd-layer:last-child { border-bottom: 0; }
.wd-layer:hover { background: var(--surface-hover); }
.wd-layer.sel { background: rgba(220, 38, 38, 0.12); }
.wd-layer-type {
  width: 20px;
  height: 20px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 5px;
  background: var(--crimson);
  color: #fff;
  font-size: 0.5625rem;
  font-weight: 700;
}
.wd-layer-name { flex: 1; min-width: 0; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wd-layer-acts { display: flex; gap: 2px; }
.wd-layer-acts button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--wd-icon-h);
  height: var(--wd-icon-h);
  flex: none;
  border: 0;
  border-radius: 4px;
  background: var(--bg-tertiary);
  color: var(--text-muted);
  font-size: 0.625rem;
  line-height: 1;
}
.wd-layer-acts button:hover { background: rgba(220, 38, 38, 0.2); color: var(--crimson-glow); }
.wd-layer-acts button:active,
.wd-z button:active { transform: scale(0.92); }

/* Reorder feedback. The list is re-rendered from scratch on every change, so a row
   that moved would just reappear one slot away — the arrow buttons would look like
   they did nothing. `playReorder` in welcome-design.js FLIPs the rows (transform
   them back to where they were, then let this transition carry them home) and marks
   the moved one, which flashes so the eye can follow it. The same transition is what
   parts the other rows around a row being dragged. */
.wd-layer { transition: transform 0.22s cubic-bezier(0.2, 0.7, 0.3, 1); }
.wd-layer.wd-shifted { position: relative; }
.wd-layer.wd-moved   { position: relative; z-index: 2; animation: wd-moved-flash 0.5s ease-out; }

/* A row being dragged tracks the pointer exactly, so it must NOT inherit the
   transition above — that is for the rows parting around it. */
.wd-layers.wd-reordering { user-select: none; }
.wd-layer.wd-dragging {
  position: relative;
  z-index: 3;
  transition: none;
  background: var(--surface-hover);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45), inset 3px 0 0 var(--crimson-glow);
}
.wd-layer.wd-dragging,
.wd-layer.wd-dragging .wd-layer-grip { cursor: grabbing; }
.wd-layer.wd-dragging .wd-layer-grip { color: var(--crimson-glow); }
@keyframes wd-moved-flash {
  0%   { background: rgba(220, 38, 38, 0.35); box-shadow: inset 3px 0 0 var(--crimson-glow); }
  100% { background: transparent;             box-shadow: inset 3px 0 0 transparent; }
}
/* A move that can't happen (already at the front, or already sitting straight on the
   background) nudges the button instead of silently ignoring the click. */
.wd-bump { animation: wd-bump 0.3s ease; }
@keyframes wd-bump {
  0%, 100% { transform: translateX(0); }
  30%      { transform: translateX(-3px); }
  65%      { transform: translateX(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .wd-layer { transition: none; }
  .wd-layer.wd-moved { animation: none; }
  .wd-bump { animation: none; }
}

/* properties panel */
.wd-props {
  background: var(--bg-tertiary);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.875rem;
  max-height: 640px;
  overflow-y: auto;
}
.wd-props-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.wd-props-head-right { display: inline-flex; align-items: center; flex: none; }
.wd-props-head b { font-size: 0.8125rem; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wd-tag {
  flex: none;
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  letter-spacing: 0.08em;
  background: var(--crimson);
  color: #fff;
  border-radius: 4px;
  padding: 2px 5px;
}
/* A .wd-row sets a text input, an enhanced <select> and a colour pair beside each
   other, so all three are built to --wd-ctl-h and share one radius. Measured before
   this rule: 34 / 37 / 38px — three heights in one row.
   The <select> rule has to target .cs-trigger, NOT .input: select-enhance.js hides the
   real <select> (.cs-native) and renders a button in its place, so sizing .input alone
   styled the invisible one and left every select 3px taller than the input beside it.
   Block padding is zeroed so --wd-ctl-h alone decides the box and the control centres
   its own value; the inline padding drops to match the compact scale. */
.wd-props .input:not(.wd-textarea):not(.cs-native),
.wd-props .cs-trigger {
  height: var(--wd-ctl-h);
  padding: 0 0.625rem;
  font-size: 0.75rem;
  border-radius: var(--wd-ctl-r);
}
.wd-props .cs-trigger { gap: 0.375rem; }
.wd-props .wd-textarea { padding: 0.5rem 0.625rem; font-size: 0.75rem; border-radius: var(--wd-ctl-r); }
.wd-props .wd-color { height: var(--wd-ctl-h); border-radius: var(--wd-ctl-r); }
.wd-props .btn { height: var(--wd-btn-h); padding-block: 0; }
.wd-props input[type="range"] { width: 100%; accent-color: var(--crimson); }

/* CORNER ROUNDING / SIDES: a slider and a number box over ONE value. The box is a fixed
   64px so the slider takes whatever is left — the pair has to fit the props rail, and a
   box sized in fr units would grow with the column and squeeze the slider to nothing.
   `is-off` is for a shape with no corners to round (Circle, Ellipse, Capsule, Heart):
   the control stays in place, greyed, rather than disappearing and reflowing the panel
   every time the shape changes. */
.wd-slider { display: grid; grid-template-columns: 1fr 64px; gap: 0.5rem; align-items: center; }
.wd-slider.is-off { opacity: 0.45; }
.wd-slider.is-off input { cursor: not-allowed; }

.wd-align { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.wd-align button, .wd-z button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--wd-btn-h);
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  border-radius: var(--wd-btn-r);
  padding: 0 0.25rem;
  font-size: 0.75rem;
  line-height: 1;
}
.wd-align button:hover, .wd-z button:hover { background: rgba(220, 38, 38, 0.15); border-color: var(--crimson); }
.wd-z { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin-top: 0.5rem; }
.wd-z button { font-size: 0.6875rem; font-weight: 600; }

.wd-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 0.375rem; }
.wd-chips button {
  display: inline-flex;
  align-items: center;
  height: var(--wd-chip-h);
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--crimson-glow);
  border-radius: 999px;
  padding: 0 8px;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  line-height: 1;
}
.wd-chips button:hover { background: rgba(220, 38, 38, 0.15); }

.wd-fonts { display: flex; flex-wrap: wrap; gap: 4px; }
.wd-fonts button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--wd-btn-h);
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  border-radius: var(--wd-btn-r);
  padding: 0 9px;
  font-size: 0.75rem;
  line-height: 1;
}
.wd-fonts button.active { background: var(--crimson); border-color: var(--crimson); color: #fff; }

.wd-opt { margin-top: 0.75rem; padding-top: 0.625rem; border-top: 1px solid var(--border); }
.wd-opt-body { margin-top: 0.5rem; }

/* shadow-offset joystick */
.wd-joy {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  position: relative;
  background: radial-gradient(circle at center, var(--bg-card) 0%, var(--bg-tertiary) 100%);
  border: 1px solid var(--border-strong);
  cursor: grab;
  touch-action: none;
}
.wd-joy::before,
.wd-joy::after {
  content: "";
  position: absolute;
  background: var(--border);
}
.wd-joy::before { left: 50%; top: 8px; bottom: 8px; width: 1px; }
.wd-joy::after  { top: 50%; left: 8px; right: 8px; height: 1px; }
.wd-joy-knob {
  position: absolute;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: var(--crimson-glow);
  box-shadow: 0 0 8px rgba(239, 68, 68, 0.7);
  pointer-events: none;
}

/* ready-made content presets */
.wd-content {
  margin-top: 1rem;
  padding: 0.875rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-tertiary);
}
.wd-content > .wd-label:first-child { margin-top: 0; }
.wd-content > .wd-hint { margin-bottom: 0.625rem; }
.wd-presets { display: flex; flex-wrap: wrap; gap: 6px; }
.wd-presets button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--wd-btn-h);
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  border-radius: var(--wd-btn-r);
  padding: 0 10px;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1;
}
.wd-presets button:hover { background: var(--surface-hover); }
.wd-presets button.on {
  background: rgba(220, 38, 38, 0.15);
  border-color: rgba(220, 38, 38, 0.45);
  color: var(--crimson-glow);
}

/* the floating on-canvas text editor */
.wd-float-edit {
  position: fixed;
  z-index: 500;
  background: rgba(10, 10, 15, 0.85);
  border: 1px dashed var(--crimson-glow);
  border-radius: 6px;
  padding: 6px;
  resize: none;
  outline: none;
  overflow: hidden;
  line-height: 1.2;
  box-sizing: border-box;
}

@media (max-width: 1200px) {
  .wz-layout { grid-template-columns: 1fr; }
  /* Stacked: the preview is a normal block again, so drop the pin, the height cap
     and the inner scroll — the page itself scrolls. */
  .wz-preview { position: static; max-height: none; display: block; }
  .wz-discord { overflow: visible; }
}
@media (max-width: 950px) {
  .wd-editor { grid-template-columns: 1fr; }
  .wd-props { order: 2; max-height: none; }
}
@media (max-width: 700px) {
  .wz-row, .wd-row { grid-template-columns: 1fr; }
  .wz-panel { padding: 1.125rem; }
  .wz-savebar { flex-direction: column; align-items: stretch; }
  .wz-savebar > div { justify-content: flex-end; }
}
