/* ===========================================
   LIGHT THEME — COMPONENT OVERRIDES
   ===========================================

   Everything the original css/theme-light.css never covered, because it was
   written before these components existed. Two rules for this file:

   1. NO `!important`. It is the LAST stylesheet on the page (index.html), so a
      `.light-theme .foo` rule already outranks the `.foo` rule it overrides on
      order alone. `js/__tests__/theme-light-components.test.js` fails the build
      if an `!important` (or a dark background hex) creeps back in.
   2. Dark theme is never touched. Every selector is scoped to `.light-theme`,
      so `origin/main`'s dark computed styles are byte-for-byte unchanged —
      `js/e2e/light-theme-batch1.spec.js` proves that on every surface here.

   Scope so far: BATCH 1 — the Generate (image) tab and the Video tab.
   BATCH 2 — Creator Studio (Pages) and Photo Set Studio. BATCH 3 — the Fanvue
   Vault panel and post composer. BATCH 4 — the Edit page's image tools and the
   Clip Editor. BATCH 5 — the global layer (grey tokens, the blanket element
   rules, modal scrims, toasts and the status one-offs), measured on Library,
   Marketplace, Learn, Caption, Account, the mobile nav and the six modals.
   Desktop and mobile throughout.
   Later batches append their own sections.

   Palette conventions come from css/send-to-menu.css (the macOS/Notion menu
   look shipped in PRs #248-#250): white or near-white panels, hairline borders
   rather than heavy strokes, a neutral (not pink) hover, near-black primary
   text and a mid-grey secondary that still clears 4.5:1 on white.
   =========================================== */

.light-theme {
  /* Surfaces */
  --lt-panel: #ffffff;
  --lt-panel-sunken: #f5f5f7;
  --lt-hairline: rgba(0, 0, 0, 0.1);
  --lt-hairline-strong: rgba(0, 0, 0, 0.16);
  --lt-hover: rgba(0, 0, 0, 0.06);

  /* Text. --lt-text-secondary is 8.6:1 on white and 7.2:1 on --bg-tertiary;
     the palette's own --text-secondary (#6e6e73) only reaches 4.15:1 there,
     which is why muted labels on these surfaces name this token instead. */
  --lt-text: #1d1d1f;
  --lt-text-secondary: #4a4a4f;
  --lt-text-placeholder: #6b6b70;

  /* Accents. The dark theme's brand gradient (#ff2ebb -> #00b2ff) carries
     white labels at only 2.4:1 at its blue end once the page around it is
     white. This is the same gradient walked into the shade: >= 5.7:1 for white
     text at BOTH ends, and still unmistakably the Vixxxen magenta-to-blue. */
  --lt-accent-gradient: linear-gradient(135deg, #c2117f, #0069a8);
  --lt-accent-gradient-3: linear-gradient(135deg, #c2117f 0%, #6b31d6 48%, #0069a8 100%);
  --lt-accent-pink: #b3106f;
  --lt-accent-blue: #0069a8;
  --lt-accent-tint: rgba(233, 30, 156, 0.08);
  --lt-accent-tint-border: rgba(233, 30, 156, 0.45);

  /* Elevation: light UIs read depth from a soft shadow, not from a darker fill. */
  --lt-shadow-panel: 0 24px 60px rgba(0, 0, 0, 0.12), 0 6px 18px rgba(0, 0, 0, 0.07);
  --lt-shadow-menu: 0 16px 36px rgba(0, 0, 0, 0.14);
  --lt-shadow-card: 0 20px 38px rgba(0, 0, 0, 0.1);
}

/* ===========================================
   1. APP SHELL — navigation labels
   =========================================== */

/* .nav-tab is painted #8e8e93 !important by theme-light.css and .nav-label
   inherits it, giving 3.17:1. An explicit colour on the label itself beats
   inheritance without needing !important. The active tab keeps its own colour. */
.light-theme .nav-tab:not(.active) .nav-label,
.light-theme .nav-tab:not(.active) .nav-label span {
  color: var(--lt-text-secondary);
}

body.light-theme.vix-app-shell-v2 .vix-nav-section-label {
  color: var(--lt-text-secondary);
}

/* Mobile drawer: header and footer are near-black panels from css/app-shell.css.
   body.light-theme.vix-app-shell-v2 outranks that file's body.vix-app-shell-v2. */
body.light-theme.vix-app-shell-v2 .vix-drawer-header {
  background: var(--lt-panel);
  border-bottom-color: var(--lt-hairline);
}

body.light-theme.vix-app-shell-v2 .vix-drawer-close {
  background: var(--lt-panel-sunken);
  border-color: var(--lt-hairline);
  color: var(--lt-text);
}

body.light-theme.vix-app-shell-v2 .user-area {
  background: var(--lt-panel);
  border-top-color: var(--lt-hairline);
}

/* ===========================================
   2. IMAGE COMMAND BAR  (#imageCommandBar)
   The always-on composer on the default landing tab: 139k px2 on desktop and
   roughly 40% of a phone screen, hard-coded to
   linear-gradient(135deg, rgba(29,31,35,.95), rgba(17,19,23,.96)).
   =========================================== */

.light-theme #imageCommandBar.image-command-bar {
  border-color: var(--lt-hairline);
  background:
    radial-gradient(circle at 10% 20%, rgba(233, 30, 156, 0.05), transparent 30%),
    radial-gradient(circle at 86% 10%, rgba(0, 122, 255, 0.05), transparent 32%),
    linear-gradient(135deg, #ffffff, #f7f7f9);
  box-shadow: var(--lt-shadow-panel), 0 0 0 1px rgba(0, 0, 0, 0.02) inset;
}

/* Controls inside the bar are all rgba(255,255,255,0.03) fills — invisible
   once the bar is white — so they need a real sunken fill and a hairline. */
.light-theme #imageCommandBar .image-command-icon-btn,
.light-theme #imageCommandBar .image-command-more-btn,
.light-theme #imageCommandBar .image-command-stepper-btn,
.light-theme #imageCommandBar .image-command-select,
.light-theme #imageCommandBar .image-command-toggle,
.light-theme #imageCommandBar .image-command-mode-toggle,
.light-theme #imageCommandBar .image-command-count-control,
.light-theme #imageCommandBar .image-command-ref-add,
.light-theme #imageCommandBar .prompt-tool {
  border-color: var(--lt-hairline);
  background-color: var(--lt-panel-sunken);
  color: var(--lt-text);
}

.light-theme #imageCommandBar .image-command-icon-btn:hover:not(:disabled),
.light-theme #imageCommandBar .image-command-more-btn:hover:not(:disabled),
.light-theme #imageCommandBar .image-command-stepper-btn:hover:not(:disabled),
.light-theme #imageCommandBar .image-command-ref-add:hover:not(:disabled),
.light-theme #imageCommandBar .prompt-tool:hover {
  border-color: var(--lt-accent-tint-border);
  background-color: var(--lt-accent-tint);
  color: var(--lt-text);
  box-shadow: 0 10px 24px rgba(233, 30, 156, 0.12);
}

.light-theme #imageCommandBar .image-command-icon-btn.is-analyzing {
  border-color: var(--lt-accent-tint-border);
  background-color: rgba(233, 30, 156, 0.1);
  color: var(--lt-accent-pink);
}

.light-theme #imageCommandBar .image-command-icon-btn.is-analyzing::before {
  border-color: rgba(233, 30, 156, 0.3);
  border-top-color: var(--lt-accent-pink);
}

.light-theme #imageCommandBar .image-command-prompt {
  color: var(--lt-text);
}

.light-theme #imageCommandBar .image-command-prompt::placeholder {
  color: var(--lt-text-placeholder);
}

.light-theme #imageCommandBar .image-command-prompt:focus-visible {
  background: linear-gradient(90deg, rgba(233, 30, 156, 0.22), rgba(0, 122, 255, 0.14))
    bottom / 100% 2px no-repeat;
}

.light-theme #imageCommandBar .image-command-count-value {
  color: var(--lt-text);
}

.light-theme #imageCommandBar .image-command-status {
  color: var(--lt-text-secondary);
}

/* The horizontal scroll hint at the end of the control row is a dark fade. */
.light-theme #imageCommandBar .image-command-controls::after {
  box-shadow: -18px 0 14px -6px rgba(255, 255, 255, 0.92);
}

.light-theme #imageCommandBar .image-command-mode-btn {
  color: var(--lt-text-secondary);
}

.light-theme #imageCommandBar .image-command-mode-btn.active {
  color: #ffffff;
  background: var(--lt-accent-gradient);
  box-shadow: 0 8px 18px rgba(194, 17, 127, 0.18);
}

.light-theme #imageCommandBar .image-command-generate-btn {
  background: var(--lt-accent-gradient-3);
  box-shadow: 0 12px 26px rgba(194, 17, 127, 0.2), 0 8px 20px rgba(0, 105, 168, 0.12);
}

