:root {
  --primary: var(--nv-accent);
  --primary-dark: var(--nv-accent-dark);
  --primary-light: var(--nv-accent-light);
  --primary-bg: var(--nv-accent-soft);
  --secondary: var(--nv-danger);
  --success: var(--nv-success);
  --warning: var(--nv-warning);
  --info: var(--nv-accent);
  --gold: var(--nv-gold);
  --dark: var(--nv-ink-soft);
  --gray: var(--nv-body);
  --gray-light: var(--nv-line);
  --gray-bg: var(--nv-surface-2);
  --white: #ffffff;
  --card: #ffffff;
  --text: var(--nv-ink-soft);
  --sidebar-w: 204px;
  --header-h: 56px;
  --shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
  --radius: 16px;
  --radius-sm: 10px;
  /* Owner-facing borders. The toolkit cards read "blue"; the four editing tool
     groups read "navy blue" — one step darker than the body text navy, so the
     two border colours are never mistaken for each other on the same screen. */
  --studio-blue-border: #1E5BFF;
  --studio-navy-border: #16205C;
  /* Timeline slider — owner 2026-09-19: "the slider bars shall be different
     colours that should be seen now". The old track was navy (#16305C) sitting
     on the navy preview panel (#001134), so it was effectively invisible.
     These are deliberately high-contrast, and each PART is a different colour:
     pale track, electric-blue fill, gold thumb. The preview panel is navy in
     BOTH themes, so these values are shared by light and dark (see .dark below). */
  --studio-slider-track: #C9D4E8;
  --studio-slider-track-line: rgba(0, 17, 52, 0.18);
  --studio-slider-fill: var(--nv-accent);
  --studio-slider-thumb: var(--nv-gold);
}

.dark {
  --gray-bg: var(--nv-ink-2);
  --white: var(--nv-ink-soft);
  --card: var(--nv-ink-soft);
  --text: var(--nv-on-dark);
  --dark: var(--nv-on-dark);
  --gray: var(--nv-on-dark-2);
  --gray-light: var(--nv-ink-line);
  --primary-bg: var(--nv-accent-soft-dark);
  --shadow: 0 4px 20px rgba(0, 0, 0, 0.35);
  /* Navy on a navy card is invisible: lift both borders for dark mode, keeping
     the navy hue (not a pastel blue) so tool groups still read as navy. */
  --studio-blue-border: #4C7DFF;
  --studio-navy-border: #33509E;
  --studio-slider-track: #C9D4E8;
  --studio-slider-track-line: rgba(0, 17, 52, 0.18);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html, body { height: 100%; }

body {
  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
  background: var(--gray-bg);
  color: var(--text);
  min-height: 100dvh;
  display: flex;
}

button { font-family: inherit; }

.sidebar {
  width: var(--sidebar-w);
  min-width: var(--sidebar-w);
  background: var(--white);
  border-right: 1px solid var(--gray-light);
  display: flex;
  flex-direction: column;
  height: 100dvh;
  position: sticky;
  top: 0;
  z-index: 40;
  transition: transform 0.25s ease, width 0.25s ease, min-width 0.25s ease;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 16px 6px;
  font-size: 0.98rem;
  font-weight: 800;
  color: var(--primary);
}

.sidebar-brand .pro {
  color: var(--gold);
  font-size: 0.7rem;
  margin-left: 4px;
  vertical-align: super;
  font-weight: 700;
}

.sidebar-nav {
  list-style: none;
  padding: 6px 12px;
  flex: 1;
  overflow-y: auto;
}

.sidebar-nav li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  color: var(--gray);
  font-size: 0.8rem;
  font-weight: 500;
  cursor: pointer;
  /* Owner 2026-10-10: "add a little more space, just a tad bit too close to each
     other." The 17:32 compaction set this to 4px/0; a small lift restores breathing
    room between rows without losing the compact sidebar. */
  margin-bottom: 2px;
}

.sidebar-nav li:hover { background: var(--primary-bg); color: var(--primary); }
.sidebar-nav li.active { background: var(--primary); color: #fff; }
/* Owner 2026-09-22: "when user click something there shall not be a blue
   background where you cannot see the text. make the text white in the blue
   backgrounds."

   The active row WAS white-on-blue here, but nibvok-theme.css loads AFTER this
   file and carries `.sidebar .sidebar-nav li { color: var(--nv-body); }` — one
   extra class of specificity (0,2,1) that beat the plain `.sidebar-nav li.active`
   (0,2,1) above by source order. Measured on the real page: the selected row was
   `color rgb(48,61,89)` on `background rgb(48,52,244)` — navy text on the blue
   fill, effectively invisible, exactly as reported.

   Fixed here rather than in the theme file so the studio owns its own styling and
   demo-studio.html (which loads both files) is covered by the same rule. The
   selector below is (0,3,1), so it outranks the theme's (0,2,1) whichever order
   the sheets load in. `i` and `span` are named explicitly because the icon and the
   label are separate elements that would otherwise inherit the theme's navy. */
.sidebar .sidebar-nav li.active,
.sidebar .sidebar-nav li.active i,
.sidebar .sidebar-nav li.active span { color: #fff; }
.sidebar-nav li .badge {
  margin-left: auto;
  background: var(--secondary);
  color: #fff;
  font-size: 0.65rem;
  padding: 2px 7px;
  border-radius: 999px;
  font-weight: 700;
}
.sidebar-nav li.active .badge { background: #fff; color: var(--primary); }

/* Owner 2026-10-10: "remove ai tools box to the side bar. when user clicks ai
   tools, the color grades will be a sub menu." The AI Tools box now lives in the
   sidebar as an expandable group. The row reuses .library-heading (the sidebar's
   existing section-heading style), so only the caret and the panel need styling.
   Clicking the row toggles #aiToolsPanelItem in place; it never swaps screens. */
.sidebar-nav li.ai-tools-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  /* `.library-heading` forces cursor:default/background:none with !important; this
     row IS interactive, so restore a pointer + hover affordance. */
  cursor: pointer !important;
}
.sidebar-nav li.ai-tools-bar:hover { background: var(--primary-bg) !important; color: var(--primary) !important; }
.ai-tools-bar-caret { margin-left: auto; transition: transform 0.15s ease; }
.ai-tools-bar.is-open .ai-tools-bar-caret { transform: rotate(180deg); }

.sidebar-nav li.ai-tools-panel-item { display: block; padding: 0; margin-bottom: 4px; }

/* Owner 2026-10-10: "under projects menu - the images and audio links should be
   submenu of projects." Projects is a parent row (caret + toggle) whose SUBMENU holds
   the Images and Audio links, nested and indented under a left rule. Expanded by
   default (nothing the user could see before disappears); the caret flips when open. */
.sidebar-nav li.library-parent { display: flex; align-items: center; gap: 10px; cursor: pointer; }
.sidebar-nav li.library-parent .library-parent-caret { margin-left: auto; transition: transform 0.15s ease; }
.sidebar-nav li.library-parent.is-open .library-parent-caret { transform: rotate(180deg); }
.sidebar-nav li.library-submenu-item { display: block; padding: 0; margin-bottom: 4px; }
.sidebar-submenu { list-style: none; margin: 0 0 0 8px; padding: 1px 0 0 10px; border-left: 2px solid var(--primary-bg); }

/* Owner 2026-10-10: a My Library control sits at the LEFT of the "Describe the
   video you want" box in the generator, and opens the creator's library. */
.ai-my-library-btn { border: 0; cursor: pointer; font-family: inherit; }
.ai-my-library-btn:hover { background: var(--primary); color: #fff; }

/* Owner 2026-10-10: AI Tools lives in the right rail as a compact trigger; clicking it
   opens an OVERLAY POPUP (the .model-overlay pattern) with the tools. The trigger is a
   real <button>; the popup is a body-level dialog, so the rail's zoom never scales it. */
.ai-tools-rail { padding: 8px; }
.ai-tools-rail .ai-tools-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--primary);
  font: inherit;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.ai-tools-rail .ai-tools-bar:hover { background: var(--primary-bg); }
.ai-tools-rail .ai-tools-bar-caret { margin-left: auto; }
/* Owner 2026-10-10: "ai tools - add the words to the right of ai tools - Use Ai to
   enhance your video." The note sits immediately right of the "AI Tools" label,
   before the caret; it takes the remaining row width and wraps inside itself on the
   narrow rail rather than pushing the caret off the bar. */
.ai-tools-bar-note {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.68rem;
  font-weight: 500;
  opacity: 0.85;
  line-height: 1.15;
  white-space: normal;
}
.ai-tools-overlay .ai-tools-overlay-card { max-width: 460px; }
.ai-tools-overlay .ai-tools-menu { margin: 0; padding: 2px 0 0; border-left: 0; }
.ai-tools-menu {
  padding: 4px 4px 6px 10px;
  margin-left: 8px;
  border-left: 2px solid var(--primary-bg);
}
.ai-tools-sub { list-style: none; margin: 0; padding: 0; }
.ai-tools-subitem {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--gray);
  cursor: pointer;
}
.ai-tools-subitem:hover { background: var(--primary-bg); color: var(--primary); }
.ai-tools-subitem i:first-child { width: 15px; text-align: center; }
.ai-tools-subcaret { margin-left: auto; font-size: 0.7rem; opacity: 0.7; transition: transform 0.15s ease; }
.ai-tools-subitem.is-open .ai-tools-subcaret { transform: rotate(90deg); }

/* The Colour grade nested submenu (the server's real presets). */
.ai-tools-sub--nested {
  padding: 2px 0 4px 16px;
  display: grid;
  gap: 2px;
}
.ai-tools-sub--nested .ai-tools-subitem {
  padding: 5px 8px;
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--gray);
}
.ai-tools-sub--nested .ai-tools-subitem i:first-child { width: 12px; }
.ai-tools-sub--nested[hidden] { display: none; }
.ai-tools-subitem.is-playing { opacity: 0.6; pointer-events: none; }
.ai-tools-menu-foot { margin-top: 4px; padding: 6px 0 0 4px; }
.ai-tools-menu-foot .tool-group-note { margin: 4px 0 0; }

.sidebar-footer {
  padding: 4px 10px;
  border-top: 1px solid var(--gray-light);
}

.token-display {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--primary-bg);
  border-radius: var(--radius-sm);
  padding: 4px 10px;
  margin-bottom: 3px;
  font-weight: 700;
  color: var(--primary);
}

.token-display i { color: var(--gold); }

.btn-buy-tokens {
  margin-left: auto;
  border: none;
  background: var(--primary);
  color: var(--nv-on-dark);
  border-radius: var(--radius-sm);   /* owner 2026-09-19: soft, not square */
  font-family: var(--nv-font-mono);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 4px 10px;
  font-size: 0.75rem;
  font-weight: 700;
  cursor: pointer;
}

.user-card {
  display: flex;
  align-items: center;
  gap: 10px;
}

.avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--primary);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  flex-shrink: 0;
}

.user-card .name { display: block; font-size: 0.85rem; font-weight: 600; }
.user-card .plan { display: block; font-size: 0.72rem; color: var(--gray); }

.main-content {
  flex: 1;
  min-width: 0;
  padding: 0 16px 40px;
  overflow-x: hidden;
}

.top-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  height: var(--header-h);
  position: sticky;
  top: 0;
  background: var(--gray-bg);
  z-index: 20;
}

/* Owner 2026-09-22: "move these boxes to the header bottom 21 Videos Created /
   Not tracked Total Views / Engagement 687 / Tokens Available." The four stat
   boxes are now the header's second row, so the header grows to fit them and the
   stats band that used to sit under the welcome banner is gone.

   Scoped with the --stats modifier because demo-studio.html shares .top-header
   and must keep its single-row header. */
.top-header--stats {
  height: auto;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  padding: 8px 0;
}
.top-header-main {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: var(--header-h);
}
/* Inside the header the stats are a tighter strip: they sit directly under the
   breadcrumb row, so the band's own bottom margin is dropped. */
.top-header--stats .stats-grid { margin-bottom: 0; }

.header-left, .header-right { display: flex; align-items: center; gap: 8px; }

.menu-toggle, .icon-btn {
  /* Owner 2026-10-04: "reduce the size of the buttons by 20%. ... white
     background, and blue font." 36px -> 28.8px (= -20%) and the glyph 16px ->
     ~12.8px; the background stays white and the icon goes blue. */
  width: 28.8px;
  height: 28.8px;
  font-size: 0.8rem;
  border: 1px solid var(--gray-light);
  background: var(--white);
  border-radius: 8px;
  color: var(--primary);
  cursor: pointer;
  position: relative;
}

.icon-btn .dot {
  /* Nudged in with the button, so the badge stays on the corner after the
     -20% resize. */
  position: absolute;
  top: 6px;
  right: 6px;
  width: 7px;
  height: 7px;
  background: var(--secondary);
  border-radius: 50%;
}

.breadcrumb { font-size: 0.85rem; color: var(--gray); }
.breadcrumb .separator { margin: 0 6px; }
.breadcrumb .active { color: var(--text); font-weight: 600; }

.welcome-banner {
  background: linear-gradient(135deg, var(--primary), var(--primary-dark));
  color: #fff;
  border-radius: var(--radius);
  padding: 22px 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-bottom: 18px;
}

/* Owner 2026-09-23: the two read-outs that moved into the blue bar are boxes like
   the buttons beside them, so every item in this row shares one width and height.
   Sizes are a fixed flex basis rather than content width, or the longer
   "Not tracked" would set a different width from "23".

   Scoped to `.studio-page` (studio.html's body class) because demo-studio.html
   shares `.welcome-banner` and `.banner-actions` and has only the two buttons: an
   unscoped `flex: 0 0 148px` would stretch the demo's New Project and Import to a
   size nobody asked for. */
.studio-page .welcome-banner .banner-actions { align-items: stretch; gap: 8px; }
.studio-page .welcome-banner .banner-content { flex: 1 1 320px; min-width: 0; }
/* Owner 2026-10-04 (Iris-relayed): "IN THE HEADER THERE ARE 5 BOXES, PLEASE REDUCE
   THE WIDTH OF THE BOXES. BACKGROUND COLOR OF THOSE BOXES SHALL BE WHITE." The five
   boxes are the welcome-banner row - Total Views, Engagement, New Project, Import
   and Save. Narrower than the old shared 148px, and each gets a solid white fill. */
.studio-page .welcome-banner .banner-actions > * {
  flex: 0 0 116px;
  min-height: 56px;
  background: var(--white);
}
.banner-stat {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: 6px 12px;
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  min-width: 0;
}
/* White boxes: the read-out text flips from white-on-blue to ink-on-white, and the
   hairline edge (drawn for the blue gradient) goes to the page's light line so the
   box still reads as a box on white. */
.studio-page .welcome-banner .banner-stat {
  background: var(--white);
  border-color: var(--gray-light);
  color: var(--text);
}
.banner-stat-value { font-size: 1rem; font-weight: 800; line-height: 1.2; }
/* "Not tracked" is longer and lower-case; keep it inside the box. */
.banner-stat-value.is-na { font-size: 0.8rem; font-weight: 600; opacity: 0.9; }
.banner-stat-label { font-size: 0.68rem; opacity: 0.85; }
.studio-page .welcome-banner .banner-stat .banner-stat-value,
.studio-page .welcome-banner .banner-stat .banner-stat-label { color: var(--gray); opacity: 1; }
/* Owner 2026-10-10: the Schedule box moved out of the editor rail into the welcome
   banner, between the Engagement read-out and New Project. It keeps the tool-group
   markup but is restyled as one card in the action row: same white fill + light
   hairline as the read-outs, auto width, and title/note/link on a single row. */
.studio-page .welcome-banner .banner-actions > .banner-schedule {
  flex: 0 0 auto;
  min-height: 46px;
  background: var(--white);
  border: 1px solid var(--gray-light);
  border-radius: var(--radius-sm);
  color: var(--text);
  padding: 8px 12px;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
}
.studio-page .welcome-banner .banner-actions > .banner-schedule h4 { margin: 0; white-space: nowrap; }
.studio-page .welcome-banner .banner-actions > .banner-schedule .tool-group-note { margin: 0; }
.studio-page .welcome-banner .banner-actions > .banner-schedule .tool-buttons { margin: 0; }
.studio-page .welcome-banner .banner-actions > .banner-schedule .tool-link {
  white-space: nowrap; text-decoration: none; font-weight: 600; color: var(--primary);
}
@media (max-width: 900px) {
  .studio-page .welcome-banner .banner-actions > .banner-schedule {
    flex: 0 0 100%; flex-direction: column; align-items: flex-start; gap: 6px;
  }
}
/* The two buttons keep their row layout but take the same box as the read-outs, so
   all four items read as one set. The padding is tightened to fit "New Project"
   plus its icon inside the shared width. */
.studio-page .welcome-banner .banner-actions > .btn-primary,
.studio-page .welcome-banner .banner-actions > .btn-secondary {
  justify-content: center;
  padding: 12px 10px;
  font-size: 0.85rem;
}

.welcome-banner h1 { font-size: 1.4rem; font-weight: 800; }
.welcome-banner p { opacity: 0.92; font-size: 0.92rem; margin-top: 4px; }
/* Owner 2026-09-22: "increase the size of the words Your social media creator
   studio is ready. Create, edit, and publish content faster than ever." Measured
   before: 14.72px. Raised to 1.05rem (16.8px) so the line reads as a statement
   rather than fine print. Scoped to #welcomeCopy so the other banner <p>
   elements are untouched. */
#welcomeCopy { font-size: 1.05rem; line-height: 1.45; }
.banner-actions { display: flex; flex-wrap: wrap; gap: 10px; }

/* Owner 2026-09-22: "the import button word at upper top right should be white so
   user can see better." It rendered rgb(0,17,52) because nibvok-theme.css loads
   AFTER studio.css and its `.btn-secondary { color: var(--nv-text) }` won on
   source order. The id in this selector outranks that class rule, so the label is
   white without !important. The border is lightened too: a white label on the blue
   gradient with the theme's dark hairline left the button edge hard to see. */
.welcome-banner #importBtn { color: #fff; border-color: rgba(255, 255, 255, 0.65); }
.welcome-banner #importBtn:hover { background: rgba(255, 255, 255, 0.14); }

/* Owner 2026-10-04: the boxes are white now, so Import's white-on-blue label would
   vanish. Ink it to the accent and give it the page's light hairline. Higher
   specificity than `.welcome-banner #importBtn` above (two classes + id) so it wins
   without `!important`. */
.studio-page .welcome-banner #importBtn {
  color: var(--primary);
  border-color: var(--gray-light);
}
.studio-page .welcome-banner #importBtn:hover {
  background: var(--primary-bg);
  border-color: var(--primary);
}

.btn-primary, .btn-secondary, .btn-outline, .btn-package {
  border: none;
  border-radius: var(--radius-sm);
  padding: 10px 16px;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
}

