/* ==========================================================================
   KLIKA Content Generator — design system
   ==========================================================================

   Dark cinematic glassmorphism built on the KLIKA brand board. Rewritten in
   the v1.8 UI pass; the brand colours, the self-hosted fonts and the two
   contrast rules below are carried over unchanged from the v1.1 design because
   they were validated, not guessed.

   THE TWO HARD COLOUR RULES (do not break these):

   1. Raw --klika-violet (#7914AF) fails WCAG as small text (~2.4:1 on our
      background). It is a FILL / GRADIENT / GLOW / BORDER colour only. When
      violet must carry text, use --klika-violet-text (#C79BE8).
   2. White text on a solid --klika-turquoise fill fails (~1.9:1). Turquoise
      fills always take DARK text (--bg-base). This is why .nav-link.is-active
      and .badge-accent set a dark foreground.

   WHAT CHANGED IN v1.8, AND WHY

   - TYPE SCALE. The old file had 17 font sizes and 15 of them sat between
     11.5px and 18.4px, so a card title, a field label and a hint were all
     nearly the same size. Hierarchy now comes from a real stepped scale plus
     weight, and nothing renders below --fs-2xs (13px).
   - SPACING RATIO. Card padding used to be LARGER than the gap between cards
     (2rem inside vs 1.5rem between), so a stack of cards read as one slab.
     That is inverted now: --gap-section > --pad-panel.
   - SURFACE TIERS. There were four different backdrop-blur values and three
     card radii. There are now exactly three surfaces: panel (floats, blurred),
     inset (nested inside a panel, never blurred — nested blur is both a perf
     trap and visually muddy), and media (the flat bed an image sits on).
   - MEDIA BOXES. Five separate implementations of "letterbox an image at 4:5
     or 1:1" are now one .media-box. object-fit stays CONTAIN, which is
     load-bearing: it is the v1.5 fix that stopped 1:1 static posts being
     cropped in the UI. Do not change it to cover.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts (self-hosted, no CDN — same files as before)
   -------------------------------------------------------------------------- */

@font-face {
  font-family: 'Anta';
  src: url('/static/fonts/Anta-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Red Hat Display';
  src: url('/static/fonts/RedHatDisplay-Variable.woff2') format('woff2');
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* --- brand ------------------------------------------------------------ */
  --klika-navy: #011378;
  --klika-royal: #0020D4;
  --klika-turquoise: #00CFFF;
  --klika-violet: #7914AF;
  --klika-violet-text: #C79BE8;

  /* --- surface tiers ---------------------------------------------------- */
  --bg-base: #05081f;
  --bg-base-2: #070b28;
  /* tier 1: floats above the page, blurred */
  --surface-panel: rgba(15, 23, 63, 0.55);
  --surface-panel-strong: rgba(18, 26, 68, 0.78);
  /* tier 2: nested inside a panel, never blurred */
  --surface-inset: rgba(8, 13, 40, 0.62);
  --surface-inset-hover: rgba(13, 20, 58, 0.82);
  /* tier 3: the bed an image or preview sits on */
  --surface-media: #04071a;
  --surface-input: rgba(4, 7, 22, 0.72);

  /* --- borders ---------------------------------------------------------- */
  --border-subtle: rgba(0, 207, 255, 0.09);
  --border: rgba(0, 207, 255, 0.16);
  --border-strong: rgba(0, 207, 255, 0.34);
  --border-hover: rgba(0, 207, 255, 0.5);
  /* a 1px inner highlight along the top edge — the thing that reads as
     "lit from above" and makes a flat panel look like a physical surface */
  --edge-light: inset 0 1px 0 rgba(255, 255, 255, 0.06);

  /* --- text ------------------------------------------------------------- */
  --text-primary: #F4F7FF;
  --text-secondary: #AAB4D8;
  --text-muted: #949CC2;
  --text-placeholder: #7C86AE;

  /* --- semantic --------------------------------------------------------- */
  --success: #35D48A;
  --warning: #F7C533;
  --danger: #FF6B7A;

  /* --- type scale ------------------------------------------------------- */
  --fs-display: clamp(2rem, 1.5rem + 2.1vw, 3rem);
  --fs-h1: clamp(1.9rem, 1.5rem + 1.6vw, 2.6rem);
  --fs-h2: clamp(1.35rem, 1.2rem + 0.6vw, 1.6rem);
  --fs-h3: 1.0625rem;
  --fs-lg: 1.0625rem;
  --fs-body: 1rem;
  --fs-sm: 0.9375rem;
  --fs-xs: 0.875rem;
  --fs-2xs: 0.8125rem; /* hard floor — nothing smaller than 13px */

  --fw-normal: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;
  --fw-black: 800;

  --lh-tight: 1.18;
  --lh-snug: 1.35;
  --lh-body: 1.6;

  --tracking-tight: -0.015em;
  --tracking-eyebrow: 0.11em;

  --font-body: 'Red Hat Display', system-ui, -apple-system, sans-serif;
  --font-display: 'Anta', 'Red Hat Display', system-ui, sans-serif;
  --font-mono: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;

  /* --- spacing ---------------------------------------------------------- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: 4rem;
  /* the ratio that fixes "everything reads as one slab": the gap BETWEEN
     panels must beat the padding INSIDE them */
  --pad-panel: 1.75rem;
  --gap-section: 2.5rem;

  /* --- radius ----------------------------------------------------------- */
  --radius-xs: 8px;
  --radius-sm: 12px;
  --radius-md: 16px;
  --radius-lg: 22px;
  --radius-xl: 28px;
  --radius-pill: 999px;

  /* --- elevation (3 steps, used consistently) --------------------------- */
  --shadow-1: 0 2px 10px rgba(1, 4, 18, 0.4);
  --shadow-2: 0 14px 44px rgba(1, 4, 18, 0.55);
  --shadow-3: 0 30px 80px rgba(1, 4, 18, 0.68);
  --glow-turquoise: 0 0 32px rgba(0, 207, 255, 0.28);
  --glow-turquoise-soft: 0 0 20px rgba(0, 207, 255, 0.16);
  --glow-violet: 0 0 34px rgba(121, 20, 175, 0.26);

  /* --- blur (one value app-wide) ---------------------------------------- */
  --blur-panel: blur(20px) saturate(150%);

  /* --- gradients -------------------------------------------------------- */
  --grad-primary: linear-gradient(135deg, var(--klika-navy) 0%, var(--klika-royal) 65%, var(--klika-turquoise) 160%);
  --grad-primary-hover: linear-gradient(135deg, var(--klika-navy) 0%, var(--klika-royal) 55%, var(--klika-turquoise) 130%);
  --grad-text: linear-gradient(120deg, var(--klika-violet-text) 0%, var(--klika-turquoise) 55%, #FFFFFF 100%);
  --grad-rule: linear-gradient(90deg, transparent, var(--border-strong) 22%, var(--border-strong) 78%, transparent);

  /* --- z-index scale ---------------------------------------------------- */
  --z-behind: -1;
  --z-base: 1;
  --z-sticky: 40;
  --z-overlay: 100;

  /* --- container widths ------------------------------------------------- */
  --w-narrow: 760px;
  --w-default: 1180px;
  --w-wide: 1340px;

  /* --- motion ----------------------------------------------------------- */
  --dur-fast: 160ms;
  --dur-base: 240ms;
  --dur-slow: 380ms;
  --ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);

  color-scheme: dark;
}

/* --------------------------------------------------------------------------
   3. Reset & base
   -------------------------------------------------------------------------- */

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

* {
  scrollbar-color: rgba(0, 207, 255, 0.22) transparent;
  scrollbar-width: thin;
}

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

body {
  margin: 0;
  min-height: 100dvh;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--text-primary);
  background: var(--bg-base);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--fw-normal);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
  margin: 0 0 var(--space-3);
  color: var(--text-primary);
}

h1 { font-size: var(--fs-display); }
h2 { font-size: var(--fs-h1); }
h3 { font-size: var(--fs-h2); }
h4 { font-size: var(--fs-h3); }

p {
  margin: 0 0 var(--space-3);
}

p:last-child {
  margin-bottom: 0;
}

small {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

a {
  color: var(--klika-turquoise);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-standard);
}

a:hover {
  color: #7FE4FF;
}

/* Links inside body copy keep an underline so they are not colour-only. */
p a,
.field-hint a,
.alert a,
.prose a {
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-color: rgba(0, 207, 255, 0.4);
}

img {
  max-width: 100%;
  display: block;
}

button {
  font-family: inherit;
}

:focus-visible {
  outline: 2px solid var(--klika-turquoise);
  outline-offset: 3px;
  border-radius: var(--radius-xs);
}

/* Pill-shaped controls need a pill-shaped focus ring; the generic rule above
   drew a 8px-radius rectangle over a 999px-radius button. */
.btn:focus-visible,
.nav-link:focus-visible,
.chip-radio input:focus-visible + label,
.status-chip:focus-visible {
  outline: 2px solid var(--klika-turquoise);
  outline-offset: 3px;
  border-radius: var(--radius-pill);
}

::selection {
  background: rgba(0, 207, 255, 0.3);
  color: #fff;
}

/* --------------------------------------------------------------------------
   4. Ambient backdrop
   --------------------------------------------------------------------------
   Three stacked layers, all fixed and non-interactive:
     .ambient-glow   — the slow colour blooms (GSAP drifts these)
     ::before        — a fine dot grid, the "technical" texture
     ::after         — a horizon beam across the top
   -------------------------------------------------------------------------- */

/* The glow below is deliberately larger than the viewport and drifts. That is
   fine visually and NOT fine for layout: it made the page scroll sideways on a
   768px tablet. `clip` contains it without turning <html> into a scroll
   container, which `hidden` would do — and that would kill every position:
   sticky in the app. Real CONTENT overflow is still caught, by
   tools_fragment_click_test.py, which measures non-fixed elements. */
html { overflow-x: clip; }

.ambient-glow {
  position: fixed;
  inset: -20%;
  z-index: var(--z-behind);
  pointer-events: none;
  background:
    radial-gradient(ellipse 52% 38% at 14% 2%, rgba(0, 207, 255, 0.20), transparent 62%),
    radial-gradient(ellipse 48% 40% at 92% 10%, rgba(121, 20, 175, 0.22), transparent 62%),
    radial-gradient(ellipse 62% 50% at 50% 104%, rgba(0, 32, 212, 0.26), transparent 64%);
  will-change: transform;
}

.ambient-glow::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(0, 207, 255, 0.16) 1px, transparent 1px);
  background-size: 40px 40px;
  opacity: 0.32;
  mask-image: radial-gradient(ellipse 70% 60% at 50% 30%, #000 20%, transparent 78%);
  -webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 30%, #000 20%, transparent 78%);
}

