/* KitchenDrop operator portal. Depends on brand.css.
   Built phone-first — this gets used standing at the pass, not at a desk. */

:root {
  /* The width of the work on a desktop, before the view's own padding. Every
     view is this wide and centred, so a form, a card, a section heading and
     the drops grid all share one left and one right edge no matter how big
     the monitor is. Wider than this and a single-line input starts reading as
     a mistake; narrower and a row of drop cards stops fitting three across. */
  --col: 880px;
}

/* ---------- Which agent a thing came from ----------
   Anything carrying data-intent picks up that agent's colour and wash here,
   once, and every component downstream just reads --intent. Each agent's
   colour used to be written out again in every component that showed one,
   which is how the pitch pills and the mix meters ended up disagreeing about
   Reach -- and how two of them ended up as raw hexes that went unreadable the
   moment the theme flipped. */

[data-intent="reach"] {
  --intent: var(--intent-reach);
  --intent-wash: var(--intent-reach-wash);
}

[data-intent="craving"] {
  --intent: var(--intent-craving);
  --intent-wash: var(--intent-craving-wash);
}

[data-intent="trust"] {
  --intent: var(--intent-trust);
  --intent-wash: var(--intent-trust-wash);
}

[data-intent="booking_order"] {
  --intent: var(--intent-booking_order);
  --intent-wash: var(--intent-booking_order-wash);
}

body {
  display: flex;
  justify-content: center;
  min-height: 100vh;
  background: var(--surface-2);
}

.app {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 520px;
  min-height: 100vh;
  padding-bottom: calc(var(--tabbar-h, 72px) + 20px);
  background: var(--bg);
  border-inline: 1px solid var(--line);
}

/* ---------- App bar ---------- */

.app-bar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: var(--veil);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line);
}

.app-bar .kd-logo {
  font-size: 0.98rem;
  margin-right: auto;
}

.app-bar-tools {
  display: flex;
  align-items: center;
  gap: 8px;
}

.btn-sm {
  padding: 0.45em 0.75em;
  font-size: 0.82rem;
}

/* Which restaurant you're working on is context, not a form field. It reads as
   one quiet line under the app bar so the first real thing on the page is the
   thing you came to do. */
.place-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--surface);
}

.place-bar-icon {
  width: 17px;
  height: 17px;
  flex: none;
  color: var(--fg-3);
}

.place-select {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: 2px 0;
  font: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--fg-1);
  cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2378808f' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right 0 center;
  background-size: 15px;
  padding-right: 20px;
  text-overflow: ellipsis;
}

.place-select:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: 4px;
}

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

/* ---------- Views ---------- */

.view {
  display: none;
  padding: 16px;
}

.view.is-active {
  display: block;
  animation: kd-view 0.18s var(--ease);
}

@keyframes kd-view {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* ---------- Send card ---------- */




/* ---------- Cards & forms ---------- */

.card {
  padding: 18px;
  margin-bottom: 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

.card-head {
  margin-bottom: 18px;
}

.card-head h2 {
  font-family: var(--sans);
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -0.012em;
}

.card-head p {
  max-width: 62ch;
  margin-top: 4px;
  font-size: 0.84rem;
  color: var(--fg-3);
  line-height: 1.5;
}

.switch-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 0;
  border-top: 1px solid var(--line-soft);
}

.switch-row b {
  display: block;
  font-size: 0.9rem;
  font-weight: 600;
}

.switch-row span {
  display: block;
  margin-top: 2px;
  font-size: 0.8rem;
  color: var(--fg-3);
  line-height: 1.45;
}

.switch-row:last-child {
  padding-bottom: 0;
}

/* A quiet "optional" tag inside a field label. */
/* Two short fields that belong together read better on one line than stacked. */
.field-pair {
  display: grid;
  gap: 0 12px;
  grid-template-columns: 1fr 1fr;
}

@media (max-width: 400px) {
  .field-pair { grid-template-columns: 1fr; }
}

.label-optional {
  margin-left: 5px;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--fg-4);
  text-transform: none;
  letter-spacing: 0;
}

/* ---------- Dropzone ---------- */

/* Both selectors carry the same weight on purpose. A bare `.dropzone` loses to
   `.field label` in brand.css, which forces display:block and collapses the
   icon, the title and the hint onto one line — so every context a dropzone
   lives in has to be named rather than relying on the class alone. */
.field .dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 26px 16px;
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface-2);
  cursor: pointer;
  text-align: center;
  transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease);
}

.dropzone:hover,
.dropzone.is-hot {
  border-color: var(--brand);
  background: var(--brand-wash);
}

.dropzone svg {
  width: 22px;
  height: 22px;
  margin-bottom: 5px;
  color: var(--fg-3);
}

