/* ==========================================================================
   FXAP 解密 — 设计令牌与样式
   令牌取自 linux-do/cdk 的 globals.css（OKLCH 色彩空间、shadcn/ui 结构），
   但去掉了 Google Fonts 依赖：本机在国内机房，fonts.googleapis.com 不可达，
   直接引用会静默回退并拖慢首屏。改用等价的系统字体栈。

   布局：桌面为左右两栏（左输入 / 右状态），整体锁定一屏高度。
   滚动条全局隐藏，但可滚动区域仍然响应滚轮与触摸 —— 隐藏的是滚动条本身，
   不是滚动能力，否则矮屏上内容会永久不可达。
   ========================================================================== */

:root {
  --radius: 0.625rem;
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);

  --background: oklch(1 0 0);
  --foreground: oklch(0.141 0.005 285.823);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.141 0.005 285.823);
  --popover: oklch(1 0 0);
  --primary: oklch(0.21 0.006 285.885);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.967 0.001 286.375);
  --secondary-foreground: oklch(0.21 0.006 285.885);
  --muted: oklch(0.967 0.001 286.375);
  --muted-foreground: oklch(0.552 0.016 285.938);
  --accent: oklch(0.967 0.001 286.375);
  --accent-foreground: oklch(0.21 0.006 285.885);
  --destructive: oklch(0.577 0.245 27.325);
  --border: oklch(0.92 0.004 286.32);
  --input: oklch(0.92 0.004 286.32);
  --ring: oklch(0.705 0.015 286.067);

  /* 状态色（对齐参考实现的 sonner toast 色板） */
  --success: #10b981;
  --error: #ef4444;
  --warning: #f59e0b;
  --info: #3b82f6;

  --shadow-sm: 0 1px 2px 0 oklch(0.141 0.005 285.823 / 5%);
  --shadow-md: 0 4px 12px -2px oklch(0.141 0.005 285.823 / 8%),
               0 2px 4px -2px oklch(0.141 0.005 285.823 / 6%);
  --shadow-lg: 0 12px 32px -8px oklch(0.141 0.005 285.823 / 14%),
               0 4px 8px -4px oklch(0.141 0.005 285.823 / 8%);
}

.dark {
  --background: oklch(0.141 0.005 285.823);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.21 0.006 285.885);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.21 0.006 285.885);
  --primary: oklch(0.92 0.004 286.32);
  --primary-foreground: oklch(0.21 0.006 285.885);
  --secondary: oklch(0.274 0.006 286.033);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.274 0.006 286.033);
  --muted-foreground: oklch(0.705 0.015 286.067);
  --accent: oklch(0.274 0.006 286.033);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.552 0.016 285.938);

  --success: #34d399;
  --error: #f87171;
  --warning: #fbbf24;
  --info: #60a5fa;

  --shadow-sm: 0 1px 2px 0 oklch(0 0 0 / 30%);
  --shadow-md: 0 4px 12px -2px oklch(0 0 0 / 40%);
  --shadow-lg: 0 12px 32px -8px oklch(0 0 0 / 50%);
}

/* --------------------------------------------------------------- reset -- */

*,
*::before,
*::after { box-sizing: border-box; }

* { border-color: var(--border); }

html { -webkit-text-size-adjust: 100%; height: 100%; }

