:root {
  --paper: #F5F6F3;
  --paper-raised: #FFFFFF;
  --ink: #161B18;
  --ink-soft: #5B6560;
  --line: #DEE3DE;
  --accent: #0E7A5F;
  --accent-soft: #E4F2ED;
  --money: #7A5A1E;
  --money-soft: rgba(185, 134, 47, .14);
  --risk: #A83A2C;
  --risk-soft: rgba(168, 58, 44, .14);
  --shadow: 0 1px 2px rgba(15, 20, 15, .03), 0 16px 32px -22px rgba(15, 20, 15, .28);

  /* input/output get their own two dedicated hues (violet/rose) instead of borrowing
     --money or --accent, so "give this" and "get this" each read as their own deliberate
     color rather than a wash of whatever else was nearby in the palette */
  --input-accent: #6B3FA0;
  --input-accent-soft: rgba(107, 63, 160, .20);
  --input-tint: var(--input-accent-soft);
  --input-tint-border: rgba(107, 63, 160, .42);

  --output-accent: #A13D5E;
  --output-accent-soft: rgba(161, 61, 94, .12);
  --output-tint: var(--output-accent-soft);
  --output-tint-border: rgba(161, 61, 94, .32);

  --font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper: #12150F;
    --paper-raised: #191D16;
    --ink: #E7ECE6;
    --ink-soft: #8E988F;
    --line: #2C332B;
    --accent: #3FBF93;
    --accent-soft: rgba(63, 191, 147, 0.14);
    --money: #E0A94E;
    --money-soft: rgba(224, 169, 78, .14);
    --risk: #E2695A;
    --risk-soft: rgba(226, 105, 90, .14);
    --shadow: 0 1px 2px rgba(0, 0, 0, .2), 0 16px 32px -20px rgba(0, 0, 0, .6);

    --input-accent: #B49BE8;
    --input-accent-soft: rgba(180, 155, 232, .26);
    --input-tint: var(--input-accent-soft);
    --input-tint-border: rgba(180, 155, 232, .4);

    --output-accent: #E28AB0;
    --output-accent-soft: rgba(226, 138, 176, .16);
    --output-tint: var(--output-accent-soft);
    --output-tint-border: rgba(226, 138, 176, .3);
  }
}

:root[data-theme="dark"] {
  --paper: #12150F;
  --paper-raised: #191D16;
  --ink: #E7ECE6;
  --ink-soft: #8E988F;
  --line: #2C332B;
  --accent: #3FBF93;
  --accent-soft: rgba(63, 191, 147, 0.14);
  --money: #E0A94E;
  --money-soft: rgba(224, 169, 78, .14);
  --risk: #E2695A;
  --risk-soft: rgba(226, 105, 90, .14);
  --shadow: 0 1px 2px rgba(0, 0, 0, .2), 0 16px 32px -20px rgba(0, 0, 0, .6);

  --input-accent: #B49BE8;
  --input-accent-soft: rgba(180, 155, 232, .26);
  --input-tint: var(--input-accent-soft);
  --input-tint-border: rgba(180, 155, 232, .4);

  --output-accent: #E28AB0;
  --output-accent-soft: rgba(226, 138, 176, .16);
  --output-tint: var(--output-accent-soft);
  --output-tint-border: rgba(226, 138, 176, .3);
}

:root[data-theme="light"] {
  --paper: #F5F6F3;
  --paper-raised: #FFFFFF;
  --ink: #161B18;
  --ink-soft: #5B6560;
  --line: #DEE3DE;
  --accent: #0E7A5F;
  --accent-soft: #E4F2ED;
  --money: #7A5A1E;
  --money-soft: rgba(185, 134, 47, .14);
  --risk: #A83A2C;
  --risk-soft: rgba(168, 58, 44, .14);
  --shadow: 0 1px 2px rgba(15, 20, 15, .03), 0 16px 32px -22px rgba(15, 20, 15, .28);

  --input-accent: #6B3FA0;
  --input-accent-soft: rgba(107, 63, 160, .20);
  --input-tint: var(--input-accent-soft);
  --input-tint-border: rgba(107, 63, 160, .42);

  --output-accent: #A13D5E;
  --output-accent-soft: rgba(161, 61, 94, .12);
  --output-tint: var(--output-accent-soft);
  --output-tint-border: rgba(161, 61, 94, .32);
}