/* First-run "choose your character" strip inside the bar. */
.light-theme #imageCommandBar .image-command-first-run-title,
.light-theme #imageCommandBar .image-command-first-run-character-name {
  color: var(--lt-text);
}

.light-theme #imageCommandBar .image-command-first-run-subtitle,
.light-theme #imageCommandBar .image-command-first-run-credit {
  color: var(--lt-text-secondary);
}

.light-theme #imageCommandBar .image-command-first-run-exit,
.light-theme #imageCommandBar .image-command-first-run-character-media {
  border-color: var(--lt-hairline);
  background-color: var(--lt-panel-sunken);
  color: var(--lt-text);
}

/* ===========================================
   3. MODEL TABS AND MODEL PICKERS
   Shared by the Image tab, the compact picker in the command bar and the
   Video rail — the same defect class css/lora-combobox.css fixed for the
   character combobox in PR #252.
   =========================================== */

/* Selected states keep a gradient, but the deeper one, so white labels pass. */
.light-theme .model-tab.active,
.light-theme .model-picker-current,
.light-theme .model-picker-option.active {
  background: var(--lt-accent-gradient);
  border-color: transparent;
  box-shadow: 0 4px 12px rgba(194, 17, 127, 0.22);
}

/* The labels are <span>s, which theme-light.css paints #1d1d1f (0-1-1). These
   are 0-3-0, so they win without !important — same trick as PR #252.
   .model-tab-icon is listed with every label it sits beside: it is a <span>
   too, so `.light-theme span` reaches it directly and it would NOT follow the
   label by inheritance. js/e2e/tab-and-toolbar-icons.spec.js enforces that the
   glyph and its label always resolve to the same colour. */
.light-theme .model-tab.active .model-tab-label,
.light-theme .model-tab.active .model-tab-title,
.light-theme .model-tab.active .model-tab-icon,
.light-theme .model-picker-current .model-picker-current-label,
.light-theme .model-picker-current .model-tab-title,
.light-theme .model-picker-current .model-tab-icon,
.light-theme .model-picker-current .model-picker-caret,
.light-theme .model-picker-option.active .model-tab-title,
.light-theme .model-picker-option.active .model-tab-icon {
  color: #ffffff;
}

.light-theme .model-tab:not(.active) .model-tab-title,
.light-theme .model-tab:not(.active) .model-tab-icon,
.light-theme .model-picker-option:not(.active) .model-tab-title,
.light-theme .model-picker-option:not(.active) .model-tab-icon {
  color: var(--lt-text);
}

.light-theme .model-picker-menu {
  background: var(--lt-panel);
  border-color: var(--lt-hairline);
  box-shadow: var(--lt-shadow-menu);
}

.light-theme .model-picker-option {
  background: var(--lt-panel-sunken);
  border-color: var(--lt-hairline);
  color: var(--lt-text);
}

.light-theme .model-picker-option:hover:not(.active) {
  background: var(--lt-hover);
  border-color: var(--lt-hairline-strong);
  color: var(--lt-text);
}

/* The compact picker inside the command bar restyles its own options. */
.light-theme .image-command-model-menu .model-picker-option {
  background: var(--lt-panel-sunken);
  color: var(--lt-text);
}

.light-theme .image-command-model-menu .model-picker-option:hover:not(.active) {
  border-color: var(--lt-accent-tint-border);
  background: var(--lt-accent-tint);
  color: var(--lt-text);
}

.light-theme .image-command-model-menu .model-picker-option.active {
  border-color: transparent;
  background: var(--lt-accent-gradient);
  color: #ffffff;
  box-shadow: 0 10px 22px rgba(194, 17, 127, 0.16);
}

/* Capability badges: #ff9bdd / #8fdcff are dark-theme pastels and vanish on a
   light chip. Deep ink on a faint tint of the same hue instead. */
.light-theme .model-capability-badge.nsfw {
  color: #a3106e;
  background: rgba(233, 30, 156, 0.12);
  border-color: rgba(233, 30, 156, 0.38);
}

.light-theme .model-capability-badge.safe {
  color: #0b5d86;
  background: rgba(0, 122, 255, 0.1);
  border-color: rgba(0, 122, 255, 0.34);
}

/* On a selected (gradient) tab the badge sits on the accent, so it keeps the
   dark-on-light inversion the dark theme already uses. */
.light-theme .model-tab.active .model-capability-badge,
.light-theme .model-picker-option.active .model-capability-badge,
.light-theme .model-picker-current .model-capability-badge {
  color: #ffffff;
  background: rgba(0, 0, 0, 0.28);
  border-color: rgba(255, 255, 255, 0.6);
}

/* The circular "i" affordances: .model-info-icon in the tabs, and the one
   js/utils.js injects into upload dropzones (a runtime <style>, so this needs
   the extra class of .light-theme to outrank it — which it has). */
.light-theme .model-info-icon,
.light-theme .vix-upload-info-icon {
  border-color: rgba(0, 105, 168, 0.45);
  background: #eaf4fb;
  color: #0b4a70;
  box-shadow: none;
}

.light-theme .model-info-icon:hover,
.light-theme .model-info-icon:focus-visible,
.light-theme .vix-upload-info-icon:hover,
.light-theme .vix-upload-info-icon:focus-visible {
  border-color: var(--lt-accent-tint-border);
  color: #7a0d51;
  box-shadow: 0 0 12px rgba(233, 30, 156, 0.22);
}

.light-theme .model-picker-caret {
  color: var(--lt-text-secondary);
}

/* Two triggers deliberately opt OUT of the accent gradient and stay quiet
   chips: the compact one in the command bar and the one in the video rail.
   Their own stylesheets outrank the shared .model-picker-current rule, so the
   light versions match that specificity and re-ink their labels and badges. */
.light-theme #imageCommandBar .image-command-model-picker > .model-picker-current,
.light-theme #videoSection .video-model-rail .model-picker > .model-picker-current {
  border-color: var(--lt-hairline);
  background:
    linear-gradient(135deg, rgba(233, 30, 156, 0.08), rgba(0, 122, 255, 0.06)),
    var(--lt-panel-sunken);
  box-shadow: none;
}

.light-theme #imageCommandBar .image-command-model-picker.open > .model-picker-current,
.light-theme #imageCommandBar .image-command-model-picker > .model-picker-current:hover,
.light-theme #videoSection .video-model-rail .model-picker > .model-picker-current:hover {
  border-color: var(--lt-accent-tint-border);
  background:
    linear-gradient(135deg, rgba(233, 30, 156, 0.14), rgba(0, 122, 255, 0.1)),
    var(--lt-panel-sunken);
  box-shadow: 0 0 0 1px rgba(233, 30, 156, 0.16), 0 10px 24px rgba(233, 30, 156, 0.12);
}

/* Scoped THROUGH .model-picker-current on purpose: the dropdown panel is a DOM
   descendant of .image-command-model-picker, so a rule that stopped at the
   picker would also re-ink the selected option inside the open menu — which
   does keep the accent gradient and white labels. */
.light-theme #imageCommandBar .image-command-model-picker .model-picker-current .model-picker-current-label,
.light-theme #imageCommandBar .image-command-model-picker .model-picker-current .model-tab-title,
.light-theme #imageCommandBar .image-command-model-picker .model-picker-current .model-tab-icon,
.light-theme #imageCommandBar .image-command-model-picker .model-picker-current .model-picker-caret,
.light-theme #videoSection .video-model-rail .model-picker-current .model-picker-current-label,
.light-theme #videoSection .video-model-rail .model-picker-current .model-tab-title,
.light-theme #videoSection .video-model-rail .model-picker-current .model-tab-icon,
.light-theme #videoSection .video-model-rail .model-picker-current .model-picker-caret {
  color: var(--lt-text);
}

.light-theme #imageCommandBar .image-command-model-picker .model-picker-current .model-capability-badge.nsfw,
.light-theme #videoSection .video-model-rail .model-picker-current .model-capability-badge.nsfw {
  color: #a3106e;
  background: rgba(233, 30, 156, 0.12);
  border-color: rgba(233, 30, 156, 0.38);
}

.light-theme #imageCommandBar .image-command-model-picker .model-picker-current .model-capability-badge.safe,
.light-theme #videoSection .video-model-rail .model-picker-current .model-capability-badge.safe {
  color: #0b5d86;
  background: rgba(0, 122, 255, 0.1);
  border-color: rgba(0, 122, 255, 0.34);
}

/* The full-width Generate button shared by the Image advanced panel and the
   Video tab. Its label lives in a <span>, which theme-light.css paints
   near-black — 4.23:1 on the accent, and unreadable as a call to action. */
.light-theme .generate-btn {
  background: var(--lt-accent-gradient-3);
}

.light-theme .generate-btn,
.light-theme .generate-btn span {
  color: #ffffff;
}

