/* ─────────────────────────────────────────────────────────────
   PIXIFY STUDIO · Design Tokens & Shared Components
   ─────────────────────────────────────────────────────────────
   Drop into any HTML page:
     {% include "_fonts.html" %}   ← links the faces in use from static/fonts/ (and any admin override)
     <link rel="stylesheet" href="/static/pixify-tokens.css">

   Source of truth: design lab tokens committed 2026-04-25.
   Extracted from: pixify-preview/report_pixify_v4.html
   ───────────────────────────────────────────────────────────── */


/* ── 1. Tokens ────────────────────────────────────────────── */
:root {
  /* Surfaces */
  --bg:               #FDFCF9;
  --bg-warm:          #F8F5EE;
  --surface:          #FFFFFF;
  --surface-warm:     #FDFBF6;
  --surface-stone:    #F4F1EB;
  --surface-input:    #F4F1EB;

  /* Borders */
  --border:           #E8E4DB;
  --border-soft:      #EFEBE1;
  --border-warm:      #E5DFD1;

  /* Text */
  --text:             #0A0A0A;
  --text-warm:        #1C1917;
  --text-muted:       #44403C;
  --text-dim:         #78716C;
  --text-quiet:       #A8A29E;

  /* Accent (forest green — primary action) */
  --accent:           #047857;
  --accent-hover:     #065F46;
  --accent-ink:       #FFFFFF;   /* text/icon on a solid accent fill (auto-flips in dark) */
  --accent-light:     #ECFDF5;
  --accent-border:    #A7F3D0;

  /* Danger (warm terracotta — never primary) */
  --red:              #B84A2B;
  --red-bg:           #FCF0EB;
  --red-border:       #F3D5C3;

  /* Warning (orange — unassigned, drafts) */
  --orange:           #C2410C;
  --orange-bg:        #FFF7ED;
  --orange-border:    #FED7AA;

  /* Brand */
  --pixify-red:       #DC0000;

  /* Sand (warm neutral chip / accent) */
  --sand:             #B08968;
  --sand-bg:          #F5EDE2;
  --sand-border:      #E5D5BD;

  /* Amber (ownership / elevated-privilege badges) */
  --amber:            #B45309;
  --amber-bg:         #FEF6E7;
  --amber-border:     #F3D9A6;

  /* Mint — the lighter end of the accent, for gradients only */
  --accent-mint:      #2BB789;
  /* Accent that still reads on a dark photo scrim (badges over imagery) */
  --accent-on-dark:   #6EE7B7;

  /* Categorical — diagrams and status keys on ADMIN surfaces only.
     Kept apart from the semantic colours so meaning never rides on the
     brand accent. Do not use these for state (use accent/red/orange). */
  --cat-blue:         #1D4ED8;
  --cat-blue-bg:      rgba(29, 78, 216, 0.10);
  --cat-purple:       #6D28D9;
  --cat-purple-bg:    rgba(109, 40, 217, 0.10);
  --cat-green:        #059669;
  --cat-green-bg:     rgba(5, 150, 105, 0.12);
  --cat-gold:         #8A5A2B;
  --cat-gold-bg:      rgba(138, 90, 43, 0.10);

  /* Code on the page (the API documentation, static/css/connections.css):
     a warm sheet and four inks from the palette's own hues — a comment, a
     keyword, a string, a number — and the name of a function. */
  --code-bg:          #FBFAF6;
  --code-ink:         #1C1917;
  --code-comment:     #8A8580;
  --code-keyword:     #6D28D9;
  --code-string:      #047857;
  --code-number:      #B45309;
  --code-name:        #1D4ED8;

  /* Layout — three named page widths replace ~47 one-off values */
  --sidebar-w:        320px;  /* the report's concept list needs the room */
  --content-narrow:   760px;    /* forms, settings, auth */
  --content:          1100px;   /* most pages */
  --content-wide:     1400px;   /* report, data grids */
  --content-max:      1400px;   /* legacy alias → --content-wide */

  /* Spacing scale — eight steps replace ~678 hand-typed gaps */
  --space-xs:         4px;
  --space-sm:         8px;
  --space-md:         12px;
  --space-lg:         16px;
  --space-xl:         24px;
  --space-2xl:        32px;
  --space-3xl:        48px;
  --space-4xl:        64px;

  /* Radii */
  --radius-card:      12px;
  --radius-image:     6px;
  --radius-button:    6px;
  --radius-pill:      4px;
  --radius-modal:     18px;
  --radius-hero:      18px;
  --radius-field:     8px;    /* text inputs, selects */
  --radius-tile:      10px;   /* an icon's square tile beside its words (the welcome tour's points) */
  --radius-round:     999px;  /* fully round: avatars, chips, switches */

  /* Shadows (warm-tinted, never cool grey) */
  --shadow-soft:      0 1px 2px rgba(28,25,23,0.04);
  --shadow-card:      0 1px 3px rgba(28,25,23,0.05), 0 1px 2px rgba(28,25,23,0.03);
  --shadow-hover:     0 6px 20px rgba(28,25,23,0.06), 0 2px 6px rgba(28,25,23,0.04);
  --shadow-hero:      0 16px 40px -16px rgba(28,25,23,0.14), 0 6px 16px -6px rgba(28,25,23,0.06);
  --shadow-modal:     0 32px 80px -16px rgba(28,25,23,0.4), 0 8px 24px -8px rgba(28,25,23,0.2);
  --shadow-float:     0 1px 2px rgba(28,25,23,0.04), 0 18px 48px -22px rgba(28,25,23,0.20), 0 6px 16px -10px rgba(28,25,23,0.08);
  --shadow-photo:     0 0 0 1px var(--border), 0 0 0 5px var(--surface), 0 6px 18px -8px rgba(28,25,23,0.22);

  /* Focus — the soft halo a text field wears while you type in it, and the
     solid ring every other control shows when it is reached from the
     keyboard (the rail's links had it first). */
  --focus-ring:       0 0 0 3px rgba(4, 120, 87, 0.12);
  --focus-outline:    2px solid var(--accent);

  /* Motion — three curves and four durations, so nothing hand-types a
     timing again. Motion here is feedback, never decoration: the style
     guide's Motion section says when to use none at all. Values from the
     animation standards in the skills repo (Emil Kowalski's): anything
     arriving or leaving starts fast, and interface motion stays under 300ms.
     A count is not movement: a figure the customer changed counts to its
     new value in --dur-count (Jacob, 2026-09-30), long enough to be seen
     counting, and nothing waits for it. */
  --ease-out:         cubic-bezier(0.23, 1, 0.32, 1);    /* arriving and leaving */
  --ease-in-out:      cubic-bezier(0.77, 0, 0.175, 1);   /* moving across the screen */
  --ease-drawer:      cubic-bezier(0.32, 0.72, 0, 1);    /* a panel sliding in from an edge */
  --dur-press:        120ms;   /* the press of a button */
  --dur-fast:         150ms;   /* hovers, menus, anything on its way out */
  --dur-base:         200ms;   /* dialogs and toasts arriving */
  --dur-slow:         300ms;   /* large surfaces */
  --dur-count:        450ms;   /* a figure counting to its new value (static/js/ui/count.js) */
  --dur-page:         220ms;   /* a dialog's next page sliding in (the welcome tour) */
  /* How far things travel, so every dialog, toast and press moves the same
     amount. */
  --motion-rise:      8px;     /* dialogs and toasts rise this far into place */
  --motion-page:      10px;    /* …and a dialog's next page slides this far in from the side */
  --motion-scale-in:  0.98;    /* …from this size, never from nothing */
  --press-scale:      0.97;    /* a pressed control gives this much */
  --motion-stagger:   40ms;    /* the step between items arriving as a group */

  /* Typography — the DEFAULT faces. design_fonts.py reads the family names off
     these three lines (one source of truth) and an admin can override them at
     runtime from Settings → Typography; the override rides on <html style>
     via templates/_fonts.html, so no page ever re-declares these tokens.
     2026-09-14: Geist (interface) and Geist Mono (labels/data) are settled;
     the heading face is on trial — Playfair Display first, DM Sans second.
     Rejected: Instrument Serif (too extreme), Newsreader / Source Serif 4 /
     Literata (calmer serifs, not picked). */
  --font-body:        'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-display:     'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono:        'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  /* Digits that change while you watch (stat tiles, the processing card's
     percentage). DM Sans has no equal-width figures at all — its 1 is half
     the width of its 0 — so a ticking count shuffled sideways. The interface
     face has them; these numbers use it with tabular-nums. */
  --font-figures:     var(--font-body);
  --font-base:        12.5px;

  /* Heading tuning for the display face. These describe --font-display above
     (a grotesk: scale 1, medium); design_fonts.PROFILES overrides all four per
     family when an admin picks a serif, which needs to sit larger and looser. */
  --display-size-scale:     1;
  --display-weight:         500;
  --display-name-weight:    600;
  --display-tracking-scale: 1;

  /* Type ladder — a named scale replaces ~34 ad-hoc sizes */
  --fs-xs:            11px;     /* meta, captions */
  --fs-sm:            12px;     /* secondary UI text */
  --fs-base:          13px;     /* body / default */
  --fs-md:            15px;     /* emphasised body */
  --fs-lg:            18px;     /* small headings */
  --fs-xl:            22px;     /* section headings */
  --fs-2xl:           28px;     /* page headings */
  --fs-3xl:           36px;     /* display (serif) */
  --fs-display:       48px;     /* hero display (serif) */
}


/* ── 2. Reset & body ──────────────────────────────────────── */
* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--text);
  font-size: var(--font-base);
  line-height: 1.55;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* App-shell variant — for full-bleed admin pages with sidebar */
body.app-shell {
  display: flex;
  height: 100vh;
  overflow: hidden;
}


/* ── 3. Type utilities & display headings ─────────────────── */
.serif        { font-family: var(--font-display); font-weight: 400; letter-spacing: -0.01em; }
.serif-italic { font-family: var(--font-display); font-style: italic; font-weight: 400; }
.mono         { font-family: var(--font-mono); }
/* Words for a screen reader alone — a figure said once it has landed,
   while the one on screen counts to it (the billing page's top-up price,
   the plans page's sum to try and its switch box). */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0; padding: 0; margin: -1px;
}

h1.display {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: calc(48px * var(--display-size-scale));
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--text);
  margin-bottom: 10px;
}
h1.display .italic { font-style: italic; color: var(--text-muted); }

h2.display {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: calc(36px * var(--display-size-scale));
  line-height: 1.1;
  letter-spacing: -0.015em;
  color: var(--text);
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.eyebrow .eyebrow-dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--accent);
  display: inline-block;
}


/* ── 4. Buttons ───────────────────────────────────────────── */
.btn {
  padding: 6px 12px;
  border-radius: var(--radius-button);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  transition: background-color var(--dur-press) ease, border-color var(--dur-press) ease,
              color var(--dur-press) ease, box-shadow var(--dur-press) ease,
              transform var(--dur-press) var(--ease-out);
  white-space: nowrap;
  font-family: inherit;
  text-decoration: none;      /* <a class="btn"> must read as a button */
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  line-height: 1;
  letter-spacing: -0.005em;
}
.btn:hover {
  border-color: var(--border-warm);
  background: var(--surface-warm);
  box-shadow: var(--shadow-soft);
}
/* The press shows on pointer-down, before the click lands, so the button
   answers the finger rather than the release. */