* {
  box-sizing: border-box;
}

/* .wallet/.user-chip etc set their own `display`, which (being an author style) otherwise
   beats the UA stylesheet's [hidden]{display:none} - without this, toggling .hidden via the
   hidden IDL property on those elements would silently do nothing. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  line-height: 1.5;
}

.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.muted {
  color: var(--ink-soft);
}

h2 {
  font-family: var(--font-display);
  font-weight: 600;
  margin: 0 0 4px;
  font-size: 20px;
}

/* --- header --- */

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 16px 24px;
  border-bottom: 1px solid var(--line);
  background: var(--paper-raised);
  position: sticky;
  top: 0;
  z-index: 10;
}

.header-left {
  display: flex;
  align-items: center;
  gap: 12px;
}

.rail-toggle-btn {
  display: none;
  width: 30px;
  height: 30px;
  padding: 0;
  align-items: center;
  justify-content: center;
}

.rail-toggle-btn svg {
  width: 16px;
  height: 16px;
}

.brand {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  letter-spacing: .01em;
}

.brand:hover {
  color: var(--accent);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.wallet {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--accent-soft);
  border-radius: 20px;
  padding: 6px 10px;
}

#balance {
  font-weight: 600;
  color: var(--accent);
  padding: 0 4px;
}

.user-chip {
  display: flex;
  align-items: center;
  padding: 7px 12px;
  border-radius: 20px;
  background: var(--paper);
  border: 1px solid var(--line);
}

.user-chip-name {
  font-size: 13px;
  font-weight: 500;
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* sign-in sits where the wallet balance normally does (see #wallet-chip/#signin-link
   toggling in checkAuth()) - the rest of the app stays browsable signed out, so this is a
   small header control, not a wall blocking the page. */

.signin-link {
  padding: 7px 14px;
  font-size: 13px;
  text-decoration: none;
}

/* every number field (top-up amount, extract-patterns top_n, generate-script duration, ...)
   - the native up/down spinner adds width and visual noise these compact inputs don't need */

input[type="number"] {
  -moz-appearance: textfield;
}

input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}

.icon-btn {
  border-radius: 20px;
  font-size: 13px;
}

.icon-btn svg {
  display: block;
  width: 15px;
  height: 15px;
}

.icon-btn--round {
  width: 22px;
  height: 22px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  line-height: 1;
  border-radius: 50%;
}

/* --- project banner --- */

.project-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--accent-soft);
  font-size: 13px;
  padding: 8px 24px;
  border-bottom: 1px solid var(--line);
}

.project-banner-icon {
  display: inline-flex;
  width: 15px;
  height: 15px;
  color: var(--accent);
  flex-shrink: 0;
}

.project-banner strong {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--ink);
}

.project-banner .num {
  color: var(--ink-soft);
}

/* which of the two guided pipelines this project uses - see PIPELINE_ICON/PIPELINE_LABEL
   in app.js. The one place this is shown (see renderWorkspaceRail's single cta button,
   which doesn't repeat the project's name/id already here). */
.project-banner-pipeline {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-left: 8px;
  padding: 3px 10px 3px 6px;
  border-radius: 20px;
  background: var(--paper-raised);
  border: 1px solid var(--line);
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
}

.project-banner-pipeline svg {
  width: 12px;
  height: 12px;
}