.light-theme .generate-btn:hover:not(:disabled) {
  box-shadow: 0 8px 24px rgba(194, 17, 127, 0.3);
}

.light-theme .generate-btn:disabled {
  background: var(--lt-panel-sunken);
}

.light-theme .generate-btn:disabled,
.light-theme .generate-btn:disabled span {
  color: var(--lt-text-secondary);
}

/* ===========================================
   4. OUTPUT AREA (empty state, headers, cards)
   =========================================== */

/* The three tilted "photo frames" of the empty canvas. They are the artwork of
   the empty state, so they keep their shape, tilt and brand wash — they just
   become light cards lifted off the page by a shadow instead of black holes. */
.light-theme .output-empty-frame {
  border-color: var(--lt-hairline);
  background:
    linear-gradient(150deg, rgba(255, 255, 255, 0.85), transparent 36%),
    linear-gradient(160deg, #ffffff 0%, #ecebf1 72%);
  box-shadow: var(--lt-shadow-card);
}

.light-theme .output-empty-frame-left {
  background:
    radial-gradient(circle at 35% 30%, rgba(233, 30, 156, 0.16), transparent 35%),
    linear-gradient(160deg, #fdf4f9, #efedf3 72%);
}

.light-theme .output-empty-frame-right {
  background:
    radial-gradient(circle at 66% 35%, rgba(0, 122, 255, 0.15), transparent 38%),
    linear-gradient(200deg, #f1f6fb, #ecedf3 72%);
}

.light-theme .output-empty-frame-main {
  border-color: var(--lt-hairline-strong);
  background:
    radial-gradient(circle at 50% 45%, rgba(127, 88, 255, 0.12), transparent 42%),
    linear-gradient(165deg, #ffffff, #eeecf4 76%);
  box-shadow:
    0 0 0 1px rgba(233, 30, 156, 0.08),
    0 22px 46px rgba(0, 0, 0, 0.12);
}

.light-theme .output-empty-frame-meta::before,
.light-theme .output-empty-frame-meta::after {
  background: rgba(0, 0, 0, 0.14);
}

.light-theme .output-empty-frame-meta::after {
  background: linear-gradient(90deg, rgba(233, 30, 156, 0.6), rgba(0, 122, 255, 0.45));
}

.light-theme .output-empty-spark-one {
  color: var(--lt-accent-pink);
  text-shadow: none;
}

.light-theme .output-empty-spark-two {
  color: var(--lt-accent-blue);
  text-shadow: none;
}

.light-theme .output-placeholder-kicker {
  color: var(--lt-text-secondary);
}

.light-theme .output-placeholder-title {
  color: var(--lt-text);
}

.light-theme .output-placeholder-text,
.light-theme .output-placeholder-image .output-placeholder-text,
.light-theme .output-placeholder-hint,
.light-theme .output-placeholder-icon {
  color: var(--lt-text-secondary);
}

.light-theme .selection-mode-toggle {
  background: var(--lt-panel-sunken);
  border-color: var(--lt-hairline);
  color: var(--lt-text-secondary);
}

.light-theme .selection-mode-toggle:hover {
  background: var(--lt-hover);
  color: var(--lt-text);
}

.light-theme .selection-mode-toggle.active {
  background: rgba(122, 72, 196, 0.14);
  border-color: rgba(122, 72, 196, 0.4);
  color: #5b2ea6;
}

/* Result-card chrome. .library-item-overlay stays a dark scrim on purpose — it
   is painted over an arbitrary photo — so its label has to stay white, which
   .light-theme span would otherwise repaint near-black (1.25:1 in the audit). */
.light-theme .library-item-overlay-meta,
.light-theme .library-item-overlay-title {
  color: #ffffff;
}

.light-theme .output-actions .output-action-btn,
.light-theme .output-item .output-action-btn {
  border-color: var(--lt-hairline);
  background-color: rgba(255, 255, 255, 0.92);
  color: var(--lt-text);
}

/* ===========================================
   5. VIDEO WORKSPACE
   The 201k px2 model rail (css/video-workspace.css), its picker, the media
   tray and the shared section labels around the stage.
   =========================================== */

.light-theme .video-model-rail {
  background:
    radial-gradient(circle at 15% 0%, rgba(233, 30, 156, 0.07), transparent 34%),
    linear-gradient(180deg, #ffffff, #f5f5f7 72%);
  border-right-color: var(--lt-hairline);
}

.light-theme .video-model-rail-kicker,
.light-theme .video-media-tray-header span,
.light-theme .video-model-summary > span {
  color: var(--lt-accent-pink);
}

.light-theme .video-model-rail-heading h2,
.light-theme .video-model-summary h3 {
  color: var(--lt-text);
}

.light-theme .video-model-rail-heading p,
.light-theme .video-model-summary p {
  color: var(--lt-text-secondary);
}

.light-theme .video-library-toggle {
  background: linear-gradient(120deg, rgba(233, 30, 156, 0.07), rgba(0, 122, 255, 0.06));
  border-color: var(--lt-hairline);
  color: var(--lt-text);
}

.light-theme .video-library-toggle > span:first-child {
  color: var(--lt-accent-pink);
}

.light-theme .video-library-toggle strong {
  color: var(--lt-text);
}

/* Reachable only because css/theme-light.css no longer marks its blanket
   `small` colour !important — see the comment on that rule. */
.light-theme .video-library-toggle small,
.light-theme .video-media-tray-header small,
.light-theme #imageCommandBar small {
  color: var(--lt-text-secondary);
}

.light-theme .video-library-toggle-caret {
  color: var(--lt-accent-blue);
}

.light-theme #videoSection .video-media-tray {
  background: var(--lt-panel);
  border-left-color: var(--lt-hairline);
}

.light-theme .video-media-tray-header strong {
  color: var(--lt-text);
}

.light-theme .video-media-tray-header button {
  background: var(--lt-panel-sunken);
  border-color: var(--lt-hairline);
  color: var(--lt-text-secondary);
}

.light-theme .video-media-tray-tabs {
  background: rgba(0, 0, 0, 0.04);
  border-color: var(--lt-hairline);
}

.light-theme .video-media-tray-tabs button {
  color: var(--lt-text-secondary);
}

.light-theme .video-media-tray-tabs button.active {
  color: #ffffff;
  background: var(--lt-accent-gradient);
}

/* The dark half of the Extended-video action pair
   (linear-gradient(135deg,#3a3a3a,#222)); the primary purple button already
   clears 5.7:1 for white text on a light page, so it is left alone. */
.light-theme .extended-action-btn.extended-action-btn-alt {
  background: var(--lt-panel-sunken);
  border-color: var(--lt-hairline);
  color: var(--lt-text);
}

.light-theme .extended-action-btn.extended-action-btn-alt:hover:not(:disabled) {
  border-color: var(--lt-accent-tint-border);
  background: var(--lt-accent-tint);
}

.light-theme .extended-studio-card-label,
.light-theme .extended-studio-subtitle,
.light-theme .extended-credit-hint,
.light-theme .palette-sub-label,
.light-theme .empty-hint,
.light-theme .minimax-h3-audio-note,
.light-theme .minimax-h3-timeline-help {
  color: var(--lt-text-secondary);
}

/* ===========================================
   6. SHARED FORM / SECTION LABELS on these surfaces
   #6e6e73 on --bg-tertiary is 4.15:1 and #8e8e93 is 2.67:1; both fail. These
   are colour-only swaps to the file's secondary ink.
   =========================================== */

.light-theme .section-label,
.light-theme .reference-upload-hint,
.light-theme .reference-upload-text,
.light-theme .vixn2-modifier-group-title,
.light-theme details.advanced-settings > summary {
  color: var(--lt-text-secondary);
}

/* ===========================================
   7. CREATOR STUDIO  (the Pages tab)  — BATCH 2

   css/creator-studio.css is entirely token-driven, so the whole surface
   turns over from this one block: `div.creator-studio` was 1,139,400 px2 of
   rgb(14,11,19) — the largest dark island in the app — with its own <h1>
   painted #1d1d1f on top of it at 1.16:1 by theme-light.css's blanket
   heading rule.

   The five accidental red variants the dark theme grew (--studio-danger-*,
   --studio-danger-btn-*, --studio-error-ink, --studio-warning-*) all land on
   one red here. They stay separate tokens in creator-studio.css only because
   collapsing them there would move the dark theme.
   =========================================== */

.light-theme .creator-studio,
.light-theme .studio-modal {
  --studio-accent: var(--lt-accent-pink);
  --studio-accent-2: var(--lt-accent-blue);

  /* Surfaces. The studio reads its depth from shadow and hairlines now, not
     from progressively darker fills. */
  --studio-canvas: var(--lt-panel-sunken);
  --studio-surface: var(--lt-panel);
  --studio-surface-fade: rgba(255, 255, 255, 0.94);
  --studio-panel: var(--lt-panel);
  --studio-panel-soft: var(--lt-panel-sunken);
  --studio-inset-bg: var(--lt-panel-sunken);
  --studio-chip-bg: var(--lt-panel-sunken);
  --studio-scrim: rgba(28, 24, 32, 0.55);
  --studio-image-wash: linear-gradient(135deg, rgba(179, 16, 111, 0.1), rgba(0, 105, 168, 0.07));

  --studio-line: var(--lt-hairline);
  --studio-inset-line: var(--lt-hairline);
  --studio-inset-line-soft: var(--lt-hairline);

  --studio-ink: var(--lt-text);
  --studio-muted: var(--lt-text-secondary);
  /* 6.6:1 on white and 5.9:1 on the sunken panel; the dark theme's #ed9cff is
     1.3:1 on either. */
  --studio-eyebrow-ink: #8b2fb8;
  --studio-placeholder-ink: var(--lt-text-secondary);
  --studio-on-accent: #ffffff;

  /* State inks, each >= 5.6:1 on its own tinted chip. */
  --studio-ok-ink: #0d6b45;
  --studio-ok-line: rgba(13, 107, 69, 0.34);
  --studio-ok-bg: rgba(13, 107, 69, 0.09);
  --studio-success-ink: #0d6b45;
  --studio-warn-ink: #7a4b00;
  --studio-warn-line: rgba(122, 75, 0, 0.34);
  --studio-warn-bg: rgba(214, 150, 0, 0.14);
  --studio-danger-ink: #b3123a;
  --studio-danger-line: rgba(179, 18, 58, 0.32);
  --studio-danger-bg: rgba(179, 18, 58, 0.07);
  --studio-danger-btn-ink: #b3123a;
  --studio-danger-btn-line: rgba(179, 18, 58, 0.32);
  --studio-danger-btn-bg: rgba(179, 18, 58, 0.07);
  --studio-error-ink: #b3123a;
  --studio-warning-ink: #b3123a;
  --studio-warning-line: rgba(179, 18, 58, 0.28);
  --studio-warning-bg: rgba(179, 18, 58, 0.07);

  --studio-tab-active-bg: var(--lt-accent-gradient);
  --studio-review-line: var(--lt-accent-tint-border);
  --studio-review-wash: var(--lt-accent-tint);
  --studio-code-bg: var(--lt-panel-sunken);
  --studio-code-ink: var(--lt-text);
  /* The status pill and the selection badges sit ON a photograph, so they keep
     a backdrop of their own — a light one here, which also keeps the pill's
     state colours (green / amber / red) legible on it. */
  --studio-photo-chip-bg: rgba(255, 255, 255, 0.88);
  --studio-photo-badge-bg: rgba(29, 29, 31, 0.72);
  --studio-thumb-badge-bg: rgba(29, 29, 31, 0.72);
  --studio-spinner-track: rgba(0, 0, 0, 0.12);
  --studio-shimmer: rgba(0, 0, 0, 0.06);
  --studio-focus-ring: var(--lt-text);

  --studio-shadow-button: 0 10px 24px rgba(179, 16, 111, 0.18);
  --studio-shadow-card: var(--lt-shadow-card);
  --studio-shadow-panel: var(--lt-shadow-card);
  --studio-shadow-modal: var(--lt-shadow-panel);
}

/* ===========================================
   8. PHOTO SET STUDIO  (inside the Library tab)  — BATCH 2

   Never mounted in the audit: the studio needs an authenticated
   /api/photo-sets response before it paints anything, so its ~60 hard-coded
   near-black literals were invisible to the sweep. Every surface it owns —
   the set cards, the set summary, the slots, the candidate tiles, the prompt
   editor — was a dark island on a white page.
   =========================================== */

.light-theme .photo-set-studio {
  /* Surfaces */
  --ps-canvas: var(--lt-panel-sunken);
  --ps-wash: linear-gradient(180deg, rgba(179, 16, 111, 0.05), transparent 260px);
  --ps-card-bg: var(--lt-panel);
  --ps-preview-bg: var(--lt-panel-sunken);
  --ps-summary-bg: var(--lt-panel);
  --ps-continuity-bg: var(--lt-panel-sunken);
  --ps-slot-bg: var(--lt-panel);
  --ps-candidate-bg: var(--lt-panel-sunken);
  --ps-pending-bg: var(--lt-panel-sunken);
  --ps-empty-bg: var(--lt-panel);
  --ps-excluded-bg: var(--lt-panel-sunken);
  --ps-prompt-bg: var(--lt-panel-sunken);
  --ps-input-bg: var(--lt-panel);
  --ps-progress-track: rgba(0, 0, 0, 0.1);
  --ps-progress-fill: var(--lt-accent-gradient);

  /* Lines */
  --ps-card-line: var(--lt-hairline);
  --ps-card-line-hover: var(--lt-accent-tint-border);
  --ps-summary-line: var(--lt-hairline);
  --ps-continuity-line: var(--lt-hairline);
  --ps-slot-line: var(--lt-hairline);
  --ps-slot-line-ready: var(--lt-hairline-strong);
  --ps-dashed-line: var(--lt-hairline-strong);
  --ps-pending-line: var(--lt-hairline-strong);
  --ps-excluded-line: var(--lt-hairline-strong);
  --ps-prompt-line: var(--lt-hairline);
  --ps-textarea-line: var(--lt-hairline-strong);
  --ps-input-line: var(--lt-accent-tint-border);

  /* Ink. The dark theme carried four near-identical muted greys
     (#b9adbf / #b8aabd / #a99cad / #988c9d); all four land on one value that
     clears 4.5:1 on both the white panel and the sunken one. */
  --ps-ink: var(--lt-text);
  --ps-ink-muted: var(--lt-text-secondary);
  --ps-ink-muted-alt: var(--lt-text-secondary);
  --ps-ink-faint: var(--lt-text-secondary);
  --ps-ink-quiet: var(--lt-text-secondary);
  --ps-eyebrow-ink: #8b2fb8;
  --ps-card-body-ink: var(--lt-text-secondary);
  --ps-meta-ink: var(--lt-text-secondary);
  --ps-continuity-label-ink: #8b2fb8;
  --ps-preview-empty-ink: var(--lt-text-secondary);
  --ps-preview-empty-glyph: #8b2fb8;
  --ps-pending-ink: var(--lt-text-secondary);
  --ps-error-ink: #b3123a;
  /* White stays white: it labels the accent-filled primary button, and
     --lt-accent-gradient carries white at >= 5.7:1 at both ends. */
  --ps-on-accent: #ffffff;

  /* Buttons */
  --ps-btn-bg: var(--lt-panel);
  --ps-btn-bg-hover: var(--lt-hover);
  --ps-btn-line: var(--lt-hairline-strong);
  --ps-btn-line-hover: var(--lt-accent-tint-border);
  --ps-btn-ink: var(--lt-text);
  --ps-btn-primary-bg: var(--lt-accent-gradient);
  --ps-mini-btn-bg: var(--lt-panel);
  --ps-mini-btn-bg-hover: var(--lt-hover);
  --ps-mini-btn-line: var(--lt-hairline-strong);
  --ps-mini-btn-ink: var(--lt-text-secondary);
  --ps-mini-btn-ink-hover: var(--lt-text);
  --ps-submit-bg: var(--lt-accent-pink);
  --ps-submit-line: var(--lt-accent-pink);
  --ps-submit-ink: #ffffff;

  /* Status pills */
  --ps-chip-bg: var(--lt-panel-sunken);
  --ps-chip-line: var(--lt-hairline-strong);
  --ps-chip-ink: #8b2fb8;
  --ps-ok-bg: rgba(13, 107, 69, 0.09);
  --ps-ok-line: rgba(13, 107, 69, 0.34);
  --ps-ok-ink: #0d6b45;
  --ps-warn-bg: rgba(214, 150, 0, 0.14);
  --ps-warn-line: rgba(122, 75, 0, 0.34);
  --ps-warn-ink: #7a4b00;
  --ps-pulse: var(--lt-accent-pink);

  /* Destructive / restorative / Fanvue actions */
  --ps-remove-bg: rgba(179, 18, 58, 0.07);
  --ps-remove-bg-hover: rgba(179, 18, 58, 0.13);
  --ps-remove-line: rgba(179, 18, 58, 0.32);
  --ps-remove-line-hover: rgba(179, 18, 58, 0.5);
  --ps-remove-ink: #b3123a;
  --ps-restore-bg: rgba(13, 107, 69, 0.09);
  --ps-restore-line: rgba(13, 107, 69, 0.34);
  --ps-restore-ink: #0d6b45;
  --ps-fanvue-bg: rgba(13, 107, 69, 0.09);
  --ps-fanvue-bg-hover: rgba(13, 107, 69, 0.16);
  --ps-fanvue-line: rgba(13, 107, 69, 0.34);
  --ps-fanvue-line-hover: rgba(13, 107, 69, 0.55);
  --ps-fanvue-ink: #0d6b45;
  --ps-fanvue-ink-hover: #0a5334;

  /* Slots and candidates. The badge over a thumbnail goes light-on-photo; the
     select toggle stays a dark glass button, because its CHECKED state is the
     accent fill and only white ink clears 4.5:1 on that. */
  --ps-slot-number-bg: rgba(179, 16, 111, 0.1);
  --ps-slot-number-ink: #8b2fb8;
  --ps-excluded-number-bg: var(--lt-panel-sunken);
  --ps-excluded-number-ink: var(--lt-text-secondary);
  --ps-candidate-line-selected: var(--lt-accent-pink);
  --ps-candidate-badge-bg: rgba(255, 255, 255, 0.9);
  --ps-candidate-badge-ink: var(--lt-text);
  --ps-candidate-check-bg: rgba(29, 29, 31, 0.72);
  --ps-candidate-check-bg-on: var(--lt-accent-pink);
  --ps-candidate-check-ink: #ffffff;
  --ps-spinner-track: rgba(0, 0, 0, 0.12);
  --ps-spinner-head: var(--lt-accent-pink);

  /* Prompt editor */
  --ps-prompt-title-ink: var(--lt-text);
  --ps-prompt-count-ink: var(--lt-text-secondary);
  --ps-prompt-help-ink: var(--lt-text-secondary);
  --ps-textarea-ink: var(--lt-text);
  --ps-textarea-focus-line: var(--lt-accent-blue);

  /* Elevation and focus */
  --ps-shadow-card: var(--lt-shadow-card);
  --ps-shadow-card-hover: var(--lt-shadow-panel);
  --ps-shadow-primary: 0 10px 24px rgba(179, 16, 111, 0.18);
  --ps-shadow-preview-chip: 0 4px 14px rgba(0, 0, 0, 0.12);
  --ps-shadow-prompt-inset: inset 0 1px 0 rgba(255, 255, 255, 0.6);
  --ps-focus-ring: var(--lt-accent-pink);
  --ps-input-glow: 0 0 0 3px rgba(179, 16, 111, 0.14);
  --ps-textarea-glow: 0 0 0 3px rgba(0, 105, 168, 0.14);
  --ps-candidate-glow: 0 0 0 3px rgba(179, 16, 111, 0.16);
}

/* ===========================================
   9. <small> ON THE STUDIO SURFACES

   The one place a token swap cannot reach. `.light-theme small { #8e8e93 }`
   in theme-light.css is (0,1,1) — exactly the specificity of the studios' own
   `.studio-field small` / `.photo-set-slot__title small` rules — and it loads
   after them, so it wins every tie and drags the slug preview, the field
   hints, the gallery image count and the slot subtitles down to 2.99-3.26:1.
   This is the re-inking hook that batch 1 opened by dropping the !important
   from that rule.
   =========================================== */

.light-theme .creator-studio small,
.light-theme .studio-modal small,
.light-theme .photo-set-studio small {
  color: var(--lt-text-secondary);
}

/* ===========================================
   BATCH 3 — FANVUE VAULT PANEL AND FANVUE POST COMPOSER

   The audit's two darkest surfaces: `div.fanvue-vault` at 527,237 px2 of
   rgb(18,12,23), `footer.fanvue-vault__foot` at 71,288 px2, and the panel
   title reading 1.14:1 because .light-theme's blanket h1-h4 rule painted
   near-black ink onto a near-black panel.

   css/fanvue-vault.css and css/fanvue-post.css shipped minified with the dark
   values inlined. They are now de-minified, and every colour they paint is a
   --fv-* / --fvp-* token declared at the overlay root. Light theme therefore
   restyles both panels by redefining tokens — no rule from either feature
   stylesheet is restated here, and no dark value is touched.
   =========================================== */

/* -------------------------------------------
   10. FANVUE VAULT PANEL — light token values
   ------------------------------------------- */

.light-theme .fanvue-vault-overlay {
  /* A modal scrim stays a scrim in light mode; it is just lighter, so the page
     behind reads as dimmed rather than extinguished. */
  --fv-scrim: rgba(28, 20, 34, 0.45);

  --fv-ink: var(--lt-text);
  --fv-ink-soft: var(--lt-text-secondary);
  --fv-ink-muted: var(--lt-text-secondary);

  --fv-panel: var(--lt-panel);
  /* The dark panel's magenta radial wash, kept as a 5% tint so the panel still
     reads as Vixxxen's rather than as a generic white sheet. */
  --fv-panel-wash: rgba(194, 17, 127, 0.05);
  --fv-panel-border: var(--lt-hairline);
  --fv-panel-shadow: var(--lt-shadow-panel);
  --fv-hairline: var(--lt-hairline);
  --fv-eyebrow: var(--lt-accent-pink);

  --fv-chip: var(--lt-panel-sunken);
  --fv-chip-border: var(--lt-hairline-strong);
  --fv-chip-hover: var(--lt-accent-tint);
  --fv-chip-hover-border: var(--lt-accent-tint-border);

  --fv-spinner-track: rgba(0, 0, 0, 0.12);
  --fv-spinner-head: var(--lt-accent-pink);

  --fv-card: var(--lt-panel-sunken);
  --fv-card-border: var(--lt-hairline);
  /* #58f280 with #0b2512 ink is a neon chip that only works on a dark panel; on
     white its initial is 3.46:1. Same green walked into the shade, white ink:
     6.1:1. */
  --fv-avatar: #0f7048;
  --fv-avatar-ink: #ffffff;
  --fv-meta: var(--lt-text-secondary);
  --fv-quiet-ink: var(--lt-text-secondary);
  --fv-quiet-ink-hover: var(--lt-text);

  --fv-notice: #fff8e6;
  --fv-notice-border: #e0b64c;
  --fv-notice-ink: #6b4a05;

  --fv-primary: var(--lt-accent-gradient);
  --fv-primary-shadow: 0 10px 24px rgba(194, 17, 127, 0.22);
  --fv-primary-ink: #ffffff;

  --fv-tile: var(--lt-panel-sunken);
  --fv-tile-border: var(--lt-hairline);
  --fv-tile-video: #e8e8ec;
  /* The status pill is painted ON the thumbnail, so it needs a ground of its
     own. A near-opaque white plate with near-black ink reads over any photo the
     way the dark plate did, and is not a hole punched in a light page. */
  --fv-pill: rgba(255, 255, 255, 0.92);
  --fv-pill-ink: var(--lt-text);
  --fv-pill-done: rgba(232, 247, 240, 0.95);
  --fv-pill-done-ink: #0b5138;

  --fv-state-uploading: var(--lt-accent-pink);
  --fv-state-processing: #6b31d6;
  --fv-state-ready: #1f8a5f;
  --fv-state-failed: #c2354a;

  --fv-label: var(--lt-text);
  /* css/theme-light.css already forces every input to #f0f0f5 / #d1d1d6 /
     #1d1d1f and every focus border to #007aff, with !important. Naming the same
     values here keeps the token honest about what the panel actually paints,
     and leaves the panel correct on its own the day that !important retires. */
  --fv-input: #f0f0f5;
  --fv-input-border: #d1d1d6;
  --fv-input-ink: var(--lt-text);
  --fv-input-disabled-ink: var(--lt-text-secondary);
  --fv-focus-border: #007aff;
  --fv-focus-ring: rgba(0, 122, 255, 0.18);
  --fv-help: var(--lt-text-secondary);

  --fv-progress: var(--lt-panel-sunken);
  --fv-progress-border: var(--lt-hairline);
  --fv-progress-meta: var(--lt-text-secondary);
  --fv-track: rgba(0, 0, 0, 0.12);
  --fv-track-fill: var(--lt-accent-gradient);
  --fv-done: #eaf7f1;
  --fv-done-border: #9fd3bd;
  --fv-done-fill: #1f8a5f;
  --fv-error-ink: #a3182f;

  --fv-foot: var(--lt-panel-sunken);
  --fv-ghost: var(--lt-panel);
  --fv-ghost-border: var(--lt-hairline-strong);
  --fv-ghost-ink: var(--lt-text);
}

/* -------------------------------------------
   11. FANVUE POST COMPOSER — light token values
   ------------------------------------------- */

.light-theme .fanvue-post-overlay {
  --fvp-scrim: rgba(28, 20, 34, 0.45);

  --fvp-ink: var(--lt-text);
  --fvp-ink-soft: var(--lt-text-secondary);
  --fvp-ink-muted: var(--lt-text-secondary);

  --fvp-panel: var(--lt-panel);
  --fvp-panel-wash: rgba(194, 17, 127, 0.05);
  --fvp-panel-border: var(--lt-hairline);
  --fvp-panel-shadow: var(--lt-shadow-panel);
  --fvp-hairline: var(--lt-hairline);
  --fvp-eyebrow: var(--lt-accent-pink);

  --fvp-chip: var(--lt-panel-sunken);
  --fvp-chip-border: var(--lt-hairline-strong);
  --fvp-chip-hover: var(--lt-accent-tint);
  --fvp-chip-hover-border: var(--lt-accent-tint-border);

  --fvp-spinner-track: rgba(0, 0, 0, 0.12);
  --fvp-spinner-head: var(--lt-accent-pink);

  --fvp-card: var(--lt-panel-sunken);
  --fvp-card-border: var(--lt-hairline);
  --fvp-avatar: #0f7048;
  --fvp-avatar-ink: #ffffff;
  --fvp-meta: var(--lt-text-secondary);

  --fvp-notice: #fff8e6;
  --fvp-notice-border: #e0b64c;
  --fvp-notice-ink: #6b4a05;
  --fvp-connect-border: var(--lt-accent-tint-border);

  --fvp-primary: var(--lt-accent-gradient);
  --fvp-primary-shadow: 0 10px 24px rgba(194, 17, 127, 0.22);
  --fvp-primary-ink: #ffffff;

  --fvp-tile: var(--lt-panel-sunken);
  --fvp-tile-border: var(--lt-hairline);

  --fvp-label: var(--lt-text);
  --fvp-help: var(--lt-text-secondary);
  --fvp-help-strong: var(--lt-text);

  --fvp-ghost: var(--lt-panel);
  --fvp-ghost-border: var(--lt-hairline-strong);
  --fvp-ghost-ink: var(--lt-text);
  --fvp-ghost-hover-border: var(--lt-accent-tint-border);

  --fvp-input: #f0f0f5;
  --fvp-input-border: #d1d1d6;
  --fvp-input-ink: var(--lt-text);
  /* The datetime-local control draws its own native calendar; color-scheme is
     what tells the engine which way round to paint it. */
  --fvp-scheme: light;
  --fvp-focus-border: #007aff;
  --fvp-focus-ring: rgba(0, 122, 255, 0.18);

  /* The selected half of Free/PPV and Now/Schedule keeps the brand gradient —
     deepened, as batch 1 did, so its white label clears 5.7:1 at both ends
     instead of 2.4:1 at the blue end. The unselected half becomes a plain white
     segment with secondary ink. */
  --fvp-segment: var(--lt-panel);
  --fvp-segment-border: var(--lt-hairline-strong);
  --fvp-segment-ink: var(--lt-text-secondary);
  --fvp-segment-active: var(--lt-accent-gradient);
  --fvp-segment-active-ink: #ffffff;

  --fvp-money-ink: var(--lt-text-secondary);
  --fvp-check-ink: var(--lt-text);
  --fvp-accent: var(--lt-accent-pink);

  --fvp-teaser: var(--lt-panel-sunken);
  --fvp-teaser-border: var(--lt-hairline);
  --fvp-slider-ink: var(--lt-text-secondary);

  --fvp-icon-btn: var(--lt-panel);
  --fvp-icon-btn-border: var(--lt-hairline-strong);
  --fvp-icon-btn-ink: var(--lt-text-secondary);
  --fvp-icon-btn-hover-border: #c2354a;
  --fvp-icon-btn-hover-ink: #a3182f;

  --fvp-hint: var(--lt-text-secondary);
  --fvp-summary: var(--lt-panel-sunken);
  --fvp-summary-border: var(--lt-hairline);
  --fvp-summary-ink: var(--lt-text);

  /* Carried over from PR #256, which shipped these three as a one-off
     .light-theme rule inside css/fanvue-post.css. Same values, now the light
     half of a token pair like everything else on this surface. */
  --fvp-error: #fdeef0;
  --fvp-error-border: #e3a2ae;
  --fvp-error-ink: #8a1f34;

  --fvp-done: #eaf7f1;
  --fvp-done-border: #9fd3bd;
  --fvp-done-meta: #0f6047;

  --fvp-foot: var(--lt-panel-sunken);
  --fvp-foot-btn: var(--lt-panel);
  --fvp-foot-btn-border: var(--lt-hairline-strong);
  --fvp-foot-btn-ink: var(--lt-text);
}

/* -------------------------------------------
   12. RE-INKING THE TEXT css/theme-light.css CLAIMS BY ELEMENT

   theme-light.css paints `.light-theme p, span, label` near-black and
   `.light-theme small` #8e8e93, and it loads BEFORE this file. Both panels'
   own rules — `.fanvue-post__field small`, `.fanvue-vault__account span` and
   the rest — carry identical (0,1,1) specificity, so they lose the tie on order
   and the token they name never reaches the page. #8e8e93 is 2.85:1 on white,
   so every <small> on these panels was unreadable no matter what the component
   asked for.

   Adding the .light-theme class to the component's own selector wins the
   cascade and still keeps the value in one place: the token.
   ------------------------------------------- */

.light-theme .fanvue-vault__eyebrow {
  color: var(--fv-eyebrow);
}

.light-theme .fanvue-vault__head p {
  color: var(--fv-ink-soft);
}

.light-theme .fanvue-vault__account span,
.light-theme .fanvue-vault__connect span,
.light-theme .fanvue-vault__notice span {
  color: var(--fv-meta);
}

.light-theme .fanvue-vault__account button {
  color: var(--fv-quiet-ink);
}

.light-theme .fanvue-vault__account button:hover {
  color: var(--fv-quiet-ink-hover);
}

.light-theme .fanvue-vault__preview span {
  color: var(--fv-pill-ink);
}

.light-theme .fanvue-vault__preview[data-state="attached"] span {
  color: var(--fv-pill-done-ink);
}

.light-theme .fanvue-vault__folder > span {
  color: var(--fv-label);
}

.light-theme .fanvue-vault__folder small {
  color: var(--fv-help);
}

.light-theme .fanvue-vault__progress span {
  color: var(--fv-progress-meta);
}

.light-theme .fanvue-vault__progress p {
  color: var(--fv-error-ink);
}

.light-theme .fanvue-post__eyebrow {
  color: var(--fvp-eyebrow);
}

.light-theme .fanvue-post__head p {
  color: var(--fvp-ink-soft);
}

.light-theme .fanvue-post__account span,
.light-theme .fanvue-post__connect span,
.light-theme .fanvue-post__notice span {
  color: var(--fvp-meta);
}

.light-theme .fanvue-post__label,
.light-theme .fanvue-post__label-row {
  color: var(--fvp-label);
}

.light-theme .fanvue-post__field small {
  color: var(--fvp-help);
}

.light-theme .fanvue-post__money > span {
  color: var(--fvp-money-ink);
}

.light-theme .fanvue-post__check {
  color: var(--fvp-check-ink);
}

.light-theme .fanvue-post__slider > span {
  color: var(--fvp-slider-ink);
}

.light-theme .fanvue-post__hint {
  color: var(--fvp-hint);
}

.light-theme .fanvue-post__summary {
  color: var(--fvp-summary-ink);
}

.light-theme .fanvue-post__error {
  color: var(--fvp-error-ink);
}

.light-theme .fanvue-post__done span {
  color: var(--fvp-done-meta);
}

/* ===========================================
   13. EDIT PAGE — image tools  (#editSection > #editImageSection)
   Batch 4. The tool rail (`.edit-toolbar`, 53k px2 on desktop and 28k on a
   phone) was a hard-coded #0d0d14 slab whose BG/CROP/BODY/BLUR/CLONE labels
   are <span>s, so `.light-theme span` painted them #1d1d1f on top of it —
   1.03:1, i.e. gone. The dock faded to #111 and the canvas overlay hints were
   near-black pills. All of those literals are `--ed-*` tokens now
   (css/edit-page.css), so this section repoints them instead of restating
   whole rules.
   =========================================== */

/* The Edit sidebar states roughly thirty of its hint and label colours as
   INLINE style="color: var(--text-muted)" / var(--text-secondary) attributes.
   An inline style outranks every stylesheet, so no rule can ever reach those
   rows — but what they name are custom properties, and those inherit. One
   repoint scoped to #editSection re-inks every one of them at once and reaches
   nothing outside the Edit page. On these surfaces #8e8e93 is 2.67:1 and
   #6e6e73 is 4.15:1; --lt-text-secondary is 8.8:1 on white and 7.2:1 on
   --bg-tertiary. */
.light-theme #editSection {
  --text-muted: var(--lt-text-secondary);
  --text-tertiary: var(--lt-text-secondary);
  --text-secondary: var(--lt-text-secondary);
  --text-light: var(--lt-text-secondary);
}

/* Chrome tokens from css/edit-page.css. --ed-stage-bg is deliberately absent:
   the editing stage stays dark in both themes — see .edit-canvas-wrapper. */
.light-theme {
  --ed-rail-bg: var(--lt-panel);
  --ed-sidebar-bg: var(--lt-panel);
  --ed-dock-bg: var(--lt-panel);
  --ed-hairline: var(--lt-hairline-strong);

  /* The hint pill floats ON the dark stage. A frosted white pill with dark
     ink is the light-theme reading of it, and it stays legible over whatever
     the canvas happens to be showing. */
  --ed-hint-bg: rgba(255, 255, 255, 0.92);
  --ed-hint-fg: var(--lt-text);

  /* Timeline: the ground a filmstrip is drawn on, the not-yet-sampled tile,
     and the recents thumbnails. Light cards, not holes. */
  --ed-block-bg: #e4e4ea;
  --ed-film-bg: #eceaf1;
  --ed-film-stripe-a: #e9e9ef;
  --ed-film-stripe-b: #dcdce4;
  --ed-thumb-bg: #e8e8ed;

  /* Chips and badges painted OVER video frames. They stay scrims — the frame
     underneath is arbitrary — but a light theme's scrim is a frosted white
     pill with dark ink, not a black one with white. */
  --ed-chip-bg: rgba(255, 255, 255, 0.9);
  --ed-chip-strong-bg: rgba(255, 255, 255, 0.94);
  --ed-chip-actions-bg: rgba(255, 255, 255, 0.92);
  --ed-badge-bg: rgba(255, 255, 255, 0.88);
  --ed-badge-strong-bg: rgba(255, 255, 255, 0.92);
}

/* --- Tool rail --------------------------------------------------------- */
.light-theme .edit-toolbar {
  border-right-color: var(--lt-hairline);
}

.light-theme .edit-tool-btn,
.light-theme .edit-tool-btn span {
  color: var(--lt-text-secondary);
}

.light-theme .edit-tool-btn:hover {
  background: var(--lt-hover);
}

.light-theme .edit-tool-btn:hover,
.light-theme .edit-tool-btn:hover span {
  color: var(--lt-text);
}

.light-theme .edit-tool-btn.active {
  background: var(--lt-accent-tint);
  border-color: var(--lt-accent-tint-border);
}

.light-theme .edit-tool-btn.active,
.light-theme .edit-tool-btn.active span {
  color: var(--lt-accent-pink);
}

/* --- Workspace, surfaces, sidebar -------------------------------------- */
.light-theme .edit-surface {
  border-color: var(--lt-hairline);
}

.light-theme .edit-sidebar,
.light-theme #editSection .right-panel {
  border-left-color: var(--lt-hairline);
}

.light-theme .edit-subtabs {
  border-bottom-color: var(--lt-hairline);
}

/* Gradient text: the dark theme's #ff2ebb -> #00b2ff averages to 3.98:1 on
   white. Same gradient, walked into the shade (8.1:1). */
.light-theme .edit-sidebar-header h2 {
  background-image: var(--lt-accent-gradient);
}

.light-theme .edit-sidebar-header .tagline,
.light-theme .edit-session-status,
.light-theme .shared-gallery-hint,
.light-theme .brush-hotkey-hint,
.light-theme #cloneSourceStatus {
  color: var(--lt-text-secondary);
}