.btn:not(:disabled):active { transform: scale(var(--press-scale)); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }

.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
}
.btn-primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.btn-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
}
.btn-ghost:hover {
  background: var(--surface-stone);
  border-color: var(--border-soft);
  color: var(--text);
}

.btn-danger {
  color: var(--red);
  border-color: var(--border);
}
.btn-danger:hover {
  background: var(--red-bg);
  border-color: var(--red-border);
}
/* Filled danger: only the final button of a dialog that has already asked
   ("Delete batch"). Anywhere else danger stays the outline above. */
.btn-danger-solid {
  background: var(--red);
  border-color: var(--red);
  color: #fff;
}
.btn-danger-solid:hover {
  background: color-mix(in srgb, var(--red) 86%, #000);
  border-color: color-mix(in srgb, var(--red) 86%, #000);
}
[data-theme="dark"] .btn-danger-solid { color: #2A0F06; }

.btn-sm { padding: 5px 10px; font-size: 11.5px; height: 26px; }
.btn-lg { padding: 8px 16px; font-size: 13px;   height: 34px; }
.btn-block { width: 100%; justify-content: center; }   /* the one button a card is about */


/* ── 5. Confirm badge (status-toggle) ─────────────────────── */
.confirm-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: var(--radius-button);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color var(--dur-press) ease, border-color var(--dur-press) ease,
              color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
  height: 30px;
  line-height: 1;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-family: inherit;
}
.confirm-badge:hover { border-color: var(--accent); color: var(--accent); }
.confirm-badge:active { transform: scale(var(--press-scale)); }
.confirm-badge:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
.confirm-badge.confirmed {
  background: var(--accent-light);
  color: var(--accent);
  border-color: var(--accent-border);
}


/* ── 6. Pills & tags ──────────────────────────────────────── */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  font-size: 11.5px;
  color: var(--text-muted);
  background: var(--surface-stone);
  border: 1px solid var(--border-soft);
  line-height: 1.5;
  font-weight: 500;
}
.pill-green  { background: var(--accent-light);  color: var(--accent);  border-color: var(--accent-border); }
.pill-red    { background: var(--red-bg);        color: var(--red);     border-color: var(--red-border); }
.pill-orange { background: var(--orange-bg);     color: var(--orange);  border-color: var(--orange-border); }
.pill-sand   { background: var(--sand-bg);       color: var(--sand);    border-color: var(--sand-border); }
.pill-amber  { background: var(--amber-bg);      color: var(--amber);   border-color: var(--amber-border); }  /* ownership */
.pill-dot    { width: 6px; height: 6px; border-radius: 50%; background: currentColor; display: inline-block; }

/* A pill that explains itself. Rest the pointer on it, or tab to it, and a
   short note opens beneath — for a state the reader can do nothing about
   this minute (a batch whose originals are gone), where a banner is noise
   and a bare pill is a riddle. The note is the pill's data-tip; give a
   screen reader the same words through aria-describedby. Dark on light and
   light on dark, like the sidebar's tooltips. */
.pill-tip { position: relative; cursor: default; }
.pill-tip::after {
  content: attr(data-tip);
  position: absolute;
  z-index: 30;
  top: calc(100% + 7px);
  left: 0;
  width: max-content;
  max-width: 300px;
  padding: 8px 11px;
  border-radius: 7px;
  background: var(--text);
  color: var(--surface);
  font-family: var(--font-body);
  font-size: 11.5px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: -0.005em;
  text-align: left;
  text-transform: none;
  white-space: normal;
  box-shadow: var(--shadow-float);
  opacity: 0;
  transform: translateY(-3px);
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.pill-tip:hover::after,
.pill-tip:focus-visible::after {
  opacity: 1;
  transform: none;
  transition-delay: 150ms;     /* passing over it is not asking */
}
.pill-tip:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
/* While its note is open the pill rises above its neighbours: on the batch
   list every row's pill is a positioned layer, and the row below was
   painting its own pill through the note. */
.pill-tip:hover, .pill-tip:focus-visible { z-index: 40; }

/* The batch's retention state, beside its name on both batch pages
   (templates/_retention_pill.html). Never shrinks and never wraps: the
   name gives way before the deadline does. */
.retention-pill { flex: none; white-space: nowrap; }

/* "Live updates paused" (static/js/ui/live-pill.js): in the concept panel's
   Export row, at its empty right end, on every view, scrolled or not. With
   the panel folded to its 44 px strip it shrinks to a dot inside the strip,
   just above that row, the note still on hover — never over the page, whose
   first file or row it would cover. It floats, out of the page's flow, so
   showing it moves nothing; it fades in and out rather than popping, and
   stays out of sight while the panel is on its way. Neutral: nothing the
   reader needs to do. The note opens upwards, the spot being at the bottom
   of the window. */
.live-pill-spot {
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translate(calc(-100% - 10px), -50%);
  z-index: 40;
  pointer-events: none;            /* the empty spot never takes a click */
  transition: opacity var(--dur-fast) var(--ease-out);
}
.sidebar.is-folding .live-pill-spot { opacity: 0; }
.sidebar.is-folded .live-pill-spot {
  left: 50%;
  top: auto;
  bottom: calc(100% + 10px);
  transform: translateX(-50%);
}
.live-pill .live-pill-dot { display: none; }
.sidebar.is-folded .live-pill { padding: 5px; border-radius: 50%; }
.sidebar.is-folded .live-pill .live-pill-dot {
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
}
.sidebar.is-folded .live-pill .live-pill-label {
  /* Still read out; only the dot is drawn. */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
.live-pill {
  pointer-events: auto;
  white-space: nowrap;
  box-shadow: var(--shadow-float);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.live-pill.is-paused {
  opacity: 1;
  animation: live-pill-in var(--dur-fast) var(--ease-out);
}
@keyframes live-pill-in { from { opacity: 0; } to { opacity: 1; } }
.live-pill.pill-tip::after {
  top: auto;
  bottom: calc(100% + 7px);
  transform: translateY(3px);
}
.live-pill.pill-tip:hover::after,
.live-pill.pill-tip:focus-visible::after { transform: none; }


/* ── 7. Sidebar shell ─────────────────────────────────────── */
.sidebar {
  width: var(--sidebar-w);
  background: var(--bg-warm);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
}
.sidebar-brand {
  padding: 18px 20px 14px;
  display: flex;
  align-items: flex-start;
  gap: 10px;
}
.brand-logo {
  width: 26px;
  height: 32px;
  border-radius: 5px;
  flex-shrink: 0;
  display: block;
  box-shadow: 0 1px 2px rgba(220, 0, 0, 0.25);
  margin-top: 1px;
}
.brand-wordmark {
  display: flex;
  flex-direction: column;
  gap: 0;
  line-height: 1;
}
.brand-title {
  font-family: var(--font-body);
  font-size: 18px;
  letter-spacing: -0.5px;
  color: var(--text);
  line-height: 1.1;
}
.brand-title .pixify { font-weight: 500; }
.brand-title .studio { font-weight: 400; }
.brand-sub {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-dim);
  letter-spacing: -0.01em;
  margin-top: 4px;
  font-weight: 400;
}

.sidebar-section-label {
  padding: 14px 20px 6px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 500;
}
.sidebar-nav { flex: 1; overflow-y: auto; padding: 0 10px 8px; }

.nav-item {
  padding: 7px 10px;
  border-radius: 7px;
  cursor: pointer;
  margin-bottom: 1px;
  display: flex;
  align-items: center;
  gap: 10px;
  transition: background 0.12s, border-color 0.12s;
  color: var(--text-muted);
  border: 1px solid transparent;
}
.nav-item:hover { background: rgba(28, 25, 23, 0.035); }
.nav-item.active {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border);
  box-shadow: var(--shadow-soft);
}
.nav-item .nav-icon {
  width: 28px; height: 28px;
  border-radius: 6px;
  overflow: hidden;
  flex-shrink: 0;
  background: var(--surface-stone);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px;
  color: var(--text-dim);
  border: 1px solid var(--border-soft);
}
.nav-item .nav-icon img { width: 100%; height: 100%; object-fit: cover; }
.nav-item .nav-icon.green  { background: var(--accent); color: #fff; border-color: var(--accent); }
.nav-item .nav-icon.sand   { background: var(--sand);   color: #fff; border-color: var(--sand); }
.nav-item .nav-icon.orange { background: var(--orange); color: #fff; border-color: var(--orange); }
.nav-item .nav-label { font-size: 13px; flex: 1; min-width: 0; }
.nav-item .nav-label .name {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  letter-spacing: -0.01em;
}
.nav-item .nav-label .count {
  font-size: 10.5px;
  color: var(--text-dim);
  font-weight: 400;
  margin-top: 1px;
  font-family: var(--font-mono);
}
.nav-check {
  width: 13px; height: 13px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  font-size: 8px;
  flex-shrink: 0;
  color: transparent;
}
.nav-check.done {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-light);
}

/* Sidebar footer (progress, etc.) */
.sidebar-footer { padding: 14px 20px; border-top: 1px solid var(--border); }
.progress-label {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--text-dim);
  margin-bottom: 7px;
  font-weight: 500;
}
.progress-label .count-num {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text);
}
.progress-track {
  height: 3px;
  background: var(--surface-stone);
  border-radius: 2px;
  overflow: hidden;
}
.progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
  transition: width 0.3s;
}


/* ── 8. Main content shell + sticky top bar ───────────────── */
.main {
  flex: 1;
  overflow-y: auto;
  padding: 0 48px 140px;
}
.main-inner {
  max-width: var(--content-max);
  margin: 0 auto;
}

.top-bar {
  position: sticky;
  top: 0;
  background: color-mix(in srgb, var(--bg) 88%, transparent); /* the page colour, translucent */
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  padding: 14px 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  z-index: 100;
  border-bottom: 1px solid transparent;
  transition: border-color 0.2s;
  min-height: 56px;
}
.top-bar.scrolled { border-bottom-color: var(--border); }
.top-bar-left {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
  flex: 1;
}

.breadcrumb {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: var(--text-dim);
  font-weight: 500;
  min-width: 0;
}
.breadcrumb a { color: var(--text-dim); text-decoration: none; transition: color 0.12s; }
.breadcrumb a:hover { color: var(--text); }
.breadcrumb .sep { color: var(--text-quiet); }
.breadcrumb .current { color: var(--text); }

/* Sticky title appears when hero scrolls past */
.top-bar-title {
  font-family: var(--font-display);
  font-size: 18px;
  line-height: 1.2;
  color: var(--text);
  letter-spacing: -0.015em;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 0.2s, transform 0.2s;
  pointer-events: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 540px;
  display: none;
}
.top-bar.scrolled .top-bar-title.active {
  opacity: 1;
  transform: translateY(0);
  display: block;
}
.top-bar.scrolled .breadcrumb.hide-on-scroll { display: none; }

.top-actions { display: flex; gap: 8px; flex-shrink: 0; }


/* ── 9. Hero (overview-style) ─────────────────────────────── */
.hero {
  position: relative;
  margin: 6px 0 36px;
  border-radius: var(--radius-hero);
  overflow: hidden;
  background: var(--surface-stone);
  box-shadow: var(--shadow-hero);
}
.hero .hero-image {
  aspect-ratio: 16 / 6;
  width: 100%;
  background-size: cover;
  background-position: center;
  position: relative;
}
.hero .hero-image::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(253, 252, 249, 0)    0%,
    rgba(253, 252, 249, 0)    50%,
    rgba(253, 252, 249, 0.85) 92%,
    rgba(253, 252, 249, 1)    100%);
  pointer-events: none;
}
.hero .hero-content {
  position: absolute;
  inset: auto 0 0 0;
  padding: 36px 40px 36px;
  z-index: 2;
}
.hero-desc {
  font-size: 14.5px;
  color: var(--text-muted);
  line-height: 1.55;
  max-width: 680px;
}