body {
  margin: 0;
  height: 100%;
  /* The page itself never scrolls; inner columns do, with hidden bars. */
  overflow: hidden;
  background: var(--background);
  color: var(--foreground);
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
               "Helvetica Neue", "Noto Sans SC", "PingFang SC",
               "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
  font-size: 14px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Every scrollbar in the document, hidden — the scroll behaviour remains. */
* {
  scrollbar-width: none;          /* Firefox */
  -ms-overflow-style: none;       /* legacy Edge/IE */
}
*::-webkit-scrollbar {
  display: none;
  width: 0;
  height: 0;
}

button { cursor: pointer; font: inherit; color: inherit; }
svg { display: block; }

::selection { background: oklch(0.646 0.222 41.116 / 20%); }

/* ------------------------------------------------------------- layout --- */

.page {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 100%;
  max-width: 1120px;
  height: 100vh;
  height: 100dvh;
  margin: 0 auto;
  padding: 18px 24px 20px;
  overflow: hidden;
}

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex: none;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* The mark is a bare transparent glyph — no plate, no ring, no border. It
   inherits currentColor, so it follows the *site* theme toggle (which the user
   can flip independently of the OS setting) rather than a media query. */
.brand-mark {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  background: none;
  border: 0;
  color: var(--foreground);
}
.brand-mark svg {
  width: 100%;
  height: 100%;
}

.brand-text { font-size: 15px; }
.brand-dim { color: var(--muted-foreground); font-weight: 500; }

.icon-btn {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--muted-foreground);
  flex-shrink: 0;
  transition: background-color .15s, color .15s, border-color .15s;
}
.icon-btn:hover { background: var(--accent); color: var(--accent-foreground); }
.icon-btn svg { width: 16px; height: 16px; }

.icon-sun { display: none; }
.dark .icon-sun { display: block; }
.dark .icon-moon { display: none; }

/* Two columns: input on the left, live status on the right. */
.layout {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 430px) minmax(0, 1fr);
  gap: 20px;
  align-items: stretch;
}

.col {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.col-input { overflow-y: auto; }

/* The status column supplies no spacing of its own; .col already puts the
   gap between the step strip and the card below it, matching the left side. */
.col-status { gap: 14px; }

/* --------------------------------------------------------------- title -- */

/* The title and the step strip occupy matching header rows in the two columns,
   so both must be exactly the same height or the cards below them land on
   different baselines. 24px * 1.25 = 30px, stated explicitly here and mirrored
   by .steps so the two can never drift apart. */
:root {
  --head-row: 30px;
}

.title {
  margin: 0;
  height: var(--head-row);
  font-size: 24px;
  font-weight: 650;
  line-height: var(--head-row);
  letter-spacing: -0.025em;
  flex: none;
}

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

.card {
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 20px;
  box-shadow: var(--shadow-sm);
}

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

.field { margin-bottom: 18px; }
.field-last { margin-bottom: 20px; }

.field-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}

.field-head label {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
}

.field-meta {
  font-size: 11.5px;
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------ dropzone -- */

.dropzone {
  position: relative;
  border: 1.5px dashed var(--border);
  border-radius: var(--radius-lg);
  background: color-mix(in oklab, var(--muted) 45%, transparent);
  transition: border-color .18s, background-color .18s, transform .18s;
  cursor: pointer;
}
.dropzone:hover { border-color: var(--ring); }
.dropzone:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.dropzone.is-over {
  border-color: var(--primary);
  background: color-mix(in oklab, var(--primary) 6%, transparent);
  transform: scale(1.005);
}
.dropzone input { display: none; }

.dropzone-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 22px 16px;
  text-align: center;
  pointer-events: none;
}

.dropzone-icon {
  color: var(--muted-foreground);
  margin-bottom: 4px;
  transition: color .18s;
}
.dropzone:hover .dropzone-icon,
.dropzone.is-over .dropzone-icon { color: var(--primary); }
.dropzone-icon svg { width: 24px; height: 24px; }

.dropzone-title { font-size: 13.5px; font-weight: 600; }
.dropzone-sub { font-size: 12.5px; color: var(--muted-foreground); }

/* ----------------------------------------------------------- file chip -- */

.file-chip {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: 10px;
  padding: 9px 11px;
  border: 1px solid color-mix(in oklab, var(--success) 35%, transparent);
  border-radius: var(--radius-md);
  background: color-mix(in oklab, var(--success) 8%, transparent);
  animation: chip-in .22s ease-out;
}

@keyframes chip-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.file-chip-icon { color: var(--success); flex-shrink: 0; }
.file-chip-icon svg { width: 16px; height: 16px; }