/* --- category nav: modules live in their own right-hand column (see renderCategoryNav() in
   app.js) - a mirror of the left rail's "your stuff" column, so the two sides read as one
   consistent pattern instead of one flat list. Every category shown at once, each its own
   vertical list - nothing hidden behind a click. Sticky, same offset as .rail. */

.category-nav {
  position: sticky;
  top: 88px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.category-nav[hidden] {
  display: none;
}

.category-section-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 8px;
}

.category-section-label svg {
  width: 13px;
  height: 13px;
}

.category-chips {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* --- error toast: replaces alert() so a failed call can't block the tab --- */

.toast-stack {
  position: fixed;
  top: 76px;
  right: 16px;
  z-index: 1000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-width: 340px;
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: var(--paper-raised);
  border: 1px solid var(--risk-soft);
  border-left: 3px solid var(--risk);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 13px;
  color: var(--ink);
  box-shadow: var(--shadow);
  animation: toast-in .15s ease-out;
}

.toast-dismiss {
  margin-left: auto;
  border: none;
  background: none;
  color: var(--ink-soft);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  padding: 0 0 0 8px;
}

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

/* --- gallery rails: real generated media in the wide-viewport gutters, purely decorative
   (non-interactive, faded top/bottom, low opacity) so the centered work zone stays the one
   thing that's actually operable - this is what's already in the Library, just given
   somewhere to live instead of only surfacing after you go look for it */

.gallery-rail {
  display: none;
  position: fixed;
  top: 96px;
  bottom: 24px;
  width: 180px;
  overflow: hidden;
  pointer-events: none;
  opacity: .5;
  -webkit-mask-image: linear-gradient(to bottom, transparent, black 14%, black 86%, transparent);
  mask-image: linear-gradient(to bottom, transparent, black 14%, black 86%, transparent);
}

.gallery-rail--left { left: 28px; }
.gallery-rail--right { right: 28px; }

.gallery-rail img,
.gallery-rail video {
  width: 100%;
  display: block;
  object-fit: cover;
  aspect-ratio: 3 / 4;
  border-radius: 14px;
  margin-bottom: 14px;
  box-shadow: var(--shadow);
}

@media (min-width: 1680px) {
  .gallery-rail.has-items {
    display: block;
  }
}

/* --- shell: rail + stage --- */

.shell {
  max-width: 1320px;
  margin: 0 auto;
  padding: 40px 24px 120px;
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr) 240px;
  gap: 32px;
  align-items: start;
}

/* The rail only holds "your stuff" now (projects, outputs, admin) - the step/tool modules
   live in their own column on the right instead (#category-nav, see renderCategoryNav in
   app.js) - so this stays short enough that it doesn't need to scroll independently of the
   page. */
.rail {
  position: sticky;
  top: 88px;
  display: flex;
  flex-direction: column;
  gap: 28px;
}

/* No inner scrollbar on the project list on purpose - now that modules live in the top bar
   (not the rail), a long project list has nothing below it left to push down. If it grows
   past the viewport, the whole page just scrolls, same as any normal page. */

.rail-overlay[hidden] {
  display: none;
}

@media (max-width: 860px) {
  .rail-toggle-btn {
    display: inline-flex;
  }

  .shell {
    grid-template-columns: 1fr;
    gap: 24px;
    padding: 24px 16px 100px;
  }

  .rail {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 280px;
    max-width: 85vw;
    margin: 0;
    padding: 88px 20px 24px;
    background: var(--paper-raised);
    border-right: 1px solid var(--line);
    box-shadow: var(--shadow);
    z-index: 50;
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform .22s ease;
  }

  .rail--open {
    transform: translateX(0);
  }

  .rail-overlay {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(10, 12, 9, .45);
    z-index: 40;
  }

  /* Modules are tucked behind the same hamburger as the project rail on mobile instead of
     always taking up screen height above the stage - "unless it's mobile" is the operative
     phrase (see toggleRail()/closeRail() in app.js, which open/close both together). Its own
     slide-in panel from the right, so it doesn't collide with the rail's from the left. */
  .category-nav {
    display: none;
  }

  .category-nav.category-nav--open {
    display: flex;
    flex-direction: column;
    gap: 20px;
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: 280px;
    max-width: 85vw;
    margin: 0;
    padding: 88px 20px 24px;
    background: var(--paper-raised);
    border-left: 1px solid var(--line);
    box-shadow: var(--shadow);
    z-index: 50;
    overflow-y: auto;
  }
}