/* ── 10. Stats strip ──────────────────────────────────────── */
.stats-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  margin-bottom: 40px;
  overflow: hidden;
}
.stat {
  padding: 14px 18px;
  border-right: 1px solid var(--border-soft);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.stat:last-child { border-right: none; }
.stat-value {
  font-family: var(--font-figures);
  font-variant-numeric: tabular-nums;
  font-size: 28px;
  font-weight: 400;
  line-height: 1;
  color: var(--text);
  letter-spacing: -0.02em;
}
.stat-value.accent { color: var(--accent); }
.stat-value.orange { color: var(--orange); }
.stat-label {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 500;
  margin-top: 4px;
}
/* Three cells when the row is three facts, not four (Billing's credit buckets) */
.stats-strip.cols-3 { grid-template-columns: repeat(3, 1fr); }
/* Label first: the words on top, the figure under them (Billing's buckets;
   Jacob, 2026-09-30 — "text is on top"). A label can run to two lines in one
   cell and one in the next, so the figure goes to the cell's foot and the
   figures share a line across the strip instead of following their labels
   up and down. The recap keeps its figure first: it counts things. */
.stats-strip.label-first .stat { justify-content: space-between; gap: var(--space-md); }
.stats-strip.label-first .stat-label { margin-top: 0; }
/* Inside a settings group the card is the frame, so the strip drops its own:
   it becomes a ruled band between the body above it and the note below. */
.settings-group > .stats-strip {
  border: 0;
  border-top: 1px solid var(--border-soft);
  border-radius: 0;
  margin: 0;
  background: transparent;
}
.settings-group > .stats-strip .stat { padding: 14px 22px; }

/* Separate stat cards — the shoot overview's KPI row. Lived in main.css as
   .overview-stats/.stat-card; the admin pages use the same row, so it is
   shared. The values above (.stat-value / .stat-label) style the inside. */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-lg);
  margin-bottom: var(--space-2xl);
}
.stat-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 20px;
}


/* ── 11. Section label ────────────────────────────────────── */
.section-label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 18px;
}
.section-label h3 {
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: 400;
  color: var(--text);
  letter-spacing: -0.015em;
}
.section-label h3 .italic { font-style: italic; color: var(--text-muted); }
.section-label .right-meta {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  font-weight: 500;
  letter-spacing: -0.01em;
}


/* ── 12. Inline meta line (· separated) ───────────────────── */
.meta-line {
  font-size: 13px;
  color: var(--text-dim);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0;
}
.meta-line .meta-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.meta-line .meta-item + .meta-item::before {
  content: '·';
  color: var(--text-quiet);
  margin: 0 10px;
}
.meta-line .meta-num {
  font-family: var(--font-mono);
  color: var(--text);
  font-size: 12.5px;
}
.meta-line .meta-status-confirmed { color: var(--accent); font-weight: 500; }
.meta-line .meta-status-pending   { color: var(--text-dim); }


/* ── 13. Editorial description (article body) ────────────── */
.body-prose {
  font-size: 16px;
  color: var(--text-warm);
  line-height: 1.65;
  max-width: 780px;
}


/* ── 14. Thin rule (section divider) ──────────────────────── */
.thin-rule {
  border: 0;
  height: 1px;
  background: var(--border);
  margin: 0 0 28px;
}


/* ── 15. Card grid (catalogue / listings) ─────────────────── */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 20px;
  margin-bottom: 44px;
}
.card {
  position: relative;
  cursor: pointer;
  border-radius: var(--radius-card);
  background: var(--surface);
  border: 1px solid var(--border);
  overflow: hidden;
  transition: transform var(--dur-base) ease, box-shadow var(--dur-base) ease, border-color var(--dur-fast) ease;
}
.card:hover {
  box-shadow: var(--shadow-hover);
  border-color: var(--border-warm);
}
/* Hover movement only where there is a real pointer: a touch screen fires
   hover on tap and leaves the card lifted after the finger has gone. */
@media (hover: hover) and (pointer: fine) {
  .card:hover { transform: translateY(-2px); }
  .card:hover .card-img-wrap img { transform: scale(1.02); }
  .image-card:hover img { transform: scale(1.04); }
}
.card-img-wrap {
  aspect-ratio: 16 / 10;
  background: var(--surface-stone);
  position: relative;
  overflow: hidden;
}
.card-img-wrap img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slow) ease;
}
.card-status {
  position: absolute;
  top: 12px; right: 12px;
  background: rgba(255, 255, 255, 0.92);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  padding: 3px 9px;
  border-radius: 20px;
  backdrop-filter: blur(8px);
  display: flex;
  align-items: center;
  gap: 5px;
  border: 1px solid rgba(167, 243, 208, 0.4);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.card-body { padding: 16px 18px 18px; }
.card-eyebrow {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 500;
  margin-bottom: 6px;
}
.card-title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 22px;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--text);
  margin-bottom: 6px;
}
.card-desc {
  font-size: 12.5px;
  color: var(--text-muted);
  line-height: 1.5;
  margin-bottom: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.card-tags { display: flex; flex-wrap: wrap; gap: 4px; }


/* ── 16. Image grid ───────────────────────────────────────── */
.image-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 12px;
  margin-bottom: 32px;
}
.image-card {
  position: relative;
  border-radius: var(--radius-image);
  overflow: hidden;
  aspect-ratio: 1;
  cursor: pointer;
  background: var(--surface-stone);
  border: 1px solid var(--border-soft);
  transition: transform var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.image-card img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slow) ease;
}
.image-card.selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent);
}
.image-card .check-overlay {
  position: absolute;
  top: 7px; left: 7px;
  width: 20px; height: 20px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.9);
  border: 1px solid rgba(0, 0, 0, 0.06);
  display: flex; align-items: center; justify-content: center;
  font-size: 10px;
  color: transparent;
  backdrop-filter: blur(4px);
  transition: opacity var(--dur-press) ease, background-color var(--dur-press) ease,
              border-color var(--dur-press) ease, color var(--dur-press) ease;
  opacity: 0;
}
.image-card:hover .check-overlay,
.image-card.selected .check-overlay { opacity: 1; }
.image-card.selected .check-overlay {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

.grid-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 14px;
}
.grid-toolbar .left-group {
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.grid-toolbar .count-serif {
  font-family: var(--font-figures);
  font-variant-numeric: tabular-nums;
  font-size: 22px;
  font-weight: 400;
  color: var(--text);
  line-height: 1;
}
.grid-toolbar .count-label {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 500;
}


/* ── 17. Inline keyword rows (no card) ────────────────────── */
.kw-inline { margin-bottom: 32px; }
.kw-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border-soft);
}
.kw-row:last-child { border-bottom: none; }
.kw-cat-label {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 15px;
  color: var(--text-dim);
  min-width: 100px;
  padding-top: 3px;
  flex-shrink: 0;
}
.kw-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  align-items: center;
  flex: 1;
}
.kw-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px 3px 10px;
  border-radius: var(--radius-pill);
  font-size: 12.5px;
  color: var(--text-muted);
  background: var(--surface-warm);
  border: 1px solid var(--border-soft);
  line-height: 1.4;
  font-weight: 500;
}
.kw-pill .pill-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px; height: 14px;
  border-radius: 50%;
  font-size: 9px;
  color: var(--text-quiet);
  cursor: pointer;
  transition: background-color var(--dur-press) ease, border-color var(--dur-press) ease, color var(--dur-press) ease;
  line-height: 1;
}
.kw-pill .pill-x:hover { background: var(--red-bg); color: var(--red); }
.kw-add-input {
  background: transparent;
  border: 1px dashed var(--border);
  color: var(--text);
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  font-size: 12.5px;
  width: 120px;
  line-height: 1.4;
  outline: none;
  font-family: inherit;
  font-weight: 500;
}
.kw-add-input:focus {
  border-color: var(--accent);
  border-style: solid;
  background: var(--accent-light);
  width: 150px;
}
.kw-add-input::placeholder { color: var(--text-quiet); }


/* ── 18. Page nav (prev/next at bottom of page) ────────────── */
.page-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 40px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
  gap: 16px;
}
.page-nav .page-meta {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}


/* ── 19. Lightbox ─────────────────────────────────────────── */
.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(28, 25, 23, 0.92);
  z-index: 1200;
  display: none;
  align-items: center;
  justify-content: center;
}
.lightbox.show { display: flex; }
.lightbox img {
  max-width: 88vw;
  max-height: 88vh;
  border-radius: 10px;
  object-fit: contain;
  cursor: default;
}
.lb-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: #fff;
  font-size: 18px;
  width: 44px;
  height: 56px;
  border-radius: 10px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s;
  z-index: 1;
  backdrop-filter: blur(8px);
}
.lb-nav:hover { background: rgba(255, 255, 255, 0.16); }
.lb-prev { left: 24px; }
.lb-next { right: 24px; }
.lb-counter {
  position: absolute;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.9);
  font-size: 11px;
  padding: 7px 14px;
  border-radius: 20px;
  pointer-events: none;
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
  backdrop-filter: blur(8px);
}
.lb-close {
  position: absolute;
  top: 20px; right: 20px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: #fff;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  cursor: pointer;
  font-size: 14px;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.15s;
  backdrop-filter: blur(8px);
}
.lb-close:hover { background: rgba(255, 255, 255, 0.18); }


/* ── 19b. Cookie consent bar (static/js/ui/consent-bar.js) ──
   One paragraph and two equal buttons, centred at the foot of the page,
   above everything but a dialog; on a phone the buttons go under the words. */
.consent-bar {
  position: fixed;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  width: min(720px, calc(100vw - 32px));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
  padding: 12px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  color: var(--text-muted);
  font-size: var(--fs-base);
  line-height: 1.5;
  box-shadow: var(--shadow-hover);
  z-index: 2100;
}
.consent-bar p { margin: 0; flex: 1 1 320px; }
.consent-bar b { color: var(--text); font-weight: 600; }
.consent-bar .consent-actions { display: flex; gap: 8px; margin-left: auto; }

