/* EditButler — mobile-first warm-clinic UI · paper + terracotta (theme E) */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
/* hidden must ALWAYS win: any element with a display rule (flex/grid/…) used
   to silently override the hidden attribute — the "We're editing it… + Watch
   showing at once on a never-recorded script" bug was exactly this. */
[hidden] { display: none !important; }

:root {
  /* surfaces — warm paper ground, white cards (recorder keeps its own dark camera chrome) */
  --bg: #f8f4ec;
  --bg-2: #f1ebdf;
  --card: #ffffff;
  --card-2: #f6f1e7;
  --card-3: #eee7d9;

  /* text — warm ink */
  --text: #241f18;
  --muted: #7d7466;
  --faint: #a39a8a;

  /* brand — terracotta, same family as the warm-editorial renders */
  --accent: #c05a2b;
  --accent-hover: #cd6a3c;
  --accent-press: #a94d22;
  --accent-soft: rgba(192, 90, 43, .12);
  --accent-line: rgba(192, 90, 43, .35);
  --glow: 0 1px 2px rgba(36,31,24,.08), 0 10px 26px -8px rgba(192,90,43,.45);

  /* status */
  --rec: #e5484d;
  --done: #22885a;

  /* lines + depth */
  --border: rgba(36, 31, 24, .08);
  --border-2: rgba(36, 31, 24, .15);
  --shadow-sm: 0 1px 2px rgba(36,31,24,.06);
  --shadow: 0 1px 2px rgba(36,31,24,.05), 0 6px 20px -4px rgba(36,31,24,.10);
  --shadow-lg: 0 12px 40px -8px rgba(36,31,24,.18);

  --radius: 16px;
  --radius-lg: 20px;
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 16px;
  /* true regular weight is the body-text floor — most of the app's text was
     drifting onto whatever the nearest ancestor happened to set (buttons,
     bold-context spans, …); pin it here so hierarchy has to be OPTED into,
     not inherited by accident (Travis's "everything shouts equally" note). */
  font-weight: 400;
  font-feature-settings: "cv11", "ss01";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* faint warm wash at the top so the paper doesn't read flat */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(80% 50% at 50% -10%, rgba(192,90,43,.07), transparent 60%),
    var(--bg);
}

/* ---------- views ---------- */
/* padding-bottom clears the tab bar AND the fixed help ? (which sits 88-128px up
   from the bottom right). Without the extra room the last element on a screen,
   usually the primary CTA, ends up underneath the ? . */
.view { display: none; padding: 16px; padding-bottom: 136px; max-width: 560px; margin: 0 auto; }
.view.active { display: block; animation: viewIn .28s ease; }
@keyframes viewIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ---------- boot curtain (Travis, Jul 7/14: home screen flashes before login) ----------
   html.booting is set inline in <head> before first paint and removed by init()
   once authBoot() has decided the real destination; #boot-splash is the curtain
   itself, .view stays hidden underneath the whole time so nothing can flash. */
html.booting .view { display: none !important; }
#boot-splash {
  position: fixed; inset: 0; z-index: 100; display: none;
  background: var(--accent);   /* terracotta curtain behind the brand mark while authBoot() decides */
  align-items: center; justify-content: center;
}
html.booting #boot-splash { display: flex; }
/* just the paper bow-tie MARK centered on the terracotta ground -- no wordmark
   on the loading screen (Travis, Jul 23: "just the bow tie, a little bigger, then
   it pops in"). Paper variant so it reads on the accent. A soft pulse so a slow
   authBoot() feels alive rather than stuck. */
#boot-splash .boot-logo {
  width: min(42vw, 176px); height: auto; display: block;
  animation: bootPulse 1.6s ease-in-out infinite;
}
@keyframes bootPulse { 0%,100% { opacity: 1; } 50% { opacity: .72; } }
@media (prefers-reduced-motion: reduce) { #boot-splash .boot-logo { animation: none; } }

.app-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 6px 0 18px;
}
.app-header h1 {
  font-size: 1.4rem; margin: 0; font-weight: 700; flex: 1;
  letter-spacing: -.02em;
}

.icon-btn {
  background: var(--card); color: var(--text);
  border: 1px solid var(--border);
  width: 44px; height: 44px; border-radius: 12px; font-size: 1.15rem;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  flex: none; transition: background .15s ease, border-color .15s ease, transform .1s ease;
}
.icon-btn svg { width: 20px; height: 20px; }
.icon-btn:hover { background: var(--card-2); border-color: var(--border-2); }
.icon-btn:active { background: var(--card-3); transform: scale(.94); }

.header-btn {
  background: var(--accent); color: #fff; border: none;
  height: 44px; padding: 0 18px; border-radius: 999px; font-size: .95rem;
  font-weight: 600; cursor: pointer; flex: none; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 7px;
  box-shadow: var(--glow); transition: background .15s ease, transform .1s ease;
}
.header-btn svg { width: 17px; height: 17px; }
.header-btn:hover { background: var(--accent-hover); }
.header-btn:active { background: var(--accent-press); transform: scale(.97); }

.muted { color: var(--muted); font-weight: 400; }
.small { font-size: .85rem; font-weight: 400; }
.center { text-align: center; }

/* ---------- buttons ---------- */
.btn-primary, .btn-secondary {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; border: none; border-radius: 999px;
  /* GLOBAL button-size floor (Travis, Jul 7): a bare .btn-* is never tiny again.
     14px padding -> ~50px comfortable tap target by default; .btn-big bumps hero
     actions to 56px; compact/inline contexts override padding on their own
     more-specific selector (so they stay as designed). */
  padding: 14px 22px;
  font-size: 1.05rem; font-weight: 600; cursor: pointer; color: #fff;
  letter-spacing: -.01em;
  transition: background .15s ease, transform .1s ease, box-shadow .2s ease;
}
.btn-primary { background: var(--accent); box-shadow: var(--glow); }
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:active { background: var(--accent-press); transform: scale(.985); }
.btn-secondary {
  /* quieter: white card ground instead of the tinted fill — reads lighter and
     lets the primary carry the weight (modern-polish pass) */
  background: var(--card); color: var(--text);
  border: 1px solid var(--border-2);
  box-shadow: var(--shadow-sm);
}
.btn-secondary:hover { background: var(--card-2); border-color: var(--border-2); }
.btn-secondary:active { transform: scale(.985); }
.btn-big { min-height: 56px; margin-top: 12px; }
.btn-text {
  background: none; border: none; color: var(--accent);
  font-size: .95rem; padding: 12px 4px; cursor: pointer; font-weight: 500;
  transition: color .15s ease;
}
.btn-text:hover { color: var(--accent-hover); }
button:disabled { opacity: .45; pointer-events: none; }

/* (home "N of M recorded" progress bar removed — the Content meter replaces it) */

/* ---------- home: next-up card ---------- */
.next-card {
  position: relative; overflow: hidden;
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(192,90,43,.08), transparent 55%),
    var(--card);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-lg); padding: 20px; margin-bottom: 24px;
  box-shadow: var(--shadow);
}
.next-card .eyebrow { margin: 0 0 8px; }
.next-card h3 { margin: 0 0 6px; font-size: 1.25rem; font-weight: 800; letter-spacing: -.02em; }
.next-card .meta { font-size: .85rem; color: var(--muted); margin: 0 0 16px; }
.all-done {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 28px 20px; text-align: center; margin-bottom: 24px;
  box-shadow: var(--shadow-sm);
}

/* ---------- home: library ---------- */
.section-title { font-size: .95rem; color: var(--text); margin: 24px 0 12px; font-weight: 600; letter-spacing: -.01em; }
.cat-title { font-size: .75rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--faint); font-weight: 600; margin: 20px 0 10px; }

/* ---------- eyebrow: small uppercase accent label above a card/section title ----------
   The ONE canonical definition (component kit, docs/APP_STYLE_GUIDE.md). Six near-
   identical copies of this used to be hand-pasted per card (next-card/week-card/
   planned-card/report-card/report-top/niche-pick) — consolidated here; callers below
   only override margin where a card's rhythm needs it. Color is the sanctioned darker
   terracotta (#a34a20, the style guide's hand-picked "small text on tinted chips/
   eyebrows" value — not --accent itself, which runs too bright at this size on white). */
.eyebrow, .niche-pick-eyebrow {
  display: block; font-size: .72rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: #a34a20; margin: 0;
}
.niche-pick-eyebrow { margin: 0 0 6px; }
.script-item {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: var(--card); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--radius); padding: 14px 16px; margin-bottom: 8px;
  font-size: .98rem; cursor: pointer;
  transition: background .15s ease, border-color .15s ease, transform .1s ease;
}
.script-item:hover { background: var(--card-2); border-color: var(--border-2); }
.script-item:active { transform: scale(.99); }
.script-item .t { flex: 1; }
.script-item .t .sub { display: block; font-size: .8rem; color: var(--muted); margin-top: 3px; }
.badge {
  font-size: .72rem; font-weight: 700; padding: 4px 9px; border-radius: 999px; flex: none;
  border: 1px solid transparent;
}
.badge-done { background: rgba(70,201,139,.12); color: var(--done); border-color: rgba(70,201,139,.25); }
.badge-todo { background: rgba(138,143,152,.1); color: var(--muted); border-color: var(--border); }
.badge-editing {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--accent-soft); color: var(--accent-press); border-color: var(--accent-line);
}
/* the script's own "Your video" status row on the detail page */
.detail-vid-row {
  display: flex; align-items: center; gap: 10px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 11px 14px; margin: 2px 0 14px;
}
.detail-vid-row .dv-label { flex: 1; min-width: 0; font-size: .96rem; font-weight: 600; }
.detail-vid-row .btn-secondary { flex: none; width: auto; padding: 8px 20px; }
.spin-sm { width: 14px; height: 14px; border-width: 2px; }

/* ---------- detail ---------- */
.detail-title { font-size: 1.15rem !important; letter-spacing: -.02em; }
.detail-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.chip {
  background: var(--accent-soft); color: #a34a20;
  font-size: .75rem; font-weight: 700; padding: 4px 11px; border-radius: 999px;
  border: 1px solid var(--accent-line);
}
/* review: one player + part chips underneath (multi-part takes) */
.part-chips {
  display: flex; gap: 8px; justify-content: center; align-items: center;
  flex-wrap: wrap; margin: 10px 0 2px;
}
.part-chips .chip { cursor: pointer; opacity: .5; font-family: inherit; }
.part-chips .chip.active { opacity: 1; }
.review-redo { display: block; margin: 4px auto 0; }
/* min-height is the FLOOR the corner drag-grip can shrink to, not the opening size —
   the box opens short (SCRIPT_BOX_OPEN_H in app.js) so "Record this script" is
   on screen without scrolling, and the grip pulls it down to read.
   Keep this <= SCRIPT_BOX_OPEN_H: min-height beats an inline height, so the old
   220px floor silently overrode any shorter opening height JS asked for. */
#script-edit {
  width: 100%; min-height: 120px; background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: var(--radius);
  padding: 16px; font-size: 1.05rem; line-height: 1.6; resize: none;
  font-family: inherit;
  transition: border-color .15s ease, box-shadow .15s ease;
}
#script-edit:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* the old full-width .tx-grab bar (and its bottom-squaring :has() rule) is gone
   everywhere now — #script-edit uses the corner .tx-grip (addGrabber(t)), same as
   every other resizable textarea: it wraps in .tx-grip-wrap and picks up that
   rule's corner-squaring (~line 1174) instead of a bar-specific one here. */
.detail-hint { margin: 10px 2px 0; line-height: 1.4; }
.detail-hint b { color: var(--accent); font-weight: 700; }
.detail-actions-row {
  display: flex; align-items: center; justify-content: space-between; min-height: 40px;
}
.detail-actions-left { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
/* a quiet round "?" that opens the position-change help popup */
.help-dot {
  width: 22px; height: 22px; border-radius: 50%; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 800; line-height: 1;
  color: var(--accent); background: var(--accent-soft, rgba(192,90,43,.12));
  border: 1px solid var(--accent); cursor: pointer; margin-left: -8px;
}
.help-dot:hover { background: var(--accent); color: #fff; }

/* "give me another version": the same 2-take picker shape #three-list uses (its cards
   ARE .cardq-draft/.three-opt, reused outright), just parked under the script instead
   of on its own screen. */
#detail-rewrite-pick { margin: 10px 0 16px; text-align: center; }

/* ---------- recorder ---------- */
.recorder-view.active {
  display: block; position: fixed; inset: 0; padding: 0; max-width: none;
  background: #000; z-index: 50; overflow: hidden; animation: none;
}
#preview {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; transform: scaleX(-1); /* mirror selfie view */
  background: #000;
}
#preview.no-mirror { transform: none; } /* back camera: don't mirror */
/* ---------- recorder: head framing guide ---------- */
#head-guide { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
#head-guide[hidden] { display: none; }
/* the whole ghost figure is ONE SVG (viewBox 390x780 ~= a phone screen) so
   head/neck/shoulders/belly button always keep human proportions: the head
   oval runs a size SMALL on purpose (matching your face to it puts you a
   step further back than instinct — Travis-locked), chin lands just past
   screen-middle, shoulders reach ~50px from the edges. */
.hg-svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.hg-oval { transition: opacity .5s ease; }  /* dims but stays while recording */
/* pre-record "map of your video": orange rule on top, then the headline
   zone, the head oval, and the caption/script divider down the screen */
.hg-label {
  position: absolute; inset: 0; text-align: center;
  transition: opacity .4s ease;
}
.hg-arm {
  /* the distance suggestion, sitting right above the headline box */
  position: absolute; left: 16px; right: 16px; top: 13.5%;
  color: #ff9f2b; font-size: 1rem; font-weight: 800; line-height: 1.3;
  text-shadow: 0 1px 6px rgba(0,0,0,.95);
}
.hg-zone {
  /* EXACTLY where the render puts the hook: top 360px of 1920 (18.75%),
     ~200px tall, 56px side margins of 1080 (5.2%) — see render.py .hook */
  position: absolute; left: 5.2%; right: 5.2%; top: 18.75%; height: 10.4%;
  display: flex; align-items: center; justify-content: center;
  border: 2px dashed rgba(255,255,255,.4); border-radius: 16px;
  color: rgba(255,255,255,.65);
  font-size: .88rem; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; text-shadow: 0 1px 5px rgba(0,0,0,.9);
  transition: opacity .9s ease;
}
/* after a few seconds the map softens — still readable, no longer loud */
.hg-label.calm .hg-zone { opacity: .32; }
.hg-label.calm .hg-arm { opacity: .55; transition: opacity .9s ease; }
.pr-divider {
  /* small, tight, RIGHT above the first script line */
  display: block; text-align: center; font-size: .55em;
  color: #ff9f2b; font-weight: 800; letter-spacing: .04em;
  margin-bottom: 2px; transition: opacity .9s ease;
}
.pr-divider.calm { opacity: .4; }

/* ---------- the ? help popup: WRITTEN help, video optional ----------
   A warm paper card, not a video shell: the copy is the answer and has to be
   comfortable to read on a phone. .help-scroll (copy + the optional clip under
   it) is the only scrolling part, so "Got it" stays pinned no matter how long
   the copy runs (My Videos) and the text never gets squeezed by the video. */
/* Parked bottom-right, clear of the tab bar. It used to sit at left:12px/top:30%
   with a heavy dark-theme shadow, which nobody ever saw because the ? was
   video-gated and never rendered. Once the ? went text-first it showed up
   half-over the cards, so it moved to the conventional corner and took a soft
   warm shadow instead of the old black one. */
.help-fab {
  position: fixed; right: 14px; bottom: calc(env(safe-area-inset-bottom, 0px) + 88px);
  z-index: 45;
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--accent); color: #fff; border: none; cursor: pointer;
  font-size: 1.2rem; font-weight: 800; line-height: 1;
  box-shadow: 0 3px 10px rgba(36,31,24,.22);
}
.help-fab[hidden] { display: none; }
#help-pop {
  position: fixed; inset: 0; z-index: 70; background: rgba(36,31,24,.55);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
#help-pop[hidden] { display: none; }
.help-box {
  display: flex; flex-direction: column; gap: 10px;
  width: 100%; max-width: 400px; max-height: 86vh;
  background: var(--card); border-radius: var(--radius-lg);
  padding: 20px; box-shadow: 0 12px 40px rgba(0,0,0,.25);
}
.help-box [hidden] { display: none; }
.help-box > button { flex: none; }
.help-scroll {
  display: flex; flex-direction: column; gap: 12px;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  min-height: 0; margin-bottom: 4px;
}
.help-scroll > * { flex: none; }
.help-text h3 { margin: 0 0 12px; font-size: 1.2rem; letter-spacing: -.01em; }
.help-text p { margin: 0 0 13px; font-size: 1rem; line-height: 1.6; color: var(--text); }
.help-text p:last-child { margin-bottom: 0; }
.help-text b { font-weight: 700; }
/* .help-box .help-scroll beats the later `.vplayer video { max-height: 64vh }`
   once buildPlayer() skins it, which would otherwise overflow the card */
.help-box .help-scroll video {
  width: 100%; max-height: 48vh; background: #000;
  border-radius: 14px; border: 1px solid var(--border);
}
.help-box .btn-big { width: 100%; }
/* quiet under the primary: it is an opt-out, not the action */
.help-box .btn-text { color: var(--muted); font-size: .88rem; padding: 4px; }
.help-toggle { display: flex; gap: 10px; align-items: center; font-size: .95rem; }
.hg-zone-cap {
  /* where the render puts captions in the WARM look (Travis's clinics):
     bottom 470px of 1920 (24.5%), 80px side margins of 1080 (7.4%).
     Captions are NEVER at the very bottom — the 4:5 crop kills the last
     285px and the IG/TikTok UI covers the bottom of the player. */
  top: auto; bottom: 24.5%; height: 11%; left: 7.4%; right: 7.4%;
}
.hg-body { transition: opacity .4s ease; }  /* vanishes while recording */

#prompter {
  position: absolute; left: 0; right: 0; top: 0; height: 58%;
  overflow: hidden; z-index: 2;
  /* touching the moving script = hold-to-pause, NOT text selection (Travis):
     kill selection + the long-press callout so a finger never highlights text */
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
  touch-action: none;
  /* light scrim so the camera preview shows through — text stays legible via its
     own shadow (was a heavy near-black top half) */
  background: linear-gradient(rgba(0,0,0,.34), rgba(0,0,0,.14) 62%, transparent);
  /* the bottom line shows the TOP HALF of its letters, sliced clean —
     reading line at 77% of this box stays fully legible */
  -webkit-mask-image: linear-gradient(#000 88%, rgba(0,0,0,.6) 93%, transparent 96.5%);
          mask-image: linear-gradient(#000 88%, rgba(0,0,0,.6) 93%, transparent 96.5%);
}
#prompter-content {
  padding: 45vh 24px 60px;
  font-size: 30px; line-height: 1.5; font-weight: 600; text-align: center;
  color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.9);
  will-change: transform;
}
/* one sentence / list item per line, grouped into paragraphs with a gap between */
#prompter-content .pline { display: block; }
#prompter-content .pline + .pline { margin-top: .28em; }
#prompter-content .pgap { display: block; height: .85em; }
/* silent stage direction on the teleprompter — [change your position] etc.
   reads as a clear cue, visibly NOT part of the words you speak */
#prompter-content .cue {
  display: block; width: fit-content; max-width: 88%; margin: 14px auto;
  font-size: .58em; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
  color: #fff; background: var(--done);   /* green = go/do something (Travis, Jul 7) */
  padding: 9px 18px; border-radius: 999px;
  box-shadow: 0 2px 12px rgba(0,0,0,.5); text-shadow: none;
}
/* the draggable reading line — a faint full-width guide with a grip on the right.
   The line itself never blocks touches (hold-to-pause still works); only the grip
   is interactive. Positioned by JS at cueLine() (top pad of the script matches it). */
#cue-line {
  position: absolute; left: 0; right: 0; z-index: 3;
  border-top: 2px dashed rgba(255,255,255,.45);
  pointer-events: auto; touch-action: none; cursor: ns-resize;   /* whole line draggable, not just the grip */
  transition: opacity .25s;
}
/* invisible full-width grab band so the line is easy to grab anywhere, not only the right-side grip.
   Biased DOWNWARD so even when the line sits high there's always a catchable strip below the top bar. */
#cue-line::after { content: ''; position: absolute; left: 0; right: 0; top: -4px; height: 56px; }
.cue-line-grip {
  position: absolute; right: 12px; top: 0; transform: translateY(-50%);
  pointer-events: auto; touch-action: none; cursor: ns-resize;
  display: inline-flex; align-items: center; gap: 5px;
  background: rgba(0,0,0,.55); color: #fff; border: 1px solid rgba(255,255,255,.28);
  font-size: 12px; font-weight: 700; letter-spacing: .02em; line-height: 1;
  padding: 6px 11px; border-radius: 999px;
  box-shadow: 0 2px 10px rgba(0,0,0,.5);
}
.cue-line-grip .cue-line-arrows { font-size: 14px; opacity: .9; }
.rec-topbar {
  position: absolute; z-index: 3; left: 0; right: 0;
  top: env(safe-area-inset-top, 0px);
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 14px;
}
.rec-close { background: rgba(0,0,0,.45); border-color: transparent; color: #fff; }   /* white icons on the dark button so X + flip are visible */
.rec-close:hover { background: rgba(0,0,0,.6); }
.rec-indicator {
  display: flex; align-items: center; gap: 8px;
  /* solid WHITE pill so the timer reads against any camera scene (Travis) */
  background: #fff; color: #1a1712; border-radius: 999px; padding: 8px 14px;
  font-variant-numeric: tabular-nums; font-weight: 800;
  border: 1px solid rgba(0,0,0,.08); box-shadow: 0 2px 8px rgba(0,0,0,.35);
}
.rec-dot {
  width: 10px; height: 10px; border-radius: 50%; background: var(--rec);
  box-shadow: 0 0 8px var(--rec);
  animation: blink 1.2s infinite;
}
@keyframes blink { 50% { opacity: .25; } }

.rec-controls {
  position: absolute; z-index: 3; left: 0; right: 0;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 16px);
  display: flex; flex-direction: column; align-items: center; gap: 14px;
}
.rec-adjust { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.adjust-group {
  display: flex; align-items: center; gap: 6px;
  background: rgba(0,0,0,.5); border-radius: 999px; padding: 6px 10px;
  border: 1px solid rgba(255,255,255,.1);
}
.adjust-label { font-size: .75rem; color: #cdd3df; padding: 0 4px; }
.adjust-btn {
  background: rgba(255,255,255,.14); color: #fff; border: none;
  min-width: 42px; height: 38px; border-radius: 999px;
  font-size: 1rem; font-weight: 700; cursor: pointer;
  transition: background .12s ease;
}
.adjust-btn:active { background: rgba(255,255,255,.3); }
.adjust-val { color: #fff; font-weight: 800; min-width: 1.5em; text-align: center;
  font-size: 1rem; text-shadow: 0 1px 3px rgba(0,0,0,.8); }
/* teleprompter colour swatch — tap to cycle white/blue/orange/red (works while rolling) */
.adjust-color { min-width: 38px; padding: 0; }
.color-dot {
  display: inline-block; width: 20px; height: 20px; border-radius: 50%;
  background: #fff; border: 2px solid rgba(255,255,255,.85);
  box-shadow: 0 1px 4px rgba(0,0,0,.5);
}

.record-btn {
  width: 76px; height: 76px; border-radius: 50%; cursor: pointer;
  border: 4px solid #fff; background: transparent; position: relative; padding: 0;
  box-shadow: 0 0 0 4px rgba(0,0,0,.25);
}
.record-btn::after {
  content: ""; position: absolute; inset: 6px;
  background: var(--rec); border-radius: 50%;
  transition: border-radius .2s, inset .2s;
}
/* while recording the button is a PAUSE control: two bars, not a stop-square */
.record-btn.recording::after {
  inset: 24px; border-radius: 3px;
  background: linear-gradient(to right,
    var(--rec) 0 34%, transparent 34% 66%, var(--rec) 66% 100%);
}

/* the STOP moment — prompter freezes, recording keeps rolling; an instruction
   tells them exactly what to do, and the big red STOP button does it */
/* the new-clip prompt: just clean RED WORDS sitting inside the dark pad, not a
   big bold pill (Travis, Jul 22: "rather than being so big and bold it could
   literally just be red words that fit inside that black square") */
.part-done-hint {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 200px; z-index: 6; margin: 0;
  color: #ff6b6b; font-size: .92rem; font-weight: 700; text-align: center;
  white-space: nowrap; text-shadow: 0 1px 3px rgba(0,0,0,.6);
}
/* a soft dark, blurred fade behind the whole pause moment so the prompt + PAUSE
   button pop off the busy video/script behind them (Travis, Jul 22) */
/* a SMALL blurred pad hugging just the prompt + PAUSE (Travis, Jul 22: the
   full-screen fade was way too big -- only want it behind those two, ~10px
   around them). Covers ~bottom 120px (below the button) up to ~265px (above
   the prompt pill). */
#part-done-scrim {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 122px; width: min(340px, 90vw); height: 104px; z-index: 4;   /* hugs the PAUSE + prompt, ~10px each side (Travis: black space above was huge) */
  background: rgba(0,0,0,.5);
  backdrop-filter: blur(6px);
  border-radius: 22px;
  pointer-events: none;
}
#part-done-scrim[hidden] { display: none; }
.part-done-hint[hidden] { display: none; }
/* momentary "hold a finger to pause reading" hint — sits above the reading line
   (finger is on the moving script); recording never stops, so it's calm, not red */
.prompter-hold-hint {
  position: absolute; left: 50%; transform: translateX(-50%);
  top: 14%; z-index: 5; margin: 0;
  color: #fff; font-size: 1rem; font-weight: 700; text-align: center;
  white-space: nowrap;
  background: rgba(0,0,0,.72); padding: 9px 18px; border-radius: 999px;
  backdrop-filter: blur(4px); pointer-events: none;
}
.prompter-hold-hint[hidden] { display: none; }
/* parked after a thumb-drag scrub — invites a tap to keep reading (the tap lands
   on the script underneath; this label is just the affordance) */
.prompter-park-hint { background: rgba(20,90,60,.82); border: 1px solid rgba(120,230,180,.4); }
.part-done-btn {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 132px; z-index: 5;
  background: var(--rec); color: #fff; border: none; cursor: pointer;
  font-size: 1.3rem; font-weight: 900; letter-spacing: .06em;
  padding: 18px 34px; border-radius: 999px;
  box-shadow: 0 6px 24px rgba(0,0,0,.45);
  animation: partDonePulse 1.6s ease-in-out infinite;
  white-space: nowrap;
}
.part-done-btn[hidden] { display: none; }
@keyframes partDonePulse {
  0%, 100% { transform: translateX(-50%) scale(1); box-shadow: 0 6px 24px rgba(0,0,0,.45), 0 0 0 0 rgba(255,59,48,.55); }
  50%      { transform: translateX(-50%) scale(1.05); box-shadow: 0 6px 24px rgba(0,0,0,.45), 0 0 0 14px rgba(255,59,48,0); }
}
/* plain-language helper line on the "part saved" panel */
.clip-hint { color: rgba(255,255,255,.85); font-size: 1rem; margin: -4px 0 10px; text-align: center; max-width: 340px; }

#countdown {
  position: absolute; inset: 0; z-index: 4;
  display: flex; align-items: center; justify-content: center;
  font-size: 120px; font-weight: 800; color: #fff;
  background: rgba(0,0,0,.35); text-shadow: 0 2px 12px rgba(0,0,0,.8);
}
#countdown[hidden] { display: none; }

/* ---------- recorder: pause DOCK (record in parts) ----------
   No full-screen scrim any more (it blocked the reader's face — Tamara). The
   panel is a transparent, click-through layer; only the small bottom dock is
   interactive, so the camera + clip strip behind it stay tappable while paused. */
#clip-panel {
  position: absolute; inset: 0; z-index: 8;   /* above the strip (z7) so a dragged dock never hides behind thumbs */
  pointer-events: none;   /* click-through — the face + strip behind stay tappable */
}
#clip-panel[hidden] { display: none; }
/* a compact card docked at the BOTTOM (above the clip strip), primary button in
   the thumb zone, ~110-120px tall instead of a face-covering centre card */