.ambient-glow::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 16%;
  width: 120%;
  height: 340px;
  transform: translateX(-50%);
  background: radial-gradient(ellipse 50% 50% at 50% 50%, rgba(0, 207, 255, 0.13), transparent 70%);
  filter: blur(30px);
}

/* --------------------------------------------------------------------------
   5. Layout primitives
   -------------------------------------------------------------------------- */

.container {
  width: 100%;
  max-width: var(--w-default);
  margin: 0 auto;
  padding: 0 var(--space-5);
}

.container-narrow { max-width: var(--w-narrow); }
.container-wide { max-width: var(--w-wide); }

main.container {
  padding-top: var(--space-7);
  padding-bottom: var(--space-8);
}

/* The one vertical-rhythm utility. Templates must use this instead of inline
   margin overrides (the old file had 14 of those scattered across templates). */
.section-stack > * + * {
  margin-top: var(--gap-section);
}

.stack-sm > * + * { margin-top: var(--space-3); }
.stack-md > * + * { margin-top: var(--space-5); }

.text-center { text-align: center; }

.rule {
  height: 1px;
  border: 0;
  margin: var(--space-6) 0;
  background: var(--grad-rule);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: var(--space-4);
  z-index: var(--z-overlay);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-pill);
  background: var(--klika-turquoise);
  color: var(--bg-base);
  font-weight: var(--fw-bold);
  font-size: var(--fs-xs);
  transform: translateY(-160%);
  transition: transform var(--dur-base) var(--ease-standard);
}

.skip-link:focus-visible {
  transform: translateY(0);
  color: var(--bg-base);
}

/* --------------------------------------------------------------------------
   6. Topbar
   -------------------------------------------------------------------------- */

.topbar {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  background: rgba(5, 8, 31, 0.72);
  backdrop-filter: var(--blur-panel);
  -webkit-backdrop-filter: var(--blur-panel);
  border-bottom: 1px solid var(--border-subtle);
}

.topbar-inner {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  min-height: 68px;
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-display);
  font-size: 1.05rem;
  letter-spacing: 0.16em;
  color: var(--text-primary);
  flex-shrink: 0;
}

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

.klika-mark-wrap {
  display: inline-flex;
  filter: drop-shadow(0 0 12px rgba(0, 207, 255, 0.35));
}

.brand-word {
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.topbar-links {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-left: var(--space-4);
}

.nav-link {
  position: relative;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  transition: color var(--dur-fast) var(--ease-standard);
}

.nav-link:hover { color: var(--text-primary); }

.nav-link.is-active {
  /* Hard rule 2: dark text on a turquoise fill. */
  color: var(--bg-base);
  background: var(--klika-turquoise);
  box-shadow: var(--glow-turquoise-soft);
}

.topbar-user {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
  padding-left: var(--space-5);
  border-left: 1px solid var(--border-subtle);
}

.user-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
  max-width: 190px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   7. Page header
   -------------------------------------------------------------------------- */

.page-header {
  margin-bottom: var(--gap-section);
}

.page-header h2 {
  margin-bottom: var(--space-2);
}

.page-header-meta {
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  max-width: 68ch;
  margin: 0;
}

.eyebrow {
  display: block;
  font-family: var(--font-body);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--klika-turquoise);
  margin-bottom: var(--space-2);
}

.breadcrumb {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  margin-bottom: var(--space-4);
}

.breadcrumb a { color: var(--text-secondary); }
.breadcrumb a:hover { color: var(--klika-turquoise); }

/* --------------------------------------------------------------------------
   8. Surfaces
   -------------------------------------------------------------------------- */

/* Tier 1 — panel. Floats, blurred, lit along the top edge. */
.glass-card {
  position: relative;
  padding: var(--pad-panel);
  border-radius: var(--radius-lg);
  background: var(--surface-panel);
  backdrop-filter: var(--blur-panel);
  -webkit-backdrop-filter: var(--blur-panel);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-2), var(--edge-light);
  transition: border-color var(--dur-base) var(--ease-standard),
              box-shadow var(--dur-base) var(--ease-standard);
}

.glass-card > h3:first-child,
.glass-card > .card-head:first-child {
  margin-top: 0;
}

/* Tier 2 — inset. Nested inside a panel; deliberately NOT blurred. */
.inset {
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
}

.card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.card-title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: var(--fw-normal);
  letter-spacing: 0.01em;
  margin: 0;
  color: var(--text-primary);
}

.card-title > svg {
  color: var(--klika-turquoise);
  flex-shrink: 0;
}

.auth-shell {
  max-width: 460px;
  margin: var(--space-8) auto;
}

.auth-shell .glass-card {
  padding: var(--space-7) var(--space-6);
  box-shadow: var(--shadow-3), var(--edge-light);
}

.auth-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-6);
  text-align: center;
}

.auth-switch {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  text-align: center;
}

/* --------------------------------------------------------------------------
   9. Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.7rem 1.35rem;
  min-height: 44px; /* touch target */
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.01em;
  line-height: 1;
  cursor: pointer;
  white-space: nowrap;
  background: transparent;
  color: var(--text-primary);
  transition: background var(--dur-base) var(--ease-standard),
              border-color var(--dur-base) var(--ease-standard),
              box-shadow var(--dur-base) var(--ease-standard),
              color var(--dur-base) var(--ease-standard);
}

.btn:disabled,
.btn[aria-disabled='true'] {
  opacity: 0.45;
  cursor: not-allowed;
}

.btn-primary {
  background: var(--grad-primary);
  color: #fff;
  box-shadow: var(--shadow-1), var(--edge-light);
}

.btn-primary:hover:not(:disabled) {
  background: var(--grad-primary-hover);
  box-shadow: var(--shadow-2), var(--glow-turquoise-soft), var(--edge-light);
}

.btn-secondary {
  background: var(--surface-inset);
  border-color: var(--border);
  color: var(--text-primary);
}

.btn-secondary:hover:not(:disabled) {
  background: var(--surface-inset-hover);
  border-color: var(--border-hover);
}

.btn-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-secondary);
}

.btn-ghost:hover:not(:disabled) {
  background: var(--surface-inset);
  color: var(--text-primary);
}

.btn-danger-outline {
  background: transparent;
  border-color: rgba(255, 107, 122, 0.4);
  color: var(--danger);
}

.btn-danger-outline:hover:not(:disabled) {
  background: rgba(255, 107, 122, 0.12);
  border-color: var(--danger);
}

.btn-sm {
  padding: 0.5rem 0.95rem;
  min-height: 38px;
  font-size: var(--fs-2xs);
}

.btn-lg {
  padding: 0.95rem 2rem;
  min-height: 52px;
  font-size: var(--fs-sm);
}

.btn-block {
  display: flex;
  width: 100%;
}

/* One rule replacing the five near-identical action-row rules the old file
   had (.draft-actions / .slide-actions / .settings-actions / .lifecycle-actions
   / .revise-actions). Those names are kept as aliases so templates and any
   muscle memory still work. */
.action-row,
.draft-actions,
.slide-actions,
.settings-actions,
.lifecycle-actions,
.revise-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.action-row-end {
  justify-content: flex-end;
}

.action-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding-top: var(--space-5);
  margin-top: var(--space-5);
  border-top: 1px solid var(--border-subtle);
}

.action-bar-note {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  margin: 0;
  max-width: 46ch;
}

.inline-form,
.approve-form,
.lifecycle-form,
.clear-key-form {
  display: inline-flex;
}

/* --------------------------------------------------------------------------
   10. Forms
   -------------------------------------------------------------------------- */

.field {
  margin-bottom: var(--space-5);
}

.field:last-child {
  margin-bottom: 0;
}

/* Label and hint are separated by WEIGHT and COLOUR, not just a 0.1rem size
   difference like the old file. */
.field-label,
fieldset legend {
  display: block;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  margin-bottom: var(--space-2);
  padding: 0;
}

.field-label .opt {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-normal);
  color: var(--text-muted);
  margin-left: var(--space-2);
}

.field-hint {
  display: block;
  font-size: var(--fs-2xs);
  line-height: var(--lh-snug);
  color: var(--text-muted);
  margin: var(--space-2) 0 0;
  max-width: 74ch;
}

fieldset {
  border: 0;
  padding: 0;
  margin: 0;
}

.input,
.textarea,
.select {
  width: 100%;
  padding: 0.8rem 1rem;
  font-family: inherit;
  font-size: var(--fs-sm);
  color: var(--text-primary);
  background: var(--surface-input);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  appearance: none;
  transition: border-color var(--dur-base) var(--ease-standard),
              box-shadow var(--dur-base) var(--ease-standard),
              background var(--dur-base) var(--ease-standard);
}

.input::placeholder,
.textarea::placeholder {
  /* Rule: placeholders are soft grey, typed text is bright. Never dark-on-dark. */
  color: var(--text-placeholder);
}

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

.input:focus,
.textarea:focus,
.select:focus {
  outline: none;
  border-color: var(--klika-turquoise);
  background: rgba(4, 7, 22, 0.9);
  box-shadow: 0 0 0 3px rgba(0, 207, 255, 0.18);
}

.textarea {
  min-height: 130px;
  line-height: var(--lh-body);
  resize: vertical;
}

.textarea-hero {
  min-height: 190px;
  font-size: var(--fs-lg);
  line-height: 1.55;
  padding: var(--space-5);
  border-radius: var(--radius-md);
}

.select {
  padding-right: 2.75rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%2300CFFF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
}

.input-group {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
}

.input-group .input {
  flex: 1;
  min-width: 0;
}

.input-group-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  flex-shrink: 0;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  color: var(--text-secondary);
  cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-standard),
              background var(--dur-base) var(--ease-standard),
              color var(--dur-base) var(--ease-standard);
}

.input-group-btn:hover {
  border-color: var(--border-hover);
  background: var(--surface-inset-hover);
  color: var(--text-primary);
}

/* --- radio chips ------------------------------------------------------- */

.chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.chip-radio {
  position: relative;
  display: inline-flex;
}

.chip-radio input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

.chip-radio label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.65rem 1.15rem;
  min-height: 44px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: var(--surface-inset);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  cursor: pointer;
  transition: border-color var(--dur-base) var(--ease-standard),
              background var(--dur-base) var(--ease-standard),
              color var(--dur-base) var(--ease-standard);
}

.chip-radio label:hover {
  border-color: var(--border);
  color: var(--text-primary);
}