/* ── 20. Toast ────────────────────────────────────────────── */
.toast {
  position: fixed;
  bottom: 24px;
  right: 24px;
  /* A gutter on the left too, and the rail's width on top of it: a long
     message (Undo of one of Studio's blocks says how many files got the word
     back) grew to the screen's edge on a phone, over the sidebar; capped at
     100vw - 48px it still started 24px from the edge, over the 60px
     navigation rail. Its left edge now stays beside the rail. */
  max-width: min(560px, calc(100vw - var(--rail-w, 0px) - 48px));
  background: var(--surface);
  border: 1px solid var(--accent-border);
  color: var(--accent);
  padding: 10px 16px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  z-index: 2000;
  box-shadow: var(--shadow-hover);
  /* It rises into its corner and sinks back out the same way — quicker on
     the way out, because by then it has been read. Hidden, it lets clicks
     through: an invisible toast used to sit over whatever was in the
     bottom-right corner, Undo link and all, and swallow the click. */
  opacity: 0;
  transform: translateY(var(--motion-rise));
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.toast.show {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  transition-duration: var(--dur-base);
}
.toast.error { border-color: var(--red-border); color: var(--red); }
/* The toast's Undo (static/js/ui/toast.js). It was amber from the old dark
   theme — 1.7:1 against this white toast, the one word a slip needs. */
.toast-undo {
  margin-left: 8px;
  color: var(--text);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.toast-undo:hover { color: var(--accent-hover); }
.toast.error .toast-undo:hover { color: var(--red); }


/* ── 21. Spinner ──────────────────────────────────────────── */
.spinner {
  display: inline-block;
  width: 12px; height: 12px;
  border: 1.5px solid rgba(255, 255, 255, 0.3);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}
.btn:not(.btn-primary) .spinner {
  border-color: rgba(0, 0, 0, 0.15);
  border-top-color: var(--text);
}
@keyframes spin { to { transform: rotate(360deg); } }


/* ── 22. Scrollbar ────────────────────────────────────────── */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 10px;
  border: 2px solid var(--bg);
}
::-webkit-scrollbar-thumb:hover { background: var(--text-quiet); }


/* ── 23. Single-image keywording modal ────────────────────── */
.kw-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(28, 25, 23, 0.62);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 2000;
  padding: 32px;
  animation: kwFade var(--dur-base) var(--ease-out);
}
.kw-modal-backdrop.show { display: flex; }
@keyframes kwFade { from { opacity: 0; } to { opacity: 1; } }

.kw-modal {
  width: 100%;
  max-width: 1320px;
  height: 100%;
  max-height: 840px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-modal);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  box-shadow: var(--shadow-modal);
  animation: kwSlide var(--dur-base) var(--ease-out);
}
/* A panel arriving: it rises a little into place as it fades in. The same
   rise every dialog uses (§27), read from the motion tokens. */
@keyframes kwSlide {
  from { opacity: 0; transform: translateY(var(--motion-rise)) scale(var(--motion-scale-in)); }
  to   { opacity: 1; transform: none; }
}

.kw-modal-header {
  padding: 11px 14px 11px 18px;
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 14px;
  flex-shrink: 0;
  background: var(--surface);
}
.kw-modal-header .header-nav { display: flex; gap: 4px; align-items: center; }
.kw-nav-btn {
  width: 28px; height: 28px;
  border-radius: var(--radius-button);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px;
  transition: background-color var(--dur-press) ease, border-color var(--dur-press) ease, color var(--dur-press) ease, transform var(--dur-press) var(--ease-out);
}
.kw-nav-btn:hover {
  background: var(--surface-warm);
  color: var(--text);
  border-color: var(--text-quiet);
}
.kw-nav-btn:active { transform: scale(var(--press-scale)); }
.kw-nav-btn:focus-visible, .kw-modal-close:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
.kw-nav-btn:disabled { opacity: 0.3; cursor: not-allowed; pointer-events: none; }

.kw-modal-header .header-info {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.kw-modal-header .file-name {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text);
  font-weight: 500;
  letter-spacing: -0.01em;
}
.kw-modal-header .file-meta {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-dim);
  letter-spacing: 0.04em;
}
.kw-modal-header .header-actions { display: flex; gap: 8px; align-items: center; }
.kw-saved-indicator {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-dim);
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.kw-saved-indicator .dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  display: inline-block;
}
.kw-modal-close {
  width: 30px; height: 30px;
  border-radius: var(--radius-button);
  border: none;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px;
  transition: background-color var(--dur-press) ease, border-color var(--dur-press) ease, color var(--dur-press) ease;
}
.kw-modal-close:hover { background: var(--surface-stone); color: var(--text); }

.kw-modal-body { flex: 1; display: flex; min-height: 0; }
.kw-image-side {
  flex: 1.55;
  min-width: 0;
  background: #1C1917;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.kw-image-side img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
}
.kw-image-meta {
  position: absolute;
  bottom: 16px;
  left: 18px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: rgba(255, 255, 255, 0.78);
  background: rgba(0, 0, 0, 0.45);
  padding: 6px 12px;
  border-radius: 6px;
  backdrop-filter: blur(8px);
  display: flex;
  gap: 14px;
  border: 1px solid rgba(255, 255, 255, 0.08);
}
.kw-image-meta span { display: inline-flex; align-items: center; gap: 5px; }
.kw-image-meta .meta-dot {
  width: 4px; height: 4px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.4);
}

.kw-form-side {
  width: 460px;
  flex-shrink: 0;
  border-left: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  background: var(--bg);
  min-height: 0;
}
.kw-form-body {
  flex: 1;
  overflow-y: auto;
  padding: 18px 24px 24px;
}

.kw-context {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--surface-warm);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  margin-bottom: 20px;
}
.kw-context .ctx-thumb {
  width: 30px; height: 30px;
  border-radius: 5px;
  overflow: hidden;
  flex-shrink: 0;
  border: 1px solid var(--border-soft);
}
.kw-context .ctx-thumb img { width: 100%; height: 100%; object-fit: cover; }
.kw-context .ctx-info {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
  gap: 1px; line-height: 1.2;
}
.kw-context .ctx-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.kw-context .ctx-name {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--text);
  font-style: italic;
}
.kw-context .ctx-link {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-dim);
  text-decoration: none;
  padding: 4px 8px;
  border-radius: 5px;
  border: 1px solid var(--border-soft);
  transition: background-color var(--dur-press) ease, border-color var(--dur-press) ease, color var(--dur-press) ease;
  flex-shrink: 0;
}
.kw-context .ctx-link:hover { color: var(--text); border-color: var(--text-quiet); }

.kw-form-section {
  margin-bottom: 22px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--border-soft);
}
.kw-form-section:last-child { border-bottom: none; padding-bottom: 0; margin-bottom: 0; }
.kw-section-label {
  font-family: var(--font-mono);
  font-size: 9.5px;
  color: var(--text-dim);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 500;
  margin-bottom: 8px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.ai-chip {
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--accent);
  background: var(--accent-light);
  border: 1px solid var(--accent-border);
  padding: 1px 7px 1px 6px;
  border-radius: 8px;
  letter-spacing: 0.04em;
  font-weight: 500;
  text-transform: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  line-height: 1.3;
}
.ai-chip .sparkle { font-size: 9px; }

.kw-title-input {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--surface);
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 500;
  color: var(--text);
  outline: none;
  letter-spacing: -0.01em;
  line-height: 1.25;
  transition: border-color 0.12s;
}
.kw-title-input:focus { border-color: var(--accent); }
.kw-title-input::placeholder { color: var(--text-quiet); font-style: italic; }

.kw-desc-input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--surface);
  font-family: var(--font-body);
  font-size: 13px;
  color: var(--text);
  outline: none;
  line-height: 1.55;
  resize: vertical;
  min-height: 84px;
  transition: border-color 0.12s;
}
.kw-desc-input:focus { border-color: var(--accent); }
.kw-desc-input::placeholder { color: var(--text-quiet); }

/* Tighter keyword rows inside modal */
.kw-modal .kw-row {
  padding: 7px 0;
  align-items: flex-start;
  gap: 12px;
}
.kw-modal .kw-cat-label { font-size: 13px; min-width: 78px; padding-top: 2px; }
.kw-modal .kw-pill { font-size: 11.5px; padding: 2px 8px 2px 9px; }
.kw-modal .kw-add-input { width: 100px; font-size: 11.5px; padding: 2px 9px; }

.kw-modal-footer {
  padding: 12px 18px;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-shrink: 0;
  background: var(--surface);
}
.kw-quality-bar { display: flex; align-items: center; gap: 10px; }
.kw-quality-track {
  width: 64px;
  height: 4px;
  background: var(--surface-stone);
  border-radius: 2px;
  overflow: hidden;
}
.kw-quality-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
}
.kw-quality-label {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-dim);
  display: flex;
  align-items: baseline;
  gap: 5px;
}
.kw-quality-label .num { color: var(--text); font-weight: 500; }
.kw-modal-footer .footer-actions { display: flex; gap: 8px; }


/* ── 24. Floating selection bar ───────────────────────────── */
#selection-float {
  position: fixed;
  bottom: 20px;
  left: calc(var(--sidebar-w) + 24px);
  right: 24px;
  padding: 9px 12px;
  border-radius: 10px;
  display: none;
  align-items: center;
  gap: 10px;
  z-index: 1000;
  box-shadow: var(--shadow-hover);
  background: var(--surface);
  border: 1px solid var(--border);
  max-width: calc(100vw - var(--sidebar-w) - 48px);
}
#selection-float.sel-mode {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}


/* ── 26. Forms (text input / select / textarea / label) ───── */
.label {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text-muted);
  margin-bottom: var(--space-xs);
}
.input, .select, .textarea {
  width: 100%;
  font-family: inherit;
  font-size: var(--fs-base);
  color: var(--text);
  background: var(--surface-input);
  border: 1px solid var(--border);
  border-radius: var(--radius-button);
  padding: 8px 11px;
  line-height: 1.4;
  transition: border-color 0.12s, box-shadow 0.12s, background 0.12s;
}
.input::placeholder, .textarea::placeholder { color: var(--text-quiet); }
.input:hover, .select:hover, .textarea:hover { border-color: var(--border-warm); }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  background: var(--surface);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-light);
}
.textarea { resize: vertical; min-height: 72px; }
.input:disabled, .select:disabled, .textarea:disabled { opacity: 0.5; cursor: not-allowed; }