.field .dropzone b {
  margin: 0;
  color: var(--fg);
  font-size: 0.9rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.field .dropzone span {
  font-size: 0.79rem;
  color: var(--fg-3);
}

/* Picked files, listed under the dropzone so photos/videos are clearly visible with thumbnails */
.file-list {
  list-style: none;
  margin: 12px 0 0;
  padding: 0 2px 0 0;
  display: grid;
  gap: 8px;
  max-height: 240px;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong, #d0c8bc) transparent;
}
.file-list::-webkit-scrollbar {
  width: 5px;
}
.file-list::-webkit-scrollbar-thumb {
  background: var(--line-strong, #d0c8bc);
  border-radius: 4px;
}
.file-list li,
.file-list .file-item {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 13px;
  color: var(--fg-2, #4a5468);
  background: var(--surface-2, #f3f0eb);
  border: 1px solid var(--line, #e4dfd6);
  border-radius: 10px;
  padding: 8px 12px;
  position: relative;
  overflow: hidden;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.file-list li:hover,
.file-list .file-item:hover {
  border-color: var(--line-strong, #d0c8bc);
  background: var(--surface, #ffffff);
}
.file-list-badge {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: var(--r-pill);
  background: var(--brand, #dd5a1f);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  display: grid;
  place-items: center;
}
/* A drop is a running order, so the rows can be dragged into one. The handle is
   a real button: the same reorder has to work from a keyboard, and native
   drag-and-drop never fires on touch. */
.file-drag-handle {
  flex: 0 0 auto;
  width: 24px;
  height: 32px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: var(--fg-3, #78808f);
  cursor: grab;
  display: grid;
  place-items: center;
  padding: 0;
  margin-left: -4px;
  touch-action: none;
  transition: color 0.15s ease, background 0.15s ease;
}
.file-drag-handle:hover {
  color: var(--fg, #16203a);
  background: var(--surface-3, #e8e3da);
}
.file-drag-handle:focus-visible {
  outline: none;
  border-color: var(--brand, #dd5a1f);
  color: var(--brand, #dd5a1f);
}
.file-drag-handle:active {
  cursor: grabbing;
}
.file-drag-handle svg {
  width: 15px;
  height: 15px;
  display: block;
}
.file-list .file-item.is-dragging {
  border-color: var(--brand, #dd5a1f);
  background: var(--brand-wash, #fdf1ea);
  box-shadow: var(--shadow-2, 0 4px 12px rgba(22, 32, 58, 0.12));
}

/* Wraps the thumb so a clip's length can sit on top of its own frame. */
.file-thumb-wrap {
  position: relative;
  flex: 0 0 48px;
  width: 48px;
  height: 48px;
}
.file-thumb-wrap .file-list-thumb,
.file-thumb-wrap .file-list-video-icon {
  flex: none;
  width: 100%;
  height: 100%;
}
.file-thumb-duration {
  position: absolute;
  right: 2px;
  bottom: 2px;
  padding: 1px 4px;
  border-radius: 4px;
  background: rgba(11, 16, 32, 0.78);
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.4;
}

/* What the shots add up to, said before the owner sends rather than after the
   render comes back a different length than they pictured. */
.shot-summary {
  margin: 10px 2px 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--fg-3, #78808f);
}
/* Said before sending, when the attached shots will not all survive the cut. */
.shot-summary.is-warning {
  color: var(--warn);
  font-weight: 600;
}


/* The pre-send check under the format picker. Three tones, one box. */

.file-list-thumb {
  flex: 0 0 48px;
  width: 48px;
  height: 48px;
  border-radius: var(--r-sm);
  object-fit: cover;
  background: var(--surface-3, #e8e3da);
  border: 1px solid var(--line-strong, #d0c8bc);
  display: block;
  box-shadow: var(--shadow-1, 0 1px 2px rgba(22, 32, 58, 0.07));
}
.file-list-video-icon {
  flex: 0 0 48px;
  width: 48px;
  height: 48px;
  border-radius: var(--r-sm);
  background: var(--surface-3, #e8e3da);
  border: 1px solid var(--line-strong, #d0c8bc);
  display: grid;
  place-items: center;
  color: var(--brand, #dd5a1f);
  box-shadow: var(--shadow-1, 0 1px 2px rgba(22, 32, 58, 0.07));
}
.file-list-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
  gap: 2px;
}
.file-list-name {
  font-weight: 600;
  color: var(--fg, #16203a);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
}
.file-list-meta {
  font-size: 11px;
  color: var(--fg-3, #78808f);
  font-weight: 500;
}
.file-remove-btn {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--fg-3, #78808f);
  cursor: pointer;
  display: grid;
  place-items: center;
  padding: 0;
  margin-left: auto;
  transition: all 0.15s ease;
}
.file-remove-btn:hover {
  background: var(--bad-wash, #fbecea);
  color: var(--bad, #b23b2c);
  border-color: rgba(178, 59, 44, 0.2);
}
.file-remove-btn svg {
  width: 15px;
  height: 15px;
  display: block;
}

.dropzone.has-file {
  border-style: solid;
  border-color: var(--brand);
  background: var(--brand-wash);
}

.dropzone.has-file svg {
  color: var(--brand);
}

/* ---------- Feed ---------- */

/* A row, not a column. Cards keep the size a drop actually is and the row runs
   past the right edge, which is what says there is more without spending a
   screen saying it. */
/* A grid, not a row.
   This was a horizontal carousel: on a 1280px screen it showed three and a half
   drops out of nine, with the bottom half of the page empty underneath, and it
   needed arrows, edge fades and scroll-position bookkeeping to make being a row
   bearable. A grid needs none of that, shows all of them, and scrolls the way
   every other page does. */
.feed {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 16px;
  /* Vertical room for the card shadow, which a clipped overflow would cut. */
  padding: 4px;
  margin: 0 -4px;
}

/* Two up on a phone. One 340px-wide card per row is a screen per drop — worse
   than the row this replaced, which at least let you swipe. Narrow enough that
   the action labels shorten, which .drop-actions-minor already handles. */
@media (max-width: 560px) {
  .feed {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
  }

  /* Two folds, two action rows, a caption and its hashtags all stack in a
     column 139px wide, so every row of chrome costs real depth. */
  .drop-body .vo { margin-top: 6px; }
  .drop-body .vo summary { padding-top: 6px; padding-bottom: 6px; }

  /* One line of hashtags, not two. Two was already a truncation — the second
     line only ever bought one more tag, for a fifth of the body's height.
     Scoped through .drop-body to outrank the base rule further down. */
  .drop-body .tags { -webkit-line-clamp: 1; }

  /* A flex line is collected from each item's content width, so a label wider
     than what the tick left over jumped to a line of its own and left the tick
     sitting alone. A zero basis puts the label on the tick's line and lets it
     wrap inside itself; the link takes the line under them both. */
  .drop-body .stamp-text { flex: 1 1 0; text-align: center; }
  .drop-body .stamp-link { flex: 1 0 100%; margin-left: 0; text-align: center; }
}


/* Empty states and the loading skeleton are messages about the grid, not items
   in it, so they take the full width rather than sitting in one cell. */
.feed > :not(.drop):not(.feed-more) { grid-column: 1 / -1; }

.drop {
  display: flex;
  /* Was `flex: 0 0 260px` for the carousel; the grid track sets the width now,
     so a card fills its column instead of being a fixed slab in a row. */
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-2);
  overflow: hidden;
}

/* The card opens on the drop itself. State, dish and provenance are laid over
   the picture rather than stacked above it in a bar, so what the owner is
   judging is the first thing on screen and every card is the same shape. */
.drop-media {
  position: relative;
  flex: none;
  width: 100%;
  aspect-ratio: 4 / 5;
  background: #0a0e0b;
  display: grid;
  place-items: center;
  overflow: hidden;
}

.drop-media.is-still { background: var(--surface-2); }

.drop-media video,
.drop-media img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* Fills the stage a still of another shape would leave bare. The frame itself
   stays whole on top of it: cropping to fit would hide the part of the picture
   the owner is being asked to stand behind. */
.drop-backdrop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: cover;
  filter: blur(26px) saturate(1.25);
  /* Past the edges, so the blur samples picture rather than its own falloff. */
  transform: scale(1.18);
}

.drop-shot { position: relative; }

/* ---- The overlay ----
   Inert as a whole. The stage under it is one large play target, and a lid of
   transparent boxes over that would swallow the click. Only remove opts back
   in to pointer events. */

.drop-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  transition: opacity 0.2s var(--ease);
}

/* The edit, on the picture. Hidden until the pointer is on the card so a grid
   of drops stays a grid of drops; the Edit button in the row below is what
   touch uses, where there is no hover to show this. */
.drop-edit {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) scale(0.96);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 9px 16px;
  border: none;
  border-radius: 999px;
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-size: 0.86rem;
  font-weight: 600;
  box-shadow: var(--shadow-3);
  opacity: 0;
  cursor: pointer;
  transition: opacity 0.16s var(--ease), transform 0.16s var(--ease);
}

.drop-media:hover .drop-edit,
.drop-edit:focus-visible {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

@media (hover: none) {
  .drop-edit { display: none; }
}

/* Over a photograph the wash backgrounds have nothing to sit on, so the chip
   keeps a dark base for legibility and takes its state from a tint laid over
   that base — enough hue to tell the states apart down a row of cards, not so
   much that it stops being readable against a bright picture. */
.drop-overlay .pill {
  position: absolute;
  top: 9px;
  left: 9px;
  max-width: calc(100% - 60px);
  padding: 0.28em 0.62em;
  border-color: var(--edge, rgba(255, 255, 255, 0.18));
  background: linear-gradient(rgba(10, 14, 24, 0.62), rgba(10, 14, 24, 0.62)),
    var(--tint, transparent);
  color: var(--ink, #fff);
  box-shadow: 0 1px 10px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.drop-overlay .pill::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--dot, #cbd5e1);
  box-shadow: 0 0 0 3px var(--halo, transparent);
}

.drop-overlay .pill-live::before { animation: kd-pulse 1.6s var(--ease) infinite; }

.drop-overlay .pill-good {
  --dot: #4ade80;
  --tint: rgba(34, 197, 94, 0.34);
  --edge: rgba(134, 239, 172, 0.5);
  --ink: #d7f8e3;
}

.drop-overlay .pill-warn {
  --dot: #fb923c;
  --tint: rgba(249, 115, 22, 0.34);
  --edge: rgba(253, 186, 116, 0.5);
  --ink: #ffe6cf;
}

.drop-overlay .pill-bad {
  --dot: #f87171;
  --tint: rgba(239, 68, 68, 0.36);
  --edge: rgba(252, 165, 165, 0.5);
  --ink: #ffdedb;
}

.drop-overlay .pill-brand {
  --dot: #fb923c;
  --tint: rgba(221, 90, 31, 0.36);
  --edge: rgba(253, 186, 116, 0.5);
  --ink: #ffe6cf;
}

/* The one state that is a request rather than a report: it is the only thing
   on this screen the owner has to do something about, so it drops the dark
   base entirely and reads as a solid marker down the row. */
.drop-overlay .pill-gold {
  --dot: #7a5300;
  --halo: rgba(122, 83, 0, 0.16);
  border-color: rgba(255, 255, 255, 0.42);
  background: linear-gradient(180deg, #ffd873, #f7bf3c);
  color: #4a3400;
  font-weight: 700;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}

.drop-capbar {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 26px 12px 10px;
  background: linear-gradient(
    to top,
    rgba(8, 11, 18, 0.88) 0%,
    rgba(8, 11, 18, 0.55) 46%,
    rgba(8, 11, 18, 0) 100%
  );
  color: #fff;
}

/* The pipeline and the stopped panel are centred columns and need the floor
   they stand on; the capbar's gradient is also cut for photographs, and over a
   pale panel it reads as a stray dark band. */
.drop-media.is-pipeline .drop-capbar,
.drop-media.is-stalled .drop-capbar { display: none; }

.drop-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 650;
  line-height: 1.25;
  letter-spacing: -0.01em;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.drop-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  margin-top: 5px;
  font-size: 0.74rem;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.84);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.drop-facts span + span::before {
  content: "";
  display: inline-block;
  width: 1px;
  height: 0.85em;
  margin: 0 8px;
  vertical-align: -0.1em;
  background: rgba(255, 255, 255, 0.34);
}

@media (prefers-reduced-motion: reduce) {
  .drop-overlay,
  .drop-remove { transition: none; }
}

/* ---- The opener ----
   A reel's first frame is often dark and always still, which reads as a broken
   image rather than as something you can start. One disc over the whole stage
   says otherwise. The stage no longer plays in place — it opens the drop in the
   media sheet — so the affordance stays put instead of clearing out to free the
   controls underneath it. */

.drop-open {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  width: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  transition: background-color 0.2s var(--ease);
}

/* Only a clip needs lifting off its frame; a still is its own invitation. */
.drop-open.is-video {
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.16));
}

.drop-open:hover { background-color: rgba(0, 0, 0, 0.14); }

.drop-play-disc {
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  padding-left: 4px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.94);
  color: #16203a;
  box-shadow: var(--shadow-2);
  transition: transform 0.16s var(--ease);
}

.drop-play-disc svg {
  width: 24px;
  height: 24px;
}

.drop-open:hover .drop-play-disc,
.drop-open:focus-visible .drop-play-disc {
  transform: scale(1.07);
}

.drop-open:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: -4px;
}

@media (prefers-reduced-motion: reduce) {
  .drop-open,
  .drop-play-disc {
    transition: none;
  }
}

.drop-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding: 12px;
}

/* The height a stretched card gains goes above the buttons, so a row of cards
   ends on one line rather than wherever each card's own copy ran out. Only the
   last block takes the slack — a failed drop carries both a reason and a retry,
   and pinning each would push them apart instead of down. */
.drop-body > .drop-actions:last-child,
.drop-body > .stamp:last-child,
.drop-body > .failure:last-child { margin-top: auto; }

/* Three lines, always — not at most three. A caption that ran to two would
   otherwise start its hashtags a line higher than the card beside it, and the
   whole point of a row is that the eye can read across it. */
.caption {
  display: -webkit-box;
  min-height: calc(3em * 1.55);
  font-size: 0.9rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow: hidden;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}

/* Only appears where the clamp actually cut something. It opens the drop in
   the sheet rather than growing the card: expanding one card in a row where
   every card shares a height would stretch all of them to read one. */
.caption-more {
  align-self: flex-end;
  margin-top: 3px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--fg-3);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}

.caption-more:hover { color: var(--brand-strong); }
.caption-more.is-hidden { visibility: hidden; }

/* Every card in the row is as tall as the tallest, so one drop that came back
   with seven hashtags sets the height for all of them. Two lines here, and the
   rest stay in the copy itself — the caption is never cut, because that is the
   text being approved. */
/* One line, always — reserved whether or not this drop has tags. The caption
   above already holds three lines for the same reason: a card without tags used
   to pull everything under it up by a line, so no two cards in the row agreed
   on where the caption ended and the actions began. */
.tags {
  display: -webkit-box;
  min-height: 1.3em;
  margin-top: 6px;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--brand-strong);
  overflow: hidden;
  overflow-wrap: anywhere;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.caption-empty { color: var(--fg-3); }

/* The script matters when you're deciding, not while you're scrolling, so it
   stays folded until asked for. */
.vo {
  margin-top: 10px;
  border-top: 1px solid var(--line-soft);
}

/* The voiceover and the activity log, side by side while both are shut. Two
   full-width bars for two things you open occasionally is a row of chrome per
   card, and the card is 165px wide on a phone. Opening one gives it the whole
   width back, so the script and the log still read as paragraphs. */
.drop-folds {
  display: flex;
  flex-wrap: wrap;
  column-gap: 10px;
}

/* Full width each on a phone, where "Voiceover" plus its icon and chevron is
   already most of a 165px card and two of them would squeeze both to nothing.
   They only pair up once there is room for the pair. */
.drop-folds > .vo { flex: 1 1 100%; min-width: 0; }

@media (min-width: 561px) {
  .drop-folds > .vo { flex-basis: 0; }
  .drop-folds > .vo[open] { flex-basis: 100%; }
  .drop-folds > .vo summary { padding-right: 0; }
}

/* The disc says "click to play"; while hover is already playing it, it is in
   the way of the frame being judged. */
.drop-media.is-playing .drop-play-disc { opacity: 0; }

.drop-compare {
  position: absolute;
  top: 8px;
  right: 44px;
  z-index: 2;
  pointer-events: auto;
  padding: 6px 10px;
  border: 0;
  border-radius: var(--r-pill);
  background: rgba(10, 14, 24, 0.5);
  color: rgba(255, 255, 255, 0.88);
  font-size: 0.72rem;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
}

.drop-media.is-original .drop-compare { background: var(--brand); }

.picks {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin: 8px 0 2px;
}

.pick-thumb {
  width: 52px;
  height: 52px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--surface-2);
  cursor: pointer;
}

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

.pick-thumb.is-on { border-color: var(--brand); cursor: default; }

.picks-hint { font-size: 0.72rem; color: var(--fg-3); }

.take-video {
  display: block;
  width: 100%;
  max-height: 260px;
  margin: 8px 0;
  border-radius: var(--r-sm);
  background: #0d1117;
}

.take .btn { margin-top: 6px; }

.vo[open] {
  border-radius: var(--r-sm);
  border-top-color: transparent;
  background: var(--surface-2);
}

.vo summary {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 2px;
  list-style: none;
  cursor: pointer;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--fg-3);
}

.vo[open] summary { padding: 9px 11px; }

.vo summary::-webkit-details-marker { display: none; }
.vo summary:hover { color: var(--fg-2); }

.vo-chevron {
  margin-left: auto;
  transition: transform 0.18s var(--ease);
}

.vo[open] .vo-chevron { transform: rotate(180deg); }

/* The activity fold. A timestamp then what happened — the times line up down
   the left so the gaps between them are readable at a glance. */
.drop-log {
  margin: 0;
  padding: 0 11px 11px;
  list-style: none;
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--fg-3);
}

.drop-log li {
  display: flex;
  gap: 8px;
  padding: 3px 0;
  border-top: 1px solid var(--line-soft);
}

.drop-log li:first-child { border-top: 0; }

.drop-log b {
  flex: none;
  width: 10.5em;
  font-weight: 600;
  color: var(--fg-2);
}

.vo p {
  padding: 0 11px 11px;
  margin-top: -2px;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--fg-2);
}

.drop-notes {
  margin-top: 10px;
  font-size: 0.82rem;
  color: var(--fg-3);
}

.drop-notes b {
  color: var(--fg-2);
  font-weight: 600;
}

.drop-notes.is-warning {
  padding-left: 8px;
  border-left: 2px solid #fb923c;
  color: var(--fg-2);
}

/* The notes fold. Their count is what belongs on a card being scanned; the
   prose behind it is what belongs to a card being decided on. A warning in
   there colours the row, so nothing that needs looking at is folded away
   silently. */
.notes-fold summary { font-weight: 600; }
.notes-fold.is-warning summary { color: var(--warn); }
.notes-fold.is-warning[open] { background: var(--warn-wash); }

.notes-body { padding: 0 11px 10px; }
.notes-body > .drop-notes:first-child { margin-top: 0; }

/* One loud button per drop. Scheduling and re-rolling are alternatives to it,
   not rivals, so they sit underneath at half the volume. */
/* `minmax(0, 1fr)`, not the default `auto`: a grid column sizes to its widest
   item's min-content, and three nowrap labels in the row below came to 230px
   inside a 139px card. The column took that width, `.btn-block` filled it, and
   "Post now" ran out under the card's own overflow clip. */
.drop-actions {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
  margin-top: 14px;
}

/* One row, always, whether a drop carries two of these or three: the primary
   button above sits on whatever this row leaves it, so a row that grew a line
   would drop "Post now" out of step with the card beside it. The labels are
   short enough to hold a third of a card without breaking, and each keeps its
   full phrasing in a title. */
/* It wraps rather than being clipped. Two up on a phone the row is 139px wide
   and "Edit · Schedule · Re-roll 0/2" is not, so the choice was never one line
   or two — it was a second line or a third action nobody can read. */
.drop-actions-minor {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  min-width: 0;
}

/* Sized to their labels, not to an equal share: thirds of a narrow card give
   "Re-roll 0/2" the same room as "Edit" and clip it. Each takes what its text
   needs and they split what is left over. */
.drop-actions-minor .btn {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.52em 0.35em;
  gap: 0.32em;
  font-size: 0.76rem;
  white-space: nowrap;
}

/* The icon is the part that survives a narrow card; the label may ellipsise. */
.drop-actions-minor .btn svg { flex: none; }

.stamp {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 14px;
  padding: 11px;
  border-radius: var(--r-sm);
  background: var(--good-wash);
  color: var(--good);
  font-size: 0.85rem;
  font-weight: 600;
}

/* Two up on a phone this row is 139px wide, and it used to squeeze its own
   tick to nothing and break "View post" over three lines. The tick and the
   link keep their width; the label is the part that gives, wrapping inside
   itself rather than sending the tick to a line of its own. */
.stamp svg { flex: none; }
.stamp-text { min-width: 0; }

/* "Scheduled" is not the same news as "posted", and a card that says both in
   the same green makes the owner read the words to tell them apart. */
.stamp-plan {
  background: var(--brand-wash);
  color: var(--brand);
}

.stamp-wait {
  background: var(--gold-wash);
  color: var(--gold);
}

.stamp-bad {
  background: var(--bad-wash);
  color: var(--bad);
}

/* Stand-in for copy that is still being written, sized to the three lines and
   the tag row the caption will occupy. */
.copy-skeleton {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding-top: 3px;
}

.copy-skeleton span {
  height: 9px;
  border-radius: 4px;
  background: linear-gradient(
    90deg,
    var(--surface-2) 0%,
    var(--line) 42%,
    var(--surface-2) 84%
  );
  background-size: 240% 100%;
  animation: kd-shimmer 1.9s linear infinite;
}

.copy-skeleton span:nth-child(2) { width: 92%; }
.copy-skeleton span:nth-child(3) { width: 64%; }

.copy-skeleton .sk-tags {
  width: 46%;
  height: 8px;
  margin-top: 7px;
}

@keyframes kd-shimmer {
  from { background-position: 160% 0; }
  to   { background-position: -60% 0; }
}

/* Quieter than the rest: it reports that nothing is wrong, not that anything
   happened. The card's news is still the stage above it. */
.stamp-live {
  background: var(--surface-2);
  color: var(--fg-3, var(--fg-2));
  font-weight: 500;
}

.stamp-spin {
  width: 12px;
  height: 12px;
  flex: none;
  border-radius: 50%;
  border: 2px solid var(--line-strong);
  border-top-color: var(--brand);
  animation: kd-spin 0.9s linear infinite;
}

@keyframes kd-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .stamp-spin,
  .drop-media.is-pipeline::before,
  .copy-skeleton span,
  .pipe-step.now i { animation: none; }
}

.stamp-link {
  color: var(--good);
  text-decoration: underline;
  margin-left: 6px;
  font-weight: 700;
  white-space: nowrap;
  transition: opacity 0.15s;
}

.stamp-link:hover {
  opacity: 0.8;
}

.failure {
  margin-top: 14px;
  padding: 11px 12px;
  border-radius: var(--r-sm);
  background: var(--bad-wash);
  color: var(--bad);
  font-size: 0.83rem;
  line-height: 1.5;
}

.failure b {
  display: block;
  margin-bottom: 2px;
}

/* Two lines of the provider's own words. Printed whole, a 400 from Instagram
   ran four hundred pixels down a card 165px wide — and dragged the card beside
   it to the same height, since a grid row is as tall as its tallest cell. */
.failure-why {
  display: -webkit-box;
  overflow: hidden;
  overflow-wrap: anywhere;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.failure.is-open .failure-why {
  display: block;
  -webkit-line-clamp: unset;
}

.failure-more {
  display: block;
  margin-top: 4px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  text-decoration: underline;
  cursor: pointer;
}

/* ---------- Pipeline progress ---------- */

/* A drop mid-render is the one card that cannot be acted on, so the stage
   answers the two questions worth asking about it: what is happening now, and
   is it still moving. A dark panel three cards wide is a lot of nothing, so a
   slow sweep keeps it alive between six-second polls. */
.drop-media.is-pipeline {
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(221, 90, 31, 0.16), transparent 62%),
    #0d1117;
}

.drop-media.is-pipeline::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    115deg,
    transparent 38%,
    rgba(255, 255, 255, 0.045) 50%,
    transparent 62%
  );
  background-size: 260% 100%;
  animation: kd-sweep 3.4s linear infinite;
  pointer-events: none;
}

@keyframes kd-sweep {
  from { background-position: 160% 0; }
  to   { background-position: -60% 0; }
}

/* The owner's upload under the steps: present, but not competing with them. */
.pipe-source {
  opacity: 0.38;
  filter: blur(3px) saturate(0.85);
  transform: scale(1.06);
}

.drop-hook {
  display: flex;
  gap: 8px;
  align-items: baseline;
  margin-bottom: 6px;
  font-size: 0.92rem;
  font-weight: 650;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.drop-hook > span:first-child {
  flex: none;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--brand);
}

/* Two lines of it. A hook is a line of on-screen text, but nothing enforces
   that, and a long one pushed the caption it introduces off the card. */
.drop-hook-text {
  display: -webkit-box;
  min-width: 0;
  overflow: hidden;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.vo-audio {
  display: block;
  width: calc(100% - 22px);
  height: 32px;
  margin: 2px 11px 8px;
}

.pipeline {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 230px;
  padding: 18px;
}

.pipe-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 14px;
}

.pipe-head b {
  font-size: 0.92rem;
  font-weight: 620;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: #f4f6fa;
}

.pipe-count {
  flex: none;
  font-size: 0.68rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: rgba(238, 241, 247, 0.42);
}

/* The rail is what makes four dots read as one run rather than four unrelated
   lines: it is drawn behind the markers and lit as far as the work has got. */
.pipe-steps {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 11px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pipe-steps::before {
  content: "";
  position: absolute;
  top: 9px;
  bottom: 9px;
  left: 6px;
  width: 1.5px;
  border-radius: 1px;
  background: rgba(238, 241, 247, 0.14);
}

.pipe-step {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.8rem;
  line-height: 1.3;
  color: rgba(238, 241, 247, 0.38);
}

.pipe-step i {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 13px;
  height: 13px;
  flex: none;
  border-radius: 50%;
  background: #171d27;
  box-shadow: inset 0 0 0 1.5px rgba(238, 241, 247, 0.2);
}

.pipe-step i svg {
  width: 8px;
  height: 8px;
}

.pipe-step.done {
  color: rgba(238, 241, 247, 0.6);
}

.pipe-step.done i {
  background: var(--good);
  color: #06210f;
  box-shadow: none;
}

.pipe-step.now {
  color: #eef1f7;
  font-weight: 560;
}

.pipe-step.now i {
  background: var(--brand);
  box-shadow: 0 0 0 4px rgba(221, 90, 31, 0.2);
  animation: kd-throb 1.6s var(--ease) infinite;
}

@keyframes kd-throb {
  0%, 100% { box-shadow: 0 0 0 3px rgba(221, 90, 31, 0.28); }
  50%      { box-shadow: 0 0 0 7px rgba(221, 90, 31, 0.05); }
}

/* Two up on a phone the stage is 163x203 and this panel was 282 tall: the
   current step's name spilled out over the top edge and "Ready for you" was
   cut off the bottom, on the one card the owner is actually watching. The head
   names the step the list below already lights, so at this size the list is
   the whole panel. */
@media (max-width: 560px) {
  .pipeline { padding: 10px; }
  .pipe-head { display: none; }
  .pipe-steps { gap: 8px; }
  .pipe-step { font-size: 0.72rem; gap: 8px; }
  .pipe-foot {
    flex-wrap: wrap;
    margin-top: 10px;
    padding-top: 8px;
    font-size: 0.66rem;
  }
}

/* Elapsed and remaining, because a step name alone cannot tell the owner
   whether four minutes on "Rendering video" is normal or wedged. */
.pipe-foot {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 14px;
  padding-top: 11px;
  border-top: 1px solid rgba(238, 241, 247, 0.1);
  font-size: 0.7rem;
  font-variant-numeric: tabular-nums;
  color: rgba(238, 241, 247, 0.44);
}

.pipe-slow { color: #f0b47a; }

/* Progress without a word of it: readable from across the grid, and the part
   that visibly moves when the poll comes back. */
.pipe-bar {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: 3px;
  background: rgba(238, 241, 247, 0.1);
}

.pipe-bar span {
  display: block;
  height: 100%;
  border-radius: 0 2px 2px 0;
  background: linear-gradient(90deg, var(--brand), #ffb37a);
  box-shadow: 0 0 12px rgba(221, 90, 31, 0.55);
  transition: width 0.6s var(--ease);
}

/* ---------- Stopped, with nothing rendered ----------
   Deliberately still. The pipeline animates, and animation reads as progress;
   a drop the allowance already killed must not borrow that. */

.drop-media.is-stalled { background: var(--surface-2); }

.stalled {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  max-width: 210px;
  padding: 18px;
  text-align: center;
}

.stalled-icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  margin-bottom: 3px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--fg-3, var(--fg-2));
}

.stalled-icon svg { width: 18px; height: 18px; }

.stalled b {
  font-size: 0.86rem;
  font-weight: 620;
  color: var(--fg-1);
}

.stalled p {
  margin: 0;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--fg-3, var(--fg-2));
}

/* ---------- Empty & loading ---------- */

.empty {
  padding: 34px 24px 30px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  text-align: center;
}

.empty-art {
  width: 84px;
  height: 84px;
  margin: 0 auto 16px;
  border-radius: 50%;
  object-fit: cover;
  filter: grayscale(0.35);
  box-shadow: var(--shadow-2);
}

.empty h3 {
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 600;
  margin-bottom: 5px;
}

.empty p {
  /* 32ch broke this sentence into five short lines down the middle of a panel
     three times that wide, which reads as a column that failed to fill rather
     than as a centred paragraph. */
  max-width: 46ch;
  margin: 0 auto 18px;
  font-size: 0.86rem;
  color: var(--fg-3);
  line-height: 1.55;
  text-wrap: balance;
}

.skeleton {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  overflow: hidden;
}

.skeleton-media {
  aspect-ratio: 9 / 16;
  max-height: 300px;
}

.skeleton-line {
  height: 11px;
  margin: 12px 14px;
  border-radius: 4px;
}

.skeleton-line.short {
  width: 45%;
}

.skeleton-media,
.skeleton-line {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: kd-shimmer 1.3s ease-in-out infinite;
}

@keyframes kd-shimmer {
  from { background-position: 100% 0; }
  to { background-position: 0 0; }
}

/* ---------- Social list ---------- */

.social-list {
  margin-bottom: 12px;
}

.social-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 0;
  border-bottom: 1px solid var(--line-soft);
}

.social-row:last-child {
  border-bottom: none;
}

.social-row svg,
.social-row img {
  width: 19px;
  height: 19px;
  flex: none;
  color: var(--fg-2);
}

/* An unlinked row ends in a Connect button where a linked one ends in a pill. */
.social-row .btn {
  margin-left: auto;
}

.social-row b {
  display: block;
  font-size: 0.89rem;
  font-weight: 600;
}

.social-row span {
  display: block;
  font-size: 0.79rem;
  color: var(--fg-3);
}

.social-row .pill {
  margin-left: auto;
}

.none-yet {
  padding: 4px 0 12px;
  font-size: 0.85rem;
  color: var(--fg-3);
  line-height: 1.5;
}

/* When this sentence is the whole body of a section it has to look like an
   empty state, not like a paragraph whose card failed to render — Queue, Post
   log, Library and the two Memory grids all opened as one grey line floating
   in the page. Scoped to :only-child so the same class stays a quiet inline
   note wherever it sits underneath real content. */
.none-yet:only-child,
#formatEmpty {
  /* Several of the containers it lands in are flex rows, where a block-level
     panel is only as wide as its sentence and reads as a stray tooltip. */
  width: 100%;
  flex: 1 1 100%;
  padding: 26px 20px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  text-align: center;
  text-wrap: balance;
}

/* The pickers it lands in are grids, so it has to claim the whole row rather
   than sit in the first cell as a one-column panel. */
.voice-picker > .none-yet,
.intent-mix-grid > .none-yet,
/* ---------- Instagram connect ---------- */

.connect-note {
  padding: 4px 0 14px;
  font-size: 0.85rem;
  color: var(--fg-3);
  line-height: 1.5;
}

.connect-note b {
  color: var(--fg-2, var(--fg-3));
  font-weight: 600;
}

.connect-status {
  margin-top: 12px;
  font-size: 0.85rem;
  color: var(--fg-3);
  line-height: 1.5;
  text-align: center;
}


.connect-status a {
  color: inherit;
  text-decoration: underline;
}

/* ---------- Sheet actions ---------- */

.sheet-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 9px;
}

/* ---------- Tab bar ---------- */

.tab-bar {
  position: fixed;
  bottom: 0;
  z-index: 50;
  display: flex;
  flex-wrap: wrap;
  width: 100%;
  max-width: 520px;
  padding: 8px 0 max(10px, env(safe-area-inset-bottom));
  background: var(--veil);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid var(--line);
}

.tab {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 5px 2px;
  border: none;
  background: none;
  color: var(--fg-3);
  /* Four destinations and More across a 375px phone leaves ~72px each, which
     fits "Schedule" at a readable size. Still allowed to ellipsis rather than
     wrap, because a two-line label makes the bar jump height. */
  font-size: 0.72rem;
  font-weight: 500;
  cursor: pointer;
  transition: color 0.15s var(--ease);
}

.tab span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tab svg {
  width: 20px;
  height: 20px;
}

.tab.is-active {
  color: var(--brand);
}

/* How many queued posts are still waiting on a yes. The queue is one tab of
   seven, so without this the only way to learn something needs approving is to
   go and look. On the phone bar it rides the icon; on the rail there is room
   for it at the end of the row. */
.tab-badge {
  position: absolute;
  top: 1px;
  left: 50%;
  margin-left: 4px;
  min-width: 16px;
  padding: 0 4px;
  border-radius: var(--r-pill);
  background: var(--brand);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

.tab-badge[hidden] {
  display: none;
}

/* ---------- Drops sub-nav ----------
   Two subsections of one tab, so they belong to the nav rather than to the
   page. On the phone bar there is no room to nest, so they take a row above
   the seven tabs; on the rail they indent under Drops as a nested list.

   Phone: a segmented control on a sunken track, deliberately not the outlined
   chip used for the feed filters a few pixels below it. Two rows of identical
   chips doing unrelated jobs is the sort of thing that makes people stop
   trusting what a control does. */

.tab-subnav {
  display: flex;
  gap: 2px;
  order: -1;
  width: calc(100% - 24px);
  margin: 0 12px 9px;
  padding: 3px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
}

.tab-subnav[hidden] { display: none; }

.subtab {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 0.77rem;
  font-weight: 550;
  padding: 6px 10px;
  border: 0;
  border-radius: var(--r-pill);
  background: none;
  color: var(--fg-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  transition: background-color 0.15s var(--ease), color 0.15s var(--ease),
              box-shadow 0.15s var(--ease);
}

.subtab.is-active {
  background: var(--surface);
  color: var(--fg);
  font-weight: 650;
  box-shadow: var(--shadow-1);
}

.subtab:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.subtab-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  padding: 0 5px;
  border-radius: var(--r-pill);
  font-size: 0.68rem;
  font-weight: 700;
  line-height: 1;
  background: var(--surface-2);
  color: var(--fg-2);
}

.subtab.is-active .subtab-badge {
  background: var(--brand-wash);
  color: var(--brand);
}

.subtab-badge.pill-live {
  background: var(--warn-wash);
  color: var(--warn);
}

.subtab-badge[hidden] {
  display: none;
}

@media (min-width: 560px) and (max-width: 899px) {
  .app {
    box-shadow: var(--shadow-2);
  }
}

/* ---------- Desktop: the same app, laid out for a screen ----------
   Below 900px this stays what it was built as — a phone app with its thumbs at
   the bottom of the screen, used standing at the pass. Above it, the bottom bar
   becomes a left rail and the column opens up, because at a desk seven tabs
   crushed into a strip is a waste of a monitor. Nothing moves in the markup
   between the two; only where the nav sits and how wide the work gets. */
@media (min-width: 900px) {
  body { background: var(--bg); }

  /* Fills the screen. A centred column left a few inches of dead background
     down each side of a wide monitor, which reads as the page having failed to
     load rather than as a margin. The rail sits flush left and the work runs
     from it, the way an app on a desktop is expected to. */
  .app {
    max-width: none;
    padding-bottom: 0;
    border-inline: 0;
  }

  .app-bar { padding-inline: 24px; }

  /* The rail is only one viewport tall — it is sticky, not a column — so on
     any view longer than the screen its own background ran out and left a seam
     across the page. The strip is painted here instead, on the element that is
     as tall as the content, and the rail sits on top of it. */
  .dashboard-section {
    display: flex;
    align-items: flex-start;
    min-height: calc(100vh - var(--bar-h, 59px));
    background: linear-gradient(
      to right,
      var(--surface-2) 0 236px,
      var(--line) 236px 237px,
      transparent 237px
    );
  }

  .tab-bar {
    position: sticky;
    top: var(--bar-h, 59px);
    z-index: 30;
    flex: none;
    flex-direction: column;
    gap: 2px;
    width: 236px;
    max-width: none;
    height: calc(100vh - var(--bar-h, 59px));
    padding: 16px 12px;
    background: var(--surface-2);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-top: 0;
    border-right: 1px solid var(--line);
    /* Seven destinations plus an open subnav is taller than a laptop window
       with devtools open, and the rail is a fixed 100vh — the bottom items
       simply could not be reached. The page's own scroll does not help: the
       rail is sticky and scrolls with nothing. */
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--line-strong) transparent;
  }

  .tab-bar::-webkit-scrollbar { width: 8px; }
  .tab-bar::-webkit-scrollbar-thumb {
    border: 2px solid transparent;
    border-radius: var(--r-pill);
    background: var(--line-strong);
    background-clip: content-box;
  }
  .tab-bar::-webkit-scrollbar-track { background: transparent; }

  .tab {
    flex: none;
    flex-direction: row;
    justify-content: flex-start;
    gap: 11px;
    padding: 9px 12px;
    border-radius: var(--r-sm);
    font-size: 0.86rem;
    font-weight: 500;
  }

  .tab span {
    max-width: none;
    overflow: visible;
  }

  .tab svg { width: 18px; height: 18px; }

  .tab-badge {
    position: static;
    margin-left: auto;
  }

  /* Indented to where the tab labels start, so the nesting is read as nesting
     rather than as two more top-level destinations. */
  .tab-subnav {
    flex-direction: column;
    gap: 0;
    order: 0;
    width: auto;
    margin: 4px 0 6px 21px;
    padding: 0;
    border-radius: 0;
    background: none;
  }

  /* The guide line is built out of the items themselves — each owns a segment
     of it, and the active one lights its own. The filled pill this replaced sat
     directly under the parent tab's filled pill, so the rail read as two cards
     stacked rather than as a heading with children. */
  .subtab {
    flex: none;
    text-align: left;
    padding: 7px 10px 7px 15px;
    border-left: 2px solid var(--line);
    border-radius: 0;
    background: none;
    color: var(--fg-3);
    font-size: 0.81rem;
    font-weight: 500;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }

  /* A wash, not the filled pill: the row needs a visible target — nothing but
     a 2px line moved before — without restaging as a second card under the
     parent tab's. */
  .subtab:hover {
    background: var(--surface-3);
    border-left-color: var(--line-strong);
    color: var(--fg);
  }

  .subtab.is-active {
    background: var(--brand-wash);
    border-left-color: var(--brand);
    color: var(--brand);
    font-weight: 600;
    box-shadow: none;
  }

  .subtab:hover,
  .subtab.is-active {
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
  }

  .subtab:focus-visible {
    outline-offset: -2px;
  }

  .tab:hover { background: var(--surface-3); color: var(--fg); }

  .tab.is-active {
    background: var(--surface);
    color: var(--brand);
    font-weight: 600;
    box-shadow: var(--shadow-1);
  }

  .workspace {
    flex: 1;
    min-width: 0;
  }

  /* The strip spans the window, but the name inside it lines up with the left
     edge of the work below rather than sitting alone against the rail while
     the column starts a few hundred pixels further in. */
  .place-bar {
    padding-block: 12px;
    padding-inline: max(32px, calc((100% - var(--col)) / 2));
  }

  /* Which restaurant you are on is a label, not a field. Left stretching on
     flex:1 the select ran the whole window and parked its chevron a foot from
     the name on a wide monitor, reading as a broken input rather than as the
     one line of context it is. */
  .place-select { flex: 0 1 auto; }

  /* One column, centred, for every view. Capping the children instead pinned
     the work against the rail and left the rest of the monitor empty to its
     right — 840px of bare background at 1920, which reads as a page that
     failed to load rather than as a margin. Centring the column spends the
     same space as two even margins, and the column itself keeps the measure
     forms and prose need. Everything on a view now shares one left and one
     right edge: header, cards, feed and composer all line up. */
  .view {
    max-width: calc(var(--col) + 64px);
    margin-inline: auto;
    padding: 24px 32px 72px;
  }
}


/* ---------- Password reset ----------
   A link, not a button: it sits under the primary action and must not read as
   a second one competing with it. */

.auth-forgot {
  display: block;
  width: 100%;
  margin-top: 12px;
  text-align: center;
  font-size: 0.85rem;
}

.auth-step-note {
  margin: 0 0 16px;
  text-align: center;
}

/* ---------- First run ----------
   Three rows, and then it is gone. Deliberately not a card like everything
   else on the page: it is scaffolding, and scaffolding that looks permanent
   is the kind people learn to scroll past. */

.setup-card {
  margin-bottom: 16px;
  padding: 13px 14px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

.setup-card[hidden] { display: none; }

.setup-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.setup-head b { font-size: 0.92rem; font-weight: 650; }

.setup-head span { font-size: 0.78rem; color: var(--fg-3); }

.setup-steps { display: grid; gap: 6px; }

.setup-step {
  display: grid;
  grid-template-columns: 22px 1fr auto;
  align-items: center;
  gap: 11px;
  width: 100%;
  padding: 10px 11px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease);
}

.setup-step:hover:not(:disabled) { border-color: var(--brand); }

.setup-step:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.setup-step:disabled { cursor: default; opacity: 0.62; }

.setup-tick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--fg-2);
  font-size: 0.75rem;
  font-weight: 700;
}

.setup-tick svg { width: 12px; height: 12px; }

.setup-step.is-done .setup-tick { background: var(--good-wash); color: var(--good); }

.setup-text b {
  display: block;
  /* The row's own colour is the muted explanation; the step name is not. */
  color: var(--fg);
  font-size: 0.9rem;
  font-weight: 650;
}

.setup-text {
  display: block;
  font-size: 0.79rem;
  line-height: 1.45;
  color: var(--fg-3);
}

.setup-step.is-done .setup-text b { text-decoration: line-through; }

.setup-go { color: var(--brand); font-weight: 700; }

/* ---------- The phone bar's fifth destination ----------
   Seven tabs across 390px gave each one 55px, which truncated "Schedule" and
   left no gap between targets. Three go behind More — insights and activity
   are read weekly, settings is set once — leaving Drops, Schedule, Memory and
   Studio, the four you open to do something. The rail on a desktop has room
   for all seven, so it keeps them and drops More. */

.tab-more { display: flex; }

.tab[data-secondary] { display: none; }

@media (min-width: 900px) {
  .tab-more { display: none; }
  .tab[data-secondary] { display: flex; }
}

.more-list {
  display: grid;
  gap: 6px;
}

.more-row {
  display: grid;
  grid-template-columns: 22px 1fr;
  align-items: center;
  gap: 13px;
  width: 100%;
  padding: 13px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease);
}

.more-row:hover { border-color: var(--line-strong); background: var(--surface-2); }

.more-row:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.more-row svg { width: 22px; height: 22px; color: var(--fg-2); }

.more-row b {
  display: block;
  color: var(--fg);
  font-size: 0.95rem;
  font-weight: 650;
}

.more-row span {
  display: block;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--fg-3);
}

/* ---------- Intel: before the first scan ----------
   Seven cards each printing a variant of "run an AI Web Scan" is one sentence
   set seven times, above a page with a Scan button already in its header.
   Until a scan has run, the page is that sentence and that button. */

/* Descendants, not children: the cards moved into subsections, and the empty
   state has to keep covering them. */
#viewRestaurant.is-unscanned .card:not(.own-card),
#viewRestaurant.is-unscanned > .claim-banner,
#viewRestaurant.is-unscanned > .claim-verified-note {
  display: none;
}

/* With no scan there is no subnav either, so the sections stop being sections
   and hand their contents back to the page — which the rule above has already
   emptied down to the owner's own photos, exactly as before they existed. */
#viewRestaurant.is-unscanned .tab-section { display: contents; }

#viewRestaurant:not(.is-unscanned) > .intel-first-run {
  display: none;
}

/* Your restaurant: the owner's own photos and clips, look-only. */
.own-source + .own-source {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.own-source h3 {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 12px 0 0;
  font-size: 0.98rem;
}

.own-card h4 {
  margin: 14px 0 0;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--fg-3);
}

/* Centred by hand: the body is a flex column, and Safari lets that leak
   into a modal dialog's placement, which put the picture hard against the
   left edge. */