.rail-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.rail-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.rail-label::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ink-soft);
  flex-shrink: 0;
}

.nav-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--paper-raised);
  color: var(--ink);
  cursor: pointer;
  transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease, background-color .12s ease, color .12s ease;
}

.nav-item-icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  transition: inherit;
}

.nav-item-icon svg {
  width: 13px;
  height: 13px;
}

.nav-item-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* done state - a filled checkmark badge (not just coloured text) plus a soft tint on the
   whole row, so "already ran this" reads at a glance instead of requiring a close look at
   a small ✓ off to the side */

.nav-item-done {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--paper-raised);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
}

.nav-item--done:not(.nav-item--active) {
  background: var(--accent-soft);
  border-color: var(--accent-soft);
}

.nav-item--active .nav-item-done {
  background: var(--paper-raised);
  color: var(--accent);
}

/* locked state - visible (not hidden) so the rail always shows the pipeline's true shape,
   but dimmed and inert until its prerequisite is met */

.nav-item--locked {
  opacity: .5;
  cursor: not-allowed;
}

.nav-item--locked:hover {
  transform: none;
  box-shadow: none;
  border-color: var(--line);
  background: var(--paper-raised);
  color: var(--ink);
}

.nav-item--locked:hover .nav-item-icon {
  transform: none;
}

.nav-item-lock {
  flex-shrink: 0;
  display: inline-flex;
  color: var(--ink-soft);
}

.nav-item-lock svg {
  width: 13px;
  height: 13px;
}

.nav-item:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
  transform: translateY(-1px);
  box-shadow: 0 6px 14px -9px rgba(0, 0, 0, .4);
}

.nav-item:hover .nav-item-icon {
  transform: scale(1.12);
}

.nav-item:active {
  transform: translateY(0) scale(.97);
}

.nav-item--active,
.nav-item--active:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper-raised);
  font-weight: 700;
  transform: translateY(-1px);
  box-shadow: 0 8px 20px -8px var(--accent-soft);
}

.nav-item--active .nav-item-icon {
  background: rgba(255, 255, 255, .22);
  color: var(--paper-raised);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, .28);
}

.nav-item--ghost {
  color: var(--ink-soft);
  border: 1px dashed var(--line);
}

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

.nav-item--ghost.nav-item--active,
.nav-item--ghost.nav-item--active:hover {
  background: var(--accent);
  border-style: solid;
  border-color: var(--accent);
  color: var(--paper-raised);
}

/* projects get a rounded-square badge + bolder weight so they read as a different kind of
   thing from step/tool modules (which stay circular, regular weight) at a glance */

.nav-item--project {
  font-weight: 600;
  background: var(--accent-soft);
  border-color: var(--accent-soft);
}

.nav-item--project:hover {
  border-color: var(--accent);
}

.nav-item--project .nav-item-icon {
  border-radius: 6px;
}

.rail-back {
  align-self: flex-start;
  font: inherit;
  font-size: 13px;
  color: var(--ink-soft);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

.rail-back:hover {
  color: var(--accent);
}

.cta-btn {
  font: inherit;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 14px;
  padding: 9px 14px;
  border-radius: 8px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--paper-raised);
  cursor: pointer;
}

.cta-btn:hover {
  opacity: .92;
}

.cta-btn--secondary {
  background: var(--input-tint);
  border-color: var(--input-tint-border);
  color: var(--input-accent);
}

/* --- stage: cards, forms, output --- */