.chip-radio input:checked + label {
  /* Gradient stop pushed past 100% so white stays >=5.5:1 on the light end. */
  background: var(--grad-primary);
  border-color: transparent;
  color: #fff;
  box-shadow: var(--glow-turquoise-soft);
}

/* --------------------------------------------------------------------------
   11. Disclosure (progressive disclosure for the long explanatory copy)
   -------------------------------------------------------------------------- */

.disclosure {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
  overflow: hidden;
}

.disclosure + .disclosure {
  margin-top: var(--space-3);
}

.disclosure > summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  min-height: 52px;
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  transition: background var(--dur-fast) var(--ease-standard);
}

.disclosure > summary::-webkit-details-marker { display: none; }

.disclosure > summary:hover {
  background: var(--surface-inset-hover);
}

.disclosure-caret {
  margin-left: auto;
  display: inline-flex;
  color: var(--text-muted);
  transition: transform var(--dur-base) var(--ease-standard);
}

.disclosure[open] > summary .disclosure-caret {
  transform: rotate(180deg);
}

.disclosure-body {
  padding: 0 var(--space-4) var(--space-4);
}

.disclosure-body > .field:last-child,
.disclosure-body > p:last-child {
  margin-bottom: 0;
}

/* Body copy that used to be a wall of muted micro-text. */
.prose {
  font-size: var(--fs-2xs);
  line-height: var(--lh-body);
  color: var(--text-secondary);
  max-width: 78ch;
}

.prose p + p { margin-top: var(--space-3); }
.prose strong { color: var(--text-primary); font-weight: var(--fw-bold); }
.prose code {
  font-family: var(--font-mono);
  font-size: 0.95em;
  padding: 0.1em 0.4em;
  border-radius: var(--radius-xs);
  background: rgba(0, 207, 255, 0.08);
  color: var(--klika-turquoise);
}

/* --------------------------------------------------------------------------
   12. Badges, chips, alerts
   -------------------------------------------------------------------------- */

.status-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.3rem 0.7rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border: 1px solid transparent;
  transition: background var(--dur-base) var(--ease-standard),
              color var(--dur-base) var(--ease-standard),
              border-color var(--dur-base) var(--ease-standard);
}

/* in flight */
.status-pending,
.status-generating,
.status-researching,
.status-drafting,
.status-rendering {
  background: rgba(0, 207, 255, 0.12);
  border-color: rgba(0, 207, 255, 0.3);
  color: var(--klika-turquoise);
}

/* settled, good */
.status-done,
.status-added,
.status-approved {
  background: rgba(53, 212, 138, 0.12);
  border-color: rgba(53, 212, 138, 0.3);
  color: var(--success);
}

/* needs a decision */
.status-partial,
.status-researched {
  background: rgba(247, 197, 51, 0.12);
  border-color: rgba(247, 197, 51, 0.3);
  color: var(--warning);
}

/* bad */
.status-failed {
  background: rgba(255, 107, 122, 0.12);
  border-color: rgba(255, 107, 122, 0.32);
  color: var(--danger);
}

/* neutral */
.status-draft,
.status-cancelled {
  background: var(--surface-inset);
  border-color: var(--border-subtle);
  color: var(--text-secondary);
}

/* An ordinal label ("Cover", "Slide 3") is not a status. The old file reused
   .status-badge.status-draft for it, so a neutral state chip and a slide
   number looked identical. This is the label treatment. */
.ordinal-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.28rem 0.7rem;
  border-radius: var(--radius-xs);
  background: rgba(0, 207, 255, 0.1);
  border: 1px solid var(--border-subtle);
  font-family: var(--font-display);
  font-size: var(--fs-2xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--klika-turquoise);
}

.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--surface-inset);
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
  color: var(--text-secondary);
}

.alert > svg { flex-shrink: 0; margin-top: 0.15em; }
.alert p { margin: 0; }
.alert p + p { margin-top: var(--space-2); }
.alert strong { color: var(--text-primary); }

.alert-error {
  background: rgba(255, 107, 122, 0.1);
  border-color: rgba(255, 107, 122, 0.32);
  color: #FFD3D8;
}

.alert-warn {
  background: rgba(247, 197, 51, 0.09);
  border-color: rgba(247, 197, 51, 0.3);
  color: #FFECC0;
}

.alert-success {
  background: rgba(53, 212, 138, 0.1);
  border-color: rgba(53, 212, 138, 0.3);
  color: #C6F5DF;
}

.alert-info {
  background: rgba(0, 207, 255, 0.08);
  border-color: rgba(0, 207, 255, 0.24);
  color: #CFEEFB;
}

/* --------------------------------------------------------------------------
   13. Media boxes — ONE implementation
   --------------------------------------------------------------------------
   object-fit: contain is load-bearing (the v1.5 fix for 1:1 static posts being
   cropped in the UI). .is-square switches 4:5 to 1:1.
   -------------------------------------------------------------------------- */

.media-box,
.slide-image-wrap,
.history-thumb,
.style-preview-media {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface-media);
  display: flex;
  align-items: center;
  justify-content: center;
}

.media-box.is-square,
.slide-image-wrap.is-square,
.history-thumb.is-square,
.style-preview-media.is-square {
  aspect-ratio: 1 / 1;
}

.media-box img,
.slide-image-wrap img,
.history-thumb img,
.style-preview-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.history-thumb { border-radius: 0; }

.history-thumb-placeholder,
.slide-pending,
.slide-failed,
.draft-canvas-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-4);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.slide-failed { color: var(--danger); }

/* --------------------------------------------------------------------------
   14. Result page
   -------------------------------------------------------------------------- */

.job-status-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  margin-bottom: var(--space-5);
}

/* Hero + rail. The generated image is the product, so it gets the space. */
.result-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 360px);
  gap: var(--gap-section);
  align-items: start;
}

/* While a job is still rendering there is no rail yet, so the images should
   not sit in a narrowed column waiting for one. */
.result-layout.is-solo { grid-template-columns: minmax(0, 1fr); }

.result-main { min-width: 0; }

.result-hook {
  font-size: var(--fs-h2);
  margin-bottom: var(--space-5);
  max-width: 30ch;
}

.result-rail {
  position: sticky;
  top: calc(68px + var(--space-5));
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.result-rail .btn-block + .field-hint { margin-top: var(--space-3); }

.slide-copy .ordinal-badge { margin-bottom: var(--space-2); }

.slide-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--space-5);
}

/* A single slide (a static post) has no reason to sit in a 300px column
   with the rest of the row empty. */
.slide-grid.is-single {
  grid-template-columns: minmax(0, 560px);
  justify-content: center;
}

.slide-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-lg);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-1);
  transition: border-color var(--dur-base) var(--ease-standard),
              box-shadow var(--dur-base) var(--ease-standard);
}

.slide-card:hover {
  border-color: var(--border);
  box-shadow: var(--shadow-2);
}

.slide-copy {
  padding: 0 var(--space-2);
}

.slide-headline {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  color: var(--text-primary);
  margin: 0 0 var(--space-1);
}

.slide-subtext {
  font-size: var(--fs-2xs);
  line-height: var(--lh-snug);
  color: var(--text-secondary);
  margin: 0;
}

.photo-credit {
  display: block;
  padding: 0 var(--space-2);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.photo-credit-license {
  display: block;
  margin-top: var(--space-1);
  line-height: var(--lh-snug);
  color: var(--text-muted);
}

.slide-actions {
  padding: 0 var(--space-2) var(--space-2);
  gap: var(--space-2);
}

/* Height comes from each caller's rows attribute (result uses 7, draft uses 5)
   rather than a fixed min-height, which made the draft's short caption box
   look padded out. */
.caption-card .textarea {
  min-height: 0;
  font-size: var(--fs-xs);
  line-height: 1.65;
}

/* --------------------------------------------------------------------------
   15. Generate form (the composer)
   -------------------------------------------------------------------------- */

/* A form has no business spanning 1180px. Centring the column (header
   included, via .page-narrow) is what stops the page reading as "content
   crammed left, void on the right". */
.page-narrow,
.composer {
  max-width: var(--w-narrow);
  margin-inline: auto;
}

.composer-hero {
  padding: var(--space-6);
}

.composer-hero .textarea-hero { min-height: 168px; }

/* Format / slide-count / style sit together because they are all "settings
   for this generation". Three stacked full-width cards is what made the old
   page 3200px tall. */
.option-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-5);
}

.option-grid > .field { margin-bottom: 0; }

#slide-count-field {
  /* overflow:hidden is required by the GSAP height collapse in
     new_generation.html — do not remove. */
  overflow: hidden;
}

.style-preview {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px dashed var(--border-subtle);
  background: rgba(255, 255, 255, 0.02);
  font-size: var(--fs-2xs);
  line-height: var(--lh-snug);
  color: var(--text-secondary);
}

.style-preview > svg {
  flex-shrink: 0;
  margin-top: 0.15em;
  color: var(--klika-turquoise);
}

/* --------------------------------------------------------------------------
   16. Settings — status strip + accordion
   -------------------------------------------------------------------------- */

.status-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-4);
}

.status-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: var(--surface-inset);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  transition: border-color var(--dur-base) var(--ease-standard),
              color var(--dur-base) var(--ease-standard);
}

.status-chip:hover {
  border-color: var(--border-hover);
  color: var(--text-primary);
}

/* Status is carried by a dot AND a label, never colour alone. */
.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--text-muted);
}

.status-chip.is-on { color: var(--text-primary); border-color: rgba(53, 212, 138, 0.35); }
.status-chip.is-on .status-dot {
  background: var(--success);
  box-shadow: 0 0 10px rgba(53, 212, 138, 0.7);
}

.status-chip.is-off .status-dot { background: #4A5378; }

.settings-row {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--surface-panel);
  backdrop-filter: var(--blur-panel);
  -webkit-backdrop-filter: var(--blur-panel);
  box-shadow: var(--shadow-1), var(--edge-light);
  overflow: hidden;
  transition: border-color var(--dur-base) var(--ease-standard),
              box-shadow var(--dur-base) var(--ease-standard);
}

.settings-row[open] {
  border-color: var(--border);
  box-shadow: var(--shadow-2), var(--edge-light);
}

.settings-row + .settings-row { margin-top: var(--space-3); }

.settings-row > summary {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-5);
  min-height: 76px;
  cursor: pointer;
  list-style: none;
}

.settings-row > summary::-webkit-details-marker { display: none; }
.settings-row > summary:hover { background: rgba(255, 255, 255, 0.02); }