.light-theme .edit-session-bar button {
  background: var(--lt-panel-sunken);
  color: var(--lt-text);
}

/* --- The editing stage -------------------------------------------------
   #editCanvasWrapper and the inpaint stage stay a neutral dark ground in BOTH
   themes: a mask edge, a clone seam or an exposure cannot be judged against a
   white page, which is why every photo editor keeps a dark canvas surround in
   light mode too. What light theme changes is the FRAME — a hairline and a
   soft drop shadow, so the stage reads as a deliberate, lifted surface rather
   than a hole punched in the page. The hairline is an INSET shadow and not a
   real border on purpose: .inpaint-canvas-wrapper is `inset: 0` with
   absolutely positioned canvases inside it, and adding a border would move
   them — the layout PR #253 fixed. js/e2e/light-theme-batch4.spec.js lists
   both wrappers as the only allowed dark islands. */
.light-theme .edit-canvas-wrapper,
.light-theme .inpaint-canvas-wrapper {
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.16),
    0 10px 30px rgba(0, 0, 0, 0.16);
}

/* Everything that sits ON the stage therefore keeps light ink. */
.light-theme .inpaint-upload-text {
  color: #f5f5f7;
}

.light-theme .inpaint-upload-hint,
.light-theme .inpaint-touch-hint {
  color: #c9c9d1;
}