.stage {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.card {
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  box-shadow: var(--shadow);
}

.card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

.card-sub {
  margin: 0;
  font-size: 13px;
  color: var(--ink-soft);
  max-width: 48ch;
}

/* pipeline type picker (new project, step 1) - two big clickable cards instead of a
   dropdown, so the choice (and its consequence - which module set you get afterward) is
   obvious before you commit to it */
.pipeline-picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}

.pipeline-option {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  text-align: left;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--paper);
  cursor: pointer;
  font: inherit;
  color: var(--ink);
  transition: border-color .12s ease, transform .12s ease, box-shadow .12s ease;
}

.pipeline-option:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}

.pipeline-option-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
}

.pipeline-option-icon svg {
  width: 18px;
  height: 18px;
}

.pipeline-option-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 16px;
}

.pipeline-option-desc {
  margin: 0;
  font-size: 13px;
  color: var(--ink-soft);
}

.chip {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .04em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 20px;
  white-space: nowrap;
  flex-shrink: 0;
}

.chip--free {
  background: var(--accent-soft);
  color: var(--accent);
}

.chip--billed {
  background: var(--money-soft);
  color: var(--money);
}

.chip--risk {
  background: var(--risk-soft);
  color: var(--risk);
}

/* score-video's QC result - scores + a pass/reject verdict, see OUTPUT_KIND's "qc" kind */

.qc-result {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.qc-scores {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.qc-score-row {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
  padding: 4px 0;
  border-bottom: 1px solid var(--line);
}

.qc-flags {
  margin: 0;
  padding-left: 18px;
  font-size: 13px;
  color: var(--risk);
}

/* generic structured-result rendering (app.js's prettyValue/patternsHtml) - a readable
   stand-in for dumping raw JSON at someone who isn't reading it as a developer would */

.kv-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.kv-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 13px;
  padding: 4px 0;
  border-bottom: 1px solid var(--line);
}

.kv-key {
  color: var(--ink-soft);
  flex-shrink: 0;
}

.kv-value {
  text-align: right;
}

.pill-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.pill {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 3px 9px;
  border-radius: 10px;
  background: var(--accent-soft);
  color: var(--accent);
  max-width: 100%;
  /* pills are built for a short tag, but content ultimately comes from an LLM that doesn't
     always cooperate (see patternsHtml's own hook_type note) - wrap rather than force a
     long value into one broken horizontal strip */
  white-space: normal;
  overflow-wrap: break-word;
}

.result-cards {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.result-card {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.result-card-title {
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
}

.result-card-title:hover {
  color: var(--accent);
}

.result-card-hook {
  margin: 0;
  font-size: 13px;
  font-style: italic;
  color: var(--ink-soft);
}

.result-card-hook-type {
  margin: 0;
  font-size: 12px;
}

.io-block {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* input/output are told apart by border style, not a filled color wash: solid means "this
   holds something", dashed means "empty - will populate once you run it". A filled icon
   badge stands in for the old "INPUT"/"OUTPUT" text label. */

/* input = violet ("give this"), output = rose ("get this") - two dedicated hues instead of
   a neutral wash, so each zone reads as a distinct, deliberate destination. An output with
   nothing in it yet stays neutral and dashed (nothing to show off) and only takes on its
   rose fill once a result actually lands - the color becomes the "you got something" cue. */

.io-block--input,
.io-block--output {
  border-radius: 10px;
  padding: 12px 14px;
  border: 1px solid transparent;
}

.io-block--input {
  background: var(--input-tint);
  border-color: var(--input-tint-border);
}

.io-block--output {
  background: var(--output-tint);
  border-color: var(--output-tint-border);
}

.io-block--output:has(.io-empty),
.io-block--output:has(.output--empty) {
  background: var(--paper);
  border-color: var(--line);
  border-style: dashed;
}

.io-label {
  display: flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .06em;
  text-transform: uppercase;
  margin-bottom: 6px;
}

.io-label-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  flex-shrink: 0;
}

.io-label-icon svg {
  width: 9px;
  height: 9px;
}

.io-block--input .io-label { color: var(--input-accent); }
.io-block--input .io-label-icon { background: var(--input-accent); color: var(--paper-raised); }

.io-block--output .io-label { color: var(--output-accent); }
.io-block--output .io-label-icon { background: var(--output-accent); color: var(--paper-raised); }

.io-block--output:has(.io-empty) .io-label,
.io-block--output:has(.output--empty) .io-label {
  color: var(--ink-soft);
}

.io-block--output:has(.io-empty) .io-label-icon,
.io-block--output:has(.output--empty) .io-label-icon {
  background: transparent;
  color: var(--ink-soft);
  box-shadow: inset 0 0 0 1px var(--line);
}

.io-empty {
  margin: 0;
  font-size: 13px;
  color: var(--ink-soft);
  font-style: italic;
}

.panel-fields {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.field label {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-soft);
}

.field input,
.field textarea,
.field select,
select,
input[type="text"],
input[type="number"] {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--input-tint-border);
  border-radius: 9px;
  background: var(--input-tint);
  color: var(--ink);
  font: inherit;
  transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}

.field input::placeholder,
.field textarea::placeholder {
  color: var(--ink-soft);
  opacity: .75;
}

.field input:hover,
.field textarea:hover,
.field select:hover {
  border-color: var(--input-accent);
}

.field input:focus,
.field textarea:focus,
.field select:focus {
  outline: none;
  border-color: var(--input-accent);
  background: var(--paper-raised);
  box-shadow: 0 0 0 3px var(--input-accent-soft);
}

.field textarea {
  min-height: 70px;
  resize: vertical;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
}

.checkbox-label input {
  width: auto;
}

/* custom file field */

.file-field {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px dashed var(--input-tint-border);
  border-radius: 8px;
  cursor: pointer;
  background: var(--input-tint);
  color: var(--ink-soft);
  font-size: 13px;
}

.file-field:hover {
  border-color: var(--input-accent);
  color: var(--input-accent);
}

.file-field input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.file-field-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--input-accent-soft);
  color: var(--input-accent);
  font-weight: 700;
  font-size: 14px;
}