/* Field label in the editorial voice — small mono caps, like an eyebrow */
.label.label-mono {
  font-family: var(--font-mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: var(--space-sm);
}
/* Roomier input for the one field a dialog is about */
.input.input-lg { padding: 11px 14px; font-size: 14px; }
/* One field = label + control + the gap to the next thing */
.field { margin-bottom: var(--space-xl); }

/* Switch — one on/off setting (the Create batch dialog's workflow rows).
   The checkbox stays in the page, invisible over the track, so Tab reaches
   it, Space flips it and a screen reader announces it. The batch list used
   to hide it with display:none, which took both of its switches out of the
   keyboard's reach. */
.switch { position: relative; display: inline-block; width: 42px; height: 24px; flex-shrink: 0; }
.switch input {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; margin: 0;
  opacity: 0; cursor: pointer;
}
.switch-slider {
  position: absolute; inset: 0;
  background: var(--border-warm);
  border-radius: var(--radius-round);
  pointer-events: none;
  transition: background-color var(--dur-fast) ease;
}
.switch-slider::before {
  content: "";
  position: absolute; top: 3px; left: 3px;
  width: 18px; height: 18px;
  background: #FFFFFF;          /* the knob: white on either track, both themes */
  border-radius: 50%;
  box-shadow: var(--shadow-soft);
  transition: transform var(--dur-base) var(--ease-out);
}
.switch input:checked + .switch-slider { background: var(--accent); }
.switch input:checked + .switch-slider::before { transform: translateX(18px); }
.switch input:focus-visible + .switch-slider { outline: var(--focus-outline); outline-offset: 2px; }
.switch input:disabled { cursor: not-allowed; }
.switch input:disabled + .switch-slider { opacity: 0.45; }

/* A switch row in a settings card: what it does on the left (a title and a
   line of small print), the switch on the right. FTP's New batches, and
   Profile's Downloads. */
.settings-row.is-toggle { justify-content: space-between; }
.toggle-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.toggle-title { font-weight: 600; font-size: 13.5px; color: var(--text); }
.fact-sub { font-size: 12px; color: var(--text-dim); }


/* ── 27. Modal (overlay + panel) ───────────────────────────
   Was hand-rolled on three separate pages. One recipe: a blurred warm
   scrim, a surface panel, a serif heading, actions right-aligned. */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  padding: var(--space-xl);
  background: rgba(28, 25, 23, 0.5);
  backdrop-filter: blur(8px) saturate(1.1);
  -webkit-backdrop-filter: blur(8px) saturate(1.1);
  display: none;
  align-items: center;
  justify-content: center;
  /* Open and close in one motion: the scrim fades, the panel rises into
     place and sinks back the way it came — quicker out than in, since the
     reader has already decided. Transitions, not keyframes, so a dialog
     closed while still arriving turns round where it is. A browser without
     @starting-style simply opens and closes at once, as before. */
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out), display var(--dur-fast);
  transition-behavior: allow-discrete;
}
.modal-overlay.open { display: flex; opacity: 1; transition-duration: var(--dur-base); }
.modal-overlay:not(.open) { pointer-events: none; }   /* on its way out, clicks reach the page */
.modal-overlay .modal {
  transform: translateY(var(--motion-rise)) scale(var(--motion-scale-in));
  transition: transform var(--dur-fast) var(--ease-out);
}
.modal-overlay.open .modal { transform: none; transition-duration: var(--dur-base); }
@starting-style {
  .modal-overlay.open { opacity: 0; }
  .modal-overlay.open .modal { transform: translateY(var(--motion-rise)) scale(var(--motion-scale-in)); }
}
.modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-modal);
  padding: 30px;
  width: 540px;
  max-width: 92vw;
  box-shadow: var(--shadow-modal);
}
.modal.modal-lg { width: 640px; max-height: 80vh; display: flex; flex-direction: column; }
.modal h2 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 26px;
  letter-spacing: -0.015em;
  margin-bottom: 6px;
  color: var(--text);
}
.modal .subtitle {
  color: var(--text-dim);
  font-size: 13.5px;
  margin-bottom: var(--space-xl);
  line-height: 1.5;
}
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: var(--space-lg);
}

/* The "are you sure?" dialog (static/js/ui/confirm-dialog.js): the modal
   above, narrower, holding one question, what will happen, and whether it
   can be undone in bold.
   It is the top layer, as the browser's confirm() it replaces was: a
   question can be asked from inside the image editor (1500), the cost panel
   (2000) or a keyword menu (10000), and one under them would be invisible
   while it still held the keyboard. The highest layer on any page is the
   concept lightbox at 10001; tests/unit/ui/confirm-dialog.stacking.test.js
   fails if anything goes above this. */
.modal-overlay.confirm-dialog { z-index: 11000; }
.confirm-dialog .modal { width: 440px; padding: 26px 28px 22px; }
.confirm-dialog h2 { font-size: 22px; margin-bottom: 10px; }
.confirm-dialog .confirm-body {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.confirm-dialog .confirm-body b { color: var(--text); font-weight: 600; }
/* The links a notice hands out (an export already made: one per ZIP). */
.confirm-dialog .confirm-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin: var(--space-md) 0 0;
  padding: 0;
  list-style: none;
  font-size: 13.5px;
  line-height: 1.4;
}
.confirm-dialog .confirm-links a {
  color: var(--accent);
  text-decoration: none;
  overflow-wrap: anywhere;
}
.confirm-dialog .confirm-links a:hover { color: var(--accent-hover); text-decoration: underline; }
.confirm-dialog .modal-actions { margin-top: 22px; align-items: center; }
/* A quiet line beside the buttons, saying where the confirm button leads. */
.confirm-dialog .confirm-footnote {
  flex: 1 1 auto;
  margin-right: var(--space-sm);
  font-size: var(--fs-xs);
  line-height: 1.4;
  color: var(--text-dim);
}
@media (max-width: 520px) {
  .confirm-dialog .modal { padding: 22px 20px 20px; }
  .confirm-dialog .modal-actions { flex-direction: column-reverse; align-items: stretch; }
  .confirm-dialog .modal-actions .btn { justify-content: center; }
  .confirm-dialog .confirm-footnote { margin: var(--space-xs) 0 0; text-align: center; }
}


/* ── 28. Page shell — the centered page (Batches, Upload, Account) ──
   One wrapper for every page that is not the sidebar app-shell, so the
   list, the upload step and the account pages share one width, one header
   rhythm and one back-affordance. Wide data pages add .page-wide. */
.page {
  max-width: var(--content);
  margin: 0 auto;
  padding: 56px 32px 80px;
}
.page.page-wide   { max-width: var(--content-wide); }
.page.page-narrow { max-width: var(--content-narrow); }

.page-header {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-xl);
  margin-bottom: 34px;
}
.page-header h1 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 42px;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--text);
}
/* Title block: optional back-link above, h1 with an optional status pill beside it */
.page-title { display: flex; flex-direction: column; gap: var(--space-md); min-width: 0; }
.page-title .title-row { display: flex; align-items: center; gap: var(--space-lg); min-width: 0; }
/* The pills beside the name (the phase, the retention state) keep their one
   line; the name is what gives way, and it has its ellipsis for that. Without
   this a tight row folded "Ready to process" into three lines. */
.page-title .title-row .pill { flex: none; white-space: nowrap; }
.page-title h1 {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* overflow:hidden cuts at the line box, and at line-height 1 the tails of
     g, j, p, q and y hang below it — "Billing" and "Learning" lost theirs.
     The padding moves the cut out past the letters; the equal negative
     margin keeps the heading exactly where it was. */
  padding-block: 0.2em;
  margin-block: -0.2em;
}
.header-actions { display: flex; gap: 10px; align-items: center; flex-shrink: 0; }
/* When the title block carries a .page-lede, the actions line up with the h1, not the lede */
.page-header.with-lede { align-items: flex-start; }
.page-header.with-lede .header-actions { margin-top: 6px; }

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
  text-decoration: none;
  letter-spacing: 0.02em;
  transition: color 0.12s;
}
.back-link:hover { color: var(--text); }

/* One sentence under the h1 saying what the page is for (admin pages) */
.page-lede {
  font-size: var(--fs-md);
  color: var(--text-dim);
  line-height: 1.5;
  max-width: 680px;
}


/* ── 29. User menu & avatar ──────────────────────────────────
   The profile dropdown (templates/_user_menu.html) and the initials
   avatar it is built from. Lived in auth.css; every v5 page needs it. */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  color: #FFFFFF;             /* initials on a saturated hue: white in both themes */
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.3px;
  user-select: none;
  flex-shrink: 0;
  border: 2px solid var(--surface);
}
.avatar.lg { width: 36px; height: 36px; font-size: 13px; }
.avatar.xl { width: 64px; height: 64px; font-size: 22px; }
/* An uploaded photo fills the circle; the initials are the fallback. */
.avatar > img { display: block; width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
.avatar-stack { display: inline-flex; align-items: center; }
.avatar-stack .avatar + .avatar { margin-left: -6px; }
.avatar-stack .overflow {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 24px; height: 24px; border-radius: 999px;
  background: var(--surface-stone); color: var(--text-dim);
  font-size: 10px; padding: 0 6px; margin-left: -6px;
  border: 2px solid var(--surface);
}
/* A viewer who just left fades for a few seconds (presence, static/js/ui/avatar.js) */
.avatar.ghost {
  opacity: 0.35;
  filter: saturate(0.3);
  transition: opacity 0.4s ease;
}

/* The profile-photo cropper (static/js/ui/avatar-crop.js): the photo behind
   a circle, the outside dimmed, a zoom slider underneath. Any size of photo
   goes in; a 512 px square comes out. Jacob 2026-10-19. */
.avatar-crop { display: flex; flex-direction: column; align-items: center; gap: var(--space-lg); }
.avatar-crop-stage {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-card);
  background: var(--surface-stone);
  cursor: grab;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
.avatar-crop-stage:active { cursor: grabbing; }
.avatar-crop-stage:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.avatar-crop-img {
  position: absolute;
  left: 0;
  top: 0;
  max-width: none;
  transform-origin: 0 0;
  pointer-events: none;
}
/* Scrim over the photo outside the circle; raw colour on purpose (a scrim over a photograph). */
.avatar-crop-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: 0 0 0 999px rgba(28, 25, 23, 0.55);
  outline: 2px solid rgba(255, 255, 255, 0.9);
  outline-offset: -2px;
  pointer-events: none;
}
.avatar-crop-zoom {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  width: 240px;
  color: var(--text-dim);
}
.avatar-crop-zoom input[type="range"] { flex: 1 1 auto; accent-color: var(--accent); }
.avatar-crop-modal { width: 420px; }
.avatar-crop-modal .avatar-crop-host { margin: var(--space-sm) 0 var(--space-xl); }