.clip-dock {
  pointer-events: auto;   /* the dock itself is the only interactive bit */
  position: absolute; left: 12px; right: 12px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 98px);   /* sits above the strip band */
  margin: 0 auto; max-width: 420px;
  background: rgba(13,15,19,.92); backdrop-filter: blur(10px);
  border-radius: 18px; border: 1px solid rgba(255,255,255,.10);
  box-shadow: 0 8px 30px rgba(0,0,0,.5);
  padding: 10px 12px 12px;
  display: flex; flex-direction: column; gap: 8px;
  animation: clipDockIn .18s ease backwards;   /* backwards (not `both`): the entrance is identical, but forwards-fill would LOCK opacity:1 after it ends and outrank the hold-to-peek fade (.recorder-view.peeking .clip-dock{opacity:.12}) -- so peek couldn't hide the dock until a drag set animation:none. backwards keeps the fade-in without locking the resting state. */
  transition: opacity .15s ease;
}
@keyframes clipDockIn { from { transform: translateY(14px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .clip-dock { animation: none; } }
.clip-dock.dragging { animation: none; transition: none; cursor: grabbing; }
.clip-dock .btn-big { width: 100%; min-height: 48px; }
/* handle row: drag grip + status/hint + hold-to-peek eye */
.clip-dock-handle {
  display: flex; align-items: center; gap: 10px; cursor: grab; touch-action: none;
}
.clip-dock.dragging .clip-dock-handle { cursor: grabbing; }
.clip-grip {
  flex: none; width: 22px; height: 22px; border-radius: 6px;
  /* two rows of drag dots */
  background-image: radial-gradient(rgba(255,255,255,.55) 1.4px, transparent 1.6px);
  background-size: 7px 7px; background-position: center;
}
.clip-dock-text { flex: 1; min-width: 0; }
#clip-status { color: #fff; font-size: 1.05rem; font-weight: 800; margin: 0; line-height: 1.15;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#clip-hint { color: rgba(255,255,255,.72); font-size: .82rem; margin: 2px 0 0;
  white-space: normal; line-height: 1.28;   /* wrap to 2 lines instead of clipping with ... */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
#clip-hint .hint-key { color: var(--accent); font-weight: 700; }   /* the important bit, in orange */
.clip-peek-btn {
  flex: none; width: 44px; height: 44px; border-radius: 50%;
  background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.18);
  color: #fff; font-size: 1.2rem; cursor: pointer; touch-action: none;
  display: flex; align-items: center; justify-content: center;
  /* it's a HOLD button, so iOS wants to long-press-select the 👁 glyph and pop
     the copy/callout bubble — kill that so it reads as a button, not text
     (Travis, Jul 23: "it highlights the eye like you'd copy it"). */
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent;
}
.clip-peek-btn:active { background: rgba(255,255,255,.24); }
.clip-row { display: flex; gap: 8px; justify-content: center; width: 100%; }
.clip-row .btn-text { font-size: .9rem; padding: 8px 6px; flex: 1; white-space: nowrap; color: #fff; }

/* PAUSED state: no scrim, so calm the rest of the screen instead — hide the
   record controls (the dock owns the actions) and drop the head-guide to a
   whisper so only oval + face + dock + strip remain (setRecUI restores the full
   ghost on stop; this is CSS-only, it never touches setRecUI internals). */
.recorder-view.paused .rec-controls { display: none; }
.recorder-view.paused .hg-body,
.recorder-view.paused #hg-label { opacity: 0 !important; }
.recorder-view.paused .hg-oval { opacity: .45 !important; }

/* HOLD-TO-PEEK: while the eye button is held, fade the dock + frozen script far
   down so the whole face shows through; the oval + strip stay put. */
.recorder-view.peeking #prompter { opacity: .06; transition: opacity .15s ease; pointer-events: none; }
.recorder-view.peeking .clip-dock { opacity: .12; pointer-events: none; transition: opacity .22s ease; }
.recorder-view.peeking .clip-dock .clip-peek-btn { pointer-events: auto; }   /* keep the held button alive */

.cam-error {
  position: absolute; inset: 0; z-index: 5; background: var(--bg);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 32px; gap: 6px;
}
.cam-error[hidden] { display: none; }
.cam-error .btn-primary { max-width: 280px; min-height: 52px; margin-top: 16px; }

/* ---------- review ---------- */
#playback, #review-parts video {
  display: block; margin: 0 auto;
  width: auto; max-width: 100%; max-height: 64vh;
  background: #000; border-radius: var(--radius-lg);
  border: 1px solid var(--border); object-fit: contain;
}
#review-parts { margin-bottom: 6px; }
.review-part { margin-bottom: 16px; }
.review-part-lbl { font-weight: 800; font-size: .9rem; margin: 0 0 7px; color: var(--muted); }
#review-parts video { max-height: 42vh; }
.review-actions { margin-top: 8px; }
.review-actions[hidden] { display: none; }
.upload-status { min-height: 22px; text-align: center; font-size: .92rem; color: var(--muted); }
.upload-status.ok { color: var(--done); }
.upload-status.err { color: var(--rec); }
/* client-side transcode progress, shown briefly on review-stage-1 before the
   existing background-send flow takes over -- reuses the onboarding scan-bar
   look (style.css .scan-bar) so it's one visual language, not a new widget. */
.review-optimize { margin: 4px 0 14px; }
.review-optimize[hidden] { display: none; }
.review-optimize-label {
  text-align: center; font-size: .92rem; font-weight: 600; color: var(--accent);
  margin: 0 0 6px;
}
/* half-size button: real tap target at half the visual weight (Save-to-phone) */
.btn-half {
  width: auto; min-height: 40px; padding: 8px 22px;
  margin: 10px auto 0; font-size: .92rem;
}
.btn-next { background: var(--done) !important; box-shadow: 0 8px 24px -6px rgba(70,201,139,.5) !important; }

/* ---------- create-page quick nav (glowing left-edge dots) ----------
   Travis: "a way to jump to the sections instead of scrolling"; "little
   glowing" -> the accent token, never a raw red. Pinned to the true left
   edge, entirely inside the page's own 16px gutter (see .view padding) so
   the dots can NEVER sit on top of a card's tap target -- verified against
   .qn-dot's own width below, not eyeballed. Fixed (not sticky) so the rail
   stays put through the whole scroll, and hidden/shown + which dots exist +
   the active one are all driven by updateQuickNav() in app.js. */
.quicknav {
  position: fixed; left: max(4px, env(safe-area-inset-left, 0px)); top: 50%;
  transform: translateY(-50%); z-index: 30;
  display: flex; flex-direction: column; align-items: center; gap: 16px;
}
.quicknav[hidden] { display: none; }
/* the button is a taller, still-edge-safe TAP target; the visible glowing
   mark is the small ::before dot centered inside it -- keeps the touch area
   generous without the visible chrome getting any less quiet/tiny */
.qn-dot {
  width: 10px; height: 30px; padding: 0; margin: 0; border: none; background: none;
  cursor: pointer; flex: none;
  display: flex; align-items: center; justify-content: center;
}
.qn-dot[hidden] { display: none; }
.qn-dot::before {
  content: ''; width: 6px; height: 6px; border-radius: 999px; background: var(--border-2);
  transition: background .2s ease, box-shadow .2s ease, transform .2s ease;
}
.qn-dot.active::before {
  background: var(--accent); transform: scale(1.35);
  box-shadow: 0 0 0 4px var(--accent-soft), 0 0 9px rgba(192,90,43,.6);
}
/* a little breathing room above each jump target so a dot-tap doesn't land
   the section flush against the very top edge */
#scripts-week, #scripts-lib, #scripts-ideas { scroll-margin-top: 14px; }

/* ---------- tab bar ---------- */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  display: flex;
  background: rgba(255,253,249,.86); backdrop-filter: blur(16px) saturate(160%);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.tab {
  flex: 1; background: none; border: none; color: var(--muted);
  font-size: .72rem; font-weight: 600; padding: 10px 0 12px; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  transition: color .15s ease;
}
.tab svg { width: 22px; height: 22px; }
.tab.active { color: var(--accent); }

/* ---------- home: upload-own card ---------- */
.upload-own {
  width: 100%; background: var(--card); border: 1px dashed var(--border-2);
  border-radius: var(--radius); padding: 16px; margin: 0 0 8px;
  color: var(--text); cursor: pointer; text-align: left;
  display: flex; flex-direction: column; gap: 4px;
  transition: background .15s ease, border-color .15s ease;
}
.upload-own .big { font-size: 1rem; font-weight: 600; }
.upload-own:hover { background: var(--card-2); border-color: var(--accent-line); }
.upload-own:active { background: var(--card-3); }

/* ---------- my videos: filter + sort controls ----------
   ONE row of chrome, not two (Travis, Jul 16): pills for WHAT to show, and HOW
   to order it folded into a pulldown. They stay independent, so "Needs posting"
   can still be sorted by category. The filter keeps the pill track because it is
   the control that drives action and has to advertise itself; the sort is the
   one almost nobody moves off Newest, so it is the one that folds away. Both
   still wired to the same client-side renderVideosList(), no server round-trip.

   Neither is a native <select> — that was killed deliberately (Travis: "ugly
   gray popup menu, make it way cleaner"). Don't bring one back.

   The pulldown rides the HINT line rather than the pills' row: at a real 390px
   phone the three filter pills already eat ~86% of the content width, so a
   trigger beside them wraps to a second line and we are back to two rows. On the
   hint line it costs no row at all. */
.videos-hintrow { display: flex; align-items: flex-start; gap: 10px; }
.videos-hintrow #videos-hint { flex: 1; min-width: 0; }
.videos-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 10px; }
.videos-controls .seg-control { margin: 0; }
/* the selected stage's plain-language blurb, sitting just under the tabs — tells a
   non-technical clinic owner what the stage means and that they can walk away */
.videos-filter-blurb {
  margin: 0 0 14px; color: var(--muted); font-size: .82rem; line-height: 1.4;
}
.videos-filter-blurb:empty { display: none; }
.sort-trigger {
  flex: none; width: auto; gap: 6px; margin-left: auto;
  /* compact override of the global .btn-* size floor, per the style guide:
     this is an inline control on a line of small print, not a hero button */
  padding: 7px 12px; font-size: .8rem; font-weight: 700; color: var(--muted);
}
.sort-trigger:hover { color: var(--text); }
.sort-chev { font-size: .7rem; line-height: 1; color: var(--faint); }
.seg-control {
  display: inline-flex; gap: 2px; padding: 3px; margin: 0 0 14px;
  background: var(--card-2); border: 1px solid var(--border); border-radius: 999px;
}
.seg-btn {
  appearance: none; border: 0; background: none; cursor: pointer; font-family: inherit;
  font-size: .82rem; font-weight: 700; color: var(--muted);
  padding: 7px 14px; border-radius: 999px; white-space: nowrap;
  transition: background .15s ease, color .15s ease;
}
.seg-btn:active { transform: scale(.97); }
.seg-btn.active { background: var(--accent-soft); color: var(--accent-press); }
/* the lifecycle filter has five pills — let the track scroll sideways on a phone
   instead of wrapping into a second row of chrome */
.seg-scroll {
  display: flex; max-width: 100%; overflow-x: auto;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.seg-scroll::-webkit-scrollbar { display: none; }
.seg-scroll .seg-btn { flex: none; }

/* ---------- lifecycle stage chips (#8) ----------
   One quiet pill per ladder stop, each with its own look so "ready to edit" vs
   "ready to post" reads at a glance. Posted keeps the green .posted-tag. */
.stage-chip {
  display: inline-block; margin-top: 6px; margin-right: 8px;
  font-size: .7rem; font-weight: 700; letter-spacing: .01em;
  padding: 3px 10px; border-radius: 999px; line-height: 1.35;
  white-space: nowrap;
}
.stage-chip.stg-recorded {
  color: var(--muted); background: var(--card-2); border: 1px solid var(--border-2);
}
.stage-chip.stg-rendering {
  color: #9c6f12; background: rgba(240,180,70,.16); border: 1px solid rgba(176,125,30,.32);
  animation: statePulse 1.8s ease-in-out infinite;
}
.stage-chip.stg-ready-edit {
  color: var(--accent-press); background: var(--accent-soft); border: 1px solid var(--accent-line);
}
.stage-chip.stg-ready-post {
  color: var(--done); background: rgba(52,199,89,.13); border: 1px solid rgba(52,199,89,.35);
}

/* ---------- my videos ---------- */
.vid-row {
  position: relative;
  display: flex; align-items: center; gap: 12px;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 14px 16px; margin-bottom: 10px; cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background .15s ease, border-color .15s ease;
}
.vid-row:hover { background: var(--card-2); border-color: var(--border-2); }
.vid-row:active { background: var(--card-3); }
.vid-row .vthumb {
  font-size: 1.4rem; flex: none; width: 44px; height: 58px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px; background: var(--card-2) center/cover no-repeat; overflow: hidden;
}
.vid-row .vt { flex: 1; min-width: 0; }
.vid-row .vt b { display: block; font-size: .95rem; font-weight: 800; letter-spacing: -.01em; }
.vid-row .chev { color: var(--muted); font-size: 1.4rem; flex: none; transition: transform .2s ease; }
/* open = row and detail fuse into ONE bubble; the arrow points down */
.vid-row.open {
  border-bottom-left-radius: 0; border-bottom-right-radius: 0;
  margin-bottom: 0; border-bottom-color: transparent;
}
.vid-row.open .chev { transform: rotate(90deg); }
.vid-row.pending { cursor: default; }
/* posted videos read at a glance in the list: green outline + a small tag */
.vid-row.posted { border-color: var(--done); }
.vid-row.posted:hover { border-color: var(--done); }
/* ---------- "posted to X" status tag — the flat-monochrome half of the social
   pattern. A brand-colored logo makes sense on the CONNECT flow (Settings),
   where the icon itself IS the action; here it's just a metadata receipt on
   a video card, so it stays quiet: ink glyph + card chip, green only on the
   checkmark that means "confirmed". Deliberately not the platform's brand
   color — a rainbow of logos on every video card would out-shout the content. */
.posted-tag {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: .68rem; font-weight: 700; letter-spacing: .02em;
  padding: 4px 10px; border-radius: 999px;
  background: var(--card-2); border: 1px solid var(--border); color: var(--text);
}
.posted-tag svg { width: 12px; height: 12px; stroke: var(--text); fill: none; stroke-width: 2; flex: none; }
.posted-tag .pt-ok { color: var(--done); font-weight: 800; }
.posted-tag.pt-row { position: absolute; top: 5px; left: 50%; transform: translateX(-50%); }
.posted-tag.pt-inline { margin-left: 8px; }
/* posted rows: a touch more top room so the top-center tag never crowds the title */
.vid-row.posted { padding-top: 22px; }
/* a re-render of this video is cooking: spinner + state line inside the card */
.vrow-re {
  display: flex; align-items: center; gap: 8px; margin-top: 6px;
  font-size: .8rem; font-weight: 600; color: var(--accent);
}
.spin {
  width: 22px; height: 22px; border: 3px solid var(--card-3);
  border-top-color: var(--accent); border-radius: 50%;
  animation: spin 1s linear infinite; flex: none;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* white inline spinner for the Send button while a take uploads */
.send-spin {
  display: inline-block; width: 16px; height: 16px; vertical-align: -3px;
  margin-right: 8px; border: 2px solid rgba(255,255,255,.45);
  border-top-color: #fff; border-radius: 50%; animation: spin .8s linear infinite;
}

/* ---------- skeleton loaders ----------
   Shimmer between --card-2 and --card-3 so the ground never leaves the warm-
   paper family. Used anywhere a list/card is about to appear (My Videos, the
   home library, onboarding's scan) instead of a bare spinner —
   it shows the SHAPE of what's coming, so the wait reads as "almost here"
   rather than "unknown". THE SHAPE HAS TO MATCH THE ROW IT STANDS IN FOR, or it
   promises something that never arrives: `.skel-row` mimics a .vid-row (thumb +
   two lines, My Videos); `.skel-card` mimics a .script-card (stacked lines, NO
   thumb, the Create list); `.skel-line` alone covers simpler one-line waits. */
.skel-wrap { display: flex; flex-direction: column; gap: 10px; }
.skel-row {
  display: flex; align-items: center; gap: 12px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 14px 16px;
}
.skeleton {
  background: linear-gradient(100deg, var(--card-2) 30%, var(--card-3) 50%, var(--card-2) 70%);
  background-size: 220% 100%;
  border-radius: 8px;
  animation: skelShimmer 1.35s ease-in-out infinite;
}
.skel-thumb { width: 52px; height: 52px; border-radius: 11px; flex: none; }
.skel-lines { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.skel-line { height: 11px; width: 100%; }
/* the home library / week planner now show a single small spinner instead of
   shimmer cards on load (Travis, Jul 21): a quiet turning thing, centered, then
   the content. (.skel-card/.skel-hook/.skel-meta retired with the shimmer bars.) */
.list-loading { display: flex; justify-content: center; padding: 26px 0; }
/* width helpers ride on .skeleton so every shape above can use them */
.skeleton.w-60 { width: 60%; }
.skeleton.w-40 { width: 40%; }
.skeleton.w-85 { width: 85%; }
@keyframes skelShimmer { 0% { background-position: 150% 0; } 100% { background-position: -50% 0; } }
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; background: var(--card-3); }
}

/* expanded video fuses with its row — one continuous bubble */
.vid-detail {
  margin: 0 0 16px; background: var(--card);
  border: 1px solid var(--border); border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
  padding: 4px 12px 6px;
}
.vid-detail video {
  display: block; margin: 0 auto;
  width: auto; max-width: 100%; max-height: 70vh;
  background: #000; border-radius: var(--radius-lg);
  border: 1px solid var(--border); object-fit: contain;
}
/* quiet honesty note under the 720p preview: the download + posted file are full HD */
.vid-proxy-note { text-align: center; margin: 8px auto 2px; max-width: 34ch; }
/* stacked, phone-safe: hero Share on top, a tidy wrap-row underneath */
.vid-detail .links { display: block; padding: 12px 4px 4px; }
.vid-detail .links a { color: var(--accent); font-size: .95rem; text-decoration: none; font-weight: 500; }
.vid-detail .links a:hover { color: var(--accent-hover); }
/* Download + Start-over = two real buttons SIDE BY SIDE (Travis), not layered
   text links; a subtle divider separates them from Post above. */
.vid-actions-2 {
  display: flex; gap: 10px; margin-top: 18px; padding-top: 14px;
  border-top: 1px solid var(--border);
}
.vid-detail .links .vid-actions-2 a {
  flex: 1; text-align: center; padding: 12px 10px; border-radius: 999px;
  font-weight: 600; font-size: .92rem;
  background: var(--card-2); border: 1px solid var(--border-2); color: var(--text);
}
.vid-detail .links .vid-actions-2 a:hover { background: var(--card-3); border-color: var(--accent-line); }
.vid-detail .links .vid-actions-2 a.del-link { color: var(--rec); border-color: rgba(229,72,77,.28); }
.vid-detail .links .vid-actions-2 a.del-link:hover { background: rgba(229,72,77,.08); border-color: rgba(229,72,77,.5); }

/* Post-first video card (Travis #6, Jul 23): the numbered 1·2·3 ladder below
   is retired for this card — Post is now the hero action right under the
   player (.vid-post-lead, reusing .share-link's existing filled-pill look —
   no new button skin), and Edit + New Thumbnail become quiet optional extras
   (.vid-optional) tucked underneath with a small muted label, separated by a
   hairline like the Download/Start-over row already was. */
.vid-post-lead { text-align: center; margin-bottom: 4px; }
.vid-post-lead .posted-tag { margin: 0 0 10px; }
.vid-optional { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--border); }
.vid-optional-lbl { margin-bottom: 10px; }

/* Stepped video card — a guided 1·2·3 ladder for older clinic owners
   (Travis, Jul 7): subtle labels + gentle spacing so "what do I do first"
   reads at a glance, without going bulky. Refined Jul 21 (Travis: a touch more
   premium): a numbered disc + calm title, considered spacing, understated.
   NOTE (Jul 23): the video-card render no longer uses .vstep/.vstep-head/
   .vstep-num/.vstep-lbl (see .vid-post-lead/.vid-optional above) — left here
   in case another screen still reaches for this component; safe to prune if not. */
.vstep { margin-top: 24px; }
.vstep:first-child { margin-top: 6px; }
.vstep-head { display: flex; align-items: center; gap: 11px; margin-bottom: 13px; }
.vstep-num {
  flex: none; width: 25px; height: 25px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .8rem; font-weight: 800; line-height: 1;
  color: var(--accent); background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  font-variant-numeric: tabular-nums;
}
.vstep-lbl {
  display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: .96rem; font-weight: 700; letter-spacing: -.005em; color: var(--text);
}
/* Step 3 already shared -> the shared .posted-tag chip, inline beside the label */

/* ---------- settings: collapsible account hub ---------- */
.set-section {
  /* the heavier border-2 read dated next to the card kit — same quiet line +
     whisper of depth every other surface carries now */
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); margin-bottom: 14px; overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.set-section > summary {
  list-style: none; cursor: pointer; padding: 16px 16px;
  font-size: 1.02rem; font-weight: 700; display: flex; align-items: center;
  gap: 10px; -webkit-tap-highlight-color: transparent;
}
.set-section > summary::-webkit-details-marker { display: none; }
.set-section > summary::after {
  content: "\203A"; margin-left: auto; color: var(--muted);
  font-size: 1.4rem; transform: rotate(90deg); transition: transform .18s ease;
}
.set-section[open] > summary::after { transform: rotate(-90deg); }
.set-section[open] > summary { border-bottom: 1px solid var(--border); }
.set-body { padding: 16px 16px 4px; }
.set-body .field:last-child { margin-bottom: 12px; }
.set-body .btn-big { margin-bottom: 16px; }
.set-body .field-hint { margin: -12px 0 20px; }   /* a helper line tucked directly under its control */
.set-soon {
  line-height: 1.5; background: var(--card-2); border-radius: 10px;
  padding: 12px 14px; margin: 0 0 14px;
}
.set-soon b { color: var(--text); }

/* ---------- legal + patient-information surfaces ---------- */
.legal-inline { line-height: 1.5; }
.legal-inline a, .legal-links a, .legal-document a {
  color: var(--accent); font-weight: 600;
}
.legal-inline span { margin: 0 7px; color: var(--faint); }
.login-legal { margin: -2px 0 0; }
.legal-links {
  display: flex; flex-wrap: wrap; gap: 8px 18px; margin: 0 0 14px;
}
.patient-notice { line-height: 1.5; margin: 12px 0 4px; }
.cc-pop > .patient-notice { margin: -2px 2px 12px; }

.legal-page { max-width: 780px; padding-bottom: 48px; }
.legal-header .icon-btn { text-decoration: none; }
.legal-draft { margin: -6px 0 4px 56px; }
.legal-updated { margin: 0 0 18px 56px; }
.legal-document { padding: clamp(20px, 5vw, 42px); }
.legal-document h1 { margin: 0 0 22px; font-size: clamp(1.7rem, 5vw, 2.3rem); }
.legal-document h2 { margin: 30px 0 12px; font-size: 1.2rem; }
.legal-document h3, .legal-document h4 { margin: 24px 0 10px; }
.legal-document p, .legal-document li { line-height: 1.7; }
.legal-document p { margin: 0 0 16px; }
.legal-document ul { padding-left: 22px; margin: 0 0 18px; }
.legal-document li { margin-bottom: 7px; overflow-wrap: anywhere; }
.legal-document blockquote {
  margin: 0 0 24px; padding: 16px 18px;
  background: var(--card-2); border-left: 3px solid var(--accent-line);
  border-radius: 0 12px 12px 0;
}
.legal-document blockquote p:last-child { margin-bottom: 0; }
.legal-document hr { border: 0; border-top: 1px solid var(--border-2); margin: 26px 0; }
.legal-document code {
  padding: 2px 5px; border-radius: 6px; background: var(--bg-2);
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: .9em; overflow-wrap: anywhere;
}
.legal-document table {
  width: 100%; table-layout: fixed; border-collapse: collapse; margin: 0 0 24px;
}
.legal-document th, .legal-document td {
  padding: 10px 8px; border: 1px solid var(--border-2);
  text-align: left; vertical-align: top; overflow-wrap: anywhere;
  font-size: .84rem; line-height: 1.5;
}
.legal-document th { background: var(--card-2); }

@media (max-width: 430px) {
  .legal-draft, .legal-updated { margin-left: 0; }
  .legal-document { padding: 20px 16px; }
  .legal-document th, .legal-document td { padding: 8px 5px; font-size: .75rem; }
}

.field { display: block; margin-bottom: 20px; }
/* Onboarding is the first thing a clinic ever sees, and it read as one crammed
   block. Give its groups room to breathe (Travis, live walkthrough). */
#view-onboard .field { margin-bottom: 26px; }
#view-onboard .field-row { margin-bottom: 6px; }
.field > span { display: block; font-size: .9rem; color: var(--muted); margin-bottom: 8px; }
/* .fp-trigger stands in for a native select (enhanceSelect(), app.js) and so it
   SHARES this rule rather than inventing a lookalike: same border, radius,
   padding and chevron as every other select in the app. */