.file-field--filled {
  color: var(--ink);
  border-style: solid;
  border-color: var(--input-accent);
}

.file-field--filled .file-field-icon {
  background: var(--input-accent);
  color: var(--paper-raised);
}

/* buttons */

button {
  font: inherit;
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper-raised);
  color: var(--ink);
  cursor: pointer;
}

button:hover:not(:disabled):not(.primary):not(.run-btn):not(.cta-btn):not(.nav-item) {
  border-color: var(--accent);
  color: var(--accent);
}

button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper-raised);
}

button.primary:hover {
  color: var(--paper-raised);
  opacity: .9;
}

.panel-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* the gradient runs input-hue -> output-hue left to right, and the arrow nudges further
   right on hover - both read as "this turns into that" rather than a plain neutral button */

.run-btn {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: inherit;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 14px;
  padding: 9px 18px;
  border-radius: 8px;
  border: 1px solid transparent;
  background: linear-gradient(90deg, var(--input-accent), var(--output-accent));
  background-size: 160% 100%;
  background-position: 0% 0%;
  color: var(--paper-raised);
  cursor: pointer;
  transition: background-position .3s ease, opacity .15s ease;
}

.run-btn:hover {
  background-position: 100% 0%;
  opacity: .95;
  color: var(--paper-raised);
}

.run-btn:active {
  transform: translateY(1px);
}

.run-btn-flow {
  display: inline-flex;
  transition: transform .25s ease;
}

.run-btn-flow svg {
  width: 14px;
  height: 14px;
}

.run-btn:hover .run-btn-flow {
  transform: translateX(4px);
}