.own-lightbox {
  position: fixed;
  inset: 0;
  margin: auto;
  width: fit-content;
  height: fit-content;
  padding: 0;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  max-width: 92vw;
  max-height: 92vh;
  cursor: zoom-out;
}

.own-lightbox::backdrop {
  background: rgba(0, 0, 0, 0.7);
}

.own-lightbox img {
  display: block;
  max-width: 92vw;
  max-height: 92vh;
  object-fit: contain;
  border-radius: var(--r-sm);
}

/* Over the picture, at its edges. The dialog is fit-content around the image,
   so these sit on the slide rather than floating in the backdrop. */
.lightbox-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}
.lightbox-arrow:hover { background: rgba(0, 0, 0, 0.8); }
.lightbox-arrow.is-prev { left: 10px; }
.lightbox-arrow.is-next { right: 10px; }

.lightbox-count {
  position: absolute;
  left: 50%;
  bottom: 10px;
  transform: translateX(-50%);
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
}

.own-clips {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
  margin: 10px 0 16px;
}

.own-clips video {
  width: 100%;
  aspect-ratio: 9 / 16;
  object-fit: cover;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}

.own-more {
  margin: -8px 0 14px;
}

.staff-role {
  color: var(--fg-3);
  font-size: 0.8rem;
}

.intel-first-run .btn-quiet {
  margin-top: 10px;
}

/* ---------- Voiceover picker ----------
   A wall of cards, not a column of rows: the markup is a flat list of options
   with heading paragraphs between them, and the grid is what makes it a
   gallery. Done in CSS alone on purpose — selection, preview playback and the
   clone/persona switch are all wired to this DOM.

   The play control is the card's subject, so it gets the size and the brand,
   and the rest of the card stays one hit target for choosing the voice. */

.voice-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(214px, 1fr));
  gap: 10px;
  margin-top: 7px;
}

/* The pace pills and the try-out line sit above the grid, full width. */
.voice-filters { margin-top: 8px; margin-bottom: 10px; }

#voiceWho:not(:empty) { margin-bottom: 18px; padding-bottom: 16px; border-bottom: 1px solid var(--line-soft); }
#voiceWho .voice-own { margin-top: 0; }

.voice-try { margin-top: 18px; }
.voice-try .input { margin-bottom: 4px; }

/* The folded tail of the ranked list. A link, not a card. */
.voice-more {
  grid-column: 1 / -1;
  justify-self: start;
  padding: 6px 0;
  border: 0;
  background: none;
  color: var(--brand);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.voice-more:hover { text-decoration: underline; }

/* Pace and accent, in the corner where the check lives until it is chosen. */
.voice-option .voice-tags { display: block; margin-top: 6px; }

.voice-option .voice-tag,
.voice-tag {
  display: inline-block;
  margin-right: 5px;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--fg-3);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}

.voice-option.is-on .voice-tag { background: var(--brand-wash); color: var(--brand); }

/* Headings and the narrator switch are dividers, not cards. */
.voice-picker > .voice-group,
.voice-picker > .voice-own,
.voice-picker > .narrate-switch {
  grid-column: 1 / -1;
}

.voice-option {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    "play  .     check"
    "text  text  text";
  grid-template-rows: auto 1fr;
  align-items: start;
  gap: 11px 10px;
  padding: 13px 13px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease),
              box-shadow 0.15s var(--ease);
}

.voice-option > .voice-text  { grid-area: text; align-self: stretch; }
.voice-option > .voice-check { grid-area: check; }

/* One thing moves on hover. Lifting the card and darkening its border at the
   same time reads as two states on a grid this dense. */
.voice-option:hover {
  box-shadow: var(--shadow-1);
}

/* `.voice-option span` below sets every span in a row to block; the badge is
   the one span that must stay inline or it stretches into a full-width bar. */
.voice-option .voice-badge,
.voice-badge {
  display: inline-block;
  width: auto;
  margin-top: 0;
  margin-left: 7px;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  background: var(--brand-wash);
  color: var(--brand-strong);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  vertical-align: 1px;
}

.voice-why {
  display: block;
  margin-top: 3px;
  font-size: 0.74rem;
  color: var(--fg-2);
  line-height: 1.45;
}

/* The server writes reasons mid-sentence ("your vibe profile describes…");
   shown on their own they should read as sentences. */
.voice-why::first-letter {
  text-transform: uppercase;
}

.voice-group {
  margin: 12px 2px 1px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.voice-picker > .voice-group:first-child {
  margin-top: 4px;
}

/* Whose voice speaks, not one of the styles to pick between. It used to carry
   the identical border and wash as `.is-on` below, so with a persona selected
   two rows lit up brand-orange and the picker read as two live selections.
   A neutral surface with a brand edge keeps it tied to the brand while making
   it plainly a different kind of thing from the cards underneath. */
.voice-own {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 12px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}

.voice-own .voice-text {
  flex: 1;
  min-width: 0;
}

.voice-own b {
  display: block;
  font-size: 0.9rem;
  font-weight: 600;
}

.voice-own > .voice-text > span {
  display: block;
  margin-top: 1px;
  font-size: 0.79rem;
  color: var(--fg-3);
  line-height: 1.45;
}

/* Parked: still enrolled, not speaking. Dimmed rather than hidden, because the
   way back has to stay visible. */
.voice-own.is-idle {
  border-left-color: var(--line-strong);
}

.voice-own.is-idle .voice-badge {
  background: var(--surface-3);
  color: var(--fg-3);
}

.voice-own.is-idle b,
.voice-own.is-idle .voice-play {
  opacity: 0.75;
}

/* ---------- Narrator switch ---------- */

.narrate-switch {
  display: grid;
  gap: 7px;
  margin: 9px 0 2px;
}

.narrate-label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.narrate-opts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 5px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}

.narrate-opt {
  padding: 8px 6px;
  border: none;
  border-radius: calc(var(--r-sm) - 4px);
  background: transparent;
  color: var(--fg-2);
  font: inherit;
  font-size: 0.81rem;
  font-weight: 600;
  line-height: 1.3;
  cursor: pointer;
  transition: background-color 0.15s var(--ease), color 0.15s var(--ease);
}

.narrate-opt:hover {
  color: var(--fg-1);
}

.narrate-opt.is-on {
  background: var(--surface);
  color: var(--brand-strong);
  box-shadow: var(--shadow-1);
}

[data-theme="dark"] .narrate-opt.is-on {
  color: var(--brand);
}

.narrate-means {
  display: block;
  width: 100%;
  color: var(--fg-2);
  font-size: 0.8rem;
  line-height: 1.45;
}


.voice-option.is-off {
  opacity: 0.55;
}

.voice-note {
  display: block;
  margin-top: 3px;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--warn);
}

.voice-option.is-on {
  border-color: var(--brand);
  background: var(--brand-wash);
}

/* The tick that says "this is the one you're using" — a tinted background
   alone is easy to miss on a card that is mostly its play button. Reads as an
   empty slot until it is the chosen one. Scoped past the `.voice-option span`
   block/colour rules below. */
.voice-option .voice-check {
  width: 20px;
  height: 20px;
  margin: 0;
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  border: 1.5px solid var(--line-strong);
  color: transparent;
  transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease);
}

.voice-check svg {
  width: 11px;
  height: 11px;
}

.voice-option.is-on .voice-check {
  border-color: var(--brand);
  background: var(--brand);
  color: #fff;
}

/* A column, so the tag row and the reason under it can be pushed to the
   bottom of the card. Left flowing, they sat wherever the description above
   happened to end, and a row of three cards showed its pace pills at three
   different heights — the grid stretches the cards to the same height, so the
   only thing that made the row look ragged was the content inside them. */
.voice-option .voice-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.voice-option .voice-text > span:not([class]) {
  flex: 1 0 auto;
}

.voice-option b {
  display: block;
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.25;
}

.voice-option span {
  display: block;
  margin-top: 1px;
  font-size: 0.78rem;
  color: var(--fg-3);
  line-height: 1.45;
}

/* Bigger than the plain control below: on a card it is the thing you reach
   for, so it carries the brand rather than sitting in a grey pill. */
.voice-option > .voice-play {
  grid-area: play;
  width: 40px;
  height: 40px;
  border-color: transparent;
  background: var(--brand-wash);
  color: var(--brand-strong);
}

.voice-option > .voice-play svg { width: 15px; height: 15px; }

.voice-option > .voice-play:hover,
.voice-option.is-on > .voice-play {
  background: var(--brand);
  color: #fff;
}

/* On a phone the cards carry a name and a play button, so they go two up.
   `auto-fill` with a 150px floor was one column short of fitting inside the
   card's own padding at 375px, which quietly turned sixteen narrators into a
   screen and a half of full-width rows. Two columns, stated. */
@media (max-width: 560px) {
  .voice-picker {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }
  .voice-option {
    padding: 10px 11px 11px;
    gap: 8px 8px;
  }
  .voice-option > .voice-play { width: 34px; height: 34px; }
  /* The blurb and the ranking reason are sentences; they do not fit a
     half-width card. The pace tags and the "needs a key" note are short and
     are the two things you would otherwise have to guess at, so they stay. */
  .voice-option .voice-text > span:not([class]),
  .voice-option .voice-why { display: none; }
  /* Two tags have to sit on one line inside a half-width card, or the row
     they wrap onto costs more height than the tags are worth. */
  .voice-option .voice-tags { margin-top: 5px; }
  .voice-option .voice-tag {
    margin-right: 4px;
    padding: 2px 6px;
    font-size: 0.63rem;
    letter-spacing: 0.02em;
  }
  .voice-group { margin-top: 10px; }
}

/* The play control sits inside the option row, so it stops the click from
   also selecting the voice — hearing one should not commit you to it. */
.voice-play {
  flex: none;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--fg-2);
  cursor: pointer;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}

.voice-play:hover {
  border-color: var(--brand);
  color: var(--brand);
}

.voice-play svg {
  width: 13px;
  height: 13px;
}

/* Play at rest, stop while playing — the button says what tapping it does. */
.voice-play .ico-stop {
  display: none;
}

.voice-play.is-playing {
  border-color: var(--brand);
  background: var(--brand);
  color: #fff;
}

.voice-play.is-playing .ico-play {
  display: none;
}

.voice-play.is-playing .ico-stop {
  display: block;
}

.voice-play.is-loading {
  color: var(--fg-4);
  cursor: progress;
}

.voice-play.is-loading svg {
  animation: voice-pulse 1s ease-in-out infinite;
}

@keyframes voice-pulse {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 1; }
}

/* ---------- Voice clone ---------- */

.clone-state {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 13px;
  margin-bottom: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}

.clone-state.is-ready {
  border-color: transparent;
  background: var(--brand-wash);
}

.clone-state.is-bad {
  border-color: transparent;
  background: var(--bad-wash);
}

.clone-state svg {
  width: 18px;
  height: 18px;
  margin-top: 2px;
  flex: none;
  color: var(--fg-3);
}

.clone-state.is-ready svg { color: var(--brand); }
.clone-state.is-bad svg { color: var(--bad); }

.clone-state b {
  display: block;
  font-size: 0.9rem;
  font-weight: 600;
}

.clone-state span {
  display: block;
  margin-top: 2px;
  font-size: 0.82rem;
  color: var(--fg-2);
  line-height: 1.5;
}

.clone-actions {
  display: flex;
  gap: 8px;
}

.clone-actions .btn {
  flex: 1;
}

/* Script the owner reads while recording */
.script {
  max-height: 190px;
  overflow-y: auto;
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  font-size: 0.88rem;
  line-height: 1.6;
}

.script p + p {
  margin-top: 0.8em;
  color: var(--fg-2);
}

.script-consent {
  font-weight: 500;
  padding-left: 10px;
  border-left: 2px solid var(--brand);
}

.consent-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 16px;
  font-size: 0.86rem;
  line-height: 1.5;
  cursor: pointer;
}

.consent-row input {
  width: 17px;
  height: 17px;
  margin-top: 2px;
  flex: none;
  accent-color: var(--brand);
  cursor: pointer;
}

/* Recorder */
.recorder {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 14px;
  margin-bottom: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

.rec-btn {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  flex: none;
  border: none;
  border-radius: 50%;
  background: var(--bad);
  cursor: pointer;
  transition: background-color 0.15s var(--ease);
}

.rec-btn:disabled {
  background: var(--surface-3);
  color: var(--fg-4);
  cursor: not-allowed;
}

.rec-dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  transition: border-radius 0.18s var(--ease), width 0.18s var(--ease),
    height 0.18s var(--ease);
}

/* Recording: the dot becomes a stop square and the button pulses. */
.recorder.is-live .rec-dot {
  width: 14px;
  height: 14px;
  border-radius: 3px;
}

.recorder.is-live .rec-btn {
  animation: kd-rec 1.5s var(--ease) infinite;
}

@keyframes kd-rec {
  0%, 100% { box-shadow: 0 0 0 0 rgba(178, 59, 44, 0.5); }
  60% { box-shadow: 0 0 0 10px rgba(178, 59, 44, 0); }
}

.rec-meta {
  flex: 1;
}

.rec-meta b {
  display: block;
  font-family: var(--mono);
  font-size: 1.05rem;
  font-weight: 500;
}

.rec-meta span {
  display: block;
  font-size: 0.79rem;
  color: var(--fg-3);
}

.rec-level {
  width: 4px;
  height: 40px;
  flex: none;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  overflow: hidden;
  display: flex;
  align-items: flex-end;
}

.rec-level i {
  display: block;
  width: 100%;
  height: 0%;
  border-radius: inherit;
  background: var(--brand);
  transition: height 0.08s linear;
}

/* ==========================================================================
   Authentication Gate & OTP Styles
   ========================================================================== */

.auth-gate {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 32px 16px 48px;
  animation: kd-view 0.24s var(--ease);
}

.auth-card {
  width: 100%;
  max-width: 440px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 28px 22px;
  box-shadow: var(--shadow-2);
}

.auth-header {
  text-align: center;
  margin-bottom: 22px;
}

.auth-icon-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--brand-wash);
  color: var(--brand);
  margin-bottom: 12px;
}

.auth-header h2 {
  font-family: var(--serif, Fraunces, Georgia, serif);
  font-size: 1.45rem;
  font-weight: 600;
  color: var(--fg);
  margin-bottom: 6px;
  letter-spacing: -0.02em;
}

.auth-sub {
  font-size: 0.88rem;
  color: var(--fg-3);
  line-height: 1.45;
  max-width: 320px;
  margin-inline: auto;
}

/* Channel Pills */
.channel-pills {
  display: flex;
  gap: 6px;
  background: var(--surface-2);
  padding: 4px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-soft);
  margin-bottom: 20px;
}

.channel-pill {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 8px 10px;
  font-size: 0.82rem;
  font-weight: 600;
  border: none;
  background: transparent;
  color: var(--fg-3);
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: all 0.15s ease;
}

.channel-pill:hover {
  color: var(--fg);
}

.channel-pill.is-active {
  background: var(--surface);
  color: var(--brand);
  box-shadow: var(--shadow-1);
}

/* Sent Banner */
.auth-sent-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px;
  background: var(--brand-wash);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  margin-bottom: 18px;
  font-size: 0.84rem;
}

.banner-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  color: var(--fg-2);
}

.banner-dest {
  font-family: var(--mono, "JetBrains Mono", monospace);
  color: var(--fg);
  font-size: 0.92rem;
}

.btn-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--brand);
  font: inherit;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
}

.btn-link:hover:not(:disabled) {
  text-decoration: underline;
}

.btn-link:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* 6-Digit OTP Group */
.otp-inputs {
  display: flex;
  gap: 8px;
  justify-content: space-between;
  margin-top: 4px;
}

.otp-digit {
  width: 100%;
  height: 52px;
  text-align: center;
  font-family: var(--mono, "JetBrains Mono", monospace);
  font-size: 1.35rem;
  font-weight: 600;
  background: var(--field);
  border: 1.5px solid var(--line);
  border-radius: 10px;
  color: var(--fg);
  transition: all 0.15s ease;
  caret-color: var(--brand);
}

.otp-digit:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-wash);
}

/* Resend Row & Footer */
.resend-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 14px;
  font-size: 0.83rem;
  color: var(--fg-3);
}

.auth-footer {
  margin-top: 24px;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
  text-align: center;
  font-size: 0.84rem;
  color: var(--fg-3);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 4px 8px;
}

.auth-link {
  color: var(--brand);
  font-weight: 600;
  text-decoration: none;
}

.auth-link:hover {
  text-decoration: underline;
}

/* Superadmin Mode Toggle Pill */
.mode-toggle-pill {
  display: inline-flex;
  align-items: center;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 2px;
  gap: 2px;
  margin-right: 6px;
  box-shadow: var(--shadow-1);
}

.mode-btn {
  background: transparent;
  border: none;
  border-radius: var(--r-pill);
  font-size: 0.74rem;
  font-weight: 600;
  color: var(--fg-3);
  padding: 3px 8px;
  cursor: pointer;
  transition: all 0.15s ease;
}

.mode-btn:hover {
  color: var(--fg);
}

.mode-btn.active {
  background: var(--surface);
  color: var(--brand-strong);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}

.nav-trends-btn,
.nav-creator-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--brand-strong);
  background: var(--brand-wash);
  border: 1px solid rgba(221, 90, 31, 0.25);
  border-radius: var(--r-pill);
  padding: 4px 10px;
  text-decoration: none;
  margin-right: 6px;
  transition: all 0.15s ease;
}

.nav-trends-btn:hover,
.nav-creator-btn:hover {
  background: var(--brand);
  color: #fff;
}

/* User Pill in App Bar */
.user-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 6px 4px 4px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-size: 0.78rem;
  color: var(--fg-2);
  margin-right: 4px;
  box-shadow: var(--shadow-1);
}

.user-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--grad-brand);
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 1px 2px rgba(140, 55, 20, 0.25);
}

.user-dest {
  font-family: var(--mono, "JetBrains Mono", monospace);
  font-weight: 500;
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.btn-logout {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  background: transparent;
  border: none;
  padding: 0;
  margin-left: 1px;
  color: var(--fg-4);
  cursor: pointer;
  border-radius: 50%;
  transition: color 0.15s ease, background-color 0.15s ease;
}

.btn-logout:hover {
  color: var(--bad);
  background: var(--bad-wash);
}

/* Button Spinner */
.btn-spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid rgba(255, 255, 255, 0.4);
  border-top-color: #fff;
  border-radius: 50%;
  animation: kd-spin 0.6s linear infinite;
  margin-left: 6px;
  vertical-align: middle;
}

@keyframes kd-spin {
  to { transform: rotate(360deg); }
}

/* ---------- Auth mode tabs ---------- */

/* The reset steps are neither tab, so the strip steps back — and `display:
   flex` beats the hidden attribute unless it is said out loud. */
.auth-tabs[hidden] { display: none; }

.auth-tabs {
  display: flex;
  gap: 4px;
  padding: 4px;
  margin-bottom: 20px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
}

.auth-tab {
  flex: 1;
  padding: 0.55em 0.8em;
  border: none;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--fg-3);
  font-size: 0.87rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.15s var(--ease), color 0.15s var(--ease);
}

.auth-tab:hover {
  color: var(--fg);
}

.auth-tab.is-active {
  background: var(--surface);
  color: var(--brand);
  box-shadow: var(--shadow-1);
}

/* ---------- Verification checklist ---------- */

.verify-list {
  list-style: none;
  margin-bottom: 20px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
}

.verify-row {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--surface);
}

.verify-row:last-child {
  border-bottom: none;
}

.verify-row.is-done {
  background: var(--brand-wash);
}

.verify-mark {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: none;
  color: var(--fg-4);
}

.verify-row.is-done .verify-mark {
  color: var(--brand);
}

.verify-mark svg {
  width: 100%;
  height: 100%;
}

.verify-row b {
  display: block;
  font-size: 0.89rem;
  font-weight: 600;
}

.verify-row span:not(.verify-mark):not(.pill) {
  display: block;
  font-size: 0.79rem;
  color: var(--fg-3);
  overflow-wrap: anywhere;
}

.verify-row .pill {
  margin-left: auto;
}

.verify-row > div {
  min-width: 0;
}

/* A long email must not push the theme toggle off the bar. Flex items default
   to min-width:auto, so without these the pill refuses to shrink below its
   text and the controls to its right get pushed out of the container. */
.app-bar .kd-logo,
.app-bar-tools,
.app-bar .user-pill {
  min-width: 0;
  flex: 0 1 auto;
}

.app-bar .user-pill {
  max-width: none;
  overflow: hidden;
}

.app-bar .user-dest {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Never shrink the controls themselves — only the label between them. */
.app-bar .theme-btn,
.app-bar .btn,
.app-bar .btn-logout,
.app-bar .user-avatar,
.app-bar .kd-logo img {
  flex: none;
}

/* On a phone there isn't room for the wordmark, the email and the buttons.
   Shrinking lets the wordmark paint under the pill, so drop the text outright:
   the mark still says who we are, and the email lives on in the Profile tab. */
@media (max-width: 540px) {
  .app-bar .kd-logo b,
  .app-bar .user-dest {
    display: none;
  }

  .app-bar .user-pill {
    gap: 4px;
  }
}

.auth-footer .auth-link {
  white-space: nowrap;
}

/* =============================================================================
   Intelligence & Deals Styles
   ============================================================================= */

.card-head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 14px;
}

.card-head-row .card-head {
  margin-bottom: 0;
}

.intel-hero-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px 18px;
  margin-bottom: 16px;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  box-shadow: var(--shadow-1);
}

@media (min-width: 480px) {
  .intel-hero-card {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
  }
}

.intel-status-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}

.agent-pulse-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--brand);
  position: relative;
}

.agent-pulse-dot.is-active::after {
  content: '';
  position: absolute;
  top: -3px;
  left: -3px;
  right: -3px;
  bottom: -3px;
  border-radius: 50%;
  border: 1.5px solid var(--brand);
  animation: pulse-ring 2s infinite ease-out;
}

.agent-pulse-dot.is-scanning {
  background: var(--warn);
}

.agent-pulse-dot.is-scanning::after {
  content: '';
  position: absolute;
  top: -4px;
  left: -4px;
  right: -4px;
  bottom: -4px;
  border-radius: 50%;
  border: 2px solid var(--warn);
  animation: pulse-ring 1s infinite ease-out;
}

@keyframes pulse-ring {
  0% { transform: scale(0.7); opacity: 1; }
  100% { transform: scale(2.2); opacity: 0; }
}

.intel-badge {
  font-size: 0.72rem;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 20px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.intel-last-scan {
  font-size: 0.73rem;
  color: var(--fg-3);
  margin-left: 4px;
}

.intel-hero-content h2 {
  font-size: 1.15rem;
  font-weight: 600;
  margin: 0 0 4px;
  color: var(--fg-1);
}

.intel-hero-content p {
  font-size: 0.82rem;
  color: var(--fg-2);
  margin: 0;
  line-height: 1.4;
}

.intel-empty-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 14px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--fg-3);
  font-size: 0.83rem;
}

.booking-list, .deals-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.booking-item-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: var(--surface-2);
}

.booking-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-width: 0;
}

.booking-link-text {
  font-size: 0.87rem;
  font-weight: 500;
  color: var(--fg-1);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.plat-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.7rem;
  font-weight: 600;
  padding: 2px 8px 2px 7px;
  border-radius: var(--r-pill);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  background: var(--surface-1);
  color: var(--fg-2);
  border: 1px solid var(--line);
}

.plat-badge::before {
  content: "";
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: 50%;
  background: var(--dot, var(--fg-4));
}

.badge-resy { --dot: #ea580c; }
.badge-opentable { --dot: #dc2626; }

.pill-xs {
  font-size: 0.68rem;
  padding: 1px 6px;
}

.btn-xs {
  padding: 4px 9px;
  font-size: 0.75rem;
}

.booking-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.btn-icon-subtle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  border: none;
  background: transparent;
  color: var(--fg-3);
  cursor: pointer;
  transition: all 0.15s ease;
}

.btn-icon-subtle:hover {
  background: var(--bad-wash);
  color: var(--bad);
}

/* Deal Cards */
.deal-card-item {
  padding: 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: var(--surface-2);
  transition: border-color 0.15s ease;
}

.deal-card-item.is-inactive-deal {
  opacity: 0.55;
}

.deal-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.deal-title-group {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.deal-title-group b {
  font-size: 0.92rem;
  color: var(--fg-1);
}

.deal-type-pill {
  font-size: 0.7rem;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 4px;
  background: var(--navy-wash);
  color: var(--navy);
}

.deal-schedule-tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.76rem;
  font-weight: 500;
  color: var(--fg-2);
  margin-bottom: 6px;
}

.deal-desc {
  font-size: 0.82rem;
  color: var(--fg-2);
  margin: 4px 0 10px;
  line-height: 1.4;
}

.deal-card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 8px;
  border-top: 1px dashed var(--line);
}

.deal-source-link {
  font-size: 0.74rem;
  color: var(--fg-3);
  text-decoration: underline;
}

/* Intel Profile Details Grid */
.intel-details-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.intel-grid-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  font-size: 0.84rem;
}

.intel-grid-row.full-width {
  grid-column: 1 / -1;
}