.user-menu { position: relative; display: inline-flex; align-items: center; }
.user-menu-trigger {
  display: flex; align-items: center; gap: 8px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 8px 4px 4px;
  color: var(--text);
  cursor: pointer;
  font-size: 13px;
  font-family: inherit;
}
.user-menu-trigger:hover { border-color: var(--accent); }
.user-menu-trigger:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
.user-menu-trigger .chevron { color: var(--text-dim); font-size: 11px; }
.user-menu-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 220px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 6px;
  box-shadow: var(--shadow-hover);
  z-index: 500;
  /* It grows out of the button that opened it (the rail moves the origin
     to the avatar, app-nav.css) and folds back into it when closed. */
  transform-origin: top right;
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out),
              display var(--dur-fast);
  transition-behavior: allow-discrete;
}
.user-menu-panel[hidden] { display: none; opacity: 0; transform: scale(var(--motion-scale-in)); }
@starting-style {
  .user-menu-panel:not([hidden]) { opacity: 0; transform: scale(var(--motion-scale-in)); }
}
.user-menu-panel .header { padding: 10px 12px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.user-menu-panel .header .name  { font-weight: 600; font-size: 14px; }
.user-menu-panel .header .email { color: var(--text-dim); font-size: 12px; }
.user-menu-panel a,
.user-menu-panel button {
  display: block; width: 100%; text-align: left;
  padding: 8px 12px;
  background: transparent; color: var(--text);
  border: none; font: inherit; text-decoration: none;
  cursor: pointer; border-radius: 6px; font-size: 13px;
}
.user-menu-panel a:hover,
.user-menu-panel button:hover { background: var(--surface-stone); }
.user-menu-panel .divider { height: 1px; background: var(--border); margin: 4px 0; }


/* ── 30. Data table (admin ledgers) ────────────────────────────
   Session Overview, Audit log, Email queue and Team share one table
   recipe: a card with an optional toolbar on top and a footer for paging,
   mono caps column heads like the batch list, quiet row rules, numbers
   right-aligned in the mono face. */
.table-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
/* A ledger wider than its card scrolls inside the card; the page never scrolls sideways */
.table-scroll { overflow-x: auto; }

.toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 13px 20px;
  border-bottom: 1px solid var(--border-soft);
}
.toolbar-search {
  flex: 1;
  min-width: 160px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.toolbar-search > span { color: var(--text-quiet); font-size: 13px; line-height: 1; }
.toolbar-search input {
  flex: 1; min-width: 0;
  background: transparent; border: none; outline: none;
  color: var(--text); font-family: var(--font-body); font-size: 13.5px;
}
.toolbar-search input::placeholder { color: var(--text-quiet); }
.toolbar .input, .toolbar .select {
  width: auto; height: 30px; padding: 0 10px; font-size: var(--fs-sm);
}
.toolbar .label { margin: 0 0 0 var(--space-xs); }

.data-table { width: 100%; border-collapse: collapse; }
.data-table thead th {
  text-align: left;
  padding: 11px 20px;
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-quiet);
  font-weight: 500;
  background: var(--surface-warm);
  border-bottom: 1px solid var(--border-soft);
  white-space: nowrap;
}
.data-table thead th.sortable { cursor: pointer; user-select: none; }
.data-table thead th.sortable:hover { color: var(--text-muted); }
.data-table thead th.sorted { color: var(--text); }
.data-table thead th .sort-arrow { margin-left: 4px; color: var(--accent); }
.data-table tbody td {
  padding: 13px 20px;
  border-bottom: 1px solid var(--border-soft);
  font-size: var(--fs-base);
  vertical-align: middle;
}
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover td { background: var(--surface-warm); }
.data-table .num  { text-align: right; font-family: var(--font-mono); font-size: 11.5px; font-variant-numeric: tabular-nums; }
.data-table .mono { font-family: var(--font-mono); font-size: 11.5px; }
.data-table .dim  { color: var(--text-dim); }
.data-table .row-title { font-weight: 600; color: var(--text); letter-spacing: -0.01em; text-decoration: none; }
.data-table a.row-title:hover { color: var(--accent); }
.data-table .row-sub { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-dim); margin-top: 2px; }
.data-table .nowrap { white-space: nowrap; }
/* A state row (loading / empty / error) fills the table and never highlights */
.data-table tr.state td { padding: 0; }
.data-table tr.state:hover td { background: transparent; }

/* Empty, loading and error states — one shape, the words belong to the page */
.table-state {
  padding: var(--space-3xl) var(--space-xl);
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-md);
  text-align: center;
  color: var(--text-dim);
  font-size: var(--fs-base);
  line-height: 1.5;
}
.table-state .title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 500;
  color: var(--text);
  letter-spacing: -0.01em;
}
.table-state.error .title { color: var(--red); }
.table-state .btn { margin-top: var(--space-xs); }

.table-foot {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--space-lg);
  padding: 10px 20px;
  border-top: 1px solid var(--border-soft);
  background: var(--surface-warm);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-dim);
}
.table-foot .paging { display: flex; align-items: center; gap: var(--space-sm); }


/* ── 31. Segmented switch (two or three exclusive options) ─────── */
.segmented {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: var(--radius-button);
  overflow: hidden;
  background: var(--surface);
}
.segmented button {
  border: none;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 500;
  padding: 0 12px;
  height: 28px;
  line-height: 1;
  transition: background 0.12s, color 0.12s;
}
.segmented button:focus-visible { outline: var(--focus-outline); outline-offset: -2px; border-radius: inherit; }
.segmented button + button { border-left: 1px solid var(--border); }
.segmented button:hover { color: var(--text); background: var(--surface-warm); }
.segmented button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
/* The same switch made of links (the recap's Before/After): a plain <a> per
   option, the current one marked with aria-current, so it works with no
   script and survives a reload. */
.segmented a {
  display: inline-flex; align-items: center;
  color: var(--text-dim); text-decoration: none;
  font-size: var(--fs-sm); font-weight: 500;
  padding: 0 12px; height: 28px; line-height: 1;
  transition: background 0.12s, color 0.12s;
}
.segmented a:focus-visible { outline: var(--focus-outline); outline-offset: -2px; border-radius: inherit; }
.segmented a + a { border-left: 1px solid var(--border); }
.segmented a:hover { color: var(--text); background: var(--surface-warm); }
.segmented a[aria-current="page"] { background: var(--accent); color: var(--accent-ink); }


/* ── 32. Icon switch (two exclusive options, each an icon) ─────── */
/* The quiet sibling of the segmented switch, for a preference that sits
   beside content it must not shout over (the report's Grid|List and
   Fill|Fit). Selected is ink on stone, never the accent: on that page green
   means "done". Name every button (title + aria-label) — the icon is all
   that shows. */
.icon-switch {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius-button);
  background: var(--surface);
}
.icon-switch button {
  width: 28px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: 4px;
  background: transparent;
  color: var(--text-quiet);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color var(--dur-press) ease, background-color var(--dur-press) ease;
}
.icon-switch button svg { width: 14px; height: 14px; display: block; }
.icon-switch button:hover { color: var(--text); }
.icon-switch button[aria-pressed="true"] { background: var(--surface-stone); color: var(--text); }
.icon-switch button:focus-visible { outline: var(--focus-outline); outline-offset: -2px; }


/* ── 32. Notice — one sentence of status, coloured by meaning ───
   Green = healthy, red = stopped or broken, orange = the machine is
   working or something is pending. Neutral for plain information. */
.notice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  padding: 12px 16px;
  margin-bottom: var(--space-xl);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface-warm);
  color: var(--text-muted);
  font-size: var(--fs-base);
  line-height: 1.5;
}
.notice[hidden] { display: none; }
.notice b { color: var(--text); font-weight: 600; }
.notice .btn { margin-left: auto; flex-shrink: 0; }
.notice-green  { background: var(--accent-light); border-color: var(--accent-border); color: var(--accent); }
.notice-red    { background: var(--red-bg);       border-color: var(--red-border);    color: var(--red); }
.notice-orange { background: var(--orange-bg);    border-color: var(--orange-border); color: var(--orange); }
.notice-green b, .notice-red b, .notice-orange b { color: inherit; }
/* The free signup credits' card (static/js/ui/free-credits-bar.js): the
   coins, one heading, one sentence and the button, on a white card; on a
   phone the buttons go under the words. Design A, Jacob 2026-10-19. */
.free-credits-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 18px;
  padding: 18px 20px;
  margin-bottom: var(--space-xl);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
.free-credits-card .free-credits-icon {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-round);
  background: var(--accent-light);
  border: 1px solid var(--accent-border);
  color: var(--accent);
}
.free-credits-card .free-credits-text { flex: 1 1 360px; min-width: 0; }
.free-credits-card .free-credits-title {
  margin: 0 0 3px;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 18px;
  letter-spacing: -0.01em;
  color: var(--text);
}
.free-credits-card .free-credits-line { margin: 0; font-size: 13.5px; line-height: 1.5; color: var(--text-dim); }
.free-credits-card .free-credits-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
@media (max-width: 560px) {
  .free-credits-card .free-credits-icon { display: none; }   /* on a phone the words need the row */
}


/* ── 33. Settings group — a card of labelled rows (Profile, Team) ──
   One heading, an optional intro line, rows of mono label · value · action,
   and an action strip at the bottom. Tables inside use .data-table. */
.settings-group {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  margin-bottom: var(--space-xl);
  overflow: hidden;
}
.settings-group > h2 {
  font-family: var(--font-body);
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  padding: 16px 22px 14px;
  display: flex;
  align-items: baseline;
  gap: 10px;
}
.settings-group > h2 .meta {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
}
.settings-group > .intro {
  font-size: var(--fs-base);
  color: var(--text-dim);
  line-height: 1.5;
  padding: 0 22px 14px;
  margin-top: -4px;
}
/* Free-form content at the rows' inset — a hero figure, a notice, a chart.
   Rows stay .settings-row; this is for the one thing in a card that is not
   a labelled row (Billing's balance). */
.settings-group > .settings-body { padding: 4px 22px 18px; }
.settings-group > .settings-body .notice { margin: 0; }
.settings-group > .settings-body .notice + .notice { margin-top: var(--space-md); }
/* A note that follows a strip or a table gets its own rule and some air,
   instead of hanging off the last row. */
.settings-group > .stats-strip + .settings-note,
.settings-group > .table-scroll + .settings-note {
  border-top: 1px solid var(--border-soft);
  padding-top: 12px;
  margin-top: 0;
}
.settings-row {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding: 14px 22px;
  min-height: 56px;
  border-top: 1px solid var(--border-soft);
  font-size: 14px;
}
.settings-row[hidden] { display: none; }
.settings-row .label { width: 132px; flex-shrink: 0; margin: 0; }
.settings-row .value {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
  color: var(--text-warm);
}
.settings-row .value a { color: var(--accent); text-decoration: none; font-weight: 500; }
.settings-row .value a:hover { text-decoration: underline; text-underline-offset: 3px; }
.settings-row .input, .settings-row .select { max-width: 320px; }
.settings-note {
  padding: 0 22px 14px;
  margin-top: -4px;
  font-size: var(--fs-sm);
  color: var(--text-dim);
}
.settings-note:empty { display: none; }
.settings-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 16px 22px 18px;
  border-top: 1px solid var(--border-soft);
}
.settings-group .data-table thead th { border-top: 1px solid var(--border-soft); }
.settings-group .data-table thead th, .settings-group .data-table tbody td { padding-left: 22px; padding-right: 22px; }
.settings-group .data-table tbody tr:last-child td { border-bottom: none; }


/* ── 34. Live dot — something is happening right now ──────────── */
.live-dot {
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-light);
  animation: live-pulse 2s infinite;
  vertical-align: middle;
}
@keyframes live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }


/* Reveal — a section that unfolds in place instead of appearing.
   Markup: .reveal > .reveal-inner > content. The script still only sets
   `hidden`; here [hidden] means folded: a zero-height grid row, faded out,
   and visibility:hidden so neither Tab nor a screen reader reaches it. The
   row animates between 0fr and 1fr, so nothing measures a height. The inner
   box reaches 4px past each edge (margin -4px, padding 4px) so its clip
   never cuts a focus ring. Folds quicker than it opens, like the dialog.
   Used by the folds below, the Getty/iStock fields (§37) and concept review
   in the Create batch dialog. */
.reveal {
  display: grid;
  grid-template-rows: 1fr;
  opacity: 1;
  transition: grid-template-rows var(--dur-base) var(--ease-out),
              opacity var(--dur-base) var(--ease-out),
              visibility 0s;
}
.reveal[hidden] {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows var(--dur-fast) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out),
              visibility 0s var(--dur-fast);
}
.reveal > .reveal-inner { min-height: 0; overflow: hidden; margin: -4px; padding: 4px; }
/* Reduced motion: no growing, only the fade. */
@media (prefers-reduced-motion: reduce) {
  .reveal { transition: opacity var(--dur-base) ease, visibility 0s; }
  .reveal[hidden] { transition: opacity var(--dur-fast) ease, visibility 0s var(--dur-fast); }
}