.btn-primary { background: #fff; color: var(--primary); }
.welcome-banner .btn-primary { background: #fff; color: var(--primary); }
.studio-controls .btn-primary { background: var(--primary); color: #fff; }
.btn-secondary {
  background: transparent;
  color: #fff;
  border: 1.5px solid rgba(255,255,255,0.4);
}
.btn-outline {
  background: var(--white);
  color: var(--text);
  border: 1px solid var(--gray-light);
}

/* Owner 2026-09-20: "all these pages should be 50% smaller to take up less
   space." Halved on the FOOTPRINT (padding, gaps, icon and label scale) rather
   than on the numbers alone - halving the figures would make the page harder to
   read, not smaller. Every value below is roughly half its previous value. */
.stats-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px;
  margin-bottom: 10px;
}

.stat-card {
  background: var(--card);
  border: 1px solid var(--gray-light);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
  display: flex;
  align-items: center;
  gap: 8px;
  box-shadow: var(--shadow);
}

.stat-icon {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  font-size: 0.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  flex-shrink: 0;
}
.stat-icon.purple { background: var(--primary); }
.stat-icon.green { background: var(--success); }
.stat-icon.orange { background: var(--warning); }
.stat-icon.blue { background: var(--info); }

.stat-info { flex: 1; min-width: 0; }
.stat-value { display: block; font-size: 1.05rem; font-weight: 800; }
/* "Not tracked" is longer and lower-case; keep it inside the card. */
.stat-value.is-na { font-size: 0.8rem; font-weight: 600; color: var(--gray); }
.stat-label { font-size: 0.68rem; color: var(--gray); }
.stat-change { font-size: 0.8rem; font-weight: 600; }
.stat-change.up { color: var(--success); }
.stat-change.down { color: var(--secondary); }

.studio-section, .recent-projects, .token-packages, .recent-activity, .media-library {
  background: var(--card);
  border: 1px solid var(--gray-light);
  border-radius: var(--radius);
  padding: 10px;
  margin-bottom: 10px;
  box-shadow: var(--shadow);
}
/* Token-purchase panel follows the cart's square geometry (scoped: the other
   panels in the group above keep their existing radius). */
.token-packages { border-radius: var(--radius); padding: 24px; }

.studio-header, .section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.studio-header h2, .section-header h2 {
  font-size: 0.95rem;
  display: flex;
  align-items: center;
  gap: 8px;
}
.studio-header h2 i, .section-header h2 i { color: var(--primary); }
.studio-controls { display: flex; gap: 8px; }
.hint { color: var(--gray); font-size: 0.8rem; }
.view-all {
  background: none;
  border: none;
  color: var(--primary);
  font-weight: 600;
  cursor: pointer;
  font-size: 0.85rem;
}

.studio-workspace { display: flex; flex-direction: column; gap: 8px; }

.preview-container {
  background: var(--nv-text);
  border-radius: 12px;
  overflow: hidden;
  /* Positioning context for the studio-only score badge. It must be anchored to the
     VIDEO, not the page, or it would float over unrelated chrome. */
  position: relative;
}

.preview-stage {
  position: relative;
  /* Owner 2026-09-22: "when you click on videos the video viewer reduces to a
     very small box."

     A <video> and the overlay are BOTH position:absolute, so neither contributes
     any height. The only in-flow child was .preview-thumbnail, whose
     `aspect-ratio: 16 / 9` was the sole thing giving this stage its height.
     studio.js hides that thumbnail when a video loads (it would otherwise sit
     behind the clip), so the stage lost its last in-flow child and collapsed to
     0px - measured 325px --> 0px. The clip then filled 0 height and vanished.

     The 16/9 ratio moved HERE from the thumbnail, so the stage owns its own
     height and keeps it whether the thumbnail is shown or hidden. Measured size
     is unchanged (577x325 at the current column width), so nothing else moves. */
  aspect-ratio: 16 / 9;
}

/* The generated clip paints into this element, which must sit ON the poster
   layer. It used to be an unstyled in-flow <video> with no src: the browser gave
   it its default 300x150 box and painted it black, so an empty studio showed a
   black rectangle in the video screen's top-left, a navy gap beside it, and the
   poster pushed 150px down out of alignment. Absolute + inset:0 means the empty
   element occupies no space; it fills the stage once it actually has media.
   Owner 2026-09-20. */
.preview-stage .preview-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  z-index: 1;
}

/* A <video> with no src paints an opaque dark surface. Left visible it would
   now cover the whole poster layer, so an element with no media is not shown at
   all; the moment studio.js sets a src the selector stops matching and the clip
   appears. This is what keeps the blue screen visible in an empty studio. */
.preview-stage .preview-video:not([src]) { display: none; }

.preview-thumbnail {
  width: 100%;
  max-width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  background:
    radial-gradient(circle at 30% 40%, rgb(var(--nv-accent-rgb) / 0.45), transparent 50%),
    linear-gradient(135deg, var(--nv-ink-soft), var(--nv-ink-line));
}

.preview-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.18);
  z-index: 2;
}

.play-btn {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  border: none;
  background: var(--primary);
  color: #fff;
  font-size: 1.2rem;
  cursor: pointer;
}

.preview-timeline { padding: 10px 12px 4px; color: var(--nv-body-2); }

/* ------------------------------------------------------------------
   Timeline scrub slider — the "slider bar" the owner asked to SEE.
   The range input ships `disabled` in the markup (no media loaded yet),
   so it reads greyed-and-ready; JS enables it the moment a video is in
   the preview. Both the track and the thumb are painted explicitly,
   because a native range look on a navy panel is near-invisible.
   Owner 2026-09-19: each part is a DIFFERENT colour and high-contrast.
   ------------------------------------------------------------------ */
.timeline-slider {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: 10px;
  margin: 6px 0 8px;
  padding: 0;
  border-radius: 999px;
  border: 1px solid var(--studio-slider-track-line);
  /* Fallback surface for engines that do not paint the track pseudo. */
  background: var(--studio-slider-track);
  cursor: pointer;
}
.timeline-slider:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
}
.timeline-slider::-webkit-slider-runnable-track {
  height: 8px;
  border-radius: 999px;
  background-color: var(--studio-slider-track);
  /* The played portion, in the accent colour. --scrub-pct is written by
     studio.js on every playhead move, so the fill is the REAL position. */
  background-image: linear-gradient(
    to right,
    var(--studio-slider-fill) 0 var(--scrub-pct, 0%),
    transparent var(--scrub-pct, 0%) 100%
  );
}
.timeline-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  margin-top: -5px;
  border-radius: 50%;
  border: 2px solid var(--nv-ink);
  background: var(--studio-slider-thumb);
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.45);
}
.timeline-slider::-moz-range-track {
  height: 8px;
  border-radius: 999px;
  background-color: var(--studio-slider-track);
}
/* Firefox paints the played portion itself - use it rather than a gradient. */
.timeline-slider::-moz-range-progress {
  height: 8px;
  border-radius: 999px;
  background-color: var(--studio-slider-fill);
}
.timeline-slider::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--nv-ink);
  background: var(--studio-slider-thumb);
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.45);
}
/* Disabled = no media loaded yet. Muted, but STILL clearly visible. */
.timeline-slider:disabled {
  cursor: not-allowed;
  opacity: 0.85;
}
.timeline-slider:disabled::-webkit-slider-thumb {
  background: var(--nv-on-dark-2);
  border-color: var(--nv-ink);
}
.timeline-slider:disabled::-moz-range-thumb {
  background: var(--nv-on-dark-2);
  border-color: var(--nv-ink);
}
/* Enabled = a video is loaded: full contrast, accent ring. */
.timeline-slider:not(:disabled) {
  opacity: 1;
  border-color: var(--primary);
}
.timeline-slider:not(:disabled)::-webkit-slider-thumb {
  background: var(--studio-slider-thumb);
  border-color: var(--nv-ink);
}
.timeline-slider:not(:disabled)::-moz-range-thumb {
  background: var(--studio-slider-thumb);
  border-color: var(--nv-ink);
}
.timeline-bar {
  height: 6px;
  /* Was var(--nv-text) — navy on the navy preview panel, i.e. invisible.
     Owner 2026-09-19: the bar must be seen, and in a different colour to
     the scrub slider above it. */
  background: var(--studio-slider-track);
  border-radius: 999px;
  position: relative;
  cursor: pointer;
}
.timeline-progress {
  height: 100%;
  background: var(--studio-slider-fill);
  border-radius: 999px;
}
/* Time marks ON the bar (owner 2026-09-22: "timelines need to have time marks on
   the bar"). Absolutely positioned inside `.timeline-bar` so a mark sits at its
   true fraction of the bar's own width; the tick list is filled by JS from the
   real duration and is aria-hidden because the times are also printed as text in
   `.timeline-time` below. */
.bar-ticks {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.bar-tick {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 1px;
  background: var(--nv-on-dark-2, rgba(255, 255, 255, 0.55));
}
.bar-tick.is-zero { background: var(--primary); width: 2px; }
/* Tenth-of-a-second subdivision on the preview bar, matching the ruler's. Fainter
   than a whole-second mark, so the seconds still read as the landmarks. */
.bar-tick.is-minor { opacity: 0.45; }
.playhead {
  position: absolute;
  top: 50%;
  width: 12px;
  height: 12px;
  background: var(--studio-slider-thumb);
  border-radius: 50%;
  transform: translate(-50%, -50%);
}
.timeline-time {
  display: flex;
  justify-content: space-between;
  font-size: 0.65rem;
  margin-top: 6px;
  opacity: 0.8;
}
/* [hidden] MUST win. The author `display:flex` above overrides the UA
   [hidden]{display:none}, so this row painted five empty spans under the scrub bar
   before any video duration existed. Class+attribute (0,2,0) beats class alone. */
.timeline-time[hidden] { display: none; }

.preview-controls {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px 12px;
  color: #fff;
  flex-wrap: wrap;
}
.preview-controls button {
  background: transparent;
  border: none;
  color: #fff;
  cursor: pointer;
  width: 32px;
  height: 32px;
}
.preview-time { margin-left: auto; font-size: 0.8rem; opacity: 0.85; }

/* Owner 2026-09-25: "the bottom of the video screen shall be 2 revert buttons,
   back and forward". These carry a word as well as an icon, so they need a width
   their icon-only neighbours do not - and a border, per the owner's standing rule
   that every pill and box is bordered. Disabled state is visibly inert so a
   control never looks available when there is nothing to step to. */
.preview-controls .history-btn {
  width: auto;
  min-width: 32px;
  padding: 0 9px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.72rem;
  font-weight: 700;
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 6px;
}
.preview-controls .history-btn:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.14);
}
.preview-controls .history-btn:disabled { opacity: 0.38; cursor: not-allowed; }

/* Owner 2026-10-04: screen actions on the video viewer ("add to timeline or
   draft"). Same bordered-pill shape as the Back/Forward buttons so the viewer
   reads as one control row; "Add to timeline" carries the accent border because
   it is the primary action on a freshly loaded clip. */
.preview-controls .screen-action {
  width: auto;
  min-width: 32px;
  padding: 0 10px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.72rem;
  font-weight: 700;
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 6px;
}
.preview-controls .screen-action:hover:not(:disabled) { background: rgba(255, 255, 255, 0.14); }
.preview-controls .screen-action:disabled { opacity: 0.38; cursor: not-allowed; }
.preview-controls #addToTimelineBtn {
  border-color: rgba(124, 231, 192, 0.65);
  color: #bff3e1;
}
.preview-controls #addToTimelineBtn:hover:not(:disabled) { background: rgba(124, 231, 192, 0.18); }

/* Owner 2026-10-04: the Render-timeline control and the link to its finished file.
   The link is an <a>, so it needs the same pill shape the buttons get via
   `.screen-action` plus the text-colour/text-decoration the button rules supply. */
.preview-controls .export-link {
  text-decoration: none;
  color: #cfe0ff;
  border-color: rgba(125, 177, 255, 0.6);
}
.preview-controls .export-link:hover { background: rgba(125, 177, 255, 0.16); }

/* ------------------------------------------------------------------
   Volume slider (owner 2026-09-25: "video controls should have a volume bar").

   The mute button alone could only toggle all-or-nothing, so there was no way to
   turn a clip down. This is a real <input type="range"> bound to the preview
   video's own `volume`, so the value it shows IS the value being played.

   The track and thumb are styled per-engine because `appearance: none` removes
   the native control in Blink/WebKit; `accent-color` is kept as the fallback for
   engines that ignore the pseudo-elements. */
.preview-controls .preview-volume {
  -webkit-appearance: none;
  appearance: none;
  width: 88px;
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.38);
  cursor: pointer;
  accent-color: #fff;
  vertical-align: middle;
}
.preview-controls .preview-volume:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
}
.preview-controls .preview-volume::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #fff;
  border: none;
  cursor: pointer;
}
.preview-controls .preview-volume::-moz-range-thumb {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #fff;
  border: none;
  cursor: pointer;
}
.preview-controls .preview-volume::-moz-range-track {
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.38);
}

/* ------------------------------------------------------------------
   MUSIC LIBRARY (owner 2026-09-25: "the audio library button links to video and
   pictures. please find a library of music that users can add music to their
   tracks.")

   This panel is what the Audio tool-group's Library button opens now, instead of
   sending the creator to the PHOTO/VIDEO gallery. It follows the house border rule
   (every box and pill is bordered) and reuses the tool-group shell so it reads as
   one of the editing boxes rather than a bolted-on modal.
   ------------------------------------------------------------------ */
/* ------------------------------------------------------------------
   Music library (owner 2026-09-25: "move music library to videos created. get rid
   of video created. everyting below the searc box should be an overlay.").

   The box is now ONE row: the heading and the search field. Everything the search
   produces - source filters, the licence note, the results and Close - lives in
   `.music-lib-pop`, an OVERLAY floating over the editor.

   Two clipping traps, both measured rather than assumed:
     * the old `.music-lib` carried `max-height: 420px; overflow-y: auto`. An
       overflow container CLIPS its absolutely positioned descendants, so that
       rule would have cut the overlay off at the box. The scroll now lives on
       `.music-lib-results` (already capped), which leaves the overlay free to
       extend past the box.
     * the overlay sits inside `.main-content`, which sets `overflow-x: hidden`.
       `left: 0; right: 0` keeps it inside the rail's own width, so nothing clips
       it horizontally; it grows DOWNWARD, where no ancestor clips.

   Hidden state uses `opacity` + `visibility`, like `.ai-lib-pop`: `visibility:
   hidden` also removes the panel from hit-testing, so an invisible panel can
   never swallow a click in the editor behind it. */
.music-lib { position: relative; }
/* Owner 2026-10-10: "fit music library words on same line as the search music
   box." The heading and the search row were stacked (the h4 took its own line).
   The box is now a flex row: heading left (nowrap), search row right, filling the
   remaining width. .music-lib-pop stays position:absolute, so it is out of flow
   and keeps floating below the box unchanged. */