.intel-label {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--fg-3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.intel-summary-p {
  margin: 4px 0 0;
  font-size: 0.86rem;
  color: var(--fg-1);
  line-height: 1.45;
}

.intel-price-val {
  color: var(--brand);
  font-size: 0.95rem;
}

.intel-web-link {
  color: var(--brand);
  font-weight: 500;
  text-decoration: none;
}

.intel-chips-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}

.intel-chip {
  font-size: 0.76rem;
  font-weight: 500;
  padding: 3px 9px;
  border-radius: 20px;
  background: var(--surface-1);
  border: 1px solid var(--line);
  color: var(--fg-1);
}

/* Reviews and Customer Sentiment */
.intel-reviews-wrap {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.intel-reviews-summary {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--fg-1);
  background: var(--surface-2);
  padding: 12px 14px;
  border-radius: var(--r-sm);
  border-left: 3px solid var(--brand);
}

.intel-mini-title {
  display: block;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--fg-3);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 8px;
}

.intel-praises-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}

.praise-item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.84rem;
  color: var(--fg-1);
  background: var(--surface-2);
  padding: 8px 12px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
}

.intel-quotes-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

@media (max-width: 680px) {
  .intel-quotes-grid {
    grid-template-columns: 1fr;
  }
}

.review-quote-card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 12px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--line);
  position: relative;
}

.quote-text {
  margin: 0 0 8px;
  font-size: 0.82rem;
  font-style: italic;
  color: var(--fg-1);
  line-height: 1.4;
}

.quote-source {
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--brand);
  text-align: right;
}

/* Vibe and Overview */
.intel-vibe-wrap {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.vibe-section {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.vibe-text {
  margin: 0;
  font-size: 0.88rem;
  color: var(--fg-1);
  line-height: 1.45;
}

.vibe-tags-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}

.vibe-tag-pill {
  font-size: 0.75rem;
  font-weight: 500;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--brand-wash);
  color: var(--brand);
}

/* Danger Zone Styles */
/* Destructive controls need distance, not just a red border. Save sat flush
   against the card that deletes the restaurant — nothing but a card edge
   between "keep my changes" and "erase this place" — so the danger zone is
   pushed below a rule and out of the reach of a mis-aimed click. */
.danger-card {
  border-color: var(--bad) !important;
  margin-top: 56px;
  position: relative;
}

.danger-card::before {
  content: "";
  position: absolute;
  top: -28px;
  left: 0;
  right: 0;
  border-top: 1px solid var(--line);
}

.danger-title {
  color: var(--bad) !important;
}

.danger-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0 4px;
}

.danger-p {
  margin: 4px 0 0;
  font-size: 0.82rem;
  color: var(--fg-3);
  line-height: 1.4;
}

.btn-danger {
  /* Set through --btn-fg like every other variant rather than an !important
     colour. The !important could only be answered with another !important, and
     the disabled treatment then hung on :disabled invalidating cleanly through
     two competing important declarations. This just wins on specificity. */
  --btn-fg: #ffffff;
  background: var(--grad-danger);
  border: 1px solid rgba(120, 30, 20, 0.5);
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(120, 30, 20, 0.16), 0 4px 12px -6px rgba(120, 30, 20, 0.28);
}

.btn-danger:hover {
  background: var(--grad-danger-hover);
}

.btn-danger:active {
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.25);
}




/* Account Profile Modal */
.account-info-box {
  padding: 14px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--line);
}

.account-email-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.account-plan-box {
  margin-top: 14px;
  padding: 14px;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--line);
}

.account-plan-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.account-plan-head .plan-title {
  margin: 2px 0 0;
}

/* The composer sets its two meters side by side under a wide box; in here they
   are a two-line summary of the plan above them, so they stack. */
.account-plan-usage {
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  margin: 12px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.account-plan-usage .usage-meter { justify-content: space-between; }
.account-plan-usage .usage-bar { flex: 1; width: auto; }

.user-avatar-lg {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--grad-brand);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3), 0 2px 5px -1px rgba(140, 55, 20, 0.38);
}

.account-verified-tag {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 4px;
  background: var(--surface-3);
  color: var(--fg-3);
  margin-top: 3px;
}

.account-verified-tag.is-verified {
  background: var(--good-wash);
  color: var(--good);
}

.danger-box {
  margin-top: 40px;
  padding: 14px;
  border-radius: var(--r-sm);
  border: 1px solid var(--bad);
  background: var(--bad-wash);
}

.danger-box b {
  color: var(--bad);
  font-size: 0.88rem;
  display: block;
}

.danger-box p {
  margin: 4px 0 8px;
  font-size: 0.8rem;
  color: var(--fg-2);
  line-height: 1.4;
}

.modal-sm {
  max-width: 440px !important;
}

/* =============================================================================
   Usage Metrics & Activity Dashboard Styles
   ============================================================================= */

/* This grid always lives inside the phone-width `.app` shell (max-width:
   520px), never the browser viewport, so breakpoints keyed to window width
   don't track its real available space — on any normal desktop browser the
   window clears typical breakpoints while the shell itself stays ~488px.
   `minmax(0, 1fr)` overrides Grid's default content-based minimum column
   width, so a long dynamic string can't force a column wider than its fair
   share and push a card out past the shell's edge (see the plan quota line
   that used to live in a 4th card here — moved to Account settings). */
.metrics-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}

.metric-card {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
  padding: 16px;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.metric-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.metric-icon-wrap {
  width: 44px;
  height: 44px;
  border-radius: var(--r-md);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* One treatment for all four — the number is the story, not the icon. */
.icon-reels,
.icon-posts,
.icon-published {
  background: var(--brand-wash);
  color: var(--brand);
}

.metric-meta {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.metric-label {
  font-size: 0.76rem;
  font-weight: 600;
  color: var(--fg-3);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.metric-value {
  font-size: 1.45rem;
  font-weight: 700;
  color: var(--fg-1);
  line-height: 1.2;
  margin: 2px 0;
}

.metric-sub {
  font-size: 0.75rem;
  color: var(--fg-2);
  overflow-wrap: anywhere;
}

/* Three columns used to squeeze each card to ~100px and wrap the labels a
   letter per line, so they were stacked instead — and three stacked cards ate
   the whole first screen of Activity to say three zeros. What cost the width
   was the 44px icon chip, which left the label ~55px to wrap in. The icons
   were never the point (they all carry the same wash, above), so on a
   phone they go and the row stays a row: at 375px that leaves each column
   ~107px, enough for "Image posts" on one line. */
@media (max-width: 620px) {
  .metrics-grid {
    gap: 8px;
  }

  .metric-card {
    gap: 0;
    padding: 11px 12px;
  }

  .metric-icon-wrap {
    display: none;
  }

  .metric-meta {
    flex: 1;
  }

  /* The number leads; the label under it says what it counts. */
  .metric-value {
    order: -1;
    font-size: 1.35rem;
    margin: 0 0 1px;
  }

  .metric-label {
    font-size: 0.66rem;
    letter-spacing: 0.05em;
    overflow-wrap: anywhere;
  }

  .metric-sub {
    font-size: 0.7rem;
  }
}

/* Activity Timeline Feed */
.activity-filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}

.filter-chip {
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--fg-2);
  font-size: 0.8rem;
  font-weight: 500;
  padding: 5px 12px;
  border-radius: 20px;
  cursor: pointer;
  transition: all 0.15s ease;
}

.filter-chip:hover {
  background: var(--surface-3);
  color: var(--fg-1);
}

.filter-chip.is-active {
  background: var(--grad-brand);
  border-color: transparent;
  color: #ffffff;
  font-weight: 600;
}

[data-theme="dark"] .filter-chip.is-active {
  color: #101828;
}

.activity-timeline {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.activity-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  transition: background 0.15s ease;
}

.activity-row:hover {
  background: var(--surface-3);
}

.activity-media-wrap {
  position: relative;
  width: 54px;
  height: 54px;
  border-radius: var(--r-sm);
  overflow: hidden;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  background: var(--surface-3);
}

/* Icon and artwork share the one cell — the artwork covers the icon while it
   loads, and if it fails the icon is simply there underneath. */
.activity-media-wrap > * {
  grid-area: 1 / 1;
}

/* Openable rows only: the tile is a button there, and has to shed the browser's
   own button chrome to stay the same tile it is on a row with nothing to open. */
button.activity-media-wrap {
  padding: 0;
  border: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

button.activity-media-wrap img {
  transition: transform 0.16s var(--ease);
}

button.activity-media-wrap:hover img,
button.activity-media-wrap:focus-visible img {
  transform: scale(1.07);
}

button.activity-media-wrap:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  button.activity-media-wrap img { transition: none; }
}

.activity-media-wrap img {
  width: 54px;
  height: 54px;
  object-fit: cover;
}

/* Items with no artwork get a tinted type icon, matching their badge, rather
   than a stock photo pretending to be their food. */
.activity-media-wrap > svg {
  width: 22px;
  height: 22px;
}

.tile-reel    { background: var(--brand-wash); color: var(--brand); }
.tile-post    { background: var(--navy-wash);  color: var(--navy); }
.tile-publish { background: var(--good-wash);  color: var(--good); }
.tile-scan    { background: var(--surface-3);  color: var(--fg-3); }

.play-indicator {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: rgba(0, 0, 0, 0.35);
}

.play-indicator svg {
  width: 16px;
  height: 16px;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.4));
}

.activity-content-wrap {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.activity-top-line {
  display: flex;
  align-items: center;
  gap: 8px;
}

.activity-type-badge {
  font-size: 0.7rem;
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 4px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

.badge-reel {
  background: var(--brand-wash);
  color: var(--brand);
}

.badge-post {
  background: var(--navy-wash);
  color: var(--navy);
}

.badge-publish {
  background: var(--good-wash);
  color: var(--good);
}

.badge-scan {
  background: var(--surface-3);
  color: var(--fg-2);
}

.activity-time {
  font-size: 0.72rem;
  color: var(--fg-3);
}

.activity-title {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--fg-1);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Two lines, then an ellipsis: on a phone a single-line caption cuts off
   after three or four words, which reads as broken rather than tidy. */
.activity-caption {
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--fg-2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.activity-action-col {
  flex-shrink: 0;
}

.activity-empty {
  display: grid;
  justify-items: center;
  gap: 10px;
  padding: 28px 18px;
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--r-md);
  text-align: center;
}

.activity-empty svg {
  width: 26px;
  height: 26px;
  color: var(--fg-4);
}

.activity-empty p {
  margin: 0;
  max-width: 34ch;
  font-size: 0.84rem;
  line-height: 1.5;
  color: var(--fg-3);
}

/* Plan Card & Quota Meters */
.plan-head-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
}

.plan-title {
  font-family: var(--sans);
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  margin: 4px 0 4px;
}

.plan-sub {
  margin: 0;
  font-size: 0.84rem;
  color: var(--fg-3);
  line-height: 1.5;
  max-width: 48ch;
}

.quota-bars-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  margin-top: 18px;
}

@media (max-width: 680px) {
  .quota-bars-grid {
    grid-template-columns: 1fr;
  }
}

.quota-box {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 14px;
}

.quota-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
  font-size: 0.86rem;
}

.quota-head span {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--brand);
}

.progress-bar-bg {
  width: 100%;
  height: 8px;
  background: var(--surface-3);
  border-radius: 4px;
  overflow: hidden;
}

.progress-bar-fill {
  height: 100%;
  background: var(--grad-brand);
  border-radius: 4px;
  transition: width 0.3s ease;
}

.progress-bar-fill.fill-posts {
  background: var(--grad-navy);
}

/* The same three states the composer's usage strip uses, on the same
   thresholds, so a meter that is orange under the button is orange here too.
   Colour beats reading the count, which is the point of having a bar at all. */
.quota-box.is-low { border-color: var(--warn); }
.quota-box.is-low .quota-head span { color: var(--warn); }
.quota-box.is-low .progress-bar-fill,
.quota-box.is-low .progress-bar-fill.fill-posts { background: var(--warn); }

.quota-box.is-out { border-color: var(--bad); background: var(--bad-wash); }
.quota-box.is-out .quota-head span { color: var(--bad); }
.quota-box.is-out .progress-bar-fill,
.quota-box.is-out .progress-bar-fill.fill-posts { background: var(--bad); }
.quota-box.is-out .quota-hint { color: var(--bad); font-weight: 600; }

/* Full and flat. There is nothing to draw down, and the bar is only still here
   so the card keeps its shape next to the metered one beside it. */
.quota-box.is-unlimited .quota-head span { color: var(--fg-3); }
.quota-box.is-unlimited .progress-bar-fill,
.quota-box.is-unlimited .progress-bar-fill.fill-posts { background: var(--line-strong); }

.quota-hint {
  display: block;
  font-size: 0.74rem;
  color: var(--fg-3);
  margin-top: 6px;
}

.syndication-channels-row {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
}

.syndication-label {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--fg-2);
}

/* Platform chips: one neutral chrome, the platform's own logo on a fixed-
   white badge so it stays legible in dark mode (TikTok's mark is pure black).
   (These class names also exist in landing.css; the two files never load
   on the same page.) */
.social-pills-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.social-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px 3px 4px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--fg-2);
  font-size: 0.73rem;
  font-weight: 500;
  white-space: nowrap;
}

.social-pill img {
  width: 15px;
  height: 15px;
  flex: none;
  padding: 3px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
}





/* =============================================================================
   Schedule: library grid, queue rows, posting-window picker
   ============================================================================= */

.library-grid > .none-yet {
  grid-column: 1 / -1;
}

.library-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: 8px;
  margin: 14px 0 16px;
}

.lib-tile {
  position: relative;
  aspect-ratio: 1;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  overflow: hidden;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), transform 0.08s var(--ease);
}

.lib-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.lib-tile:active { transform: scale(0.97); }

.lib-tile.is-on {
  border-color: var(--brand);
  box-shadow: 0 0 0 2px var(--brand-wash);
}

/* Selection tick, only painted once the tile is chosen. */
.lib-check {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.9);
  background: rgba(0, 0, 0, 0.28);
  transition: background-color 0.15s var(--ease);
}

.lib-tile.is-on .lib-check {
  background: var(--brand);
  border-color: #fff;
}

.lib-tile.is-on .lib-check::after {
  content: "";
  position: absolute;
  inset: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") center / 11px no-repeat;
}

/* ---- Queue ---- */

.schedule-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.sched-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}

/* The state, as a stripe down the row's leading edge. A queue is read as a
   column: the pill on the right answers "what is this one waiting on", but
   only the stripe answers it for twelve rows at once. */
.sched-row[data-state] {
  border-left: 3px solid var(--edge, var(--line));
}

.sched-row[data-state="draft"]     { --edge: var(--gold); }
.sched-row[data-state="approved"]  { --edge: var(--good); }
.sched-row[data-state="posting"]   { --edge: var(--warn); }
.sched-row[data-state="failed"]    { --edge: var(--bad); }
.sched-row[data-state="posted"]    { --edge: var(--good); }
.sched-row[data-state="cancelled"] { --edge: var(--line-strong); }

/* Landed on from a feed card's "See in queue". The colour is the answer to
   "which of these is the one I was just looking at" — it fades once read. */
.sched-row.is-found {
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-wash);
}

.sched-thumb {
  position: relative;
  width: 46px;
  height: 46px;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: var(--r-xs);
  overflow: hidden;
  background: var(--surface-3);
}

button.sched-thumb {
  cursor: pointer;
}

button.sched-thumb:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* Centred rather than tucked in a corner: it is the thing you press, not a
   label saying the file happens to be a video. */
.sched-play {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 21px;
  height: 21px;
  padding-left: 1px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.58);
  color: #fff;
  font-size: 8px;
  line-height: 1;
  transition: background-color 0.15s var(--ease);
}

button.sched-thumb:hover .sched-play {
  background: var(--brand);
}

/* ---- Media preview ---- */

/* Wider than a form sheet: this one exists to be looked at, so the clip gets
   the room a 4:5 frame needs before the page around it starts to matter. */
.sheet-media { max-width: 620px; }

/* A question is a couple of lines and two buttons. At the full sheet width it
   reads as a form with the fields missing. */
.sheet-narrow { max-width: 372px; }

/* No head/body gap on a question: the body IS the question, and 1.2rem of air
   between it and the buttons makes a two-line dialog feel half empty. */
#askSheet .sheet-head { margin-bottom: 1rem; }
#askSheet .sheet-head p { margin-top: 6px; }

/* ---------- Asking in the page (the enrolment link) ----------
   That page has no modal machinery, and a staff phone opened straight from a
   text message is the wrong place to introduce one. The button gives way to
   the question instead. */

.drop-confirm {
  margin-top: 10px;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--bad-wash);
}

.drop-confirm[hidden] { display: none; }

.drop-confirm b {
  display: block;
  font-size: 0.95rem;
  color: var(--bad);
}

.drop-confirm p {
  margin: 5px 0 12px;
  font-size: 0.83rem;
  line-height: 1.5;
  color: var(--fg-2);
}

.drop-confirm-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.media-preview video,
.media-preview img {
  display: block;
  width: 100%;
  max-height: 72vh;
  object-fit: contain;
  border-radius: var(--r-sm);
  background: #000;
}

.sched-thumb img,
.sched-thumb video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.sched-time {
  font: inherit;
  color: inherit;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  padding: 1px 4px;
  max-width: 100%;
}

.sched-error {
  color: var(--bad, #d14343);
}

/* "Wed, Sep 3, 11:30" is the fact; "in 2 days" is the answer. Both, because
   the first is what you edit and the second is what you were asking. */
.sched-meta b > .sched-when {
  flex: none;
  font-size: 0.74rem;
  font-weight: 500;
  color: var(--fg-3);
  white-space: nowrap;
}

/* A slot already gone by is the one thing on this screen worth interrupting
   for — either the cron is behind or the post is waiting on a yes it will
   never now get in time. */
.sched-meta b > .sched-when.is-late {
  color: var(--bad);
  font-weight: 600;
}

/* The lifecycle line: queued/approved/posted times. Quieter than the caption —
   it's there to be checked, not read every time. */
.sched-meta .sched-log {
  font-size: 0.72rem;
  color: var(--fg-4, var(--fg-3));
  white-space: normal;
  overflow: visible;
}

.sched-meta {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* The slot time must stay on one line — it's the thing being scanned. On a
   phone a reel badge and a date input do not both fit on that line, so the
   badge sits above it rather than squeezing the time down to an ellipsis. */
.sched-meta b {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  min-width: 0;
  font-size: 0.86rem;
  font-weight: 600;
}

.sched-meta b > .sched-time {
  flex: none;
  max-width: 100%;
}

.sched-meta span {
  font-size: 0.78rem;
  color: var(--fg-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sched-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: none;
}

/* On a phone, thumb + time + buttons on one line leaves the date control about
   108px, which is enough to clip the year off it. The buttons drop to their own
   line instead — the row is taller, and the time is readable. */
@media (max-width: 430px) {
  .sched-row {
    flex-wrap: wrap;
  }

  .sched-meta {
    flex: 1 1 calc(100% - 58px);
  }

  .sched-actions {
    width: 100%;
    justify-content: flex-end;
  }
}

/* ---- Day picker ---- */

/* A week is seven columns, not a wrapping row. Left to flex-wrap it broke
   five-and-two on a phone, which reads as a list that ran out of room rather
   than as Monday to Sunday, and the two orphans sat under a gap where Sat and
   Sun ought to be. Capped so the chips don't stretch into slabs on a desktop. */
.day-picker {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
  max-width: 430px;
}

.day-chip {
  padding: 8px 4px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--fg-3);
  font-size: 0.8rem;
  font-weight: 500;
  text-align: center;
  cursor: pointer;
  transition: background-color 0.15s var(--ease), color 0.15s var(--ease),
    border-color 0.15s var(--ease);
}

.day-chip:hover { background: var(--surface-3); }

/* Seven filled orange pills is the loudest thing on a page whose actual
   action is a Save button — and every account starts with all seven on, so
   the default state shouted. The wash reads as clearly chosen against the
   grey of an off day, and matches how `.seg-btn` marks its selection two
   sections down rather than inventing a third way to say "on". */
.day-chip.is-on {
  background: var(--brand-wash);
  border-color: var(--brand);
  color: var(--brand);
  font-weight: 650;
}


/* Quieter than the mode switch above it: photo-or-generated is a setting
   within Image post, not a peer of the choice between video and image. */
.seg-quiet {
  max-width: 320px;
  margin-bottom: 16px;
  background: transparent;
  border-color: transparent;
  padding: 0;
  gap: 6px;
}

.seg-quiet .seg-btn {
  border: 1px solid var(--line);
  font-size: 0.79rem;
}

.seg-quiet .seg-btn.is-on {
  border-color: var(--brand);
  background: var(--brand-wash);
  color: var(--brand);
}

.seg-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

.seg-btn svg { flex: none; }



/* Two selects that read as one control: the second narrows to the first. */
.field-pair-tight {
  gap: 8px;
  margin-bottom: 6px;
}

/* Arriving from "Use this", the format is a decision already taken, so it is

.chosen-format[hidden] { display: none; }




/* Same 9:13 as the gallery card, small enough to sit beside the words rather

.chosen-thumb[hidden] { display: none; }


.chosen-thumb[data-intent="craving"] { background: var(--grad-brand); }
.chosen-thumb[data-intent="trust"] { background: var(--grad-good); }
.chosen-thumb[data-intent="booking_order"] { background: var(--grad-violet); }

/* The fallback when a format has no example clip: the frame it opens on. */





/* ---------------- The composer ----------------
   One box, the shape every AI tool now takes a request in: the words, what
   is attached above them, the tools underneath, one button that sends. The
   two-column form it replaced (dropzone | paragraph, label, textarea, hint,
   button, three footnotes) asked the owner to read before they could type. */

/* Fills the column like everything else on the view. Its own cap left the
   composer 120px short of the section heading directly above it, so the one
   thing you came to type sat inside a ragged right edge. */
.composer { max-width: 100%; }

.prompt-box {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 12px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow-2);
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}

.prompt-box:focus-within {
  border-color: var(--brand);
  box-shadow: var(--shadow-2), 0 0 0 3px var(--brand-wash);
}

/* The words. No border of their own: the box is the field. Grows with what
   is typed (field-sizing where the browser has it; two lines elsewhere). */
.prompt-text {
  width: 100%;
  min-height: 56px;
  max-height: 40vh;
  padding: 6px 4px;
  border: 0;
  background: transparent;
  color: var(--fg);
  font-size: 1.02rem;
  line-height: 1.5;
  resize: none;
  field-sizing: content;
}

.prompt-text:focus { outline: none; }
.prompt-text::placeholder { color: var(--fg-4); }

/* Under the words: attach, what to make, send. */
.compose-tools {
  --tool-h: 38px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.compose-tools .dropzone {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: var(--tool-h);
  padding: 0 12px 0 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--fg-2);
  font-size: 0.82rem;
  line-height: 1;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease), color 0.15s var(--ease);
}

.compose-tools .dropzone b { font-weight: inherit; color: inherit; font-size: inherit; }
.compose-tools .dropzone svg { width: 16px; height: 16px; margin: 0; color: inherit; }
.compose-tools .dropzone:hover,
.compose-tools .dropzone.has-file { color: var(--brand); }
.compose-tools .dropzone:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* Video or image, as a small switch in the toolbar rather than a bar above
   the whole form: it is a setting on the request, not a page. */
.compose-tools .seg-mini {
  height: var(--tool-h);
  margin: 0;
  padding: 3px;
  gap: 2px;
  box-sizing: border-box;
}

.compose-tools .seg-mini .seg-btn {
  padding: 0 11px;
  font-size: 0.8rem;
  gap: 6px;
}

.compose-send {
  height: var(--tool-h);
  margin-left: auto;
  padding: 0 0.8em 0 1.05em;
  border-radius: var(--r-pill);
  gap: 0.4em;
}

.compose-send:disabled { opacity: 0.45; }

/* Dropped anywhere on the box. The veil says so while a file is over it. */
.drop-veil {
  position: absolute;
  inset: 0;
  display: none;
  place-items: center;
  border-radius: inherit;
  border: 2px dashed var(--brand);
  background: var(--brand-wash);
  color: var(--brand);
  font-weight: 700;
  pointer-events: none;
}

.prompt-box.is-hot .drop-veil { display: grid; }

/* Attached shots as a storyboard in the order they play: tall tiles in the
   reel's own proportion, the shot number on a gradient at the foot, a dashed
   tile at the end to add the next one. The row list this replaced showed one
   48px thumb beside a filename; a reel is judged by its pictures. */
.prompt-box .file-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
  padding: 2px 0 0;
  max-height: none;
  overflow: visible;
}