.settings-row-title {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.settings-row-name {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}

.settings-row-sub {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.settings-row-meta {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
}

.settings-row-body {
  padding: 0 var(--space-5) var(--space-5);
}

.settings-row-body > * + * { margin-top: var(--space-5); }

.key-status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: var(--surface-inset);
  font-size: var(--fs-2xs);
  color: var(--text-secondary);
}

.key-status-active {
  border-color: rgba(53, 212, 138, 0.3);
  color: var(--success);
}

.key-status-empty {
  border-color: var(--border-subtle);
  color: var(--text-muted);
}

.masked-key {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.06em;
  color: var(--text-primary);
}

/* The test-connection result containers. Only #test-result had a rule before;
   the other five appeared with no spacing at all. */
#test-result:not(:empty),
#apify-test-result:not(:empty),
[id^='test-result-']:not(:empty) {
  margin-top: var(--space-4);
}

#test-result .alert,
#apify-test-result .alert,
[id^='test-result-'] .alert {
  margin-bottom: 0;
}

.settings-danger {
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}

/* --- v1.9: Apify credit meter ------------------------------------------- */
/* The bar is reinforcement, never the only carrier of meaning: the exact
   figures sit above it and the plain-language remainder below it, so the
   state survives colour-blindness and a failed paint alike. */

.quota {
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
}

.quota-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.quota-label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}

.quota-figure {
  font-size: var(--fs-sm);
  color: var(--text-primary);
  /* tabular figures so the number doesn't jitter as it changes */
  font-variant-numeric: tabular-nums;
}

.quota-figure strong { font-weight: var(--fw-bold); }
.quota-of { color: var(--text-muted); font-weight: var(--fw-normal); }

.quota-bar {
  height: 8px;
  border-radius: var(--radius-pill);
  background: rgba(0, 207, 255, 0.1);
  overflow: hidden;
}

.quota-bar-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--klika-royal), var(--klika-turquoise));
  box-shadow: var(--glow-turquoise-soft);
  transition: width var(--dur-slow) var(--ease-out-expo);
}

.quota-bar-fill.is-mid {
  background: linear-gradient(90deg, var(--klika-royal), var(--warning));
  box-shadow: none;
}

.quota-bar-fill.is-low {
  background: linear-gradient(90deg, var(--warning), var(--danger));
  box-shadow: 0 0 18px rgba(255, 107, 122, 0.35);
}

.quota-note {
  margin: var(--space-3) 0 0;
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
  color: var(--text-secondary);
}

.quota-note strong { color: var(--text-primary); }
.quota .field-hint { margin-top: var(--space-3); }

/* --------------------------------------------------------------------------
   17. History grid
   -------------------------------------------------------------------------- */

.history-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: var(--space-5);
}

.history-card {
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-1);
  color: inherit;
  /* Uniform height: the media box is fixed-ratio and the meta block is a
     fixed 3-row layout, so rows stop coming out ragged. */
  transition: border-color var(--dur-base) var(--ease-standard),
              box-shadow var(--dur-base) var(--ease-standard);
}

.history-card:hover {
  border-color: var(--border-hover);
  box-shadow: var(--shadow-2), var(--glow-turquoise-soft);
  color: inherit;
}

.history-meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  margin-top: auto;
}

.history-prompt {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  color: var(--text-primary);
  margin: 0;
  /* two-line clamp keeps every card the same height */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(2 * var(--fs-xs) * var(--lh-snug));
}

.history-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.history-empty,
.empty-state {
  padding: var(--space-8) var(--space-5);
  text-align: center;
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

/* --------------------------------------------------------------------------
   18. Draft stage
   -------------------------------------------------------------------------- */

/* The draft page is a stack of top-level blocks (status alert, hook title, the
   slide grid, the caption card, the approve card) and NONE of them carried any
   spacing — measured at literally 0px between the last slide and the caption
   card, and again between the caption card and "Udah oke?". They read as one
   fused wall. The container owns the rhythm now, so a new block added later
   cannot forget it.

   Two steps on purpose: 32px BETWEEN sections, 24px between slide cards inside
   the grid. Tighter within a group than between groups is what makes a long
   page legible. */
#draft-fragment {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* The hook used to space itself with a bottom margin; the gap owns that now,
   and leaving both would double it. */
#draft-fragment > .result-hook { margin-bottom: 0; }

.draft-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.draft-slide {
  padding: var(--space-4);
}

.draft-slide-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

/* The canvas used to be squeezed into a 200-280px column, which made
   .draft-canvas-sub render at 6.6px. It gets a real column now. */
.draft-slide-body {
  display: grid;
  grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
  gap: var(--space-5);
  align-items: start;
}

.draft-slide-controls {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

/* --- v1.11 compact slide card ---------------------------------------------
   The card was 1161px tall against a 900px viewport, so a single slide did not
   fit on one screen and a 6-slide draft ran to 8272px. The height came from
   stacking six full-width labelled sections down the right-hand column while a
   third of the row sat empty. These rules spend that width instead.

   Headline and subtext side by side is the single biggest saving. Everything
   else is: one toolbar row instead of three action rows, the AI instruction
   and its button on one line, and the section headers dropped (the cost chip
   moved inline, which is the only part of them that carried information). */

.draft-copy-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-4);
}

/* .field carries a 24px bottom margin globally, right in a stacked form and
   pure dead space here: the grid gap separates the two columns already, and
   the save row below has its own margin. That 24px was the gap the owner
   pointed at between the headline box and "Simpan teks". */
.draft-copy-pair .field { margin-bottom: 0; }

/* The global .textarea min-height is 130px, which silently overrode rows="2"
   and kept ~46px of empty box under every headline. Scoped down here only —
   the caption box and the composer still want the taller default. Still
   resizable, so a long headline can be dragged open. */
.draft-copy-pair .textarea { min-height: 84px; }

.draft-actions--tight { margin-top: var(--space-2); }

/* One row for: cari foto lain, pakai foto sendiri, and the four fit chips. */
/* ONE line, always. The fit chips dropping to a second row was the untidy bit
   the owner called out. Two things made it wrap: the gaps, and two
   .htmx-indicator spans which are opacity-hidden rather than display:none (so
   they never reflow when they appear) and therefore consumed width while
   invisible. The spans are gone; htmx marks the control itself instead. */
.draft-toolbar {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}

.draft-toolbar > * { flex: 0 0 auto; }

.draft-toolbar .btn.htmx-request,
.draft-toolbar form.htmx-request .btn { opacity: 0.55; }

.draft-toolbar-sep {
  width: 1px;
  align-self: stretch;
  min-height: 20px;
  background: var(--border-subtle);
  margin: 0 var(--space-1);
}

/* The AI action, previously a whole labelled section with its own heading,
   hint line and separate button row. */
.draft-ai-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}

.draft-ai-row .input {
  flex: 1 1 240px;
  min-width: 0;
}

/* The photo query moved into the card header as a quiet caption; it used to
   trail the action row and force it to wrap. */
.draft-slide-query {
  margin-left: auto;
  font-size: var(--fs-2xs);
  color: var(--text-placeholder);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 45%;
}

/* An error raised by one card is shown in that card, so it is never scrolled
   away from the control that caused it. */
.alert-inline {
  margin-bottom: var(--space-3);
  padding: var(--space-2) var(--space-3);
  font-size: var(--fs-2xs);
}

.draft-section + .draft-section {
  padding-top: var(--space-5);
  border-top: 1px solid var(--border-subtle);
}

.draft-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.draft-section-title {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
}

.draft-saved {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-2xs);
  color: var(--success);
}

/* Says what an action costs, once per section, instead of trailing a sentence
   off the end of every button. */
.cost-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.2rem 0.6rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  white-space: nowrap;
}

.cost-chip.is-free {
  border-color: rgba(53, 212, 138, 0.28);
  color: var(--success);
}

.cost-chip.is-paid {
  border-color: rgba(247, 197, 51, 0.3);
  color: var(--warning);
}

.cost-chip.is-ready {
  border-color: rgba(0, 207, 255, 0.32);
  color: var(--klika-turquoise);
}

.draft-hint-inline {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.draft-option-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.draft-option {
  position: relative;
  /* 124 -> 96 to stop the row wrapping, then up to 128 once the height was
     reclaimed elsewhere. The candidates are the thing the user is actually
     judging, so they get the space. Five at 128px still fit one row. */
  width: 128px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface-media);
  cursor: pointer;
  overflow: hidden;
  text-align: left;
  transition: border-color var(--dur-base) var(--ease-standard),
              box-shadow var(--dur-base) var(--ease-standard);
}

.draft-option img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

.draft-option:hover {
  border-color: var(--border-hover);
}

.draft-option.is-selected {
  border-color: var(--klika-turquoise);
  box-shadow: var(--glow-turquoise-soft);
}

.draft-option-meta {
  display: block;
  padding: var(--space-1) var(--space-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: var(--fs-2xs);
  line-height: var(--lh-snug);
  color: var(--text-secondary);
  background: var(--surface-inset);
}

.draft-option-check {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--klika-turquoise);
  color: var(--bg-base);
  box-shadow: var(--shadow-1);
}

.draft-copy-form,
.draft-rewrite-form {
  display: block;
}

.caption-card .field:last-child { margin-bottom: 0; }

.draft-finish-card,
.revise-card,
.lifecycle-card {
  padding: var(--pad-panel);
}

/* --- draft canvas (runtime-themed by --dc-* from draft_routes.py) ------- */

/* THIS BLOCK MIRRORS compositing.py. Every proportion below is a cqw value on
   purpose: the canvas is a mock of the real composite, so its text must scale
   with the box and keep the composite's proportions. DO NOT retune these
   ratios for legibility — that would make the preview lie about what renders.
   The v1.8 legibility fix was to widen the COLUMN (see .draft-slide-body),
   which was pinned at 200-280px and made the subtext resolve to about 6.6px.
   Sticky so the preview stays beside the controls while you scroll a slide's
   three sections, instead of leaving a tall empty column under it. */
.draft-canvas {
  position: sticky;
  top: calc(68px + var(--space-5));
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface-media);
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  container-type: inline-size;
  box-shadow: var(--shadow-2);
}

.draft-canvas.is-square { aspect-ratio: 1 / 1; }

.draft-canvas-photo { position: relative; flex: 1; min-height: 0; overflow: hidden; }
.draft-canvas-photo--full { position: absolute; inset: 0; }
/* v1.10: the blanket `.draft-canvas-photo img { object-fit: cover }` that used
   to live here is GONE, and its absence is deliberate. At specificity (0,1,1)
   it outranked the (0,1,0) fit-mode classes below, so "Muat semua" silently
   rendered as a crop — the preview claiming a framing the render would not
   produce, which is the one failure this whole stage exists to prevent. Every
   <img> in this box now comes from the canvas_photo() macro and always carries
   a .draft-canvas-img class, so nothing is left unstyled. Do not reintroduce a
   bare element selector here. */