/* ── 35. Folded settings — a dialog's optional sections ─────────
   Studio v1 put "Instructions (optional)" and "Keywords (optional)" behind
   an accordion in its Add-new-folder dialog: out of the way of the one field
   that matters (the name), one click from anyone who wants them. Same shape
   here, shared by the Create batch dialog and the batch page's Custom
   settings dialog — see templates/_custom_settings.html and
   static/js/ui/custom-settings.js. A closed fold still says whether it is
   carrying anything (.fold-summary), because a setting you cannot see is a
   setting you forget you set. */
.fold {
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-button);
  background: var(--surface-warm);
  margin-bottom: 8px;
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.fold.is-open { border-color: var(--border-warm); background: var(--surface); }
.fold-head {
  width: 100%;
  display: flex; align-items: center; gap: 10px;
  padding: 13px 16px;
  background: none; border: none; cursor: pointer; text-align: left;
  font-family: inherit; font-size: 13.5px; font-weight: 600; color: var(--text);
}
.fold-head:hover { color: var(--accent); }
.fold-head:focus-visible { outline: var(--focus-outline); outline-offset: -2px; border-radius: var(--radius-button); }
.fold-head .optional { font-weight: 400; color: var(--text-quiet); }
.fold-summary {
  margin-left: auto;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.04em;
  color: var(--text-dim); font-weight: 500;
}
.fold-summary:empty { display: none; }
.fold-caret {
  color: var(--text-quiet); font-size: 15px; line-height: 1;
  transition: transform var(--dur-base) var(--ease-out);
}
.fold.is-open .fold-caret { transform: rotate(90deg); }
.fold-body { /* a .reveal: it unfolds under the head */ }
.fold-content { padding: 0 16px 14px; }
.fold-help {
  color: var(--text-dim); font-size: 12px; line-height: 1.5;
  margin: 0 0 8px;
}
/* The keyword field: the editor's own pill (report_pixify.html .pill-edit /
   fast-list.css .fl-pill — fully round, 3px 10px, 11.5px, text on surface
   with a hairline border, a thin × on hover) and the dashed add-input (§17),
   laid out as one bordered field so it reads as an input rather than a list.
   One look for a keyword wherever it appears (Jacob, 2026-09-16).

   Each pill carries where the keyword goes on every file — its placement —
   as a colour on the pill and a dot before the word: plain is "anywhere"
   (the default, the editor's pill exactly), green "first" (the accent: this
   is the term the file is found by), sand "tail" (warm and quiet: on the
   file, at the back). The pills stand in those three groups, and one whose
   placement changes slides to its group (custom-settings.js, FLIP). */
.kw-field {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 9px;
  padding: 8px 9px;
  background: var(--surface-input);
  border: 1px solid var(--border);
  border-radius: var(--radius-button);
}
.kw-field:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-light); }
.kw-field .kw-add-input { flex: 1 1 200px; width: auto; border-color: transparent; }
.kw-field .kw-add-input:focus { width: auto; background: transparent; border-color: transparent; }
.kw-field .kw-pill {
  gap: 5px;
  padding: 3px 6px 3px 10px;
  border-radius: var(--radius-round);
  font-size: 11.5px;
  letter-spacing: -0.005em;
  line-height: 1.3;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  cursor: default;
  user-select: none;
  transition: transform var(--dur-base) var(--ease-in-out),
              background-color var(--dur-press) ease, border-color var(--dur-press) ease,
              color var(--dur-press) ease, box-shadow var(--dur-press) ease;
}
.kw-field .kw-pill:hover { border-color: var(--text-quiet); }
.kw-field .kw-pill:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-border);
}
.kw-field .kw-pill.is-first {
  color: var(--accent); background: var(--accent-light); border-color: var(--accent-border);
}
.kw-field .kw-pill.is-tail {
  color: var(--sand); background: var(--sand-bg); border-color: var(--sand-border);
}
.kw-field .kw-pill.is-first:hover { border-color: var(--accent); }
.kw-field .kw-pill.is-tail:hover { border-color: var(--sand); }
.kw-pill-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 6px; }
/* The × is a bare glyph, faint until the pointer or the focus is on its pill. */
.kw-field .kw-pill .pill-x {
  appearance: none; -webkit-appearance: none;
  width: 12px; height: 14px; margin: 0; padding: 0;
  border: 0; border-radius: 0; background: transparent;
  font: inherit; font-size: 13px; line-height: 1;
  color: var(--text-dim);
  opacity: 0;
  cursor: pointer;
  transition: opacity var(--dur-press) ease, color var(--dur-press) ease;
}
.kw-field .kw-pill:hover .pill-x,
.kw-field .kw-pill:focus-visible .pill-x { opacity: 0.65; }
.kw-field .kw-pill .pill-x:hover { opacity: 1; color: var(--red); background: transparent; }
@media (hover: none) { .kw-field .kw-pill .pill-x { opacity: 0.65; } }
@media (prefers-reduced-motion: reduce) { .kw-field .kw-pill { transition: none; } }

/* The key under the field: the three placements in their colours, and how
   to change one. */
.kw-legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin-top: 8px;
  font-size: 11px; color: var(--text-dim);
}
.kw-legend-key {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 8px;
  border-radius: var(--radius-round);
  font-size: 10.5px; font-weight: 500;
  color: var(--text-muted); background: var(--surface); border: 1px solid var(--border);
}
.kw-legend-key::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; opacity: 0.6; }
.kw-legend-key.is-first { color: var(--accent); background: var(--accent-light); border-color: var(--accent-border); }
.kw-legend-key.is-tail { color: var(--sand); background: var(--sand-bg); border-color: var(--sand-border); }
.kw-legend-hint { margin-left: 4px; }

/* The keyword menu (click a pill): First / Anywhere / Tail, then Remove. The
   editor's pill menu (fast-list.css), with a line of help under each choice. */