/* loading state - see withLoading() in app.js. Applies to any button (run-btn, cta-btn,
   icon-btn, plain button) while its async action is in flight. */

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

.btn-spinner {
  display: inline-block;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid rgba(127, 127, 127, .35);
  border-top-color: currentColor;
  animation: btn-spin .6s linear infinite;
  flex-shrink: 0;
}

button.is-loading {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: wait;
  opacity: .85;
}

.btn-secondary {
  align-self: flex-start;
  font: inherit;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 14px;
  padding: 9px 18px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--paper-raised);
  color: var(--ink-soft);
  cursor: pointer;
}

.btn-secondary:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.btn-secondary:active {
  transform: translateY(1px);
}

/* guided full pipeline */

/* Same accent color regardless of which of the two guided pipelines is running - color here
   means "a guide is active", not "which one" (the icon does that job instead, see
   PIPELINE_ICON in app.js). */
.guide-progress {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--accent);
  background: var(--accent-soft);
  padding: 5px 12px 5px 8px;
  border-radius: 20px;
}

.guide-progress-icon {
  display: inline-flex;
  width: 20px;
  height: 20px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent);
  color: var(--paper-raised);
  flex-shrink: 0;
}

.guide-progress-icon svg {
  width: 11px;
  height: 11px;
}

.guide-progress-count {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
}

.guide-progress-title {
  font-size: 12px;
  letter-spacing: .02em;
  border-left: 1px solid currentColor;
  opacity: .5;
  padding-left: 8px;
}

/* roadmap header icon - which pipeline this is, at a glance (see pipelineRoadmapHtml) */
.pipeline-badge-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  vertical-align: middle;
  margin-right: 8px;
}

.pipeline-badge-icon svg {
  width: 14px;
  height: 14px;
}

.guide-bar {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-top: 18px;
  border-top: 1px dashed var(--line);
}

.guide-done {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
}

.guide-done-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent-soft);
  font-size: 11px;
  font-weight: 700;
}

.roadmap {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.roadmap li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-size: 13px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper);
}

.roadmap-index {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 11px;
  margin-top: 1px;
}

.roadmap-item-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.roadmap-item-name {
  font-weight: 600;
}

.roadmap-item-desc {
  margin: 0;
  font-size: 13px;
  line-height: 1.4;
}

/* output */

.output {
  display: flex;
  flex-direction: column;
  gap: 12px;
  font-size: 14px;
}

.output--empty {
  color: var(--ink-soft);
  font-size: 13px;
  font-style: italic;
}

.output img,
.output video,
.output audio {
  width: 100%;
  border-radius: 8px;
}

/* media frame - wraps a generated image/video with an always-visible Download control so
   "save the currently made image" is one click, not right-click-save-as guesswork */

.media-frame {
  position: relative;
  display: block;
  border-radius: 10px;
  overflow: hidden;
  background: var(--paper);
}

.media-frame-media {
  display: block;
  width: 100%;
}

.download-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: rgba(10, 14, 10, .55);
  color: #fff;
  transition: background-color .15s ease, transform .15s ease;
}

.download-btn svg {
  width: 15px;
  height: 15px;
}

.download-btn:hover {
  background: var(--output-accent);
  color: #fff;
  transform: scale(1.06);
}

.audio-frame {
  display: flex;
  align-items: center;
  gap: 10px;
}

.audio-frame audio {
  flex: 1;
  min-width: 0;
  width: auto;
}

.audio-frame .download-btn {
  position: static;
  flex-shrink: 0;
  background: var(--output-accent-soft);
  color: var(--output-accent);
}

.audio-frame .download-btn:hover {
  background: var(--output-accent);
  color: #fff;
}

/* library (GET /tools/outputs) - every standalone-tool output, kept viewable after the
   panel that made it closes */

.library-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 16px;
}