.prompt-box .file-item,
.prompt-box .file-add {
  position: relative;
  width: 76px;
  height: 118px;
  padding: 0;
  border-radius: 10px;
  overflow: hidden;
  display: block;
}
.prompt-box .file-item {
  background: var(--surface-3);
  box-shadow: inset 0 0 0 1px rgba(22, 32, 58, 0.08), var(--shadow-1);
  transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.prompt-box .file-item:hover {
  background: var(--surface-3);
  border-color: transparent;
  transform: translateY(-2px);
  box-shadow: inset 0 0 0 1px rgba(22, 32, 58, 0.08), var(--shadow-2);
}
/* A foot of shade so the number and the length read on any picture. */
.prompt-box .file-item::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 44%;
  background: linear-gradient(to top, rgba(11, 16, 32, 0.6), rgba(11, 16, 32, 0));
  pointer-events: none;
  z-index: 1;
}

.prompt-box .file-thumb-wrap { position: static; width: 100%; height: 100%; }
.prompt-box .file-list-thumb,
.prompt-box .file-list-video-icon { width: 100%; height: 100%; border: 0; border-radius: 0; box-shadow: none; }
.prompt-box .file-thumb-duration {
  right: 6px;
  bottom: 7px;
  padding: 0;
  background: none;
  font-size: 10px;
  font-weight: 600;
  z-index: 2;
}

/* The handle is the whole tile: drag it anywhere. The number and the remove
   sit on top of it. */
.prompt-box .file-drag-handle {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  margin: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: transparent;
  z-index: 1;
  cursor: grab;
}
.prompt-box .file-drag-handle:hover { background: transparent; }
.prompt-box .file-drag-handle:focus-visible { box-shadow: inset 0 0 0 2px var(--brand); }

.prompt-box .file-list-badge {
  position: absolute;
  top: auto;
  bottom: 6px;
  left: 6px;
  width: 20px;
  height: 20px;
  border-radius: var(--r-pill);
  background: #fff;
  color: #16203a;
  font-size: 11px;
  z-index: 2;
  pointer-events: none;
}

.prompt-box .file-list-info { display: none; }

.prompt-box .file-remove-btn {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 22px;
  height: 22px;
  margin: 0;
  border-radius: var(--r-pill);
  background: rgba(11, 16, 32, 0.65);
  color: #fff;
  z-index: 2;
  opacity: 0;
  transition: opacity 0.12s var(--ease);
}
.prompt-box .file-remove-btn svg { width: 12px; height: 12px; }
.prompt-box .file-item:hover .file-remove-btn,
.prompt-box .file-item:focus-within .file-remove-btn { opacity: 1; }
@media (hover: none) { .prompt-box .file-remove-btn { opacity: 1; } }
.prompt-box .file-remove-btn:hover { background: var(--bad, #b23b2c); border-color: transparent; }

.prompt-box .file-item.is-dragging {
  transform: scale(1.04);
  box-shadow: 0 0 0 2px var(--brand), var(--shadow-2);
}

/* The next shot goes here: a label for the same hidden input the toolbar
   button opens, so the strip grows from its own end. */
.prompt-box .file-add {
  border: 1.5px dashed var(--line-strong);
  background: transparent;
  color: var(--fg-3);
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease), background-color 0.15s var(--ease);
}
.prompt-box .file-add label {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 4px;
  width: 100%;
  height: 100%;
  font-size: 11px;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
}
.prompt-box .file-add:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-wash); }
.prompt-box .file-add svg { width: 18px; height: 18px; }

/* What the shots add up to, as a quiet line under the strip. */
.prompt-box .shot-summary { margin: 2px 2px 0; }

@media (max-width: 560px) {
  .prompt-box .file-item,
  .prompt-box .file-add { width: 64px; height: 100px; }
}

/* The chosen format, as a chip on top of the box: a 9:13 thumb, the name,
   a way out. */
.compose-pick {
  display: inline-flex;
  align-items: flex-start;
  gap: 10px;
  width: fit-content;
  max-width: min(100%, 460px);
  padding: 8px 10px 8px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.compose-pick[hidden] { display: none; }
.compose-pick-card { flex: 0 0 34px; width: 34px; max-width: 34px; margin: 0; border: 0; box-shadow: none; background: transparent; }
.compose-pick-card .format-frame { width: 34px; height: 49px; padding: 0; border-radius: 4px; }
/* Too small for a mosaic: beside the box the chip is 34px wide, so the sequence
   shows its opening slide the way the feed does. */
.compose-pick-card .format-mosaic { grid-template-columns: 1fr; gap: 0; }
.compose-pick-card .format-mosaic img:nth-child(n+2) { display: none; }
.compose-pick-card .format-frame-hook,
.compose-pick-card .format-frame-chip,
.compose-pick-card .format-film-tag { display: none; }
.compose-pick-body {
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 10px;
  row-gap: 2px;
  align-items: center;
}
.compose-pick-body b { font-size: 0.86rem; line-height: 1.3; }
.compose-pick-body b::before { content: "Format \00b7\00a0"; color: var(--fg-3); font-weight: 500; }
.compose-pick-body p {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--fg-3);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.compose-pick-body .btn { grid-row: 1; grid-column: 2; }

/* Three ways in, under the box, gone once anything is in it. */
.compose-starters {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}
.composer.has-input .compose-starters,
.compose-starters.is-other-kind { display: none; }

.compose-starters .chip {
  /* These are the way into a blank screen, so they are sized as buttons --
     they were reading as metadata tags at a tag's height. */
  min-height: 38px;
  padding: 0.42em 0.95em;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--fg-2);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease),
    background-color 0.15s var(--ease);
}
.compose-starters .chip:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-wash); }

/* The chat's answer, under the box, like a reply. */
/* The thread: the owner on the right, the agent on the left, like any
   messaging app. Errors stay in .compose-reply under the box. */
.chat-thread {
  list-style: none;
  margin: 0 0 14px;
  padding: 0 2px 0 0;
  display: flex;
  flex-direction: column;
  /* Tight, because this is the gap between two lines from the same side --
     one speaker carrying on. The gap that matters is the one below. */
  gap: 4px;
}
/* A turn and the answer to it are two different people, and eight pixels of
   air between them read as one block of text alternating colour. Hand-offs get
   the room; a run from one side stays a run. */
.chat-turn.is-user + .chat-turn.is-assistant,
.chat-turn.is-assistant + .chat-turn.is-user { margin-top: 18px; }
/* The chat fills the window the way a messaging app does: the view is
   sized to the screen, the thread is the scroll box inside it and the box
   sits under the thread, so the box never moves while the owner scrolls.
   It was a sticky box against a page scroll before, and that drifted with
   every bit of padding on the way up the tree. */