.light-theme .inpaint-zoom-controls {
  border-color: rgba(255, 255, 255, 0.18);
}

/* --- Shared tool-panel controls ---------------------------------------- */
/* Segmented toggles (brush/eraser, VIXN model). The selected half carries the
   brand gradient, which puts white — or, once `.light-theme span` has had it,
   near-black — on a mid-tone pink: 1.27:1 to 4.23:1. The deeper gradient
   clears 5.7:1 for white at both ends. (The SFW/NSFW and bloat/pinch pairs are
   `.caption-model-tab`, whose light values live in css/theme-light.css behind
   !important; they are fixed at the source there.) */
.light-theme #editSection .brush-mode-btn.active,
.light-theme #editSection .inpaint-mode-btn.active {
  background: var(--lt-accent-gradient);
}

.light-theme #editSection .brush-mode-btn.active,
.light-theme #editSection .brush-mode-btn.active span,
.light-theme #editSection .inpaint-mode-btn.active,
.light-theme #editSection .inpaint-mode-btn.active span {
  color: #ffffff;
}

.light-theme #editSection .brush-mode-btn:not(.active) {
  color: var(--lt-text-secondary);
}

/* The phone-only paint/pan pill floats on the stage. --accent-primary is
   #e91e9c in light and carries its label at 4.11:1 once `.light-theme span`
   repaints the icon and text near-black. Deep pink, white ink. The navigate
   state keeps its own quiet chip. */