.file-chip-name {
  flex: 1;
  min-width: 0;
  font-size: 13px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-chip-size {
  font-size: 12px;
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.file-chip-clear {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted-foreground);
  flex-shrink: 0;
  transition: background-color .15s, color .15s;
}
.file-chip-clear:hover {
  background: color-mix(in oklab, var(--error) 14%, transparent);
  color: var(--error);
}
.file-chip-clear svg { width: 13px; height: 13px; }

/* --------------------------------------------------------- input group -- */

.input-group {
  display: flex;
  align-items: center;
  border: 1px solid var(--input);
  border-radius: var(--radius-md);
  transition: border-color .15s, box-shadow .15s;
}
.input-group:focus-within {
  border-color: var(--ring);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 22%, transparent);
}

.input-icon {
  display: grid;
  place-items: center;
  padding-left: 11px;
  color: var(--muted-foreground);
  flex-shrink: 0;
}
.input-icon svg { width: 15px; height: 15px; }

.input-group input {
  flex: 1;
  min-width: 0;
  padding: 10px;
  border: 0;
  background: transparent;
  color: inherit;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;
  font-size: 13px;
  letter-spacing: 0.01em;
}
.input-group input:focus { outline: none; }
.input-group input::placeholder { color: var(--muted-foreground); opacity: .7; }

.input-suffix {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted-foreground);
  flex-shrink: 0;
  transition: background-color .15s, color .15s;
}
.input-suffix:last-child { margin-right: 3px; }
.input-suffix:hover { background: var(--accent); color: var(--accent-foreground); }
.input-suffix svg { width: 15px; height: 15px; }

.icon-eye-off { display: none; }
.input-suffix.is-revealed .icon-eye { display: none; }
.input-suffix.is-revealed .icon-eye-off { display: block; }

/* "remember key" toggle — icon only, state carried by aria-pressed */
#rememberBtn[aria-pressed="true"] {
  background: var(--accent);
  color: var(--accent-foreground);
}

/* -------------------------------------------------------------- button -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 11px 18px;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.005em;
  text-decoration: none;
  transition: opacity .15s, transform .1s, background-color .15s,
              box-shadow .15s, border-color .15s;
}
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn svg { width: 16px; height: 16px; }

.btn-primary {
  background: var(--primary);
  color: var(--primary-foreground);
  box-shadow: var(--shadow-sm);
}
.btn-primary:hover:not(:disabled) { opacity: .9; }
.btn-primary:disabled { opacity: .55; cursor: not-allowed; }
.btn-primary:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

.btn-ghost {
  background: transparent;
  border-color: var(--border);
  color: var(--foreground);
}
.btn-ghost:hover { background: var(--accent); }

/* ------------------------------------------------------------- spinner -- */

.spinner {
  width: 15px;
  height: 15px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 999px;
  animation: spin .65s linear infinite;
  flex-shrink: 0;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* -------------------------------------------------------------- steps --- */

/* Three circles, no connector lines. Each circle carries the whole state:
     pending  — grey outline, number visible
     active   — rotating arc ring, number stays upright and legible
     done     — green fill, checkmark
     ready    — green outline + pulsing halo (waiting for the user)
     failed   — red fill, cross                                          */

.steps {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 42px;
  margin: 0;
  padding: 0;
  /* Matches the title's row exactly, so #form and the status area start on the
     same y in both columns. */
  height: var(--head-row);
  list-style: none;
  flex: none;
}

.step {
  display: flex;
  align-items: center;
  gap: 9px;
  flex: none;
}

.step-dot {
  position: relative;
  display: grid;
  place-items: center;
  width: 27px;
  height: 27px;
  border-radius: 999px;
  border: 1.5px solid var(--border);
  background: var(--background);
  color: var(--muted-foreground);
  font-size: 11.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
  transition: background-color .25s, border-color .25s, color .25s;
}

.step-label {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--muted-foreground);
  white-space: nowrap;
  transition: color .25s;
}

/* --- active: the circle spins ------------------------------------------- */