@media (max-width: 899px) {
  .dashboard-section:not([hidden]) { display: flex; flex-direction: column; flex: 1; }
}
.app:has(#viewChat.is-active) { height: 100dvh; }
.app:has(#viewChat.is-active) .dashboard-section { flex: 1; min-height: 0; }
.workspace { display: flex; flex-direction: column; flex: 1; align-self: stretch; min-height: 0; }
#viewChat { width: 100%; max-width: 860px; }
#viewChat.is-active { display: flex; flex-direction: column; flex: 1; min-height: 0; }
/* The head of a screen whose whole body is a conversation, and which does not
   draw its title (see the h2 in the markup). What is left is the sentence that
   introduces an empty screen, and Start over. Top-aligned, with the sentence
   held to a readable measure, they read as one block; once the thread starts
   the sentence goes and the button is all that is left of the band. */
#viewChat .section-head { align-items: flex-start; margin-bottom: 12px; }
#viewChat .section-head-text p { max-width: 48ch; margin-top: 0; }
/* The sentence says what the agent is. It is the introduction to an empty
   screen, and once there is a conversation on the screen it is an
   explanation of something the owner is already doing -- three lines of chrome
   charged against a thread that only had a few to begin with. It goes with the
   empty state, on every width, and comes back with it. */
#viewChat:has(#chatEmpty[hidden]) .section-head-text p { display: none; }
/* On a phone the sentence is hidden, so the button belongs on the one line
   rather than wrapped underneath with a hole above it. */
@media (max-width: 599px) {
  #viewChat .section-head { align-items: center; flex-wrap: nowrap; }
  #viewChat .section-head-text { flex: 1 1 auto; }
  #viewChat #btnChatReset { flex: none; }
}
/* The blank page is a screen in its own right, not a caption sitting where the
   first reply will go. It gets a mark, a line at reading size and the chips
   under it, centred as a block -- so an owner opening the agent for the first
   time is looking at something composed rather than at an empty box with a
   note beside it. */
.chat-empty {
  margin: 0 0 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  padding: 24px 16px 8px;
}
.chat-empty p { margin: 0; max-width: 34ch; font-size: 1rem; line-height: 1.5; color: var(--fg-2); }
/* The flame, quiet: the empty screen is the one place in the portal with room
   for the mark, and it says whose agent this is without a line of copy. */
.chat-empty::before {
  content: "";
  width: 40px;
  height: 40px;
  background: url("/static/img/logo-mark.svg") center / contain no-repeat;
}
/* On the Agent screen the chips are not part of the composer, so nothing
   hides them when the box fills -- the empty screen goes when the thread has
   a line on it, which is the moment they stop being the way in. */
.chat-starters { margin-top: 10px; justify-content: center; }
.chat-thread { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
/* A short conversation sits against the box, the way a messaging app stacks
   from the bottom, rather than hanging from the heading with a hole under it.
   An auto margin on the first line and not justify-content, which clips the
   top of a thread once it is long enough to scroll. */
.chat-turn:first-child { margin-top: auto; }
/* The thread takes the slack, so the conversation fills the screen and the
   rail and the box sit under it. An auto margin here would take that slack
   first -- auto margins are served before flex-grow -- which left the thread
   at the height of its own lines with a hole beneath it. It is still what
   holds the box at the foot before anything has been said. */
.chat-dock { margin-top: 0; }
#chatEmpty:not([hidden]) ~ .chat-dock { margin-top: auto; }
/* On a desk the thread runs the full width of the work so its scrollbar is
   at the edge of the window, not down the middle of the screen at the edge
   of the column. The words keep the column: the gutter is the same sum the
   place bar uses. */
@media (min-width: 900px) {
  /* Every other view is --col wide so a form, a card and a heading share one
     left and one right edge across the whole portal. This one is wider, and
     deliberately: the others are a column of work on a page, while this is the
     screen itself -- the thread, the drop it is about and the box you answer
     in, and at 880px on a wide monitor those sat in a strip with a hand's
     width of empty background down either side. Raising --col here rather
     than writing a second number into each rule below means the thread's
     gutter and the card's edges move together, as they must. */
  #viewChat { --col: 1120px; max-width: none; padding: 16px 0 12px; }
  #viewChat > * { width: calc(100% - 64px); max-width: var(--col); margin-inline: auto; }
  #viewChat > .chat-thread { width: 100%; max-width: none; padding-inline: max(32px, calc((100% - var(--col)) / 2)); }
}

/* A share of the column, up to a measure. The share is what keeps a bubble
   looking like a bubble rather than a full-width band on any screen; the cap
   is because a share of a wide column is a line of prose nobody can track
   back to the start of. The agent gets the longer of the two -- it writes
   paragraphs, the owner writes a sentence. */
.chat-turn {
  max-width: min(78%, 640px);
  padding: 12px 16px;
  border-radius: var(--r-lg);
  /* The thread is the content of this screen, not chrome on the side of it, so
     it reads a size up from the labels and captions elsewhere in the portal --
     reading size, with the tightening a sans wants at that size. */
  font-size: 1rem;
  line-height: 1.4;
  letter-spacing: -0.012em;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.chat-turn.is-user {
  align-self: flex-end;
  background: var(--surface-3);
  color: var(--fg);
  border-bottom-right-radius: var(--r-xs);
}
/* The agent does not get a bubble. It writes paragraphs, lists and cards, and
   a tinted box around those reads as a quoted block rather than as the page
   talking back; the owner's sentence is the thing that needs a shape to sit
   in. So: the reply is just the page's own prose, one step down in weight of
   colour, with the room a paragraph wants. */
.chat-turn.is-assistant {
  align-self: flex-start;
  max-width: min(100%, 820px);
  padding: 2px 4px;
  border-radius: 0;
  background: none;
  color: var(--fg-2);
  line-height: 1.6;
  white-space: normal;
}
/* Only the newest line comes in. The thread is rewritten whole whenever it
   changes, so every bubble is a new element every time -- animating all of
   them would flash the whole conversation on each reply. :last-child is the
   one that was not there a moment ago, and the one before it stops matching
   the moment it is no longer last. */
.chat-thread .chat-turn:last-child { animation: chat-turn-in 0.26s var(--ease) both; }
.chat-thread .chat-turn.is-user:last-child { animation-name: chat-turn-in-right; }
@keyframes chat-turn-in {
  from { opacity: 0; transform: translateY(6px) translateX(-4px); }
  to { opacity: 1; transform: none; }
}
@keyframes chat-turn-in-right {
  from { opacity: 0; transform: translateY(6px) translateX(4px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .chat-thread .chat-turn:last-child { animation: none; }
}
/* A reply arriving word by word rewrites the line on every word, and a line
   that faded in on each of them shook. */
.chat-thread.is-settling .chat-turn:last-child { animation: none; }
.chat-turn p, .compose-reply p { margin: 0; }
.chat-turn p + p, .chat-turn p + ul, .chat-turn p + ol, .chat-turn ul + p, .chat-turn ol + p { margin-top: 10px; }
.compose-reply p + p, .compose-reply p + ul, .compose-reply ul + p { margin-top: 8px; }
.chat-turn ul, .chat-turn ol, .compose-reply ul, .compose-reply ol { margin: 0; padding-left: 1.2em; }
.chat-turn li + li { margin-top: 6px; }
.compose-reply li + li { margin-top: 4px; }
.chat-turn li::marker { color: var(--brand); }
.chat-turn a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.chat-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.chat-chip { cursor: pointer; }
.chat-chip:hover { border-color: var(--brand); color: var(--brand); }
.chat-turn.is-pending {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--fg-muted, #6b6b6b);
}
.chat-step { font-size: 0.82rem; animation: chat-step-in 0.25s var(--ease); }
.chat-dots { display: inline-flex; gap: 4px; }
/* Waiting is the one moment the screen has nothing to say, so it is worth the
   brand's own two colours rather than three grey dots. They swell instead of
   hopping -- a bounce reads as a page still loading, a pulse reads as
   something thinking. */
.chat-dots i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--brand);
  animation: chat-dot 1.2s infinite ease-in-out;
}
.chat-dots i:nth-child(2) { background: var(--navy); animation-delay: 0.2s; }
.chat-dots i:nth-child(3) { background: var(--brand-strong); animation-delay: 0.4s; }
@keyframes chat-dot {
  0%, 60%, 100% { opacity: 0.4; transform: scale(0.9); }
  30% { opacity: 1; transform: scale(1.25); }
}
@keyframes chat-step-in { from { opacity: 0; transform: translateX(-4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .chat-dots i { animation: chat-dot-fade 1.2s infinite ease-in-out; }
  .chat-step { animation: none; }
  .chat-cursor { animation: none; }
}
@keyframes chat-dot-fade { 0%, 60%, 100% { opacity: 0.4; } 30% { opacity: 1; } }

/* The steps of a turn stack: what it has done, quiet and ticked, over what it
   is doing. */
.chat-work { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
/* The reply being written is the reply, in its own colour and size, not one
   more status line in the grey the steps use; a hair of air between them. */
.chat-turn.is-pending .chat-text { color: var(--fg); font-size: 1rem; margin-top: 3px; }
/* The reply being written: a cursor at the end of the last word. */
.chat-cursor {
  display: inline-block; width: 2px; height: 1em; margin-left: 2px; vertical-align: text-bottom;
  background: currentColor; animation: chat-cursor-blink 1s steps(2, start) infinite;
}
@keyframes chat-cursor-blink { to { visibility: hidden; } }
.chat-live { display: flex; align-items: center; gap: 10px; }
.chat-step.is-done { display: flex; align-items: center; gap: 6px; opacity: 0.6; }
.chat-step.is-done svg { width: 11px; height: 11px; flex: none; color: var(--brand); }

/* A message that never landed: their own words, kept, but plainly not sent. */
.chat-turn.is-user.is-failed {
  background: transparent;
  color: var(--fg-muted, #6b6b6b);
  border: 1px dashed var(--line-strong, #d0c8bc);
}
.chat-turn.is-failed .chat-chips { margin-top: 6px; }

/* Scrolled up, something arrived: a pill over the foot of the thread rather
   than a jump the owner did not ask for. */
.chat-jump {
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: -34px auto 8px;
  position: relative;
  z-index: 2;
  padding: 0.4em 0.9em;
  border: 1px solid var(--line, #e4dfd6);
  border-radius: var(--r-pill);
  background: var(--surface, #fff);
  box-shadow: var(--shadow-2, 0 4px 12px rgba(22, 32, 58, 0.12));
  color: var(--fg);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}
@media (min-width: 900px) { #viewChat > .chat-jump { width: auto; } }

/* The drop the conversation is about, between the thread and the box: it is
   the subject of every follow-up, so it sits where the follow-ups are typed
   rather than one tab away. Shut it is a strip; opened, it is the reel itself,
   because the Agent screen used to be the one place that would not show the
   owner what the agent had just made. */
.chat-drop { margin: 0 0 10px; }
.chat-drop-card {
  padding: 10px;
  border: 1px solid var(--line, #e4dfd6);
  border-radius: var(--r-md);
  background: var(--surface, #fff);
  box-shadow: var(--shadow-1);
}
/* The rail arrives when the agent starts something. Coming up from under the
   box rather than appearing between two frames is what makes it read as a
   consequence of the message they just sent.

   On the class and not on the card, because the card is a fresh element every
   time anything about the drop changes -- a second ticking off the estimate,
   a change spent. Left on the element, the entrance played four times a minute
   while the owner watched a render, which is a flicker, not an arrival. */
.chat-drop-card.is-new { animation: chat-rail-in 0.28s var(--ease) both; }
.chat-drop-row { display: flex; gap: 12px; align-items: flex-start; }

/* The corner of it. 9:16 whatever is inside, so a reel and a still sit at the
   same size and the row does not jump when the render replaces the upload. */
.chat-drop-shot {
  position: relative;
  width: 62px;
  aspect-ratio: 9 / 16;
  flex: none;
  padding: 0;
  border: 0;
  overflow: hidden;
  border-radius: var(--r-sm);
  background: var(--surface-3, #e8e3da);
}
.chat-drop-shot > video, .chat-drop-shot > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.35s var(--ease), filter 0.35s var(--ease);
}
/* Nothing has come back yet: their own upload, held down, so the card is about
   their plate from the first second and still reads as unfinished. */
.chat-drop-shot.is-waiting > video, .chat-drop-shot.is-waiting > img {
  filter: saturate(0.5) brightness(0.72);
}
.chat-drop-shot.is-ready { cursor: pointer; }
/* Playing: the same element, wider. A reel keeps 9:16; a still is 4:5 and is
   let alone rather than cropped, because this is the frame being approved. */
.chat-drop-shot.is-open {
  width: clamp(132px, 34vh * 9 / 16, 176px);
  background: #16120f;
  animation: chat-shot-open 0.3s var(--ease) both;
}
.chat-drop-shot.is-open.is-still { aspect-ratio: 4 / 5; width: clamp(148px, 30vh * 4 / 5, 200px); }
.chat-drop-shot.is-open > video, .chat-drop-shot.is-open > img { object-fit: contain; }
.chat-drop-shot.is-open > img { cursor: zoom-in; }
.chat-drop-shut {
  position: absolute;
  top: 5px;
  right: 5px;
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(20, 16, 12, 0.6);
  backdrop-filter: blur(3px);
  color: #fff;
  cursor: pointer;
  transition: background 0.2s var(--ease);
}
.chat-drop-shut svg { width: 11px; height: 11px; }
.chat-drop-shut:hover { background: var(--brand); }
.chat-drop-shot.is-ready:hover > video, .chat-drop-shot.is-ready:hover > img { transform: scale(1.06); }
.chat-drop-shot:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* A sweep across the held-down upload, so the strip says "still working" from
   across the room. The bar under the line says how far along; this only has to
   say it is moving. */
.chat-drop-sweep {
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, 0.34) 50%, transparent 70%);
  transform: translateX(-100%);
  animation: chat-sweep 1.9s var(--ease) infinite;
}

.chat-drop-disc {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(20, 16, 12, 0.56);
  backdrop-filter: blur(3px);
  color: #fff;
  transition: transform 0.2s var(--ease), background 0.2s var(--ease);
}
/* The nudge right is the play triangle's own weight, which sits left of the
   glyph's box. */
.chat-drop-disc svg { width: 12px; height: 12px; margin-left: 1px; }
.chat-drop-shot.is-ready:hover .chat-drop-disc { transform: scale(1.12); background: var(--brand); }

.chat-drop-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
/* Watching it, there is room to read the whole caption beside the player, and
   the column runs the height of it so the follow-ups sit at its foot rather
   than stranded halfway up a tall card. */
.chat-drop-card.is-watching .chat-drop-body { align-self: stretch; }
.chat-drop-card.is-watching .chat-drop-why { -webkit-line-clamp: 6; }
.chat-drop-card.is-watching .chat-drop-note { margin-top: auto; }
.chat-drop-head { display: flex; align-items: center; gap: 8px; }
.chat-drop-head b { font-size: 0.86rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-drop-open {
  /* What it is and where it stands are one fact, so they stay together on the
     left; only the way out goes to the far edge. The whole group used to be
     pushed right as a block, which on a wide card left the title alone at one
     end and its own status a foot away at the other. */
  margin-left: auto;
  flex: none;
  border: 0;
  padding: 0;
  background: none;
  color: var(--fg-muted, #6b6b6b);
  font: inherit;
  font-size: 0.78rem;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.chat-drop-open:hover { color: var(--brand); }
.chat-drop-why {
  font-size: 0.82rem;
  color: var(--fg-muted, #6b6b6b);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.chat-drop-why.is-live { display: flex; align-items: center; gap: 8px; color: var(--fg); }
.chat-drop-eta { color: var(--fg-muted, #6b6b6b); }
.chat-drop-bar {
  display: block;
  height: 3px;
  border-radius: var(--r-pill);
  background: var(--surface-3, #e8e3da);
  overflow: hidden;
}
.chat-drop-bar i { display: block; height: 100%; background: var(--brand); transition: width 0.6s var(--ease); }
.chat-drop-note { margin: 0; font-size: 0.75rem; color: var(--fg-muted, #6b6b6b); }

/* The follow-ups come in behind the card rather than with it, left to right,
   so the eye lands on the drop and then on what can be done to it. Only on the
   two renders worth an entrance, for the same reason as the card. */
.chat-drop-card.is-new .chat-chip, .chat-drop-card.is-landed .chat-chip {
  animation: chat-chip-in 0.24s var(--ease) both;
  animation-delay: calc(60ms + var(--i, 0) * 32ms);
}

/* It finished while they were watching it finish. One pass of brand light
   around the card is the whole announcement: the player opening under it is
   the rest. */
.chat-drop-card.is-landed { animation: chat-landed 0.72s var(--ease) both; }

@keyframes chat-rail-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}
@keyframes chat-shot-open {
  from { opacity: 0.3; transform: scale(0.88); transform-origin: left center; }
  to { opacity: 1; transform: none; }
}
@keyframes chat-chip-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}
@keyframes chat-sweep {
  0% { transform: translateX(-100%); }
  60%, 100% { transform: translateX(100%); }
}
@keyframes chat-landed {
  0% { border-color: var(--brand); box-shadow: 0 0 0 0 var(--brand-wash); }
  40% { border-color: var(--brand); box-shadow: 0 0 0 5px var(--brand-wash); }
  100% { box-shadow: var(--shadow-1); }
}

/* Motion is the wrong way to tell someone something when they have asked for
   none of it. The states stay; only the movement goes. */
@media (prefers-reduced-motion: reduce) {
  .chat-drop-card, .chat-drop-shot.is-open, .chat-drop-card .chat-chip { animation: none; }
  .chat-drop-sweep { animation: none; opacity: 0.25; }
  .chat-drop-shot.is-ready:hover > video, .chat-drop-shot.is-ready:hover > img { transform: none; }
}

/* On a phone the rail, the box and the heading between them will take the
   whole screen if they are allowed to: the thread is the flexible one, so it
   is the one that gets squeezed, and it went to nothing. The rail gives up the
   second line of caption and puts its follow-ups on one scrolling row, and the
   thread keeps a floor under it. */
@media (max-width: 599px) {
  /* The standing explanation of what the agent is sits above a conversation
     that is already the answer to it, and on a phone it is the difference
     between a thread you can read and one squeezed to nothing. */
  #viewChat .section-head-text p { display: none; }
  .chat-drop-card { padding: 8px; }
  .chat-drop-row { gap: 10px; }
  .chat-drop-shot { width: 50px; }
  .chat-drop-why { -webkit-line-clamp: 1; }
  .chat-drop .chat-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: 2px;
  }
  .chat-drop .chat-chips::-webkit-scrollbar { display: none; }
  .chat-drop .chat-chip { flex: none; }
  /* The player is the reason to be on this screen, so it keeps a size worth
     watching even where the card has least room -- but the box under it has to
     stay reachable, so it gives up what the taller screen could afford. */
  .chat-drop-shot.is-open { width: clamp(100px, 26vh * 9 / 16, 128px); }
  .chat-drop-shot.is-open.is-still { width: clamp(112px, 23vh * 4 / 5, 148px); }
  /* Beside the player there is half a card's width for the head, which is not
     enough for a format name, a pill and a way out on one line. */
  .chat-drop-card.is-watching .chat-drop-head { flex-wrap: wrap; }
  .chat-drop-card.is-watching .chat-drop-head b { white-space: normal; margin-right: 0; flex: 1 0 100%; }
}
#viewChat.is-active > .chat-thread:not([hidden]) { min-height: 110px; }
/* The one follow-up that is most likely the next thing they want, coloured
   like the button it stands in for on the card. */
.chat-chip.is-primary { border-color: var(--brand); background: var(--brand); color: var(--on-brand, #fff); }
.chat-chip.is-primary:hover { border-color: var(--brand); color: var(--on-brand, #fff); filter: brightness(1.06); }
.chat-chip.is-quiet { color: var(--fg-muted, #6b6b6b); }

.compose-reply {
  margin: 12px 0 0;
  padding: 10px 14px;
  border-radius: var(--r-md);
  border-top-left-radius: var(--r-xs);
  background: var(--brand-wash);
  color: var(--fg);
  font-size: 0.88rem;
  line-height: 1.5;
}
.compose-reply.is-bad { background: var(--bad-wash); }

.compose-foot {
  margin: 12px 2px 0;
  font-size: 0.78rem;
  line-height: 1.45;
  color: var(--fg-3);
}
/* The allowance, as the AI tools show theirs: a thin bar, the count, when
   it resets. One row, one line, quiet until it runs low. */
.compose-usage {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 18px;
  margin: 10px 2px 0;
  font-size: 0.78rem;
  color: var(--fg-3);
}
.usage-meter {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.usage-meter[hidden] { display: none; }
.usage-label { white-space: nowrap; }
.usage-label b { color: var(--fg); font-weight: 600; }
.usage-bar {
  display: inline-block;
  width: 96px;
  height: 4px;
  border-radius: 2px;
  background: var(--surface-3);
  overflow: hidden;
}
.usage-bar i {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--fg-3);
  transition: width 0.3s var(--ease);
}
.usage-reset { color: var(--fg-4); white-space: nowrap; }
.usage-meter.is-low .usage-bar i { background: var(--warn); }
.usage-meter.is-low .usage-label b { color: var(--warn); }
.usage-meter.is-out .usage-bar i { background: var(--bad); }
.usage-meter.is-out .usage-label { color: var(--bad); font-weight: 600; }
.usage-note {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-left: auto;
  color: var(--fg-4);
}
.usage-note svg { color: var(--fg-4); }
@media (max-width: 560px) { .usage-note { margin-left: 0; } }
.composer .is-other-kind { display: none; }

@media (max-width: 560px) {
  .compose-tools .dropzone b { display: none; }
  .compose-tools .dropzone { padding: 8px; }
  .compose-send { flex: 1 1 100%; }
}

.drop-section .create { margin-bottom: 0; }

/* ---------------- Ideas, next to the buttons that need them ----------------
   The catalogue itself lives in Studio and is browsed there. This is a look at
   it from the one screen where "what should I film?" is the actual question. */

.idea-strip {
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}

.idea-strip[hidden] { display: none; }

.idea-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.idea-head b {
  display: block;
  font-size: 0.92rem;
  font-weight: 620;
  letter-spacing: -0.01em;
}

.idea-head span {
  display: block;
  margin-top: 2px;
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--fg-3);
}

.idea-head .btn { flex: none; }

/* Scrolls sideways: a second row would push the two things you came here to
   press off the top of the screen. */
.idea-rail {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding-bottom: 6px;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
}

.idea-rail::-webkit-scrollbar { display: none; }

.idea-card {
  flex: 0 0 152px;
  scroll-snap-align: start;
}

/* The hook is set for a card twice this wide; at rail size it needs to come
   down or every one of them wraps to six lines. */
.idea-card .format-frame { padding: 26px 11px 11px; }

.idea-card .format-frame-hook { font-size: 0.82rem; }

/* "Example clip" fits beside the hook on the gallery card and lands on top of
   it here. The strip's own heading already says these play. */
.idea-card .format-film-tag { display: none; }

.idea-card .format-body {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 9px 10px;
}

/* The rail's whole point once a format can actually be used. Full width so it
   is a target on a phone, not a word to aim at, and pushed to the floor for the
   same reason the gallery's is: a one-line name and a two-line name otherwise
   put their buttons at different heights along the row. */
.idea-use {
  justify-content: center;
  margin-top: auto;
}

/* Always there, never on hover. Revealing it on hover hid the one thing the
   gallery is now for behind a gesture that does not exist on a phone and that
   nobody performs on a card they were only reading.

   margin-top:auto, not a fixed gap: the beats above run from two lines to five,
   so a button spaced off the text lands at a different height on every card and
   a row of them reads as crooked. Pinned to the bottom, they line up across the
   row whatever the copy does — the same thing .drop-body does with its
   actions. */
.format-use {
  width: 100%;
  justify-content: center;
  margin-top: auto;
  border-color: var(--line-strong);
}

.format-card:hover .format-use {
  border-color: var(--brand);
  color: var(--brand);
}

/* The chosen format's beats, under the picker: an instruction, not a caption,
   so it gets the weight of one. */
.hint.is-brief {
  color: var(--fg-2);
  border-left: 2px solid var(--brand);
  padding-left: 9px;
}

.idea-card .format-body h3 {
  font-size: 0.79rem;
  line-height: 1.25;
}

@media (max-width: 420px) {
  .idea-head { flex-direction: column; align-items: flex-start; gap: 8px; }
  .idea-card { flex-basis: 132px; }
}

@keyframes kd-create-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}

.make-foot {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin: 10px 2px 0;
  font-size: 0.78rem;
  color: var(--fg-3);
  line-height: 1.4;
}

.make-foot svg { flex: none; margin-top: 2px; color: var(--fg-4); }

/* ---------------- Where the picture comes from ----------------
   A photo and a drawing are the same post with a different camera, so this is a
   source switch inside one sheet, not a choice between two products. */
.seg {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 3px;
  padding: 3px;
  margin-bottom: 18px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: 1px solid var(--line);
}

.seg-btn {
  padding: 8px 10px;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  font: inherit;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--fg-3);
  cursor: pointer;
  transition: background-color 0.15s var(--ease), color 0.15s var(--ease);
}

.seg-btn:hover { color: var(--fg); }

.seg-btn.is-on {
  background: var(--surface);
  color: var(--fg);
  font-weight: 600;
  box-shadow: var(--shadow-1);
}

.seg-btn:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

/* ---------------- Photo post: what happens to the picture ---------------- */
.mode-choices { display: grid; gap: 8px; }
.mode-choice {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 12px;
  border: 1px solid var(--line, #e6e2db);
  border-radius: 10px;
  cursor: pointer;
}
.mode-choice input { margin-top: 3px; accent-color: var(--brand, #dd5a1f); }
.mode-choice b { display: block; font-size: 14px; }
.mode-choice em {
  display: block; font-style: normal;
  font-size: 12.5px; color: var(--fg-3, #7b8494); line-height: 1.4; margin-top: 1px;
}
.mode-choice:has(input:checked) {
  border-color: var(--brand, #dd5a1f);
  background: var(--brand-soft, #fdeee6);
}

/* ---------- A row of three, when three stacked cards is too much ----------
   Same radios as .mode-choice, but for a secondary decision whose options are
   a couple of words each. Three of those as full-width cards with two lines of
   prose apiece took more of the sheet than the photo they were about. */

.pick-row {
  display: grid;
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.pick {
  position: relative;
  display: block;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  text-align: center;
  transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease);
}

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

.pick b {
  display: block;
  font-size: 0.83rem;
  font-weight: 600;
}

.pick em {
  display: block;
  margin-top: 2px;
  font-style: normal;
  font-size: 0.71rem;
  line-height: 1.3;
  color: var(--fg-3);
}

.pick:hover { border-color: var(--line-strong); }

.pick-row:disabled .pick { opacity: 0.45; cursor: default; }
.pick-row:disabled .pick:hover { border-color: var(--line); }

.pick:has(input:checked) {
  border-color: var(--brand);
  background: var(--brand-wash);
}

.pick:has(input:checked) b { color: var(--brand); }

.pick:has(input:focus-visible) {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* Two words each is tight at three across on a small phone. */
@media (max-width: 380px) {
  .pick-row { grid-template-columns: 1fr; }
  .pick { text-align: left; }
}

/* ---------- Settings that have a right default ----------




.more-options[open] > summary svg { transform: rotate(180deg); }

.more-options[open] > summary {
  border-bottom: 1px solid var(--line);
}


.more-options[open] > summary + * { padding-top: 14px; }


/* A caveat the owner has to read once, where the thing it warns about is



/* ---------------- Drop sections ----------------
   Making a drop and judging the ones you already made are two different jobs.
   They are switched from the nav rather than stacked, so only ever one is on
   screen; what is left here is that one's masthead. */

.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
  margin-bottom: 16px;
}

.section-head-text {
  flex: 1 1 260px;
  min-width: 0;
}

/* A short brand rule above the title — the mark that says this heading names
   the whole view rather than the first card under it. */
.section-head-text h2::before {
  content: "";
  display: block;
  width: 22px;
  height: 3px;
  margin-bottom: 9px;
  border-radius: var(--r-pill);
  background: var(--brand);
}

/* Bigger than the card headings inside the band, so the hierarchy is obvious:
   this names the section, those name the things in it. */
.section-head-text h2 {
  font-family: var(--sans);
  font-size: 1.16rem;
  font-weight: 650;
  letter-spacing: -0.02em;
  margin: 0;
}

/* One line of what the band is for. It drops away on a phone, where the
   heading and the controls under it already say it. */
.section-head-text p {
  /* A line of explanation is prose, and prose set to whatever width the
     column happens to be runs past 100 characters on a desktop, which is
     roughly twice what stays readable. */
  max-width: 62ch;
  margin: 3px 0 0;
  font-size: 0.81rem;
  line-height: 1.5;
  color: var(--fg-3);
}

.create-active-section {
  margin-top: 24px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  animation: kd-create-in 0.2s var(--ease);
}

.create-active-section[hidden] { display: none; }

/* Done: the pulse stops, the heading turns the colour of "Needs a yes". */
.create-active-section.is-done .agent-pulse-dot { animation: none; background: var(--gold); }
.create-active-section.is-done h3 { color: var(--gold); }

.create-active-title-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.create-active-title-row h3 {
  font-family: var(--sans);
  font-size: 1.05rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  margin: 0;
}

.feed-tools {
  display: flex;
  align-items: center;
  gap: 2px;
}

.feed-filters {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  padding: 2px;
  margin: -2px -2px 12px;
}

/* display:flex above outranks the UA's [hidden] rule, so collapsing the feed
   left its filters sitting there. */
.feed-filters[hidden] { display: none; }

.feed-filters::-webkit-scrollbar { display: none; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  font: inherit;
  font-size: 0.79rem;
  font-weight: 500;
  padding: 5px 11px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--fg-2);
  cursor: pointer;
  transition: background-color 0.15s var(--ease), border-color 0.15s var(--ease), color 0.15s var(--ease);
}

.chip:hover { border-color: var(--line-strong); }

.chip.is-on {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

.chip:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.chip-count {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  padding: 1px 5px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--fg-3);
}

.chip.is-on .chip-count {
  background: rgba(255, 255, 255, 0.22);
  color: #fff;
}

/* The last cell of the grid, not a block beneath it. Running out of drops and
   finding the way to more in the same place you were already looking beats a
   full-width button under content you may never have scrolled to the end of.
   It takes a cell like a card so the grid stays regular, but it is dashed and
   empty so it does not read as one. */
.feed-more {
  display: grid;
  place-items: center;
  gap: 8px;
  min-height: 200px;
  padding: 16px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
  background: transparent;
  color: var(--fg-2);
  font-size: 0.84rem;
  font-weight: 600;
  line-height: 1.4;
  text-align: center;
  cursor: pointer;
  transition: background-color 0.15s var(--ease), border-color 0.15s var(--ease),
    color 0.15s var(--ease);
}

.feed-more:hover {
  border-color: var(--brand);
  background: var(--brand-wash);
  color: var(--brand-strong);
}

.feed-more:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.feed-more[hidden] { display: none; }

.feed-hidden-note {
  padding: 16px;
  border: 1px dashed var(--line);
  border-radius: var(--r-md);
  text-align: center;
  font-size: 0.82rem;
  color: var(--fg-3);
}

/* A quiet remove on each drop — destructive, so it never competes with the
   approve action for attention. It stays visible rather than appearing on
   hover: on a phone there is no hover to reveal it with. */
.drop-remove {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  pointer-events: auto;
  padding: 6px;
  border: 0;
  border-radius: var(--r-pill);
  background: rgba(10, 14, 24, 0.5);
  color: rgba(255, 255, 255, 0.88);
  cursor: pointer;
  display: inline-grid;
  place-items: center;
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: background-color 0.16s var(--ease), color 0.16s var(--ease), opacity 0.2s var(--ease);
}
.drop-remove:hover { background: var(--bad); color: #fff; }
.drop-remove:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }


/* ---- Intel: claiming what the scan found ---- */

.claim-banner {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 16px;
  margin-bottom: 16px;
  background: var(--warn-wash);
  border: 1px solid var(--warn-wash);
  border-left: 3px solid var(--warn);
  border-radius: var(--r-md);
}

.claim-banner-text b {
  display: block;
  font-size: 14px;
  color: var(--warn);
}

.claim-banner-text span {
  display: block;
  margin-top: 3px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg-2);
}

.claim-banner-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.claim-verified-note {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  margin-bottom: 16px;
  border-radius: var(--r-sm);
  background: var(--good-wash, var(--surface-2));
  color: var(--good);
  font-size: 0.8rem;
  font-weight: 500;
}

.claim-verified-note svg { flex-shrink: 0; }

.deal-claim-btn { white-space: nowrap; }

/* ---- Intel: menu knowledge base ---- */

.menu-card-summary:not(:empty) { margin-bottom: 12px; }

.menu-summary-meta {
  font-size: 0.78rem;
  color: var(--fg-3);
}

.menu-cat-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin-bottom: 14px;
}

.menu-cat-chips:empty { display: none; }

.menu-cat-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--fg-2);
  font-size: 0.76rem;
  font-weight: 500;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease);
}

.menu-cat-chip:hover { border-color: var(--line-strong); }

.menu-cat-chip.is-active {
  border-color: var(--brand);
  background: var(--brand-wash);
  color: var(--brand-strong);
}

.menu-cat-chip b {
  color: var(--fg-3);
  font-weight: 600;
  font-size: 0.72rem;
}

.menu-cat-chip.is-active b { color: var(--brand-strong); }

/* Newspaper columns, not a grid: grid rows are as tall as their tallest cell,
   so a short section next to a long one left a hole the height of the long one.
   Columns flow, and a section is kept whole inside one of them. */
.menu-list {
  column-width: 330px;
  column-gap: 34px;
}

.menu-cat-block { break-inside: avoid; }

/* One column while a dish is open, so the form has the page's full width. */
.menu-list.is-editing { columns: 1; }

.menu-cat-block + .menu-cat-block { margin-top: 20px; }

.menu-cat-title {
  margin: 0 0 8px;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--fg-3);
  border-bottom: 1px solid var(--line);
  padding-bottom: 6px;
}

.menu-item-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 0;
}

.menu-item-row + .menu-item-row { border-top: 1px dashed var(--line); }

.menu-item-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.menu-item-photo {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--line);
}

.menu-item-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.menu-item-main { flex: 1; }

.menu-item-name {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--fg-1);
}

.menu-item-desc {
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--fg-2);
}

.menu-item-tags {
  font-size: 0.7rem;
  color: var(--fg-3);
}

.menu-item-price {
  flex-shrink: 0;
  font-family: "JetBrains Mono", monospace;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--fg-1);
}

/* ---- Menu: the owner's own edits ---- */

.menu-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-bottom: 12px;
}

.menu-toolbar .input { flex: 1 1 200px; min-width: 0; }
.menu-toolbar .btn { flex-shrink: 0; }

.menu-item-side {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}

.menu-row-edit {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  color: var(--fg-3);
  opacity: 0.55;
}

.menu-item-row:hover .menu-row-edit,
.menu-row-edit:focus-visible { opacity: 1; }

/* A touch screen has no hover to reveal it with. */
@media (hover: none) {
  .menu-row-edit { opacity: 1; }
}

/* Two columns of fields when the sheet is wide, one when it is not — a form
   this tall pushes its own Save button off a laptop screen otherwise. */
.menu-item-edit {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  align-items: start;
  gap: 12px 20px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

/* auto-fit, not two fixed halves: on a phone these become one field per line
   instead of two boxes too narrow to read a dish name in. */
.menu-item-edit .field { margin-bottom: 0; }
.menu-item-edit .field-pair { gap: 0 14px; }

.menu-edit-title,
.menu-edit-actions { grid-column: 1 / -1; }

.menu-edit-title {
  margin: 0 0 2px;
  font-size: 0.95rem;
  font-weight: 600;
}

.menu-edit-flags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  font-size: 0.78rem;
  color: var(--fg-2);
}

.menu-edit-flags label {
  display: flex;
  align-items: center;
  gap: 6px;
}

.menu-edit-photo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.menu-edit-photo img,
.menu-edit-photo-empty {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  border-radius: var(--r-sm);
  background: var(--surface);
  border: 1px dashed var(--line);
  object-fit: cover;
  font-size: 0.66rem;
  color: var(--fg-3);
  text-align: center;
}

.menu-edit-photo img { border-style: solid; }

/* The native file input is a grey box with a filename in it. The label is the
   button; the input only has to be reachable, not looked at. */
.menu-edit-photo label { cursor: pointer; }
.menu-edit-photo .hint { flex: 1 1 200px; margin-top: 0; }

.menu-edit-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-top: 2px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.menu-edit-del { margin-left: auto; color: var(--bad); }

/* ---- Profile: what came from the web, and what the owner owns ---- */

.found-banner {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-start;
  padding: 14px 16px;
  margin-bottom: 14px;
  background: var(--warn-wash);
  border: 1px solid var(--warn-wash);
  border-left: 3px solid var(--warn);
  border-radius: var(--r-md);
}

.found-banner-text { flex: 1 1 240px; min-width: 0; }

.found-banner-btn {
  flex-shrink: 0;
  align-self: center;
  border-color: var(--warn);
  color: var(--warn);
}

.found-banner b { display: block; font-size: 14px; color: var(--warn); }

.found-banner span {
  display: block;
  margin-top: 3px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg-2);
}

/* Sits on the label of whichever field it belongs to, so the claim is next to
   the value it is making a claim about rather than in a list somewhere. */
.found-chip {
  margin-left: 8px;
  padding: 1px 8px;
  border: 1px solid var(--warn);
  border-radius: var(--r-pill);
  background: var(--warn-wash);
  color: var(--warn);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  vertical-align: middle;
  cursor: help;
}

/* ---- Profile: identity ---- */

.profile-identity {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr);
  gap: 20px;
  align-items: start;
}

.profile-identity-fields { display: grid; gap: 0; }

.logo-slot { display: grid; gap: 8px; }

.logo-frame,
.logo-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-md);
  min-height: 130px;
  padding: 16px;
}

/* The closing card is drawn on this exact panel colour, so a white wordmark
   previews the way it will actually appear rather than as a broken image. */
.logo-frame {
  background: #111418;
  border: 1px solid var(--line);
}

.logo-frame img { max-width: 100%; max-height: 110px; object-fit: contain; }

.logo-empty {
  flex-direction: column;
  gap: 8px;
  border: 1px dashed var(--line-strong);
  color: var(--fg-3);
  font-size: 12px;
}

.logo-empty svg { width: 26px; height: 26px; }

.logo-actions { display: grid; gap: 6px; }

@media (max-width: 620px) {
  .profile-identity { grid-template-columns: minmax(0, 1fr); }
}

/* ==========================================================================
   Memory & Content Strategy Dashboard
   ========================================================================== */

.memory-hero-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px 18px;
  margin-bottom: 16px;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  box-shadow: var(--shadow-1);
}

@media (min-width: 480px) {
  .memory-hero-card {
    flex-direction: row;
    align-items: flex-start;
    justify-content: space-between;
  }
}

.memory-hero-content {
  flex: 1;
  min-width: 0;
}

.memory-hero-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  margin-top: 6px;
}

@media (min-width: 480px) {
  .memory-hero-actions {
    margin-top: 0;
  }
}

.memory-status-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}

.memory-strategy-summary {
  margin-top: 10px;
  padding: 8px 12px;
  background: var(--surface-2);
  border-radius: var(--r-sm);
  border: 1px solid var(--line-soft);
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--fg-2);
  font-style: italic;
}

/* Intent Mix Meters */
.intent-mix-grid {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 12px;
}

.mix-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.mix-caption {
  margin-top: 12px;
}

.mix-row-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.mix-row-title-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
}

.mix-row-title {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--fg-1);
}

.mix-row-stats {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8rem;
  color: var(--fg-3);
}

.mix-row-stats b {
  color: var(--fg-1);
}

.mix-deficit-badge {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 0.72rem;
  font-weight: 600;
  background: var(--warn-wash);
  color: var(--warn);
  border: 1px solid rgba(168, 106, 18, 0.25);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.mix-bar-wrap {
  position: relative;
  width: 100%;
  height: 8px;
  background: var(--surface-3);
  border-radius: var(--r-pill);
  overflow: visible;
}

.mix-bar-fill {
  height: 100%;
  border-radius: var(--r-pill);
  transition: width 0.4s var(--ease);
}

.mix-bar-fill {
  background: var(--intent, var(--brand));
}

.mix-target-marker {
  position: absolute;
  top: -3px;
  width: 2px;
  height: 14px;
  background: var(--fg-1);
  border-radius: 1px;
  transform: translateX(-50%);
  pointer-events: none;
  z-index: 2;
  box-shadow: 0 0 0 1px var(--surface);
}

/* Coverage Gaps */
/* Four short lists stacked ran the card down the page; they are read side by
   side once there is width for it. */
.coverage-sections {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 12px;
  margin-top: 12px;
}

.coverage-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 14px;
  background: var(--surface-2);
  border-radius: var(--r-sm);
  border: 1px solid var(--line-soft);
}

.coverage-sec-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 0.84rem;
  color: var(--fg-1);
}

.coverage-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.coverage-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 9px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-size: 0.78rem;
  color: var(--fg-1);
  font-weight: 500;
}

.coverage-chip.is-signature {
  border-color: var(--brand);
  background: var(--brand-wash);
  color: var(--brand-strong);
}

.coverage-chip.is-stale {
  border-color: rgba(168, 106, 18, 0.3);
  background: var(--warn-wash);
  color: var(--warn);
}

/* The remainder is a count, not an item — it must not look tappable like the
   dish chips beside it. */
.coverage-chip.is-more {
  background: none;
  border-style: dashed;
  color: var(--fg-3);
  font-weight: 500;
}

.coverage-chip .chip-sub {
  font-size: 0.72rem;
  color: var(--fg-3);
  font-weight: normal;
}

.recent-hooks-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.hook-quote-pill {
  padding: 6px 10px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  font-size: 0.8rem;
  color: var(--fg-2);
  font-style: italic;
  line-height: 1.35;
}

.none-yet-inline {
  font-size: 0.8rem;
  color: var(--fg-4);
  font-style: italic;
}

/* Suggestions & Pitches */
.pitches-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 12px;
}

.pitch-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  position: relative;
  transition: border-color 0.15s ease;
}

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

.pitch-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

.pitch-badges-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.pitch-agent-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: 4px;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.pitch-agent-pill {
  background: var(--intent-wash, var(--surface-3));
  color: var(--intent, var(--fg-2));
}

.pitch-headline {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--fg-1);
  line-height: 1.35;
}

.pitch-rationale {
  font-size: 0.82rem;
  color: var(--fg-2);
  line-height: 1.4;
  background: var(--surface);
  padding: 8px 10px;
  border-radius: 6px;
  border-left: 2px solid var(--brand);
}

.pitch-angle {
  font-size: 0.82rem;
  color: var(--fg-3);
  line-height: 1.4;
}

.pitch-hook-box {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 0.84rem;
  color: var(--fg-1);
  font-style: italic;
}

.pitch-hook-box b {
  font-style: normal;
  font-size: 0.75rem;
  color: var(--fg-3);
  text-transform: uppercase;
}

.pitch-shotlist-box {
  padding: 8px 10px;
  background: var(--surface);
  border: 1px dashed var(--line);
  border-radius: 6px;
  font-size: 0.78rem;
  color: var(--fg-2);
  line-height: 1.4;
}

.pitch-shotlist-box b {
  display: block;
  font-size: 0.74rem;
  color: var(--fg-3);
  text-transform: uppercase;
  margin-bottom: 2px;
}

.pitch-meta-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
  padding-top: 4px;
  border-top: 1px solid var(--line-soft);
}