.light-theme .inpaint-touch-mode-toggle:not(.navigate-mode) {
  background: var(--lt-accent-pink);
}

.light-theme .inpaint-touch-mode-toggle:not(.navigate-mode),
.light-theme .inpaint-touch-mode-toggle:not(.navigate-mode) .toggle-icon,
.light-theme .inpaint-touch-mode-toggle:not(.navigate-mode) .toggle-label {
  color: #ffffff;
}

/* ===========================================
   14. CLIP EDITOR  (#editVideoSection)
   The timeline blocks (#111), the filmstrip placeholder hatch and the
   "continue where you left off" thumbnails (#0a0a11) were the dark islands;
   every duration, index and ruler label on top of them was near-black ink on
   near-black. The block grounds are tokens (section 7); this section fixes
   the accent, the scrims and the labels.
   =========================================== */

/* One repoint covers the play button, the selected-block ring, the back
   glyph, the junction markers and every focus outline in the editor.
   --accent-primary is #e91e9c in light, which carries white at only 3.3:1. */
.light-theme .clip-editor-layout {
  --ce-accent: var(--lt-accent-pink);
  --ce-accent-soft: rgba(179, 16, 111, 0.18);
}

.light-theme .clip-editor-timeline,
.light-theme .clip-editor-transport,
.light-theme .clip-editor-inspector {
  border-color: var(--lt-hairline);
}