.kw-menu {
  position: fixed; z-index: 1100;
  min-width: 250px; max-width: 320px;
  padding: 6px;
  border-radius: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-hover);
}
.kw-menu[hidden] { display: none; }
.kw-menu-heading {
  padding: 6px 12px 4px;
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.kw-menu-item {
  display: flex; align-items: flex-start; gap: 9px;
  width: 100%; padding: 7px 12px;
  border: 0; border-radius: 7px; background: none;
  font-family: var(--font-body); font-size: 12.5px; text-align: left;
  color: var(--text); cursor: pointer;
}
.kw-menu-item:hover, .kw-menu-item:focus-visible { background: var(--surface-stone); outline: none; }
.kw-menu-item.is-danger { color: var(--red); }
.kw-menu-item.is-danger:hover, .kw-menu-item.is-danger:focus-visible { background: var(--red-bg); }
.kw-menu-mark {
  flex: 0 0 8px; width: 8px; height: 8px; margin-top: 5px;
  border-radius: 50%; border: 1px solid var(--text-quiet); background: transparent;
}
.kw-menu-item.is-first .kw-menu-mark { border-color: var(--accent); }
.kw-menu-item.is-tail .kw-menu-mark { border-color: var(--sand); }
.kw-menu-item[aria-checked="true"] .kw-menu-mark { background: currentColor; border-color: currentColor; color: var(--text-muted); }
.kw-menu-item.is-first[aria-checked="true"] .kw-menu-mark { color: var(--accent); }
.kw-menu-item.is-tail[aria-checked="true"] .kw-menu-mark { color: var(--sand); }
.kw-menu-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.kw-menu-text b { font-weight: 600; }
.kw-menu-text small { font-size: 11px; line-height: 1.4; color: var(--text-dim); }
.kw-menu-divider { height: 1px; margin: 4px 2px; background: var(--border-soft); }


/* "Copy from a batch" (2026-10-24): each fold's own quiet button beside its
   help text, the panel it opens, and the line under the field that says
   where the copy came from, with Undo. Instructions and keywords copy
   separately (custom-settings.js, copy_settings.py). The panel is the
   keyword menu's kind of floating card, fixed to the window over the
   dialog; the line unfolds as a .reveal, so nothing below it jumps. */
.fold-help-row { display: flex; align-items: flex-start; gap: 14px; margin: 0 0 8px; }
.fold-help-row .fold-help { flex: 1; min-width: 0; margin: 0; }
.btn.fold-copy { flex-shrink: 0; color: var(--accent); border-color: var(--accent-border); }
.btn.fold-copy:hover,
.btn.fold-copy[aria-expanded="true"] { background: var(--accent-light); border-color: var(--accent); }
.fold-copied-line {
  display: flex; align-items: baseline; gap: 8px;
  margin: 8px 0 0;
  font-size: 12px; line-height: 1.4; color: var(--accent-hover);
}
.fold-copied-line span { min-width: 0; overflow-wrap: anywhere; }
.fold-copied-line b { font-weight: 600; }
.fold-undo {
  margin-left: auto; flex-shrink: 0;
  padding: 0; border: 0; background: none; cursor: pointer;
  font-family: inherit; font-size: 12px; color: var(--accent);
  text-decoration: underline; text-underline-offset: 2px;
}
.fold-undo:hover { color: var(--accent-hover); }
.fold-undo:focus-visible { outline: var(--focus-outline); outline-offset: 2px; border-radius: 2px; }
.copy-pop {
  position: fixed; z-index: 1100;
  width: 400px; max-width: calc(100vw - 16px);
  overflow: hidden;
  border-radius: var(--radius-card);
  background: var(--surface);
  border: 1px solid var(--border-warm);
  box-shadow: var(--shadow-float);
}
.copy-pop[hidden] { display: none; }
.copy-pop-top { padding: 12px 12px 8px; border-bottom: 1px solid var(--border-soft); }
.copy-pop-title { margin: 0 0 8px; font-size: 13px; font-weight: 600; color: var(--text); }
.copy-pop-search.input { padding: 7px 10px; font-size: 12.5px; }
.copy-pop-list { max-height: 300px; overflow-y: auto; padding: 6px; overscroll-behavior: contain; }
.copy-pop-item {
  display: block; width: 100%;
  padding: 10px; border: 0; border-radius: 8px; background: none;
  font-family: inherit; text-align: left; color: var(--text); cursor: pointer;
}
.copy-pop-item:hover, .copy-pop-item:focus-visible {
  outline: none;
  background: var(--surface-warm);
  box-shadow: inset 0 0 0 1px var(--border-soft);
}
.copy-pop-item:focus-visible { box-shadow: inset 0 0 0 1px var(--accent); }
.copy-pop-item-top { display: flex; align-items: baseline; gap: 10px; }
.copy-pop-item-name {
  flex: 1; min-width: 0;
  font-size: 12.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.copy-pop-item-when {
  font-family: var(--font-mono); font-size: 10.5px; color: var(--text-dim); white-space: nowrap;
}
.copy-pop-item-body {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
  margin-top: 3px;
  font-size: 12px; line-height: 1.45; color: var(--text-muted);
}
.copy-pop-empty { margin: 0; padding: 22px 12px; text-align: center; font-size: 12px; color: var(--text-dim); }
.copy-pop-foot {
  margin: 0; padding: 9px 14px;
  border-top: 1px solid var(--border-soft);
  background: var(--surface-warm);
  font-size: 11.5px; color: var(--text-dim);
}
@media (max-width: 520px) {
  .fold-help-row { flex-direction: column; gap: 8px; }
}

/* ── 37. Getty/iStock batch — Standard or Getty/iStock, and ESP's fields ──
   Studio v1's "Metadata model" choice (templates/_getty_settings.html,
   static/js/ui/getty-settings.js): two cards side by side, the chosen one
   in the accent tint the Create batch dialog's switched-on rows use, and
   under Getty/iStock the date created and the country of shoot. Plus the
   batch's quiet Getty/iStock pill beside its name, which opens those two
   fields in a dialog (the choice itself is made once, at Create). */
.getty-settings { margin-bottom: var(--space-xl); }
.getty-settings > .label { display: block; margin-bottom: 8px; }
.meta-choice {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 8px;
}
.meta-card {
  appearance: none;
  -webkit-appearance: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  padding: 12px 14px;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-button);
  background: var(--surface-warm);
  font: inherit;
  text-align: left;
  color: var(--text);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              transform var(--dur-press) var(--ease-out);
}
.meta-card:active { transform: scale(var(--press-scale)); }
.meta-card:hover { border-color: var(--border-warm); }
.meta-card:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
.meta-card[aria-pressed="true"] { border-color: var(--accent-border); background: var(--accent-light); }
.meta-card-head { display: flex; align-items: center; gap: 8px; }
.meta-card-name { font-weight: 600; font-size: 13.5px; }
.meta-card-help { font-size: 12px; line-height: 1.5; color: var(--text-dim); }
/* The chosen card's tick: an empty ring until chosen, then the accent disc
   fills in and the tick grows into it, the old one shrinking away. */
.meta-card-check {
  position: relative;
  flex: none;
  width: 14px;
  height: 14px;
  margin-left: auto;
  border-radius: 50%;
  border: 1.5px solid var(--border-warm);
  box-sizing: border-box;
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.meta-card-check::after {
  content: "";
  position: absolute;
  inset: 0;
  background: center / 9px 9px no-repeat
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 6.3l2.3 2.3 4.7-5' fill='none' stroke='white' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  opacity: 0;
  transform: scale(0.5);
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.meta-card[aria-pressed="true"] .meta-card-check {
  border-color: var(--accent);
  background-color: var(--accent);
}
.meta-card[aria-pressed="true"] .meta-card-check::after { opacity: 1; transform: none; }
/* .getty-fields is a .reveal: the fields unfold under the cards. Their
   columns are the cards' own, so the date sits under Standard and the
   country under Getty/iStock. */
.getty-fields-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px 8px;
  padding-top: 12px;
}
.getty-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
/* One height for both. Safari draws a native select at its own, shorter
   height and ignores the padding, and its date field at another, so both
   drop the native look; the select gets its chevron back as an image. The
   rule names :focus too, because .select:focus resets the background. */
.getty-field .input, .getty-field .select {
  width: 100%;
  height: 38px;
  appearance: none;
  -webkit-appearance: none;
}
.getty-field .select,
.getty-field .select:focus {
  padding-right: 32px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238C857C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 10px 6px;
  cursor: pointer;
}
.getty-field .input::-webkit-date-and-time-value { text-align: left; }
/* The date is optional: each photo's camera date is used (getty_batch.py). */
.getty-optional { font-weight: 400; color: var(--text-dim); }
.getty-help {
  grid-column: 1 / -1; margin: 0; font-size: 12px; line-height: 1.5; color: var(--text-dim);
}
.getty-error {
  grid-column: 1 / -1; margin: 0; font-size: 12px; line-height: 1.5; color: var(--red);
  transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
/* A complaint arrives with a short drop rather than a jump. */
@starting-style { .getty-error { opacity: 0; transform: translateY(-3px); } }
.getty-error[hidden] { display: none; }
/* Above the review page's Export menu (1600), which can open it. */
.modal-overlay.getty-dialog { z-index: 1700; }
.getty-dialog .modal { width: 520px; }
/* The batch's state beside its name: a pill that is also the way to change
   it, so it takes a pointer and a focus ring. */
.getty-pill {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  white-space: nowrap;
  font-family: var(--font-body);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.getty-pill:hover { border-color: var(--border-warm); color: var(--text); }
.getty-pill:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
/* The batch page's title row holds it in a slot the script can refill;
   the slot itself takes no room. */
.getty-pill-slot { display: contents; }
/* A test batch's pill beside its name (templates/_test_pill.html): an
   admin's mark that Studio learns nothing from the batch. Never shrinks or
   wraps. For an admin it is a button, the way to take the mark off, so it
   takes a pointer like the Getty/iStock pill. */
.test-pill { flex: none; white-space: nowrap; }
button.test-pill {
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--font-body);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
button.test-pill:hover { border-color: var(--border-warm); color: var(--text); }
@media (max-width: 560px) {
  .meta-choice, .getty-fields-grid { grid-template-columns: minmax(0, 1fr); }
}


/* ── 36. Dark theme ───────────────────────────────────────────
   Opt-in only: applies when [data-theme="dark"] is set on <html>. */
[data-theme="dark"] {
  /* Surfaces */
  --bg:               #14110D;
  --bg-warm:          #1A1611;
  --surface:          #1F1A14;
  --surface-warm:     #241E17;
  --surface-stone:    #2A231B;
  --surface-input:    #221C15;

  /* Borders */
  --border:           #352D23;
  --border-soft:      #2C261D;
  --border-warm:      #41382C;

  /* Text */
  --text:             #F7F3EC;
  --text-warm:        #EFEAE1;
  --text-muted:       #CFC7B9;
  --text-dim:         #A29789;
  --text-quiet:       #756B5E;

  /* Accent — brighter emerald stays legible on dark; ink flips to dark green */
  --accent:           #34D399;
  --accent-hover:     #6EE7B7;
  --accent-ink:       #052E22;
  --accent-light:     rgba(52,211,153,0.14);
  --accent-border:    rgba(52,211,153,0.38);

  /* Danger */
  --red:              #F0876A;
  --red-bg:           rgba(240,135,106,0.13);
  --red-border:       rgba(240,135,106,0.34);

  /* Warning */
  --orange:           #F2A65A;
  --orange-bg:        rgba(242,166,90,0.13);
  --orange-border:    rgba(242,166,90,0.32);

  /* Sand */
  --sand:             #D2A77B;
  --sand-bg:          rgba(210,167,123,0.12);
  --sand-border:      rgba(210,167,123,0.30);

  /* Shadows — deeper on dark */
  --shadow-soft:      0 1px 2px rgba(0,0,0,0.4);
  --shadow-card:      0 1px 3px rgba(0,0,0,0.45), 0 1px 2px rgba(0,0,0,0.3);
  --shadow-hover:     0 6px 20px rgba(0,0,0,0.5), 0 2px 6px rgba(0,0,0,0.35);
  --shadow-hero:      0 16px 40px -16px rgba(0,0,0,0.6), 0 6px 16px -6px rgba(0,0,0,0.4);
  --shadow-modal:     0 32px 80px -16px rgba(0,0,0,0.7), 0 8px 24px -8px rgba(0,0,0,0.5);
}

/* ── 40. Narrow screens ──────────────────────────────────────
   The v5 shell was designed at desktop width and carried no media queries
   at all until 2026-09-17. Two shared components actively break on a phone,
   and both are fixed here rather than page-by-page (tests/test_design_system
   .py::test_shared_components_are_defined_once enforces that):

   * `.page-header` is a flex row with `.header-actions` at flex-shrink:0
     beside an ellipsised h1, so the title collapses — "Billing" rendered as
     "B…" at 390px, and Profile's h1 collapsed to zero width. Wrapping puts
     the actions under the title instead.
   * `.settings-row` stays one flex row, so label + input + button overlap
     once the nav rail and card padding are subtracted. Stacking keeps every
     control tappable.

   Both are no-ops above the breakpoint. This is a targeted fix, not a
   responsive pass: the grids and tables on the admin pages have not been
   reviewed at this width. */
/* A tablet held upright (Monday 13068218439): a title and its buttons that do not
   fit on one line put the buttons under it, rather than cutting the title (a
   batch's name lost its tail beside "Start processing" at 768px). */
@media (max-width: 900px) {
  .page-header { flex-wrap: wrap; row-gap: var(--space-md); }
  /* A tablet held upright too: a long name wraps instead of ending in "…" (the phone rule below). */
  .page-title h1 { white-space: normal; overflow-wrap: anywhere; }
}
@media (max-width: 560px) {
  .page { padding: 32px 16px 56px; }

  .page-header { flex-wrap: wrap; gap: var(--space-md); }
  .page-header h1 { font-size: 32px; }
  .page-title { flex: 1 1 100%; }
  .header-actions { flex: 1 1 100%; }
  /* The pills beside a title drop under it when they do not fit, rather than
     squeezing the title to "Email qu…" (Monday 13068218439: no title may be
     cut on a phone). A name too long for the line on its own still takes the
     whole line. A phone has the room to wrap a title, not to cut it: "Rules &
     preferences" lost its last letters at 375px (the recap page already does
     this for its own title). */
  .page-title .title-row { flex-wrap: wrap; row-gap: var(--space-sm); }
  .page-title h1 { white-space: normal; overflow-wrap: anywhere; }

  /* A card heading with a meta label ("4 plans · billed monthly") cannot fit
     both on one phone-width line: the title would wrap mid-word. The meta
     drops under the title instead. */
  .settings-group > h2 { flex-wrap: wrap; row-gap: var(--space-xs); }
  .settings-group > h2 .meta { flex: 1 1 100%; margin-left: 0; }

  .settings-row { flex-wrap: wrap; align-items: flex-start; }
  .settings-row .label { width: auto; flex: 1 1 100%; }
  .settings-row .value { flex: 1 1 100%; }
  .settings-row > .btn { width: 100%; justify-content: center; }

  .stat-grid { grid-template-columns: minmax(0, 1fr); }
  .stats-strip, .stats-strip.cols-3 { grid-template-columns: minmax(0, 1fr); }
  .stats-strip .stat { border-right: 0; border-bottom: 1px solid var(--border-soft); }
  .stats-strip .stat:last-child { border-bottom: 0; }

  /* A ledger inside a settings card: tighter cells, so three columns fit a
     phone instead of scrolling sideways under the card's edge. */
  .settings-group .data-table thead th,
  .settings-group .data-table tbody td { padding-left: 12px; padding-right: 12px; }

  /* The toolbar and the foot of a ledger card (Engine Room's batches, the
     Audit log, the Email queue, Team; Monday 13194077088). The toolbar was one
     no-wrap row inside a card that hides what overflows it, so the search took
     the card and the date range, owner and page-size selects were cut off at
     its edge (measured scrollWidth 558 against 375). The search now has a line
     of its own and the selects share the next; the table below still scrolls
     inside its card. */
  .toolbar { flex-wrap: wrap; padding: 12px 16px; row-gap: var(--space-sm); }
  .toolbar-search { flex: 1 1 100%; min-width: 0; }
  .toolbar .select, .toolbar .input { flex: 1 1 8rem; min-width: 0; }
  .table-foot { flex-wrap: wrap; row-gap: var(--space-sm); padding-left: 16px; padding-right: 16px; }
}


/* Reduced transparency: the frosted bars turn solid. */
@media (prefers-reduced-transparency: reduce) {
  .top-bar { background: var(--bg); backdrop-filter: none; -webkit-backdrop-filter: none; }
}