/* v1.10 — the fit modes, mirroring compositing.fit_to_ratio() exactly.
   This block is load-bearing in the same way the cqw type ratios above are:
   the draft exists so the user approves what they will actually get, so if
   these diverge from the Pillow side the whole stage is worse than useless.

   `--fit-auto` is 33%, not 50%: crop_to_ratio() takes its crop box at
   (h - new_h) // 3 from the top, and the canvas was silently sitting at the
   browser's 50% default until v1.10. */
.draft-canvas-img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.draft-canvas-img--fit-auto   { object-fit: cover; object-position: 50% 33%; }
.draft-canvas-img--fit-top    { object-fit: cover; object-position: 50% 0%; }
.draft-canvas-img--fit-bottom { object-fit: cover; object-position: 50% 100%; }

/* "Muat semua": a blurred, zoomed copy fills the frame, the real photo sits
   whole on top of it. Two stacked <img>s rather than a CSS background-image
   because the source is an authenticated route — one src, fetched once,
   painted twice. The blur radius tracks compositing's target_w // 22 as
   closely as a fixed CSS length can at canvas scale. */
.draft-canvas-img--blur {
  object-fit: cover;
  object-position: 50% 50%;
  filter: blur(14px);
  transform: scale(1.12); /* hide the soft edge the blur leaves at the frame */
}
.draft-canvas-img--contain { object-fit: contain; object-position: 50% 50%; }

/* --- the fit control ------------------------------------------------------
   A row of pill toggles. The active one is a filled chip with a soft glow,
   deliberately NOT a coloured side border — the same rule the rest of this
   sheet follows. Sizes sit on --fs-2xs, the sheet's 13px hard floor. */
.draft-fit-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.draft-fit-label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}

.draft-fit-btn {
  padding: 0.2rem 0.55rem;
  white-space: nowrap;
  font-family: inherit;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard);
}

.draft-fit-btn:hover {
  color: var(--text-primary);
  background: var(--surface-inset-hover);
  border-color: var(--border);
}

.draft-fit-btn.is-active {
  color: var(--klika-turquoise);
  background: rgba(0, 207, 255, 0.12);
  border-color: var(--border-strong);
  box-shadow: var(--glow-turquoise-soft);
}

.draft-fit-btn:focus-visible {
  outline: 2px solid var(--klika-turquoise);
  outline-offset: 2px;
}

/* The upload control is a <label> wrapping a hidden file input, so it needs
   the pointer a <button> would have given it for free. */
.draft-upload-label { cursor: pointer; }

.draft-canvas-empty {
  width: 100%;
  height: 100%;
  flex-direction: column;
  gap: var(--space-2);
}

/* photo_overlay: bottom gradient scrim + text baked over the photo. The stop
   position and peak alpha come straight from the style's own
   gradient_start_frac / gradient_max_opacity, so a style with a shallower
   scrim previews with a shallower scrim.
   --dc-gradient is a flat COLOUR from draft_routes._style_css_vars, not a
   gradient — it has to be composed here. Assigning it directly paints an
   opaque block over the whole photo. */
.draft-canvas-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    transparent var(--dc-gradient-start),
    color-mix(in srgb, var(--dc-gradient) calc(var(--dc-gradient-alpha) * 100%), transparent) 100%
  );
  pointer-events: none;
}

.draft-canvas--photo_overlay .draft-canvas-text {
  position: absolute;
  left: 6.6%;
  right: 6.6%;
  bottom: 5.4%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.draft-canvas-rule {
  display: block;
  width: 12cqw;
  height: 0.46cqw;
  min-height: 2px;
  background: var(--dc-rule);
  margin-bottom: 2cqw;
}

/* top_text_bar: solid colour block on top holding the text, clean photo below,
   no scrim and no accent rule — the same two deliberate omissions
   compositing._render_top_text_bar makes. */
.draft-canvas--top_text_bar .draft-canvas-bar {
  background: var(--dc-bar);
  padding: 4.2cqw 5.5cqw;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
}

.draft-canvas--top_text_bar .draft-canvas-headline,
.draft-canvas--top_text_bar .draft-canvas-sub { text-align: center; }

.draft-canvas-headline {
  font-family: var(--dc-font-headline);
  color: var(--dc-headline);
  font-size: 7cqw;
  line-height: 1.08;
  margin: 0;
  text-transform: uppercase;
  word-break: break-word;
}

.draft-canvas-sub {
  font-family: var(--dc-font-body);
  color: var(--dc-body);
  font-size: 3.3cqw;
  line-height: 1.35;
  /* MIRRORS compositing.HEADLINE_BODY_GAP_RATIO (0.40), not a taste call.
     The headline above is 7cqw, so the beat between them is 0.40 x 7 = 2.8cqw.
     It was 1.6cqw while the renderer used a bare `y += 6`, and both were too
     tight; they are corrected together on purpose, because a draft that spaces
     its type differently from the render is the same lie in a smaller font.
     If that ratio ever changes, change this with it. */
  margin: 2.8cqw 0 0;
  font-weight: 700;
}

.draft-canvas-label {
  font-family: var(--dc-font-headline);
  color: var(--dc-headline);
  font-size: 4cqw;
  line-height: 1;
  margin-bottom: 1.4cqw;
}

.draft-canvas--top_text_bar .draft-canvas-label {
  color: var(--dc-body);
  font-family: var(--dc-font-body);
  font-size: 2.8cqw;
}

/* --------------------------------------------------------------------------
   19. Style research
   -------------------------------------------------------------------------- */

.style-request-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.style-request-card {
  display: block;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  color: inherit;
  transition: border-color var(--dur-base) var(--ease-standard),
              background var(--dur-base) var(--ease-standard);
}

.style-request-card:hover {
  border-color: var(--border-hover);
  background: var(--surface-inset-hover);
  color: inherit;
}

.style-request-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.style-request-head strong {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
}

.style-request-url,
.style-request-notes {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

.style-request-footer {
  margin-top: var(--space-3);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.style-research-grid {
  display: grid;
  grid-template-columns: minmax(260px, 360px) minmax(0, 1fr);
  gap: var(--gap-section);
  align-items: start;
}

.style-research-summary {
  min-width: 0;
}

.style-swatch-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.style-swatch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  font-size: var(--fs-2xs);
  line-height: var(--lh-snug);
  color: var(--text-secondary);
}

.style-swatch-color {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.18);
  flex-shrink: 0;
}

.style-swatch code {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--text-primary);
}

/* Used as a <ul> everywhere in the app; the dt/dd rules are here so it also
   works if a caller reaches for a description list. */
.style-fact-list {
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-xs);
  line-height: var(--lh-body);
  color: var(--text-secondary);
}

.style-fact-list li {
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-subtle);
}

.style-fact-list li:first-child { padding-top: 0; }
.style-fact-list li:last-child { border-bottom: 0; padding-bottom: 0; }
.style-fact-list strong { color: var(--text-primary); font-weight: var(--fw-bold); }
.style-fact-list code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  color: var(--klika-turquoise);
}

.style-fact-list dt {
  font-weight: var(--fw-bold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: var(--fs-2xs);
  margin-top: var(--space-3);
}

.style-fact-list dt:first-child { margin-top: 0; }
.style-fact-list dd { margin: var(--space-1) 0 0; color: var(--text-primary); }

/* One labelled sub-section inside a card. Replaces the pattern of dropping a
   third <h3> inside a card, which made card titles and sub-headings
   indistinguishable. */
.voice-block + .voice-block {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--border-subtle);
}

.voice-block > p {
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  margin: 0;
}

.scraped-thumb-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* The wrapper is an <a> or <span>; the image inside has to be sized or the
   tile renders with dead space under a shrunken thumbnail. */
.scraped-thumb {
  display: block;
  width: 82px;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-xs);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
  background: var(--surface-media);
  transition: border-color var(--dur-base) var(--ease-standard);
}

.scraped-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.scraped-thumb:hover { border-color: var(--border-hover); }

/* --------------------------------------------------------------------------
   20. Loader
   -------------------------------------------------------------------------- */

.pulse-loader {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
}

.pulse-loader__glow {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 320px;
  height: 320px;
  margin: -160px 0 0 -160px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(0, 207, 255, 0.22), transparent 68%);
  pointer-events: none;
  will-change: transform, opacity;
}

.pulse-loader__track {
  position: relative;
  height: 4px;
  border-radius: var(--radius-pill);
  background: rgba(0, 207, 255, 0.12);
  overflow: hidden;
}

.pulse-loader__bar {
  position: absolute;
  inset: 0;
  width: 40%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, transparent, var(--klika-turquoise), transparent);
}

.pulse-loader__label {
  position: relative;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
}

/* --------------------------------------------------------------------------
   21. Animation plumbing
   --------------------------------------------------------------------------
   .anim-in starts hidden and is revealed by GSAP. Two fallbacks keep content
   from ever being stuck invisible: .anim-fallback (added by app.js when GSAP
   fails to load) and a delayed CSS keyframe (slow-network safety net).
   -------------------------------------------------------------------------- */

.anim-in { opacity: 0; }

.no-js .anim-in,
.anim-in.anim-fallback {
  opacity: 1;
  animation: none !important;
}

@media (prefers-reduced-motion: no-preference) {
  .anim-in {
    animation: anim-in-reveal 0.45s var(--ease-standard) 700ms both;
  }

  @keyframes anim-in-reveal {
    from { opacity: 0; transform: translateY(16px); }
    to { opacity: 1; transform: none; }
  }

  @keyframes pulse-sweep {
    0% { transform: translateX(-120%); }
    100% { transform: translateX(320%); }
  }

  .pulse-loader__bar { animation: pulse-sweep 1.8s var(--ease-standard) infinite; }

  @keyframes icon-pulse {
    0%, 100% { opacity: 0.35; }
    50% { opacity: 1; }
  }

  /* NOT applied to a resting htmx indicator — see the note below. */
  .icon-pulse:not(.htmx-indicator) { animation: icon-pulse 1.4s ease-in-out infinite; }

  .htmx-request.htmx-indicator,
  .htmx-request .htmx-indicator {
    animation: icon-pulse 1.4s ease-in-out infinite;
  }
}

/* htmx ships no CSS of its own. The indicator is hidden with opacity rather
   than display:none so it never reflows the action row when it appears.
   BUG FIXED IN v1.8: every indicator span carries .icon-pulse, whose keyframes
   animate opacity — and a running CSS animation outranks the plain
   `opacity: 0` here, so all nine indicators were permanently visible as a
   stray pulsing icon next to their button. The pulse is now scoped to the
   in-flight state only. */