.light-theme .clip-editor-ruler {
  border-bottom-color: var(--lt-hairline);
}

.light-theme .clip-editor-tick {
  border-left-color: var(--lt-hairline);
  color: var(--lt-text-secondary);
}

.light-theme .clip-editor-kbd,
.light-theme .clip-editor-junction,
.light-theme .clip-editor-inspector-title,
.light-theme .clip-editor-transform-reset,
.light-theme .clip-editor-transform-heading small,
.light-theme .clip-editor-approx-hint,
.light-theme .clip-editor-recent-meta {
  color: var(--lt-text-secondary);
}

.light-theme .clip-editor-play-btn,
.light-theme .clip-editor-play-btn .clip-editor-play-icon {
  color: #ffffff;
}

.light-theme .clip-editor-export-total {
  background: rgba(0, 0, 0, 0.3);
  color: #ffffff;
}

/* The block's bottom scrim exists so the duration stays legible over an
   arbitrary frame. In light theme it becomes a WHITE scrim and the label
   becomes dark ink — the same trick, inverted. */
.light-theme .clip-editor-block::before {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.72) 100%);
}

.light-theme .clip-editor-block {
  border-color: var(--lt-hairline-strong);
}

.light-theme .clip-editor-block-dur {
  color: var(--lt-text);
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.95);
}

.light-theme .clip-editor-block-index,
.light-theme .clip-editor-block-muted,
.light-theme .clip-editor-block-status,
.light-theme .clip-editor-recent-badge,
.light-theme .clip-editor-recent-duration,
.light-theme .clip-editor-edited-badge {
  color: var(--lt-text);
}

/* The failure chip keeps its red, and takes deep red ink to carry it. */
.light-theme .clip-editor-block-status.broken {
  color: #4a0000;
}