#musicLibBox { display: flex; align-items: center; gap: 8px; }
#musicLibBox > h4 { margin: 0; flex: 0 0 auto; white-space: nowrap; }
#musicLibBox > .music-lib-search { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
.music-lib-pop {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  max-height: 62vh;
  overflow-y: auto;
  background: var(--card, #fff);
  border: 2px solid var(--studio-navy-border);
  border-radius: var(--radius-sm);
  box-shadow: 0 12px 30px rgba(16, 21, 43, 0.20);
  padding: 10px;
  z-index: 70;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s ease;
}
.music-lib.is-open .music-lib-pop {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.music-lib-search { display: flex; gap: 6px; margin-bottom: 6px; }
.music-lib-search input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px 9px;
  border: 1px solid var(--border, rgba(0, 0, 0, 0.18));
  border-radius: 6px;
  font-size: 0.78rem;
  background: #fff;
}
.music-lib-filters { flex-wrap: wrap; }
/* The active source filter is stated in colour AND weight, so it is not conveyed
   by hue alone for anyone who cannot distinguish it. */
.music-lib-filters .tool-btn.is-active {
  border-color: var(--primary, #0f3460);
  font-weight: 800;
  background: rgba(15, 52, 96, 0.08);
}
.music-lib-licences { font-size: 0.68rem; line-height: 1.35; }
.music-lib-results { list-style: none; margin: 6px 0; padding: 0; max-height: 230px; overflow-y: auto; }
.music-lib-empty { font-size: 0.74rem; opacity: 0.75; padding: 6px 2px; }
/* A source being unreachable is a warning, not an ordinary empty result. */
.music-lib-warn { color: #8a5a00; opacity: 1; font-weight: 700; }
.music-lib-track {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 7px 8px;
  margin-bottom: 5px;
  border: 1px solid var(--border, rgba(0, 0, 0, 0.14));
  border-radius: 7px;
  background: var(--gray-bg, #f7f7fb);
}
.music-lib-meta { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.music-lib-meta strong { font-size: 0.78rem; word-break: break-word; }
.music-lib-by { font-size: 0.7rem; opacity: 0.85; }
/* The licence is always on screen: these tracks require attribution, so the credit
   is shown on the row itself and not hidden behind a tooltip. */
.music-lib-lic { font-size: 0.66rem; font-weight: 700; color: var(--primary, #0f3460); }
.music-lib-src { font-size: 0.64rem; opacity: 0.7; }
.music-lib-actions { flex-wrap: wrap; }

.editing-tools {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}

/* Every editing box shares ONE border, including the Upload/platform group.
   Owner 2026-09-22: "the upload box should have a border color to keep
   uniformity." Before this the base rule was a 1px grey hairline and only the
   four `[data-tool-group]` boxes overrode it to 2px navy, so Upload read as a
   different (lesser) kind of box beside them. The navy border now lives on the
   single base rule, so every group - present and future - is uniform by default
   instead of by remembering to add an override. */
.tool-group {
  border: 2px solid var(--studio-navy-border);
  border-radius: var(--radius-sm);
  padding: 8px;
}
/* Owner 2026-10-10: "all boxes on both sides of the video viewer should have a black
   border outside." Every boxed panel in the two editor rails is either a `.tool-group`
   (Cut & Trim, Audio, Music library, Text on video, Schedule, AI Tools) or the right
   rail's model control box (`.ai-gen-select-box`); both now take a solid black border
   so the boxes read identically on either side of the video. Scoped to `.editing-tools`
   so it cannot reach a `.tool-group` elsewhere on the page. Black is written as a
   literal rather than a theme token because the border is the same in light and dark,
   and this must out-specify the dark `.ai-gen-select-box { border-color: … }` lift. */
.editing-tools .tool-group,
.editing-tools--right .ai-gen-select-box {
  border-color: #000;
}

/* One short line under a group heading, where the heading alone could be read
   the wrong way round (see the Upload group). */
.tool-group-note {
  font-size: 0.66rem;
  line-height: 1.35;
  color: var(--gray);
  margin: -2px 0 6px;
}

/* Owner 2026-09-25: "keep my work out of NIBVOK galleries (+20%)". Sits inside
   the existing Upload group (NOT a new .tool-group, which would break the 5-box
   contract the layout tests pin). Never pre-ticked on purpose. */
.gallery-optout-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.7rem;
  line-height: 1.3;
  color: var(--navy, #16233a);
  margin: 8px 0 4px;
  cursor: pointer;
}

.gallery-optout-row input[type="checkbox"] {
  width: 14px;
  height: 14px;
  margin: 0;
  flex: 0 0 auto;
  cursor: pointer;
}

.gallery-optout-note {
  margin-top: 0;
}

.tool-group h4 {
  font-size: 0.78rem;
  margin-bottom: 6px;
  display: flex;
  align-items: center;
  gap: 6px;
  /* Owner 2026-09-25: "titles in all the boxes should bold and darker in color."
     The default was 0.72rem at `--group`-only strength, which sat close to the
     note text in weight. 800 + the darker `--group-ink` below makes the title the
     first thing read in each box. */
  font-weight: 800;
  color: var(--group-ink, var(--nv-text, var(--text)));
}

/* Owner 2026-10-04 (Iris-relayed): "CUT AND TRIM BOX - MOVE THE 4 BUTTONS TO THE
   RIGHT OF THE CUT AND TRIM WORDS." One row per tool box: heading on the left, its
   buttons on the right. The row does NOT wrap as a whole - instead the button block
   itself wraps internally, right-aligned, so the pills always stay to the RIGHT of
   the heading even when the rail is too narrow to hold all four on one line. */
.tool-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 6px 8px;
}

/* Hold the heading at its natural width and keep it on one line, so a tight rail
   shrinks the button block rather than breaking "Cut & Trim" across two lines. */
.tool-head h4 { margin-bottom: 0; flex: 0 0 auto; white-space: nowrap; }
.tool-head .tool-buttons { flex: 1 1 auto; min-width: 0; justify-content: flex-end; }

.tool-buttons, .platform-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Owner 2026-10-04 (latest): "move all 4 buttons that are flush right up to the
   top of the box across from the icon and AI Tools words, keep flush right. move
   generate b-roll to the left of auto captions. move enhance to the left of the
   color grade." The pills sit in the box's TOP row beside the heading, flush
   right, as a 2x2: row 1 = Generate B-Roll | Auto Captions, row 2 = Enhance |
   Color Grade. Grid fills row-major, so the DOM order is B-Roll, Auto Captions,
   Enhance, Color Grade. If the rail is too narrow to hold the heading and the
   grid side by side, the grid wraps to its own line (still flush right) rather
   than overflowing. */
.ai-tools-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 6px;
}

.ai-tools-head h4 { margin-bottom: 0; }

.ai-tools-stack {
  display: grid;
  grid-template-columns: repeat(2, auto);
  gap: 6px;
  justify-items: end;
  justify-content: end;
  margin-left: auto;
}

.tool-btn, .platform-btn {
  border: 1px solid var(--gray-light);
  background: var(--gray-bg);
  color: var(--text);
  border-radius: 8px;
  padding: 5px 8px;
  font-size: 0.7rem;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.tool-btn:hover, .platform-btn:hover, .tool-btn.active, .platform-btn.active {
  border-color: var(--primary);
  color: var(--primary);
  background: var(--primary-bg);
}

/* ------------------------------------------------------------------
   Per-group button colours (owner 2026-09-22: "make the buttons
   different colors to bring life to the page").

   Before this, every one of the 14 tool buttons measured the SAME
   background (rgb(246,249,254)) and the same grey border, so the five
   groups were indistinguishable at a glance and the rail read as one
   undifferentiated block of grey.

   Each group now owns a hue. The hover/active state is derived from the
   group's own colour rather than the global accent, so the state change
   is visible *within* the group instead of every button turning the same
   blue on hover.
   ------------------------------------------------------------------ */
/* `--group-ink` is a DARKER shade of each group's own hue, used for the box titles
   (owner 2026-09-25: "titles in all the boxes should bold and darker in color").
   The hue is kept rather than flattened to one ink so each box still reads as
   itself - which is what the per-group palette above was for - while the title
   gains the contrast the owner asked for. `--group` still drives the border and
   the buttons, so nothing else changes colour. */
.tool-group[data-tool-group="cut"]   { --group: #F43F5E; --group-soft: #FFF1F3; --group-ink: #9F1239; }  /* rose */
.tool-group[data-tool-group="audio"] { --group: #22C55E; --group-soft: #F0FDF4; --group-ink: #15803D; }  /* green */
.tool-group[data-tool-group="text"]  { --group: #8B5CF6; --group-soft: #F5F3FF; --group-ink: #5B21B6; }  /* violet */
.tool-group[data-tool-group="ai"]    { --group: #F59E0B; --group-soft: #FFFBEB; --group-ink: #92400E; }  /* amber */
/* The fifth group (Upload / platform) has no data-tool-group, exactly as the
   border rule above already relies on. */
.tool-group:not([data-tool-group])   { --group: #1E5BFF; --group-soft: #EFF5FF; --group-ink: #12308F; }  /* blue */

.tool-group[data-tool-group] h4,
.tool-group:not([data-tool-group]) h4 { color: var(--group-ink, var(--group)); }

.tool-btn, .platform-btn {
  border-color: var(--group, var(--gray-light));
  color: var(--group, var(--text));
  background: var(--group-soft, var(--gray-bg));
}

.tool-btn:hover, .platform-btn:hover,
.tool-btn.active, .platform-btn.active {
  border-color: var(--group, var(--primary));
  color: #fff;
  background: var(--group, var(--primary));
}

/* Owner 2026-09-22 (23:26): the Schedule and Analytics boxes moved into this rail
   (see studio.html). They NAVIGATE, so they are anchors - but studio.js binds every
   `.tool-btn` at load to the editing-tool dispatcher, which would answer
   "<label> isn't connected yet". `.tool-link` is therefore a separate class that
   borrows the exact button look without entering that dispatcher. It is not a
   `.tool-btn`, so the five editing groups keep their own contract. */
.tool-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--group, var(--gray-light));
  background: var(--group-soft, var(--gray-bg));
  color: var(--group, var(--text));
  border-radius: 8px;
  padding: 5px 8px;
  font-size: 0.7rem;
  font-weight: 600;
  text-decoration: none;
}
.tool-link:hover { border-color: var(--group, var(--primary)); color: #fff; background: var(--group, var(--primary)); }
/* The two navigation groups take the same blue hue as Upload, so the rail does not
   grow a third accent that nobody asked for. */
.tool-group--nav { --group: #1E5BFF; --group-soft: #EFF5FF; }
/* Owner 2026-09-23: the Videos Created read-out moved into the left rail under the
   "Text on video" box. It is a read-out, not an editing tool, so it carries this
   modifier instead of a bare `.tool-group` - which is what keeps the "five editing
   tools" contract counting five. It takes the same navy border as the boxes around
   it, so the rail still reads as one column of matched boxes. */
.tool-group--stat { --group: var(--studio-navy-border); }

/* ------------------------------------------------------------------
   Half-and-half nav pair (owner 2026-09-25: "in the schedule box in the
   creative studio, split the box in half and add another box link for AI
   Library of Tools").

   The two boxes are real `.tool-group`s (so they keep the shared navy border
   and the blue `--group` accent), placed by a grid wrapper that owns the
   halving. The wrapper deliberately does NOT carry `.tool-group`: it is a
   layout row, not a box, so it must not paint a third border around the pair.
   `align-items:start` keeps each box at its own content height instead of
   stretching the shorter one to match. */
.tool-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  align-items: start;
}
/* Each rail is ~290px wide at the split layout, so two ~140px boxes fit the half
   links but not much else. Below the 640px breakpoint the pair stacks, because
   two half-width boxes there would be ~120px and wrap their labels. */
@media (max-width: 640px) {
  .tool-pair { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------------
   AI Library hover overlay (same owner request: "create categories so user
   knows and can choose").

   Opens LEFTWARD (`right: 0; left: auto`) because this box sits in the rightmost
   rail - anchored the other way the panel would run off the page edge.

   Hidden state uses `opacity` + `visibility`, NOT `display`. `visibility:hidden`
   also removes the panel from hit-testing, so unlike the ai-store backdrop bug
   fixed today (`display:flex` beating `[hidden]`) this overlay can never
   swallow a click while invisible. The transition on `visibility` is what lets
   it fade out instead of vanishing.  */
.ai-lib { position: relative; }
.ai-lib-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  left: auto;
  width: 250px;
  max-width: min(250px, 82vw);
  max-height: 60vh;
  overflow-y: auto;
  background: var(--card);
  border: 2px solid var(--studio-navy-border);
  border-radius: var(--radius-sm);
  box-shadow: 0 12px 30px rgba(16, 21, 43, 0.20);
  padding: 10px;
  z-index: 60;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity 0.14s ease, transform 0.14s ease, visibility 0.14s ease;
}
/* `:focus-within` gives the same panel to keyboard users, who never fire :hover. */
.ai-lib:hover .ai-lib-pop,
.ai-lib:focus-within .ai-lib-pop {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.ai-lib-pop-title {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--gray);
  margin: 0 0 8px;
}
.ai-lib-cats { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.ai-lib-cats a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
}
.ai-lib-cats a:hover { background: var(--primary-bg); color: var(--primary); }
.ai-lib-cats .n {
  font-size: 0.64rem;
  font-weight: 700;
  color: var(--gray);
  background: var(--gray-bg);
  border-radius: 999px;
  padding: 1px 7px;
}
.ai-lib-cats a:hover .n { background: #fff; color: var(--primary); }

.timeline-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}
.timeline-header h4 { font-size: 0.9rem; display: flex; gap: 8px; align-items: center; }
/* Owner 2026-10-04: the play-from-cursor control, sitting with the zoom buttons so
   playback controls live together in the timeline header. */
.timeline-play-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  margin-right: 10px;
  padding: 0 12px;
  height: 28px;
  background: var(--gray-bg);
  border: 1px solid rgba(245, 165, 36, 0.7);
  border-radius: 6px;
  color: #F5A524;
  font-size: 0.74rem;
  font-weight: 700;
  cursor: pointer;
}
.timeline-play-btn:hover { background: rgba(245, 165, 36, 0.16); }

/* P1 (owner 2026-10-11): the labeled "Preview timeline" action. Blue, not amber,
   so it never reads as the same button as "Play from cursor"; it is a distinct
   action (play the whole composed sequence, no render). */
.timeline-preview-btn {
  border-color: rgba(30, 91, 255, 0.75);
  color: #7FA8FF;
}
.timeline-preview-btn:hover { background: rgba(30, 91, 255, 0.18); }

/* P2: a real client-grabbed frame behind a timeline bar. `has-thumb` is added by
   studio.js only when a frame was actually obtained, so the bare coloured bar
   remains the honest fallback rather than a blank tile. */
.lane-span.has-thumb {
  background-size: cover;
  background-position: center;
  opacity: 0.62;
}
.timeline-lane .track-clip.has-thumb {
  background-size: cover;
  background-position: center;
  background-blend-mode: multiply;
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.85);
}

/* P3 (owner 2026-10-11): hover/scrub frame preview floating over the timeline bar
   (the time-rail-thumbnails pattern). Positioned by studio.js at the hover x; an
   <img> only when a frame is available, so it never fakes one. */
.timeline-hover {
  position: absolute;
  bottom: calc(100% + 6px);
  transform: translateX(-50%);
  z-index: 20;
  background: #0F0E17;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 6px;
  padding: 3px;
  box-shadow: 0 8px 22px rgb(0 0 0 / 0.5);
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  max-width: 180px;
}
.timeline-hover[hidden] { display: none; }
.timeline-hover-img {
  display: block;
  width: 160px;
  height: 90px;
  object-fit: cover;
  border-radius: 4px;
  background: #1b1b24;
}
.timeline-hover-time {
  font-size: 0.62rem;
  color: #F5A524;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.timeline-zoom button {
  background: var(--gray-bg);
  border: 1px solid var(--gray-light);
  border-radius: 6px;
  width: 28px;
  height: 28px;
  cursor: pointer;
  color: var(--text);
}

/* Owner 2026-09-23 (03:52): "timeline section should have a blue background... the
   timeline section shall have a blue background."

   Scoped to `.studio-page`: demo-studio.html carries the same `.project-timeline`
   element, and an unscoped repaint would silently restyle the demo (the exact trap
   the 2026-09-23 header work hit). The blue is the page's OWN blue - the same
   gradient the welcome banner uses - so the page does not grow a second, near-miss
   blue beside the first.

   The panel is dark now, so the text that used to sit on the white card has to be
   lifted: the header, the lane names and the two note paragraphs go white-ish. The
   per-lane time readouts CANNOT simply be whitened - their colour is a pinned
   contract (a time must never be read against the wrong bar), so each one keeps its
   lane colour and sits on its own white chip instead: legible, and still mapped to
   its lane. The lane rows keep their light surface, so the coloured clips still read
   against them. */
/* Owner 2026-09-25: "make the background for the timeline section black so the
user can see better." The panel was a blue gradient; the four lane colours and
the white ruler read against black with far more separation than they did against
mid-blue, which is the point of the change. Scoped to `.studio-page` so the demo
keeps whatever the demo sets. */
.studio-page .project-timeline {
  background: #000;
  border-radius: var(--radius);
  padding: 12px;
  /* THE single source of truth for the timeline's label-column width. studio.js
     and studio-demo.js READ this value (they must not carry their own copy):
     the same number feeds the `grid-template-columns` below and the px
     arithmetic that sizes the lanes, so the ruler and the bars cannot slide out
     of alignment if it is ever changed again. */
  --tl-label-col: 106px;
}
.studio-page .project-timeline .timeline-header h4 { color: #fff; }
.studio-page .project-timeline .track-label { color: rgba(255, 255, 255, 0.92); }
.studio-page .project-timeline .timeline-ruler { border-bottom-color: rgba(255, 255, 255, 0.35); }
/* The ruler's own text and marks were styled for the WHITE card this panel used to be.
   Repainting the panel blue (2026-09-23) lifted the header, the lane names and the
   notes but MISSED these, so the time numbers sat at 1.14:1 - present in the DOM,
   invisible on screen. That is exactly what "the 5 second timelines did not show up"
   looked like. Measured before: labels rgb(48,61,89) on the gradient = 1.14:1; after:
   9.5:1 against the gradient's dark end.

   `.is-minor` is the tenth-of-a-second subdivision (owner 2026-09-23: "the timelines
   need 10 hash marks between each second"). It is drawn fainter than a second mark so
   the seconds still read as the landmarks rather than one grey band. */
.studio-page .project-timeline .ruler-label { color: rgba(255, 255, 255, 0.95); }
.studio-page .project-timeline .ruler-tick { background: rgba(255, 255, 255, 0.55); }
.studio-page .project-timeline .ruler-tick.is-minor { background: rgba(255, 255, 255, 0.26); }
.studio-page .project-timeline .timeline-empty,
.studio-page .project-timeline .timeline-meta { color: rgba(255, 255, 255, 0.92); }
/* `width: fit-content` keeps the chip the width of the time itself while leaving the
   readout stacked under its lane name (`display: block` is preserved).

   Owner 2026-09-25: "the flush left time pills are way too small in the timeline
   section, make them wider so they be easier to read, use bold font." The chip was
   `padding: 0 4px` at 0.58rem - a 4px-tall target that was hard to read against the
   navy band. `min-width` (not `width`) is what makes it WIDER: it sets a readable
   floor while a longer value can still grow past it. */
.studio-page .project-timeline .lane-time {
  background: rgba(255, 255, 255, 0.92);
  border-radius: 6px;
  padding: 2px 10px;
  min-width: 52px;
  text-align: center;
  width: fit-content;
}
.studio-page .project-timeline .timeline-zoom button {
  background: rgba(255, 255, 255, 0.92);
  border-color: rgba(255, 255, 255, 0.92);
  color: var(--primary-dark);
}

/* The label column width is OWNED by studio.js (TL_LABEL_COL_PX) and published
as `--tl-label-col`, because the same number is needed by the px arithmetic that
sizes the lanes. It used to be the literal 60px here AND in the two JS files'
`calc(60px + ...)`, so widening the column in one place without the other would
have slid the ruler out of line with the bars below it.

Owner 2026-09-25: "move the 4 timelines over to the right so they dont overlap
with the pills to the left." At 60px the lane-time pill (min-width 52px, 10px
padding each side = 72px) overflowed its column and spilled over the start of the
track area. The column is now wide enough to hold the pill with room to spare, so
the bars begin clear of it at every width. The fallback is the same value the JS
sends, so the column is correct even if the script has not run yet. */
.track {
  display: grid;
  grid-template-columns: var(--tl-label-col, 150px) 1fr;
  gap: 6px;
  align-items: center;
  margin-bottom: 5px;
}
.track-label {
  font-size: 0.68rem;
  color: var(--gray);
  /* Owner 2026-10-10: "move timers (0.00) to the right of the words video, audio,
     images and text. then flush the beginning of the timelines to the right of the
     timers."
     flex-direction:row puts the timer beside the word; justify-content:space-between
     parks the timer at the column's RIGHT edge, so every lane's bar begins flush at
     the same x immediately to the right of its timer. The column width is still
     --tl-label-col, which studio.js reads for its px arithmetic, so clip/cursor
     positions stay aligned. flex-wrap:nowrap keeps the word on ONE line when the
     column is tightened (owner: "keep it tight"). */
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}

/* Per-lane time readout (owner 2026-09-22). Sits under the lane name and is
   painted in the lane's own colour so a time is never attributed to the wrong
   bar. The ruler row also has one, printed in slate because it is a scale, not a
   media lane. Tabular numerals keep the digits from shifting as the value
   changes during a drag. */
.lane-time {
  display: block;
  margin-top: 0;
  /* Owner 2026-09-25: bolder and larger so the time is readable at a glance. */
  font-size: 0.78rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--lane, var(--gray));
  white-space: nowrap;
}
.track[data-track="time"] .lane-time { color: var(--lane, var(--gray)); }
.track-row {
  display: flex;
  align-items: center;
  /* Darkened with the section (owner 2026-09-25: "make the background for the
     timeline section black so the user can see better"). The lane rows were a
     pale grey that matched the OLD light panel; on the black panel a pale row
     would glare and fight the four saturated bars. A near-black row keeps the
     bars as the brightest thing on the row, which is what makes a clip's length
     and colour readable at a glance. */
  background: #15161a;
  border-radius: 8px;
  min-height: 20px;
  height: 20px;
  overflow: hidden;
}

/* Owner 2026-09-20: "make the timelines different colors." .track-clip had NO
   background of its own, so a clip rendered transparent on the pale track row.
   Each track now owns a distinct colour, keyed off the `data-track` attribute
   the markup already carries, so a clip's colour identifies its track at a
   glance. Image clips get amber rather than reusing the video blue.

   Owner 2026-09-20 (later): "use the colors blue orange green purple". The four
   lanes are named exactly those four colours now, and each lane's `|` marks /
   span read the SAME custom property, so a clip and its marks can never
   disagree. `--lane` is declared on the LANE wrapper, not the row, so both the
   row's clips and the sibling mark overlay inherit it from one source.

   Owner 2026-09-22: the four bars must be four DIFFERENT colours (they are), and
   each lane needs its own time readout in the SAME colour, so a creator can tell
   which bar a time belongs to when dialling in an audio/video overlap. Rather
   than copy the palette into the label, `--lane` is set on BOTH the `.track`
   wrapper and the `.timeline-lane`, so the measured label and its bar inherit
   one value and cannot drift. The bar colours themselves are unchanged. */
.timeline-lane[data-track="video"], .track[data-track="video"] { --lane: #1E5BFF; }  /* blue */
.timeline-lane[data-track="audio"], .track[data-track="audio"] { --lane: #22C55E; }  /* green */
.timeline-lane[data-track="image"], .track[data-track="image"] { --lane: #F59E0B; }  /* orange */
.timeline-lane[data-track="text"],  .track[data-track="text"]  { --lane: #8B5CF6; }  /* purple */
.track[data-track="time"] { --lane: #94A3B8; }  /* slate: the ruler is not a media lane */

.track-row {
  position: relative;
}
/* Scoped to `.timeline-lane`, which exists on studio.html only. demo-studio.html
   reuses this stylesheet and renders its own `.track-clip` elements with no inline
   position — absolute positioning scoped here cannot reach them. */
.timeline-lane .track-clip {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--clip, var(--lane, #1E5BFF));
  overflow: hidden;
}

/* ---- Shared-scale timeline (ruler above + the four lanes) ----
   The ruler row and all four lanes sit in ONE `.timeline-inner`, so the label
   column is 60px for every row and the track column starts at the same x on all
   of them. That is what makes the `|` marks below line up with the ruler above
   by construction, rather than by hand-tuned offsets. */
.timeline-scroll { overflow-x: auto; overflow-y: hidden; }
.timeline-inner { min-width: 100%; position: relative; }

/* Owner 2026-10-04: "we need a moveable cursor... the cursor should be distinct
   in color." The playhead is a full-height amber needle that crosses the ruler and
   all four lanes; amber is chosen because no lane is amber, so the cursor never
   reads as a clip. The grab handle at the top is the move affordance and the one
   part that takes pointer events; a wider, invisible hit area makes it easy to grab. */
.timeline-cursor {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: #F5A524;
  box-shadow: 0 0 5px rgba(245, 165, 36, 0.75);
  transform: translateX(-1px);
  z-index: 8;
  pointer-events: none;
}
.timeline-cursor::before {
  content: "";
  position: absolute;
  top: -3px;
  left: 50%;
  transform: translateX(-50%);
  width: 12px;
  height: 12px;
  background: #F5A524;
  border-radius: 3px;
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.4);
}
.timeline-cursor::after {
  content: "";
  position: absolute;
  top: -4px;
  left: 50%;
  transform: translateX(-50%);
  width: 24px;
  height: 20px;
  background: transparent;
  border-radius: 5px;
  pointer-events: auto;
  cursor: ew-resize;
}
.track-ruler { margin-bottom: 3px; }
.timeline-ruler {
  position: relative;
  height: 20px;
  border-bottom: 1px solid var(--gray-light);
}
/* The stand-in ruler shown before any media exists is drawn muted and italic, so a
   default scale can never be read as this project's length (owner 2026-09-22). */
.timeline-ruler.is-default .ruler-label {
  font-style: italic;
  opacity: 0.75;
}
.timeline-ruler.is-default {
  border-bottom-style: dashed;
}
/* Empty lane outline: shows where a bar will appear without claiming a duration. */
.lane-empty {
  position: absolute;
  inset: 0;
  border: 1px dashed var(--lane, #1E5BFF);
  border-radius: 6px;
  opacity: 0.42;
  pointer-events: none;
}
.ruler-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--gray-light);
}
.ruler-label {
  position: absolute;
  top: 3px;
  font-size: 0.6rem;
  color: var(--gray);
  transform: translateX(3px);
  white-space: nowrap;
}

.timeline-lane { position: relative; min-width: 0; }
/* The marks overlay sits above the row, and never eats clicks meant for a clip. */
.lane-ruler {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
}
/* The start/stop `|` pair, drawn in the lane's own colour. */
.lane-mark {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 2px;
  background: var(--lane, #1E5BFF);
}
/* The span between start and stop reads as the "action" window. */
.lane-span {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--lane, #1E5BFF);
  opacity: 0.22;
}

.track-clip {
  height: 18px;
  min-height: 18px;
  border-radius: 6px;
  color: #fff;
  font-size: 0.62rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 6px;
  white-space: nowrap;
}

/* ------------------------------------------------------------------
   Movable timeline bars (owner 2026-09-22: "the timeline bars must be
   moveable so users can edit the video properly... so if audio is
   supposed to overlap the video, the user will be able to dial in the
   time").

   A clip is a real drag handle: grabbing it moves its start time along
   the shared scale, which is what lets audio be positioned against a
   different point in the video. The cursor is the affordance; without it a
   bar looks like static output.

   Deliberately NO edge-resize handles. Resizing a bar would have to change
   the clip's real length, and the only server path for that is
   `trim_video`, which re-encodes the file with ffmpeg `-c copy`. Dragging an
   edge on a paid render would destroy the source on an accidental gesture,
   and a purely visual resize would show a length the file does not have -
   exactly the dishonesty the studio contract forbids. Length stays with the
   existing Split / Trim buttons, which say what they do.
   ------------------------------------------------------------------ */
.timeline-lane .track-clip { cursor: grab; }
.timeline-lane .track-clip:active { cursor: grabbing; }
.timeline-lane .track-clip.selected {
  outline: 2px solid #fff;
  outline-offset: -2px;
  box-shadow: 0 0 0 2px var(--lane, #1E5BFF);
}
.timeline-lane .track-clip.is-dragging {
  opacity: 0.85;
  z-index: 6;
  box-shadow: 0 4px 12px rgb(0 0 0 / 0.35);
}

/* Live readout shown while dragging, so the exact second is dialled in rather
   than eyeballed. Positioned by studio.js inside the lane. */
.clip-drag-badge {
  position: absolute;
  top: -16px;
  transform: translateX(-50%);
  background: #0F0E17;
  color: #fff;
  border: 1px solid var(--lane, #1E5BFF);
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 0.6rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  z-index: 7;
  white-space: nowrap;
}

.project-grid, .package-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

/* ------------------------------------------------------------------
   My Library - the sidebar filing system for a creator's prior work
   (owner 2026-09-22).

   A small-caps heading groups the folder rows, each row carries a live
   count badge, and the panel opens inline underneath the sidebar rather
   than as a modal so it reads as navigation ("a navigational bar on the
   side bar") instead of a dialog.
   ------------------------------------------------------------------ */
.library-heading {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--gray-light);
  font-size: 0.66rem !important;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gray) !important;
  cursor: default !important;
  background: none !important;
}
.library-heading:hover { background: none !important; color: var(--gray) !important; }

.library-nav { cursor: pointer; }
.library-nav.active {
  background: var(--primary);
  color: #fff;
}
.library-nav.active .badge { background: #fff; color: var(--primary); }

.library-panel-item { display: block !important; padding: 0 !important; }
/* Both rules are !important, so specificity decides: class+attribute (0,2,0) beats
   class alone (0,1,0). Without this the author `display:block !important` beat the
   UA [hidden] rule and the folder panel painted empty at first paint. */
.library-panel-item[hidden] { display: none !important; }
.library-panel-item:hover { background: none !important; }
.library-panel {
  margin: 6px 0 10px;
  border: 1px solid var(--gray-light);
  border-radius: 10px;
  background: var(--card);
  overflow: hidden;
}
.library-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--gray-light);
  font-size: 0.72rem;
}
.library-panel-head button {
  background: none;
  border: none;
  color: var(--gray);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.library-list {
  list-style: none;
  margin: 0;
  padding: 6px;
  max-height: 260px;
  overflow-y: auto;
}
.library-list li {
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 0.72rem;
  cursor: pointer;
  min-width: 0;
}
.library-list li:hover { background: var(--primary-bg); }
.library-list .library-empty {
  cursor: default;
  color: var(--gray);
  font-size: 0.7rem;
  line-height: 1.4;
  white-space: normal;
}
.library-list .library-empty:hover { background: none; }
.library-list .library-name {
  display: block;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.library-list .library-meta { display: block; color: var(--gray); font-size: 0.66rem; }

/* Draft row actions (owner 2026-10-04: "figure out a way if user wants to delete a
   draft" and "the user must download to their computer"). Kept small and inside the
   row so Download/Delete read as actions on the draft they sit beside. */
.library-list .library-row-actions { display: flex; gap: 6px; margin-top: 5px; }
.library-list .library-row-btn {
  border: 1px solid var(--gray-light);
  background: var(--card);
  color: var(--text);
  border-radius: 6px;
  font-size: 0.62rem;
  line-height: 1;
  padding: 4px 7px;
  cursor: pointer;
}
.library-list .library-row-btn:hover { border-color: var(--primary); color: var(--primary); }
.library-list .library-row-btn--danger:hover { border-color: #dc2626; color: #dc2626; }

/* The draft-limit notice (owner 2026-10-04). A warning note, not a silent block:
   it says WHY the user cannot keep adding drafts and WHAT frees space. */
.library-list .library-limit-note {
  cursor: default;
  background: #fef3c7;
  border: 1px solid #f59e0b;
  color: #92400e;
  font-size: 0.68rem;
  line-height: 1.45;
  white-space: normal;
  margin-bottom: 6px;
}
.library-list .library-limit-note:hover { background: #fef3c7; }

/* Owner: "recent projects shall be half the size on one row or 6 on one row."
   Six compact cards on one desktop row; the card itself is roughly half the
   old height (thumbnail 110px -> 54px, tighter type and padding). */
.project-grid { gap: 10px; }

.project-card {
  border: 1px solid var(--gray-light);
  border-radius: 12px;
  overflow: hidden;
  background: var(--card);
  min-width: 0;
  cursor: pointer;
}

.project-thumbnail {
  height: 54px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 1rem;
}
.project-info { padding: 8px; min-width: 0; }
.project-info h4 {
  font-size: 0.74rem;
  margin-bottom: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.project-info p {
  font-size: 0.64rem;
  color: var(--gray);
  margin-bottom: 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.project-status, .status {
  font-size: 0.6rem;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 999px;
  display: inline-block;
  white-space: nowrap;
}
.project-status.completed { background: var(--nv-success-soft); color: var(--nv-success-dark); }
.project-status.draft { background: var(--primary-bg); color: var(--primary); }
.project-status.pending { background: var(--nv-warning-soft); color: var(--nv-warning-dark); }
.dark .project-status.completed { background: var(--nv-success-soft-dark); color: var(--nv-success); }
.dark .project-status.pending { background: var(--nv-warning-soft-dark); color: var(--nv-warning); }

.package-card {
  border: 1px solid var(--gray-light);
  border-radius: var(--radius);   /* owner 2026-09-19: soft, not square */
  padding: 24px 18px;
  text-align: center;
  position: relative;
}
.package-card.popular { border-color: var(--primary); }
.package-card .badge {
  position: absolute;
  top: -10px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--primary);
  color: #fff;
  font-family: var(--nv-font-mono);
  font-size: 0.68rem;
  padding: 3px 10px;
  border-radius: 999px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.package-card .tokens { font-family: var(--nv-font-display); font-size: 1.6rem; font-weight: 800; }
.package-card .tokens span { font-size: 0.8rem; color: var(--success); }
.package-card .price { font-family: var(--nv-font-display); color: var(--primary); font-weight: 800; margin: 6px 0; font-size: 1.5rem; }
.package-card .per-token { font-family: var(--nv-font-mono); font-size: 0.75rem; letter-spacing: 0.03em; text-transform: uppercase; color: var(--gray); margin-bottom: 12px; }

.btn-package {
  width: 100%;
  justify-content: center;
  background: var(--gray-bg);
  color: var(--text);
  border: 1px solid var(--gray-light);
  border-radius: var(--radius-sm);   /* owner 2026-09-19: soft, not square */
  font-family: var(--nv-font-mono);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  padding: 14px 18px;
}
.btn-package.primary { background: var(--primary); color: var(--nv-on-dark); border: none; }
.btn-package.outline {
  background: transparent;
  color: var(--primary);
  border: 1px solid var(--primary);
}

.activity-list { display: flex; flex-direction: column; gap: 10px; }
.activity-item {
  display: grid;
  grid-template-columns: 36px 1fr auto;
  gap: 10px;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid var(--gray-light);
}
.activity-item:last-child { border-bottom: none; }
.activity-icon {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: var(--primary-bg);
  color: var(--primary);
  display: flex;
  align-items: center;
  justify-content: center;
}
.activity-item p { font-size: 0.85rem; }
.activity-item span { font-size: 0.72rem; color: var(--gray); }
.activity-tokens { font-size: 0.78rem !important; font-weight: 700; color: var(--secondary); white-space: nowrap; }
.activity-tokens.positive { color: var(--success); }

.toast {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--text);
  color: var(--gray-bg);
  padding: 10px 16px;
  border-radius: 8px;
  font-size: 0.82rem;
  font-weight: 500;
  z-index: 80;
  max-width: calc(100% - 24px);
}

@media (min-width: 640px) {
  .stats-grid { grid-template-columns: repeat(2, 1fr); }
  .package-grid { grid-template-columns: repeat(2, 1fr); }
  .project-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .welcome-banner { flex-direction: row; align-items: center; justify-content: space-between; padding: 24px 28px; }
  .welcome-banner h1 { font-size: 1.7rem; }
  .editing-tools { grid-template-columns: 1fr 1fr; }
}

@media (min-width: 768px) {
  .main-content { padding: 0 24px 48px; }
  .stats-grid { grid-template-columns: repeat(4, 1fr); }
  .package-grid { grid-template-columns: repeat(4, 1fr); }
  .project-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .menu-toggle { display: none; }
}

/* Six on one row, the size the owner asked for. */
@media (min-width: 1200px) {
  .project-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 8px; }
}

@media (min-width: 1024px) {
  /* Owner 2026-09-20: "these boxes should be to the right of the video box, no
     need to be underneath. the timelines shall be underneath the video screen."

     Owner 2026-09-22: "reduce the size of the video screen by 50% and increase
     the size of the boxes to the right of the video screen by 50%."

     MEASURED before this change at a 1500px viewport: video 887px, tools 280px
     (ratio 3.17:1) - the video dominated and the rail was a narrow grey strip.
     The targets are therefore 444px and 420px, i.e. roughly 50/50.

     The video column is CAPPED at half its old width; the tool rail has a floor
     of 1.5x its old width and takes the freed space. A fixed 444px + 420px pair
     would leave ~300px of dead space on the right, because the two targets do not
     add up to the container. Capping the video and letting the rail grow keeps
     both directions of the request (video strictly halved, rail strictly larger)
     with no gap. The timeline still spans the full width UNDER both, unchanged. */
  .studio-workspace {
    display: grid;
    /* Owner 2026-09-22 (later): "increase the video screen by 30%". The measured
       baseline here was 444px, so 444 * 1.30 = 577px. Expressed with `minmax(0,
       ...)` rather than a fixed width so the screen can still shrink on a narrow
       window instead of overflowing the grid. The rail keeps `1fr`, so it absorbs
       whatever is left and the two columns still fill the container - no dead
       space on the right, which is what the original 444/420 pair was tuned for.

       This is the UNSHARED-DEFAULT layout, used by demo-studio.html, whose rail
       is a single `.editing-tools`. studio.html overrides it with
       `.studio-workspace--split` below. */
    grid-template-columns: minmax(0, 577px) minmax(320px, 1fr);
    grid-template-areas:
      "preview tools"
      "timeline timeline";
    gap: 8px;
    align-items: start;
  }
  .video-preview { grid-area: preview; width: 100%; max-width: none; margin: 0; }
  .editing-tools { grid-area: tools; grid-template-columns: 1fr; }
  .project-timeline { grid-area: timeline; }

  /* The rail is wide enough for two columns, so its groups are laid out 2-up
     instead of one tall stack. Measured: the rail is 723px at a 1500px viewport
     (it takes the space the video gave up), and a single column there left the
     boxes stretched thin with dead space beside them. Two columns turn the freed
     width into more visible controls - the "bring life to the page" half of the
     request. The 640px breakpoint's 2-column rule applies below this size. */
  .editing-tools { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* ------------------------------------------------------------------
     SPLIT-RAIL variant - studio.html ONLY.

     Owner 2026-09-22: "there are 5 tools to the right of the video, move any 3 of
     the tools to the left of the video screen."

     Scoped to a `--split` modifier rather than applied to `.studio-workspace`
     directly, because demo-studio.html shares every one of these classes and has a
     SINGLE rail with no `--left`/`--right` children. Measured when the split was
     unscoped: the demo's lone rail took the third column at 291px while still
     laying its groups out 2-up (219px + 107px), i.e. squeezed and overflowing. The
     modifier keeps the demo on the default two-column layout above.

     The video keeps its 577px cap (that earlier request still stands) and the two
     rails split the remainder equally via 1fr, so no dead space opens up. The
     timeline still spans the full width UNDER all three, unchanged. */
  .studio-workspace--split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 577px) minmax(0, 1fr);
    grid-template-areas:
      "toolsleft preview toolsright"
      "timeline timeline timeline";
  }
  .studio-workspace--split .editing-tools--left { grid-area: toolsleft; grid-template-columns: 1fr; }
  .studio-workspace--split .editing-tools--right { grid-area: toolsright; grid-template-columns: 1fr; }
  /* Each rail is now roughly half the old rail width, so the 2-up layout does not
     fit: two tool buttons in a ~290px box wrap into a cramped column. Each rail
     stacks single-column. */
  .studio-workspace--split .editing-tools--left,
  .studio-workspace--split .editing-tools--right { grid-template-columns: 1fr; }
}

.industry-pill {
  display: inline-block;
  margin-top: 10px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--primary-bg);
  color: var(--primary);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.industry-features { width: 100%; margin: 0 0 24px; }
.industry-features .section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.industry-features h2 {
  font-size: 1.05rem;
  display: flex;
  align-items: center;
  gap: 8px;
}
#industryLabel {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--gray);
  font-weight: 700;
}
.industry-feature-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
/* Owner: "the 7 boxes shall be all on one row shrunk to fit on the one row
   with blue borders." min-width:0 + overflow-wrap let a card shrink below its
   text's intrinsic width instead of forcing the row wider than the container. */
.industry-feature-card {
  background: var(--card);
  border: 2px solid var(--studio-blue-border);
  border-radius: var(--radius-sm);
  padding: 14px;
  min-width: 0;
  overflow-wrap: anywhere;
}
.industry-feature-card i { color: var(--primary); margin-bottom: 8px; display: block; }
.industry-feature-card strong { display: block; margin-bottom: 4px; min-width: 0; overflow-wrap: anywhere; }
.industry-feature-card span { color: var(--gray); font-size: 0.85rem; display: block; min-width: 0; overflow-wrap: anywhere; }
@media (min-width: 768px) {
  .industry-feature-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* Owner: the 7 toolkit boxes are one row, shrunk to fit. Flex (not a fixed
   `repeat(7, ...)`) is deliberate: it puts *every* rendered card on one row and
   shrinks them equally, so the row is correct whether the industry route yields
   3 cards or 7 — a fixed 7-track grid would leave the other 4 tracks empty. */
@media (min-width: 1200px) {
  /* Owner 2026-09-20 (later): "the boxes below the your studio headline, move those
     3 boxes to the same line of the headline your studio and reduce the size of the
     boxes by 50%". This SUPERSEDES the earlier same-height-as-the-row-below fix: the
     boxes now sit ON the headline line, so they are no longer a band with the stats
     grid. `.industry-features` is the flex container that puts the header and the
     grid side by side on one line; the grid then takes the remaining width. */
  .industry-features {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 16px;
  }
  .industry-features .section-header {
    flex: 0 0 auto;
    margin-bottom: 0;
    gap: 8px;
  }
  .industry-feature-grid {
    flex: 1 1 auto;
    display: flex;
    flex-wrap: nowrap;
    gap: 8px;
    min-width: 0;
  }
  /* 50% smaller box: the footprint halves (padding, min-height, icon and type
     scale). The icon/title/copy stay legible — halving the font SIZES would make
     the row unreadable, so the box shrinks around them. */
  .industry-feature-grid .industry-feature-card {
    flex: 1 1 0;
    min-width: 0;
    padding: 3px 6px;
    min-height: 34px;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  .industry-feature-grid .industry-feature-card i { font-size: 0.6rem; margin-bottom: 1px; }
  .industry-feature-grid .industry-feature-card strong { font-size: 0.72rem; margin-bottom: 1px; }
  .industry-feature-grid .industry-feature-card span { font-size: 0.62rem; line-height: 1.15; }
}

.gallery-strip {
  width: 100%;
  margin: 0 0 24px;
}
.gallery-strip-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.gallery-strip-header h2 {
  font-size: 1.05rem;
  display: flex;
  align-items: center;
  gap: 8px;
}
.gallery-link {
  color: var(--primary);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
}
/* One compact row that scrolls sideways instead of a tall 4x2 grid.
   Cards are fixed-width so the row height stays small. */
.gallery-strip-row {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 6px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.gallery-strip-row::-webkit-scrollbar { height: 6px; }
.gallery-strip-row::-webkit-scrollbar-thumb {
  background: var(--nv-line);
  border-radius: 3px;
}
.gallery-strip-card {
  position: relative;
  flex: 0 0 auto;
  width: 96px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  aspect-ratio: 1 / 1;
  background: var(--gray-light);
  scroll-snap-align: start;
}
.gallery-strip-card img {
  width: 100%;
  max-width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.gallery-strip-card span {
  position: absolute;
  left: 5px;
  right: 5px;
  bottom: 5px;
  font-size: 0.6rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0,0,0,0.6);
}
.gallery-strip-empty { color: var(--gray); font-size: 0.9rem; }

/* ------------------------------------------------------------------
   Owner 2026-09-23 (06:17): "when you click video to add the video viewer a message
   pops up 'add to project to keep it' but i dont see an add to project button."

   The toast is real and it named a real control - but only the Stock Gallery tab
   rendered that control. The stock strip and both libraries now carry their own
   Add-to-project button, so the toast can never point at something absent again.

   Mechanics: the strip card was a 96px SQUARE with `overflow: hidden`, so a button
   placed inside it would be clipped away. The card becomes a column - the tile keeps
   the 1:1 shape and the button sits under it - and the tile wrapper takes over the
   clipping so the caption still overlays the image. Widened to 132px to match the
   library cards, which leaves the label room to read on one line.

   Every rule is scoped to `.studio-page`: demo-studio.html builds its own
   `.gallery-strip-card`s from the same class (studio-demo.js) with no button, and an
   unscoped rule would stretch the demo's square tiles and clip its new button-less
   layout. The demo also has no `.gallery-strip-tile`, so it cannot pick up the
   wrapper styling below.
   ------------------------------------------------------------------ */
.studio-page .gallery-strip-card {
  display: flex;
  flex-direction: column;
  width: 132px;
  aspect-ratio: auto;
  overflow: visible;
  background: transparent;
}
.studio-page .gallery-strip-tile {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--gray-light);
}
.studio-page .gallery-strip-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The one button look, shared by the strip and the two libraries, so the three
   surfaces cannot drift into three different treatments of the same action. */
.studio-page .gallery-strip-add,
.studio-page .media-card-add {
  margin-top: 6px;
  width: 100%;
  border: none;
  background: var(--primary);
  color: #fff;
  border-radius: 6px;
  padding: 6px 8px;
  cursor: pointer;
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1.2;
}
.studio-page .gallery-strip-add:hover,
.studio-page .media-card-add:hover { background: var(--primary-dark); }
/* The library card is already a column with padding-free edges, so the button only
   needs breathing room from the caption above it. */
.studio-page .media-library-card .media-card-add { margin: 0 8px 8px; width: auto; }

.track-clip.image-clip {
  background: var(--nv-accent);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 8px;
  min-width: 72px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.track-clip.image-clip img {
  width: 22px;
  height: 22px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}

.studio-tab {
  width: 100%;
  padding: 8px 0 40px;
}
.studio-tab h2 {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.studio-tab > p { color: var(--gray); margin-bottom: 16px; }
.studio-tab .gallery-toolbar {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 16px;
}
.studio-tab .gallery-toolbar input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--gray-light);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--text);
}
.studio-tab .category-filters { display: flex; flex-wrap: wrap; gap: 8px; }
.studio-tab .filter-btn {
  border: 1px solid var(--gray-light);
  background: var(--card);
  color: var(--text);
  border-radius: 999px;
  padding: 6px 12px;
  cursor: pointer;
  text-transform: capitalize;
}
.studio-tab .filter-btn.active {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.studio-tab .gallery-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}
.studio-gallery-card {
  background: var(--card);
  border: 1px solid var(--gray-light);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.studio-gallery-card img {
  width: 100%;
  max-width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
}
.studio-gallery-card .meta { padding: 10px 12px 12px; }
.studio-gallery-card .cat {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--primary);
  font-weight: 700;
}
.studio-gallery-card .who { font-size: 0.88rem; margin: 4px 0 8px; }
.studio-gallery-card button {
  border: none;
  background: var(--primary);
  color: #fff;
  border-radius: 8px;
  padding: 8px 10px;
  cursor: pointer;
  font-weight: 600;
}
@media (min-width: 640px) {
  .studio-tab .gallery-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 900px) {
  .studio-tab .gallery-grid { grid-template-columns: repeat(3, 1fr); }
}

@media (min-width: 768px) {
  .gallery-strip-card { width: 108px; }
}

.mobile-studio-nav { display: none; }
.studio-tab .load-more-container {
  display: flex;
  justify-content: center;
  padding: 16px 0 8px;
}
.studio-tab .btn-load-more {
  border: 1px solid var(--gray-light);
  background: var(--card);
  color: var(--text);
  border-radius: 10px;
  padding: 10px 28px;
  font-weight: 600;
  cursor: pointer;
}
.studio-tab .btn-load-more[hidden] { display: none; }

@media (max-width: 767px) {
  body { flex-direction: column; }
  .sidebar {
    position: fixed;
    left: 0;
    top: 0;
    transform: translateX(-100%);
    box-shadow: none;
    height: 100dvh;
  }
  .sidebar.open { transform: translateX(0); box-shadow: 8px 0 24px rgba(0,0,0,0.12); }
  .mobile-studio-nav {
    display: flex;
    width: 100%;
    position: sticky;
    top: 0;
    z-index: 30;
    background: var(--white);
    border-bottom: 1px solid var(--gray-light);
  }
  .mobile-tab {
    flex: 1;
    border: none;
    background: transparent;
    color: var(--gray);
    padding: 12px 8px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    cursor: pointer;
  }
  .mobile-tab.active {
    color: var(--primary);
    box-shadow: inset 0 -2px 0 var(--primary);
  }
  .main-content { width: 100%; padding: 12px 12px 32px; }
  .studio-tab .gallery-toolbar input { width: 100%; }
  .studio-tab .category-filters {
    overflow-x: auto;
    flex-wrap: nowrap;
    padding-bottom: 4px;
  }
  .studio-tab .category-filters .filter-btn { white-space: nowrap; }
  .studio-tab .gallery-grid {
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
  }
  .studio-gallery-card .meta { display: none; }
  .studio-gallery-card { cursor: pointer; }
}

@media (max-width: 767px) {
  .ai-gen-pill { font-size: 0.72rem; padding: 4px 10px; }
  .ai-gen-pill-title { font-size: 0.78rem; padding: 6px 12px; }
  .ai-gen-select-box {
    flex-direction: column;
    align-items: stretch;
    width: 100%;
  }
  .ai-gen-select-box .ai-gen-pill { align-self: stretch; justify-content: center; }
  .ai-gen-select-box .ai-gen-select { width: 100%; }
  .ai-gen-row-2 #aiGenerateBtn { margin-left: 0; width: 100%; justify-content: center; }
  .ai-gen-prompt-row { flex-direction: column; align-items: stretch; }
  .ai-gen-prompt-row .ai-gen-pill { align-self: flex-start; }
  .ai-gen-prompt { flex: 1 1 auto; width: 100%; }
}

@media (max-width: 1199px) {
  /* Below the 7-across breakpoint the toolkit wraps rather than squashing
     the cards into unreadable slivers. */
  .industry-feature-grid { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }
}

.dark .ai-gen-balance-box,
.dark .ai-gen-select-box,
.dark .ai-gen-pill-model-cost { background: var(--nv-accent-soft-dark); }
.dark .ai-gen-balance-box,
.dark .ai-gen-select-box { border-color: var(--primary-light); }

/* Text-on-video controls */
/* ------------------------------------------------------------------
   AI generator — two rows, every word on a coloured pill.
   Row 1: Generate a video | Balance box | Save | Publish
   Row 2: Type of video | [selections box] | Generate video (flush right)
   ------------------------------------------------------------------ */
.ai-generator {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 14px;
}
.ai-gen-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
}
/* Owner 2026-09-23: "all the boxes on that row should be the same height and
   widthe with borders." Stretching row 1 makes the description box, the balance
   box, the four chips and the two buttons all take the row's height, instead of
   each sitting at its own content height. The pill labels stay at their own size
   and are centred against it, so the row does not grow a tall pill. */
.studio-page .ai-gen-row-1 { align-items: stretch; }
.studio-page .ai-gen-row-1 .ai-gen-pill-field { align-self: center; }
.studio-page .ai-gen-row-1 .ai-gen-balance-box { align-self: stretch; }
.ai-gen-row-1 .ai-gen-row-gap { flex: 1 1 12px; }
/* Publish used to be coloured by `.studio-controls .btn-primary`, a wrapper it
   no longer lives in. Without this it would be white text-bg on a white card. */
#aiGenRow1 #publishBtn { background: var(--primary); color: #fff; }
#aiGenRow1 #publishBtn:hover { background: var(--primary-dark); }
/* Generate moved into row 1 on 2026-09-23 (04:46) and brings its `.btn-primary` with
   it. Same reason as Publish above: the wrapper that used to colour it is gone, so the
   blue must be re-declared here or the button renders white-on-white. */
#aiGenRow1 #aiGenerateBtn { background: var(--primary); color: #fff; }
#aiGenRow1 #aiGenerateBtn:hover { background: var(--primary-dark); }

/* Owner 2026-10-05: "move Publish button to the right of the Buy more tokens now
   button. make other buttons smaller so Publish button will fit." Publish now sits
   immediately right of Buy-more-tokens, so every control sharing row 1 is slimmed
   (tighter horizontal padding + slightly smaller type) to keep the whole row on ONE
   line instead of wrapping. Vertical padding is left to the row-height rule above. */
.studio-page #aiGenRow1 { gap: 6px; }
/* Owner 2026-10-10: "all buttons above the video viewer need to be uniformed like
   the new project button. keep the description box the same."

   Every BUTTON in row 1 (New Project, My Library, Generate, Publish, Import, Save and
   the Buy-more box) now wears the New Project look - solid accent blue, white label,
   uppercase monospace, the theme's button geometry - so the row reads as one set. The
   description box (`#aiPrompt`) and its "Describe the video you want" label (the only
   `.ai-gen-pill-field` in this row) are deliberately NOT touched. Supersedes the
   per-button slim/font passes of 2026-10-05 and 2026-10-10. */
.studio-page #aiGenRow1 #newProjectBtn,
.studio-page #aiGenRow1 #aiMyLibraryBtn,
.studio-page #aiGenRow1 #aiGenerateBtn,
.studio-page #aiGenRow1 #publishBtn,
.studio-page #aiGenRow1 #importBtn,
.studio-page #aiGenRow1 #saveBtn,
.studio-page #aiGenRow1 #aiGenBuyMore {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 38px;
  padding: 0 14px;
  border-radius: var(--nv-radius-btn);
  background: var(--nv-accent);
  color: #fff;
  border: none;
  font-family: var(--nv-font-mono);
  font-weight: 700;
  font-size: var(--nv-btn);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
}
.studio-page #aiGenRow1 #newProjectBtn:hover,
.studio-page #aiGenRow1 #aiMyLibraryBtn:hover,
.studio-page #aiGenRow1 #aiGenerateBtn:hover,
.studio-page #aiGenRow1 #publishBtn:hover,
.studio-page #aiGenRow1 #importBtn:hover,
.studio-page #aiGenRow1 #saveBtn:hover,
.studio-page #aiGenRow1 #aiGenBuyMore:hover {
  background: var(--nv-accent-dark);
  color: #fff;
}
/* Icons ride the button colour and size (colour: currentColor, size: inherit). */
.studio-page #aiGenRow1 #newProjectBtn i,
.studio-page #aiGenRow1 #aiMyLibraryBtn i,
.studio-page #aiGenRow1 #aiGenerateBtn i,
.studio-page #aiGenRow1 #publishBtn i,
.studio-page #aiGenRow1 #importBtn i,
.studio-page #aiGenRow1 #saveBtn i,
.studio-page #aiGenRow1 #aiGenBuyMore i { font-size: 1em; }
/* The describe box is the deliberate exception: it is the only FIELD (navy border +
   pointer cursor) and the owner wants it stretched 30% wider - its row-1 min-width
   goes 260px -> 338px. Height is matched to the other boxes so the row stays level. */
.studio-page #aiGenRow1 .ai-gen-prompt {
  min-width: 338px;
  height: 38px;
}
.studio-page #aiGenRow1 .ai-gen-balance-box {
  padding: 6px 10px;
  font-size: 0.78rem;
}
.studio-page #aiGenRow1 .ai-gen-balance-box strong { font-size: 0.92rem; }

/* Every label/word reads as a coloured pill. */
.ai-gen-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--primary-bg);
  color: var(--primary);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.ai-gen-pill i { font-size: 0.72rem; }
.ai-gen-pill-title {
  background: var(--primary);
  color: #fff;
  font-size: 0.85rem;
  font-weight: 800;
  padding: 7px 14px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.ai-gen-pill-model-cost { background: var(--nv-accent-soft); color: var(--nv-accent-dark); }

/* The balance box: a literal boxed figure, not just text. */
.ai-gen-balance-box {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  border: 2px solid var(--primary);
  border-radius: var(--radius-sm);
  background: var(--primary-bg);
  color: var(--primary);
  font-size: 0.82rem;
  font-weight: 700;
  white-space: nowrap;
}
.ai-gen-balance-box strong { font-size: 1rem; font-weight: 800; }
.ai-gen-balance-label { color: var(--primary); }
.ai-gen-balance-unit { font-weight: 600; opacity: 0.85; }

/* The coloured box holding the row-2 selections (model + cost + length). */
.ai-gen-select-box {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
  padding: 6px 10px;
  border: 2px solid var(--primary);
  border-radius: var(--radius-sm);
  background: var(--primary-bg);
}
.ai-gen-select-box .ai-gen-select { background: var(--card); }

/* Generate sits flush right on row 2. */
.ai-gen-row-2 .ai-gen-select-box { flex: 1 1 auto; }
.ai-gen-row-2 #aiGenerateBtn { margin-left: auto; flex: 0 0 auto; }

/* Owner 2026-10-10: the Model / Media / Type-of-video / Length controls moved OUT of
   the generator into the rail right of the video viewer. They stack as label+select
   rows (the rail is narrower than the generator row), and Media + Type of video share
   one row ("type of video next to the media section"). */
.ai-gen-select-box.ai-gen-controls { flex-direction: column; align-items: stretch; gap: 8px; }
.ai-gen-controls .ai-gen-control { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ai-gen-controls .ai-gen-control .ai-gen-pill { align-self: flex-start; }
.ai-gen-controls .ai-gen-control .ai-gen-select { width: 100%; }
.ai-gen-controls .ai-gen-control-row { display: flex; gap: 8px; }
.ai-gen-controls .ai-gen-control-row .ai-gen-control { flex: 1 1 0; min-width: 0; }

/* Owner 2026-10-10: Balance box + "Buy more tokens now" moved OUTSIDE the Model
   control box, directly underneath it in the rail. Stack them; the box (inline-flex)
   and the button both stretch to the rail's full width and centre their labels. */
.ai-gen-balance-row { display: flex; flex-direction: column; gap: 8px; }
.ai-gen-balance-row .ai-gen-balance-box { justify-content: center; }
.ai-gen-balance-row #aiGenBuyMore { width: 100%; justify-content: center; }

/* ---- Cartoon / image controls (2026-09-29) --------------------------
   Row 3 holds the image controls and is hidden until #aiMediaMode is
   Image (studio.js toggles the `hidden` attribute - no default styling
   here may fight that). Reuses the existing .ai-gen-select / .ai-gen-pill
   look so the image row reads as part of the same generator. */
.ai-gen-row-3 {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.ai-gen-row-3 .ai-gen-select { flex: 0 1 auto; }
.ai-gen-row-3 #aiImageBtn { margin-left: auto; flex: 0 0 auto; }
.ai-gen-row-3[hidden] { display: none; }

/* The locked character-reference chip row (the series anchor). */
.ai-image-refs {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  width: 100%;
}
.ai-image-refs[hidden] { display: none; }
.ai-image-ref-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--primary-bg);
  font-size: 0.72rem;
}

/* A generated still in the result box. */
.ai-gen-result .ai-image-preview {
  display: block;
  max-width: 100%;
  max-height: 320px;
  border-radius: var(--radius-sm);
  margin: 8px 0;
}

/* YouTube brand-channel connect row (2026-09-29). */
.youtube-connect {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 10px;
}
.youtube-connect strong { font-size: 0.8rem; }
.youtube-connect .tool-group-note { flex: 1 1 100%; margin: 0; }

/* ---- Cartoon / image controls (2026-09-29) -------------------------- */

.ai-gen-select {
  padding: 7px 10px;
  border: 1px solid var(--gray-light);
  border-radius: 8px;
  font-family: inherit;
  font-size: 0.8rem;
  color: inherit;
  background: var(--card);
  min-width: 0;
  max-width: 100%;
}
.ai-gen-prompt-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  flex-wrap: wrap;
}
.ai-gen-prompt {
  flex: 1 1 240px;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--gray-light);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 0.85rem;
  color: inherit;
  background: var(--card);
}

/* Owner 2026-09-23: the Studio-only restyling of the prompt row, layered ON TOP of
   the shared base rules above rather than replacing them.

   demo-studio.html shares `.ai-gen-prompt-row` / `.ai-gen-prompt` and does NOT
   carry the `.studio-page` body class, so it keeps the base `flex-wrap: wrap`
   (its pill sits on its own line) and its plain 1px grey textarea. Replacing the
   base rules outright was the first cut of this change and it silently stripped
   the demo's row of `display: flex` - measured `flex-wrap: nowrap` on the demo,
   which is just the CSS default once the rule is gone. */
.studio-page .ai-gen-prompt-row {
  align-items: center;                 /* pill + box on one line */
  flex-wrap: nowrap;                   /* "pill to the front of the box" */
  /* Owner 2026-09-23: "increase width of description box." The row is the box's
     share of line 1, so widening the ROW widens the box. 420px -> 620px. */
  flex: 1 1 620px;
}
.studio-page .ai-gen-prompt {
  flex: 1 1 auto;
  /* Owner 2026-09-23: "increase width of description box." A min-width is what makes
     the increase hold: with `flex-basis: auto` and `min-width: 0` the box shrinks to
     fit whatever row 1 has left, which is why it measured only 263px before. */
  min-width: 420px;
  /* "place borders on the description box" */
  border: 2px solid var(--studio-navy-border);
  cursor: pointer;                     /* it opens the typing overlay */
}
/* A readonly textarea still shows a text caret on click; the overlay is the real
   editor, so suppress the caret here and keep the placeholder legible. */
.studio-page .ai-gen-prompt[readonly] { caret-color: transparent; }
.studio-page .ai-gen-prompt[readonly]::placeholder { color: var(--gray); }

/* Owner 2026-09-23 (03:52): "the 4 token boxes at top shuld go away and there
   should be one box that says buy more tokens now. and the link will scroll to the
   section token below."

   The four `.ai-gen-pack` chips are gone, so their rules go too rather than linger as
   dead selectors. The replacement is a single box that reads as a button - it carries
   `.btn-outline` for the shared button geometry and `.ai-gen-buy-more` only for its
   own sizing. It is deliberately NOT a `.btn-package`: studio.js binds every
   `.btn-package` to the storefront redirect, which would hijack this box.
   Height comes from the row's `align-items: stretch`, so it matches Save/Publish. */
.ai-gen-buy-more {
  white-space: nowrap;
  align-self: stretch;
}

/* Owner 2026-09-23 (04:46): "the buy more tokens now box should be orange. the
   border should be blue."

   `#aiGenBuyMore` is Studio-only (the demo never renders it), but the rule is scoped
   anyway so it can never reach a future element sharing the class.

   The fill is the warning token because that IS this palette's orange (`#E8940E`).
   The TEXT goes deep navy (--nv-ink, 7.68:1): `.btn-outline`'s own colour is the
   dark body navy (6.74:1) and WHITE would be 2.42:1 - unreadable on orange, and it is
   what the shared `.btn-outline` background used to be. For the same reason the hover
   below shades to `#C87F0C` (5.75:1) rather than the obvious `--nv-warning-dark`,
   which would drop the navy text to 3.95:1 - under the 4.5 threshold.

   Owner 2026-10-10: "buy more tokens now box still blue needs to be black" - the
   border was the house blue (--studio-blue-border, which lifted to #4C7DFF in dark
   mode); it is now BLACK to match the black border every other box in the editor
   rails now wears. The orange fill and navy text are unchanged. */
.studio-page .ai-gen-buy-more {
  background: var(--warning);
  color: var(--nv-ink);
  border: 2px solid #000;
}
/* Owner 2026-10-10: "add the balance 456 tokens to the buy more tokens now." The
   live balance rides inside the box; it is set BOLD and a touch larger than the label
   so the number - not the words - is what the eye lands on. Colour is inherited from
   `.studio-page .ai-gen-buy-more` (--nv-ink) so contrast against the orange is
   unchanged. */
.ai-gen-buy-more .ai-gen-buy-more-balance {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-weight: 600;
  white-space: nowrap;
}
.ai-gen-buy-more .ai-gen-buy-more-balance strong {
  font-weight: 800;
  font-size: 1.02em;
}
.studio-page .ai-gen-buy-more:hover {
  background: #C87F0C;
  border-color: #000;
}

/* Owner 2026-09-23 (04:46): moving Generate beside the describe box makes row 1 too
   wide to fit on ONE line (measured 1176px of 1175 at a 1500px viewport). Save and
   Publish are therefore grouped: ungrouped, the wrap orphans Publish alone at the left
   margin, which reads as a broken layout. Grouped, they take the second line together
   and `margin-left: auto` keeps them flush right, under the other controls. */
.ai-gen-row-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  flex: 0 0 auto;
}

/* ------------------------------------------------------------------
   Media libraries (video gallery / photo library) — containers only;
   the behaviour agent renders the cards into them.
   ------------------------------------------------------------------ */
/* Owner 2026-09-20: "video gallery shall be one row... do the same for the
   photo gallery." Both libraries are now a SINGLE horizontally scrollable row
   instead of a wrapping grid, so neither one grows the page vertically. A
   search box per library sits in its section header (see .library-search), so a
   long strip can be narrowed without scrolling it. */
.media-library-grid {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 6px;
  scroll-snap-type: x proximity;
}
.media-library-grid .media-library-card {
  flex: 0 0 132px;
  scroll-snap-align: start;
}
.media-library-grid:empty::before {
  content: "Nothing here yet.";
  color: var(--gray);
  font-size: 0.88rem;
}
@media (min-width: 640px) {
  .media-library-grid .media-library-card { flex-basis: 144px; }
}
@media (min-width: 1024px) {
  .media-library-grid .media-library-card { flex-basis: 156px; }
}
/* Cards rendered into #videoLibrary / #photoLibrary by studio.js. Self-contained
   (no new dependencies on shared vars beyond the ones already used above) so the
   libraries look like tiles, not unstyled text, if the theme vars change. */
.media-library-card {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: var(--radius-sm, 10px);
  overflow: hidden;
  background: var(--card, #fff);
  border: 1px solid var(--gray-light, #e5e7eb);
  cursor: pointer;
  text-align: left;
}
/* The per-library search input, placed on top of the gallery row. */
.library-search {
  flex: 0 1 220px;
  min-width: 140px;
  padding: 6px 10px;
  border: 1px solid var(--gray-light);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--text);
  font-size: 0.78rem;
}
.media-library-card:hover { border-color: var(--primary, #6b7280); }
.media-library-card:focus-visible { outline: 2px solid var(--primary, #6b7280); outline-offset: 2px; }
.media-card-tile {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  background: var(--gray-light, #eef0f4);
}
.media-card-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.media-card-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-size: 0.72rem;
  color: var(--gray, #6b7280);
}
.media-card-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  pointer-events: none;
}
.media-card-duration {
  position: absolute;
  right: 6px;
  bottom: 6px;
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
  border-radius: 6px;
  padding: 2px 6px;
  font-size: 0.68rem;
  font-weight: 600;
  pointer-events: none;
}
.media-card-caption {
  display: block;
  padding: 6px 8px 8px;
  font-size: 0.7rem;
  color: var(--gray, #6b7280);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.text-overlay-controls {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}
.text-overlay-controls input[type="text"] {
  flex: 1 1 160px;
  min-width: 0;
  padding: 9px 12px;
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 10px;
  font-family: inherit;
  font-size: 0.85rem;
  background: var(--bg, #F7F7FB);
  color: inherit;
}
/* Font colour picker (owner 2026-09-25: "add font sizes and color selection in the
   text on video box"). Styled to the same height and radius as the sibling selects
   so the row stays on one line, and given a fixed width because a native colour
   input has no sensible intrinsic size of its own. */
.text-overlay-controls input[type="color"] {
  flex: 0 0 auto;
  width: 46px;
  height: 38px;
  padding: 3px;
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 10px;
  background: var(--bg, #F7F7FB);
  cursor: pointer;
}
.text-overlay-controls input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.text-overlay-controls input[type="color"]::-webkit-color-swatch {
  border: none;
  border-radius: 6px;
}
.text-overlay-controls input[type="color"]::-moz-color-swatch {
  border: none;
  border-radius: 6px;
}
.text-overlay-controls select {
  padding: 9px 10px;
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 10px;
  font-family: inherit;
  font-size: 0.85rem;
  background: var(--bg, #F7F7FB);
  color: inherit;
}

/* ------------------------------------------------------------------
   TYPE OF TEXT - TABS (owner 2026-09-23, twice):

     "text on video - need tab for type of text"
     "the text on video tools do not have a text selector tab to show the
      different types to select from."

   The first attempt shipped a dropdown. The types existed, but you could not
   SEE them without opening the control - which is exactly what the owner said
   was missing. These are tabs: every type is visible at once, and the active
   one is unmistakably selected.

   Scoped to `.studio-page`. demo-studio.html carries that class too, so the
   demo shows the same control; scoping keeps the styling reasoned about in one
   place rather than relying on the class merely happening not to collide.
   ------------------------------------------------------------------ */
.studio-page .text-type-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  /* Its own row: the tab strip is the label for the controls beneath it. */
  flex: 1 1 100%;
  margin: 0 0 2px;
}
.studio-page .text-type-tab {
  flex: 0 1 auto;
  padding: 6px 10px;
  border: 1px solid var(--gray-light, #e5e7eb);
  background: var(--gray-bg, #fff);
  color: var(--text);
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.72rem;
  font-weight: 600;
  cursor: pointer;
  line-height: 1.1;
}
/* The active tab uses the text group's own violet, so "which type is chosen"
   reads at a glance instead of needing the tab to be compared side by side. */
.studio-page .text-type-tab:hover {
  border-color: var(--group, #8B5CF6);
  color: var(--group, #8B5CF6);
}
.studio-page .text-type-tab.is-active {
  background: var(--group, #8B5CF6);
  border-color: var(--group, #8B5CF6);
  color: #fff;
}
.studio-page .text-type-tab:focus-visible {
  outline: 2px solid var(--group, #8B5CF6);
  outline-offset: 2px;
}
/* The tabs carry the choice; the hidden input only stores it. */
.studio-page #textOverlayType { display: none; }
.dark .studio-page .text-type-tab {
  background: #0f0e17;
  border-color: #2a2a40;
}
.dark .studio-page .text-type-tab.is-active {
  background: var(--group, #8B5CF6);
  border-color: var(--group, #8B5CF6);
}
/* Owner 2026-09-23: "save button needs border."

   Save is a `.btn-outline`, whose shared rule paints a 1px `--gray-light`
   (#E3E9F5) edge on a white face - measured, that is a border the eye cannot
   find. The navy border the rest of the Studio already uses for its boxes makes
   the button read as a button while staying the secondary action beside Publish.
   Scoped to `.studio-page`: demo-studio.html has its own Save and keeps its look. */
.studio-page #saveBtn {
  border: 2px solid var(--studio-navy-border);
  font-weight: 700;
}
.studio-page #saveBtn:hover { background: var(--primary-bg, #EEF2FF); }

/* Owner 2026-09-23: "page needs back to studio button."
   Sits at the top of the gallery view, above its heading. */
.gallery-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-weight: 700;
}

/* The Enhance / Colour option picker (studio.js `chooseFinishOption`). */
.finish-options {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 10px;
}
.finish-option { text-align: left; }

/* The transcript read-back under the Auto-captions result. */
.ai-gen-transcript {
  margin: 8px 0 0;
  font-size: 0.8rem;
  color: var(--gray, #6B7280);
  font-style: italic;
}

/* ------------------------------------------------------------------
   TRIMMABLE TIMELINE BARS (owner 2026-09-23):

     "the end of a timeline needs to be a different color and a user must have the
      ability to trim a timeline by clicking the end of a timeline and be able to
      trim the timeline. this is why the timelines must have ruler marks like the
      time."

   The end of every bar carries its own coloured edge, so the difference between
   "drag the whole clip" (the body) and "make it shorter" (the edge) is visible
   rather than something the user has to discover. The edge is widened past its
   painted width with a transparent hit area: a 6px visual target is hard to grab
   on a trackpad, and a missed grab MOVES the clip instead of resizing it, which
   is the one outcome that would feel broken.

   Every rule is scoped to `.studio-page`. demo-studio.html renders its own
   `.track-clip` elements from the same class and has no resize behaviour, so an
   unscoped handle would draw a control that does nothing there. */
.studio-page .track-clip {
  /* The handle is positioned against the bar, so the bar must be the anchor. */
  position: absolute;
}
.studio-page .clip-trim-end {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 6px;
  background: var(--trim-accent, #FFFFFF);
  border-left: 1px solid rgba(0, 0, 0, 0.35);
  cursor: ew-resize;
  z-index: 4;
  touch-action: none;   /* a drag on the handle must not scroll the timeline */
}
/* A 6px mark is hard to hit; the invisible margin makes the target ~14px. */
.studio-page .clip-trim-end::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  right: -4px;
  left: -4px;
}
.studio-page .clip-trim-end:hover { background: #FFD34D; }

/* Per-lane ruler marks ("the timelines must have ruler marks like the time").
   Drawn INTO each lane's own overlay, so a lane reads its own scale instead of
   the creator measuring against the single ruler above. */
.studio-page .lane-ruler .lane-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--lane, #1E5BFF);
  opacity: 0.28;
}
.studio-page .lane-ruler .lane-tick.is-minor { opacity: 0.14; }

.text-overlay-note {
  margin-top: 8px;
  font-size: 0.75rem;
  color: var(--gray, #6B7280);
}
.dark .text-overlay-controls input[type="text"],
.dark .text-overlay-controls input[type="color"],
.dark .text-overlay-controls select { background: #0f0e17; border-color: #2a2a40; }

/* ------------------------------------------------------------------
   Model explainer overlay (owner 2026-09-22: "an overlay should pop up
   when user click on the model, so user can understand what they are
   paying for, such as better quality, audio etc. let the user decide
   base on knowledge of what they are paying").

   The pill beside the model <select> is a real button now; it opens this.
   Every figure inside is rendered by studio.js from the SAME
   /api/ai/models payload the price pill uses, so the overlay cannot quote
   a number the checkout will not charge.
   ------------------------------------------------------------------ */
.ai-gen-pill-btn {
  border: 1px solid var(--studio-navy-border, var(--nv-accent));
  background: transparent;
  cursor: pointer;
  font: inherit;
  color: inherit;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.ai-gen-pill-btn:hover { background: var(--primary-bg); }

/* Owner 2026-10-10: the Model info is now an INLINE panel (was a full-screen
   pop-out). It is the last child of row 2 and spans the full row on its own line. */
.ai-gen-row-2 > .model-inline { flex: 0 0 100%; }
.model-inline {
  border: 2px solid var(--primary);
  border-radius: var(--radius-sm);
  background: var(--primary-bg);
  padding: 12px 14px;
}
.model-inline[hidden] { display: none; }
.model-inline-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}
.model-inline-head h3 {
  margin: 0;
  font-size: 0.95rem;
  display: flex;
  align-items: center;
  gap: 6px;
}
.model-inline-close {
  border: 1px solid var(--nv-line, var(--gray-light));
  background: transparent;
  color: inherit;
  border-radius: 8px;
  width: 28px;
  height: 28px;
  cursor: pointer;
  flex: 0 0 28px;
}
.model-inline-close:hover { background: var(--gray-bg); }

.model-overlay {
  position: fixed;
  inset: 0;
  z-index: 1200;
  background: rgba(15, 14, 23, 0.62);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 22px;
}
/* `hidden` must win over the flex display above, or the overlay would sit over
   the whole studio from page load. */
.model-overlay[hidden] { display: none; }

.model-overlay-card {
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--nv-line, var(--gray-light));
  border-radius: 14px;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.35);
  max-width: 680px;
  width: 100%;
  max-height: 86vh;
  overflow-y: auto;
  padding: 22px 24px 20px;
}
.model-overlay-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}
.model-overlay-head h3 { margin: 0; font-size: 1.06rem; }
.model-overlay-close {
  border: 1px solid var(--nv-line, var(--gray-light));
  background: transparent;
  color: inherit;
  border-radius: 8px;
  width: 30px;
  height: 30px;
  cursor: pointer;
  flex: 0 0 30px;
}
.model-overlay-close:hover { background: var(--gray-bg); }
.model-overlay-lede {
  font-size: 0.82rem;
  color: var(--gray);
  line-height: 1.55;
  margin: 10px 0 16px;
}
.model-overlay-list { display: grid; gap: 10px; }
.model-row {
  border: 1px solid var(--nv-line, var(--gray-light));
  border-radius: 11px;
  padding: 12px 14px;
}
.model-row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.model-row-name { font-weight: 700; font-size: 0.92rem; }
.model-row-tokens {
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--primary);
  white-space: nowrap;
}
.model-row-meta {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--gray);
  margin-top: 3px;
}
.model-row-note {
  font-size: 0.8rem;
  color: var(--gray);
  line-height: 1.55;
  margin: 7px 0 0;
}
/* A model with no working endpoint is called out rather than offered as an
   equal choice - picking it would fail at render time. */
.model-row.is-unavailable { opacity: 0.72; border-style: dashed; }
.model-row-flag {
  display: inline-block;
  margin-top: 7px;
  font-size: 0.72rem;
  font-weight: 700;
  color: #8A1C1C;
  background: #FDECEC;
  border: 1px solid #F5C2C2;
  border-radius: 999px;
  padding: 2px 9px;
}
.model-overlay-foot {
  font-size: 0.78rem;
  color: var(--gray);
  line-height: 1.55;
  margin: 15px 0 0;
  border-top: 1px solid var(--nv-line, var(--gray-light));
  padding-top: 12px;
}

/* Owner 2026-09-23: the typing overlay for the description box. It reuses the
   .model-overlay backdrop/card, so only the input and its action row are new. The
   panel is wider than the model card because a prompt is a paragraph, and the
   textarea is tall and vertically resizable so a long prompt is comfortable. */
.prompt-overlay-card { max-width: 720px; }
.prompt-overlay-input {
  width: 100%;
  margin-top: 14px;
  padding: 12px 14px;
  border: 2px solid var(--studio-navy-border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 0.95rem;
  line-height: 1.5;
  color: inherit;
  background: var(--card);
  resize: vertical;
  min-height: 140px;
}
.prompt-overlay-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}
@media (max-width: 560px) {
  .prompt-overlay-card { padding: 17px 16px; }
  .prompt-overlay-input { min-height: 110px; }
}
@media (max-width: 560px) {
  .model-overlay { padding: 10px; }
  .model-overlay-card { padding: 17px 16px; }
}

/* ------------------------------------------------------------------
   Owner 2026-09-23 (03:52): "all titles in buttons shall be bold font."

   One scoped rule rather than editing a dozen per-class declarations, so a button
   added to this page later is bold by default instead of needing to remember this.
   Scoped to `.studio-page` because the shared stylesheets also serve demo-studio.html
   and the owner asked about THIS page.

   The two compound selectors are not decoration: `.studio-tab .btn-load-more`
   (0,2,0) and `.studio-gallery-card button` (0,1,1) both already declare
   `font-weight: 600`, so the plain entry below would lose to them and those two
   button titles would silently stay semi-bold.
   ------------------------------------------------------------------ */
.studio-page button,
.studio-page .btn-package,
.studio-page .tool-link,
.studio-page .studio-gallery-card button,
.studio-page .studio-tab .btn-load-more {
  font-weight: 700;
}

/* ------------------------------------------------------------------
   Owner 2026-09-23 (04:32): "reduce the height of the welcome back section. reduce
   the height of the describe the video you want section. reduce the height of the
   timeline section."

   Every selector here is scoped to `.studio-page`. `.welcome-banner`,
   `.timeline-header`, `.track`, `.track-row` and `.timeline-ruler` are ALL shared with
   demo-studio.html, so an unscoped trim would silently shorten the demo as well - the
   same trap the earlier 2026-09-23 sets hit.

   Only padding, gaps and the fixed row heights shrink. No type size is reduced: a
   smaller font is a harder read, not a smaller section.

   Measured on the real signed-in page (one 5s clip):
     welcome banner  182px -> 107px
     prompt row       65px ->  41px   (the box itself 65px -> 36px)
     timeline        285px -> 235px
   ------------------------------------------------------------------ */
.studio-page .welcome-banner {
  padding: 14px 16px;
  gap: 10px;
  margin-bottom: 12px;
}
/* The banner is a `flex-direction: row` at desktop width, so its height is its TEXT
   column, not its padding: measured banner-content 134px against a 56px button row.
   Trimming padding alone therefore left it tall (182 -> 162). The heading and the
   statement line come down with it. This is a height change the owner asked for, and
   the heading stays well above body size (1.35rem, vs 0.92rem body).

   The h1 mattered most: at 1.7rem the greeting needed ~630px but the column is 539px
   wide, so it wrapped to two lines. At 1.35rem it fits one - which is where most of
   the saving comes from. */
.studio-page .welcome-banner h1 { font-size: 1.35rem; }
/* Owner 2026-10-04 (Iris-relayed): "reduce the size of the font of these words on
   studio - Your social media creator studio is ready...". Taken down from 0.98rem
   (measured 15.68px) to 0.85rem (~13.6px). */
.studio-page #welcomeCopy { font-size: 0.85rem; line-height: 1.35; }

/* Owner 2026-10-10: "reduce the height of the banner by 15%." Baseline measured
   154px @1440 (actions column drives it: two card rows of 56px + gap). Target ~131px.
   Trim the vertical padding, the action-row gap and the card min-heights. */
.studio-page .welcome-banner { padding-top: 7px; padding-bottom: 7px; }
.studio-page .welcome-banner .banner-actions { gap: 6px; }
.studio-page .welcome-banner h1 { font-size: 1.18rem; }
.studio-page #welcomeCopy { font-size: 0.8rem; line-height: 1.3; }
.studio-page .welcome-banner .banner-actions > * { min-height: 46px; }

/* Owner 2026-10-10: "move all these boxes to the very top, but reduce the height of
   the boxes by 25%." The action row (Total Views, Engagement, Schedule, New Project,
   Import, Save) moves ABOVE the welcome text, to the very top of the banner, and its
   boxes come down 25% (measured 64.8px -> ~48px, i.e. x0.75). The banner becomes a
   column so the row can lead; `order:-1` puts the row first without touching markup,
   and an explicit height (beating the stretch that had them at 64.8) makes the 25%
   real rather than content-driven. Scoped to `.studio-page`, which BOTH studio.html
   and demo-studio.html carry, so the two stay matched. */
.studio-page .welcome-banner { flex-direction: column; align-items: stretch; }
/* In the old ROW layout `.banner-content`'s `flex: 1 1 320px` was a WIDTH basis. With the
   banner now a COLUMN that basis became a 320px HEIGHT, ballooning the banner to 334px.
   Owner 2026-10-10: "reduce the height of the welcome back button to just underneath the
   your social media words." Reset the basis so the content hugs the greeting + copy. */
.studio-page .welcome-banner .banner-content { flex: 0 1 auto; min-width: 0; }

/* Owner 2026-10-10 (19:23Z): "move the notifications ico to far right of the welcome
   back box." The bell moved into the banner. Making the banner a ROW (again) with
   `.banner-content { flex: 0 1 auto }` lets the bell sit at the far right WITHOUT the
   banner stretching to the full column width - a stretched `align-items: stretch`
   column was the exact 334px-height bug fixed earlier, so `align-items: center` keeps
   the height at the content. On mobile the banner stacks and the bell wraps right. */
.studio-page .welcome-banner { flex-direction: row; align-items: center; justify-content: space-between; }
.studio-page .welcome-banner .banner-notify { flex: 0 0 auto; width: 34px; height: 34px; font-size: 0.95rem; }
@media (max-width: 639px) {
  .studio-page .welcome-banner { flex-direction: column; align-items: flex-end; }
  .studio-page .welcome-banner .banner-content { align-self: flex-start; width: 100%; }
}

/* Owner 2026-10-10 (later): "move to the right of the need help button. increase the
   size of the need help button." The action row now sits INSIDE the header's right
   cluster, immediately to the right of the help button, so it is a compact inline
   group: no band margin, no 48px stretch, items sized to the header. Scoped
   `.studio-page`, which both studio.html and demo-studio.html carry. */
.top-stats {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
}
.top-stats .banner-stat {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0;
  padding: 3px 9px;
  border: 1px solid var(--gray-light);
  border-radius: var(--radius-sm);
  background: var(--card);
}
.top-stats .banner-stat .banner-stat-value { font-size: 0.82rem; font-weight: 800; line-height: 1.15; }
.top-stats .banner-stat .banner-stat-label { font-size: 0.6rem; line-height: 1.15; }
.top-stats .banner-stat .banner-stat-value,
.top-stats .banner-stat .banner-stat-label { color: var(--gray); opacity: 1; }
.top-stats .banner-schedule {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 9px;
  border: 1px solid var(--gray-light);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--text);
}
.top-stats .banner-schedule h4 { margin: 0; white-space: nowrap; font-size: 0.72rem; }
.top-stats .banner-schedule .tool-group-note { margin: 0; font-size: 0.6rem; white-space: nowrap; }
.top-stats .banner-schedule .tool-buttons { margin: 0; }
.top-stats .banner-schedule .tool-link {
  white-space: nowrap; text-decoration: none; font-weight: 600; color: var(--primary); font-size: 0.72rem;
}
/* The three buttons left the blue banner, where white labels were readable. On the
   light header surface they need explicit contrasting colours, and a compact box. */
.top-stats > .btn-primary,
.top-stats > .btn-secondary,
.top-stats > .btn-outline {
  height: auto; padding: 7px 10px; font-size: 0.76rem; line-height: 1.15;
}
.top-stats > .btn-primary i,
.top-stats > .btn-secondary i,
.top-stats > .btn-outline i { font-size: 0.76rem; }
.top-stats #newProjectBtn { background: var(--primary); color: #fff; justify-content: center; }
.top-stats #newProjectBtn:hover { background: var(--primary-dark); }
.top-stats #importBtn {
  background: transparent; color: var(--primary);
  border: 1.5px solid var(--gray-light); justify-content: center;
}
.top-stats #importBtn:hover { background: var(--primary-bg); border-color: var(--primary); }
.top-stats #saveBtn { color: var(--primary); justify-content: center; }
.top-stats #saveBtn:hover { background: var(--primary-bg); }

/* Owner 2026-10-10: "increase the size of the need help button." The help button
   (the last square icon button) becomes a larger labelled pill so it reads as the
   control the owner names "need help". Wider + taller + a bigger glyph. */
.header-right > #helpBtn {
  height: 36px;
  width: auto;
  min-width: 36px;
  padding: 0 12px;
  font-size: 1.05rem;
  border-radius: 10px;
  color: #fff;
  background: var(--primary);
  border-color: var(--primary);
}
.header-right > #helpBtn:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
#helpBtn .help-label { margin-left: 7px; font-size: 0.8rem; font-weight: 700; white-space: nowrap; }
@media (max-width: 1100px) { #helpBtn .help-label { display: none; } }
/* If the header's right cluster runs out of width, drop the row's less-critical text
   (the Schedule note) before the row clips. */
@media (max-width: 1240px) { .top-stats .banner-schedule .tool-group-note { display: none; } }
/* Narrow widths: the header cannot hold the breadcrumb + 3 icon buttons + the action
   row on one line. Let the header grow and the right cluster wrap rather than clip
   the newest items off the right edge. */
@media (max-width: 1320px) {
  .studio-page .top-header { height: auto; }
  .studio-page .top-header-main { flex-wrap: wrap; }
  .studio-page .header-right { flex-wrap: wrap; justify-content: flex-end; row-gap: 6px; }
  .top-stats { flex-wrap: wrap; justify-content: flex-end; }
}

/* Row 1's height is set by its TALLEST control, and the theme gives `.btn-primary` a
   16px vertical padding - measured Publish at 54.5px against Save's 41px, so that one
   button was holding the whole row at 55px. Matching the two brings the row down to the
   height of the other controls. */
.studio-page #aiGenRow1 .btn-outline,
.studio-page #aiGenRow1 .btn-primary {
  padding-top: 9px;
  padding-bottom: 9px;
}

/* The description box is a single-line trigger for the typing overlay now (the
   markup carries rows="1"), so it needs less padding than a two-line textarea.
   `align-self: stretch` keeps it the SAME HEIGHT as the balance box and the buttons
   beside it: the row is `align-items: center` so a natural-height box would sit 5px
   short of its neighbours (measured 36 vs 41), and the owner's standing rule for this
   row is that every box on it matches. */
.studio-page .ai-gen-prompt { padding: 6px 12px; align-self: stretch; }

/* Timeline. Each track's height comes from its LABEL column (the lane name plus the
   time chip underneath), not from the bar - so trimming the bar alone saved nothing.
   The label column, the ruler, the track gaps and the meta line are what move. */
.studio-page .project-timeline { padding: 8px; }
.studio-page .project-timeline .timeline-header { margin-bottom: 6px; }
.studio-page .project-timeline .timeline-zoom button { width: 24px; height: 24px; }
.studio-page .project-timeline .timeline-ruler { height: 14px; }
.studio-page .project-timeline .track { margin-bottom: 2px; }
.studio-page .project-timeline .track-label { font-size: 0.68rem; font-weight: 700; }
/* Owner 2026-09-25: this override was shrinking the pill to 0.58rem - smaller than
   the base rule - which is what made it hard to read. Raised above the base size so
   the navy band shows the BIGGER value, not the smallest one. */
.studio-page .project-timeline .lane-time { font-size: 0.8rem; margin-top: 0; }
.studio-page .project-timeline .track-row { height: 18px; min-height: 18px; }
/* The clips have to come down with the row they sit in, or an 18px bar would overflow
   an 18px row. */
.studio-page .project-timeline .track-clip { height: 16px; min-height: 16px; }
/* `.timeline-meta` had NO rule of its own, so it carried the browser's 16px default
   (measured 24px tall for one line of project summary). */
.studio-page .project-timeline .timeline-meta { font-size: 0.7rem; margin: 2px 0 0; }

/* The failure/stopped line in the result box (see showOutcome in studio.js). It has to
   read as "not a success" without shouting. */
.ai-gen-failed { color: var(--secondary); }

/* ==================================================================
   Owner 2026-09-25 batch (logged in ox88/STUDIO-REQUEST-LOG.md)
   ================================================================== */

/* --- "every pill and box shall have a border" --------------------------
   Each of these classes had a background and radius but NO border, so they read as
   flat lozenges. The border uses each element's own accent where one exists
   (`--group`, `--primary`) so a bordered pill still belongs to its box rather than
   gaining a generic grey outline. `border` (not `outline`) keeps the box model
   honest and does not overlap neighbours. */
.ai-gen-pill {
  border: 1px solid var(--primary);
}
.preview-time {
  border: 1px solid rgba(255, 255, 255, 0.42);
  border-radius: 999px;
  padding: 1px 8px;
}
/* The time pill already had a white chip behind it; give the chip an edge so it
   reads as a pill rather than a smudge on the navy band. */
.studio-page .project-timeline .lane-time {
  border: 1px solid var(--studio-navy-border);
}
.project-status, .status {
  border: 1px solid currentColor;
}
.industry-pill {
  border: 1px solid var(--primary);
}
/* Sidebar count badges and the store tabs are pills too. */
.sidebar-nav .badge,
.studio-page .tab {
  border: 1px solid var(--primary);
}

/* --- "make the timeline numbers more bold and easier to see" -----------
   The ruler's tick labels are the NUMBERS along the top scale, a different element
   from the `.lane-time` chips handled above. They were 0.6rem at the inherited
   weight with no tabular figures, so digits shifted during a drag. */
.ruler-label {
  font-size: 0.72rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.studio-page .project-timeline .ruler-label {
  color: #fff;
  /* The band is navy, so a heavier label needs the shadow to stay legible over a
     tick rather than merging into it. */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
}
/* `.is-default` marks the empty ruler's placeholder label; it is intentionally not
   bold (it is a hint, not a number), so keep it lighter than a real reading. */
.timeline-ruler.is-default .ruler-label { font-weight: 600; }

/* --- Studio-only quality score badge -------------------------------
   Owner 2026-09-25: "all scores shall be marked on the upper right hand of the video
   when the video is in the creative studio only and not when it leaves the studio."

   This is a DOM overlay in the corner of `.preview-container`. It is deliberately
   NOT burned into the pixels: a burned-in score would leave the studio inside the
   exported file, which is exactly what the owner asked NOT to happen. Because it is
   markup, it disappears the moment the video is downloaded, published or played
   anywhere else, and no render step has to know about it.

   `pointer-events: none` keeps it from stealing a click from the video controls
   underneath - the same class of bug as the ai-store backdrop fixed today. */
.studio-score-badge {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 5;
  pointer-events: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  max-width: 46%;
}
.studio-score-badge[hidden] { display: none; }
.studio-score-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 5px 11px;
  border: 2px solid var(--studio-navy-border);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.96);
  color: var(--nv-ink, #16152b);
  font-weight: 800;
  font-size: 0.95rem;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 4px 14px rgba(16, 21, 43, 0.28);
}
.studio-score-chip .studio-score-of { font-size: 0.68rem; font-weight: 700; opacity: 0.7; }
/* Colour carries the verdict, so the number is readable at a glance without
   parsing the words. Same 80 floor the scorer uses (STRONG_SCORE). */
.studio-score-badge.is-strong .studio-score-chip { border-color: #15803D; color: #14532D; }
.studio-score-badge.is-mid .studio-score-chip    { border-color: #B45309; color: #7C2D12; }
.studio-score-badge.is-low .studio-score-chip    { border-color: #B91C1C; color: #7F1D1D; }
.studio-score-axes {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.studio-score-axes span {
  font-size: 0.62rem;
  font-weight: 700;
  padding: 2px 7px;
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 999px;
  background: rgba(16, 21, 43, 0.72);
  color: #fff;
  white-space: nowrap;
}
/* The studio-only note. Saying it on the badge is the honest thing: the score is a
   studio aid, not a claim about the video wherever else it appears. */
.studio-score-note {
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 999px;
  background: rgba(16, 21, 43, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.4);
  color: #fff;
  opacity: 0.92;
}

/* --- Background theme swatches (the box added beside Analytics) --------
   Owner 2026-09-25: "the user can make their content studio to their color. they
   cannot change the color of the tools."

   The swatches write ONE custom property (`--studio-surface`) on the page root. The
   tool boxes read their colours from `--group`/`--group-ink` (fixed per group), so
   changing `--studio-surface` cannot recolour a tool. That is the constraint held by
   CONSTRUCTION rather than by remembering not to. */
.studio-theme-swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.studio-theme-swatch {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  border: 2px solid var(--studio-navy-border);
  cursor: pointer;
  padding: 0;
  background: var(--swatch, #fff);
}
.studio-theme-swatch[aria-pressed="true"] {
  /* A ring, not a colour change, so the selected state is visible on ANY swatch
     including very light ones. */
  box-shadow: 0 0 0 3px var(--primary);
}
.studio-theme-swatch:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* --- Name-this-video popup (owner 2026-09-25) --------------------------
   "when a video is loaded to screen, a pop shall always pop up to be named and moved
   to projects immediately to store users work."

   Reuses the existing `.model-overlay` shell so it inherits the backdrop, focus
   behaviour and Escape handling already built for the other dialogs. */
.name-clip-card { max-width: 420px; }
.name-clip-input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border, #e5e7eb);
  border-radius: 10px;
  font-family: inherit;
  font-size: 0.9rem;
  background: var(--card, #fff);
  color: inherit;
  margin-bottom: 4px;
}
.name-clip-hint { font-size: 0.72rem; color: var(--gray); margin: 0 0 10px; }
.name-clip-actions { display: flex; gap: 8px; justify-content: flex-end; }

/* Applied ONLY once the creator has chosen a theme (`data-studio-theme` on <html>), so
   the default studio look stays byte-for-byte what it was before this feature existed.
   This rule paints the studio background and nothing else: the tool boxes read
   `--group` / `--group-ink`, which this never sets. */
html[data-studio-theme] .studio-page {
  background: var(--studio-surface, var(--nv-surface));
  transition: background 0.18s ease;
}

/* =====================================================================
   Owner 2026-09-29 Creative Studio defect batch — additive styles only.
   Appended at the end so the shared file's existing rules and the other
   writer's sections are untouched. Each block names the defect it serves.
   ===================================================================== */

/* --- Item 1: the centred play circle no longer overlays playing media.
   `.preview-overlay` is hidden by studio.js (its `hidden` attribute is the
   single source of truth, and it is hidden by DEFAULT in the markup too), so
   this only guarantees the attribute wins over the flex display. --- */
.preview-overlay[hidden] { display: none; }

/* The honest placeholder shown WHILE a video is still loading. Without it a
   hidden play circle on a 9MB clip reads as a frozen/dead viewer. */
.preview-loading {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: #fff;
  font-family: Inter, sans-serif;
  font-size: 0.8rem;
  font-weight: 600;
  background: rgba(10, 20, 40, 0.42);
  pointer-events: none;
}
.preview-loading[hidden] { display: none; }
.preview-loading-spinner {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.45);
  border-top-color: #fff;
  animation: preview-loading-spin 0.8s linear infinite;
}
@keyframes preview-loading-spin { to { transform: rotate(360deg); } }

/* --- Item 2: projects search input beside the "Projects" library nav. --- */
.sidebar-search-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 2px 14px 5px;
  list-style: none;
  cursor: default;
}
.sidebar-search-row > i { font-size: 0.72rem; opacity: 0.6; }
.sidebar-search {
  flex: 1 1 auto;
  min-width: 0;
  padding: 5px 9px;
  border: 1px solid var(--studio-navy-border, rgba(255, 255, 255, 0.25));
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.08);
  color: inherit;
  font-family: inherit;
  font-size: 0.74rem;
}
.sidebar-search::placeholder { color: inherit; opacity: 0.55; }
.sidebar-search:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

/* --- Item 3: the Studio assistant chat box (replaces the theme box). --- */
.studio-chat-log {
  max-height: 190px;
  overflow-y: auto;
  margin: 6px 0 8px;
  padding: 2px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.studio-chat-empty { font-size: 0.72rem; opacity: 0.7; margin: 0; }
.studio-chat-msg {
  font-size: 0.76rem;
  line-height: 1.4;
  padding: 6px 9px;
  border-radius: 9px;
  max-width: 92%;
  white-space: pre-wrap;
  word-break: break-word;
}
.studio-chat-msg.is-user {
  align-self: flex-end;
  background: var(--primary, #0f3460);
  color: #fff;
}
.studio-chat-msg.is-bot {
  align-self: flex-start;
  background: var(--gray-bg, #f7f7fb);
  color: var(--text, #1f2937);
  border: 1px solid var(--border, rgba(0, 0, 0, 0.12));
}
.studio-chat-msg.is-error {
  align-self: flex-start;
  background: #fdf3e7;
  color: #7a4a00;
  border: 1px solid #f0d9b5;
}
.studio-chat-msg.is-pending { opacity: 0.65; font-style: italic; }
/* Owner 2026-09-30: title + guidance on the left, send arrow flush right at the
   TOP of the box, log then input below. The send button is a real .tool-btn, so it
   keeps the shared button skin and is guarded from the generic dispatcher in JS. */
.studio-chat-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}
.studio-chat-titlebar { min-width: 0; }
.studio-chat-titlebar h4 { margin-bottom: 2px; }
.studio-chat-hint { margin: 0; display: flex; flex-direction: column; gap: 1px; }
.studio-chat-hint .tool-group-note { margin: 0; }
.studio-chat-send {
  flex: 0 0 auto;
  align-self: flex-start;
  padding: 6px 10px;
  line-height: 1;
}
.studio-chat-input {
  width: 100%;
  margin-top: 2px;
  resize: vertical;
  padding: 7px 9px;
  border: 1px solid var(--gray-light, #e5e7eb);
  border-radius: 8px;
  font-family: inherit;
  font-size: 0.76rem;
  background: var(--card, #fff);
  color: inherit;
}
.studio-chat-input:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

/* --- Owner 2026-09-30: the Studio assistant moved OUT of the right rail and now
   sits directly UNDER the video viewer (inside .video-preview, after
   .preview-container). The rail used to space its children; `video-preview` is a
   plain block, so the box needs its own top gap or it butts against the viewer. --- */
.video-preview > .studio-chat { margin-top: 8px; }

/* --- Owner 2026-09-30: viewer kebab (3 dots) to the right of the video. --- */
.preview-menu { position: relative; display: inline-flex; }
.preview-menu-btn { padding: 4px 8px; }
.preview-menu-panel {
  position: absolute;
  right: 0;
  bottom: calc(100% + 6px);
  min-width: 148px;
  padding: 4px;
  border: 1px solid var(--studio-navy-border, #d5dbe7);
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 10px 26px rgba(15, 52, 96, 0.18);
  z-index: 20;
}
.preview-menu-panel[hidden] { display: none; }
.preview-menu-panel button {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.78rem;
  text-align: left;
  cursor: pointer;
}
.preview-menu-panel button:hover { background: rgba(15, 52, 96, 0.08); }

/* Owner 2026-10-10: "all words in video viewer should be white to see better."
   The viewer sits on the dark ink panel (--nv-text), but several labels were NOT
   white: the timeline row inherited --nv-body-2 (a grey #6B6961), the time pill
   was dimmed, and Add-to-timeline / Open-render used mint/blue tints - all low
   contrast on dark navy. Every viewer word is now #fff. The TWO light-chipped
   elements (the score chip and the Rename popover) keep their own contrast: the
   popover flips to a DARK panel so its white label stays visible. */
.video-preview .preview-timeline { color: #fff; }
.video-preview .timeline-time { color: #fff; opacity: 0.92; }
.video-preview .preview-time { color: #fff; opacity: 0.95; }
.video-preview .preview-controls #addToTimelineBtn { color: #fff; }
.video-preview .preview-controls .export-link { color: #fff; }
.video-preview .preview-controls .history-btn { color: #fff; }
/* Owner 2026-10-10 (later): "words at bottom of video viewer should be white." The
   bottom control words (Add to timeline, Save as draft, Render timeline, Back,
   Forward) are white, but on a fresh studio the buttons are DISABLED, and the
   `.screen-action:disabled` / `.history-btn:disabled` opacity:0.38 was dimming those
   words to grey. Keep the words white; signal "disabled" with a fainter border and the
   not-allowed cursor instead of by fading the text. */
.video-preview .preview-controls .screen-action:disabled,
.video-preview .preview-controls .history-btn:disabled {
  opacity: 1;
  border-color: rgba(255, 255, 255, 0.2);
}
.video-preview .preview-menu-panel {
  background: var(--nv-ink-soft, #0A1F42);
  border-color: rgba(255, 255, 255, 0.28);
}
.video-preview .preview-menu-panel button { color: #fff; }
.video-preview .preview-menu-panel button:hover { background: rgba(255, 255, 255, 0.16); }

/* --- Item 5: rename affordance on a project card + in the viewer. --- */
.project-rename-btn {
  margin-top: 6px;
  padding: 4px 10px;
  font-size: 0.7rem;
  border: 1px solid var(--primary, #0f3460);
  border-radius: 7px;
  background: transparent;
  color: var(--primary, #0f3460);
  cursor: pointer;
}
.project-rename-btn:hover { background: rgba(15, 52, 96, 0.08); }

/* --- Item 7: royalty-free price/add controls on each music track. --- */
.music-lib-price { font-size: 0.68rem; font-weight: 700; }
.music-lib-price.is-free { color: #1a7f4b; }
.music-lib-price.is-paid { color: #8a5a00; }
.music-lib-buy[disabled] { opacity: 0.6; cursor: not-allowed; }
.music-lib-rf {
  font-size: 0.64rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #1a7f4b;
}

/* ==========================================================================
   "Made with the studio" — real draft showcase
   Owner 2026-10-04 (Iris-relayed): "PLACE OUR VIDEO DRAFTS IN THE GALLERY FOR
   USERS CAN SEE WHAT CAN BE MADE WITH OUR STUDIO." The three cut drafts the team
   rendered with our OWN engine are shown as a strip on the dashboard, so a new
   user sees what the studio produces. Real drafts only — no new renders. Clicking
   a card opens it full-screen with sound (an inline <video>, no download).
   ========================================================================== */
.draft-strip { margin: 0 0 22px; }
.draft-strip-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}
.draft-strip-header h2 { margin: 0; }
.draft-strip-row {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
}
.draft-strip-note { font-size: 0.8rem; color: var(--gray); margin: 10px 0 0; }
.draft-card {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--nv-line, var(--gray-light));
  border-radius: var(--radius-sm, 10px);
  background: var(--card, #fff);
  overflow: hidden;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}
.draft-card:hover { border-color: var(--primary); }
.draft-card:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.draft-card-tile {
  position: relative;
  display: block;
  aspect-ratio: 9 / 16;
  background: #000;
}
.draft-card-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.draft-card-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-size: 1.9rem;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
  pointer-events: none;
}
.draft-card-info { display: block; padding: 8px 10px; }
.draft-card-title { display: block; font-weight: 700; font-size: 0.85rem; color: var(--text); }
.draft-card-meta { display: block; font-size: 0.72rem; color: var(--gray); margin-top: 2px; }

/* Full-screen draft player. Same backdrop language as the model overlay. */
.draft-modal {
  position: fixed;
  inset: 0;
  z-index: 1300;
  background: rgba(15, 14, 23, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.draft-modal[hidden] { display: none; }
.draft-modal-card {
  position: relative;
  background: var(--card, #fff);
  color: var(--text);
  border-radius: 16px;
  padding: 14px;
  max-width: 460px;
  width: 100%;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.45);
}
.draft-modal-close {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  border: 0;
  background: rgba(15, 14, 23, 0.55);
  color: #fff;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  cursor: pointer;
}
.draft-modal-close:hover { background: rgba(15, 14, 23, 0.8); }
.draft-modal-card video {
  width: 100%;
  border-radius: 10px;
  display: block;
  background: #000;
  max-height: 78vh;
}
.draft-modal-caption { margin: 10px 2px 2px; font-size: 0.82rem; color: var(--gray); line-height: 1.45; }
.draft-modal-caption b { color: var(--text); }

/* Draft Box detail. Owner 2026-10-04: "when user clicks draft box, a larger box must
   pop out, so user can read clearly the name of the draft." Wider than `.draft-modal`
   and led by a large name heading, so the draft's name is the first thing read. Same
   backdrop language as the other studio modals. */
.draft-box-modal {
  position: fixed;
  inset: 0;
  z-index: 1310;
  background: rgba(15, 14, 23, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.draft-box-modal[hidden] { display: none; }
.draft-box-card {
  position: relative;
  background: var(--card, #fff);
  color: var(--text);
  border-radius: 16px;
  padding: 26px 24px 20px;
  max-width: 640px;
  width: 100%;
  box-shadow: 0 18px 60px rgba(0, 0, 0, 0.45);
}
.draft-box-close {
  position: absolute;
  top: 10px;
  right: 10px;
  border: 0;
  background: rgba(15, 14, 23, 0.06);
  color: var(--text);
  width: 34px;
  height: 34px;
  border-radius: 8px;
  cursor: pointer;
}
.draft-box-close:hover { background: rgba(15, 14, 23, 0.14); }
.draft-box-name {
  margin: 0 40px 6px 0;
  font-size: 1.8rem;
  font-weight: 800;
  line-height: 1.2;
  color: var(--text);
  word-break: break-word;
}
.draft-box-meta { margin: 0 0 18px; color: var(--gray); font-size: 0.85rem; line-height: 1.45; }
.draft-box-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.draft-box-btn {
  border: 1px solid var(--gray-light);
  background: var(--card);
  color: var(--text);
  border-radius: 9px;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 9px 16px;
  cursor: pointer;
}
.draft-box-btn i { margin-right: 6px; }
.draft-box-btn:hover { border-color: var(--primary); color: var(--primary); }
.draft-box-btn--danger { border-color: #f3c5c5; color: #b91c1c; }
.draft-box-btn--danger:hover { border-color: #dc2626; color: #dc2626; background: #fef2f2; }

/* ==========================================================================
   Studio Assistant — floating bottom-right overlay.
   Owner 2026-10-04 (Iris-relayed): "move studio assistant to bottom right hand
   corner as an overlay with an option to the user to click x to exit". The chat
   keeps its #themeBox element and the same /api/ai/chat wiring; only its POSITION
   changes — it is pulled out of the preview column and pinned to the corner.
   `.is-docked` is added in the markup, and the X removes it entirely (so the
   studio is un-obscured). No ancestor carries a transform/filter, so `fixed`
   pins to the viewport as intended.
   ========================================================================== */
.studio-chat.is-docked {
  position: fixed;
  right: 18px;
  bottom: 18px;
  width: 330px;
  max-width: calc(100vw - 24px);
  margin: 0;
  z-index: 1150;
  background: var(--card, #fff);
  box-shadow: 0 14px 40px rgba(15, 52, 96, 0.24);
}
.studio-chat.is-docked[hidden] { display: none; }
.studio-chat.is-docked .studio-chat-log { max-height: min(38vh, 240px); }
.studio-chat-actions {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  flex: 0 0 auto;
}
.studio-chat-close { padding: 6px 10px; line-height: 1; }

/* ==========================================================================
   Owner 2026-10-05: "move studio assistant next to the type of video selection
   box; then move schedule to the right of the studio assistant box."
   The assistant is no longer a floating bottom-right dock — it sits INLINE in the
   generator's row 2, beside the Type-of-video selector, with Schedule just right
   of it. Scoped sizing keeps the row from being dominated by the chat box.
   ========================================================================== */
.studio-page .ai-gen-row-2 .studio-chat--inline {
  flex: 0 1 300px;
  min-width: 240px;
  margin: 0;
  align-self: stretch;
}
.studio-page .ai-gen-row-2 .studio-chat--inline .studio-chat-log { max-height: 92px; }
.studio-page .ai-gen-row-2 .studio-chat--inline .studio-chat-hint { display: none; }
.studio-page .ai-gen-row-2 > .tool-group--nav:not(.studio-chat) {
  flex: 0 0 auto;
  align-self: stretch;
}

/* ==========================================================================
   Audio box — the word and all four pills on ONE line.
   Owner 2026-10-04: "AUDIO - VOICEOVER MUST BE ON THE SAME LINE AS THE OTHER THREE
   BUTTONS", resolved then by giving the heading its OWN line so the four pills
   could stay on one. Owner 2026-10-10 now asks for the opposite: "move audio word
   to the same line as the boxes audio library ai generate and voiceover." So the
   heading is back ON the pills' row (its `flex: 1 1 100%` own-line rule is gone).
   The four pills still never wrap: the row is nowrap and, if the rail is too narrow
   to hold heading + pills, the BUTTON BLOCK scrolls horizontally inside the box
   (min-width:0 lets it shrink below content) rather than dropping a pill to its own
   line. `margin-left:auto` on the first pill keeps the row flush right when it fits
   and scrollable from the start when it does not.
   ========================================================================== */
.tool-group[data-tool-group="audio"] { min-width: 0; }
.tool-group[data-tool-group="audio"] .tool-head { flex-wrap: nowrap; align-items: center; }
.tool-group[data-tool-group="audio"] .tool-head h4 { flex: 0 0 auto; }
.tool-group[data-tool-group="audio"] .tool-head .tool-buttons {
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: nowrap;
  justify-content: flex-start;
  /* Owner 2026-10-10 ("keep it tight"): the heading + four pills now share ONE row,
     which needs ~56px more than the row had, so the pills are tightened to fit
     without scrolling. Measured at the rail's fixed width: need 296px of 304px
     available (8px slack) with these values. Left as the compact audio-group size;
     the other tool groups keep the base .tool-btn size. */
  gap: 3px;
  overflow-x: auto;
}
.tool-group[data-tool-group="audio"] .tool-head .tool-buttons > :first-child { margin-left: auto; }
.tool-group[data-tool-group="audio"] .tool-btn {
  white-space: nowrap;
  flex: 0 0 auto;
  padding: 3px 5px;
  gap: 3px;
  font-size: 0.66rem;
}

/* ==========================================================================
   "Made with the studio" — match the other media galleries' size.
   Owner 2026-10-04 (Iris-relayed #16): "MAKE THE VIDEOS IN THE GALLERY AS THE SAME
   SIZE AS THE OTHER MEDIA GALLERIES." Measured: the draft cards were 9:16 and ~216px
   wide against the video/photo library cards' 156px, so they read as a different,
   larger gallery. They now use the SAME single-scrollable-row sizing and the same
   4:3 tile as `.media-library-grid` (132 -> 144 -> 156px at the identical
   breakpoints), so every media gallery on the page matches. The poster keeps
   object-fit:cover (its crop is the tile, exactly like a library video thumbnail).
   ========================================================================== */
.draft-strip-row {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 6px;
  scroll-snap-type: x proximity;
}
.draft-card { width: auto; flex: 0 0 132px; scroll-snap-align: start; }
.draft-card-tile { aspect-ratio: 4 / 3; }
@media (min-width: 640px) { .draft-card { flex-basis: 144px; } }
@media (min-width: 1024px) { .draft-card { flex-basis: 156px; } }

/* Owner 2026-10-11: HALF timeline / HALF preview in the TIMELINE ROW, with a
   user-draggable divider and a left/right swap. The row spans the workspace grid's
   timeline area; because the video column is centred, the 50/50 default lands on the
   video viewer's mid-point. The choice is saved per user (localStorage). */
.tl-preview-split { grid-area: timeline; display: flex; align-items: stretch; gap: 0; }
.tl-preview-split > .project-timeline { flex: 0 0 50%; min-width: 0; }
.tl-preview-split > .preview-panel { flex: 1 1 0; min-width: 0; }
/* Owner 2026-10-11: the timeline box and the preview box share ONE height. The taller
   of the two sets the row height, and the other stretches to match (they were reading
   different heights before). The preview stacks its parts and grows its stage to fill,
   so no blank gap opens at its foot. */
.tl-preview-split > .project-timeline,
.tl-preview-split > .preview-panel { align-self: stretch; height: auto; }
.tl-preview-split > .preview-panel { display: flex; flex-direction: column; }
.tl-preview-split > .preview-panel > .preview-panel-stage { flex: 1 1 auto; }
/* The timeline is now a child of the split, not of the workspace grid, so its own
   workspace grid-area must not apply here. */
.studio-workspace--split .project-timeline { grid-area: auto; }

/* Drag handle between the two panels. */
.tl-splitter { flex: 0 0 14px; position: relative; cursor: col-resize; touch-action: none; }
.tl-splitter::before { content: ""; position: absolute; top: 6px; bottom: 6px; left: 50%;
  transform: translateX(-50%); width: 3px; border-radius: 3px; background: rgba(255,255,255,0.22); }
.tl-splitter:hover::before, .tl-splitter:focus-visible::before { background: #6C63FF; }
.tl-splitter:focus-visible { outline: none; }

/* Swap: move the timeline / preview to the other side (visual order only). */
.tl-preview-split.is-swapped > .project-timeline { order: 3; }
.tl-preview-split.is-swapped > .tl-splitter { order: 2; }
.tl-preview-split.is-swapped > .preview-panel { order: 1; }

.preview-panel-controls { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.preview-swap-btn { border: 0; background: rgba(108,99,255,0.18); color: #b8b3ff; font: inherit; font-size: 12px;
  font-weight: 600; padding: 4px 9px; border-radius: 8px; cursor: pointer; }
.preview-swap-btn:hover { background: #6C63FF; color: #fff; }

@media (max-width: 900px) {
  .tl-preview-split { flex-direction: column; }
  .tl-preview-split > .project-timeline { flex: 1 1 auto; }
  .tl-splitter { display: none; }
}
.preview-panel { background: #000; border-radius: var(--radius, 12px); padding: 12px; color: #fff; }
.preview-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.preview-panel-head h4 { margin: 0; font-size: 0.9rem; display: flex; gap: 8px; align-items: center; color: #fff; }
.preview-panel-note { font-size: 12px; color: rgba(255, 255, 255, 0.72); }
/* Owner 2026-10-11: the preview panel lists the timeline's clips so a user can CLICK a
   clip (or DRAG it up to the viewer) to open it for editing. */
.preview-panel-stage { margin: 10px 0; flex: 1 1 auto; min-height: 120px; }
.pv-clips-hint { margin: 0 0 8px; font-size: 12px; color: rgba(255,255,255,0.75); }
.pv-clip-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; max-height: 260px; overflow-y: auto; }
.pv-clip { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border: 1px solid rgba(255,255,255,0.16);
  border-radius: 8px; background: #16162a; color: #e6e6ef; font-size: 12.5px; cursor: pointer; }
.pv-clip:hover { border-color: #6C63FF; background: #1d1d33; }
.pv-clip.is-selected { border-color: #6C63FF; box-shadow: inset 0 0 0 1px #6C63FF; }
.pv-clip-thumb { flex: 0 0 auto; width: 64px; height: 36px; border-radius: 6px; overflow: hidden; background: #0d0d18;
  display: flex; align-items: center; justify-content: center; position: relative; }
.pv-clip-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pv-clip-thumb i { color: #6C63FF; font-size: 16px; }
.pv-clip-body { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.pv-clip-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pv-clip-dur { color: rgba(255,255,255,0.6); font-size: 11px; }
.pv-clip-empty { font-size: 12px; color: rgba(255,255,255,0.6); padding: 6px 0; }
.preview-panel-stage i { font-size: 28px; color: #6C63FF; }
.preview-panel-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.preview-panel-hint { font-size: 12px; color: rgba(255, 255, 255, 0.72); }

/* Owner 2026-10-11: description box next to the top "Need help" button. */
.help-quick-ask { width: 260px; max-width: 34vw; padding: 7px 12px; border-radius: 10px;
  border: 1px solid rgba(108,99,255,0.35); background: #fff; color: #0f0e17; font: inherit; font-size: 13px; }
.help-quick-ask::placeholder { color: #6B7280; }
.help-quick-ask:focus { outline: none; border-color: #6C63FF; box-shadow: 0 0 0 2px rgba(108,99,255,0.18); }
.dark .help-quick-ask { background: #12121f; color: #fff; border-color: #2a2a40; }
@media (max-width: 900px) { .help-quick-ask { width: 180px; } }

/* Owner 2026-10-11: "Send to viewer" on the "Made with the studio" showcase cards. */
.draft-card { position: relative; }
.draft-card-use { margin: 6px 0 0; width: 100%; border: 1px solid rgba(108,99,255,0.5); border-radius: 8px;
  background: rgba(108,99,255,0.14); color: #b8b3ff; font: inherit; font-size: 11.5px; font-weight: 700;
  padding: 6px 8px; cursor: pointer; }
.draft-card-use:hover { background: #6C63FF; color: #fff; }

/* Owner 2026-10-11: search + the question box + the Need help button live inside the
   "Welcome back" banner, flush right; the bell keeps the far edge. */
.banner-tools { display: flex; align-items: center; gap: 8px; margin-left: auto; flex-wrap: wrap; justify-content: flex-end; }