.htmx-indicator {
  opacity: 0;
  color: var(--klika-turquoise);
  display: inline-flex;
  transition: opacity var(--dur-base) var(--ease-standard);
}

.htmx-request.htmx-indicator,
.htmx-request .htmx-indicator {
  opacity: 1;
}

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

  .anim-in { opacity: 1; }
}

/* --------------------------------------------------------------------------
   22. Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1080px) {
  .result-layout { grid-template-columns: minmax(0, 1fr); }
  .result-rail { position: static; }
}

@media (max-width: 900px) {
  .draft-slide-body { grid-template-columns: minmax(0, 1fr); }

  /* The topbar's wrap rules used to start at 760px, so every width from 761px
     to about 850px kept the desktop row and pushed the user chip and Logout
     button past the right edge. Tablets live in that gap. */
  .topbar-inner { flex-wrap: wrap; gap: var(--space-3); }
  .topbar-links { order: 3; width: 100%; margin-left: 0; overflow-x: auto; }
  .topbar-user { margin-left: auto; min-width: 0; }
  .user-chip { max-width: 150px; }

  /* The v1.11 toolbar is nowrap so the fit chips hold one line on a laptop.
     Below this width there is genuinely not enough room, and forcing one line
     pushed the whole PAGE 409px wider than a 390px phone — horizontal scroll on
     every draft. The nowrap rule shipped with a comment promising this fallback
     and without the fallback itself; this is it. */
  .draft-toolbar { flex-wrap: wrap; gap: var(--space-2); }
  .draft-toolbar-sep { display: none; }
  /* Nothing inside the card may set the page's width. */
  .draft-slide, .draft-slide-controls, .draft-toolbar, .draft-option-row { min-width: 0; max-width: 100%; }
  /* A horizontal scroller, so candidates keep their real size instead of being
     squeezed. Without flex:0 0 auto the five items shrink to ~40px slivers and
     the user is picking a photo they cannot see. */
  .draft-option-row { overflow-x: auto; flex-wrap: nowrap; padding-bottom: var(--space-2); }
  /* Both the wrapping form AND the button must refuse to shrink: the row is a
     nowrap scroller, and a flex item's default min-width:auto still let the
     five candidates squeeze down to ~34px slivers, i.e. picking a photo you
     cannot see. */
  .draft-option-row > *,
  .draft-option-row .inline-form,
  .draft-option { flex: 0 0 auto; }
  .draft-option { width: 116px; min-width: 116px; }
  .draft-copy-pair { grid-template-columns: minmax(0, 1fr); }
  .draft-canvas { max-width: 360px; }
  .style-research-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 760px) {
  :root {
    --pad-panel: 1.25rem;
    --gap-section: 1.75rem;
  }

  main.container { padding-top: var(--space-5); }

  .topbar-inner {
    flex-wrap: wrap;
    gap: var(--space-3);
    padding-bottom: var(--space-4);
  }

  .topbar-links {
    order: 3;
    width: 100%;
    margin-left: 0;
    overflow-x: auto;
    padding-bottom: var(--space-1);
  }

  .topbar-user {
    margin-left: auto;
    padding-left: var(--space-3);
    /* keep a separator when it wraps — the old file dropped the border here,
       so the user block collided with the nav row above it */
    border-left: 1px solid var(--border-subtle);
  }

  .user-chip { max-width: 120px; }

  .slide-grid,
  .history-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .slide-grid.is-single { grid-template-columns: minmax(0, 1fr); }

  .option-grid { grid-template-columns: minmax(0, 1fr); }

  .action-bar { flex-direction: column; align-items: stretch; }
  .action-bar .btn { width: 100%; }

  .draft-option { width: calc(50% - var(--space-2)); }
}


/* --------------------------------------------------------------------------
   Failed-request toast
   -------------------------------------------------------------------------- */
/* htmx does not swap non-2xx responses, so without this a failed save is
   completely silent — see the htmx:responseError handler in app.js. */
.request-error-toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-5);
  transform: translateX(-50%);
  z-index: 60;
  max-width: min(560px, calc(100vw - 2 * var(--space-4)));
  display: flex;
  align-items: center;
  gap: var(--space-3);
  box-shadow: var(--shadow-2);
}

.request-error-toast__action {
  flex: 0 0 auto;
  font-weight: var(--fw-semibold);
  color: var(--klika-turquoise);
  text-decoration: underline;
}

/* v1.12 — the draft's copy-overflow warning.
   Sits under the headline/subtext pair and says what the RENDER will do to
   copy that does not fit (shrink it, or cut it). Deliberately a tinted inline
   hint rather than a full .alert block: it is advisory, it can be true for
   several slides at once, and a stack of full-width alerts inside the cards
   would drown the controls it sits between. Colours are the same literal rgba
   pair .alert-warn uses (not color-mix) so the two read as one family, and the
   text is the light tint rather than raw --warning, which has too little
   contrast against its own tinted surface. No coloured side-border lining,
   per the house rule. */
.draft-copy-warning {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  color: #FFECC0;
  background: rgba(247, 197, 51, 0.09);
  border: 1px solid rgba(247, 197, 51, 0.3);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  max-width: none;
}

.draft-copy-warning > svg {
  flex-shrink: 0;
  margin-top: 0.1em;
  color: var(--warning);
}


/* --------------------------------------------------------------------------
   23. App shell / left sidebar (Brand Studio, 2026-08-23)
   Navigation chrome only — page content and every component above are
   untouched. Logged-out pages still render the .topbar rules in section 6.
   -------------------------------------------------------------------------- */

.app-shell {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  width: 236px;
  flex: none;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-3);
  background: rgba(5, 8, 31, 0.72);
  backdrop-filter: var(--blur-panel);
  -webkit-backdrop-filter: var(--blur-panel);
  border-right: 1px solid var(--border-subtle);
  z-index: var(--z-sticky);
}

.sidebar-brand {
  padding: 0 var(--space-3) var(--space-4);
}

.sidebar-nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.sidebar-group-label {
  margin: var(--space-3) 0 var(--space-1);
  padding: 0 var(--space-3);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-secondary);
  opacity: 0.75;
}

.side-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  transition: color var(--dur-fast) var(--ease-standard),
              background var(--dur-fast) var(--ease-standard);
}

.side-link:hover {
  color: var(--text-primary);
  background: var(--surface-inset);
}

.side-link.is-active {
  /* Hard rule 2: dark text on a turquoise fill. */
  color: var(--bg-base);
  background: var(--klika-turquoise);
  box-shadow: var(--glow-turquoise-soft);
}

.side-link > svg { flex-shrink: 0; }

.side-link-button {
  width: 100%;
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  text-align: left;
}

.sidebar-foot {
  margin-top: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--space-4);
}

.sidebar-foot .user-chip {
  margin: 0 var(--space-2);
  max-width: none;
}

.app-main {
  flex: 1;
  min-width: 0; /* nothing inside the main column may set the page's width */
}

/* Below tablet width the sidebar becomes a sticky horizontal bar — same
   links, same order, CSS-only (no toggle script to fail). */
@media (max-width: 900px) {
  .app-shell { display: block; }

  .sidebar {
    position: sticky;
    top: 0;
    width: auto;
    height: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--space-2);
    overflow-x: auto;
    overflow-y: hidden;
    padding: var(--space-3) var(--space-4);
    border-right: 0;
    border-bottom: 1px solid var(--border-subtle);
  }

  .sidebar-brand { padding: 0 var(--space-2) 0 0; }

  .sidebar-nav {
    flex-direction: row;
    align-items: center;
    gap: var(--space-1);
  }

  .sidebar-group-label { display: none; }

  .sidebar-foot {
    margin-top: 0;
    margin-left: auto;
    flex-direction: row;
    align-items: center;
    border-top: 0;
    padding-top: 0;
  }

  .side-link { flex: 0 0 auto; }
  .side-link-button { width: auto; }
}

@media (max-width: 700px) {
  /* the username chip is the one thing worth dropping on a phone-width bar */
  .sidebar-foot .user-chip { display: none; }
  .side-link { padding: var(--space-2); }
  .side-link span { display: none; }        /* icons carry the bar */
  .side-link.is-active span { display: inline; } /* except the active page name */
}


/* --------------------------------------------------------------------------
   24. Brand Studio
   The UMKM journey: brand list → wizard → hub → playbook → strategy →
   produce. Reuses the app's components (glass-card, chips, badges); the
   classes here are only what those pages add.
   -------------------------------------------------------------------------- */

/* --- shared chips ------------------------------------------------------- */

.chip-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: var(--surface-inset);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
  max-width: 100%;
}

.chip-tag.is-on {
  border-color: var(--border);
  color: var(--klika-turquoise);
}

/* A flag, not a selection (2026-08-24): blueprint_flags on a sprint retro
   names a blueprint section an audience signal contradicted. Amber reads as
   "worth a look", never "wrong" or "off" like .is-on's turquoise would. */
.chip-tag.is-warn {
  background: rgba(247, 197, 51, 0.1);
  border-color: rgba(247, 197, 51, 0.32);
  color: var(--warning);
}

.brand-avatar {
  width: 34px;
  height: 34px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  overflow: hidden;
}

.brand-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.brand-context-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4) var(--space-2) var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
}

.brand-context-chip strong {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text-primary);
  line-height: var(--lh-snug);
}

.brand-context-chip small {
  display: block;
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.brand-hub-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

/* --- brand list --------------------------------------------------------- */

.brand-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-5);
}

.brand-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  color: var(--text-primary);
}

.brand-card-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.brand-card-name {
  font-weight: var(--fw-bold);
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
}

.brand-card-product {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.brand-card-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.brand-card-add {
  display: flex;
}

.brand-card-add-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 130px;
  border: 1.5px dashed var(--border);
  border-radius: var(--radius-lg);
  background: none;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard);
}

.brand-card-add-btn:hover {
  color: var(--klika-turquoise);
  border-color: var(--border-hover);
}

/* --- wizard ------------------------------------------------------------- */

.wizard-shell {
  max-width: 660px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.wizard-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

.wizard-dots { display: flex; gap: var(--space-2); }

.wizard-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-inset-hover);
}

.wizard-dot.is-done {
  background: var(--klika-turquoise);
  box-shadow: var(--glow-turquoise-soft);
}

.wizard-count {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  flex: none;
}

.wizard-title {
  font-size: var(--fs-h1);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
}

.wizard-maksud {
  color: var(--text-secondary);
  font-size: var(--fs-sm);
}