.light-theme .clip-editor-film-tile.is-placeholder {
  opacity: 1;
}

/* ===========================================
   15. THE GLOBAL LAYER — BATCH 5
   ===========================================

   Batches 1-4 each repaired one surface. Batch 5 repairs the layer underneath
   them: the two grey tokens, the three blanket element rules in
   css/theme-light.css, the modal scrims, the toast chip and the status
   one-offs. Most of that lands in css/variables.css and css/theme-light.css,
   because most of it was a token or a rule that already existed and was simply
   wrong. What lands HERE is the residue: components that hard-code a
   dark-theme colour of their own, which no token can reach.

   The surfaces this section was measured on — Library, Marketplace, Learn,
   Caption, Account, the user menu, the mobile nav and account sheet, toasts,
   the app-update banner and the six modals — are booted by
   js/e2e/light-surfaces-batch5.js and walked by
   js/e2e/light-theme-batch5.spec.js.
   =========================================== */

/* --- Modal scrims -------------------------------------------------------

   Eleven overlays, eleven different hard-coded rgba(0,0,0,...) values between
   0.3 and 0.95 — the audit listed nine of them as separate rows because they
   were nine separate decisions. A scrim's job is to push the page back behind
   the modal, which is the same job in both themes, so light gets the same
   treatment at one consistent depth: --overlay, which batch 5 redefined from
   rgba(255,255,255,0.9) (a white veil over a white page — it pushed nothing
   back, and `.policy-modal`, the one overlay that DID name the token, was the
   only modal in the app with no usable backdrop) to rgba(0,0,0,0.45). */
.light-theme .modal-overlay,
.light-theme .mobile-overlay,
.light-theme .character-modal,
.light-theme .credits-modal,
.light-theme .payment-modal,
.light-theme .login-modal,
.light-theme .rules-modal,
.light-theme .report-modal,
.light-theme .library-detail-overlay,
.light-theme .studio-modal,
.light-theme .image-lightbox {
  background: var(--overlay);
}

/* --- The app-update banner ---------------------------------------------

   A 32k px2 rgba(17,17,17,0.94) slab pinned to the bottom of every screen in
   the app, with `Update available` painted #1d1d1f on top of it by the blanket
   `span` rule: 1.02:1, the worst single row in the audit and present on 49 of
   the 52 surfaces it visited. It keeps its shape — a floating chip with a soft
   shadow — and swaps the slab for a panel. */
.light-theme .app-update-banner {
  background: rgba(255, 255, 255, 0.94);
  border-color: var(--lt-hairline-strong);
  box-shadow: var(--lt-shadow-menu);
}

.light-theme .app-update-title {
  color: var(--lt-text);
}

.light-theme .app-update-message {
  color: var(--lt-text-secondary);
}

/* --- Marketplace character cards ----------------------------------------

   The origin badges ink themselves #b9edff / #ffc4ef / #d9ffbf — pastels
   picked to glow on a near-black card, and 1.02:1 to 1.26:1 on a white one.
   Each keeps its hue and its tinted border; only the ink moves into the shade
   far enough to read (>= 5.4:1 on the badge's own tinted ground). */
.light-theme .character-origin-badge {
  color: var(--lt-text);
}

.light-theme .character-origin-badge.digital-divas-bespoke {
  color: #005a86;
  border-color: rgba(0, 90, 134, 0.45);
}

.light-theme .character-origin-badge.vixxxen-exclusive {
  color: #9c0d61;
  border-color: rgba(156, 13, 97, 0.45);
}

.light-theme .character-origin-badge.seedream-dataset {
  color: #2c6a12;
  border-color: rgba(44, 106, 18, 0.5);
}

/* `#00ff88` is the DARK theme's --success, and on the light --bg-hover chip it
   is 1.02:1 — "✓ Owned" was an invisible label on a grey pill. --success is a
   readable green in both themes now, so the button can simply name it. */
.light-theme .character-buy-btn.owned {
  background: rgba(19, 122, 55, 0.1);
  color: var(--success);
}

/* --- Learn / Education --------------------------------------------------

   `.membership-btn` is two buttons wearing one class: `.primary` carries the
   brand gradient and white text, the plain one is a --bg-hover chip that was
   ALSO given white text (1.32:1 — see the note on that rule in
   css/theme-light.css). The chip gets a panel and near-black ink instead. */
.light-theme .membership-btn {
  background: var(--lt-panel);
  color: var(--lt-text);
  border: 1px solid var(--lt-hairline-strong);
}

.light-theme .membership-btn:hover {
  background: var(--lt-hover);
}

.light-theme .membership-btn.primary {
  border-color: transparent;
}

/* The feature lists sit on a pink-tinted card, where --text-muted lands at
   4.38:1 — close, and still short. This is the one place on these surfaces
   that needs the deeper secondary. */
.light-theme .membership-features li {
  color: var(--lt-text-secondary);
}

/* --- Account, user menu, mobile sheet -----------------------------------

   `👤` is a glyph, and the walker measures it as text: near-black on the brand
   gradient is 2.08:1. The avatar is a filled circle, so its content is white
   in both themes. */
.light-theme .mobile-sheet-avatar {
  color: #ffffff;
}

/* css/team.css inks the seat count #ff59c7 — the dark accent lightened for a
   near-black page, 2.2:1 on the light tint it sits on. */
.light-theme .team-seat-count {
  color: var(--accent-primary);
  border-color: rgba(179, 16, 111, 0.32);
}

/* --- Purchase request modal ---------------------------------------------

   Discord's brand blurple is 3.8:1 on the pale blue chip this button paints
   under it. Deepening the ink keeps the brand read and clears the bar; the
   chip and the border are untouched. */
.light-theme .pr-alt-btn.discord {
  color: #3b45c9;
}

/* --- The inline error box ----------------------------------------------

   css/base.css inks it #ff6666 on rgba(255,68,68,0.1) — 2.09:1 once the page
   behind that 10% wash is white. --error is a readable red in both themes now.
   The wash and the border stay: the box still reads as an error at a glance. */
.light-theme .error-message {
  color: var(--error);
  border-color: rgba(179, 38, 30, 0.55);
}

/* --- Brand-gradient CTAs ------------------------------------------------

   Batch 5 pointed the 64 hard-coded copies of `linear-gradient(135deg,
   #ff2ebb, #00b2ff)` in index.html, css/components.css and css/layout.css at
   --accent-gradient — the token whose DARK value is that exact literal, so
   dark cannot move, and whose light value is the same two hues deep enough to
   carry white text. What that leaves is the ink: these buttons never set a
   colour, because on a near-black page the inherited white was already right.
   In light they inherit their MODAL's near-black instead (theme-light.css
   paints every `*-modal-content` #1d1d1f), which put #1d1d1f on the gradient
   at 2.08:1. A filled brand button carries white in both themes. */
.light-theme .character-modal-buy-btn,
.light-theme .character-buy-btn:not(.owned),
.light-theme .membership-popular,
.light-theme .report-btn-submit,
.light-theme .rules-accept-btn,
.light-theme .user-menu-btn.primary {
  color: #ffffff;
}

/* Restoring what the `.membership-btn` panel rule above took away: the plain
   variant is a chip, the primary one is a filled brand button. */
.light-theme .membership-btn.primary {
  background: var(--accent-gradient);
}

/* The purchase-request submit button uses the OTHER brand gradient
   (#9d4edd -> #ff2ebb), which carries white at 4.05:1 on a light page. */
.light-theme .pr-submit-btn {
  background: var(--lt-accent-gradient);
}

/* The banner's secondary action was white-on-translucent-white — invisible the
   moment the banner itself stopped being near-black. */
.light-theme .app-update-later {
  background: var(--lt-hover);
  color: var(--lt-text-secondary);
}

/* --- Pink ink on a light ground -----------------------------------------

   Four one-offs that hard-code #ff2ebb, the DARK accent, as a text colour:
   2.7:1 to 3.3:1 wherever they land in light. --accent-primary is the light
   theme's own accent (#b3106f, 6.5:1 on white) and is what every other pink
   label on these surfaces already resolves to. */
.light-theme .credits-modal-stat-value.required,
.light-theme .login-toggle-row a,
.light-theme .pr-alt-btn.email,
.light-theme .user-menu-subscription {
  color: var(--accent-primary);
}

/* --- One casualty of retiring the blanket rules --------------------------

   `.clip-editor-hero-hint-sep` is the "·" between the Clip Editor's hero
   hints. css/edit-page.css inks it with --border-light — a BORDER token used
   as a text colour, which is #c7c7cc in light: 1.55:1. It read as black only
   because the blanket `span` rule was painting it #1d1d1f over the top, which
   is also why nobody noticed. It now matches the hints it separates. */
.light-theme .clip-editor-hero-hint-sep {
  color: var(--text-muted);
}