.pitch-tags {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.pitch-tag {
  font-size: 0.74rem;
  color: var(--fg-3);
  background: var(--surface);
  padding: 2px 6px;
  border-radius: 4px;
  border: 1px solid var(--line-soft);
}

.pitch-actions {
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Episodic Memory Feed */
.memory-filter-bar {
  margin-bottom: 12px;
  overflow-x: auto;
  padding-bottom: 4px;
}

.memory-feed {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.memory-card {
  display: grid;
  grid-template-columns: 88px minmax(0, 1fr);
  gap: 14px;
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  transition: border-color 0.15s ease;
}

@media (max-width: 440px) {
  .memory-card {
    grid-template-columns: 72px minmax(0, 1fr);
    gap: 10px;
    padding: 10px 12px;
  }
}

.memory-card-thumb-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--surface-3);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.memory-card-thumb-wrap img,
.memory-card-thumb-wrap video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.memory-card-play-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.65);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(4px);
}

.memory-card-content {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.memory-card-meta-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  flex-wrap: wrap;
}

.memory-card-pills {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
}

.memory-pill-intent {
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
}

.memory-pill-intent {
  background: var(--intent-wash, var(--surface-3));
  color: var(--intent, var(--fg-2));
}

.memory-pill-source {
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 0.68rem;
  background: var(--surface-3);
  color: var(--fg-3);
  font-weight: 500;
}

.memory-pill-dish {
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 0.72rem;
  background: var(--surface);
  color: var(--fg-1);
  font-weight: 500;
  border: 1px solid var(--line-soft);
}

.memory-card-time {
  font-size: 0.72rem;
  color: var(--fg-3);
  flex-shrink: 0;
}

.memory-card-hook {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--fg-1);
  line-height: 1.35;
}

.memory-card-caption {
  font-size: 0.78rem;
  color: var(--fg-2);
  line-height: 1.38;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}

.memory-card-stats {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 2px;
  font-size: 0.74rem;
  color: var(--fg-3);
}

.memory-stat-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* Standing Notes */
.add-note-form {
  margin-top: 12px;
  padding: 12px 14px;
  background: var(--surface-2);
  border-radius: var(--r-sm);
  border: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.standing-notes-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 14px;
}

.standing-note-card {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
}

.standing-note-main {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.standing-note-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.72rem;
}

.standing-note-badge {
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
}

.standing-note-badge[data-kind="preference"] {
  background: var(--brand-wash);
  color: var(--brand-strong);
}

.standing-note-badge[data-kind="constraint"] {
  background: var(--bad-wash);
  color: var(--bad);
}

.standing-note-badge[data-kind="fact"] {
  background: var(--navy-wash);
  color: var(--navy-strong);
}

.standing-note-badge[data-kind="observation"] {
  background: var(--good-wash);
  color: var(--good);
}

.standing-note-source {
  color: var(--fg-4);
}

.standing-note-text {
  font-size: 0.84rem;
  color: var(--fg-1);
  line-height: 1.4;
}

.btn-note-delete {
  background: transparent;
  border: none;
  color: var(--fg-4);
  cursor: pointer;
  padding: 4px;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 0.15s ease, background 0.15s ease;
}

.btn-note-delete:hover {
  color: var(--bad);
  background: var(--bad-wash);
}

/* Modals & Tuning */
.mix-tuner { padding: 10px 0; }

/* The mix as one bar, in the same four colours the pitch pills and the
   balance meters use, so the setting and its result look like the same
   thing in two places. */
.mix-preview {
  display: flex;
  height: 12px;
  border-radius: var(--r-pill);
  overflow: hidden;
  background: var(--surface-3);
}

.mix-preview-seg {
  background: var(--intent, var(--brand));
  transition: width 0.2s var(--ease);
}

.mix-preview-caption {
  margin: 8px 0 18px;
}

.mix-sliders-grid {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* The agent's colour sits down the left of its own slider, so the bar above
   can be read back to the control that moves it without a legend. */
.mix-slider {
  padding-left: 12px;
  border-left: 3px solid var(--intent, var(--brand));
}

.mix-slider-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.mix-slider-head label {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--fg);
}

/* The parenthesised half of the name — what the agent is for, not what it is
   called. Quiet enough that four of them stacked read as one column of names. */
.mix-slider-head label span {
  margin-left: 6px;
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--fg-3);
}

.mix-slider-head b {
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  color: var(--intent, var(--brand));
}

.mix-slider .input-range { accent-color: var(--intent, var(--brand)); }

.mix-run-settings { margin-top: 20px; }

.mix-auto-row {
  padding: 0;
  border-top: 0;
  justify-content: flex-start;
  gap: 8px;
  min-height: 40px;
  cursor: pointer;
}

.mix-auto-row input { width: 18px; height: 18px; accent-color: var(--brand); }

.mix-auto-row b { font-size: 0.88rem; }

.input-range {
  width: 100%;
  height: 6px;
  background: var(--surface-3);
  border-radius: var(--r-pill);
  outline: none;
  accent-color: var(--brand);
  cursor: pointer;
}

.feedback-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}


/* Sleek SaaS refinements for Dark Mode */
[data-theme="dark"] .card {
  background: var(--surface);
  border: 1px solid var(--line-strong);
  box-shadow: 0 4px 12px rgba(0,0,0,0.5);
  border-radius: var(--r-lg);
}

[data-theme="dark"] .app-bar {
  border-bottom: 1px solid var(--line-strong);
  background: rgba(25, 29, 42, 0.85); /* Matches surface but translucent */
}

[data-theme="dark"] .tab-bar {
  background: rgba(20, 24, 36, 0.85);
  border-right: 1px solid var(--line-strong);
}

[data-theme="dark"] .btn-primary {
  box-shadow: 0 4px 14px rgba(224, 95, 36, 0.4);
}

/* The format picker on a pitch card. It wears .pitch-tag, so it only has to
   undo what a browser does to a <select> that a span does not get. */
.pitch-format-select {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  max-width: 15rem;
  font-family: inherit;
  line-height: 1.35;
  padding-right: 18px;
  background-image: linear-gradient(45deg, transparent 50%, var(--fg-3) 50%),
                    linear-gradient(135deg, var(--fg-3) 50%, transparent 50%);
  background-position: right 8px top 52%, right 4px top 52%;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
}

.pitch-format-select:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

/* =============================================================================
   The format gallery
   -----------------------------------------------------------------------------
   A browsable wall of shapes rather than a list of names. Each card carries a
   9:16 frame with the example hook set the way the renderer burns it — upper
   case, high on the frame — so the format reads as a thing you can picture
   before the description is read. The tint is the goal it serves, which is the
   only colour in the grid and therefore the thing the eye sorts by.
============================================================================= */

.gallery-search {
  position: relative;
  flex: 1 1 240px;
}

.gallery-search svg {
  position: absolute;
  left: 10px;
  top: 50%;
  width: 15px;
  height: 15px;
  transform: translateY(-50%);
  color: var(--fg-3);
  pointer-events: none;
}

.gallery-search .input { padding-left: 32px; }

.gallery-filters {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 10px;
  margin-bottom: 14px;
  scrollbar-width: none;
}

.gallery-filters::-webkit-scrollbar { display: none; }

.gallery-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  padding: 7px 13px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--fg-2);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.gallery-pill:hover { border-color: var(--line-strong); color: var(--fg); }

.gallery-pill.is-on {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

.gallery-pill-n {
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  opacity: 0.6;
}

/* Four to a row, always: the catalogue reads as rows of four whatever the
   pane width, rather than reflowing to three or five as the window moves. */
.format-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.format-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
  transition: transform 0.15s var(--ease), box-shadow 0.15s var(--ease);
}

/* The card lifts, and that is all it does. A border that darkens under a card
   that is already rising is a second answer to the same question. */
.format-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-2);
}

/* The frame is the point of the card, so it gets the space a phone gives it. */
.format-frame {
  position: relative;
  aspect-ratio: 9 / 13;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 30px 14px 14px;
  background: var(--grad-navy);
  overflow: hidden;
}

.format-card[data-intent="craving"] .format-frame { background: var(--grad-brand); }
.format-card[data-intent="trust"]   .format-frame { background: var(--grad-good); }
.format-card[data-intent="booking_order"] .format-frame { background: var(--grad-violet); }

.format-frame-chip.is-camera { left: auto; right: 9px; }
.format-frame-chip {
  position: absolute;
  top: 9px;
  left: 9px;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  backdrop-filter: blur(3px);
}

/* Six words or fewer, burned high on the frame — the renderer's own rule. */
.format-frame-hook {
  color: #fff;
  font-size: 1.02rem;
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-align: center;
  text-wrap: balance;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.35);
}

/* A stage direction is not a line anyone says, so it must not look like one. */
.format-frame-hook.is-direction {
  align-self: center;
  margin-top: 12%;
  font-size: 0.82rem;
  font-weight: 600;
  font-style: italic;
  text-transform: none;
  opacity: 0.85;
}

/* Takes the slack the grid gives a short card, so the button below can be
   pushed to the floor rather than sitting wherever the beats happened to end. */
.format-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 6px;
  padding: 12px 13px 13px;
}

/* The body's own 6px gap is the minimum clearance above the button on whichever
   card in a row has no slack to give it; every shorter card gets more from the
   button's auto margin. Kept as a gap rather than padding on the button, which
   would only make the button taller and push its label off centre. */

.format-body h3 {
  margin: 0;
  font-family: var(--sans);
  font-size: 0.92rem;
  font-weight: 700;
  line-height: 1.25;
}

/* The one fact that disqualifies a format outright, so it stays on the card
   while the rest of the brief moves into the sheet. One line, ellipsised: a
   card is not the place to read a list of three requirements either. */
.format-needs {
  margin: 0;
  font-size: 0.72rem;
  line-height: 1.4;
  color: var(--fg-3, #78808f);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.format-needs b {
  font-weight: 700;
  color: var(--fg-2, #3b4250);
}

/* Stacked, not side by side: at four columns a gallery card is ~180px wide and
   at two columns on a phone it is narrower still, so a link and a button in a
   row either truncate or wrap raggedly. The pair is pinned to the floor of the
   card for the reason .format-use always was -- names run one line or two and
   a row of buttons at two heights reads as crooked. */
.format-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 7px;
  margin-top: auto;
}

/* .format-use carries its own margin-top:auto for the cards that have no
   actions wrapper; inside one, two auto margins would split the slack and
   push the link away from the button. */
.format-actions .format-use { margin-top: 0; }

.format-brief-btn {
  align-self: flex-start;
  font-size: 0.74rem;
  text-decoration-color: color-mix(in srgb, currentColor 35%, transparent);
  text-underline-offset: 2px;
}

.format-body p {
  margin: 0;
  color: var(--fg-2);
  font-size: 0.79rem;
  line-height: 1.45;
}

/* The gallery card is its frame. A real phone's 9:16, footage edge to edge,
   and everything that used to stack under it sits on a scrim at the foot: the
   name always, the beats and the shot list when the pointer is on the card.
   Scoped to the grid because the composer's idea rail wears the same card with
   its own body. */
.format-grid .format-card { background: transparent; }

.format-grid .format-frame {
  aspect-ratio: 9 / 16;
  border-radius: var(--r-md);
}

.format-over {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 100%;
  padding: 44px 12px 12px;
  color: #fff;
  background: linear-gradient(to top, rgba(8, 10, 18, 0.92) 0%, rgba(8, 10, 18, 0.72) 55%, rgba(8, 10, 18, 0) 100%);
  overflow: hidden;
  text-align: left;
}

.format-over h3 {
  margin: 0;
  font-family: var(--sans);
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.2;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.4);
}

/* Folded until the pointer arrives: three lines of beats, and that is all the
   scrim ever holds now. The shot list used to unfold under this into four
   labelled paragraphs inside a 12rem scrolling box, on a card 180px wide --
   it is a sheet instead, reached from the link below. */
.format-over p {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  margin: 0;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  font-size: 0.76rem;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.88);
  transition: max-height 0.25s ease, opacity 0.2s ease;
}

.format-grid .format-card:hover .format-over p,
.format-grid .format-card:focus-within .format-over p { max-height: 4.5rem; opacity: 1; }

/* The brief has to be reachable without a pointer -- the old folded panel was
   not -- so the link is on the scrim always, like the button under it. */
.format-over .format-brief-btn {
  color: rgba(255, 255, 255, 0.82);
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.45);
}

.format-over .format-brief-btn:hover { color: #fff; }

/* Always there, never on hover -- see .format-use above. On the scrim it is
   white on glass, and turns brand when the card is under the pointer. */
.format-over .format-use {
  margin-top: 4px;
  border-color: rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
  backdrop-filter: blur(4px);
}

.format-grid .format-card:hover .format-over .format-use {
  border-color: var(--brand);
  background: var(--brand);
  color: #fff;
}

/* A clip with a poster is visible from the first paint -- the poster is the
   footage's own frame, so there is no black box to hide -- and the hook and the
   "Example clip" tag come off it: the footage says what the format is, and the
   name on the scrim says what it is called. Gradient cards keep the hook,
   which is the only picture they have. */
.format-film[poster] { opacity: 1; }
.format-grid .format-card.has-film .format-frame-hook,
.format-grid .format-film-tag { display: none; }

/* Two chips across a 180px frame: "Booking & Orders" and "On camera" collided
   at the size the old, wider card set them. */
.format-grid .format-frame-chip { font-size: 0.58rem; padding: 2px 6px; }

/* The chips stay up while the footage runs: on a wall where every clip in
   view is playing, fading them would mean never seeing the goal at all. */
.format-grid .format-card.is-playing .format-frame-chip { opacity: 1; }

@media (max-width: 560px) {
  .format-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .format-body p { display: none; }
  .format-over { padding: 30px 8px 8px; }
  .format-over h3 { font-size: 0.82rem; }
  .format-actions { gap: 5px; }
}

/* No pointer, no hover: the beats fold away for good, and the name, the brief
   link and the button stay — a tap still plays the clip with sound. */
@media (hover: none) {
  .format-grid .format-card:hover .format-over p { max-height: 0; opacity: 0; }
}

/* The brief in the sheet: sized to the text rather than to a thumbnail. Labels
   in their own column so the answers read as one column down the page, which
   is how a shot list is read -- one line at a time, phone in the other hand.
   A label gutter is a third of a phone screen, so there it stacks instead. */
.format-spec {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: 9px 14px;
  margin: 2px 0 18px;
  font-size: 0.88rem;
  line-height: 1.5;
}

.format-spec dt {
  font-weight: 700;
  color: var(--fg-3);
}

.format-spec dd {
  margin: 0;
  color: var(--fg);
}

@media (max-width: 480px) {
  .format-spec { grid-template-columns: 1fr; gap: 2px; }
  .format-spec dt + dd { margin-bottom: 8px; }
}

/* =============================================================================
   The sound gallery
   -----------------------------------------------------------------------------
   Three beds shown as what they are for, not as three words in a dropdown. The
   card carries the formats that reach for it, so the automatic choice is
   legible: an owner can see why a chef-to-camera reel came back with lo-fi.

   A bed with no audio file is dimmed and says so. It still renders — the
   default bed is used — and hiding that would make the picker a choice between
   three names that play one track.
============================================================================= */

.bed-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 12px;
  margin-bottom: 16px;
}

.bed-card {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 15px 15px 13px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}

.bed-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-2);
}

.bed-card.is-on {
  border-color: var(--brand);
  border-left-color: var(--brand);
  background: var(--brand-wash);
}

.bed-card:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.bed-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

/* The bed's own play control, the same toggle the voices use; the tempo
   moves beside it rather than losing its corner. */
.bed-card-head .voice-play { flex: none; width: 32px; height: 32px; }
.bed-card-head .voice-play svg { width: 14px; height: 14px; }
.bed-card-title { display: flex; align-items: center; gap: 10px; min-width: 0; }

.bed-name { font-size: 1rem; font-weight: 700; }

.bed-tempo {
  color: var(--fg-3);
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

.bed-blurb { margin: 0; color: var(--fg); font-size: 0.82rem; line-height: 1.45; }

.bed-suits { margin: 0; color: var(--fg-2); font-size: 0.79rem; line-height: 1.45; }

.bed-formats {
  margin-top: 3px;
  padding-top: 9px;
  border-top: 1px solid var(--line-soft);
  color: var(--fg-3);
  font-size: 0.74rem;
  line-height: 1.4;
}

.bed-formats b { color: var(--fg-2); }

/* Says what will actually play, rather than letting the name imply it. */
.bed-missing {
  display: flex;
  gap: 6px;
  margin: 0;
  padding: 7px 9px;
  border-radius: 7px;
  background: var(--warn-wash);
  color: var(--warn);
  font-size: 0.73rem;
  line-height: 1.4;
}

.bed-card.is-missing .bed-name { color: var(--fg-2); }

/* Which formats reach for a bed is why the automatic choice is legible, but it
   is supporting detail: on seven cards at once it is seven paragraphs to
   scroll past. It belongs to the bed you actually chose. */
.bed-card:not(.is-on) .bed-formats { display: none; }

/* Folded behind .bed-more while "choose for me" is the saved answer. */
.bed-card.is-folded { display: none; }

/* The same link the voice picker uses for its long tail, in the same words. */
.bed-more {
  grid-column: 1 / -1;
  justify-self: start;
  padding: 6px 0;
  border: 0;
  background: none;
  color: var(--brand);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.bed-more:hover { text-decoration: underline; }

/* Automatic sits first in the bed grid and is what most accounts should leave
   it on, so it reads as the considered default rather than an opt-out. */
.bed-card-auto {
  border-left-color: var(--brand);
  background: var(--surface-2);
}

.bed-card-auto.is-on {
  background: var(--brand-wash);
}

@media (max-width: 560px) {
  .bed-grid { gap: 9px; margin-bottom: 12px; }
  .bed-card { padding: 12px 13px; gap: 5px; }
  .bed-card:hover { transform: none; }
}

/* A format with a generated example clip plays it in its own frame. The hook is
   the poster: it says what the format is until the film shows it, then clears. */
/* Clicking plays it with sound on every device, so the promise is honest. */
.format-card.has-film .format-frame { cursor: pointer; }

/* A still posts at 4:5, not a reel's 9:16; in the reel's frame a quarter of
   its width was cut off. */
.format-grid .format-card.is-still .format-frame { aspect-ratio: 4 / 5; }

/* A still's drawn preview fills the frame the way a clip does. */
.format-still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  cursor: zoom-in;
}

/* A sequence's frame: every slide, so the card reads as several pictures at a
   glance. Odd counts lead with a full-width first slide -- that is the one most
   people see in the feed. */
.format-mosaic {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
}
.format-mosaic img {
  width: 100%;
  height: 100%;
  min-height: 0;
  object-fit: cover;
  cursor: zoom-in;
}
.format-mosaic img:first-child:nth-last-child(odd) { grid-column: span 2; }

.format-film {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Hidden until it has something to show: an unloaded video paints a black
     box over the gradient, which looks like a broken card. */
  opacity: 0;
  transition: opacity 0.25s ease;
}

.format-card.is-playing .format-film { opacity: 1; }

/* Once the film runs, the frame is the film. The chip, the hook and the tag are
   poster furniture — they say what the card is before it moves, then get out of
   the way rather than sitting on top of the footage they are describing. */
.format-card .format-frame-chip,
.format-card .format-frame-hook,
.format-card .format-film-tag {
  position: relative;
  z-index: 1;
  transition: opacity 0.2s ease;
}

.format-card.is-playing .format-frame-chip,
.format-card.is-playing .format-frame-hook {
  opacity: 0;
}

/* Both are corner furniture, not flow content. The grouped rule above sets
   position:relative on all three so they sit above the film, which for these
   two made them flex items sharing the row with the hook — squeezed narrow
   enough that "Example clip" wrapped onto two lines. They go back to being
   pinned; nowrap so neither can ever break again. */
.format-card .format-frame-chip,
.format-card .format-film-tag {
  position: absolute;
  white-space: nowrap;
}

/* Says the card has something to play, without a play button covering the art. */
.format-film-tag {
  position: absolute;
  left: 9px;
  bottom: 9px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  backdrop-filter: blur(3px);
}

.format-film-tag::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 3.5px solid transparent;
  border-bottom: 3.5px solid transparent;
}

.format-card.is-playing .format-film-tag { opacity: 0; }

/* Playing aloud, after a click. The silent loop every card runs gets no badge — the
   motion is its own signal, and a "muted" label on every card would be noise. */
.format-card.is-live .format-frame::before {
  content: "SOUND ON";
  position: absolute;
  left: 9px;
  bottom: 9px;
  z-index: 1;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.22);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  backdrop-filter: blur(3px);
}

@media (prefers-reduced-motion: reduce) {
  .format-film { transition: none; }
}

/* ---------- The roster ----------
   A horizontal strip of the people who can front a reel, because a restaurant
   has two or three of them, not twenty — a scrolling row reads faster than a
   list and never pushes the cards it governs below the fold. */

.staff-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 4px;
  scrollbar-width: thin;
}

.staff-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  padding: 7px 12px 7px 7px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--fg-2);
  font: inherit;
  font-size: 0.86rem;
  cursor: pointer;
}

.staff-chip.is-on {
  border-color: var(--brand);
  background: var(--brand-wash);
  color: var(--fg);
}

.staff-chip img,
.staff-chip .staff-initial {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
}

.staff-chip .staff-initial {
  display: grid;
  place-items: center;
  background: var(--surface-3);
  color: var(--fg-3);
  font-weight: 600;
}

.staff-badge {
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: var(--brand);
  color: #fff;
  font-size: 0.68rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.staff-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.staff-actions:empty { display: none; }

/* ---------- One person, everything about them ----------
   Identity, then what they have, then the one thing to do next. Anything a
   manager could do instead of that one thing is behind the fold-away, because
   the wrong shortcut here -- uploading someone's face for them -- is the one
   the product exists to avoid. */

.person-head {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 16px;
}

.person-face {
  width: 56px;
  height: 56px;
  flex: 0 0 auto;
  border-radius: 50%;
  overflow: hidden;
}

.person-face:has(img) { cursor: pointer; }

/* The photo, big enough to judge, over the page rather than pushing it about. */
.face-sheet {
  width: min(320px, 84vw);
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-3);
}

.face-sheet::backdrop { background: rgba(0, 0, 0, 0.45); }

.face-sheet img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--r-sm);
}