.wizard-contoh {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.wizard-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.wizard-nav-right {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-left: auto;
}

.fork-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-4);
}

.fork-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--surface-inset);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-standard),
              background var(--dur-fast) var(--ease-standard);
}

.fork-card:hover { border-color: var(--border-hover); }

.fork-card input { position: absolute; opacity: 0; pointer-events: none; }

.fork-card:has(input:checked) {
  border-color: var(--border-strong);
  background: var(--surface-inset-hover);
  box-shadow: var(--glow-turquoise-soft);
}

.fork-card-label {
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  line-height: var(--lh-snug);
}

.fork-card-desc {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}

/* --- assets ------------------------------------------------------------- */

.asset-card { display: flex; flex-direction: column; gap: var(--space-4); }

.asset-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.asset-thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.asset-thumb {
  position: relative;
  width: 84px;
  height: 84px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 1px solid var(--border-subtle);
  background: var(--surface-media);
  display: flex;
  align-items: center;
  justify-content: center;
}

.asset-thumb img { width: 100%; height: 100%; object-fit: cover; }

.asset-thumb-pdf {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  color: var(--text-secondary);
  letter-spacing: 0.08em;
}

.asset-thumb-del {
  position: absolute;
  top: 4px;
  right: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-pill);
  border: 0;
  cursor: pointer;
  background: rgba(1, 4, 18, 0.78);
  color: var(--text-secondary);
}

.asset-thumb-del:hover { color: var(--danger); }

.asset-upload-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.asset-file-input { flex: 1; min-width: 0; font-size: var(--fs-2xs); }

.asset-manage-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-5);
}

.asset-manage-col {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

/* --- hub stages ---------------------------------------------------------- */

.stage-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-5);
}

.stage-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.stage-card.is-locked { opacity: 0.62; }

.stage-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.stage-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex: none;
  border-radius: var(--radius-pill);
  background: var(--klika-turquoise);
  color: var(--bg-base); /* hard rule 2: dark text on turquoise */
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
}

.stage-desc {
  font-size: var(--fs-xs);
  color: var(--text-secondary);
  flex: 1;
}

.stage-actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.brand-recent-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-4);
}

.brand-recent-card { display: block; color: var(--text-secondary); min-width: 0; }

.brand-recent-title {
  margin-top: var(--space-2);
  font-size: var(--fs-2xs);
  line-height: var(--lh-snug);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.style-default-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.style-default-row .select { max-width: 420px; }

.brand-danger {
  margin-top: var(--gap-section);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}

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

/* --- playbook ------------------------------------------------------------ */

.playbook-block { display: flex; flex-direction: column; gap: var(--space-4); }

.playbook-block-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.playbook-block-head .card-title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
}

.playbook-text {
  color: var(--text-primary);
  line-height: var(--lh-body);
}

.playbook-oneliner {
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
}

.playbook-oneliner p {
  margin-top: var(--space-1);
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--klika-turquoise);
  line-height: var(--lh-snug);
}

.playbook-adjectives {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.playbook-examples {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-4);
}

.playbook-quote {
  margin-top: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  color: var(--text-primary);
  line-height: var(--lh-body);
  font-size: var(--fs-sm);
}

.playbook-dodont {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-4);
}

.playbook-dodont ul {
  list-style: none;
  margin-top: var(--space-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.playbook-dodont li,
.playbook-avoid-list li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  line-height: var(--lh-snug);
}

.playbook-dodont li > svg,
.playbook-avoid-list li > svg {
  flex-shrink: 0;
  margin-top: 0.18em;
}

.playbook-avoid-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.playbook-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

.playbook-swatch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  font-family: var(--font-mono);
}

.playbook-swatch-fill {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(255, 255, 255, 0.14);
}

.playbook-visual-meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.playbook-visual-meta strong { color: var(--text-primary); }

/* --- strategy ------------------------------------------------------------ */

.grid-2col {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-5);
}

.strategy-primary {
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  color: var(--text-primary);
  margin: var(--space-2) 0 var(--space-3);
  line-height: var(--lh-tight);
}

.strategy-reasons {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.strategy-reasons li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  line-height: var(--lh-snug);
}

.strategy-reasons li > svg { flex-shrink: 0; margin-top: 0.18em; color: var(--success); }

.pillar-bar {
  display: flex;
  height: 14px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  border: 1px solid var(--border-subtle);
  margin: var(--space-4) 0;
}

.pillar-bar-seg { height: 100%; }

.pillar-seg-1 { background: rgba(0, 207, 255, 0.85); }
.pillar-seg-2 { background: rgba(0, 207, 255, 0.42); }
.pillar-seg-3 { background: rgba(121, 20, 175, 0.6); }
.pillar-seg-4 { background: rgba(255, 255, 255, 0.16); }

.pillar-legend {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-4);
}

.pillar-legend-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-width: 0;
}

.pillar-dot {
  width: 12px;
  height: 12px;
  flex: none;
  margin-top: 0.3em;
  border-radius: var(--radius-pill);
}

.pillar-name {
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
  font-size: var(--fs-sm);
}

.plan-list {
  display: flex;
  flex-direction: column;
}

.plan-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--border-subtle);
  min-width: 0;
}

.plan-row:last-child { border-bottom: 0; }

.plan-row > .chip-tag { flex: none; }

.plan-row-body { flex: 1; min-width: 0; }

.plan-idea {
  color: var(--text-primary);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
}

.plan-hook {
  margin-top: 2px;
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}

.plan-format {
  flex: none;
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.plan-action { flex: none; }

@media (max-width: 760px) {
  .plan-row { flex-wrap: wrap; }
  .plan-row-body { flex-basis: 100%; order: 3; }
  .wizard-title { font-size: var(--fs-h2); }
}


/* --- Brand Studio v2 additions (2026-08-23) ------------------------------ */

.deck-cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  flex-wrap: wrap;
  border: 1px solid var(--border);
  box-shadow: var(--glow-turquoise-soft);
}

.deck-cta-actions { display: flex; gap: var(--space-3); flex-wrap: wrap; }

.playbook-oneliner-big {
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
}

.playbook-belajar {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: rgba(0, 207, 255, 0.06);
  border: 1px solid var(--border-subtle);
  font-size: var(--fs-2xs);
  color: var(--text-secondary);
}

.playbook-belajar > svg { flex-shrink: 0; margin-top: 0.18em; color: var(--klika-turquoise); }

.playbook-block table { font-size: var(--fs-2xs); margin-top: var(--space-2); }

/* Brand list: delete affordance on the card itself (an unfinished brand's hub
   redirects into the wizard, so the list is the ONE place it can be deleted). */
.brand-card-wrap { position: relative; display: flex; }
.brand-card-wrap > .brand-card { flex: 1; }
.brand-card-del-form { position: absolute; top: var(--space-3); right: var(--space-3); z-index: 2; }
.brand-card-del {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: var(--surface-inset);
  color: var(--text-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.brand-card-wrap:hover .brand-card-del,
.brand-card-del:focus-visible { opacity: 1; }
.brand-card-del:hover { color: var(--danger); border-color: var(--border); }

/* Swatches as tidy cards: the v2 color roles are full sentences, and the old
   centered-mono layout let them sprawl across the card unaligned. */
.swatch-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--space-3);
}
.swatch-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  min-width: 0;
}
.swatch-fill {
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: var(--radius-xs);
  border: 1px solid rgba(255, 255, 255, 0.14);
}
.swatch-meta { min-width: 0; }
.swatch-hex {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  color: var(--text-primary);
}
.swatch-role {
  margin-top: 2px;
  font-size: var(--fs-2xs);
  color: var(--text-secondary);
  line-height: var(--lh-snug);
}

/* --- Deck viewer: the in-app artifact panel ------------------------------ */

.deck-viewer { position: fixed; inset: 0; z-index: 80; }
.deck-viewer-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(1, 4, 18, 0.6);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.22s var(--ease-standard);
}
.deck-viewer-panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(1120px, 94vw);
  display: flex;
  flex-direction: column;
  background: var(--bg-base-2);
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow-3);
  border-radius: var(--radius-lg) 0 0 var(--radius-lg);
  overflow: hidden;
  transform: translateX(24px);
  opacity: 0;
  transition: transform 0.22s var(--ease-standard), opacity 0.22s var(--ease-standard);
}
.deck-viewer.is-open .deck-viewer-backdrop { opacity: 1; }
.deck-viewer.is-open .deck-viewer-panel { transform: translateX(0); opacity: 1; }
.deck-viewer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--border-subtle);
  background: rgba(5, 8, 31, 0.72);
}
.deck-viewer-title {
  font-weight: var(--fw-semibold);
  font-size: var(--fs-sm);
  color: var(--text-primary);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.deck-viewer-actions { display: flex; gap: var(--space-2); flex: none; }
.deck-viewer-panel iframe { flex: 1; width: 100%; border: 0; background: var(--bg-base); }
body.deck-viewer-open { overflow: hidden; }
@media (max-width: 900px) {
  .deck-viewer-panel { width: 100vw; border-radius: 0; border-left: 0; }
}

/* Funnel-grouped pillars + plan stage labels (2026-08-23) */
.funnel-band {
  margin-top: var(--space-4);
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
}
.funnel-band-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.plan-pillar { display: flex; flex-direction: column; gap: 4px; flex: none; align-items: flex-start; }
.plan-stage {
  font-size: 10px;
  font-weight: var(--fw-semibold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--klika-turquoise);
  opacity: 0.85;
}

/* Current-brand nav item + explicit back links (2026-08-23) */
.side-link-sub {
  margin-left: var(--space-4);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  gap: var(--space-2);
}
.side-sub-rail {
  width: 2px;
  height: 14px;
  flex: none;
  border-radius: 99px;
  background: var(--border-strong);
}
.side-link-sub.is-active .side-sub-rail { background: var(--bg-base); }

.back-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-subtle);
  background: var(--surface-inset);
  margin-bottom: var(--space-3);
}
.back-link:hover { color: var(--klika-turquoise); border-color: var(--border); }

/* --------------------------------------------------------------------------
   25. Publisher Studio
   The niche-channel journey: channel list → wizard → hub → blueprint →
   sprint → produce. Reuses the app's shared components (glass-card, chips,
   the sidebar's side-link-sub); the classes here are only what the channel
   list page adds.
   -------------------------------------------------------------------------- */

.channel-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-5);
}

.channel-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  color: var(--text-primary);
}

.channel-card-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.channel-avatar {
  width: 34px;
  height: 34px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  overflow: hidden;
}

.channel-card-name {
  font-weight: var(--fw-bold);
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
}