.field select,
.field .fp-trigger {
  width: 100%; background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 12px; padding: 14px; font-size: 1rem;
  -webkit-appearance: none; appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%237d7466' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center;
  transition: border-color .15s ease, box-shadow .15s ease;
}
/* the button-shaped bits a <select> got for free */
.field .fp-trigger {
  display: block; text-align: left; font-family: inherit; font-weight: 400;
  line-height: 1.5; cursor: pointer;
  /* leave the chevron its corner: a long placeholder ellipsises BEFORE it now
     instead of clipping straight through it (the phone bug a33eabb chased on
     this very row) */
  padding-right: 40px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.field .fp-trigger:hover { background-color: var(--card-2); }
.field .fp-trigger.is-ph { color: var(--muted); }   /* unset: greyed, as the native placeholder read */
.field input[type="url"], .field input[type="text"], .field input[type="password"] {
  width: 100%; background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 12px; padding: 14px; font-size: 1rem;
  transition: border-color .15s ease, box-shadow .15s ease;
}
/* multi-clinic manager pulldown in the app header: quiet pill, only rendered
   for accounts mapped to >1 clinic */
.clinic-switch {
  max-width: 42vw; background: var(--card-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px;
  padding: 8px 12px; font-size: .88rem; font-weight: 600;
  -webkit-appearance: none; appearance: none;
}
.field select:focus,
.field .fp-trigger:focus-visible,
.field input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.field input[type="range"] { width: 100%; accent-color: var(--accent); }

/* show/hide password toggle — wrap any password input in .pw-wrap, pair it
   with a .pw-eye button (data-target = the input's id). Reusable everywhere
   a password field appears (login door, admin account tools). */
.pw-wrap { position: relative; }
.pw-wrap input[type="password"], .pw-wrap input[type="text"] { padding-right: 46px; }
.pw-eye {
  position: absolute; top: 50%; right: 5px; transform: translateY(-50%);
  width: 34px; height: 34px; border-radius: 10px;
  background: transparent; border: 0; color: var(--muted);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.05rem; line-height: 1; cursor: pointer; padding: 0;
}
.pw-eye:hover { background: var(--card-2); color: var(--text); }
.pw-eye:active { background: var(--card-3); }

/* ---------- onboarding / profile ---------- */
.onboard-intro { margin: 4px 0 22px; line-height: 1.5; }

/* niche gate path-note: sets the expectation (bank vs basics/upsell) before the
   scan. Neutral by default; accent-tinted for a banked niche so the "you get the
   full bank" path reads as the reward. */
.niche-note {
  margin: 2px 0 6px; padding: 10px 13px;
  background: var(--card-2); border: 1px solid var(--border);
  border-radius: 12px; font-size: .86rem; line-height: 1.45; color: var(--muted);
}
.niche-note b { color: var(--text); font-weight: 600; }
.niche-note-banked {
  background: var(--accent-soft); border-color: var(--accent-line);
  color: var(--accent-press);
}
.niche-note-banked b { color: var(--accent-press); }

/* multi-niche mismatch prompt (spec §3.3): a card at the top of the confirm screen
   with tappable profession chips. Default = KEEP the gate choice (chip 0 selected). */
.niche-pick { margin: 0 0 18px; }
.niche-pick h3 { margin: 0 0 6px; font-size: 1.15rem; font-weight: 800; letter-spacing: -.02em; }
.niche-pick-opts { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 12px; }
.niche-pick-opts.busy { opacity: .55; pointer-events: none; }   /* reloading rules for the picked profession */
/* selectable profession chip — extends the kit .chip: bigger tap target + a filled
   selected state. Inactive chips read quiet; the chosen one fills with accent. */
.niche-chip {
  cursor: pointer; font-family: inherit; font-size: .84rem; padding: 9px 14px;
  background: var(--card); color: var(--text); border: 1px solid var(--border-2);
  display: inline-flex; align-items: center; gap: 8px;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.niche-chip.selected { background: var(--accent); color: #fff; border-color: var(--accent); }
.niche-chip-bank {
  font-size: .62rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px;
  background: var(--accent-soft); color: var(--accent-press);
}
.niche-chip.selected .niche-chip-bank { background: rgba(255,255,255,.22); color: #fff; }
.niche-pick-note { margin: 4px 0 0; line-height: 1.45; }

/* b-roll upload: "who's in it?" selectable chips (reuse the .chip + .niche-chip
   kit; single-select toggled in JS via .selected). Light Settings surface. */
.broll-who { margin: 12px 0 2px; }
.broll-who-lbl { display: block; margin: 0 0 7px; }
.broll-who .chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
.broll-who .niche-chip { font-size: .8rem; padding: 7px 12px; }

/* ---------- confirm: the ONE "what we found" card ----------
   Travis, Jul 16: "it's too many fucking separate squares. So it needs to be almost one
   square." The found-line, the services list + add row, and the do-you-offer-these
   checklist all live inside a single .card now. (The coverage stat was a fourth row
   until Jul 17 — removed, see index.html.) Inside it, blocks are
   separated by a hairline rule instead of by gaps between boxes — one square, read top to
   bottom. Every row still hides independently, and :first-child/+ selectors mean a hidden
   row leaves no double rule or stray top margin behind. */
.cf-recap { margin-bottom: 18px; }
/* Standalone (its old life) the found-line was a green tinted box. INSIDE the card it
   drops the box and keeps only the green: a tinted rectangle stacked on another tinted
   block is exactly the "squares inside a square" Travis is calling out, and the guide
   allows one accent (green = the "we read it" status, not a second surface). */
.cf-recap .found-line {
  margin: 0 0 12px; padding: 0;
  background: none; border: 0; border-radius: 0;
}
.svc-block { border-top: 1px solid var(--border); margin-top: 16px; padding-top: 16px; }
/* Thin scrape (nothing read off the site) = the found-line is hidden, so the services
   block is the top of the card and must not wear a divider above nothing.
   `:first-child` does NOT work here: a [hidden] row is display:none but still a DOM
   child, so the services block is never actually first. Match the hidden row itself —
   the card's order is fixed (found -> services).
   NOTE this chain used to include `+ #cf-coverage[hidden]`; that row was removed
   Jul 17, and the selector had to lose the link or it would never match again and a
   thin scrape would show a rule above nothing. */
#cf-found[hidden] + .svc-block {
  border-top: 0; margin-top: 0; padding-top: 0;
}
.svc-block h3 { margin: 0 0 4px; font-size: 1.15rem; letter-spacing: -.02em; }
.svc-lead { margin: 0 0 12px; }

/* the .coverage-line / .coverage-num / .coverage-sub rules lived here until Jul 17,
   styling the "N services recognised" stat. Row and rules both removed — see the note
   in index.html on .cf-recap. Nothing else used them. */

/* services the scripts get built from: the recognised list, surfaced as removable chips
   over the hidden #cf-services textarea (Travis, Jul 16: "we should almost list those
   out"). Reuses the .chip accent-tint kit; the × is part of the chip, not a second
   control, so a 16-service list still reads as one calm block. */
.svc-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.svc-chips .svc-empty { margin: 0; }
.svc-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--accent-soft); color: #a34a20;
  font-size: .8rem; font-weight: 700; padding: 6px 8px 6px 12px; border-radius: 999px;
  border: 1px solid var(--accent-line); max-width: 100%;
}
.svc-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.svc-chip-x {
  appearance: none; border: 0; background: none; cursor: pointer; font-family: inherit;
  color: #a34a20; opacity: .55; font-size: 1rem; line-height: 1;
  padding: 0; width: 20px; height: 20px; flex: none;  /* the chip itself is the tap target */
  display: flex; align-items: center; justify-content: center; border-radius: 999px;
}
.svc-chip-x:hover, .svc-chip-x:focus-visible { opacity: 1; background: var(--accent-line); outline: none; }

/* type-a-service + [+]: the same add-a-thing mechanic as the studio's "+ Add a section"
   and the video categories, so it borrows their look — literally .studio-sec-input. */
.svc-add { display: flex; gap: 8px; align-items: center; margin-top: 12px; }
.svc-add-input { flex: 1; min-width: 0; font-weight: 500; }
.svc-add-btn {
  appearance: none; cursor: pointer; font-family: inherit; flex: none;
  width: 44px; height: 44px; border-radius: 999px;   /* full tap target, not a bare glyph */
  background: var(--accent); color: #fff; border: 0;
  font-size: 1.25rem; line-height: 1; font-weight: 700;
}
.svc-add-btn:hover { background: var(--accent-hover); }
.svc-add-btn:active { background: var(--accent-press); }

/* who's on camera: the man/woman pick reuses the .seg-control / .seg-btn kit wholesale
   (the My Videos filter's control) — a binary needs a segmented control, not a select.
   Only layout lives here; no new button look. .seg-control carries a 14px bottom margin
   for its usual standalone use, which would double up inside a .field — zero it and let
   the row own the spacing. */
.prac-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.prac-row .seg-control { margin: 0; }
.prac-role { font-weight: 600; }   /* the role half, echoed so the saved value is legible */
.prac-legacy { margin: 8px 0 0; color: #9c6f12; }   /* amber = the warning state (see guide) */

/* review-pile checklist: uncertain services to confirm (spec §3.3/§4) */
.review-pile h3 { margin: 0 0 4px; font-size: 1.15rem; letter-spacing: -.02em; }
.review-pile-list { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.review-item {
  display: flex; align-items: center; gap: 11px; cursor: pointer;
  background: var(--card-2); border: 1px solid var(--border);
  border-radius: 12px; padding: 11px 14px;
}
.review-item input[type="checkbox"] {
  width: 20px; height: 20px; accent-color: var(--accent); flex: none; cursor: pointer;
}
.review-item-label { font-weight: 600; font-size: .92rem; }
.review-item-ev { margin-left: auto; color: var(--faint); font-size: .78rem; text-align: right; }

/* logo picker: ranked scraped candidates (spec §3.4.7) */
.logo-cands { margin: 12px 0 4px; }
.logo-cands-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.logo-cands-row.busy { opacity: .55; pointer-events: none; }
.logo-cand {
  width: 62px; height: 62px; padding: 4px; border-radius: 12px;
  background: var(--card); border: 2px solid var(--border-2); cursor: pointer;
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.logo-cand img { max-width: 100%; max-height: 100%; object-fit: contain; }
.logo-cand.selected { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.logo-cands-status { display: inline-block; margin-top: 6px; }
/* the picker now sits between the logo and the colors, so an empty status line is a
   visible hole rather than harmless trailing space */
.logo-cands-status:empty { display: none; }
.field textarea {
  width: 100%; background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 12px; padding: 14px;
  font-size: 1rem; font-family: inherit; resize: vertical; line-height: 1.5;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.field textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* these two get addGrabber()'s corner grip (see .tx-grip-wrap below) — the native
   corner resize dot it replaces is unreliable to grab by touch, so turn it off
   rather than leave both affordances stacked on the same box. */
#cf-environment, #cf-conditions { resize: none; }
/* Country + State sit side by side on a wide screen, but two half-width selects
   on a phone clipped their own placeholders ("Choose your state or provin...")
   straight through the chevron. flex-basis lets the pair STACK once there is not
   room for both, and min-width:0 stops a flex child refusing to shrink. */
.field-row { display: flex; gap: 12px; flex-wrap: wrap; }
.field-row .field { flex: 1 1 240px; min-width: 0; }

/* onboarding scan: the status line "breathes" while the 30-60s scan runs */
#ob-status.scanning {
  color: var(--accent); font-weight: 500;
  animation: scanpulse 1.4s ease-in-out infinite;
}
@keyframes scanpulse { 0%,100% { opacity: .55; } 50% { opacity: 1; } }
/* real scan progress bar, filled to the server's pct as the job runs */
.scan-bar {
  height: 6px; border-radius: 999px; background: var(--card-3);
  overflow: hidden; margin: 4px auto 12px; max-width: 320px;
}
.scan-bar span {
  display: block; height: 100%; width: 0; border-radius: 999px;
  background: var(--accent); transition: width .45s ease;
}

/* ---------- post-save "building your scripts" payoff moment ---------- */
/* scope to .active — a bare #view-building would out-specify `.view{display:none}`
   and render on every screen */
#view-building.active { display: flex; align-items: center; justify-content: center; min-height: 100dvh; }
/* Vertical rhythm is TIGHT on purpose: the payoff runs 5 steps (two of them wrap to
   3 lines), and the "See my scripts" CTA has to land ON SCREEN at a real 390x664 phone,
   not just on a tall 390x844 one. Re-measure the CTA at 664 before loosening any of
   these numbers. */
.building-wrap {
  max-width: 380px; margin: 0 auto; text-align: center; padding: 24px 24px;
  display: flex; flex-direction: column; align-items: center;
}
.building-orb { position: relative; width: 66px; height: 66px; margin-bottom: 18px; }
.building-orb span {
  position: absolute; inset: 0; border-radius: 50%;
  border: 3px solid var(--accent); opacity: 0;
  animation: buildPulse 2.1s ease-out infinite;
}
.building-orb span:nth-child(2) { animation-delay: .7s; }
.building-orb span:nth-child(3) { animation-delay: 1.4s; }
@keyframes buildPulse {
  0%   { transform: scale(.35); opacity: 0; }
  30%  { opacity: .55; }
  100% { transform: scale(1); opacity: 0; }
}
.building-orb::after {   /* solid warm core */
  content: ""; position: absolute; inset: 24px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 6px 18px -4px rgba(192,90,43,.5);
}
.building-title { font-size: 1.5rem; letter-spacing: -.02em; margin: 0 0 8px; }
.building-sub { color: var(--muted); line-height: 1.5; margin: 0 0 16px; font-size: .95rem; }
.building-steps { list-style: none; padding: 0; margin: 0; width: 100%; text-align: left; }
.building-step {
  /* flex-start, not center: the regulator beat wraps to 3 lines and a centred tick
     floats away from the line it belongs to */
  display: flex; align-items: flex-start; gap: 10px; padding: 7px 2px;
  font-size: .95rem; line-height: 1.35; color: var(--text); animation: stepIn .4s ease both;
}
@keyframes stepIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.building-check {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent-press);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .8rem; font-weight: 700;
}
.building-done { margin-top: 18px; width: 100%; animation: stepIn .5s ease both; }
.building-count { font-size: 1.05rem; font-weight: 600; margin: 0 0 16px; letter-spacing: -.01em; }

/* ---------- voice capture (sound like you) ---------- */
.voice-register {
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  border-radius: 14px; padding: 14px 16px; margin: 0 0 22px;
  font-size: .9rem; line-height: 1.5; color: var(--accent-press);
}
.voice-progress { display: flex; gap: 6px; margin: 4px 0 18px; }
.voice-progress span {
  flex: 1; height: 5px; border-radius: 999px; background: var(--card-3);
  transition: background .2s ease;
}
.voice-progress span.cur { background: var(--accent-line); }
.voice-progress span.done { background: var(--accent); }
.voice-q {
  font-size: 1.28rem; font-weight: 700; letter-spacing: -.02em;
  line-height: 1.3; margin: 0 0 14px;
}
/* the question-card stepper answers into an identical box doing an identical job, so it
   shares the rule rather than cloning it (one answer box, one look, one place to fix) */
/* #three-text (the dump door's one box) JOINS this rule rather than getting its own:
   it is the same job on a third screen, the same way #cardq-answer joined it for the
   second. One look for "say it out loud into this". */
/* resize: none — the native corner dot is a desktop affordance and unreliable to grab
   by touch on Android (Travis, on the "shoes" card: "make that like a triangle... so I
   can drag it easily"). addGrabber's corner-triangle grip (.tx-grip below) replaces it. */
#voice-answer, #cardq-answer, #three-text {
  width: 100%; background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 14px; padding: 14px;
  font-size: 1rem; font-family: inherit; line-height: 1.55; resize: none;
  transition: border-color .15s ease, box-shadow .15s ease;
}
#voice-answer:focus, #cardq-answer:focus, #three-text:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* addGrabber() wraps the textarea in this so the grip can sit absolutely over its
   bottom-right corner. Square that corner off so the triangle reads as part of the
   box, not a sticker on top of it.
   `display:block` on the textarea matters more than it looks: a textarea is
   inline-block by default and sits on a text baseline, which leaves a few px of
   descender gap below its own visible box. .tx-grip-wrap (a plain block div) fits
   itself to that INLINE box, gap included — so right:0/bottom:0 on the grip landed
   a few px below the textarea's real bottom edge, reading as "hanging off the box"
   on a phone (Travis: it must be "inside the square/text box"). Forcing block here
   collapses the wrap onto the textarea's exact rendered bounds, so the grip's own
   corner is flush with the box's actual corner — inside, not below.
   44px hit area (Travis: "big touch target… click it and drag it really quickly")
   around a 26px visible triangle, inset 2px so it clearly sits ON the box, not off
   its edge. addGrabber() also pads the textarea's own bottom-right so the last
   line of typed text clears the triangle instead of running under it. */
.tx-grip-wrap { position: relative; }
.tx-grip-wrap > textarea { display: block; border-bottom-right-radius: 4px; }
.tx-grip {
  position: absolute; right: 0; bottom: 0; width: 44px; height: 44px;
  touch-action: none; cursor: ns-resize; -webkit-user-select: none; user-select: none;
}
.tx-grip::after {
  content: ''; position: absolute; right: 2px; bottom: 2px; width: 26px; height: 26px;
  background: var(--accent); opacity: .62;
  clip-path: polygon(100% 0, 100% 100%, 0 100%);
  border-radius: 0 0 8px 0;
  transition: opacity .1s ease;
}
.tx-grip:active::after { opacity: .92; }
.voice-controls { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 12px 0; }
.voice-mic.listening {
  background: var(--rec); color: #fff; border-color: var(--rec);
  animation: micpulse 1.3s ease-in-out infinite;
}
@keyframes micpulse { 0%,100% { box-shadow: 0 0 0 0 rgba(229,72,77,.4); } 50% { box-shadow: 0 0 0 9px rgba(229,72,77,0); } }
.voice-wc { white-space: nowrap; }
.voice-nav { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 6px; }
.voice-nav .btn-primary { min-width: 130px; }
.voice-nav .btn-text { white-space: nowrap; flex: none; }
.voice-total { margin-top: 18px; }
/* re-record warning (Settings entry, over a sample already on file). Deliberately a
   quiet line, not a tinted box — the register card below it owns the one accent block,
   and two stacked accent-soft boxes read heavy. */
.voice-resample { margin: -12px 0 20px; line-height: 1.5; color: var(--muted); }
/* the voice-only save's own status line (the onboard path uses #cf-status) */
.voice-save-msg { margin: 14px 0 0; color: var(--muted); line-height: 1.5; }
.voice-save-msg.err { color: var(--rec); }
.voice-save-msg:empty { display: none; }
/* Settings > Sound like you: whether a voice is already on file */
.set-voice-state { margin: -4px 0 16px; color: var(--muted); line-height: 1.5; }
.set-voice-state.on { color: var(--done); font-weight: 600; }

.found-line {
  margin: -6px 0 16px; padding: 10px 12px; font-size: .85rem; line-height: 1.5;
  color: var(--done); background: rgba(70,201,139,.08);
  border: 1px solid rgba(70,201,139,.25); border-radius: 10px;
}

/* ---------- scripts tab ---------- */
.scripts-lead { line-height: 1.55; margin: 4px 0 20px; }
.gen-status { text-align: center; margin: 14px 0 0; color: var(--muted); }
.gen-status.scanning { color: var(--accent); font-weight: 500; animation: scanpulse 1.4s ease-in-out infinite; }

/* ===== YEAR-OF-CONTENT METER — the one tinted retention hero on home ======
   Modeled on .next-card; existing tokens + token-derived rgba only (accent
   192,90,43 / ink 36,31,24). The spec's hero-demotion rules landed separately, in
   V2.1's week-planner "next" marker (app.js planCard/.is-next) — this meter's
   own markup is untouched either way. The meter is collapsible, collapsed by
   default: the FACE (.year-head-row + .year-bar-track + .year-foot-row) is
   always visible; .year-body holds the full grid + messages and is revealed by
   #year-see ("See your year"), which toggles .collapsed on .year-hero. Tapping
   the card itself goes to this week instead. */
.year-hero { position: relative; overflow: hidden;
  background: radial-gradient(120% 100% at 100% 0%, rgba(192,90,43,.10), transparent 58%), var(--card);
  border: 1px solid var(--accent-line); border-radius: var(--radius-lg);
  padding: 16px 15px 15px; margin-bottom: 16px; box-shadow: var(--shadow); }
.year-hero > * { position: relative; }
.year-hero.collapsed .year-body { display: none; }
.year-body > * { position: relative; }   /* same guarantee .year-hero > * gives its direct children */
/* THE FACE (Option A, Travis Jul 16): one adaptive lead line, the month bar,
   and a quiet foot. The old face's uppercase label + "<done> / <target>" count
   + status line are GONE — the lead line says the thing they'd have had to do
   the arithmetic for. The "?" still sits BESIDE the bar, not in it, and keeps
   its .week-help-btn look so the two home cards read as a pair. */
.year-head-row { display: flex; align-items: center; gap: 9px; }
.year-bar {
  display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0;
  background: none; border: none; padding: 0; margin: 0; cursor: pointer;
  font: inherit; color: inherit; text-align: left;
}
/* the hero of the whole card: the one instruction */
.year-bar-lead { flex: 1; min-width: 0; font-size: 1rem; font-weight: 700;
  color: var(--text); letter-spacing: -.01em; }
/* the chevron is a GO arrow now (tap -> this week), not an expand caret, so it
   points right and never rotates. "See your year" owns the expand. */
.year-bar-chev { color: var(--faint); font-size: .95rem; flex-shrink: 0; }
.year-bar-track { display: block; height: 8px; border-radius: 999px;
  background: rgba(36,31,24,.075); overflow: hidden; margin: 11px 0 0; }
.year-bar-fill { display: block; height: 100%; width: 0%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent), #de8a55); transition: width .5s cubic-bezier(.4,0,.2,1); }
.year-foot-row { display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-top: 8px; }
.year-bar-quiet { margin: 0; min-width: 0; }
.year-see { background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  font-family: inherit; font-size: .78rem; font-weight: 600; color: var(--accent);
  flex-shrink: 0; }
.year-body { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--accent-line); }
.year-top { display: flex; align-items: flex-end; justify-content: space-between; margin-top: 7px; }
.year-count { font-size: 2.3rem; font-weight: 800; line-height: .95; letter-spacing: -.02em;
  color: var(--text); font-variant-numeric: tabular-nums; }
.year-count i { font-style: normal; font-size: 1.05rem; font-weight: 700; color: var(--faint); }
.year-streak { display: inline-flex; align-items: center; gap: 5px; font-size: .78rem; font-weight: 700;
  padding: 5px 9px; border-radius: 999px; white-space: nowrap; }
.year-streak.alive { color: var(--accent); background: rgba(192,90,43,.11); }
.year-streak.rest { color: var(--faint); background: rgba(36,31,24,.055); }
.year-msg { font-size: 1rem; font-weight: 700; color: var(--text); margin: 11px 0 2px; letter-spacing: -.01em; }
.year-sub { margin: 0 0 2px; }
.year-grid { display: grid; grid-template-columns: repeat(15, 1fr); gap: 3px; margin: 12px 0 4px; }
.yc { aspect-ratio: 1; border-radius: 2.5px; background: rgba(36,31,24,.075); }
.yc.on { background: var(--accent); }
.yc.mile { box-shadow: inset 0 0 0 1.4px rgba(36,31,24,.30); }
.yc.mile.on { box-shadow: inset 0 0 0 1.4px rgba(255,255,255,.6); }
.yc.now { box-shadow: 0 0 0 2px var(--card), 0 0 0 3.6px rgba(192,90,43,.55); }
.year-nudge { display: inline-block; background: none; border: 0; color: var(--accent);
  font-weight: 700; font-size: .86rem; padding: 9px 0 1px; cursor: pointer; font-family: inherit; }
/* the closing "keep going" line that ends the expanded YEAR body */
.year-foot { margin: 12px 0 0; }

.week-card {
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(192,90,43,.07), transparent 55%),
    var(--card);
  border: 1px solid var(--accent-line); border-radius: var(--radius-lg);
  padding: 18px; margin-bottom: 26px; box-shadow: var(--shadow);
}
.week-card .eyebrow { margin: 0 0 4px; }
.week-sub { margin: 0 0 14px; }
#week-list { display: flex; flex-direction: column; gap: 8px; }

/* ---- Week / Month planner view toggle (cadence, Jul 21) ---- */
.planner-views {
  display: inline-flex; gap: 2px; padding: 3px; margin: 0 0 14px;
  background: var(--card-2); border: 1px solid var(--border); border-radius: 999px;
}
.pv-btn {
  appearance: none; border: none; cursor: pointer;
  background: transparent; color: var(--muted);
  font: inherit; font-size: .85rem; font-weight: 600;
  padding: 6px 18px; border-radius: 999px; transition: background .15s, color .15s;
}
.pv-btn:hover { color: var(--text); }
.pv-btn.selected {
  background: var(--card); color: var(--accent-press);
  box-shadow: 0 1px 3px rgba(36,31,24,.08);
}

/* ---- Month-batch card ("here's your month, N posts to knock out") ---- */
#month-list { display: flex; flex-direction: column; gap: 6px; }
.month-head { margin: 0 0 6px; }
.month-lead { margin: 0; font-size: 1.02rem; font-weight: 700; color: var(--text); }
.month-tally { margin: 3px 0 0; }
.month-day { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.month-day-date { margin: 0 0 2px; }

/* "This week" header row: small "?" placeholder-tour button, top-left of the eyebrow */
.week-head-row { display: flex; align-items: center; gap: 7px; }
.week-head-row .eyebrow { margin: 0; }
.week-help-btn {
  width: 27px; height: 27px; border-radius: 50%; padding: 0; flex: none;
  background: var(--accent-soft); border: 1px solid var(--accent-line); color: var(--accent-press);
  font-size: .82rem; font-weight: 800; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background .15s ease, border-color .15s ease, transform .1s ease;
}
.week-help-btn:hover { background: var(--accent-line); }
.week-help-btn:active { transform: scale(.9); }
/* display:flex above would beat the UA [hidden] rule, so "Hide these ? buttons" needs this */
.week-help-btn[hidden] { display: none; }

/* week-arrows: step the planner 0..6 weeks ahead so a clinic can plan the whole
   month, not just this week. Same round-chip footprint as .week-help-btn so the
   header row reads as one family; #week-eyebrow sits between the two arrows. */
.week-nav-btn {
  width: 27px; height: 27px; border-radius: 50%; padding: 0; flex: none;
  background: var(--accent-soft); border: 1px solid var(--accent-line); color: var(--accent-press);
  font-size: 1.05rem; font-weight: 800; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background .15s ease, border-color .15s ease, transform .1s ease;
}
.week-nav-btn:hover { background: var(--accent-line); }
.week-nav-btn:active { transform: scale(.9); }
.week-nav-btn:disabled { opacity: .35; cursor: default; pointer-events: none; }
.week-head-row #week-eyebrow { flex: 1; text-align: center; }

/* "Fresh variety this week" — shows once when a new week has rolled since the
   last visit (see updateWeekFreshBadge); glanceable, self-clears next render */
.week-fresh-pill {
  margin-left: auto; display: inline-flex; align-items: center;
  font-size: .74rem; font-weight: 700; padding: 4px 10px; border-radius: 999px;
  color: var(--accent-press); background: var(--accent-soft);
  border: 1px solid var(--accent-line); white-space: nowrap;
}

/* v2 home sections (Travis, Jul 8): the page reads as SECTIONS — a centered,
   more prominent title over each block (All scripts / Your ideas) */
.section-title-lg {
  display: block; text-align: center; font-size: 1.18rem; font-weight: 700;
  letter-spacing: -.02em; margin: 30px 0 12px;
}
.scripts-lib-head { display: flex; flex-direction: column; align-items: center; }
.scripts-lib-head .section-title { margin: 30px 0 0; }
.scripts-lib-head .btn-text { padding: 4px 10px 10px; }

/* the library's condition groups fold behind a small arrow (.set-section base);
   collapsed, 150 scripts read as a handful of quiet rows */
.lib-group > summary { padding: 14px 16px; font-size: .95rem; }
.lib-group > summary::after { margin-left: 8px; }
.lib-group .lg-count { margin-left: auto; color: var(--muted); font-size: .8rem; font-weight: 600; }
.lib-group .lib-group-body { padding: 10px 12px 4px; }
.lib-group .lib-group-body .script-card { margin-bottom: 8px; }

/* the Archive pulldown: the SAME fold as an All-scripts condition, parked at the
   very bottom of the page. Deliberately quiet — a place cards rest and can be
   brought back, not a bin. */
/* the Archive fold is a park-it-and-forget-it drawer, NOT a peer of All-scripts /
   Your-ideas: push it well down behind a divider, shrink + mute the header, and let
   the whole group recede so it stays way out of the way. */
#scripts-archive { margin-top: 46px; border-top: 1px solid var(--border); padding-top: 14px; }
.archive-group { opacity: .82; }
.archive-group > summary { padding: 8px 14px; font-size: .8rem; }
.archive-group > summary .lg-name { color: var(--muted); font-weight: 600; }
.archive-group > summary::after { opacity: .55; }
.archive-group .lg-count { font-size: .72rem; }

.script-card {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: var(--card); border: 1px solid var(--border); color: var(--text);
  border-radius: var(--radius); padding: 14px 16px; margin-bottom: 10px;
  box-shadow: var(--shadow-sm);
  transition: background .15s ease, border-color .15s ease, transform .1s ease;
  position: relative;
}
.script-card:hover { background: var(--card-2); border-color: var(--border-2); }
.script-card:active { transform: scale(.99); }
.script-card .sc-hook { display: block; font-size: .98rem; line-height: 1.35; font-weight: 750; letter-spacing: -.01em; }
/* the studio-category tab: sits above the hook so it reads as a filing label,
   not a fourth line of metadata. Reuses the kit .chip look, just smaller so a
   card stays uncluttered (own-* cards only, see scriptCard in app.js). */
.script-card .sc-cat {
  display: inline-block; margin-bottom: 6px; padding: 2px 9px; font-size: .68rem;
}
.script-card .sc-meta { display: block; font-size: .76rem; color: var(--muted); margin-top: 5px; }
/* #14b — the seasonal soft tag: a recommended window, never a hard date. Quiet
   by default; a touch of accent only while its window is actually open. */
.script-card .sc-season {
  display: inline-block; margin-top: 7px; margin-right: 6px;
  font-size: .68rem; font-weight: 700; letter-spacing: .01em;
  padding: 3px 10px; border-radius: 999px; line-height: 1.35;
  color: var(--muted); background: var(--card-2); border: 1px solid var(--border);
}
.script-card .sc-season.now {
  color: var(--accent-press); background: var(--accent-soft); border-color: var(--accent-line);
}
.script-card .sc-theme { color: #a34a20; font-weight: 600; }
.script-card .sc-blog { color: var(--done); }
.script-card .sc-badge {
  position: absolute; top: 12px; right: 14px; font-size: .68rem; font-weight: 700;
  color: var(--done); text-transform: capitalize;
}
/* lifecycle states: a clean pill on its own row (record -> editing -> ready -> posted) */
.script-card .sc-state {
  display: inline-block; margin-top: 9px; font-size: .76rem; font-weight: 700;
  padding: 5px 12px; border-radius: 999px; line-height: 1.2;
}
.script-card .sc-editing {
  color: #9c6f12; background: rgba(240,180,70,.16); border: 1px solid rgba(176,125,30,.32);
  animation: statePulse 1.8s ease-in-out infinite;
}
.script-card .sc-ready {
  color: var(--done); background: rgba(52,199,89,.13); border: 1px solid rgba(52,199,89,.35);
}
.script-card .sc-posted {
  color: var(--muted); background: rgba(36,31,24,.06); border: 1px solid rgba(36,31,24,.12);
}
.script-card .sc-failed {
  color: var(--rec); background: rgba(229,72,77,.1); border: 1px solid rgba(229,72,77,.35);
}
@keyframes statePulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
.script-card.done { opacity: .6; }
.script-card.done:has(.sc-ready), .script-card.done:has(.sc-editing) {
  opacity: 1;
}
.script-card.done:has(.sc-ready) { border-color: rgba(52,199,89,.4); }
.week-rest-head { margin: 14px 2px 6px; }

/* a week slot = the tappable card with a little × + ⇄ in its top-right corner
   (still-recordable slots only; a made slot has nothing to remove/swap) */
.week-slot { position: relative; }
.week-slot .script-card { margin-bottom: 0; padding-right: 46px; }   /* one corner caret now, not two buttons */
/* the single corner caret that replaced the slot's ×/⇄ and the idea's + — same
   round-pill shape family, one control; opens the card-actions sheet. */
.card-menu-btn {
  position: absolute; top: 8px; right: 8px; z-index: 3;
  width: 30px; height: 30px; padding: 0; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--card); border: 1px solid var(--border-2); color: var(--muted);
  font-size: 1.15rem; line-height: 1;
  cursor: pointer; box-shadow: var(--shadow-sm);
  transition: background .15s ease, border-color .15s ease, color .15s ease, transform .1s ease;
}
.card-menu-btn svg { width: 18px; height: 18px; }
.card-menu-btn:hover { background: var(--card-2); border-color: var(--accent-line); color: #a34a20; }
.card-menu-btn:active { transform: scale(.92); }
.next-card .card-menu-btn { top: 14px; right: 14px; }
.slot-swap-btn, .slot-remove-btn {
  position: absolute; top: 8px; right: 8px; z-index: 3;
  width: 30px; height: 30px; padding: 0; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--card); border: 1px solid var(--border-2); color: var(--muted);
  font-size: 1.05rem; line-height: 1; cursor: pointer; box-shadow: var(--shadow-sm);
  transition: background .15s ease, border-color .15s ease, color .15s ease, transform .1s ease;
}
.slot-swap-btn:hover, .slot-remove-btn:hover { background: var(--card-2); border-color: var(--accent-line); color: #a34a20; }
.slot-swap-btn:active, .slot-remove-btn:active { transform: scale(.92); }
/* × sits just left of ⇄ so neither corner control crowds the other */
.slot-remove-btn { right: 44px; }
.next-card { }                       /* already position:relative */
.next-card .slot-swap-btn { top: 14px; right: 14px; }
.next-card h3 { padding-right: 40px; }   /* keep the hook clear of the corner ⇄ */

/* an idea slot = a My-ideas card with a little + in its top-right corner (still-
   recordable, not-yet-planned ideas only — see ideaSlot()/eligibleForPlacing).
   Same shape family as .week-slot/.slot-swap-btn above, different job: this one
   STARTS placing mode instead of swap mode. */
.idea-slot { position: relative; }
/* margin-bottom is left to .lib-group-body's own .script-card rule (higher
   specificity there wins anyway) -- only padding-right is this rule's job,
   clearing the hook text of the corner + */
.idea-slot .script-card { padding-right: 46px; }
.plan-add-btn {
  position: absolute; top: 8px; right: 8px; z-index: 3;
  width: 30px; height: 30px; padding: 0; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--card); border: 1px solid var(--border-2); color: var(--muted);
  font-size: 1.15rem; line-height: 1; cursor: pointer; box-shadow: var(--shadow-sm);
  transition: background .15s ease, border-color .15s ease, color .15s ease, transform .1s ease;
}
.plan-add-btn:hover { background: var(--card-2); border-color: var(--accent-line); color: #a34a20; }
.plan-add-btn:active { transform: scale(.92); }

/* ---- on-page SWAP MODE + PLACING MODE (share the sticky-banner look) ---- */
#swap-banner, #place-banner {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 10px;
  background: var(--accent); color: #fff;
  border-radius: var(--radius); padding: 12px 14px; margin: 0 0 14px;
  box-shadow: var(--shadow);
}
#swap-banner[hidden], #place-banner[hidden] { display: none; }
#swap-banner .sb-text, #place-banner .sb-text { flex: 1; font-size: .9rem; font-weight: 600; line-height: 1.35; }
#swap-banner .sb-text b { font-weight: 800; }
#swap-banner .swap-cancel, #place-banner .swap-cancel {
  flex: none; width: 32px; height: 32px; border-radius: 999px; cursor: pointer;
  background: rgba(255,255,255,.22); border: none; color: #fff; font-size: 1rem;
}
#swap-banner .swap-cancel:active, #place-banner .swap-cancel:active { background: rgba(255,255,255,.35); }
#swap-own[hidden] { display: none; }
.swap-own-title { margin-top: 4px; }
/* #14b: the swap-only seasonal shelf at the top of the pick list */
#swap-season[hidden] { display: none; }
#swap-season-list { margin-bottom: 14px; }
#swap-season-list .script-card { margin-bottom: 8px; }
/* enterSwapMode scrolls the pickable list into view; leave room for the pinned banner */
body.swap-mode #scripts-lib { scroll-margin-top: 72px; }
/* the lit targets + the ringed source; everything else dims + goes inert */
body.swap-mode .swap-target { outline: 2px solid var(--accent); outline-offset: 2px; }
body.swap-mode .swap-target.script-card { opacity: 1; }
body.swap-mode #scripts-list .script-card.swap-off,
body.swap-mode #scripts-ideas,
body.swap-mode #scripts-planned,
body.swap-mode #report-card { opacity: .38; pointer-events: none; }
body.swap-mode .week-slot:not(.swap-source) .card-menu-btn { opacity: .4; }
/* the rest of the week card recedes so the source slot + targets lead the eye */
body.swap-mode #week-list > *:not(.swap-source) { opacity: .4; }
body.swap-mode .swap-source {
  outline: 2px dashed var(--accent); outline-offset: 3px; border-radius: var(--radius-lg);
}
body.swap-mode .swap-source, body.swap-mode #week-list > .swap-source { opacity: 1; }
/* V2 planner: the source now sits nested in a lane — keep the lane holding it legible,
   but still recede the OTHER cards in that lane so the source + library targets lead the eye */
body.swap-mode #week-list > *:has(.swap-source) { opacity: 1; }
body.swap-mode #week-list .day-cards .week-slot:not(.swap-source) { opacity: .45; }

/* ---- on-page PLACING MODE: same scaffolding as swap mode, roles inverted --
   every day-lane is a lit tap target (any day, any week the arrows reach), the
   idea being placed rings in "Your ideas" below, and everything else not part
   of the decision recedes. */
body.place-mode #scripts-week { scroll-margin-top: 72px; }
body.place-mode .day-lane { outline: 2px solid var(--accent); outline-offset: 2px; cursor: pointer; }
body.place-mode .day-lane .week-slot .card-menu-btn { display: none; }   /* not a tap target right now */
body.place-mode #scripts-lib,
body.place-mode #scripts-planned,
body.place-mode #report-card,
body.place-mode #year-meter { opacity: .38; pointer-events: none; }
/* dim every OTHER My-ideas card, wrapped (.idea-slot) or plain (already-planned/
   recorded own cards sit directly in the fold, no wrapper) — the direct-child
   combinator on the plain rule keeps a wrapped card's opacity from compounding
   with its own inner .script-card */
body.place-mode #scripts-ideas .idea-slot:not(.place-source) { opacity: .45; pointer-events: none; }
body.place-mode #scripts-ideas .lib-group-body > .script-card:not(.place-source) { opacity: .45; pointer-events: none; }
body.place-mode .place-source {
  outline: 2px dashed var(--accent); outline-offset: 3px; border-radius: var(--radius-lg); opacity: 1 !important;
}

/* ===== Week Planner V2 — day-based shoot planner ==========================
   Composed from the warm-clinic tokens (no new hex): lanes are .card surfaces,
   Today leans on --accent-line/--accent-soft, drag states on --accent + --glow. */
.day-grid { display: flex; flex-direction: column; gap: 10px; margin-top: 14px; }
.day-lane {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 10px 12px;
  transition: border-color .15s ease, background .15s ease, box-shadow .15s ease;
}
.day-lane.empty { padding: 8px 12px; }   /* V2.1: a thin minimized row, not a big dashed drop box — still a full drop target (same .day-cards underneath) */
/* TODAY is marked by a small "Today" tab next to its date, NOT a lane accent
   highlight — that competed with the "▶ next" pill, which stays the ONLY accent.
   No lane border/ring or accent day-name on today anymore; the week reads uniform. */

.day-head { display: flex; align-items: baseline; gap: 8px; width: 100%;
  background: none; border: 0; padding: 0 2px 2px; text-align: left; font-family: inherit; }
.day-name { font-size: .92rem; font-weight: 800; letter-spacing: -.01em; color: var(--text); }
.day-date { font-size: .74rem; color: var(--muted); }
/* weekday header is tap-to-collapse/expand (Travis: pull a day's cards down /
   push them up to compare) — the whole header row is the tap target, with a
   small chevron on the right as the discoverable affordance. */
.day-head-collapsible { cursor: pointer; }
.day-head-collapsible:active { opacity: .85; }
/* right-aligned cluster: the (optional) "Today" tab + the collapse chevron */
.day-head-right { margin-left: auto; display: flex; align-items: center; gap: 6px; }
/* the "Today" tab: a small NEUTRAL chip (muted, not accent) so the "▶ next" pill is the sole accent */
.today-tag { font-size: .66rem; font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
  background: var(--card-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 2px 8px; }
.day-chev { color: var(--muted); font-size: .7rem; line-height: 1; transition: transform .18s ease; }
.day-lane.day-collapsed .day-chev { transform: rotate(-90deg); }   /* ▾ -> ▸ */

.day-cards { display: flex; flex-direction: column; gap: 8px; padding-top: 6px; min-height: 8px; }
.day-lane.empty .day-cards { padding-top: 0; min-height: 0; }
.day-lane.day-collapsed .day-cards { display: none; }
body.day-dragging .day-lane.day-collapsed .day-cards { display: flex; }   /* a drop target mid-drag, same pattern as the Weekend lane */
.day-rest { margin: 1px 2px 0; font-size: .78rem; color: var(--faint); }
.day-cards .week-slot { margin: 0; }

/* V2.1 — the "next" marker: the standalone hero is gone; the very next card to
   record (first unrecorded, in flat plan order — see planCard) gets a small
   "▶ Next: click to record this one" corner pill + a light accent ring. No
   separate button/link — tapping the CARD records it; the pill is only the label
   that says so. Everything else is a plain tappable card.
   Travis, Jul 16: the pill used to read a bare "NEXT", which named the state but
   never invited the tap, so nobody knew the card itself was the button. It now
   carries his wording. Sentence case, unlike the old uppercase tag styling: this
   is a sentence to read rather than a 4-letter tag to glance at, and app copy is
   sentence case everywhere else ("Record this script", "See your year"). At a
   390px viewport it measures ~155px in a 294px card, so it fits with room spare. */
.day-cards .week-slot.is-next .script-card {
  border-color: var(--accent-line); box-shadow: 0 0 0 1px var(--accent-line) inset;
}
.day-cards .week-slot.is-next::before {
  content: '\25B6 Next: click to record this one';
  position: absolute; z-index: 2; top: -7px; left: 10px;
  font-size: .64rem; font-weight: 800; letter-spacing: .01em;
  color: var(--card); background: var(--accent); border-radius: 999px; padding: 1px 7px;
  box-shadow: var(--shadow-sm);
}

/* collapsible Weekend lane (Sat/Sun folded, closed by default) */
.weekend-head { cursor: pointer; align-items: center; }
.weekend-chev { margin-left: 6px; color: var(--muted); transition: transform .18s ease; }
.weekend-lane:not(.collapsed) .weekend-chev { transform: rotate(180deg); }
.weekend-lane.collapsed .day-cards { display: none; }
body.day-dragging .weekend-lane.collapsed .day-cards { display: flex; }   /* a drop target mid-drag */

/* ---- drag states ---- */
body.day-dragging { overscroll-behavior-y: contain; }   /* kill Android pull-to-refresh mid-drag */
.day-cards .week-slot, .day-cards .script-card {
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;   /* iOS long-press magnifier -> touchcancel otherwise */
}
.week-slot.drag-origin { opacity: .34; }
.drag-ghost {
  position: fixed; z-index: 80; pointer-events: none; margin: 0;
  transform: translate3d(var(--gx), var(--gy), 0) scale(1.03) rotate(.5deg);
  box-shadow: var(--shadow-lg);
}
.day-lane.drop-hot { border-color: var(--accent); background: var(--accent-soft); }
.drop-bar { height: 3px; border-radius: 2px; background: var(--accent); margin: 1px 0;
  box-shadow: 0 0 0 3px var(--accent-soft); pointer-events: none; }

.week-toast {
  position: fixed; left: 50%; bottom: calc(18px + env(safe-area-inset-bottom));
  transform: translate(-50%, 12px); z-index: 90; opacity: 0; pointer-events: none;
  background: var(--text); color: var(--bg); font-size: .84rem; font-weight: 650;
  padding: 10px 16px; border-radius: 999px; box-shadow: var(--shadow-lg);
  transition: opacity .2s ease, transform .2s ease; max-width: 86vw; text-align: center;
}
.week-toast.show { opacity: 1; transform: translate(-50%, 0); }

/* SLICE 2 — the morning "planned videos" card: a warm tappable status nudge,
   snug under the This-week card (deliberately NOT a second tinted hero — the
   style guide caps tinted heroes at one per screen) */
.planned-card {
  display: flex; flex-direction: column; gap: 3px; width: 100%; text-align: left;
  background: var(--card); border: 1px solid var(--border);
  border-left: 4px solid var(--accent); border-radius: var(--radius);
  padding: 15px 16px; margin: -14px 0 18px; box-shadow: var(--shadow-sm); cursor: pointer;
  transition: background .15s ease, border-color .15s ease, transform .1s ease;
}
.planned-card:hover { background: var(--card-2); }
.planned-card:active { transform: scale(.99); }
.planned-card .pl-title { font-size: 1.05rem; font-weight: 800; letter-spacing: -.01em; }
.planned-card .pl-meta { font-size: .82rem; color: var(--muted); }

/* LAST WEEK content-report card (home hook) + skim page (view-report).
   The card is a quiet white card (the week-card stays the one tinted hero);
   the three numbers are the payoff. (Travis, Jul 8 — retention proof.) */
.report-card {
  display: flex; flex-direction: column; gap: 12px; width: 100%; text-align: left;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 15px 16px; margin: 0 0 16px;
  box-shadow: var(--shadow-sm); cursor: pointer;
  transition: background .15s ease, transform .1s ease;
}
.report-card:active { transform: scale(.99); }
.report-card .rc-head { display: flex; align-items: center; justify-content: space-between; }
.report-card .rc-more { font-size: .8rem; font-weight: 600; color: var(--accent); }
.rc-stats { display: flex; gap: 8px; }
.rc-stat {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
  background: var(--card-2); border-radius: 12px; padding: 12px 6px;
}
.rc-stat b { font-size: 1.5rem; font-weight: 800; letter-spacing: -.02em; color: var(--text); }
.rc-stat small { font-size: .72rem; color: var(--muted); text-align: center; }

/* the skim page: totals grid, best-performer highlight, per-video rows */
.report-range { margin: 2px 2px 16px; }
.report-totals {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 18px;
}
.rt-tile {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 12px; padding: 14px 6px;
}
.rt-tile b { font-size: 1.4rem; font-weight: 800; letter-spacing: -.02em; }
.rt-tile small { font-size: .72rem; color: var(--muted); text-align: center; }
.report-top {
  background: var(--card); border: 1px solid var(--border);
  border-left: 4px solid var(--accent); border-radius: var(--radius);
  padding: 14px 16px; margin-bottom: 8px;
}
.report-top .eyebrow { margin: 0 0 4px; }
.report-top .rt-cap { font-size: 1rem; font-weight: 800; letter-spacing: -.01em; margin: 0 0 2px; }
.report-top .rt-meta { margin: 0; }
.report-row {
  display: grid; grid-template-columns: 1fr auto; grid-template-areas: "cap lead" "plat lead";
  gap: 0 10px; align-items: center; text-decoration: none; color: var(--text);
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 12px 14px; margin-bottom: 8px;
}
.report-row .rr-cap { grid-area: cap; font-weight: 600; font-size: .92rem; }
.report-row .rr-lead { grid-area: lead; font-weight: 800; color: var(--accent); white-space: nowrap; }
.report-row .rr-plat { grid-area: plat; text-transform: capitalize; }
.report-hint { margin-top: 14px; }

/* The #studio-writer recess rule was DELETED here, not re-scoped (Jul 17). It recessed
   the composer's inputs to var(--bg-2) because white inputs inside the studio's white
   hero card read flat — and its own comment already recorded the other half of the
   answer: "the same node also mounts bare on #view-newscript, where white is right".
   The hero card is gone (the studio is a hub of doors) and #view-newscript is now the
   composer's only home, so the condition the recess existed for can no longer occur and
   white is simply right. Re-scoping it to #ns-composer would have carried a hero-card
   fix onto a page that has no hero card. */

/* ---- the composer's answer boxes: short open, grow to fit, drag bar ----
   Travis, Jul 17: "it starts off as one little box like it is now, and as it
   writes more it does a return and it makes it go down further, and we have a
   pulldown and pull up."
   Deliberately NO min-height here, which is the whole trick. An empty textarea's
   scrollHeight already counts its WRAPPED PLACEHOLDER, so fitTextarea opens every
   box at exactly "tall enough to read the prompt" and not one line more: the two
   short prompts open at one line, the two that wrap open at two. That is one rule
   instead of five hand-tuned floors, it is what "one little box" asks for, and it
   follows the copy by itself if the examples are ever reworded. A floor here would
   also be a trap: min-height BEATS an inline height, so a stale one would silently
   override every height fitTextarea computes (a 220px floor did exactly that once).
   fitTextarea keeps its own 56px floor. */
/* NO DRAG BARS ON THIS FORM (Travis, Jul 17: "the box should just grow as we speak and
   that's it. It's just responsive"), so the two rules that dressed the bar are gone with
   it: the `:has(+ .tx-grab)` corner-squaring, which can no longer match anything here,
   and the 14px bar height. `resize: none` is the one thing that had to SURVIVE the bar
   and is therefore now stated outright rather than as a side effect of `:has()`: the
   native corner handle is a drag handle too, and leaving it behind while removing the
   bar would be answering "should we be able to drag them" with "yes, but uglier".
   Scoped to the composer: #script-edit and the #cf-* fields keep their bars and their
   handles, which are Travis-approved. */
#ns-composer textarea { resize: none; }
/* a textarea is inline-block, so it sits on a text baseline and leaves ~4px of
   descender space under itself — which showed up as a white strip under the story box.
   The mic-row boxes never had it (a .mic-grow flex column blockifies them); block does
   the same for the two that stand in a plain label. */
#ns-composer textarea { display: block; }
/* THE AIR CAME PART-WAY BACK WITH THE BARS' REMOVAL, and the number is measured, not
   chosen. It was 12px because "a bar SEPARATES two fields as well as 20px of air did —
   so 20px of gap on top of it read as a hole"; before the bars it was 20px. With no bar,
   this gap is the ONLY thing separating a box from the next question's label, so 12px
   reads as one cramped block and 20px is what the form used to want.
   14px is where the fold put it. Six questions cannot also buy 20px: at 390x844 that
   lands "Write it for me" half under the tab bar at first paint (measured: bottom 823 vs
   tabbar 783). At 14px the CTA clears it, which is the behaviour the 4-question form had
   (bottom 770). Air is worth having; a primary button that looks sliced in half on
   arrival is not. */
#ns-composer > .field, #ns-guided .field, #ns-manual .field { margin-bottom: 14px; }

/* ---------- their own sections under "Your ideas" (create page) ----------
   These carry NO look of their own on purpose. Each one is a .set-section
   .lib-group — the exact fold the All-scripts conditions use directly above —
   so the page reads as one library with their categories at the bottom. All
   that's added is the ✎/× pair in the summary, and air around the group.
   Air first: this area sits at the very bottom of a long page, so it gets a
   clear break from the door above it rather than stacking flush. */
#ideas-sections { margin-top: 18px; }
.ig-tools { display: flex; align-items: center; gap: 2px; flex: none; margin-left: 10px; }
.ig-edit, .ig-del {
  flex: none; padding: 4px 6px; font-size: 1rem; line-height: 1; color: var(--muted);
}
.ig-edit:hover { color: var(--accent); }
.ig-del { font-size: 1.15rem; }
.ig-del:hover { color: var(--rec); }
/* the count sits right of the name (margin-left:auto, from .lib-group); once the
   tools follow it, it needs to stop hugging them */
.ideas-group > summary .lg-count { margin-right: 2px; }
.ig-empty { margin: 2px 4px 8px; }
.ig-add { display: block; padding: 10px 4px; margin-top: 2px; font-size: .9rem; }

/* the inline rename box, SHARED by both places a user names something of their
   own: an idea section (swaps for .lg-name inside the summary) and a video
   category (.vcat-input below). One rename, one look. */
.studio-sec-input {
  flex: 1; min-width: 0; background: var(--card); color: var(--text);
  border: 1px solid var(--accent); border-radius: 10px; padding: 7px 10px;
  font-size: .95rem; font-weight: 600; font-family: inherit;
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.studio-sec-input:focus { outline: none; }

/* ---------- your own categories on a video (My Videos) ----------
   The same mechanic as the idea sections above, pointed at videos, so it
   borrows their language on purpose: ✎ rename, × delete, "+ New category",
   and literally the same .studio-sec-input for the inline rename.
   The row chip: dashed + muted = an empty invitation, accent tint = a name
   they chose. Small and quiet, because the video is the content, this is a tag. */
.vcat-btn {
  appearance: none; cursor: pointer; font-family: inherit;
  display: inline-block; margin-left: 8px; max-width: 100%;
  font-size: .7rem; font-weight: 700; padding: 3px 9px; border-radius: 999px;
  background: none; border: 1px dashed var(--border-2); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.vcat-btn:hover { border-color: var(--accent-line); color: var(--accent); }
.vcat-btn:active { transform: scale(.97); }
.vcat-btn.set {
  background: var(--accent-soft); border: 1px solid var(--accent-line); color: #a34a20;
}
/* the picker (bottom-sheet reusing .pick-sheet / .pick-panel / .pick-list) */
.vcat-sheet .vcat-sub {
  margin: 0 2px 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vcat-line { display: flex; align-items: center; gap: 4px; }
.vcat-opt {
  flex: 1; min-width: 0; display: flex; align-items: center; justify-content: space-between;
  gap: 10px; text-align: left; cursor: pointer; font-family: inherit;
  background: var(--card-2); border: 1px solid var(--border); border-radius: 12px;
  padding: 12px 14px; font-size: .95rem; font-weight: 600; color: var(--text);
}
.vcat-opt:hover { background: var(--card-3); border-color: var(--accent-line); }
.vcat-opt.active { background: var(--accent-soft); border-color: var(--accent); }
.vcat-opt-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vcat-tick { flex: none; color: var(--accent); font-weight: 800; visibility: hidden; }
.vcat-opt.active .vcat-tick { visibility: visible; }
/* fixed-width glyphs, so the tail measures the same whatever the font does
   with ✎ and × (the .vcat-tail spacer below is cut to match: 28 + 4 + 28) */
.vcat-edit, .vcat-del {
  flex: none; width: 28px; padding: 4px 0; text-align: center;
  font-size: 1rem; line-height: 1; color: var(--muted);
}
.vcat-edit:hover { color: var(--accent); }
.vcat-del { font-size: 1.15rem; }
.vcat-del:hover { color: var(--rec); }
.vcat-tail { flex: none; width: 60px; }
.vcat-empty { margin: 6px 2px; }
.vcat-input { flex: 1; }
.vcat-add { display: block; padding: 12px 4px 2px; font-size: .9rem; }

/* freestyle recorder: the calm line where the script would have been */
.pr-freestyle {
  display: block; text-align: center; color: rgba(255,255,255,.85);
  font-size: .62em; line-height: 1.6; padding: 10px 20px;
}
.pr-freestyle i { color: rgba(255,255,255,.55); font-style: normal; font-size: .85em; }

/* the edit FAILED — the card that replaces the eternal "Editing…" */
.detail-failed { border-color: rgba(229,72,77,.4); padding: 14px 16px; margin-bottom: 14px; }
.detail-failed b { color: var(--rec); }
.detail-failed p { margin: 8px 0; }
.detail-failed a { color: var(--accent); font-weight: 600; }

/* post-send confirmation on the review page -- also doubles as the in-flight
   "uploading" / failed state now that send advances here BEFORE the upload
   resolves (Travis, Jul 22: don't trap him on a disabled "Sending" screen).
   .sending is deliberately quiet (no red box) -- the upload isn't an
   emergency, it's just background work. */
.review-sent { text-align: center; font-weight: 700; font-size: 1.12rem; margin: 6px 0 10px; line-height: 1.5; }
.review-sent .muted { font-weight: 400; }
.review-sent.sending { font-weight: 650; font-size: 1rem; color: var(--text); }
.review-sent.err { color: var(--rec); font-size: 1rem; }
.send-spin-dot {
  display: inline-block; width: 13px; height: 13px; vertical-align: -2px;
  margin-right: 7px; border: 2px solid var(--card-3); border-top-color: var(--accent);
  border-radius: 50%; animation: spin .8s linear infinite;
}

.link-copy { display: flex; gap: 8px; align-items: stretch; }
.link-copy input {
  flex: 1; min-width: 0; background: var(--card); color: var(--muted);
  border: 1px solid var(--border-2); border-radius: 12px; padding: 12px 14px;
  font-size: .85rem; font-family: ui-monospace, monospace;
}
.link-copy .btn-secondary { width: auto; flex-shrink: 0; padding: 10px 16px; }

.card {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 18px 18px; margin-bottom: 20px;
  box-shadow: var(--shadow-sm);
}
.card h3 { margin: 0 0 12px; font-size: 1rem; display: flex; align-items: center; gap: 10px; }

.brand-row { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; }
.brand-colors { width: 100%; }
.comp-warn {
  background: rgba(192,90,43,.10); border: 1px solid rgba(192,90,43,.35);
  color: #a34a20; border-radius: 12px; padding: 12px 14px; margin: 0 0 14px;
  font-size: .92rem; line-height: 1.5;
}
/* the choose-your-logo cluster: the logo, its pencil, and the scraped candidates all
   sit together, so the control is on the thing it changes (Travis, Jul 16) */
.cf-logo-pick { width: 100%; }
.cf-logo-head { display: flex; align-items: center; gap: 12px; }
.cf-logo-change { padding: 10px 6px; margin-left: -2px; }   /* .btn-text, compact inline */
.cf-logo-btn {
  position: relative; padding: 0; background: none; border: none;
  cursor: pointer; flex-shrink: 0; margin-top: 4px;
}
.cf-logo-btn:active { transform: scale(.96); }
.cf-logo {
  display: block; width: 64px; height: 64px; object-fit: contain;
  border-radius: 12px; background: #fff; padding: 6px;
  border: 1px solid var(--border);
}
.cf-logo-empty {
  display: flex; align-items: center; justify-content: center; text-align: center;
  width: 64px; height: 64px; border-radius: 12px;
  border: 2px dashed var(--border-2); color: var(--faint);
  font-size: .72rem; line-height: 1.25;
}
.cf-logo-empty[hidden] { display: none; }
.cf-logo-edit {
  position: absolute; right: -7px; bottom: -7px; width: 22px; height: 22px;
  border-radius: 50%; background: var(--accent); color: #fff; font-size: .68rem;
  display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--card);
}
.cf-brand-intro { margin: 0 0 14px; line-height: 1.45; }
.brand-colors { flex: 1; display: flex; flex-direction: column; gap: 10px; }
.brand-colors .field { margin-bottom: 0; }
.accent-field { flex: 1; margin-bottom: 0; }
.accent-pick {
  display: flex; align-items: center; gap: 10px;
  border: 2px solid transparent; border-radius: 14px; padding: 3px;
}
.accent-pick.active { border-color: var(--accent-line); background: var(--accent-soft); }
.accent-pick input[type="color"] {
  width: 46px; height: 46px; padding: 0; border: 1px solid var(--border-2);
  border-radius: 12px; background: none; cursor: pointer;
}
.accent-pick .hex {
  width: 120px; background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 12px; padding: 12px 14px;
  font-size: 1rem; font-family: ui-monospace, monospace;
}
.accent-pick .hex:focus { outline: none; border-color: var(--accent); }

/* colors pulled from the logo */
.palette-wrap { margin-top: 14px; }
.palette-label { margin: 0 0 8px; }
.palette-row { display: flex; gap: 10px; flex-wrap: wrap; }
.swatch {
  width: 42px; height: 42px; border-radius: 12px; cursor: pointer;
  border: 2px solid rgba(36,31,24,.14);
}
.swatch:active { transform: scale(.9); }

.rules { margin: 4px 0 12px; padding-left: 20px; }
.rules li { font-size: .9rem; color: var(--text); margin-bottom: 7px; line-height: 1.45; }
.badge-warn { background: rgba(240,180,70,.16); color: #9c6f12; border-color: rgba(176,125,30,.32); }

#cf-comp-sources a { color: var(--accent); word-break: break-all; }
.comp-legal {
  margin: 12px 0 0; padding: 10px 12px; font-size: .8rem; line-height: 1.5;
  color: var(--muted); background: rgba(240,180,70,.07);
  border: 1px solid rgba(240,180,70,.22); border-radius: 10px;
}
.comp-legal strong { color: #9c6f12; }

/* compliance card: editable body + alternatives + re-check */
.comp-lead { margin: 0 0 14px; line-height: 1.5; }
.comp-body-field { margin-bottom: 10px; }
.comp-body-field input {
  width: 100%; background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 12px; padding: 14px; font-size: 1rem;
}
.comp-body-field input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.comp-alts { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 14px; }
.alt-chip {
  background: var(--card-2); color: var(--text); border: 1px solid var(--border-2);
  border-radius: 999px; padding: 6px 12px; font-size: .82rem; cursor: pointer;
}
.alt-chip:hover { background: var(--card-3); border-color: var(--accent-line); }
.comp-rules-label { margin: 6px 0 6px; font-weight: 600; }
.comp-add { margin-top: 6px; border-top: 1px solid var(--border); padding-top: 14px; }
.comp-add .field { margin-bottom: 12px; }
.comp-add .btn-secondary { width: auto; padding: 10px 16px; }
#cf-comp-status { margin-left: 10px; }
.comp-dirty {
  margin: 0 0 14px; font-size: .85rem; color: #9c6f12;
  background: rgba(240,180,70,.12); border: 1px solid rgba(176,125,30,.3);
  border-radius: 10px; padding: 9px 12px;
}
/* promote "Update my rules" to an accent button while the body is edited-but-unchecked */
.btn-attn {
  background: var(--accent) !important; color: #fff !important;
  border-color: var(--accent) !important;
}
.btn-attn:hover { background: var(--accent-hover) !important; }

/* My Videos cover check. The active cover stays dominant; alternatives are
   bounded to three house-safe options plus the explicit natural start. */
.cover-pick {
  margin-top: 10px; padding: 14px;
  background: var(--card-2); border: 1px solid var(--border);
  border-radius: var(--radius);
}
.cover-pick p { margin: 0; }
.cover-explainer { color: var(--muted); font-size: .9rem; line-height: 1.45; }
.cover-current {
  display: grid; grid-template-columns: 72px minmax(0, 1fr) auto;
  align-items: center; gap: 12px; margin-top: 12px;
}
.cover-current-preview {
  display: block; width: 72px; aspect-ratio: 9 / 16;
  border-radius: 9px; background: var(--card-3) center/cover no-repeat;
  border: 1px solid var(--border-2);
}
.cover-current-preview.is-empty::after {
  content: "No cover"; display: grid; place-items: center; height: 100%;
  padding: 6px; text-align: center; color: var(--muted); font-size: .72rem;
}
.cover-current-copy { min-width: 0; display: grid; gap: 4px; }
.cover-current-copy strong { font-size: .95rem; color: var(--text); }
.cover-current-copy > span { color: var(--muted); font-size: .8rem; line-height: 1.35; }
.vid-detail .links .cover-current a.btn-secondary.thumb-edit-link {
  width: auto; min-height: 44px; padding: 10px 16px; margin: 0;
  color: var(--text); font-size: .9rem; font-weight: 700; white-space: nowrap;
}
.cover-alternates { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.cover-alternates > p { margin-bottom: 8px; }
.cover-opts { display: flex; align-items: flex-start; flex-wrap: wrap; gap: 10px; }
.cover-choice {
  width: 68px; padding: 0; border: 0; background: none; color: var(--text);
  font: inherit; cursor: pointer; text-align: center;
}
.cover-choice-preview {
  display: block; width: 62px; margin: 0 auto 5px; aspect-ratio: 9 / 16;
  background: var(--card-3) center/cover no-repeat;
  border: 2px solid var(--border); border-radius: 8px;
  transition: border-color .15s ease, transform .1s ease;
}
.cover-choice:hover .cover-choice-preview { border-color: var(--accent-line); transform: translateY(-1px); }
.cover-choice:focus-visible { outline: 3px solid var(--accent-soft); outline-offset: 3px; border-radius: 8px; }
.cover-choice:disabled { cursor: wait; opacity: .55; }
.cover-choice-label { display: block; font-size: .68rem; font-weight: 700; line-height: 1.2; }
.cover-choice-none .cover-choice-preview { position: relative; filter: saturate(.45); }
.cover-choice-none .cover-choice-preview::after {
  content: ""; position: absolute; inset: 8px 29px;
  background: var(--accent); border-radius: 999px; transform: rotate(32deg);
  box-shadow: 0 0 0 2px var(--card);
}
.cover-status { min-height: 1.2em; margin-top: 10px !important; }
@media (max-width: 520px) {
  .cover-pick { padding: 12px; }
  .cover-current { grid-template-columns: 62px minmax(0, 1fr); gap: 10px; }
  .cover-current-preview { width: 62px; }
  .vid-detail .links .cover-current a.btn-secondary.thumb-edit-link {
    grid-column: 1 / -1; width: 100%;
  }
  .cover-opts { justify-content: flex-start; gap: 8px; }
}
/* ---------- post screen: master caption on top ---------- */
.master-cap { margin-bottom: 14px; padding: 14px 16px 12px; }
.master-cap h3 { margin: 0 0 8px; font-size: 1rem; }
.master-cap textarea {
  width: 100%; min-height: 110px; background: var(--card-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 10px; padding: 10px 12px;
  font: inherit; font-size: .95rem; line-height: 1.5; resize: none;
}
.master-row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 10px; }
.plat-field textarea { resize: none; }   /* boxes auto-grow instead */

/* ---------- deploy (post your video) ---------- */
.deploy-writing { display: flex; align-items: center; gap: 12px; padding: 22px 4px; }
.deploy-writing p { margin: 0; }

.plat-card {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg); margin-bottom: 12px; overflow: hidden;
  transition: opacity .15s ease;
}
.plat-card.off { opacity: .5; }
.plat-card.off .plat-body { display: none; }
.plat-head {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; cursor: pointer; user-select: none;
}
.plat-head input[type=checkbox] {
  width: 20px; height: 20px; accent-color: var(--accent); flex: none; margin: 0;
}
.plat-icon {
  width: 30px; height: 30px; border-radius: 8px; flex: none;
  background: var(--card-2); border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center; font-size: .95rem;
}
.plat-name { font-weight: 600; flex: 1; }
.plat-body { padding: 0 16px 16px; display: flex; flex-direction: column; gap: 12px; }
.plat-field { margin: 0; }
.plat-field-head { display: flex; justify-content: space-between; align-items: baseline; }
.char-count { font-style: normal; font-size: .75rem; color: var(--faint); }
.char-count.over { color: var(--rec); font-weight: 600; }
.plat-body textarea, .plat-body input[type=text] {
  width: 100%; background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 10px;
  padding: 11px 12px; font-size: .95rem; font-family: inherit; line-height: 1.45;
  resize: vertical;
}
.plat-body textarea:focus, .plat-body input[type=text]:focus {
  outline: none; border-color: var(--accent-line);
}
.gbp-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.gbp-actions .btn-secondary {
  flex: 1; min-width: 150px; text-align: center; text-decoration: none;
  display: flex; align-items: center; justify-content: center;
}

.deploy-when { padding: 14px 16px; margin-bottom: 14px; }
.deploy-when h3 { margin: 0 0 10px; font-size: .95rem; }
.sched-row { display: flex; gap: 14px 18px; margin-bottom: 10px; flex-wrap: wrap; }
.sched-opt { display: flex; align-items: center; gap: 8px; cursor: pointer; }
/* the planned-day choice (#9): the middle rung between Post now and Schedule —
   a quiet accent so the pre-picked day advertises itself without shouting */
.sched-opt.sched-planned { color: var(--accent-press); font-weight: 600; white-space: nowrap; }
.sched-opt input { accent-color: var(--accent); width: 18px; height: 18px; margin: 0; }
#sched-time {
  width: 100%; background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 10px;
  padding: 11px 12px; font-size: 1rem; font-family: inherit;
  color-scheme: light;
}

.deploy-done { padding: 18px; margin: 14px 0; }
.deploy-done h3 { margin: 0 0 10px; color: var(--done); }
.deploy-done p { margin: 8px 0; }

/* ---------- social accounts (Settings connect + Post page "Posting to") ---------- */
.social-accounts { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.social-acct {
  display: flex; align-items: center; gap: 10px;
  background: var(--card-2); border: 1px solid var(--border);
  border-radius: 12px; padding: 8px 12px;
}
.social-acct-ic {
  width: 34px; height: 34px; border-radius: 50%; overflow: hidden; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--card-3); font-size: 1rem;
}
.social-acct-ic img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* ---------- real platform brand marks (Settings -> connect accounts) ----------
   The mark itself communicates status: desaturated + dim = not linked yet,
   full brand color = live. This is the ONE place brand color is allowed to
   run outside the app palette — Instagram/Facebook's own published mark
   colors, not a warm-clinic theme decision. Everywhere else a "posted to
   Instagram" style tag needs to read (a video card, a step label) uses the
   flat monochrome .posted-tag glyph below instead — two visually distinct
   patterns on purpose (action vs. metadata). */
.soc-mark {
  width: 34px; height: 34px; border-radius: 10px; flex: none;
  display: flex; align-items: center; justify-content: center;
  transition: filter .15s ease;
}
.soc-mark svg { width: 18px; height: 18px; }
.soc-mark.instagram { background: linear-gradient(135deg, #feda75, #fa7e1e 28%, #d62976 58%, #962fbf 78%, #4f5bd5); }
.soc-mark.facebook { background: #1877f2; }
.soc-mark.off { filter: grayscale(1) opacity(.5); }
/* round avatar -> soft square for a brand mark; .soc-mark.instagram/.facebook
   (below) already out-specificities the base --card-3 fill, so no background
   reset needed here — leaving it out avoids a same-specificity source-order
   fight with those two rules. */
.social-acct-ic.soc-mark { border-radius: 10px; }
.social-acct-name { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.social-acct-name b {
  font-size: .92rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* "Connected" reads as a chip/pill (quiet-depth rule), not bare colored text */
.social-ok {
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  font-size: .72rem; font-weight: 700; letter-spacing: .01em;
  padding: 5px 11px; border-radius: 999px;
  background: rgba(34,136,90,.12); color: var(--done);
}
.social-ok .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--done); flex: none; }
.social-fix { color: var(--accent); flex: none; padding: 4px 0; }
/* Disconnect = the quiet secondary action, never competing with Connected/Reconnect */
.social-disconnect { color: var(--muted); flex: none; padding: 4px 0; margin-left: 4px; font-size: .85rem; }
.social-disconnect:active { color: var(--accent-press); }
/* ---- shared social connections (connect once, lend across workspaces) ---- */
.social-share { color: var(--accent); flex: none; padding: 4px 0; margin-left: 4px; font-size: .85rem; }
/* a borrowed account wears where it came from, so it never reads as a fresh
   connection made on this workspace */
.social-shared-tag {
  display: inline-block; align-self: flex-start; margin-top: 3px;
  font-size: .68rem; letter-spacing: .01em; color: var(--muted);
  background: var(--card-3); border-radius: 999px; padding: 2px 9px;
}
/* the share picker reuses the .pick-sheet / .pick-panel / .pick-list shell */
.share-list { max-height: 46vh; }
.share-clinic {
  display: flex; align-items: center; gap: 11px;
  padding: 11px 4px; cursor: pointer;
  border-bottom: 1px solid var(--border);
}
.share-clinic:last-child { border-bottom: 0; }
.share-cb { width: 18px; height: 18px; flex: none; accent-color: var(--accent); }
.share-clinic-name { font-size: .95rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.share-manual { margin: 4px 0 10px; }
.share-slugs {
  width: 100%; padding: 10px 12px; border-radius: 10px;
  border: 1px solid var(--border); font: inherit;
  background: var(--card); color: var(--text);
}
.share-go { width: 100%; margin-top: 8px; }

.social-connect-row { display: flex; gap: 8px; flex-wrap: wrap; margin: 6px 0 4px; }
.social-connect-btn { display: inline-flex; align-items: center; gap: 9px; font-size: .88rem; padding: 8px 14px 8px 8px; }
.social-connect-btn .soc-mark { width: 28px; height: 28px; border-radius: 8px; }
.social-connect-btn .soc-mark svg { width: 15px; height: 15px; }

.deploy-accounts { padding: 14px 16px; margin-bottom: 14px; }
.deploy-accounts h3 { margin: 0 0 10px; font-size: .95rem; }
.social-chip-row { display: flex; gap: 8px; flex-wrap: wrap; }
.social-chip {
  display: flex; align-items: center; gap: 7px;
  padding: 5px 13px 5px 6px; border-radius: 999px;
  background: var(--card-2); border: 1px solid var(--border);
  color: var(--faint); font-size: .86rem; font-family: inherit;
  cursor: pointer; opacity: .55;
  transition: opacity .12s ease, border-color .12s ease, background .12s ease;
}
.social-chip.on {
  opacity: 1; background: var(--accent-soft);
  border-color: var(--accent-line); color: var(--text);
}
.social-chip-ic {
  width: 26px; height: 26px; border-radius: 50%; overflow: hidden; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--card-3); font-size: .82rem;
}
.social-chip-ic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.social-chip-txt { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* geotag pulldown: pick the place to tag (IG/FB), pre-set to the clinic default */
.deploy-loc { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.deploy-loc-lbl { display: block; font-size: .84rem; font-weight: 600; color: var(--text); margin-bottom: 7px; }
.deploy-loc-lbl i { font-weight: 400; color: var(--faint); font-style: normal; }
.deploy-loc-row { display: flex; align-items: center; gap: 10px; }
.deploy-loc-sel {
  flex: 1; min-width: 0; padding: 9px 12px; border-radius: 10px;
  border: 1px solid var(--border-2); background: var(--bg-2); color: var(--text);
  font-size: .92rem; font-family: inherit;
}
.deploy-loc-sel:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.deploy-loc-def { flex: none; white-space: nowrap; }

.post-link { font-weight: 600; }

/* Edit = refine the auto-render (outline button above Post) */
.vid-detail .links a.edit-link {
  display: block; text-align: center; margin-bottom: 10px;
  color: var(--text); background: var(--card-2); border: 1px solid var(--border-2);
  font-weight: 700; font-size: 1.02rem; padding: 14px 16px; border-radius: 999px;
}
.vid-detail .links a.edit-link:hover { background: var(--card-3); border-color: var(--accent-line); color: var(--text); }
/* the thumbnail-edit button sits a touch lower for breathing room under the cover strip (Travis) */
.vid-detail .links a.edit-link.thumb-edit-link { margin-top: 14px; }
/* Share = the hero action on a finished video (native share sheet -> IG/TikTok) */
.vid-detail .links a.share-link {
  display: block; text-align: center;
  color: #fff; background: var(--accent); box-shadow: var(--glow);
  font-weight: 700; font-size: 1.05rem; padding: 15px 16px; border-radius: 999px;
}
.vid-detail .links a.share-link:hover { color: #fff; background: var(--accent-hover); }

/* ---------- content calendar ---------- */
.cal-nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 12px;
}
.cal-nav b { font-size: 1.05rem; letter-spacing: -.01em; }
.cal-dow {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
  margin-bottom: 4px; text-align: center;
  font-size: .72rem; color: var(--faint); text-transform: uppercase;
}
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal-cell {
  aspect-ratio: 1; border-radius: 10px; border: 1px solid transparent;
  background: var(--card); color: var(--text); cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; font-size: .88rem; padding: 0;
  transition: border-color .12s ease, background .12s ease;
}
.cal-cell b { font-weight: 500; }
.cal-cell:hover { background: var(--card-2); }
.cal-cell.today { border-color: var(--border-2); background: var(--card-2); }
.cal-cell.today b { color: var(--accent); font-weight: 700; }
.cal-cell.sel { border-color: var(--accent); background: var(--accent-soft); }
.cal-dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
  display: block;
}
.cal-list-title { font-size: .95rem; margin: 18px 0 10px; }
/* per-day "put something on this day" entry (calendar auto-post flow) —
   the two source cards shown directly under whatever's planned */
.cal-postown { margin-top: 18px; }
.cal-postown-lbl { margin: 0 0 8px; }
.cal-postown .cal-src { margin: 0 0 8px; }
.cal-postown .cal-src:last-child { margin-bottom: 0; }
/* video picker sheet (calendar "pick from My Videos") + working toast */
.pick-sheet {
  position: fixed; inset: 0; z-index: 60; background: rgba(36,31,24,.5);
  display: flex; align-items: flex-end;
}
.pick-panel {
  background: var(--card); width: 100%; max-height: 80vh;
  border-radius: 20px 20px 0 0; display: flex; flex-direction: column;
  padding: 10px 14px 20px;
}
.pick-head { display: flex; align-items: center; justify-content: space-between; padding: 6px 2px 10px; }
/* the card-actions sheet (openCardSheet): a stack of full-width action rows + an
   optional quiet hint, in the shared .pick-sheet shell. "Take off this week" is
   the QUIETER row on purpose — it un-schedules (the script stays), so it must not
   read as an alarming delete; Swap is the neutral-weight primary. */
.card-sheet-hook { font-size: .95rem; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 80%; }
.card-sheet-actions { display: flex; flex-direction: column; gap: 10px; padding: 6px 2px 2px; }
.card-sheet-btn {
  width: 100%; text-align: left; padding: 14px 16px; border-radius: 14px;
  background: var(--card); border: 1px solid var(--border-2); color: var(--text);
  font-size: 1rem; font-weight: 500; cursor: pointer;
  transition: background .15s ease, border-color .15s ease, transform .1s ease;
}
.card-sheet-btn:hover { background: var(--card-2); border-color: var(--accent-line); }
.card-sheet-btn:active { transform: scale(.98); }
.card-sheet-quiet { color: var(--muted); font-weight: 400; }
.card-sheet-hint { margin: 4px 4px 0; }
.card-sheet-confirm { font-size: .95rem; color: var(--text); line-height: 1.4; margin: 2px 4px 8px; }
.pick-list { overflow-y: auto; display: flex; flex-direction: column; gap: 8px; }
.pick-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: var(--card-2); border: 1px solid var(--border); border-radius: 12px; padding: 8px;
}
.pick-row:hover { background: var(--card-3); border-color: var(--accent-line); }
.pick-thumb {
  width: 46px; height: 60px; border-radius: 8px; flex: 0 0 auto;
  background: #000 center/cover no-repeat;
  display: flex; align-items: center; justify-content: center; font-size: 1.2rem;
}
.pick-title { font-weight: 600; font-size: .95rem; }
.busy-toast {
  position: fixed; left: 50%; bottom: 30px; transform: translateX(-50%); z-index: 70;
  background: var(--text); color: #fff; padding: 12px 18px; border-radius: 999px;
  font-size: .9rem; box-shadow: 0 6px 20px rgba(0,0,0,.25);
}
/* in-app "name this video" card (replaces the native prompt) */
.name-sheet {
  position: fixed; inset: 0; z-index: 65; background: rgba(36,31,24,.5);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.name-panel {
  background: var(--card); width: 100%; max-width: 380px;
  border-radius: var(--radius-lg); padding: 20px; box-shadow: 0 12px 40px rgba(0,0,0,.25);
}
.name-panel h3 { margin: 0 0 4px; }
.name-input {
  width: 100%; margin: 14px 0 2px; padding: 12px 14px; border-radius: 12px;
  border: 1px solid var(--border-2); background: var(--bg-2); color: var(--text);
  font-size: 1rem; font-family: inherit;
}
.name-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.name-actions { display: flex; gap: 10px; margin-top: 16px; }
.name-actions .btn-primary, .name-actions .btn-secondary { flex: 1; }
.cal-postown-note { margin-top: 10px; }
.cal-dep { padding: 14px 16px; margin-bottom: 10px; }
.cal-dep-head { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; }
.cal-dep-head b { font-weight: 600; }
.chips-row { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.plat-chip {
  font-size: .74rem; padding: 4px 10px; border-radius: 999px;
  background: var(--card-2); border: 1px solid var(--border);
  color: var(--muted);
}
.plat-chip.st-scheduled { color: var(--accent); border-color: var(--accent-line); }
/* the picture/carousel badge on an image-lane calendar record (#4) */
.plat-chip.cal-kind { color: var(--accent); border-color: var(--accent-line); font-weight: 700; }
.plat-chip.st-posted { color: var(--done); border-color: rgba(70,201,139,.4); }
.plat-chip.st-waiting-connection { color: #9c6f12; border-color: rgba(176,125,30,.35); }
.plat-chip.st-failed { color: var(--rec); border-color: rgba(255,92,87,.4); }
.cal-remove { margin-top: 6px; padding: 4px 0; font-size: .82rem; color: var(--faint); }

/* ---------- deploy v2: adjust-all box + collapsible cards ---------- */
.deploy-adjust { display: flex; gap: 8px; margin-bottom: 10px; }
#deploy-instruction {
  flex: 1; min-width: 0; background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 10px;
  padding: 12px 13px; font-size: .95rem; font-family: inherit;
}
#deploy-instruction:focus { outline: none; border-color: var(--accent-line); }
.deploy-adjust .btn-secondary { width: auto; flex: none; padding: 0 16px; white-space: nowrap; }
.deploy-hint { margin: 0 0 14px; }

.plat-headtext { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.plat-name { font-weight: 600; }                 /* override v1 flex:1 — headtext flexes now */
.plat-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.plat-card:not(.collapsed) .plat-preview { display: none; }
.plat-card.collapsed .plat-body { display: none; }
.plat-chev {
  flex: none; color: var(--faint); font-size: 1.3rem; line-height: 1;
  transition: transform .18s ease;
}
.plat-card:not(.collapsed) .plat-chev { transform: rotate(90deg); }

/* ---------- custom player skin (native video underneath; theme E) ----------
   placed LAST so it wins over the per-page video selectors above */
.vplayer {
  position: relative; border-radius: var(--radius-lg); overflow: hidden;
  background: #000; border: 1px solid var(--border);
  max-width: 100%; margin: 0 auto;
}
.vplayer video {
  display: block; margin: 0 auto; width: 100%; max-height: 64vh;
  background: #000; border: none !important; border-radius: 0 !important;
  object-fit: contain;
}
.vp-big {
  position: absolute; inset: 0; margin: auto; width: 72px; height: 72px;
  border-radius: 50%; border: none; cursor: pointer;
  background: rgba(255,255,255,.94); color: var(--accent);
  font-size: 1.5rem; padding: 0 0 0 5px;
  display: none; align-items: center; justify-content: center;
  box-shadow: 0 10px 30px rgba(0,0,0,.35);
}
.vplayer.paused .vp-big { display: flex; }
.vp-bar {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  display: flex; align-items: center; gap: 8px; padding: 14px 12px 10px;
  background: linear-gradient(transparent, rgba(0,0,0,.7));
}
.vp-play, .vp-mute, .vp-fs {
  -webkit-appearance: none; appearance: none;
  background: none; border: none; box-shadow: none; cursor: pointer;
  color: #fff; font-size: 1rem; width: 34px; height: 34px; min-height: 34px;
  padding: 0; flex: none; display: flex; align-items: center; justify-content: center;
}
.vp-prog { position: relative; flex: 1; height: 26px; cursor: pointer; touch-action: none; }
.vp-prog::before {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  transform: translateY(-50%); height: 5px; border-radius: 999px;
  background: rgba(255,255,255,.3);
}
.vp-fill {
  position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  height: 5px; border-radius: 999px; background: #e8763d; width: 0;
}
.vp-knob {
  position: absolute; top: 50%; left: 0; width: 14px; height: 14px;
  border-radius: 50%; background: #fff; transform: translate(-50%, -50%);
  box-shadow: 0 1px 4px rgba(0,0,0,.5);
}
.vp-time {
  color: #fff; font-size: .75rem; font-variant-numeric: tabular-nums;
  flex: none; text-shadow: 0 1px 3px rgba(0,0,0,.8);
}
.vplayer:fullscreen { border-radius: 0; border: none; }
.vplayer:fullscreen video { max-height: 100vh; height: 100%; }
.help-box .vplayer { width: 100%; }

/* ---------- your clinic footage (b-roll library, Settings) ---------- */
.broll-actions { display: flex; gap: 10px; }
.broll-actions .btn-secondary { flex: 1; }
/* clip search: a quiet rounded pill with an inline magnifier (type=search
   isn't covered by the .field input rules, so it was rendering bare) */
.broll-search { display: block; margin: 12px 0 10px; }
.broll-search input {
  width: 100%; background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 999px;
  padding: 12px 16px 12px 42px; font-size: .98rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%237d7466' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.5' y2='16.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: left 16px center;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.broll-search input:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}

/* the uploaded end card, shown at phone proportions */
.endcard-preview {
  display: block; width: 128px; aspect-ratio: 9 / 16; object-fit: cover;
  border-radius: 12px; border: 1px solid var(--border); margin: 4px 0 10px;
}
.broll-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px; margin-top: 4px;
}
.broll-item {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 8px; display: flex;
  flex-direction: column; gap: 6px;
}
.broll-item video {
  width: 100%; aspect-ratio: 9 / 16; object-fit: cover;
  border-radius: 10px; background: #000;
}
.broll-name { font-size: .85rem; font-weight: 600; line-height: 1.25; word-break: break-word; }
.broll-meta { font-size: .72rem; color: var(--muted); display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; }
/* quiet "(B-roll)" / "(Photo)" kind tag on a footage card. Decorative, not a status.
   (No longer emitted by the Settings library — its cards live under labeled
   footage/photos group headings now — kept for any other card that wants it.) */
.broll-tag { font-size: .68rem; font-weight: 700; color: var(--faint); letter-spacing: .01em; }
/* Settings library splits into "Your clinic footage" / "Your clinic photos":
   the heading + per-group empty line span the whole grid row */
.broll-group-hd { grid-column: 1 / -1; margin: 6px 0 0; }
.broll-group-empty { grid-column: 1 / -1; margin: 0; }
.broll-item video { cursor: pointer; }
.broll-more { grid-column: 1 / -1; text-align: center; padding: 10px 0; }
.broll-row { display: flex; justify-content: space-between; gap: 6px; }
.broll-row .btn-text { font-size: .8rem; padding: 4px 0; }
.broll-del { color: var(--danger, #e5484d); }

/* the record/trim sheet — dark camera chrome, like the recorder */
#broll-sheet {
  position: fixed; inset: 0; z-index: 70; background: rgba(0,0,0,.82);
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
#broll-sheet[hidden] { display: none; }
.broll-card {
  width: 100%; max-width: 400px;
  max-height: 94vh; max-height: 92dvh;   /* dvh = the VISIBLE viewport, so iOS Safari's toolbar can't hide the bottom (Save) */
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  background: var(--paper, #f8f4ec); border-radius: 18px;
  padding: 16px; padding-bottom: 0;   /* the sticky Save row owns the bottom padding */
  display: flex; flex-direction: column; gap: 12px;
}
.broll-card h2 { font-size: 1.1rem; margin: 0; }
#broll-cam, #broll-prev, #broll-prev-img {
  width: 100%; max-height: 54vh; max-height: 54dvh;   /* taller so a 9:16 clip reads clearly (Travis: "make it more vertical") */
  background: #000; border-radius: 14px;
  object-fit: contain;
}
/* keep Cancel/Save pinned to the bottom of the scrolling card so they're ALWAYS
   reachable no matter how tall the fields get (scoped to #broll-sheet so the
   .cc-pop reuse of .broll-sheet-row is untouched) */
#broll-sheet .broll-sheet-row {
  position: sticky; bottom: 0; z-index: 1;
  background: var(--paper, #f8f4ec);
  padding: 12px 0 calc(16px + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -8px 14px -10px rgba(0,0,0,.25);
}
/* the photo sheet swaps the video preview for a still and drops the three
   video-only controls. Each of those sets display:flex, which out-specifies the
   UA's `[hidden] { display: none }` — without these they would stay on screen. */
#broll-prev[hidden], #broll-prev-img[hidden],
#broll-trim-rails[hidden], #trim-zoom[hidden], #broll-defish-row[hidden] { display: none; }
/* the RECORD screen gets a bigger viewfinder than the trim preview (more usable) */
#broll-cam { max-height: 62vh; }
#broll-cap, #broll-trim { display: flex; flex-direction: column; gap: 10px; }
#broll-cap[hidden], #broll-trim[hidden] { display: none; }
#btn-broll-shoot.recording { background: var(--rec, #e5484d); }
/* framing coaching on the record screen: short, quiet lines (understated) */
.broll-coach { display: flex; flex-direction: column; gap: 3px; text-align: center; }
.broll-coach p { margin: 0; font-size: .84rem; line-height: 1.35; color: var(--muted); }
/* zoom-preset segmented control in the trim panel (reuses .seg-control/.seg-btn) */
.trim-zoom { display: flex; flex-direction: column; gap: 6px; }
.trim-zoom .seg-control { margin: 0; align-self: flex-start; }
.trim-rails { display: flex; flex-direction: column; gap: 8px; }
.trim-rails label { display: flex; flex-direction: column; gap: 4px; font-size: .85rem; }
.trim-rails input[type=range] { width: 100%; }
/* button row for the b-roll sheet (quiet-action/primary spread reads fine inline
   in a view or a card too). #ns-composer had its own copy of this shape until the
   Jul 17 bottom-actions redesign moved it to .ns-actions (see style.css near the
   dictation mic rules) — a vertical stack reads more clearly than this spread once
   there's more than one primary-looking button in play. */
.broll-sheet-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.broll-sheet-row .btn-primary { flex: 1; }

/* footage-card preview: tap a clip to play it big (dark stage like the recorder) */
#broll-preview {
  position: fixed; inset: 0; z-index: 72; background: rgba(0,0,0,.9);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
#broll-preview[hidden] { display: none; }
.broll-prev-stage { width: 100%; max-width: 440px; display: flex; justify-content: center; }
.broll-prev-stage video, .broll-prev-stage img {
  width: 100%; max-height: 82vh; aspect-ratio: 9 / 16; object-fit: contain;
  border-radius: 14px; background: #000;
}
.broll-prev-close {
  position: absolute; top: 14px; right: 14px; z-index: 1;
  background: rgba(255,255,255,.14); color: #fff; border: 0;
}
.broll-prev-close:active { background: rgba(255,255,255,.28); }

/* the create-icon chooser: a POPOVER hanging off whichever + opened it (home /
   My Videos / calendar), not a bottom sheet (Travis, Jul 16). The backdrop stays
   full-screen so a tap anywhere off the panel dismisses it; positionCreateChoice()
   in app.js writes .cc-pop's left/top from the trigger button's rect. */
#create-choice-sheet {
  position: fixed; inset: 0; z-index: 74; background: rgba(36,31,24,.32);
}
#create-choice-sheet[hidden] { display: none; }
.cc-pop {
  position: fixed; left: 0; top: 0;           /* JS overwrites both, from the trigger */
  width: min(340px, calc(100vw - 24px));
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: 14px; display: flex; flex-direction: column; gap: 10px;
}
/* Only a panel that got CLAMPED (never one showing its caret) can need to scroll,
   and overflow:auto would clip the caret, so the two states are kept exclusive. */
.cc-pop:not(.cc-caret) { max-height: calc(100dvh - 24px); overflow-y: auto; }
/* dvh, not vh: on iOS Safari `vh` is the LAYOUT viewport and stays tall while the
   toolbars cover the screen, so a vh cap does not actually keep the panel on
   screen. anchorPopover() also sets an inline max-height from visualViewport,
   which wins; this stays as the no-JS floor. */
.cc-pop h2 { font-size: 1rem; margin: 0 0 2px; }
/* compact the choice cards for a popover; the onboarding fork keeps the roomy ones */
.cc-pop .ob-choice-card { padding: 12px; gap: 12px; border-radius: var(--radius); }
.cc-pop .occ-icon { width: 40px; height: 40px; border-radius: 12px; font-size: 1.2rem; }
.cc-pop .occ-title { font-size: .96rem; }
.cc-pop .occ-sub { font-size: .78rem; }
.cc-pop .broll-sheet-row { justify-content: flex-end; }
/* ---- My Videos sort pulldown: the same .cc-pop panel + anchorPopover()
   placement, just a menu instead of choice cards. Its dismiss layer is bare
   (no scrim): dimming the whole app to change a sort order would overstate it. */
#videos-sort-pop { position: fixed; inset: 0; z-index: 74; }
#videos-sort-pop[hidden] { display: none; }
.cc-pop.sort-pop { width: min(200px, calc(100vw - 24px)); padding: 6px; gap: 2px; }
.sort-opt {
  appearance: none; border: 0; background: none; cursor: pointer; font-family: inherit;
  text-align: left; width: 100%;
  font-size: .9rem; font-weight: 600; color: var(--text);
  padding: 10px 12px; border-radius: 10px;
  transition: background .15s ease, color .15s ease;
}
.sort-opt:hover { background: var(--card-2); }
.sort-opt:active { background: var(--card-3); }
/* the current sort, in the same accent-tint language as an active .seg-btn */
.sort-opt.active { background: var(--accent-soft); color: var(--accent-press); font-weight: 700; }

/* ---- FIELD PICKER: the same .cc-pop panel + anchorPopover() placement, with a
   search box over a listbox. Its dismiss layer is bare (no scrim), like the sort
   menu: this is a form control opening, not a modal.
   NOTE the classes are fp-*, not pick-* — .pick-list/.pick-panel/.pick-row are
   already the bottom-sheet picker's (see ~line 2020). */
#fp-pop { position: fixed; inset: 0; z-index: 74; }
#fp-pop[hidden] { display: none; }
.cc-pop.fp-pop { padding: 8px; gap: 8px; }   /* JS sets width, from the field it hangs off */
.fp-search {
  width: 100%; background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 10px;
  padding: 11px 12px; font-size: 1rem; font-family: inherit;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.fp-search:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* the LIST scrolls, never the panel — that is what keeps the panel short enough
   to wear an honest caret without an overflow clip eating it. JS sets max-height
   from the visual viewport (sizePickList). */
.fp-list {
  overflow-y: auto; -webkit-overflow-scrolling: touch; min-height: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.fp-opt {
  appearance: none; border: 0; background: none; cursor: pointer; font-family: inherit;
  text-align: left; width: 100%; flex: none;
  font-size: .95rem; font-weight: 500; color: var(--text);
  padding: 11px 12px; border-radius: 10px;
  transition: background .15s ease, color .15s ease;
}
.fp-opt:hover { background: var(--card-2); }
.fp-opt:active { background: var(--card-3); }
/* the keyboard cursor — a ring, so it still reads on the picked row below */
.fp-opt.cursor { background: var(--card-2); box-shadow: inset 0 0 0 2px var(--accent); }
/* the value they already have, in the same accent-tint language as .sort-opt.active */
.fp-opt.active { background: var(--accent-soft); color: var(--accent-press); font-weight: 700; }
.fp-hint { margin: 6px; }

/* the pointer back up at the +. --cc-caret-x is the button's center, panel-relative */
.cc-pop.cc-caret::before {
  content: ''; position: absolute; top: -6px; left: var(--cc-caret-x, 50%);
  width: 11px; height: 11px; margin-left: -6px;
  background: var(--card);
  border-left: 1px solid var(--border); border-top: 1px solid var(--border);
  border-radius: 2px 0 0 0; transform: rotate(45deg);
}

/* #ns-composer: non-blocking near-duplicate warning (dedupe check on
   compose/create) — an attention card, not an error; same visual language
   as .week-card, just tighter */
.ns-dupe-warn {
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  border-radius: var(--radius); padding: 12px 14px; margin-bottom: 16px;
}
.ns-dupe-text { margin: 0; font-size: .88rem; line-height: 1.4; }
.ns-dupe-actions { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.ns-dupe-actions .btn-text { padding: 6px 0; }
.ns-dupe-actions .btn-secondary { padding: 8px 14px; font-size: .85rem; }

/* the onboarding fork cards (clinic vs start-from-scratch) */
#ob-choice { display: flex; flex-direction: column; gap: 12px; margin-top: 8px; }
#ob-choice[hidden] { display: none; }
.ob-choice-card {
  display: flex; align-items: center; gap: 14px; text-align: left; width: 100%;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 18px; cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: border-color .15s ease, box-shadow .15s ease, transform .1s ease;
}
.ob-choice-card:hover { border-color: var(--accent-line); box-shadow: var(--shadow); }
.ob-choice-card:active { transform: scale(.99); }
.occ-icon {
  flex: none; width: 48px; height: 48px; border-radius: 14px;
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  display: flex; align-items: center; justify-content: center; font-size: 1.45rem;
}
.occ-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.occ-title { font-size: 1.06rem; font-weight: 700; color: var(--text); letter-spacing: -.01em; }
.occ-sub { font-size: .85rem; color: var(--muted); line-height: 1.42; }
.occ-arrow { flex: none; color: var(--faint); font-size: 1.2rem; transition: color .15s ease, transform .15s ease; }
.ob-choice-card:hover .occ-arrow { color: var(--accent); transform: translateX(2px); }
.ob-choice-card .big { font-size: 1.08rem; font-weight: 700; color: var(--text); }
#ob-personal-flow[hidden], #ob-clinic-flow[hidden] { display: none; }

/* the "Your account" identity card (Settings) */
.acct-row { display: flex; align-items: center; gap: 12px; margin-bottom: 4px; }
.acct-avatar {
  width: 44px; height: 44px; border-radius: 50%; flex: none;
  background: var(--accent-soft); color: var(--accent-press);
  border: 1px solid var(--accent-line);
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 1.15rem;
}
.acct-id { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.acct-id b { font-size: .92rem; word-break: break-all; line-height: 1.3; }
.acct-chip {
  align-self: flex-start;
  background: var(--accent-soft); color: var(--accent-press);
  border: 1px solid var(--accent-line);
  font-size: .72rem; font-weight: 700; padding: 3px 9px; border-radius: 999px;
}
#acct-viewas { margin-top: 10px; }
#acct-viewas[hidden] { display: none; }
.acct-hint { margin: 6px 0 0; }
.acct-signout { padding-left: 0; }

/* the Bow & Play brand lockup (docs/brand/, 34084ae) */
.brand-h1 { display: flex; align-items: center; margin: 0; flex: 1; }
.app-logo { height: 30px; display: block; }
/* .login-logo/.login-tag are permanently hidden on the sign-in screen — the
   sting's .li-brand (#login-intro) IS the lockup now, so there's no second
   asset to crossfade against. Left in the DOM (unused elsewhere: .app-logo
   in the header is a separate class) in case a future no-JS/no-sting path
   needs a static fallback. */
.login-logo { display: none; }

/* dictation mics (Web Speech): tap = red pulsing "listening" state */
.mic-row { display: flex; gap: 8px; align-items: stretch; }
.mic-row input { flex: 1; min-width: 0; }
/* .mic-grow — a growable answer box inside a mic-row.
   IT OUTLIVED ITS ORIGINAL REASON, and that is worth writing down because the obvious
   cleanup is a regression. It was built to stack a textarea and its .tx-grab bar in a
   column, because addGrabber() injects the bar as the textarea's next SIBLING and it
   would otherwise have laid out as a THIRD flex child of .mic-row, wedged between the
   box and its mic. The bars are gone from this form (Jul 17), so that job is gone.
   The wrapper STAYS because it quietly does two other jobs that are still live:
     1. FLEX SIZING. `.mic-row input { flex: 1 }` matches `input` and nothing else —
        never a textarea. This wrapper is the only thing giving these boxes their width,
        so deleting it collapses every answer box to its default ~20-character size.
     2. It blockifies the textarea (see #ns-composer textarea), killing the inline-block
        baseline gap.
   Measured before removing, not assumed. */
.mic-grow { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* the mic stays a 50px square beside the FIRST line: `align-items: stretch` would
   stretch it into a tall column the moment the answer grows. */
.mic-row:has(.mic-grow) { align-items: flex-start; }
.mic-row:has(.mic-grow) .mic-btn { height: 50px; }
.mic-btn {
  flex: none; width: 50px; border-radius: 12px; cursor: pointer;
  background: var(--card-2); border: 1px solid var(--border);
  font-size: 1.15rem; display: flex; align-items: center; justify-content: center;
}
/* #ns-body-mic used to need its own selector here too: it was a big .btn-secondary
   "Talk instead of typing" button, not a .mic-btn, until the Jul 17 composer redesign
   made it a small mic-row icon like every other composer mic. .mic-btn.listening alone
   covers it now. */
.mic-btn.listening {
  background: var(--rec); color: #fff; border-color: var(--rec);
  animation: micPulse 1.4s ease-in-out infinite;
}
@keyframes micPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(229,72,77,.45); }
  50% { box-shadow: 0 0 0 9px rgba(229,72,77,0); }
}

/* #ns-composer bottom actions (Jul 17 redesign — see the markup comment for the
   "why" this stopped being a .broll-sheet-row spread). ONE accent pill on top
   (the hero action, compose <-> create swap in place via [hidden]), one neutral
   pill under it (the chat escape hatch), Clear + the guided/manual toggle pushed
   down into quiet centered text. A vertical stack reads as one obvious next step;
   a row of same-size buttons was what made "for me" and "with me" look identical. */
.ns-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 4px; }
.ns-actions-quiet { display: flex; align-items: center; justify-content: center; gap: 6px; }
.ns-actions-quiet .btn-text { padding: 10px 8px; }
.ns-actions-dot { color: var(--faint); }

/* the sandbox badge — /app-beta/ builds only */
.beta-chip {
  position: fixed; top: calc(env(safe-area-inset-top, 0px) + 6px); left: 50%;
  transform: translateX(-50%); z-index: 90; pointer-events: none;
  background: rgba(109,74,255,.92); color: #fff;
  font-size: .68rem; font-weight: 800; letter-spacing: .08em;
  padding: 4px 12px; border-radius: 999px;
  box-shadow: 0 2px 10px rgba(36,31,24,.25);
}

.viewas-chip { background: rgba(222,138,85,.95); }

/* ---------- sign-in front door ---------- */
.login-hero {
  /* top-anchored column: .li-brand physically LIVES here now (see
     settleLockup() in app.js) as a normal in-flow sibling of .login-card,
     not a translateY-positioned overlay pinned inside a fixed ancestor —
     so there's nothing left to line up against a variable-height card. */
  display: flex; flex-direction: column;
  align-items: center; justify-content: flex-start; gap: 14px; text-align: center;
  padding: 24px 8px 9vh;
}
.login-title { font-size: 2rem; font-weight: 800; letter-spacing: -.03em; margin: 0; }
.login-tag { display: none; } /* superseded by .li-tag inside the settled .li-brand */
.login-card {
  width: 100%; max-width: 340px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 22px 18px;
  box-shadow: 0 1px 2px rgba(36,31,24,.05), 0 6px 20px -4px rgba(36,31,24,.10);
  display: flex; flex-direction: column; align-items: center; gap: 14px;
}
#gsi-btn-login { min-height: 44px; display: flex; justify-content: center; }

/* ---------- the sign-in sting v3 CASCADE (each word pops center-screen,
   dives down and TUCKS INTO the little journey row, next word pops as the
   previous lands; full row holds a beat, bows out, then mark + EditButler +
   tagline land; ~5.5s, tap anywhere skips; app.js playLoginIntro) ---------- */
#login-intro {
  position: fixed; inset: 0; z-index: 85; background: var(--bg);
  cursor: pointer; overflow: hidden; transition: background .4s ease;
}
#login-intro[hidden] { display: none; }
/* RESOLVE (not "done"): the curtain never disappears — its .li-brand lockup
   IS the settled sign-in lockup now (see .login-logo/.login-tag below), so
   #login-intro stays mounted + visible; only the background goes transparent
   and pointer-events release so the card underneath becomes tappable. No
   opacity-fade here — that used to crossfade a DIFFERENT logo asset in
   underneath as this one faded, which was the seam this is fixing. */
#login-intro.resolve { background: transparent; pointer-events: none; }
/* any leftover cascade chrome (mid-word, the journey row, "tap to skip")
   shouldn't linger once resolved — only .li-brand survives past this point */
#login-intro.resolve .li-words,
#login-intro.resolve .li-row { opacity: 0; transition: opacity .3s ease; }
/* skip is meaningless once already resolved — hide outright, not just fade */
#login-intro.resolve .li-skip { display: none; }
/* the popping words: all in ONE centered grid cell, each visible alone */
.li-words { position: absolute; inset: 0; display: grid; place-items: center; }
.li-word {
  grid-area: 1 / 1; white-space: nowrap; opacity: 0;
  font-weight: 800; letter-spacing: -.02em;
  font-size: clamp(2.6rem, 14vw, 5.2rem); color: var(--text);
}
.li-grow { font-size: clamp(3rem, 17vw, 6.2rem); color: var(--accent); }
/* the journey row the words tuck into */
.li-row {
  position: absolute; left: 0; right: 0; top: 60%;
  display: flex; justify-content: center; align-items: baseline;
}
.li-r {
  font-size: clamp(.62rem, 3vw, .88rem); font-weight: 600;
  letter-spacing: .11em; color: var(--faint); opacity: 0; display: inline-block;
}
.li-r:not(.li-rg)::after {
  content: '\00b7'; margin: 0 6px; color: var(--faint); font-weight: 400;
}
.li-rg { color: var(--accent); font-weight: 800; }
/* pop -> hold a beat -> dive to the row slot (per-word --tx aim, 10vh drop) */
.play .li-w1 { --tx: -122px; animation: cascWord .95s cubic-bezier(.3,.9,.4,1) .2s both; }
.play .li-w2 { --tx: -60px;  animation: cascWord .95s cubic-bezier(.3,.9,.4,1) .82s both; }
.play .li-w3 { --tx: 8px;    animation: cascWord .95s cubic-bezier(.3,.9,.4,1) 1.44s both; }
.play .li-w4 { --tx: 72px;   animation: cascWord .95s cubic-bezier(.3,.9,.4,1) 2.06s both; }
.play .li-grow { --tx: 122px; animation: cascWord .95s cubic-bezier(.3,.9,.4,1) 2.68s both; }
/* the row catches each word just as the next one pops — the rhythm */
.play .li-r1 { animation: cascLand .35s ease .82s both; }
.play .li-r2 { animation: cascLand .35s ease 1.44s both; }
.play .li-r3 { animation: cascLand .35s ease 2.06s both; }
.play .li-r4 { animation: cascLand .35s ease 2.68s both; }
.play .li-rg { animation: cascLand .35s ease 3.3s both; }
/* finished row gets a readable beat, takes a bow, then the brand */
.play .li-row { animation: cascRowOut .5s ease 3.95s forwards; }
/* the landing: mark assembles + EditButler + the tagline, right where the
   sign-in screen shows them — the sting hands off in place. This layout
   (absolute, centered inside the fixed overlay) is for its PRE-resolve
   life only, while the cascade is still assembling it. */
.li-brand {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; text-align: center;
  padding: 24px; pointer-events: none;
}
/* RESOLVE, physically: app.js's settleLockup() moves this element OUT of
   #login-intro (position:fixed) and into .login-hero's normal document
   flow, as a sibling of .login-card — the actual un-fix, not a translateY
   illusion pinned forever inside a fixed ancestor (the old lockRise
   approach: it looked settled but was still position:fixed, so it had
   nothing keeping it lined up with a variable-height card underneath —
   any viewport/content mismatch could re-open a collision). .settled is
   added the exact tick it's reparented. The assembled mark/wordmark/tag
   look was driven by ancestor-scoped .play / #login-intro.resolve
   selectors (timed cascade reveals) — those stop matching once the node
   leaves that ancestor, so .settled re-states the finished look directly
   on the moved subtree (self-scoped, no ancestor dependency). */
.li-brand.settled {
  position: static; inset: auto; padding: 0; pointer-events: auto;
  margin: 0 auto 6vh;
  animation: lockFadeIn .4s ease;
}
@keyframes lockFadeIn { from { opacity: 0; } to { opacity: 1; } }
.li-brand.settled .li-bowL,
.li-brand.settled .li-bowR,
.li-brand.settled .li-play,
.li-brand.settled .li-wordmark,
.li-brand.settled .li-tag {
  animation: none; opacity: 1; transform: none;
}
/* the compact form: opening #user-login-form (the username/password door)
   needs the extra fields to fit without excess scrolling — shrink the
   lockup and drop its tagline. :has() is already used elsewhere in this
   file (.script-card.done, .mic-row:has(.mic-grow)) — same reuse, no new
   scaling system invented. */
.login-hero:has(#user-login-form:not([hidden])) .li-brand.settled {
  transform: scale(.7); margin-bottom: 1.5vh;
}
.login-hero:has(#user-login-form:not([hidden])) .li-tag { display: none; }
.li-mark { width: 132px; color: var(--accent); overflow: visible; }
.li-bowL, .li-bowR, .li-play {
  opacity: 0; transform-box: fill-box; transform-origin: center;
}
.play .li-bowL { animation: liFromL .55s cubic-bezier(.2,1.4,.4,1) 4.2s forwards; }
.play .li-bowR { animation: liFromR .55s cubic-bezier(.2,1.4,.4,1) 4.2s forwards; }
.play .li-play { animation: liPop .5s cubic-bezier(.2,1.7,.4,1) 4.55s forwards; }
.li-wordmark {
  margin: 8px 0 0; font-family: Georgia, 'Times New Roman', serif;
  font-size: 2.1rem; letter-spacing: -.02em; color: var(--text); opacity: 0;
}
.li-wordmark b { color: var(--accent); }
.play .li-wordmark { animation: liRise .5s ease 4.75s forwards; }
.li-tag { margin: 6px 0 0; color: var(--muted); font-size: 1rem; opacity: 0; }
.play .li-tag { animation: liRise .5s ease 5s forwards; }
.li-skip {
  position: absolute; left: 0; right: 0; text-align: center;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 26px);
  margin: 0; font-size: .82rem; color: var(--faint); opacity: 0;
}
.play .li-skip { animation: liFadeIn .6s ease 1.2s forwards; }
@keyframes cascWord {                   /* pop in -> hold -> dive into the row */
  0% { opacity: 0; transform: scale(.3); }
  16% { opacity: 1; transform: scale(1.07); }
  24% { transform: scale(1); }
  55% { opacity: 1; transform: scale(1); }
  88% { opacity: .85; }
  100% { opacity: 0; transform: translate(var(--tx), 10vh) scale(.12); }
}
@keyframes cascLand {                   /* the row catches it with a tiny bounce */
  0% { opacity: 0; transform: translateY(-10px) scale(1.5); }
  55% { opacity: 1; transform: translateY(2px) scale(.98); }
  100% { opacity: 1; transform: none; }
}
@keyframes cascRowOut { to { opacity: 0; transform: translateY(10px); } }
@keyframes liFromL { from { opacity: 0; transform: translateX(-36px); } to { opacity: 1; transform: none; } }
@keyframes liFromR { from { opacity: 0; transform: translateX(36px); } to { opacity: 1; transform: none; } }
@keyframes liPop {
  0% { opacity: 0; transform: scale(.2); }
  65% { opacity: 1; transform: scale(1.28); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes liRise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes liFadeIn { to { opacity: 1; } }

/* after the sting resolves, the card is the only thing left to bring in —
   the lockup itself already settled into place via .li-brand.settled's own
   lockFadeIn (see above). Card reveal overlaps the tail of that (.27s
   delay) so it reads as one cascade, not two separate moves. */
@keyframes signinIn { from { opacity: 0; transform: translateY(26px); } to { opacity: 1; transform: none; } }
.login-hero.hero-in .login-card { animation: signinIn .6s cubic-bezier(.2,1,.3,1) .27s both; }
/* the journey line lives on under the settled lockup's tagline — a slow
   highlight drifts across the five words on repeat.
   RESOLVE: this duplicated the sting's own journey row — #login-intro's
   .li-row already carries this same choreography, and the settled
   .li-brand lockup carries the brand (mark + wordmark + tagline); this
   second static copy is redundant regardless of where .li-brand lives. */
.login-hero .login-chain { display: none; }
.login-chain { margin: -4px 0 8px; letter-spacing: .01em; }
.login-chain span {
  display: inline-block; font-weight: 600;
  animation: liChainWord 7.5s ease-in-out infinite;
}
.login-chain span:nth-of-type(2) { animation-delay: 1.5s; }
.login-chain span:nth-of-type(3) { animation-delay: 3s; }
.login-chain span:nth-of-type(4) { animation-delay: 4.5s; }
.login-chain b {
  display: inline-block; color: var(--accent); font-weight: 800;
  animation: liChainGrow 7.5s ease-in-out infinite 6s;
}
@keyframes liChainWord {
  0%, 12%, 100% { color: var(--muted); }
  4%, 8% { color: var(--accent); }
}
@keyframes liChainGrow {
  0%, 12%, 100% { transform: none; }
  6% { transform: scale(1.18); }
}
@media (prefers-reduced-motion: reduce) {
  .login-chain span, .login-chain b { animation: none; }
  /* the sting itself never plays for reduced-motion phones (playLoginIntro
     bails before adding .play) — it jumps straight to .resolve AND calls
     the same settleLockup() as the full path, so the settled state is
     IDENTICAL layout-wise (same reparented .li-brand.settled, sitting in
     the same .login-hero flow), just landed instantly with no keyframes
     running. (The .li-brand.settled child-freeze rules above already
     apply unconditionally, not gated behind .play/.resolve, so they don't
     need repeating here.) */
  .li-brand.settled { animation: none; opacity: 1; }
  #login-intro.resolve .li-words,
  #login-intro.resolve .li-row { display: none; }
  .login-hero.hero-in .login-card { animation: none; opacity: 1; transform: none; }
}

/* ---------- older versions of a video (history pulldown on the ONE card) ---------- */
.vid-history {
  background: var(--bg-subtle, #f6f1e7); border: 1px solid var(--border);
  border-radius: 12px; padding: 4px 12px; margin-top: 4px;
}
.vid-history summary {
  cursor: pointer; font-size: .88rem; font-weight: 600; color: var(--muted);
  padding: 8px 0; list-style-position: inside;
}
.vh-row {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 0; border-top: 1px solid var(--border);
}
.vh-when { flex: 1; font-size: .85rem; color: var(--text, #241f18); }
.vh-row { cursor: pointer; }
.vh-row .btn-text { padding: 4px 6px; font-size: .85rem; }
.vh-del { color: var(--danger, #e5484d); }
.vh-note { padding: 6px 0 8px; margin: 0; }
/* older version pops up in its OWN player — main card player never swapped */
#vhver-pop {
  position: fixed; inset: 0; z-index: 70; background: rgba(0,0,0,.78);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
#vhver-pop[hidden] { display: none; }
.vh-box {
  display: flex; flex-direction: column; gap: 10px; align-items: center;
  width: 100%; max-width: 380px;
}
.vh-box video {
  width: 100%; max-height: 64vh; background: #000;
  border-radius: 14px; border: 1px solid var(--border);
}
.vh-box .btn-big { width: 100%; }
.vh-pop-label { color: rgba(255,255,255,.85); margin: 0; }

/* ── Video-style tier picker (Settings › Video defaults) ────────────────
   Three cards make Clean / Standard / Max obvious: name, plain description,
   what-you-get bullets, and a small schematic 9:16 illustration of what each
   tier ADDS. The mini-frames are CSS-drawn examples, not real render stills. */
.style-tiers { display: flex; flex-direction: column; gap: 10px; }
.tier-card {
  display: flex; gap: 14px; align-items: flex-start; text-align: left;
  width: 100%; padding: 12px; margin: 0; cursor: pointer;
  background: var(--card); color: var(--text);
  border: 1.5px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  transition: border-color .15s, box-shadow .15s, background .15s;
}
.tier-card:hover { background: var(--card-2); }
.tier-card.selected {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 0 3px var(--accent-soft), var(--shadow-sm);
}
.tier-info { flex: 1; min-width: 0; }
.tier-name {
  font-size: 1.02rem; font-weight: 700; color: var(--text);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.tier-rec {
  font-size: .66rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: #fff; background: var(--accent);
  padding: 3px 8px; border-radius: 999px;
}
.tier-desc { margin: 3px 0 7px; font-size: .88rem; color: var(--muted); }
.tier-gets { margin: 0; padding: 0; list-style: none; }
.tier-gets li {
  position: relative; padding-left: 16px; margin: 2px 0;
  font-size: .82rem; color: var(--text); line-height: 1.35;
}
.tier-gets li::before {
  content: ""; position: absolute; left: 2px; top: .5em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
}

/* the schematic 9:16 example frame (dark stage, it previews VIDEO graphics,
   same rationale as the /edit style-picker chips) */
.tier-thumb { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.tf {
  position: relative; width: 62px; height: 110px; overflow: hidden;
  border-radius: 10px; border: 1px solid var(--border-2);
  background: linear-gradient(160deg, #2c2620 0%, #1a1611 100%);
}
.tf-tag {
  font-size: .6rem; letter-spacing: .05em; text-transform: uppercase;
  color: var(--faint);
}
/* speaker silhouette */
.tf-fig { position: absolute; left: 0; right: 0; bottom: 0; top: 24%; }
.tf-fig::before {   /* head */
  content: ""; position: absolute; left: 50%; top: 8px; transform: translateX(-50%);
  width: 20px; height: 20px; border-radius: 50%; background: rgba(255,255,255,.22);
}
.tf-fig::after {    /* shoulders */
  content: ""; position: absolute; left: 50%; bottom: 0; transform: translateX(-50%);
  width: 42px; height: 34px; border-radius: 22px 22px 0 0; background: rgba(255,255,255,.16);
}
/* caption bar (all tiers) */
.tf-cap {
  position: absolute; left: 7px; right: 7px; bottom: 9px;
  display: flex; gap: 3px; justify-content: center;
}
.tf-cap i {
  height: 6px; border-radius: 2px; background: rgba(255,255,255,.9);
}
.tf-cap i:nth-child(1) { width: 12px; }
.tf-cap i:nth-child(2) { width: 18px; background: var(--accent); }
.tf-cap i:nth-child(3) { width: 10px; }
/* Standard: zoom brackets + a small text callout */
.tf-zoom { position: absolute; inset: 8px; pointer-events: none; }
.tf-zoom::before, .tf-zoom::after {
  content: ""; position: absolute; width: 11px; height: 11px;
  border: 1.5px solid rgba(255,255,255,.6);
}
.tf-zoom::before { top: 0; left: 0; border-right: none; border-bottom: none; }
.tf-zoom::after { bottom: 0; right: 0; border-left: none; border-top: none; }
.tf-callout {
  position: absolute; left: 8px; top: 30%; width: 30px; padding: 4px;
  border-radius: 5px; background: rgba(255,255,255,.92);
  display: flex; flex-direction: column; gap: 3px;
}
.tf-callout i { height: 3px; border-radius: 2px; background: #2c2620; }
.tf-callout i:nth-child(1) { width: 70%; }
.tf-callout i:nth-child(2) { width: 100%; }
/* Max: hook chip + animated-looking stat card */
.tf-hook {
  position: absolute; left: 7px; top: 8px; padding: 4px 5px;
  border-radius: 5px; background: var(--accent);
  display: flex; flex-direction: column; gap: 2px;
}
.tf-hook i { height: 3px; border-radius: 2px; background: rgba(255,255,255,.95); }
.tf-hook i:nth-child(1) { width: 22px; }
.tf-hook i:nth-child(2) { width: 14px; }
.tf-stat {
  position: absolute; right: 7px; top: 34%; width: 30px; padding: 5px 4px;
  border-radius: 6px; background: #fff;
  box-shadow: 0 4px 10px -3px rgba(192,90,43,.6);
  display: flex; flex-direction: column; align-items: center; gap: 3px;
}
.tf-stat b { display: block; width: 16px; height: 10px; border-radius: 2px; background: var(--accent); }
.tf-stat i { height: 3px; border-radius: 2px; background: rgba(36,31,24,.4); }
.tf-stat i:nth-child(2) { width: 90%; }
.tf-stat i:nth-child(3) { width: 60%; }
/* Max only: a playful sticker badge (signals the extra graphic layer) */
.tf-sticker {
  position: absolute; left: 8px; top: 34%;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 2px rgba(255,255,255,.85);
}

/* Settings footer: the legal footnote. These are the ONLY <a> links in the app
   shell (every other affordance is a <button>), so style.css has never had a
   global anchor rule. Without this reset a bare link renders browser-default
   blue and underlined, which is the exact electric-blue regression the style
   guide bans. Colour comes from .btn-text (accent = links, per the token
   table); this only kills the underline and drops it to footnote size.
   inline-block (not inline) so .btn-text's own 12px padding still builds a
   real tap target, which vertical padding on an inline box would not. */
.set-legal { margin: 2px 0 0; }
.set-legal .btn-text { display: inline-block; font-size: .85rem; text-decoration: none; }

/* ============================================================
   PUBLIC PAGES: "/", "/privacy", "/terms"  (public/*.html)
   ------------------------------------------------------------
   The ONLY pages served outside basic auth: Google's OAuth 2.0
   Policies require a publicly reachable homepage that describes
   the app and links to a privacy policy and terms of service.
   These are PROSE pages, not app screens: no .view, no .tabbar,
   so they bring their own measure and vertical rhythm. Every
   value is a token, so a palette shift carries them along too.
   Legal copy is pre-rendered from docs/legal/*.md; that markdown
   stays the source of truth. Edit it there, then re-render here.
   ============================================================ */
.pub { max-width: 65ch; margin: 0 auto; padding: 28px 20px 72px; }

/* wordmark rail: a quiet way back to the homepage from the legal pages */
.pub-brand {
  display: inline-block; margin-bottom: 28px;
  font-weight: 800; font-size: 1.05rem; letter-spacing: -.02em;
  color: var(--text); text-decoration: none;
}

.pub h1 { font-size: 2rem; font-weight: 800; letter-spacing: -.03em; line-height: 1.15; margin: 0 0 12px; }
.pub h2 { font-size: 1.3rem; font-weight: 700; letter-spacing: -.02em; line-height: 1.3; margin: 40px 0 12px; }
.pub h3 { font-size: 1.05rem; font-weight: 700; letter-spacing: -.01em; margin: 28px 0 8px; }

/* the one big promise under the title */
.pub-lede { font-size: 1.15rem; line-height: 1.5; color: var(--muted); margin: 0 0 24px; }

/* generous measure: these pages are read, not tapped */
.pub p, .pub li { line-height: 1.7; margin: 0 0 14px; }
.pub ul { padding-left: 22px; margin: 0 0 14px; }
.pub li { margin-bottom: 8px; }
.pub strong, .pub b { font-weight: 700; }
.pub hr { border: none; border-top: 1px solid var(--border); margin: 40px 0; }

/* Inline prose links. NOTE the two overrides right below: `.pub a` is (0,1,1)
   and out-specifies a bare component class like `.btn-primary`/`.pub-brand`
   (0,1,0), so without them this rule repaints those components terracotta.
   That is not hypothetical: it shipped the accent pill with accent-on-accent
   label text, i.e. an invisible button. Keep the a.<class> form (0,2,1). */
.pub a { color: var(--accent); font-weight: 600; }
.pub a.btn-primary { color: #fff; }        /* the kit's own on-accent label color */
.pub a.pub-brand { color: var(--text); }
.pub a.pub-brand:hover { color: var(--accent); }

.pub-updated { color: var(--muted); font-size: .9rem; margin: 0 0 8px; }

/* Inline code (the privacy policy quotes the Google scope verbatim). That URL
   is ONE unbreakable token: without overflow-wrap it sets a min-content floor
   wider than a phone and pushes the WHOLE page sideways (measured: 429px of
   scrollWidth in a 390px viewport). Let it break instead. */
.pub code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .88em; overflow-wrap: anywhere;
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: 6px; padding: 1px 5px;
}

/* hero action. .btn-primary is width:100% by default (the app's global floor);
   a prose column wants it to hug its label, so scope the override here. */
.pub-cta { margin: 24px 0 8px; }
.pub .btn-primary { width: auto; display: inline-flex; text-decoration: none; }

/* the "who gets what" provider table (privacy). Narrow phones scroll it
   sideways in its own box rather than squeezing the whole page. */
.pub-table { overflow-x: auto; margin: 0 0 18px; -webkit-overflow-scrolling: touch; }
.pub-table table { border-collapse: collapse; width: 100%; min-width: 30rem; font-size: .95rem; }
.pub-table th, .pub-table td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
.pub-table th { font-weight: 700; color: var(--text); border-bottom: 1px solid var(--border-2); }
.pub-table td { line-height: 1.55; color: var(--text); }

/* postal address / contact block */
.pub-addr { line-height: 1.7; margin: 0 0 14px; }

/* footer link row: carries /privacy + /terms on every public page */
.pub-foot {
  margin-top: 48px; padding-top: 20px; border-top: 1px solid var(--border);
  display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center;
  font-size: .95rem; color: var(--faint);
}
.pub-foot a { color: var(--accent); font-weight: 600; }
.pub-foot .sep { color: var(--faint); }

@media (max-width: 480px) {
  .pub { padding: 20px 16px 56px; }
  .pub h1 { font-size: 1.6rem; }
  .pub-lede { font-size: 1.05rem; }
}
/* ---------- "Write it with me" — the idea chat (#view-ideachat) ----------
   Composed from the kit: bubbles are the only genuinely new component (a chat has no
   existing analogue in the app), the draft is a plain .card, and the composer is
   .mic-row + .btn-primary with only its inline padding overridden. Every colour is a
   token — no new hex. */

/* The one screen that owns the whole viewport: showView hides the tabbar for it, so the
   header and composer pin to the edges and the LOG is the only thing that scrolls.
   #id.active beats .view.active, so this cleanly replaces the shared block layout.
   dvh, not vh: on Android the retracting URL bar makes 100vh TALLER than the visible
   page, which would push the composer under the fold — the exact class of bug the
   390x664 check exists to catch. */
#view-ideachat.active {
  display: flex; flex-direction: column;
  height: 100dvh; padding-bottom: 12px;
}
#view-ideachat .app-header { flex: 0 0 auto; }

.chat-log {
  flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 4px 2px 8px;
  /* the log scrolls, so its own scrollbar must not sit under the composer's border */
  scrollbar-width: thin;
}

.chat-msg {
  max-width: 82%; width: fit-content;
  padding: 10px 14px; margin-bottom: 10px;
  border-radius: 16px; line-height: 1.45;
  white-space: pre-wrap; overflow-wrap: anywhere;
  animation: viewIn .2s ease;
}
/* them = accent (they are the one talking), the writer = paper card. Same asymmetric
   tail every messaging app uses, so nobody has to be taught which side is which. */
.chat-you {
  margin-left: auto; background: var(--accent); color: #fff;
  border-bottom-right-radius: 6px;
}
.chat-writer {
  margin-right: auto; background: var(--card); color: var(--text);
  border: 1px solid var(--border); border-bottom-left-radius: 6px;
}
.chat-thinking { color: var(--muted); font-style: italic; }
.chat-err { border-color: var(--rec); color: var(--rec); }

/* the draft: a real .card in the flow, so a revision reads as a NEW draft above the
   old one rather than a silent mutation of something already on screen */
.chat-draft { margin: 2px 0 14px; box-shadow: var(--shadow-sm); }
.chat-draft .cd-hook { margin: 0 0 8px; font-weight: 700; font-size: 1.05rem; }
.chat-draft .cd-body { margin: 0 0 8px; white-space: pre-wrap; line-height: 1.5; }
.chat-draft .cd-meta { margin: 0 0 12px; }

.chat-compose {
  flex: 0 0 auto; display: flex; gap: 8px; align-items: stretch;
  padding-top: 10px; border-top: 1px solid var(--border);
}
.chat-compose .mic-row { flex: 1; min-width: 0; }
/* inline context: the kit's full-width pill would eat the row (guide: "compact/inline
   contexts override padding on their own more-specific selector") */
.chat-compose .btn-primary { width: auto; flex: 0 0 auto; padding: 14px 18px; }

/* the idea's onward doors on a script's page: Save is the one obvious action (full-width
   pill, the global .btn-primary floor), write-it-with-me / give-another-version sit
   underneath as quiet inline text links so they read as refinements, not competing
   choices (Travis: don't overwhelm -- Save is the point of the screen). */
.detail-idea-row { display: flex; flex-direction: column; gap: 10px; margin: 16px 0 10px; }
.detail-idea-quiet { display: flex; flex-wrap: wrap; gap: 8px 18px; justify-content: center; }

/* ---------- question cards: the "personal touch" studio door ----------
   Travis, Jul 17. Almost nothing new lives here on purpose: the stepper borrows the
   voice screen's classes wholesale (.voice-q / .voice-progress / .voice-controls /
   .voice-nav / .voice-mic) because it is the same job with the same shape, and the
   answer box shares #voice-answer's rule outright. What is left is the card GRID and
   the draft payoff. Every colour is a token. */
.studio-door { margin-bottom: 14px; }

.cards-grid { display: flex; flex-direction: column; gap: 10px; margin-bottom: 8px; }
/* A question card IS a door, so it is the door component (.upload-own) with a solid
   border rather than a new look: dashed reads "add something here", and these are a
   finite curated shelf, not a blank. Two lines: the take, then the tease. */
.q-card {
  width: 100%; background: var(--card); border: 1px solid var(--border-2);
  border-radius: var(--radius); padding: 14px 16px;
  color: var(--text); cursor: pointer; text-align: left;
  display: flex; flex-direction: column; gap: 3px;
  transition: background .15s ease, border-color .15s ease, transform .1s ease;
}
.q-card:hover { background: var(--card-2); border-color: var(--accent-line); }
.q-card:active { background: var(--card-3); transform: scale(.995); }
.q-card .q-title { font-size: 1rem; font-weight: 600; line-height: 1.35; }
.q-card .q-blurb { font-size: .85rem; color: var(--muted); line-height: 1.4; }

.cardq-which { margin: 0 0 6px; }
/* an optional "here's how to answer it" line under the question, one card-question at a
   time; a card with no hints for a question just leaves this hidden (renderCardQ). */
.cardq-hint { margin: -8px 0 14px; line-height: 1.4; }
/* reserves its own line so the pane never jumps when a status appears mid-write */
.cardq-status { min-height: 1.2em; margin: 12px 0 0; color: var(--muted); }
.cardq-status.err { color: var(--rec); }

/* the payoff: what two minutes of talking bought */
.cardq-draft { padding: 18px; margin-bottom: 14px; }
.cardq-hook {
  font-size: 1.2rem; font-weight: 700; letter-spacing: -.02em;
  line-height: 1.3; margin: 0 0 10px;
}
.cardq-body { white-space: pre-wrap; line-height: 1.6; margin: 0; }

/* ---------- the dump door's picker (studio door 3) ----------
   Almost nothing new lives here on purpose, the same way the question cards' pane went
   in: an option IS the draft payoff (.card.cardq-draft, with .cardq-hook / .cardq-body
   doing exactly their own jobs) with an angle label on top and its own Use button. The
   box, the mic row and the nav all come from the voice screen's kit outright. Every
   colour is a token. */
.three-opt { margin-bottom: 14px; }
/* the angle is the ONLY reason a picker beats one script: it says how this one differs
   before she reads 110 words to find out. It is the eyebrow, because that is what an
   eyebrow is for, but lower-cased: the model writes it in her words ("the myth", "what
   I'd do") and SHOUTING those in caps makes a plain phrase read like a section header. */
.three-angle {
  font-size: .72rem; font-weight: 700; letter-spacing: .08em;
  color: #a34a20; margin: 0 0 8px;
}
/* full-width and last, so the tap target is under the thing it commits to, and three of
   them read as three equal choices rather than a list with one call to action. */
.three-use { width: 100%; margin-top: 14px; }

/* ==== THE CREATOR CONTENT LANE (personal accounts) ====
   Composed from the kit: the ideas card is a stock .card, an idea row is the
   .script-card tap-row idea re-shaped (framework label as the eyebrow line, title,
   quiet angle line), and the composer's framework picker reuses the selectable
   .chip/.niche-chip + .selected kit the confirm screen and b-roll rows use. Every
   colour is a token or the documented hand-picked list (#a34a20 on tinted chips). */
#creator-ideas { margin-top: 14px; }
#creator-ideas .btn-secondary { margin-top: 10px; }
#creator-ideas-list { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; }
.creator-idea {
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  width: 100%; text-align: left; cursor: pointer; font-family: inherit;
  background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: var(--radius);
  padding: 12px 14px;
  transition: background .15s ease, border-color .15s ease;
}
.creator-idea:active { background: var(--card-2); }
/* the framework label plays the .three-angle role for the same reason: it says how
   this idea is built before she reads it. Same quiet lower-case treatment. */
.ci-fw {
  font-size: .68rem; font-weight: 700; letter-spacing: .08em; color: #a34a20;
}
.ci-title { font-size: .98rem; font-weight: 600; letter-spacing: -.01em; }
.ci-angle { font-size: .82rem; color: var(--muted); line-height: 1.4; }
#creator-about-field { margin-top: 12px; }
/* dump door: same .upload-own kit card, it just needs breathing room on home */
#btn-creator-dump { margin-top: 14px; }
/* the composer's framework picker row */
#ns-framework-row { margin: 2px 0 12px; }
#ns-framework-row > .muted { display: block; margin: 0 0 7px; }
#ns-framework-row .chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
.ns-fw-chip { font-size: .8rem; padding: 7px 12px; }

/* ==== CONSENT GATE (#12): compliance acknowledgment + signature ==== */
/* Same overlay pattern as #help-pop, one z above it: a consent decision must
   never render UNDER a help popup that happens to be open. */
#consent-pop {
  position: fixed; inset: 0; z-index: 80; background: rgba(36,31,24,.55);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
#consent-pop[hidden] { display: none; }
.consent-ack {
  display: flex; gap: 10px; align-items: flex-start;
  font-size: .95rem; line-height: 1.5;
}
.consent-ack input { flex: none; margin-top: 3px; width: 18px; height: 18px; accent-color: var(--accent); }
.consent-sig { display: flex; flex-direction: column; gap: 8px; }
.consent-sig-head {
  display: flex; justify-content: space-between; align-items: center;
  font-size: .9rem; color: var(--muted);
}
.consent-sig-head .btn-text { color: var(--muted); font-size: .85rem; padding: 2px 4px; }
#consent-canvas {
  display: block; width: 100%; height: 150px;
  background: var(--card-2); border: 1px dashed var(--border-2);
  border-radius: 12px; touch-action: none;   /* the finger draws, the page must not scroll */
  cursor: crosshair;
}
.consent-typed-wrap { margin-bottom: 0; }
/* reuse the app's input look without depending on a type selector */
.consent-typed-wrap input {
  width: 100%; background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 12px;
  padding: 14px; font-size: 1rem;
}
.consent-sig .btn-text { color: var(--muted); font-size: .88rem; padding: 4px; align-self: flex-start; }
.consent-err { color: var(--rec); margin: 0; }

/* ==== HOW WE HANDLE YOUR DATA + DELETE (#13) ==== */
.data-facts { list-style: none; margin: 0 0 12px; padding: 0; }
.data-facts li {
  font-size: .95rem; line-height: 1.6; color: var(--text);
  padding: 4px 0; border-bottom: 1px solid var(--border);
}
.data-facts li:last-child { border-bottom: none; }
.data-facts b { font-weight: 700; overflow-wrap: anywhere; }
#data-region, #data-compliance, #data-rolloff { line-height: 1.55; }
/* the door out: visually quarantined from every other card in Settings */
.danger-zone {
  margin-top: 18px; padding: 16px;
  border: 1px solid rgba(229, 72, 77, .35); border-radius: 12px;
  background: rgba(229, 72, 77, .05);
}
.danger-title { margin: 0 0 8px; font-weight: 700; color: var(--rec); }
.del-token {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .9em; background: var(--card-2); padding: 2px 6px;
  border-radius: 6px; border: 1px solid var(--border-2);
  overflow-wrap: anywhere;
}
.danger-zone input {
  width: 100%; margin: 8px 0 10px;
  background: var(--card); color: var(--text);
  border: 1px solid var(--border-2); border-radius: 12px;
  padding: 12px 14px; font-size: 1rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.btn-danger {
  display: block; width: 100%; padding: 13px 16px;
  border: none; border-radius: 12px; cursor: pointer;
  font-family: inherit; font-size: 1rem; font-weight: 700;
  background: var(--rec); color: #fff;
  transition: opacity .15s ease, transform .1s ease;
}
.btn-danger:disabled { opacity: .4; cursor: not-allowed; }
.btn-danger:not(:disabled):active { transform: scale(.985); }

/* ---------- image posts (calendar entry A + carousel offer B) ----------
   Sheets ride the shared .pick-sheet/.pick-panel shell; everything here is
   composed from the :root tokens (style guide). */
.ip-body {
  overflow-y: auto; display: flex; flex-direction: column; gap: 12px;
  padding: 2px 2px 4px;
}
.ip-body .field > span { font-size: .85rem; color: var(--muted); }
.ip-body .name-input { margin: 6px 0 0; }
.ip-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
}
.ip-thumb {
  position: relative; aspect-ratio: 9 / 16; border-radius: 10px; padding: 0;
  background: #000 center/cover no-repeat; cursor: pointer;
  border: 2px solid var(--border);
  transition: border-color .12s ease, transform .1s ease;
}
.ip-thumb.sel { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.ip-thumb:active { transform: scale(.97); }
.ip-ord {
  position: absolute; top: 6px; left: 6px; width: 22px; height: 22px;
  border-radius: 999px; background: var(--accent); color: #fff;
  font-size: .78rem; font-weight: 700; line-height: 22px; text-align: center;
  box-shadow: 0 2px 6px rgba(0,0,0,.3);
}
.ip-strip { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 2px; }
.ip-strip .ip-thumb { flex: 0 0 64px; cursor: default; }
.ip-wait { text-align: center; padding: 30px 0 26px; }
.ip-wait .spin { margin: 0 auto 12px; }
.ip-wait p { margin: 0; }
.ip-refrow { display: flex; align-items: center; gap: 10px; }
.ip-refthumb {
  width: 46px; height: 60px; border-radius: 8px; flex: none;
  background: #000 center/cover no-repeat; border: 1px solid var(--border);
}
.ip-cap {
  width: 100%; margin: 6px 0 0; padding: 12px 14px; border-radius: 12px;
  border: 1px solid var(--border-2); background: var(--bg-2); color: var(--text);
  font-size: .95rem; font-family: inherit; resize: vertical; min-height: 110px;
}
.ip-cap:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.ip-accts { display: flex; flex-direction: column; gap: 8px; }
.ip-accts > p { margin: 0; }
.ip-acct {
  display: flex; align-items: center; gap: 10px; font-size: .95rem;
  background: var(--card-2); border: 1px solid var(--border);
  border-radius: 12px; padding: 10px 12px; cursor: pointer;
}
.ip-acct input { width: 18px; height: 18px; accent-color: var(--accent); flex: none; }
.ip-row2 { display: flex; justify-content: center; }
.ip-offer { margin-top: 12px; }
.ip-offer h3 { margin: 0 0 4px; }
.ip-offer-row { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.ip-offer-row .btn-secondary { flex: none; width: auto; padding: 10px 18px; }

/* ---------- recorder: real-time clip strip (Edits-style) ----------
   A full-width band along the BOTTOM while paused, one thumb per SECTION (the
   pause dock sits just above it). Never over a rolling take or the countdown.
   Tap a thumb -> watch that clip -> "Redo this clip". Dark camera chrome on
   purpose (style guide: recorder keeps its own skin). */
/* a small prompt above the clip thumbs so people know they're tappable (Travis) */
.clip-strip-hint {
  position: absolute; z-index: 7; left: 0; right: 0; margin: 0;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 100px);   /* clear of the thumb cards (Travis: cards were covering it) */
  text-align: center; font-size: .78rem; font-weight: 600;
  color: rgba(255,255,255,.78); text-shadow: 0 1px 3px rgba(0,0,0,.65);
  pointer-events: none;
}
.clip-strip-hint[hidden] { display: none; }
#clip-strip {
  position: absolute; z-index: 7; left: 0; right: 0;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);
  display: flex; gap: 8px; justify-content: center; overflow-x: auto;
  padding: 4px 12px;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
#clip-strip::-webkit-scrollbar { display: none; }
#clip-strip[hidden] { display: none; }
/* The strip normally owns the bottom of the frame, because the only state that
   showed it was the PAUSED one and `.recorder-view.paused .rec-controls` hides
   the adjust row + record button. "Record another clip" (review -> back into
   the open session) returns to the READY state with those controls up, and the
   strip's z-index 7 beats their z-index 3 — so the thumbs landed on top of the
   record button. Lift the band clear of the controls whenever they're showing.
   CSS-only on purpose: it self-corrects for every state, with no JS to keep in
   sync. */
.recorder-view:not(.paused) #clip-strip {
  bottom: calc(env(safe-area-inset-bottom, 0px) + 180px);
}
.recorder-view:not(.paused) .clip-strip-hint {
  bottom: calc(env(safe-area-inset-bottom, 0px) + 262px);
}
.clip-thumb {
  position: relative; flex: none; width: 46px; height: 72px;
  border-radius: 10px; overflow: hidden; padding: 0; cursor: pointer;
  border: 2px solid rgba(255,255,255,.75); background: #000;
  box-shadow: 0 4px 14px rgba(0,0,0,.4);
}
.clip-thumb video {
  width: 100%; height: 100%; object-fit: cover; pointer-events: none;
}
.clip-thumb-n {
  position: absolute; top: 2px; left: 0; right: 0; text-align: center;
  color: #fff; font-size: .7rem; font-weight: 800;
  text-shadow: 0 1px 3px rgba(0,0,0,.9); pointer-events: none;
}
.clip-thumb-dur {
  position: absolute; bottom: 2px; left: 0; right: 0; text-align: center;
  color: rgba(255,255,255,.9); font-size: .62rem; font-weight: 700;
  text-shadow: 0 1px 3px rgba(0,0,0,.9); pointer-events: none;
}
/* inline re-watch of one clip (with a Redo action): overlay player, camera lives underneath */
.clip-watch {
  position: fixed; inset: 0; z-index: 80; background: rgba(0,0,0,.85);
  display: flex; align-items: center; justify-content: center; padding: 18px;
}
.clip-watch-box {
  width: 100%; max-width: 400px; display: flex; flex-direction: column; gap: 12px;
  position: relative;
}
/* a proper corner close X on the review player (Travis: the close could be better) */
.clip-watch-x {
  position: absolute; top: -6px; right: -2px; z-index: 2;
  width: 40px; height: 40px; border-radius: 999px; border: none; cursor: pointer;
  background: rgba(0,0,0,.6); color: #fff; font-size: 1.3rem; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.clip-watch-box video {
  width: 100%; max-height: 74vh; border-radius: 14px; background: #000;
  object-fit: contain;
}
.clip-watch-bar { display: flex; align-items: center; gap: 10px; justify-content: space-between; }
.clip-watch-lbl { color: #fff; font-weight: 700; font-size: .95rem; text-align: center; margin: 2px 0; }
.clip-watch-box .clip-watch-redo, .clip-watch-box .clip-watch-close { width: 100%; }
.clip-watch-bar .btn-secondary { padding: 9px 14px; font-size: .9rem; white-space: nowrap; }
.clip-watch-bar .btn-text { color: #fff; }

/* ============================================================
   LIVE BACKDROP RECORDER — separate lane (Travis, Aug 18, via Fable-directed
   scope change: "build this out as a separate button... a whole separate way
   to record... Don't touch anything that's currently there.").
   NEW ids only (bgr-*), so nothing here shares a selector with the existing
   recorder. Where a rule below targets an EXISTING class name (.ob-choice-card,
   .broll-card, .script-item, .rec-topbar, .rec-close, .rec-indicator, .rec-dot,
   .record-btn, .cam-error, .review-actions, .field, .broll-coach, .upload-status)
   that class is pure presentation with no JS wired to its selector — reusing it
   on new markup is style-kit composition (docs/APP_STYLE_GUIDE.md), not a touch
   to the old recorder's behaviour. This block ADDS new rules only; nothing above
   this comment was edited for this feature.
   The recorder + review screens here deliberately do NOT use .view/.active
   (that machinery is showView()'s, shared with the whole app) — they are
   fixed-position overlays toggled by the plain hidden attribute, exactly like
   #broll-sheet/#create-choice-sheet already are, so opening one never touches
   the app's view router or its tabbar-visibility list. Dark camera chrome on
   the recorder step (style guide hard rule 1); warm paper on the review step,
   like every other screen. Raw rgba(0,0,0,x)/rgba(255,255,255,x) chrome colors
   below match the EXISTING recorder section's own convention just above (its
   dark surface is intentionally outside the light-shell token palette) — not a
   new "hardcoded hex" regression. ============================================================ */
#view-bgr-recorder, #view-bgr-framing {
  position: fixed; inset: 0; z-index: 55; background: #000; overflow: hidden;
}
#view-bgr-recorder[hidden], #view-bgr-framing[hidden] { display: none; }
/* #bgr-stage (video/canvas/mirror-badge/fallback-note/cam-error) is a plain
   grouping <div> with no layout of its own -- it is reparented WHOLE between
   #view-bgr-framing and #view-bgr-recorder by bgrMountStage() in app.js, and
   its children are `position: absolute`, so they resolve against whichever of
   those two `position: fixed` sections currently contains it, not against the
   div itself. See the FLOW comment above #view-bgr-framing in index.html. */
#bgr-video, #bgr-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; background: #000;
}
/* the framing step's whole reason to exist is pinch/drag on this canvas --
   without touch-action:none a mobile browser tries to pan/zoom the PAGE
   instead of (or as well as) letting the pointer-event gesture code in app.js
   (bgrFramingPointerDown/Move) see clean, uninterrupted deltas. Harmless on
   the recorder screen too (no gestures are wired there), so this is one rule
   for the shared node rather than a framing-only override. */
#bgr-canvas { touch-action: none; }
/* the canvas is the monitor (backdrop + live cutout, or a plain mirrored frame
   while the segmenter warms up / on a device with no segmenter); the <video>
   underneath is the real capture source it draws FROM — never shown itself
   except in the level-3 performance fallback (bgrEnterFallback in app.js),
   where the canvas hides and this plays directly. */
#bgr-video { z-index: 1; }
#bgr-canvas { z-index: 2; }
#bgr-video.bgr-fallback-visible { z-index: 3; }
/* mirror choice for THIS lane — RECORD THE MIRROR (Aug 19; the full decision
   history, including the Aug 18 "mirror the backdrop too" pass this replaced,
   is in app.js bgrComposite). Only the SPEAKER is mirrored: the canvas draws
   the backdrop un-transformed and wraps just the cutout in scale(-1,1), and
   this rule mirrors the bare <video> for the level-3 performance fallback and
   the review playback, the same plain-CSS way #preview always has. The server
   flips the recorded raw once at ingest so the finished file matches.
   #bgr-playback is the local pre-upload blob, mirrored here so the review
   screen agrees with every other screen in the lane (openBgrReview). */
#bgr-video.bgr-mirror,
#bgr-playback.bgr-mirror { transform: scaleX(-1); }
/* RETIRED (Aug 19): #bgr-mirror-badge is never shown any more — nothing reads
   backwards for it to excuse. Rules kept so the surviving DOM node stays inert
   and styled if anything ever un-hides it. */
.bgr-mirror-badge {
  position: absolute; z-index: 4; left: 50%; top: calc(env(safe-area-inset-top, 0px) + 62px);
  transform: translateX(-50%);
  background: rgba(0,0,0,.55); color: #fff; font-size: .74rem; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; padding: 5px 12px; border-radius: 999px;
  border: 1px solid rgba(255,255,255,.22); pointer-events: none;
}
.bgr-mirror-badge[hidden] { display: none; }
.bgr-fallback-note {
  position: absolute; z-index: 4; left: 16px; right: 16px; top: calc(env(safe-area-inset-top, 0px) + 62px);
  margin: 0; text-align: center; color: #fff; font-size: .82rem; line-height: 1.35;
  background: rgba(0,0,0,.55); padding: 8px 14px; border-radius: 12px;
  text-shadow: none; pointer-events: none;
}
.bgr-fallback-note[hidden] { display: none; }
/* simplified prompter for this lane — deliberately NOT #prompter's markup
   (no cue pills, no drag reading-line, no voice-pace): a plain auto-scroll
   block, shown or hidden in full by the Aa toggle. See app.js bgrScrollTick. */
.bgr-prompter {
  position: absolute; left: 0; right: 0; top: 0; height: 56%; z-index: 3;
  overflow: hidden; pointer-events: none;
  background: linear-gradient(rgba(0,0,0,.34), rgba(0,0,0,.14) 62%, transparent);
  -webkit-mask-image: linear-gradient(#000 88%, rgba(0,0,0,.6) 93%, transparent 96.5%);
          mask-image: linear-gradient(#000 88%, rgba(0,0,0,.6) 93%, transparent 96.5%);
}
.bgr-prompter[hidden] { display: none; }
.bgr-prompter-content {
  padding: 42vh 24px 60px; font-size: 28px; line-height: 1.5; font-weight: 600;
  text-align: center; color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.9);
  will-change: transform;
}
.bgr-prompter-content .pline { display: block; }
.bgr-prompter-content .pline + .pline { margin-top: .28em; }
.bgr-prompter-content .pgap { display: block; height: .85em; }
/* the gesture-zone coaching line (replaces "point at things" framing — the
   matte cuts a broad, shoulder-height gesture far more cleanly than a thin
   outstretched arm). Reuses .broll-coach's quiet centered-caption look. */
.bgr-coach {
  position: absolute; z-index: 3; left: 16px; right: 16px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 108px);
  margin: 0; color: rgba(255,255,255,.85); text-shadow: 0 1px 3px rgba(0,0,0,.7);
}
.bgr-coach[hidden] { display: none; }

/* ---- framing step chrome (this task, Aug 19) -- the pinch/drag surface
   itself is #bgr-canvas above; everything here is just the topbar label and
   the bottom Reset/Done bar sitting over it. .bgr-coach (reused, not
   redeclared) carries the "pinch to zoom" hint line. ---- */
.bgr-framing-title {
  color: #fff; font-weight: 800; font-size: .92rem;
  text-shadow: 0 1px 3px rgba(0,0,0,.6);
}
/* .rec-topbar is `justify-content: space-between` for a close button + a
   right-side control (see the real recorder's Aa toggle); the framing topbar
   has no right-side control, so this empty same-width spacer keeps the title
   visually centered instead of hugging the close button. */
.bgr-framing-spacer { width: 44px; }
.bgr-framing-controls {
  position: absolute; z-index: 3; left: 0; right: 0;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 16px);
  display: flex; align-items: center; justify-content: center; gap: 18px;
  padding: 0 20px;
}
/* dark camera chrome override for both buttons in this bar -- same pattern as
   .clip-watch-bar .btn-text just above in this file (white text/quiet pill on
   a black background instead of the light-shell defaults). */
.bgr-framing-controls .btn-text { color: #fff; }
/* .btn-primary is width:100% by the style guide's global button floor (right
   for a full-width sheet action); here it sits next to Reset instead of
   spanning the screen, same override reasoning as .pub .btn-primary in
   APP_STYLE_GUIDE.md. */
.bgr-framing-controls .btn-primary { width: auto; min-width: 168px; }

/* ---- review step: warm paper, like every other screen (only the video itself
   is the usual black letterbox) ---- */
#view-bgr-review {
  position: fixed; inset: 0; z-index: 55; background: var(--bg);
  overflow-y: auto; -webkit-overflow-scrolling: touch;
}
#view-bgr-review[hidden] { display: none; }
/* same measure as .view (the shared screen wrapper this section deliberately
   does not use — see the markup comment) so this reads like every other
   screen on a wide viewport instead of stretching edge to edge */
#view-bgr-review > * { max-width: 560px; margin-left: auto; margin-right: auto; }
#view-bgr-review { padding: 16px; padding-bottom: 40px; box-sizing: border-box; }
#bgr-playback {
  display: block; margin: 0 auto 14px; width: auto; max-width: 100%; max-height: 60vh;
  background: #000; border-radius: var(--radius-lg); border: 1px solid var(--border);
  object-fit: contain;
}

/* ---- mode / script / backdrop sheets — the same full-screen dark-scrim +
   paper-card shape as #broll-sheet, just new ids so nothing shares state with
   it ---- */
#bgr-mode-sheet, #bgr-script-pick-sheet, #bgr-backdrop-sheet {
  position: fixed; inset: 0; z-index: 70; background: rgba(0,0,0,.82);
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
#bgr-mode-sheet[hidden], #bgr-script-pick-sheet[hidden], #bgr-backdrop-sheet[hidden] { display: none; }
#bgr-script-list { max-height: 50vh; overflow-y: auto; -webkit-overflow-scrolling: touch; }
/* backdrop picker grid — same recipe as .broll-list/.broll-item (Settings
   library) but its OWN class names: this picker needs BOTH <img> and <video>
   tiles sized identically, which the shared .broll-item never had to do (its
   photos have always rendered unstyled there — a pre-existing gap, not one
   this feature should fix by reaching into shared CSS). */
.bp-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 10px; max-height: 54vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
  margin: 4px 0;
}
.bp-item {
  background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 8px; display: flex; flex-direction: column; gap: 6px; cursor: pointer;
  text-align: left; font: inherit; color: inherit;
  transition: border-color .15s ease, background .15s ease;
}
.bp-item:hover { background: var(--card-2); border-color: var(--border-2); }
.bp-item:active { transform: scale(.99); }
.bp-item img, .bp-item video {
  width: 100%; aspect-ratio: 9 / 16; object-fit: cover; border-radius: 10px; background: #000;
  pointer-events: none;   /* the whole card is the tap target, not the media itself */
}
.bp-name { font-size: .8rem; font-weight: 600; line-height: 1.25; word-break: break-word; }
.bp-group-hd { grid-column: 1 / -1; margin: 6px 0 0; }

/* upload-on-the-spot escape hatch, #bgr-backdrop-sheet (this task) -- a
   <label class="btn-primary"> reuses the button kit byte-for-byte (flex/pill/
   accent fill already covers a <label>, no tag-specific rule needed), so it
   reads exactly as unmissable as any other primary action, empty library or
   not. margin-bottom separates it from the grid/empty-line below it. */
.bgr-upload-btn { margin-bottom: 12px; }
.bgr-upload-btn.is-disabled { opacity: .6; pointer-events: none; }
.bgr-upload-err { color: var(--danger, #e5484d); }
.bgr-upload-err[hidden] { display: none; }

/* ---- review-screen backdrop reassurance -------------------------------
   Playback on the review screen is the RAW take (MediaRecorder captures the
   camera, the backdrop is composited server-side at render). Travis recorded
   with a backdrop, hit review, and saw only himself -- correct behaviour that
   reads exactly like a silent failure. This row shows the chosen backdrop
   back to him so "it is still coming" is visible rather than asserted. */
.bgr-review-backdrop {
  display: flex; align-items: center; gap: 12px;
  margin: 12px 16px 0; padding: 10px 12px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
}
.bgr-review-backdrop[hidden] { display: none; }
.bgr-review-backdrop-thumb {
  flex: 0 0 auto; width: 46px; height: 62px; border-radius: 8px;
  overflow: hidden; background: var(--card-2);
  border: 1px solid var(--border); display: block;
}
.bgr-review-backdrop-thumb img,
.bgr-review-backdrop-thumb video {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.bgr-review-backdrop-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.bgr-review-backdrop-txt b { font-size: .95rem; }
.bgr-review-backdrop-txt i { font-style: normal; font-weight: 600; color: var(--text); }

/* ============================================================
   C1 IMAGE PORTAL (this task) -- frontend half only, docs/DESIGN_IMAGE_
   PORTAL.md + docs/STEAL_FILE_IMAGE_PORTAL.md + docs/ARCH_PORTALS.md.
   Same non-`.view` fixed-overlay convention as the LIVE BACKDROP RECORDER
   block above -- own ids only (imgp-*, #img-photo-*), zero shared selectors.
   Dark canvas/sheet chrome uses raw rgba(0,0,0,x)/rgba(255,255,255,x), the
   SAME documented precedent as the recorder + bgr-* blocks above (dark
   camera/canvas chrome is intentionally outside the light-shell token
   palette -- APP_STYLE_GUIDE.md hard rule 1); every OTHER color here is a
   real style.css token. #img-photo-sheet reuses .broll-card/.bp-list/
   .bgr-upload-btn/.bgr-upload-err byte-for-byte (pure presentation, no JS
   tie), same style-kit-composition precedent as the bgr-* block.
   ============================================================ */
/* photo picker -- the SAME full-screen dark-scrim + paper-card shape as
   #bgr-backdrop-sheet (own id, no shared state). */
#img-photo-sheet {
  position: fixed; inset: 0; z-index: 70; background: rgba(0,0,0,.82);
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
#img-photo-sheet[hidden] { display: none; }

#view-img-portal {
  position: fixed; inset: 0; z-index: 56; background: #000;
  display: flex; flex-direction: column; overflow: hidden;
}
#view-img-portal[hidden] { display: none; }

/* ---- top bar: back, destination toggle, draft status, Next ---- */
.imgp-topbar {
  flex: none; padding-top: env(safe-area-inset-top, 0px);
  min-height: 56px; display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding-left: 10px; padding-right: 10px; position: relative; z-index: 3;
}
.icon-btn-d {
  width: 44px; height: 44px; border-radius: 50%; background: rgba(0,0,0,.45);
  border: 1px solid rgba(255,255,255,.14); display: flex; align-items: center; justify-content: center;
  color: #fff; flex: none;
}
.icon-btn-d svg { width: 18px; height: 18px; }
.icon-btn-d:active { background: rgba(0,0,0,.6); transform: scale(.94); }
.imgp-dest-toggle {
  display: flex; background: rgba(255,255,255,.08); border-radius: 999px; padding: 3px; gap: 2px;
  flex: none;
}
.imgp-dest-toggle button {
  border: none; background: transparent; color: rgba(255,255,255,.7); font-size: .74rem; font-weight: 700;
  padding: 0 12px; min-height: 34px; border-radius: 999px;
}
.imgp-dest-toggle button.active { background: var(--accent); color: #fff; }
.imgp-draft {
  display: flex; align-items: center; gap: 5px; color: rgba(255,255,255,.62); font-size: .68rem;
  flex: none; white-space: nowrap;
}
.imgp-draft .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--done); flex: none; }
.imgp-next-pill {
  height: 44px; padding: 0 18px; border-radius: 999px; background: var(--accent); color: #fff;
  font-size: .86rem; font-weight: 700; border: none; box-shadow: var(--glow); flex: none;
}
.imgp-next-pill:disabled { opacity: .65; }

/* ---- canvas: full-bleed, centred at any width (desktop parity, item 10 --
   docs/ARCH_PORTALS.md §7b: "canvas centered... no separate desktop build").
   height:100% + aspect-ratio + max-width:100% is a pure-CSS letterbox-fit,
   no JS resize math needed for the canvas box itself. ---- */
/* z-index:1 is LOAD-BEARING, not decoration. It gives the canvas its own
   stacking context, so the selection chrome (600), the trash (601) and the
   safe-area guide (500) inside it are CONTAINED and compared against each
   other only. Without it those numbers competed directly with the contextual
   sheet (57/58) in the root stacking context and won: grips and action pills
   floated on top of the open bottom sheet (QA screenshot 15). Selection
   chrome must always sit BELOW a sheet. */
.imgp-canvas-wrap {
  flex: 1; position: relative; z-index: 1; overflow: hidden; min-height: 0;
  display: flex; align-items: center; justify-content: center;
}
.imgp-canvas {
  position: relative; height: 100%; width: auto; max-width: 100%;
  aspect-ratio: 1080 / 1350;   /* feed default; .imgp-dest-story below overrides */
  background: #15151a; overflow: hidden;
  container-type: inline-size;   /* lets .imgp-text-big-quiet size in cqw, free desktop scaling */
}
.imgp-canvas.imgp-dest-story { aspect-ratio: 1080 / 1920; }

/* ---- layers: LIVE DOM elements, never a <canvas>/fabric/konva stack
   (Fable ruling, docs/ARCH_PORTALS.md §1/§8). x/y = normalised CENTRE,
   translate(-50%,-50%) does the centring; w = normalised width. touch-
   action:none so a drag can never fight the page/canvas scroll underneath
   (anti-jank #5) -- harmless on the non-draggable photo layer too. ---- */
.imgp-layer { position: absolute; touch-action: none; }
.imgp-layer-photo { z-index: 0; }
.imgp-photo-img { width: 100%; height: 100%; object-fit: cover; display: block; -webkit-user-drag: none; }
.imgp-text {
  text-align: center; font-weight: 800; line-height: 1.14; letter-spacing: -.01em;
  text-shadow: 0 2px 10px rgba(0,0,0,.35); white-space: pre-wrap; overflow-wrap: break-word;
  padding: 2px 4px; cursor: grab;
}
/* house look #6 "Big and quiet" -- heavy centred type, soft shadow, nothing
   else. THE only text style in v1 (styleToken reserved for future named
   looks). Sized in cqw (container query width) off .imgp-canvas so it scales
   identically on a 390px phone and a desktop-width canvas with zero JS. */
.imgp-text-big-quiet { font-size: 7.6cqw; }
.imgp-sticker-img, .imgp-logo-img {
  width: 100%; height: auto; display: block;
  filter: drop-shadow(0 4px 14px rgba(0,0,0,.35)); -webkit-user-drag: none;
}
.imgp-sticker-fallback {
  width: 100%; aspect-ratio: 1; border-radius: 50%; background: var(--accent-soft);
  border: 1.5px solid var(--accent); display: flex; align-items: center; justify-content: center;
  color: var(--accent); font-size: .82rem; font-weight: 800; text-align: center; padding: 4px;
}

/* ---- selection chrome: ring on the layer itself (matches the approved
   mockup's .title-layer .sel treatment) + a sibling overlay carrying the
   four grips + contextual pills, sized/positioned from the selected layer's
   live rect in imgpUpdateSelChrome (app.js). Grips are 44px tap targets
   (anti-jank #2) with a smaller visible dot via ::after. ---- */
.imgp-layer.imgp-selected { outline: 3px solid var(--accent); outline-offset: 8px; border-radius: 6px; }
.imgp-selchrome { position: absolute; pointer-events: none; z-index: 600; }
.imgp-selchrome[hidden] { display: none; }
.imgp-grip {
  position: absolute; width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 0; pointer-events: auto; touch-action: none;
}
.imgp-grip::after {
  content: ''; width: 22px; height: 22px; border-radius: 50%; background: #fff;
  border: 2px solid var(--accent); box-shadow: 0 2px 6px rgba(0,0,0,.35); display: block;
}
.imgp-grip.tl { left: 0; top: 0; transform: translate(-50%, -50%); }
.imgp-grip.tr { left: 100%; top: 0; transform: translate(-50%, -50%); }
.imgp-grip.bl { left: 0; top: 100%; transform: translate(-50%, -50%); }
.imgp-grip.br { left: 100%; top: 100%; transform: translate(-50%, -50%); }
/* the base photo layer has no free resize (it stays full-bleed) -- ring +
   the single Replace pill only, no grips. */
.imgp-selchrome.imgp-sel-photo .imgp-grip { display: none; }

/* contextual action pills -- AT MOST TWO, per layer TYPE (never generic),
   flip ABOVE the layer near the bottom edge so they can never fall off
   screen (docs/DESIGN_IMAGE_PORTAL.md TASTE CALLS #3). */
.imgp-pills {
  position: absolute; left: 50%; top: calc(100% + 10px); transform: translateX(-50%);
  display: flex; gap: 8px; pointer-events: auto; white-space: nowrap;
}
.imgp-pills.imgp-pills-above { top: auto; bottom: calc(100% + 10px); }
.imgp-pill {
  background: rgba(0,0,0,.72); color: #fff; border: 1px solid rgba(255,255,255,.18);
  border-radius: 999px; padding: 0 16px; min-height: 44px; display: flex; align-items: center;
  font-size: .82rem; font-weight: 700;
}
.imgp-pill:active { background: rgba(0,0,0,.85); }

/* drag-to-bottom-trash delete -- a pre-trained IG Stories gesture, kept
   ALONGSIDE the Delete pill (two routes to delete: one discoverable, one
   fast). Shown only while a layer is actively being dragged. */
.imgp-trash {
  position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
  width: 56px; height: 56px; border-radius: 50%; background: rgba(0,0,0,.55);
  border: 2px solid rgba(255,255,255,.25); display: flex; align-items: center; justify-content: center;
  font-size: 1.3rem; z-index: 601; pointer-events: none; transition: background .12s ease, transform .12s ease;
}
.imgp-trash[hidden] { display: none; }
.imgp-trash-hot { background: var(--rec); border-color: #fff; transform: translateX(-50%) scale(1.16); }

/* "Clean up" tap-a-spot pin (this task's scoped build of that chip -- see
   imgpOpenCleanupSheet in app.js) */
.imgp-cleanup-pin {
  position: absolute; width: 28px; height: 28px; border-radius: 50%;
  border: 3px solid var(--accent); background: rgba(192,90,43,.25);
  transform: translate(-50%, -50%); z-index: 610; pointer-events: none;
}
.imgp-cleanup-pin[hidden] { display: none; }

/* ---- safe-area guides (item 9): destination-scoped, subtle -- a guide, not
   a billboard. Replaces the mockup's face-safe zone per Travis's Aug-19
   call ("I care about the safe zones... no guaranteed subject"). Always on
   top of the layers while editing (z-index above content) so it stays
   readable regardless of what's placed. ---- */
.imgp-safe-guide { position: absolute; inset: 0; pointer-events: none; z-index: 500; }
.imgp-safe-band { position: absolute; left: 0; right: 0; background: rgba(0,0,0,.38); }
.imgp-safe-feed .imgp-safe-band-top { top: 0; height: 10%; }
.imgp-safe-feed .imgp-safe-band-bottom { bottom: 0; height: 10%; }
.imgp-safe-line { position: absolute; left: 0; right: 0; height: 0; border-top: 1px dashed rgba(255,255,255,.55); }
.imgp-safe-line-top { top: 10%; }
.imgp-safe-line-bottom { bottom: 10%; }
.imgp-safe-story .imgp-safe-band-story-top { top: 0; height: 14%; }
.imgp-safe-story .imgp-safe-band-story-bottom { bottom: 0; height: 20%; }
.imgp-safe-label {
  position: absolute; left: 50%; transform: translateX(-50%); top: calc(10% + 6px);
  font-size: .6rem; color: rgba(255,255,255,.85); background: rgba(0,0,0,.5);
  border: 1px solid rgba(255,255,255,.14); border-radius: 6px; padding: 3px 7px;
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace; white-space: nowrap;
}
.imgp-safe-story .imgp-safe-label { top: calc(14% + 6px); }

/* ---- bottom strip (item 4): the house pattern, copied from /edit. Scrolls
   sideways, never wraps; owns horizontal pan so it never fights the canvas's
   own gestures above it (anti-jank #5). ---- */
.imgp-strip-wrap { flex: none; background: #000; position: relative; z-index: 3; }
.imgp-strip {
  display: flex; align-items: stretch; gap: 10px; padding: 12px 14px calc(14px + env(safe-area-inset-bottom, 0px));
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; touch-action: pan-x;
}
.imgp-strip::-webkit-scrollbar { display: none; }
.imgp-chip {
  flex: none; width: 64px; min-height: 60px; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px; border-radius: 14px;
  background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.14); color: #fff;
  padding: 8px 4px; font-size: .62rem; font-weight: 600; text-align: center; line-height: 1.1;
}
.imgp-chip svg { width: 20px; height: 20px; }
.imgp-chip:active { background: rgba(255,255,255,.14); }
.imgp-chip-divider { flex: none; width: 1px; background: rgba(255,255,255,.14); margin: 6px 2px; }
.imgp-chip-ai { width: auto; min-width: 88px; padding: 8px 14px; background: rgba(192,90,43,.16); border-color: var(--accent-line); }

/* ---- the ONE contextual sheet, dark (Travis, Aug 19 DECIDED). ---- */
#imgp-sheet-dim { position: fixed; inset: 0; z-index: 57; background: rgba(0,0,0,.5); }
#imgp-sheet-dim[hidden] { display: none; }
#imgp-sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 58; background: #111114;
  border-top: 1px solid rgba(255,255,255,.14); border-radius: 20px 20px 0 0;
  box-shadow: 0 -12px 30px rgba(0,0,0,.4); padding: 10px 18px calc(22px + env(safe-area-inset-bottom, 0px));
  max-height: 72vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
}
#imgp-sheet[hidden] { display: none; }
.imgp-sheet-grip { width: 36px; height: 4px; border-radius: 99px; background: rgba(255,255,255,.14); margin: 0 auto 14px; }

/* ---- compliance block (Ontario CCO ad rules). Shown INSIDE the dark
   contextual sheet, so it inherits the sheet's chrome and adds only the list
   of flagged lines. The point is that the user can see exactly which word in
   which line is the problem, and jump straight to fixing it. ---- */
.imgp-compliance-list { display: flex; flex-direction: column; gap: 10px; margin: 12px 0 14px; }
.imgp-compliance-row {
  background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.12);
  border-left: 3px solid var(--rec); border-radius: 12px; padding: 10px 12px;
}
.imgp-compliance-term { font-size: .9rem; font-weight: 700; color: #fff; }
.imgp-compliance-quote {
  font-size: .8rem; color: rgba(255,255,255,.62); margin-top: 3px; line-height: 1.4;
  overflow-wrap: break-word;
}
.imgp-compliance-fix {
  margin-top: 8px; padding: 0; min-height: 32px; color: var(--accent);
  font-size: .82rem; font-weight: 700;
}

/* ---- "Continue your image" card on home. Pure spacing on top of the shared
   .ob-choice-card component; no new look. ---- */
.imgp-resume-card { margin: 0 16px 14px; width: calc(100% - 32px); }
.imgp-resume-card[hidden] { display: none; }
.imgp-sheet-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.imgp-sheet-head h3 { margin: 0; font-size: .95rem; font-weight: 700; color: #fff; }
.imgp-sheet-body p.muted { color: rgba(255,255,255,.62); }
.imgp-sheet-body .field span { color: rgba(255,255,255,.75); }
.imgp-sheet-body .btn-primary { margin-top: 4px; }

/* colour picker -- THREE rows, brand FIRST / curated SECOND / wheel LAST.
   ORDER IS LOAD-BEARING (docs/DESIGN_IMAGE_PORTAL.md TASTE CALLS #1) -- the
   markup order in app.js's imgpOpenColorsSheet must never change; this CSS
   only styles whatever order the rows are built in, on purpose. */
.imgp-color-row { margin-bottom: 16px; }
.imgp-color-row-label {
  color: rgba(255,255,255,.62); font-size: .7rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .03em; margin-bottom: 8px;
}
.imgp-swatch-row { display: flex; gap: 10px; flex-wrap: wrap; }
.imgp-swatch { width: 44px; height: 44px; border-radius: 50%; border: 2px solid rgba(255,255,255,.25); padding: 0; }
.imgp-swatch:active { transform: scale(.92); }
.imgp-wheel-row { align-items: center; gap: 14px; }
.imgp-color-wheel { width: 44px; height: 44px; border: none; background: none; padding: 0; border-radius: 50%; overflow: hidden; }
.imgp-eyedrop-btn {
  background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.18); color: #fff;
  border-radius: 999px; padding: 0 14px; min-height: 44px; font-size: .8rem; font-weight: 600;
}

/* sticker sheet -- "the sticker itself, big", bare dark sheet, NO card
   around it (docs/DESIGN_IMAGE_PORTAL.md TASTE CALLS #4). Stickers are DATA
   (imgpFetchStickers) -- nothing here is hardcoded. */
.imgp-sticker-grid { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; padding: 6px 0 4px; }
.imgp-sticker-opt { background: none; border: none; padding: 8px; width: 88px; height: 88px; display: flex; align-items: center; justify-content: center; }
.imgp-sticker-opt img { max-width: 100%; max-height: 100%; display: block; }
.imgp-sticker-opt:active { transform: scale(.92); }

/* template gallery -- ~3 visible in v1, built as a scrolling gallery so a
   bigger set later needs no rework (item 8). */
.imgp-template-grid { display: flex; gap: 14px; overflow-x: auto; padding: 4px 0 8px; -webkit-overflow-scrolling: touch; }
.imgp-template-opt { flex: none; width: 110px; background: none; border: none; display: flex; flex-direction: column; align-items: center; gap: 8px; color: #fff; }
.imgp-tpl-thumb { width: 96px; height: 120px; border-radius: 12px; background: #26262c; border: 1px solid rgba(255,255,255,.14); display: block; position: relative; }
.imgp-tpl-thumb::after { content: ''; position: absolute; left: 10%; right: 10%; background: rgba(255,255,255,.5); border-radius: 4px; }
.imgp-tpl-img-house-1::after { top: 44%; height: 16%; }
.imgp-tpl-img-house-2::after { top: 10%; height: 20%; left: 6%; right: 6%; }
.imgp-tpl-img-house-3::after { bottom: 10%; top: auto; height: 16%; left: 8%; right: 40%; }
.imgp-tpl-name { font-size: .78rem; font-weight: 600; }