.library-item {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.library-media {
  aspect-ratio: 1 / 1;
  background: var(--output-tint);
}

.library-media .media-frame-media {
  height: 100%;
  object-fit: cover;
}

.library-media--audio {
  aspect-ratio: auto;
  padding: 14px;
  border-radius: 10px;
  background: var(--output-tint);
  border: 1px solid var(--output-tint-border);
}

.library-item-when {
  font-size: 13px;
}

.output-table {
  display: flex;
  flex-direction: column;
  overflow-x: auto;
}

.output-table-head,
.output-table-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) repeat(3, 64px);
  align-items: center;
  gap: 12px;
}

.output-table-head span:not(:first-child),
.output-table-row span:not(:first-child) {
  text-align: right;
}

.output-table-head {
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.output-table-row {
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  text-decoration: none;
  color: var(--ink);
  font-size: 13px;
}

.output-table-row .num {
  font-size: 12px;
  color: var(--ink-soft);
}

.output-table-row:last-child {
  border-bottom: none;
}

.output-table-row:hover .output-table-title {
  color: var(--accent);
}

.output-table-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.output pre {
  white-space: pre-wrap;
  margin: 0;
  font-size: 12px;
}

/* empty state */

.empty-state {
  border: 1px dashed var(--line);
  border-radius: 16px;
  padding: 56px 32px;
  text-align: center;
  color: var(--ink-soft);
}

.empty-state h2 {
  color: var(--ink);
  font-size: 20px;
}

.empty-state p {
  margin: 10px auto 0;
  max-width: 42ch;
  font-size: 14px;
}

/* shot gallery */

#shots-gallery {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.shot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 14px;
}

.shot-card {
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.shot-media {
  width: 100%;
  aspect-ratio: 9 / 16;
  background: var(--paper);
  display: block;
}

.shot-media .media-frame-media {
  height: 100%;
  object-fit: cover;
}

.shot-media--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 22px;
  color: var(--ink-soft);
}

.shot-card-body {
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.shot-num {
  font-size: 12px;
  color: var(--ink-soft);
}

.shot-card-body p {
  margin: 0;
  font-size: 12px;
  line-height: 1.4;
}

/* settings dialog */

dialog {
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 24px;
  max-width: 420px;
  width: 90vw;
  background: var(--paper-raised);
  color: var(--ink);
}

dialog::backdrop {
  background: rgba(0, 0, 0, .35);
}

/* wider than the base dialog - a user's name/email/balance/top-up form in one row needs
   more room than a plain settings form */
#admin-dialog {
  max-width: 560px;
}

#admin-dialog .admin-user-list {
  max-height: 60vh;
  overflow-y: auto;
  margin-top: 8px;
}

dialog .field {
  margin-bottom: 10px;
}

.models-note {
  font-size: 13px;
  color: var(--ink-soft);
  border-top: 1px solid var(--line);
  padding-top: 12px;
  margin: 4px 0 0;
}

.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

/* admin panel (GET/POST /admin/*, admin-only - see app/api/routes_admin.py) */

.admin-user-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.admin-user-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 16px;
  padding: 12px 4px;
  border-bottom: 1px solid var(--line);
}

.admin-user-row:last-child {
  border-bottom: none;
}

.admin-user-name {
  font-weight: 600;
}

.admin-user-email {
  font-size: 12px;
}

.admin-user-balance {
  font-weight: 600;
  color: var(--output-accent);
}

.admin-topup-form {
  display: flex;
  align-items: center;
  gap: 6px;
}

.admin-topup-form input {
  width: 70px;
  padding: 6px 8px;
}

/* past scripts (every storyboard attempt, see showStoryboardHistory in app.js) */

.storyboard-history-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.storyboard-history-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-start;
  text-align: left;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--paper-raised);
  font: inherit;
  color: var(--ink);
  cursor: pointer;
}

.storyboard-history-item:hover {
  border-color: var(--accent);
}

.storyboard-history-item--current {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.storyboard-history-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
}

.storyboard-history-meta {
  font-size: 12px;
}