.step.is-active .step-dot {
  /* Faint full ring underneath, bright quarter sweeping on top. */
  border-color: color-mix(in oklab, var(--primary) 18%, transparent);
  color: var(--foreground);
  font-weight: 700;
}
.step.is-active .step-dot::before {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 999px;
  border: 2.5px solid transparent;
  border-top-color: var(--primary);
  border-right-color: var(--primary);
  animation: step-spin .85s linear infinite;
}
.step.is-active .step-label { color: var(--foreground); }

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

/* --- done: green + check ------------------------------------------------ */

.step.is-done .step-dot {
  border-color: var(--success);
  background: var(--success);
  color: #fff;
}
.step.is-done .step-num { display: none; }
.step.is-done .step-label { color: var(--foreground); }

/* --- failed: red + cross ------------------------------------------------ */

.step.is-failed .step-dot {
  border-color: var(--error);
  background: var(--error);
  color: #fff;
}
.step.is-failed .step-num { display: none; }
.step.is-failed .step-label { color: var(--error); }

/* Shared glyph layer for the terminal states. */
.step.is-done .step-dot::after,
.step.is-failed .step-dot::after {
  content: "";
  position: absolute;
  width: 12px;
  height: 12px;
  background: currentColor;
  -webkit-mask: var(--step-glyph) center / contain no-repeat;
  mask: var(--step-glyph) center / contain no-repeat;
}
.step.is-done .step-dot {
  --step-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}
.step.is-failed .step-dot {
  --step-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6 6 18'/%3E%3Cpath d='m6 6 12 12'/%3E%3C/svg%3E");
}

/* --- ready: green halo pulsing, waiting for the user to click ----------- */

.step.is-ready .step-dot {
  border-color: var(--success);
  background: color-mix(in oklab, var(--success) 14%, transparent);
  color: var(--success);
  font-weight: 700;
}
.step.is-ready .step-dot::before {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 999px;
  border: 2px solid var(--success);
  animation: step-pulse 1.7s cubic-bezier(.4, 0, .2, 1) infinite;
}
.step.is-ready .step-label { color: var(--foreground); }

@keyframes step-pulse {
  0%   { transform: scale(.92); opacity: .85; }
  70%  { transform: scale(1.45); opacity: 0; }
  100% { transform: scale(1.45); opacity: 0; }
}

/* --- status panel ------------------------------------------------------- */

.status-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  /* Top-aligned: the status card shares a baseline with the form card in the
     left column. The idle placeholder centres itself with its own auto margin
     instead of shifting every card down. */
  justify-content: flex-start;
  gap: 12px;
  padding: 2px;
}

.status-scroll > * {
  flex: none;
}

/* Only the placeholder floats to the middle of the empty panel. */
#idleState {
  margin-block: 0;
}

/* The idle placeholder is a card in its own right: same border, radius,
   surface and shadow as the form card on the left, top-aligned with it, so the
   two columns read as a matched pair instead of one label floating in space. */
.idle-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 11px;
  min-height: 200px;
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--card);
  box-shadow: var(--shadow-sm);
  color: var(--muted-foreground);
}

.idle-icon { opacity: .45; }
.idle-icon svg { width: 30px; height: 30px; }
.idle-text { font-size: 13px; }

/* ------------------------------------------------------------ progress -- */

.progress-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.progress-label { font-size: 13.5px; font-weight: 600; }

.progress-pct {
  font-size: 12.5px;
  color: var(--muted-foreground);
  font-variant-numeric: tabular-nums;
}

.progress-track {
  position: relative;
  height: 6px;
  border-radius: 999px;
  background: var(--muted);
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--primary);
  transition: width .35s cubic-bezier(.4, 0, .2, 1);
}

/* 不确定态：参考实现的 slide 关键帧 */
.progress-fill.is-indeterminate {
  position: absolute;
  width: 33%;
  animation: slide 1.4s cubic-bezier(.65, 0, .35, 1) infinite;
}

@keyframes slide {
  0%   { left: -33%; }
  50%  { left: 100%; }
  100% { left: -33%; }
}