.face-sheet-actions {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.face-sheet-actions .btn { flex: 1; }

.person-face img,
.person-face .staff-initial {
  width: 100%;
  height: 100%;
  object-fit: cover;
  font-size: 1.3rem;
}

.person-id h2 { margin: 0; }

.person-id p {
  margin: 2px 0 0;
  color: var(--fg-3);
  font-size: 0.86rem;
}

/* Looks like the line it replaces, until you go near it. A dashed underline is
   the whole affordance -- a pencil icon on a one-word field is more furniture
   than field. */
.role-edit {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: text;
  border-bottom: 1px dashed var(--line-strong, var(--line));
}

.role-edit:hover,
.role-edit:focus-visible { color: var(--fg-1); }

.person-id p b { color: var(--brand); }

.person-status {
  display: flex;
  gap: 8px;
  margin-bottom: 14px;
}

.person-stat {
  flex: 1;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}

.person-stat b {
  display: block;
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.person-stat span {
  font-size: 0.9rem;
  color: var(--fg-2);
}

.person-stat.is-done {
  border-color: var(--good);
  background: var(--good-wash);
}

.person-stat.is-done b,
.person-stat.is-done span { color: var(--good); }

.person-say {
  margin: 0 0 12px;
  font-size: 0.94rem;
  line-height: 1.5;
  color: var(--fg-2);
}

.person-say.is-good { color: var(--good); }

.person-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.person-manual {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

.person-manual > summary {
  color: var(--fg-3);
  font-size: 0.88rem;
  font-weight: 600;
  cursor: pointer;
}

.person-manual > summary::marker { color: var(--fg-4); }

.person-block {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

.person-block h4 {
  margin: 0 0 10px;
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-3);
}

/* Three inputs and a button in one row is unreadable on a phone, which is
   where this is used. Name takes a line, role and mobile share the next. */
.staff-add {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

.staff-add #staffName { flex: 1 1 100%; }

.staff-add #staffRole,
.staff-add #staffPhone { flex: 1 1 40%; }

.staff-add button { flex: 1 1 100%; }

/* Four fields and a submit wrapped into two ragged flex rows, which left Add
   as a 60px button squeezed onto the end of the email line — the one control
   that commits the form, drawn as an afterthought. A 2x2 grid with the action
   on its own row says what the form is and where it ends. */
@media (min-width: 620px) {
  .staff-add {
    display: grid;
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }

  .staff-add button {
    grid-column: 1 / -1;
    justify-self: end;
  }
}

.staff-add input {
  min-width: 0;
  flex: 1;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--field);
  color: var(--fg);
  font: inherit;
  font-size: 0.9rem;
}

.staff-add input:focus-visible {
  outline: none;
  border-color: var(--brand);
}


/* ---------- The self-enrolment page ----------
   Opened on a phone, by somebody who has never seen this product and did not
   ask to. One column, generous type, nothing to navigate. */

.join {
  max-width: 560px;
  margin: 0 auto;
  padding: 0 16px 48px;
}

.join-bar {
  display: flex;
  align-items: center;
  padding: 18px 0 8px;
}

.join .card { margin-bottom: 14px; }

.join-done {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  background: var(--good-wash);
  color: var(--good);
  font-weight: 600;
  font-size: 0.9rem;
}

.join-done::before { content: "\2713"; }

.join-foot {
  margin: 20px 4px 0;
  color: var(--fg-3);
  font-size: 0.82rem;
  line-height: 1.5;
}



.person-stat.is-busy {
  border-color: var(--warn);
  background: var(--warn-wash);
}

.person-stat.is-busy b,
.person-stat.is-busy span { color: var(--warn); }

.join-shot {
  display: block;
  width: 128px;
  height: 128px;
  margin: 0 0 12px;
  border-radius: var(--r-sm);
  object-fit: cover;
  border: 1px solid var(--line);
}

.staff-mismatch {
  margin-top: 12px;
  padding: 12px 14px;
  border: 1px solid var(--warn);
  border-radius: var(--r-sm);
  background: var(--warn-wash);
}

.staff-mismatch p {
  margin: 0 0 10px;
  color: var(--warn);
  font-size: 0.9rem;
  line-height: 1.5;
}

/* ---------- Sending the invite ----------
   A row of ordinary links, because that is what they are: each one opens an app
   the manager already has, with the message already written. */

.invite-share {
  margin: 14px 0 0;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}

.invite-hint {
  margin: 0 0 10px;
  color: var(--fg-2);
  font-size: 0.88rem;
  line-height: 1.5;
}

.invite-channels {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.invite-channels .btn { text-decoration: none; }

.staff-add #staffEmail { flex: 1 1 100%; }

.invite-hint.is-bad { color: var(--bad); }

/* The scene script, as somebody would read it on a page before agreeing to
   shoot it. Monospace because it is laid out in columns, and scrollable because
   a four-line scene with its cast notes is taller than a sheet. */
.scene-script {
  margin: 0;
  padding: 14px;
  max-height: 46vh;
  overflow: auto;
  white-space: pre-wrap;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--fg);
  background: var(--surface-2);
  border-radius: 10px;
}

/* The ask box above the composer: one line, the input takes the room. */

/* ---- The form: the older composer beside the box ---- */
.compose-way { margin-bottom: 12px; }
.composer .is-other-way { display: none; }

.compose-grid {
  display: grid;
  /* The media side leads: it is the part being judged, and the options are
     short. Not 50/50 — a column of selects does not need half a page. */
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 22px;
  align-items: start;
}
.compose-grid[hidden] { display: none; }
.compose-media { min-width: 0; }
.compose-side { min-width: 0; display: flex; flex-direction: column; }
.compose-side .seg-mini { align-self: flex-start; margin-bottom: 14px; }
.compose-side .field { margin-bottom: 14px; }
.compose-side .compose-pick { margin-bottom: 14px; }
.field-pair-tight { gap: 8px; margin-bottom: 6px; }

/* Named rather than bare `.dropzone`: `.field label` in brand.css otherwise
   forces display:block and collapses the icon, title and hint onto one line. */
.compose-media .dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 40px 18px;
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: var(--surface-2);
  cursor: pointer;
  text-align: center;
  transition: border-color 0.15s var(--ease), background-color 0.15s var(--ease);
}
.compose-media .dropzone svg { width: 22px; height: 22px; margin-bottom: 5px; color: var(--fg-3); }
.compose-media .dropzone b { margin: 0; color: var(--fg); font-size: 0.9rem; font-weight: 600; }
.compose-media .dropzone span { font-size: 0.79rem; color: var(--fg-3); }
.compose-media .file-list { margin-top: 12px; }


@media (max-width: 720px) {
  .compose-grid { grid-template-columns: minmax(0, 1fr); gap: 16px; }
}

/* ---- Worth boosting ----

   Sits inside the recommended drop's card, under the "Live on @handle" stamp.
   It is the only thing in the feed that asks for money, so it is the only
   thing carrying the brand wash -- everything around it is neutral, and that
   contrast is doing the whole job of drawing the eye.

   All tokens, so dark mode follows on its own. */
.boost-note {
  margin: 12px 0 0;
  padding: 12px 13px;
  border: 1px solid var(--brand-wash);
  border-left: 3px solid var(--brand);
  border-radius: var(--r-sm);
  background: var(--brand-wash);
}

.boost-note-head {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--brand-strong);
}

.boost-note-head svg { flex: none; }

.boost-note-head b {
  font-size: 0.86rem;
  font-weight: 650;
}

.boost-note-why {
  margin: 5px 0 0;
  font-size: 0.79rem;
  line-height: 1.45;
  color: var(--fg);
}

/* The working, deliberately quieter than the recommendation it supports --
   there to be checked, not to be read every time. */
.boost-note-evidence {
  margin: 3px 0 10px;
  font-size: 0.73rem;
  line-height: 1.45;
  color: var(--fg-2);
}

/* Says "this is a sample" on the card itself. A screenshot outlives the query
   string it was taken under, and a made-up 3.2x passed round as real is the
   exact failure the rest of this loop exists to prevent. */
.boost-note-tag {
  margin-left: auto;
  padding: 1px 6px;
  border: 1px solid var(--brand);
  border-radius: 999px;
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--brand-strong);
}

/* The multiple, given the weight it deserves. It is the one number the decision
   turns on, and as a word in a sentence it read like every other number. */
.boost-proof {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 10px;
}

.boost-multiple {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-right: 14px;
  border-right: 1px solid var(--brand);
  line-height: 1.05;
}

.boost-multiple b {
  font-size: 1.6rem;
  font-weight: 700;
  color: var(--brand-strong);
  font-variant-numeric: tabular-nums;
}

.boost-multiple span {
  margin-top: 2px;
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.boost-bars {
  flex: 1;
  min-width: 0;
  display: grid;
  gap: 4px;
}

/* Two bars scaled against each other rather than one bar against a maximum
   nobody chose. The comparison is the whole argument, so it is the thing drawn:
   a long bar over a short one says "ahead of normal" before a word is read. */
.boost-bar {
  display: grid;
  grid-template-columns: 4.6rem 1fr 2.2rem;
  align-items: center;
  gap: 7px;
  font-size: 0.68rem;
}

.boost-bar-label { color: var(--fg-3); }

.boost-bar-track {
  height: 7px;
  border-radius: 4px;
  background: var(--surface-3);
  overflow: hidden;
}

.boost-bar-fill {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: var(--fg-4);
}

.boost-bar.is-mine .boost-bar-fill { background: var(--brand); }
.boost-bar.is-mine .boost-bar-label { color: var(--fg-2); font-weight: 600; }

.boost-bar-value {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--fg-2);
}

/* The half of the evidence an owner cannot check from their own phone: that the
   post has not already peaked. Only drawn when two readings exist to say so. */
.boost-climb {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin: 7px 0 9px;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--good-wash);
  color: var(--good);
  font-size: 0.68rem;
  font-weight: 600;
}

.boost-climb svg { flex: none; }

/* What the money buys. Three facts to be scanned, rather than the same three
   numbers buried in a sentence that has to be read to the end. */
.boost-terms {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
}

.boost-terms span {
  display: flex;
  flex-direction: column;
  flex: 1 1 4.5rem;
  padding: 6px 9px;
  border-radius: var(--r-sm);
  background: var(--surface);
  font-size: 0.62rem;
  line-height: 1.3;
  color: var(--fg-3);
}

.boost-terms b {
  font-size: 0.78rem;
  font-weight: 650;
  color: var(--fg);
}

/* A boost already running is a status line, not a card of argument. Same
   family, a fraction of the weight. */
.boost-note.is-live {
  padding: 9px 12px;
  background: var(--surface-2);
  border-color: var(--line);
  border-left-color: var(--good);
}

.boost-note-done {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--brand-strong);
}

/* Live, and saying so without a word. Money is going out right now, which is
   worth one steady mark on the card. */
.boost-live-dot {
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 0 3px var(--good-wash);
}

.boost-note-cap {
  margin: 7px 0 0;
  font-size: 0.68rem;
  text-align: center;
  color: var(--fg-3);
}

/* ---------- Boost settings ----------
   Two states, and the quiet one is the common one. A restaurant with no ad
   account gets a sentence and one button, not five credential fields. */

.boost-connect {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.boost-connect p {
  margin: 0 0 10px;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--fg-2);
}

.boost-connected {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  display: grid;
  gap: 12px;
}

.boost-account-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.boost-account-row b {
  display: block;
  font-size: 0.86rem;
}

.boost-account-row span {
  font-size: 0.73rem;
  color: var(--fg-3);
  font-variant-numeric: tabular-nums;
}

.boost-ad-account {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.boost-ad-account code {
  padding: 1px 4px;
  border-radius: 3px;
  background: var(--surface-2);
  font-size: 0.72rem;
}

/* The dollar lives outside the field rather than inside the number, so what is
   typed stays a number the browser can step and validate. */
.boost-cap-field {
  display: flex;
  align-items: center;
  gap: 6px;
}

.boost-cap-field span {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--fg-3);
}

.boost-cap-field .input { flex: 1; }

/* What is true right now rather than what could be: the deployment switch, and
   anything the ledger could not answer. */
.boost-settings-state {
  margin: 14px 0 0;
  padding: 9px 11px;
  border-radius: var(--r-sm);
  background: var(--gold-wash);
  color: var(--gold);
  font-size: 0.75rem;
  line-height: 1.5;
}

.boost-settings-actions {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px;
}

@media (max-width: 420px) {
  .boost-proof { gap: 10px; }
  .boost-multiple { padding-right: 10px; }
  .boost-bar { grid-template-columns: 4rem 1fr 2rem; }
}

/* ==========================================================================
   Fill the Cut & Edit the Cut (Timeline Strip, Gaps, Asks & Veo Meter)
   ========================================================================== */

/* Editing a video wants room: the drop editor is as close to the whole
   window as a dialog gets, and the phone preview stays beside both panes. */
/* Editing a post is two things at once: the post, and what there is to change
   about it. They sit side by side — the preview on its own dark stage, the
   form beside it — so nothing being edited is ever off screen. */
.sheet-editor {
  max-width: 1120px;
  width: 96vw;
  height: calc(100vh - 32px);
  padding: 0;
  display: grid;
  grid-template-columns: minmax(250px, 380px) 1fr;
  overflow: hidden;
}

.editor-stage {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 20px;
  overflow-y: auto;
  /* Held dark in both themes: it is a stage for a picture, not a surface. */
  background: #16203a;
  background-image: radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px);
  background-size: 14px 14px;
}

.editor-stage-head,
.editor-stage-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.editor-stage-foot {
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  margin-top: auto;
}

.editor-stage-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
}

.editor-stage-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.editor-stage .picks { margin: 0; }
.editor-stage .picks-hint { color: rgba(255, 255, 255, 0.5); }

.editor-form {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 20px 22px 0;
  background: var(--surface);
}

#editorSheet .sheet-head { margin-bottom: 1rem; }

#editorSheet .sheet-head p {
  font-size: 0.84rem;
  color: var(--fg-3);
}

.editor-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: 14px;
}

/* One row of choices, not a row of links: the tabs read as a switch. */
.editor-tabs {
  display: flex;
  gap: 4px;
  padding: 4px;
  margin-bottom: 16px;
  border-radius: var(--r-md, 10px);
  background: var(--surface-2);
}

.editor-tabs[hidden] { display: none; }

.editor-tab {
  flex: 1;
  padding: 8px 14px;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--fg-2);
  background: transparent;
  border: none;
  border-radius: var(--r-sm);
  cursor: pointer;
}

.editor-tab:hover { color: var(--fg); }

.editor-tab.is-on {
  color: var(--fg);
  background: var(--surface);
  box-shadow: var(--shadow-1, 0 1px 2px rgba(0, 0, 0, 0.12));
}

/* The footer belongs to the drop, not to the open tab, so it stays put while
   the pane above it scrolls. */
.editor-foot {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0 14px;
  border-top: 1px solid var(--line);
  background: var(--surface);
}

.editor-foot .hint { max-width: 34ch; }

/* Narrow, the note squeezes into a column of two-word lines and pushes the
   buttons off the bottom. The buttons are the part that has to be there. */
@media (max-width: 900px) {
  .editor-foot .hint { display: none; }
}

.editor-foot-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

/* Side by side for as long as there is room for both: the window a portal is
   read in is often half a laptop screen, and stacking it there would hide the
   post under the form it is being edited with. */
@media (max-width: 700px) {
  .sheet-editor {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
  }

  .editor-stage { padding: 14px; }
  .editor-stage .cut-phone { max-width: 190px; }
  .editor-form { padding: 14px 16px 0; }
}

.cut-preview-area {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.cut-phone {
  width: 100%;
  max-width: 300px;
  aspect-ratio: 9 / 16;
  border-radius: 12px;
  background: #000;
  border: 1px solid var(--line);
  position: relative;
  overflow: hidden;
  box-shadow: var(--shadow-2);
}

.cut-phone video,
.cut-phone img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* A still is not a reel and is not shot to a reel's frame. Cropped to 9:16 by
   the rule above, a 4:5 slide lost a strip off each side -- which on a slide
   is where the headline starts, so the owner was approving a picture whose
   first word was off screen. The post's own shape, and nothing cropped: these
   are the exact pixels that publish. */
.cut-phone.is-still { aspect-ratio: 4 / 5; }
.cut-phone.is-still img { object-fit: contain; }

/* display:block above outranks the browser's own [hidden] rule, so the
   player and the still each need telling. */
.cut-phone video[hidden],
.cut-phone img[hidden] { display: none; }

.cut-phone-hook {
  position: absolute;
  top: 12px;
  left: 8px;
  right: 8px;
  font-family: var(--font-body, system-ui);
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  color: #fff;
  letter-spacing: 0.03em;
  text-shadow: 0 1px 3px rgba(0,0,0,0.85);
  pointer-events: none;
  line-height: 1.2;
}

.cut-phone-sub {
  position: absolute;
  bottom: 28px;
  left: 8px;
  right: 8px;
  font-family: var(--font-body, system-ui);
  font-size: 0.65rem;
  line-height: 1.3;
  color: #fff;
  background: rgba(18, 21, 31, 0.75);
  backdrop-filter: blur(4px);
  padding: 4px 6px;
  border-radius: 4px;
  pointer-events: none;
  min-height: 20px;
  transition: opacity 0.15s ease;
}

.cut-phone-time {
  position: absolute;
  bottom: 6px;
  left: 8px;
  font-family: var(--mono);
  font-size: 0.6rem;
  color: rgba(255,255,255,0.75);
  pointer-events: none;
}

/* Timeline & strip area */
.cut-timeline-area {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

#editorSheet .strip-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 0.78rem;
  color: var(--fg-3);
  margin-bottom: 8px;
}

#editorSheet .strip-head b {
  font-weight: 600;
  color: var(--fg);
}

.cut-strip-hint {
  font-size: 0.74rem;
  color: var(--fg-3);
}

/* Read back, not a warning: what the worker already put right. */
.cut-repairs {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: grid;
  gap: 5px;
}

.cut-repairs li {
  position: relative;
  padding-left: 20px;
  font-size: 0.76rem;
  line-height: 1.4;
  color: var(--fg-2);
}

.cut-repairs li::before {
  content: "\2713";
  position: absolute;
  left: 3px;
  font-weight: 700;
  color: var(--good);
}

#editorSheet .strip {
  display: flex;
  gap: 4px;
  height: 156px;
  overflow-x: auto;
  padding: 4px 0;
  align-items: stretch;
}

#editorSheet .shot {
  position: relative;
  border-radius: 6px;
  padding: 6px 7px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  background: var(--surface-2);
  border: 1px solid var(--line);
  min-width: 96px;
  cursor: grab;
  user-select: none;
  transition: transform 0.12s var(--ease), border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}

#editorSheet .shot:hover {
  border-color: var(--line-strong);
}

#editorSheet .shot .thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 5px;
  opacity: 0.34;
  pointer-events: none;
}

#editorSheet .shot > div {
  position: relative;
}

#editorSheet .shot:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

#editorSheet .shot:active {
  cursor: grabbing;
}

#editorSheet .shot.is-dragging {
  opacity: 0.4;
  transform: scale(0.96);
}

#editorSheet .shot.is-drop-target {
  border-color: var(--brand);
  box-shadow: 0 0 0 2px var(--brand-wash);
}

#editorSheet .shot.is-on {
  outline: 2px solid var(--brand);
  outline-offset: -1px;
}

#editorSheet .shot.is-playing {
  box-shadow: 0 0 0 2px var(--good);
}

#editorSheet .shot .k {
  font-family: var(--mono);
  font-size: 0.62rem;
  gap: 4px;
  white-space: nowrap;
  overflow: hidden;
  font-weight: 500;
  color: var(--fg-3);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

#editorSheet .shot .k i {
  font-style: normal;
  width: 7px;
  height: 7px;
  border-radius: 2px;
  display: inline-block;
}

#editorSheet .shot.clip .k i { background: var(--shot-clip); }
#editorSheet .shot.photo .k i { background: var(--shot-photo); }
#editorSheet .shot.lib .k i { background: var(--shot-lib); }
#editorSheet .shot.gen {
  border-color: var(--shot-gen);
  background: var(--violet-wash);
}
#editorSheet .shot.gen .k i { background: var(--shot-gen); }
#editorSheet .shot.gen.is-on { outline-color: var(--shot-gen); }

#editorSheet .shot .say {
  font-size: 0.67rem;
  line-height: 1.25;
  color: var(--fg-2);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}

#editorSheet .shot .s {
  font-family: var(--mono);
  font-size: 0.63rem;
  font-weight: 500;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Gap styles */
#editorSheet .gap {
  border: 1px dashed var(--warn);
  border-radius: 6px;
  background: repeating-linear-gradient(135deg, var(--warn-wash) 0 6px, transparent 6px 12px);
  padding: 6px 7px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 4px;
  min-width: 132px;
}

#editorSheet .gap .k {
  font-size: 0.68rem;
  font-weight: 700;
  color: var(--warn);
}

#editorSheet .gap .say {
  font-size: 0.65rem;
  line-height: 1.2;
  color: var(--fg-2);
}







/* Veo Meter */
#editorSheet .meter {
  margin-top: 12px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.77rem;
  color: var(--fg-2);
  padding: 6px 0;
}

#editorSheet .meter .bar {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: var(--surface-3);
  overflow: hidden;
}

#editorSheet .meter .bar i {
  display: block;
  height: 100%;
  background: var(--violet);
  transition: width 0.3s ease, background-color 0.2s ease;
}

#editorSheet .meter.is-warn .bar i {
  background: var(--warn);
}

#editorSheet .meter .n {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.75rem;
}

/* Shot Edit Panel */
#editorSheet .panel {
  margin-top: 14px;
  border-top: 1px dashed var(--line);
  padding-top: 12px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  font-size: 0.78rem;
}

@media (max-width: 640px) {
  #editorSheet .panel { grid-template-columns: 1fr; }
}

#editorSheet .panel label {
  display: block;
  font-family: var(--body);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-3);
  margin-bottom: 5px;
}

#editorSheet .panel .v {
  font-family: var(--mono);
  font-size: 0.77rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 6px 8px;
  color: var(--fg);
}

#editorSheet .panel .v.text {
  font-family: var(--body);
  font-size: 0.75rem;
  max-height: 48px;
  overflow-y: auto;
  line-height: 1.35;
}

.cut-field-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.cut-field-row .input-sm {
  width: 74px;
  padding: 4px 6px;
  font-size: 0.76rem;
}

/* Buttons in panel */
#editorSheet .btnrow {
  grid-column: 1 / -1;
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 4px;
}

#editorSheet .b {
  font-family: var(--body);
  font-size: 0.75rem;
  font-weight: 500;
  padding: 6px 11px;
  border-radius: 6px;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  transition: background-color 0.15s, border-color 0.15s;
}

#editorSheet .b:hover {
  background: var(--surface-2);
}

.cut-take-picker {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.74rem;
}

.cut-take-picker select {
  padding: 3px 6px;
  font-size: 0.74rem;
}

/* Swap picker */
.cut-swap-modal {
  margin-top: 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px;
  background: var(--surface-2);
}

.cut-swap-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
  font-size: 0.8rem;
  font-weight: 600;
}

.cut-swap-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(105px, 1fr));
  gap: 8px;
  max-height: 200px;
  overflow-y: auto;
  padding: 2px;
}

.cut-swap-item {
  position: relative;
  aspect-ratio: 9 / 16;
  border-radius: 6px;
  overflow: hidden;
  border: 2px solid transparent;
  cursor: pointer;
  background: #000;
  transition: border-color 0.15s, transform 0.12s;
}

.cut-swap-item:hover {
  border-color: var(--brand);
  transform: scale(1.02);
}

.cut-swap-item img, .cut-swap-item video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.cut-swap-badge {
  position: absolute;
  bottom: 4px;
  left: 4px;
  font-size: 0.6rem;
  padding: 1px 4px;
  border-radius: 3px;
  background: rgba(0,0,0,0.65);
  color: #fff;
  font-family: var(--mono);
}

/* ---------- The menu, as a customer reads it ---------- */

.menu-preview {
  overflow-y: auto;
  padding: 4px 2px 2px;
}

.menu-preview-place {
  margin: 0 0 18px;
  text-align: center;
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg);
}

.menu-preview-cat + .menu-preview-cat {
  margin-top: 22px;
}

.menu-preview-cat h5 {
  margin: 0 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--line);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.menu-preview-row {
  display: flex;
  gap: 12px;
  padding: 9px 0;
}

.menu-preview-row + .menu-preview-row {
  border-top: 1px solid var(--line);
}

.menu-preview-photo {
  width: 52px;
  height: 52px;
  flex: none;
  border-radius: 6px;
  object-fit: cover;
}

.menu-preview-main { flex: 1; min-width: 0; }

/* Name, then a rule to the price, the way a printed menu runs it. */
.menu-preview-line {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 0.88rem;
}

.menu-preview-leader {
  flex: 1;
  border-bottom: 1px dotted var(--line-strong);
  transform: translateY(-3px);
}

.menu-preview-noprice {
  font-size: 0.76rem;
  font-style: italic;
  color: var(--warn);
}

.menu-preview-main p {
  margin: 3px 0 0;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--fg-2);
}

.menu-preview-tags {
  display: inline-block;
  margin-top: 4px;
  font-size: 0.72rem;
  color: var(--fg-3);
}

.menu-preview-gaps {
  margin: 14px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}

/* ---------- A post that is several pictures ---------- */

/* Instagram stamps a carousel so you know there is more than one frame. The
   feed card shows the first slide and nothing else, so without this the owner
   approves what looks like a single photograph. */
.drop-slides {
  position: absolute;
  top: 10px;
  right: 46px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 8px;
  border-radius: 999px;
  background: rgba(8, 12, 20, 0.66);
  backdrop-filter: blur(6px);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
  line-height: 1;
}

/* The dots and arrows under the editor's preview. */
.slide-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.slide-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  cursor: pointer;
}

.slide-arrow:hover:not(:disabled) { background: rgba(255, 255, 255, 0.22); }
.slide-arrow:disabled { opacity: 0.3; cursor: default; }

.slide-dots { display: flex; align-items: center; gap: 7px; }

.slide-dot {
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.3);
  cursor: pointer;
}

/* The one being edited is wider, not just brighter: on a phone-sized preview
   a brightness difference between two 7px dots is not a difference. */
.slide-dot.is-on {
  width: 20px;
  border-radius: 999px;
  background: #fff;
}

/* ---------- The words set on one slide ---------- */

.slide-words {
  padding-bottom: 4px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--line);
}

.slide-words-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}

.slide-words-head .editor-stage-label { color: var(--fg-3); }

.slide-badge {
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--fg-2);
  font-size: 0.7rem;
  font-weight: 700;
}

.field-aside {
  margin-left: 6px;
  color: var(--fg-3);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}

.slide-note { margin: 4px 0 0; }

.slide-steps { display: flex; flex-direction: column; gap: 8px; }

.slide-step { display: flex; align-items: center; gap: 8px; }

/* The same disc the renderer draws, so the row in the form reads as the row
   that will be on the picture. */
.slide-step-n {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--brand);
  color: #14110a;
  font-size: 0.72rem;
  font-weight: 800;
}

.slide-step input { flex: 1 1 auto; min-width: 0; }
.slide-step-cut { flex: 0 0 auto; }

.slide-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 14px 0 6px;
}

/* ---------- Picking a time ---------- */

.slot-picks {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.slot-pick {
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  color: var(--fg);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}

.slot-pick:hover { border-color: var(--brand); }

.slot-pick.is-on {
  border-color: var(--brand);
  background: var(--brand-soft, rgba(120, 180, 140, 0.12));
}

.slot-when { display: flex; gap: 8px; }
.slot-when .input { flex: 1 1 0; min-width: 0; }

.slot-says {
  margin: 12px 0 0;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--surface-2);
  font-size: 0.84rem;
  font-weight: 600;
}

.slot-says.is-bad { color: var(--bad, #b4433a); }

@media (max-width: 560px) {
  .slot-picks { grid-template-columns: 1fr; }
}