.channel-card-handle {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.channel-card-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.channel-card-add {
  display: flex;
}

.channel-card-add-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 130px;
  border: 1.5px dashed var(--border);
  border-radius: var(--radius-lg);
  background: none;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard);
}

.channel-card-add-btn:hover {
  color: var(--klika-turquoise);
  border-color: var(--border-hover);
}

/* --- hub (P3b, 2026-08-24) ------------------------------------------------
   Straight renames of Brand Studio's .brand-hub-head / .brand-context-chip /
   .brand-danger, kept separate on purpose (not a generalization) so the two
   Studios stay free to diverge later without touching the other, the same
   reasoning channel_storage.py gives for duplicating brand_storage.py.
   .channel-avatar already exists above (the list card uses it too). */

.channel-hub-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.channel-context-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4) var(--space-2) var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
}

.channel-context-chip strong {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text-primary);
  line-height: var(--lh-snug);
}

.channel-context-chip small {
  display: block;
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.channel-danger {
  margin-top: var(--gap-section);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.channel-danger .btn:hover { color: var(--danger); }

/* --- blueprint (P3b, 2026-08-24) -------------------------------------------
   Same visual language as Brand Studio's .playbook-* classes, renamed for
   the same reason as the hub classes above: a straight rename, not a
   redesign, kept separate so a future Brand Playbook restyle cannot silently
   ripple into the Channel Blueprint page. */

.blueprint-block { display: flex; flex-direction: column; gap: var(--space-4); }

.blueprint-block-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.blueprint-block-head .card-title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
}

.blueprint-oneliner-big {
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
}

.blueprint-text {
  color: var(--text-primary);
  line-height: var(--lh-body);
}

.blueprint-quote {
  margin-top: var(--space-1);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
  color: var(--text-primary);
  line-height: var(--lh-body);
  font-size: var(--fs-sm);
}

.blueprint-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.blueprint-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-4);
}

.blueprint-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.blueprint-list li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
  line-height: var(--lh-snug);
}

.blueprint-list li > svg { flex-shrink: 0; margin-top: 0.18em; }

.blueprint-belajar {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: rgba(0, 207, 255, 0.06);
  border: 1px solid var(--border-subtle);
  font-size: var(--fs-2xs);
  color: var(--text-secondary);
}

.blueprint-belajar > svg { flex-shrink: 0; margin-top: 0.18em; color: var(--klika-turquoise); }

.blueprint-visual-meta {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--fs-sm);
  color: var(--text-secondary);
}

.blueprint-visual-meta strong { color: var(--text-primary); }

.blueprint-franchise-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-4);
}

.blueprint-franchise-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.blueprint-franchise-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}

/* --- sprint (P4b, 2026-08-24) -----------------------------------------------
   The Program page. Info bar reuses .blueprint-* (same reading language as
   the DNA page); the plan list is its own family, .sprint-plan-*, mirroring
   .plan-row's shape (brand_strategy.html) but kept separate on purpose, same
   call as .blueprint-*/.playbook-* above: this Studio stays free to diverge
   without rippling into the other. */

.sprint-head-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.sprint-info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-3);
}

.sprint-plan-list {
  display: flex;
  flex-direction: column;
}

.sprint-plan-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--border-subtle);
  min-width: 0;
}

.sprint-plan-row:last-child { border-bottom: 0; }

/* The experiment marker: a soft full-row tint, never a coloured left border
   (house rule). The .chip-tag.is-on badge next to the idea carries the
   actual meaning, this is just a scan aid. */
.sprint-plan-row.is-experiment {
  background: rgba(0, 207, 255, 0.05);
  border-radius: var(--radius-sm);
  padding-left: var(--space-3);
  padding-right: var(--space-3);
  margin: 0 calc(var(--space-3) * -1);
}

.sprint-plan-date {
  flex: none;
  width: 64px;
  text-align: center;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  color: var(--text-secondary);
  line-height: var(--lh-snug);
}

.sprint-plan-date small {
  display: block;
  font-size: 0.7rem;
  font-weight: var(--fw-normal);
  color: var(--text-muted);
}

.sprint-plan-row-body { flex: 1; min-width: 0; }

.sprint-plan-idea {
  color: var(--text-primary);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
}

.sprint-plan-hook {
  margin-top: 2px;
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}

.sprint-plan-meta {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.sprint-plan-status { flex: none; }
.sprint-plan-action { flex: none; }

@media (max-width: 760px) {
  .sprint-plan-row { flex-wrap: wrap; }
  .sprint-plan-row-body { flex-basis: 100%; order: 3; }
}

/* Status chips: the computed ladder (planned -> produced -> posted ->
   measured), never a stored value. Same tinted-surface + soft-border formula
   as .status-badge, deliberately its OWN family (not a .status-badge
   modifier) because these four are a strict progression, not an open set of
   job/content states. No border-lining, no dark-on-dark: every tint below is
   a light colour on a dark tinted surface. */

.chip-status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.3rem 0.7rem;
  border-radius: var(--radius-pill);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  border: 1px solid transparent;
  white-space: nowrap;
}

.chip-status-planned {
  background: var(--surface-inset);
  border-color: var(--border-subtle);
  color: var(--text-secondary);
}

.chip-status-produced {
  background: rgba(0, 207, 255, 0.12);
  border-color: rgba(0, 207, 255, 0.3);
  color: var(--klika-turquoise);
}

.chip-status-posted {
  background: rgba(53, 212, 138, 0.12);
  border-color: rgba(53, 212, 138, 0.3);
  color: var(--success);
}

.chip-status-measured {
  background: rgba(121, 20, 175, 0.18);
  border-color: rgba(121, 20, 175, 0.4);
  color: var(--klika-violet-text);
}

.sprint-retro { display: flex; flex-direction: column; gap: var(--space-4); }

.sprint-franchise-reads {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-3);
}

/* --- IG sync + mark-posted (P6b, 2026-08-24) -------------------------------
   The "Udah diposting?" expander on a produced sprint-plan row. <summary>
   reuses .sprint-plan-row's own flex layout untouched (so a produced row
   still lines up with every planned/posted/measured row around it), the
   native <details> marker is suppressed in favour of the chevron already
   riding along in .sprint-plan-action, and the body underneath is a plain
   .disclosure-body-style stack: this item's own render (memory jog, not a
   fuzzy match against the picker below, see channel_sprint.html's own
   comment) then either a picker of the connection's recent unlinked
   SocialPosts or a permalink field. */

.sprint-plan-row-details > summary {
  list-style: none;
  cursor: pointer;
}

.sprint-plan-row-details > summary::-webkit-details-marker {
  display: none;
}

.sprint-plan-list > .sprint-plan-row-details:last-child > summary {
  border-bottom: 0;
}

.mark-posted-caret {
  display: inline-flex;
  transition: transform var(--dur-base) var(--ease-standard);
}

.sprint-plan-row-details[open] > summary .mark-posted-caret {
  transform: rotate(180deg);
}

.mark-posted-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.mark-posted-ref {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.mark-posted-thumb {
  width: 56px;
  flex: none;
  border-radius: var(--radius-sm);
}

.mark-posted-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-height: 260px;
  overflow-y: auto;
  margin-top: var(--space-2);
}

.mark-posted-option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-standard),
              background var(--dur-fast) var(--ease-standard);
}

.mark-posted-option:hover {
  border-color: var(--border);
  background: var(--surface-inset-hover);
}

.mark-posted-option:has(input:checked) {
  border-color: var(--klika-turquoise);
  background: rgba(0, 207, 255, 0.06);
}

.mark-posted-option input[type="radio"] {
  margin-top: 3px;
  accent-color: var(--klika-turquoise);
  flex: none;
}

.mark-posted-option small {
  display: block;
  margin-top: 2px;
  color: var(--text-muted);
}

.mark-posted-option small a {
  color: var(--klika-turquoise);
}

.mark-posted-or {
  text-align: center;
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  margin: 0;
}

/* --- P6c: hub mission control (2026-08-24) ---------------------------------
   The KPI strip, calendar and retro card that open the hub. KPI tiles are
   tier-2 INSET surfaces (never their own .glass-card): they sit nested
   inside the .kpi-strip-card panel, and nesting a second blurred panel
   inside a blurred panel is exactly what section 8's "surface tiers" note
   calls out as a perf trap and visually muddy — one blurred outer card,
   plain tinted tiles inside, same pattern .inset already uses everywhere
   else. No colour is ever the only signal: every status still carries an
   icon + word (.chip-status-*, reused verbatim from Task 11), and the gap
   note / connect invitation lean on text colour + icon, never a border
   accent bar. */

.kpi-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-4);
}

.kpi-tile {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
}

.kpi-label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-secondary);
}

.kpi-value {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: var(--fw-normal);
  line-height: var(--lh-tight);
  color: var(--text-primary);
}

.kpi-sub {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

.kpi-gap-note {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: var(--space-4) 0 0;
  font-size: var(--fs-2xs);
  color: var(--warning);
}

.kpi-gap-note > svg { flex-shrink: 0; margin-top: 0.1em; }

/* Disconnected: an invitation, never a fake zero. Dashed border is the
   existing "not filled in yet" idiom (.style-preview uses the same). */
.kpi-strip-empty {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--surface-inset);
  border: 1px dashed var(--border-subtle);
}

.kpi-strip-empty p {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  color: var(--text-secondary);
}

.kpi-calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--space-3);
}

.kpi-calendar-cell {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-height: 84px;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface-inset);
  border: 1px solid var(--border-subtle);
}

.kpi-calendar-cell.is-today {
  background: var(--surface-inset-hover);
  border-color: var(--border-strong);
}

.kpi-calendar-date {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.kpi-calendar-weekday {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.kpi-calendar-daynum {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  color: var(--text-primary);
}

.kpi-calendar-items {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
}

.kpi-calendar-items .chip-status {
  max-width: 100%;
  white-space: normal;
  text-align: left;
}

.kpi-calendar-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}

.kpi-retro-actions {
  margin-top: var(--space-3);
}

/* Below tablet width a 7-up week strip has no room to breathe — the grid
   becomes a stacked day list instead (same breakpoint .sprint-plan-row and
   the app shell's own sidebar collapse already use). */
@media (max-width: 760px) {
  .kpi-calendar-grid {
    grid-template-columns: 1fr;
  }

  .kpi-calendar-cell {
    flex-direction: row;
    align-items: center;
    min-height: 0;
  }

  .kpi-calendar-date {
    flex: none;
    width: 48px;
    flex-direction: column;
    align-items: center;
    gap: 0;
  }

  .kpi-calendar-items {
    flex-direction: row;
    flex-wrap: wrap;
  }
}