/* ------------------------------------------------------------ terminal -- */

.terminal {
  margin-top: 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  animation: chip-in .22s ease-out;
}

.terminal-head {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 7px 12px;
  background: var(--muted);
  border-bottom: 1px solid var(--border);
  font-size: 11.5px;
  font-weight: 600;
  color: var(--muted-foreground);
  letter-spacing: 0.01em;
}
.terminal-head svg { width: 13px; height: 13px; }

.terminal pre {
  margin: 0;
  padding: 11px 12px;
  height: 210px;
  overflow-y: auto;
  background: var(--background);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;
  font-size: 11.5px;
  line-height: 1.65;
  color: var(--muted-foreground);
  white-space: pre-wrap;
  word-break: break-word;
  overscroll-behavior: contain;
}

/* ------------------------------------------------------------ callouts -- */

.callout {
  display: flex;
  align-items: flex-start;
  gap: 11px;
}

.callout-icon { flex-shrink: 0; margin-top: 1px; }
.callout-icon svg { width: 20px; height: 20px; }

.callout-ok .callout-icon { color: var(--success); }
.callout-err .callout-icon { color: var(--error); }

.callout-body { min-width: 0; }
.callout-body h2 {
  margin: 0 0 3px;
  font-size: 16px;
  font-weight: 650;
  letter-spacing: -0.015em;
}
.callout-body p {
  margin: 0;
  font-size: 13px;
  color: var(--muted-foreground);
  line-height: 1.55;
  word-break: break-word;
}

.result-card .btn-primary { margin-top: 16px; }
.error-card .btn-ghost { margin-top: 16px; }

/* --------------------------------------------------------------- stats -- */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
  gap: 8px;
  margin-top: 16px;
}

.stat {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: color-mix(in oklab, var(--muted) 40%, transparent);
}

.stat-value {
  font-size: 18px;
  font-weight: 650;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}

.stat-label {
  margin-top: 2px;
  font-size: 11.5px;
  color: var(--muted-foreground);
}

.stat.is-warn .stat-value { color: var(--warning); }
.stat.is-err .stat-value { color: var(--error); }

/* -------------------------------------------------------------- expiry -- */

.expiry {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--muted-foreground);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------- toasts -- */

.toaster {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(360px, calc(100vw - 32px));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 11px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--popover);
  color: var(--card-foreground);
  box-shadow: var(--shadow-lg);
  font-size: 13px;
  line-height: 1.5;
  pointer-events: auto;
  animation: toast-in .26s cubic-bezier(.34, 1.4, .64, 1);
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(12px) scale(.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.toast.is-leaving { animation: toast-out .2s ease-in forwards; }
@keyframes toast-out {
  to { opacity: 0; transform: translateY(8px) scale(.97); }
}

.toast-icon { flex-shrink: 0; margin-top: 1px; }
.toast-icon svg { width: 16px; height: 16px; }

.toast-success .toast-icon { color: var(--success); }
.toast-error   .toast-icon { color: var(--error); }
.toast-info    .toast-icon { color: var(--info); }

.toast-text { min-width: 0; flex: 1; }

/* ------------------------------------------------------------ a11y/misc -- */

[hidden] { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

/* ------------------------------------------------------------ responsive -- */

/* Below ~860px the two columns cannot both breathe: stack them. The page
   keeps its single-screen shell, so the columns' own scroll areas take over. */
@media (max-width: 860px) {
  .page { padding: 14px 14px 16px; max-width: 560px; }

  .layout {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    gap: 12px;
  }

  .col-input { flex: none; overflow-y: visible; }
  .title { font-size: 21px; }

  .terminal pre { height: 150px; }
  .steps { gap: 26px; }
}

@media (max-width: 460px) {
  /* Too narrow for three labelled circles; drop the labels, keep the state. */
  .step-label { display: none; }
  .steps { gap: 30px; }
  .card { padding: 16px; }
  .dropzone-body { padding: 18px 12px; }
}
