.chat-layout { display: flex; flex-direction: column; flex: 1; min-width: 0; }

/* A faint doodle wallpaper in the spirit of WhatsApp: film, clapper, play and
   star marks at low opacity, inlined as a data URI so nothing is fetched.
   32 marks per 400px tile — roughly three times the earlier density, which is
   what makes it read as a pattern rather than as stray specks. */
.messages {
  flex: 1;
  overflow-y: auto;
  padding: 16px 20px 8px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  background-color: var(--bg);
  background-image: url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='400' height='400' viewBox='0 0 400 400'>\
<g fill='none' stroke='%23ffffff' stroke-opacity='0.013' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'>\
<g transform='translate(18 20) rotate(-12)'><rect x='0' y='0' width='30' height='21' rx='3'/><path d='M0 6h30M0 15h30M8 0v21M22 0v21'/></g>\
<g transform='translate(104 14) rotate(8)'><path d='M0 0l18 10-18 10z'/></g>\
<g transform='translate(172 26) rotate(-6)'><circle cx='0' cy='0' r='11'/><path d='M0-6v6l5 3'/></g>\
<g transform='translate(240 12) rotate(15)'><path d='M0 0l6 12 13 2-10 9 3 13-12-6-12 6 3-13-10-9 13-2z'/></g>\
<g transform='translate(316 22) rotate(-9)'><rect x='0' y='0' width='30' height='23' rx='4'/><path d='M6 0L3-7h24l-4 7'/></g>\
<g transform='translate(60 76) rotate(6)'><path d='M0 0h24v18H0z'/><path d='M24 5l11-5v18l-11-5'/></g>\
<g transform='translate(148 84) rotate(-14)'><circle cx='0' cy='0' r='12'/><path d='M-6 0h12M0-6v12'/></g>\
<g transform='translate(214 92) rotate(11)'><path d='M0 0l11 19h-22z'/></g>\
<g transform='translate(288 78) rotate(-5)'><path d='M0 16V0l12-3v16'/><circle cx='-4' cy='16' r='4'/><circle cx='8' cy='13' r='4'/></g>\
<g transform='translate(356 88) rotate(13)'><path d='M0 0l18 10-18 10z'/></g>\
<g transform='translate(14 132) rotate(9)'><circle cx='0' cy='0' r='11'/><path d='M0-6v6l5 3'/></g>\
<g transform='translate(84 146) rotate(-16)'><path d='M0 0l6 12 13 2-10 9 3 13-12-6-12 6 3-13-10-9 13-2z'/></g>\
<g transform='translate(168 140) rotate(4)'><rect x='0' y='0' width='30' height='21' rx='3'/><path d='M0 6h30M0 15h30M8 0v21M22 0v21'/></g>\
<g transform='translate(262 152) rotate(-11)'><rect x='0' y='0' width='30' height='23' rx='4'/><path d='M6 0L3-7h24l-4 7'/></g>\
<g transform='translate(348 144) rotate(7)'><circle cx='0' cy='0' r='12'/><path d='M-6 0h12M0-6v12'/></g>\
<g transform='translate(36 198) rotate(-7)'><path d='M0 0l18 10-18 10z'/></g>\
<g transform='translate(110 210) rotate(12)'><path d='M0 0h24v18H0z'/><path d='M24 5l11-5v18l-11-5'/></g>\
<g transform='translate(196 204) rotate(-3)'><path d='M0 16V0l12-3v16'/><circle cx='-4' cy='16' r='4'/><circle cx='8' cy='13' r='4'/></g>\
<g transform='translate(268 216) rotate(10)'><path d='M0 0l11 19h-22z'/></g>\
<g transform='translate(340 206) rotate(-13)'><path d='M0 0l6 12 13 2-10 9 3 13-12-6-12 6 3-13-10-9 13-2z'/></g>\
<g transform='translate(22 264) rotate(14)'><rect x='0' y='0' width='30' height='23' rx='4'/><path d='M6 0L3-7h24l-4 7'/></g>\
<g transform='translate(120 274) rotate(-8)'><circle cx='0' cy='0' r='11'/><path d='M0-6v6l5 3'/></g>\
<g transform='translate(190 266) rotate(5)'><path d='M0 0l18 10-18 10z'/></g>\
<g transform='translate(252 278) rotate(-15)'><rect x='0' y='0' width='30' height='21' rx='3'/><path d='M0 6h30M0 15h30M8 0v21M22 0v21'/></g>\
<g transform='translate(352 270) rotate(9)'><circle cx='0' cy='0' r='12'/><path d='M-6 0h12M0-6v12'/></g>\
<g transform='translate(50 326) rotate(-6)'><path d='M0 0l6 12 13 2-10 9 3 13-12-6-12 6 3-13-10-9 13-2z'/></g>\
<g transform='translate(128 340) rotate(11)'><path d='M0 0h24v18H0z'/><path d='M24 5l11-5v18l-11-5'/></g>\
<g transform='translate(214 334) rotate(-10)'><path d='M0 0l11 19h-22z'/></g>\
<g transform='translate(286 344) rotate(6)'><path d='M0 16V0l12-3v16'/><circle cx='-4' cy='16' r='4'/><circle cx='8' cy='13' r='4'/></g>\
<g transform='translate(360 330) rotate(-12)'><path d='M0 0l18 10-18 10z'/></g>\
<g transform='translate(8 372) rotate(8)'><circle cx='0' cy='0' r='11'/><path d='M0-6v6l5 3'/></g>\
<g transform='translate(168 380) rotate(-4)'><rect x='0' y='0' width='30' height='23' rx='4'/><path d='M6 0L3-7h24l-4 7'/></g>\
</g></svg>");
  background-repeat: repeat;
}

/* With only a handful of messages they should sit at the bottom, the way every
   chat app does it. Pushing the first child down is safe once the list
   overflows, where justify-content: flex-end would clip the top. */
.messages > :first-child { margin-top: auto; }

.day-divider {
  align-self: center;
  margin: 12px 0 6px;
}
.day-divider span {
  background: var(--surface);
  color: var(--muted);
  font-size: 12px;
  padding: 4px 12px;
  border-radius: 999px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

/* Says there is more above, and disappears once there is not. Scrolling near
   the top fetches the next block; this only makes that visible, so an empty
   space at the top does not read as the beginning of the conversation. */
.history-top {
  align-self: center;
  color: var(--muted);
  font-size: 12px;
  padding: 8px 0 4px;
  opacity: 0.7;
}

/* A message hugs its own content instead of stretching to the full column,
   so a two-word reply does not get a banner-sized box. */
.msg {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: fit-content;
  max-width: min(560px, 75%);
  margin-top: 10px;
}
.msg.mine { align-self: flex-end; align-items: flex-end; }

/* Consecutive messages from the same person stack tightly and only the first
   carries the name. */
.msg.grouped { margin-top: 2px; }
.msg.grouped .msg-meta { display: none; }

/*
 * The buttons are not part of the name row.
 *
 * They used to be, and grouping hides that row — so on every message after the
 * first one from the same person there was no reply button and no bin at all.
 * Which is most of a conversation: nothing could be removed except the first
 * line of each run, and from the outside that reads as removing being broken.
 *
 * Floated over the corner of the message so it costs no layout height, which is
 * what kept them in the name row in the first place.
 */
.msg { position: relative; }
.msg-tools {
  position: absolute;
  top: -2px;
  right: 0;
  display: flex;
  gap: 2px;
  padding: 1px 3px;
  border-radius: 999px;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  opacity: 0;
  transition: opacity 0.12s ease;
  z-index: 2;
}
/* Mine sit on the left, because the right edge is the wall on that side. */
.msg.mine .msg-tools { right: auto; left: 0; }
.msg:hover .msg-tools, .msg-tools:focus-within { opacity: 1; }

.msg-meta {
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 3px;
  padding: 0 2px;
}
.msg-meta .author { font-weight: 600; color: var(--text-strong); }

/*
 * Who is speaking, by colour. The names repeat on every line, so the colour is
 * read long before the word is — which of the four it is matters more here than
 * the name itself.
 */
.msg-meta .author.role-admin { color: #6f9bff; }      /* Valentijn */
.msg-meta .author.role-moderator { color: #ff9d3d; }
.msg-meta .author.role-editor { color: #ffd24a; }     /* Mohit */
.msg-meta .author.role-system { color: #ff6b6e; }     /* Doom DeepSeek */

.bubble {
  position: relative;
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: 7px 11px 7px;
  line-height: 1.375;
  word-break: break-word;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
.msg.mine .bubble { background: var(--bubble-mine); color: #fff; }

/* The time sits in the bottom-right, and the last text line reserves room for
   it so it never overlaps — the WhatsApp trick. */
.bubble .stamp {
  float: right;
  margin: 6px -2px -2px 10px;
  font-size: 11px;
  line-height: 1;
  color: var(--muted);
  white-space: nowrap;
}
.msg.mine .bubble .stamp { color: rgba(255, 255, 255, 0.65); }

/* Attachment bubbles hold laid-out HTML, so pre-wrap would render the markup
   indentation as blank space. They also shrink to the widest child, so the box
   ends at the video edge instead of running past it. */
.bubble.attachment {
  padding: 8px;
  white-space: normal;
  width: fit-content;
  max-width: 100%;
}

.attach-head { display: flex; align-items: center; gap: 10px; }
.attach-icon {
  width: 34px; height: 34px; flex-shrink: 0;
  display: grid; place-items: center;
  background: var(--bg-deep); border-radius: var(--radius-sm); font-size: 17px;
}
/* Keeps a long filename from stretching the bubble past the video. */
.attach-text { min-width: 0; }
.attach-name { font-size: 14px; font-weight: 500; word-break: break-all; }
.attach-sub { font-size: 12px; color: var(--muted); }
.msg.mine .attach-sub { color: rgba(255, 255, 255, 0.72); }

.bubble .caption { margin-top: 6px; }

.attach-actions { display: flex; gap: 6px; margin-top: 8px; }
.attach-actions a {
  font-size: 13px; padding: 4px 10px; text-decoration: none;
  color: var(--text-strong); border-radius: var(--radius-sm);
  background: var(--surface-3);
}
.attach-actions a:hover { background: var(--border); }

/* Previews cap at 320px: noticeably smaller than the bubble would allow, so a
   clip does not dominate the conversation. */
.bubble img.preview, .bubble video.preview {
  width: 320px;
  max-width: 100%;
  border-radius: var(--radius-sm);
  display: block;
  margin-top: 8px;
  background: #000;
}
.bubble pre.textfile {
  margin: 8px 0 0; padding: 10px; background: var(--bg-deep);
  border-radius: var(--radius-sm); max-height: 240px; overflow: auto;
  font-size: 13px; line-height: 1.4; white-space: pre-wrap;
}
.bubble .progress { margin-top: 8px; }

/* ---- Category strip under an uploaded video ---- */
.style-strip { margin-top: 8px; }
.style-strip-label {
  font-size: 11px; font-weight: 700; letter-spacing: 0.03em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 5px;
}
.msg.mine .style-strip-label { color: rgba(255, 255, 255, 0.6); }
.style-strip .style-choices { display: flex; gap: 5px; flex-wrap: wrap; }
.style-strip .style-choice {
  padding: 4px 10px; font-size: 12px; border-radius: 999px;
  background: var(--bg-deep); color: var(--muted);
  box-shadow: inset 0 0 0 1px var(--border);
}
.style-strip .style-choice:hover { color: var(--text-strong); }
/* In the chat the chosen category is red, so it stands apart from the blurple
   the rest of the interface uses for "selected". */
.style-strip .style-choice.selected {
  background: var(--danger); color: #fff; box-shadow: none;
}
.style-strip .style-choice.selected:hover { background: #c8353a; }

/* Once the twenty seconds are up the strip stays visible but stops responding,
   so you can still see the category without being able to move it by accident. */
/* What the editor sees: which category his video carries, not a way to set it. */
.style-strip.read-only { display: flex; align-items: center; gap: 8px; }
.style-strip.read-only .style-strip-label { margin: 0; }
.style-chip {
  padding: 3px 10px; font-size: 12px; border-radius: 999px;
  background: var(--accent-soft); color: #a5b0ff; font-weight: 600;
}

.style-strip.locked .style-choice { cursor: default; opacity: 0.75; }
.style-strip.locked .style-choice:not(.selected) { opacity: 0.4; }
.style-strip.locked .style-strip-label { color: var(--muted); }

.check-strip { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.check-strip .check-count {
  font-size: 12px; color: #ffcc4d; font-variant-numeric: tabular-nums;
  min-width: 30px;
}

/* The wait before a check, and the check itself, both read as a bar. A number
   counting down tells you how long; a bar tells you at a glance. */
.countdown {
  flex: 1; height: 6px; min-width: 60px;
  background: var(--bg-deep); border-radius: 999px; overflow: hidden;
}
.countdown.wide { flex: 1; }
.countdown-bar {
  height: 100%; background: #f0b232;
  transition: width 0.4s linear;
}
.countdown-bar.checking { background: #ffcc4d; }
.check-strip.running, .check-strip.done { font-size: 12px; color: var(--muted); }

/* Yellow, because it stops something that is already running and costs money. */
.check-strip .dont-check {
  font-size: 12px; padding: 4px 10px; border-radius: 999px;
  background: #d8ac54; color: #241a05; font-weight: 600;
}
.check-strip .dont-check:hover:not(:disabled) { background: #f0b232; }

/*
 * Yellow like everything else about checking, so it reads as the same action —
 * but a softer one. The full #f0b232 glared out of a dark bubble and pulled the
 * eye away from the video it belongs to.
 */
.check-strip .do-check {
  font-size: 12px; padding: 4px 10px; border-radius: 999px;
  background: #d8ac54; color: #241a05; font-weight: 600;
}
.check-strip .do-check:hover:not(:disabled) { background: var(--surface-3); color: var(--text-strong); }

.attach-head.removed .attach-icon { background: rgba(242, 63, 67, 0.15); }
.attach-head.removed .attach-name { text-decoration: line-through; color: var(--muted); }

.composer {
  padding: 8px 20px 20px;
  display: flex;
  gap: 8px;
  align-items: flex-end;
}
.composer .box {
  flex: 1;
  display: flex;
  align-items: flex-end;
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: 0 6px;
}
.composer textarea {
  resize: none;
  max-height: 140px;
  min-height: 38px;
  background: transparent;
  border: none;
  padding: 9px 6px;
}
.composer textarea:focus { border-color: transparent; }

/* A filled round + reads at a glance; the outlined paperclip did not.
   The plus is drawn as an SVG rather than typed as a character: a glyph sits on
   the font's baseline and never lands in the optical centre of a circle. */
.composer .attach-btn {
  width: 38px; height: 38px; flex: none;
  border-radius: 50%;
  background: var(--accent); color: #fff;
  padding: 0; line-height: 0;
  display: grid; place-items: center;
}
.composer .attach-btn svg {
  width: 20px; height: 20px; display: block;
  fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round;
}
.composer .attach-btn:hover { background: var(--accent-hover); }
.composer .send-btn { align-self: stretch; }



.typing { height: 18px; font-size: 13px; color: var(--muted); padding: 0 20px; font-style: italic; }

/*
 * The drop hint covers the page, so it must never take a click. If it is ever
 * left visible by a drag that ended somewhere the browser did not tell us
 * about, the worst that happens is a stripe on screen — not a page nobody can
 * press a button on.
 */
.dropzone-hint {
  position: fixed; inset: 12px; border-radius: var(--radius);
  background: rgba(30, 31, 34, 0.92);
  display: none; place-items: center; font-size: 18px; font-weight: 500;
  color: var(--text-strong);
  border: 2px dashed var(--accent); z-index: 50;
  pointer-events: none;
}
.dropzone-hint.visible { display: grid; }

/* The verdict of a check, in the same three colours everywhere: yellow while
   it runs, green when there is nothing to fix, red with the count when there is. */
.check-badge {
  font-size: 12px; font-weight: 600;
  padding: 3px 10px; border-radius: 999px;
}
.check-badge.checking { background: rgba(240, 178, 50, 0.18); color: #ffcc4d; }
.check-badge.clean { background: rgba(35, 165, 90, 0.2); color: #5fd894; }
.check-badge.dirty { background: rgba(242, 63, 67, 0.2); color: #ff9093; }
.check-badge.failed { background: var(--surface-3); color: var(--muted); }

/* Yellow Check button — the accent colour so it pops against the dark strip */
.do-check {
  background: #d8ac54; color: #1e1f22; border: none;
  font-size: 12px; font-weight: 600; padding: 5px 14px; border-radius: 999px; cursor: pointer;
}
.do-check:hover { background: #4e5058; color: #fff; }
.dont-check {
  background: transparent; border: none; font-size: 11px;
  color: var(--muted); cursor: pointer; padding: 3px 8px;
}
.dont-check:hover { color: var(--danger); }

/* The two switches Valentijn can flip from the chat. Green when they are doing
   their job, grey when he has told them to stop. */
.automation-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 20px; background: var(--surface);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
}
.auto-toggle {
  font-size: 12px; padding: 4px 12px; border-radius: 999px;
  background: transparent; box-shadow: inset 0 0 0 1px var(--border);
}
.auto-toggle.on { color: #5fd894; box-shadow: inset 0 0 0 1px rgba(35, 165, 90, 0.5); }
.auto-toggle.off { color: var(--muted); text-decoration: line-through; }
.auto-toggle::before { content: '● '; }
.auto-note { font-size: 12px; color: #ffb26b; }
/* display:flex above beats the browser's own rule for [hidden], so the bar has
   to be told again to disappear for anyone who is not the admin. */
.automation-bar[hidden] { display: none; }

.auto-group { display: flex; align-items: center; gap: 4px; }
.auto-slider { width: 64px; accent-color: #f0b232; }
.auto-for { font-size: 10px; color: var(--muted); min-width: 42px; text-align: right; }

.auto-back { font-size: 11px; color: #ffb26b; }
.auto-pause .ghost.small { font-size: 11px; padding: 2px 8px; }

@media (max-width: 720px) {
  .messages { padding: 12px 10px 6px; }
  .msg { max-width: 88%; }
  .composer { padding: 8px 10px 12px; }
  .automation-bar { flex-wrap: wrap; padding: 8px 12px; }
  .bubble img.preview, .bubble video.preview { width: 100%; }
}


/* One bar: what is automatic on the left, how far the schedule reaches on the
   right. Two stacked strips ate a fifth of the chat for two lines of text. */
.automation-bar .spacer { flex: 1; }
.auto-model {
  font-size: 10px; color: var(--muted);
  background: var(--surface-3); padding: 1px 6px; border-radius: 999px;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.auto-slider[hidden], .auto-for[hidden], .auto-model[hidden], .auto-back[hidden] { display: none; }

.runway-tag {
  font-size: 12px; font-weight: 600;
  padding: 3px 10px; border-radius: 999px;
}
.runway-tag[hidden] { display: none; }
.runway-tag.red { background: rgba(242, 63, 67, 0.2); color: #ff9093; }
.runway-tag.orange { background: rgba(217, 131, 36, 0.25); color: #f0b232; }
.runway-tag.yellow { background: rgba(240, 178, 50, 0.18); color: #ffcc4d; }
.runway-tag.green { background: rgba(35, 165, 90, 0.2); color: #5fd894; }

/*
 * Where everything stands, above the conversation.
 *
 * Each figure is a button to the tab it came from. The chat is where both of
 * them sit all day, and the state of the work used to live on four other tabs.
 */
.chat-overview {
  display: flex; gap: 8px; flex-wrap: wrap;
  padding: 10px 14px 0;
}
.overview-figure {
  display: flex; align-items: baseline; gap: 6px;
  padding: 6px 12px; border-radius: 999px;
  border: 1px solid var(--line); background: transparent;
  color: var(--muted); font-size: 12px; cursor: pointer;
  transition: border-color 0.12s ease, color 0.12s ease;
}
.overview-figure b { color: var(--text-strong); font-size: 14px; font-weight: 600; }
.overview-figure:hover { border-color: var(--accent); color: var(--text); }
.overview-figure.warn { border-color: color-mix(in srgb, var(--warning, #e0a341) 55%, transparent); }
.overview-figure.warn b { color: var(--warning, #e0a341); }

@media (max-width: 720px) {
  .chat-overview { padding: 8px 10px 0; gap: 6px; }
  .overview-figure { flex: 1 1 auto; justify-content: center; padding: 6px 8px; font-size: 11px; }
}

/*
 * A note only one person is shown. The check saying it has started, and the
 * report it writes afterwards: Valentijn's own working notes, which Mohit was
 * being shown on every upload.
 */
.msg.private .bubble {
  border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, var(--surface));
}
.msg-private {
  display: flex; align-items: center; gap: 10px;
  margin-top: 4px; font-size: 11px; color: var(--muted);
}
.msg.mine .msg-private { justify-content: flex-end; }
.stop-check {
  padding: 3px 10px; border-radius: 999px; font-size: 11px; cursor: pointer;
  border: 1px solid var(--line); background: transparent; color: var(--muted);
}
.stop-check:hover:not(:disabled) { border-color: var(--danger, #e0574a); color: var(--danger, #e0574a); }
.stop-check:disabled { opacity: 0.5; cursor: default; }

/*
 * A reply the assistant wrote for Valentijn, before it is sent.
 *
 * Half there on purpose: it is not a message yet, and it should not read as one
 * he has already sent. Nobody else's chat shows it at all.
 */
.msg.draft .bubble { opacity: 0.5; border: 1px dashed var(--accent); }
.msg-private.draft { color: var(--accent); }
.stop-check.danger:hover:not(:disabled) { border-color: var(--danger); color: var(--danger); }

/*
 * Someone tagged by name. Their own role colour, so being mentioned looks the
 * same as their name does at the top of a message.
 */
.mention {
  padding: 0 4px; border-radius: 4px; font-weight: 600;
  background: color-mix(in srgb, currentColor 16%, transparent);
}
.mention.role-admin { color: #6f9bff; }
.mention.role-editor { color: #ffd24a; }
.mention.role-moderator { color: #ffa552; }
.mention.role-system { color: #ff8a8a; }
.mention.me { outline: 1px solid currentColor; }

/*
 * Anything carrying `hidden` stays hidden, whatever its display is.
 *
 * `hidden` works through the browser's own `[hidden] { display: none }`, which
 * any explicit `display` in a stylesheet beats — so a bar tag with
 * `display: inline-flex` was shown to everyone regardless of the attribute.
 * That is how the editor came to see the Valentijn AI light.
 */
.automation-bar [hidden] { display: none !important; }

/* Is the assistant about to say something as Valentijn? Admin only. */
.valentijn-light {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11px; color: var(--muted); margin-left: 4px;
}
.valentijn-light .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--danger); }
.valentijn-light.active .dot { background: var(--success); }
.valentijn-light.active { color: var(--success); }

/*
 * The editor's phone has no room for the automation bar: three switches, two
 * sliders and two countdowns he cannot change anyway. What he does need from it
 * — how far the schedule reaches, how many videos are left — stays.
 */
@media (max-width: 720px) {
  .automation-bar .auto-group { display: none; }
  .automation-bar { padding: 6px 10px; gap: 6px; }
  .valentijn-light { display: none; }
}

/*
 * Answering one particular message.
 *
 * The quote above a bubble is a button: pressing it scrolls to what it answers
 * and flashes it, which is how you find a video three screens up that a report
 * is about.
 */
/*
 * Big enough to hit. They were 16 by 13 pixels — a target you aim at rather
 * than press, and half of missing it lands on the bubble, which opens a reply.
 */
.msg-reply {
  border: none; background: none; cursor: pointer;
  color: var(--muted); font-size: 14px; line-height: 1;
  min-width: 26px; min-height: 24px; padding: 0 6px;
  display: grid; place-items: center;
  opacity: 0; transition: opacity 0.12s ease;
}
.msg-reply { opacity: 1; }
.msg-reply:hover { color: var(--text-strong); }

/*
 * Removing a message, and what is left of one that was removed.
 *
 * The bin hides like the reply arrow does, so it is there when you reach for it
 * and out of the way when you are reading. A removed bubble only ever appears
 * in the admin's chat: it is faded and struck through, because "this was said
 * and then taken back" is different information from "this was said".
 */
.msg-remove {
  border: none; background: none; cursor: pointer;
  color: var(--muted); font-size: 13px; line-height: 1;
  min-width: 26px; min-height: 24px; padding: 0 6px;
  display: grid; place-items: center;
  opacity: 0; transition: opacity 0.12s ease;
}
.msg-remove { opacity: 1; }
.msg-remove:hover { color: var(--danger, #e5484d); }

.msg.removed .bubble { opacity: 0.45; text-decoration: line-through; }
.msg-private.removed { color: var(--muted); font-style: italic; }

.msg-quote {
  display: flex; align-items: baseline; gap: 8px;
  max-width: 100%; margin-bottom: 3px; padding: 4px 10px;
  border: none; border-left: 3px solid var(--accent);
  border-radius: 4px; cursor: pointer; text-align: left;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  font-size: 12px;
}
.msg.mine .msg-quote { margin-left: auto; }
.msg-quote:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
.quote-author { flex: none; font-weight: 600; }
.quote-author.role-admin { color: #6f9bff; }
.quote-author.role-editor { color: #ffd24a; }
.quote-author.role-moderator { color: #ff9d3d; }
.quote-author.role-system { color: #ff6b6e; }
.quote-body {
  color: var(--muted); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}

.msg.flash .bubble {
  animation: flash-quote 1.2s ease;
}
@keyframes flash-quote {
  0%, 100% { box-shadow: none; }
  30% { box-shadow: 0 0 0 2px var(--accent); }
}

/* What you are answering, above the box you type in. */
.replying {
  display: flex; align-items: center; gap: 8px;
  margin: 0 20px; padding: 6px 12px;
  border-left: 3px solid var(--accent);
  background: var(--surface-2); border-radius: 4px;
  font-size: 12px; color: var(--muted);
}
.replying[hidden] { display: none; }
.replying-label { flex: none; }
.replying-who { flex: none; color: var(--text-strong); font-weight: 600; }
.replying-body { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.replying button { flex: none; font-size: 16px; line-height: 1; padding: 0 6px; }

@media (max-width: 720px) {
  .replying { margin: 0 10px; }
  /* No hover on a phone, so the strip is simply always there. */
  .msg-tools { opacity: 1; }
}

/*
 * The verdict, on the card rather than only in a badge inside it.
 *
 * A badge is easy to scroll past; a card that has gone red is not. Yellow
 * while it is being looked at, green when there was nothing to fix, red when
 * there was.
 */
/*
 * The card is tinted rather than outlined.
 *
 * A 2px ring around a video read as an error box — the sort of thing that
 * means "this control is invalid", which is not what a finished review is. A
 * dark, greyish wash of the same colour says the same thing without shouting:
 * the card is still a card, it has simply gone slightly red or slightly green.
 *
 * Mixed into `--surface` rather than `--surface-2`, so a judged video sits a
 * shade deeper than an ordinary message as well as a shade warmer.
 */
/*
 * Named for `.msg.mine` as well, or a verdict never shows on your own uploads.
 *
 * `.msg.mine .bubble` is three classes and `.bubble.verdict-clean` is two, so
 * the blue won on specificity no matter which came first in the file — and it
 * sets only the background, so the verdict's outline still drew. The result was
 * a blue bubble with a green or red edge: the one reading that says neither
 * "good" nor "not good". Judged is judged, whoever sent it.
 */
.bubble.verdict-checking,
.msg.mine .bubble.verdict-checking {
  background: color-mix(in srgb, var(--warning) 14%, var(--surface));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--warning) 22%, transparent);
}
.bubble.verdict-clean,
.msg.mine .bubble.verdict-clean {
  background: color-mix(in srgb, var(--success) 16%, var(--surface));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--success) 24%, transparent);
}
.bubble.verdict-dirty,
.msg.mine .bubble.verdict-dirty {
  background: color-mix(in srgb, var(--danger) 16%, var(--surface));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--danger) 24%, transparent);
}
/* The blue bubble writes in white; on these the ordinary text colour reads
   better against a tint that is mostly surface. */
.msg.mine .bubble.verdict-clean,
.msg.mine .bubble.verdict-dirty,
.msg.mine .bubble.verdict-checking { color: var(--text); }

/* No check happening: the strip is only an anchor, and takes no room. */
.check-strip.empty { display: none; }

/* How many videos are being checked, beside the switch that starts them. */
.checking-tag {
  font-size: 11px; font-weight: 600; padding: 3px 10px; border-radius: 999px;
  background: rgba(240, 177, 50, 0.18); color: var(--warning);
  cursor: pointer; white-space: nowrap;
}
.checking-tag:hover { background: rgba(240, 177, 50, 0.3); }
.checking-tag[hidden] { display: none; }

/* Nothing in the strip means no strip: an empty bar is a bar in the way. */
.chat-overview:empty { display: none; }

/* The countdown to the evening question. Only the admin ever sees it. */
.nudge-tag {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 600; padding: 3px 6px 3px 10px;
  border-radius: 999px; white-space: nowrap;
  background: rgba(111, 155, 255, 0.16); color: #9fb8ff;
}
.nudge-tag.soon { background: rgba(240, 177, 50, 0.2); color: var(--warning); }
.nudge-tag[hidden] { display: none; }
.nudge-skip {
  border: none; background: none; cursor: pointer; color: inherit;
  font-size: 14px; line-height: 1; padding: 0 3px; opacity: 0.7;
}
.nudge-skip:hover { opacity: 1; }

/* The category, for someone who may see it but not set it. */
.style-readonly {
  display: inline-block; padding: 4px 12px; border-radius: 999px;
  font-size: 12px; color: var(--text-strong);
  background: var(--surface-3);
}

/*
 * What the assistant is allowed to say, one row per thing.
 *
 * A popup in the middle rather than a menu hanging off the light. Positioned
 * by hand it landed half off the bar, clipped by the overflow on the row it was
 * anchored to, and the labels came out blank — a dropdown has to fight every
 * container it grows out of. A centred sheet has nothing to fight, and the app
 * already has two dialogs shaped exactly like this.
 */
.valentijn-light { cursor: pointer; }
.valentijn-light.muted { opacity: 0.45; }
.valentijn-light:hover { filter: brightness(1.25); }

.voice-sheet {
  position: fixed; inset: 0; z-index: 700;
  background: rgba(8, 9, 12, 0.7);
  display: grid; place-items: center; padding: 20px;
}
.voice-card {
  background: var(--surface); border-radius: var(--radius);
  width: min(440px, 100%); max-height: 86vh; overflow-y: auto;
  padding: 18px 20px 16px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
}
.voice-card header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 14px;
  font-size: 15px; font-weight: 700; color: var(--text-strong);
}
.voice-list { display: flex; flex-direction: column; gap: 2px; }
.voice-row {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 11px 10px; border-radius: var(--radius-sm); cursor: pointer;
}
.voice-row:hover { background: var(--surface-2); }
.voice-row input { margin-top: 3px; flex-shrink: 0; width: 16px; height: 16px; accent-color: var(--accent); }
.voice-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.voice-label { font-size: 14px; color: var(--text-strong); font-weight: 600; }
.voice-what { font-size: 12px; color: var(--muted); line-height: 1.45; }
.voice-foot {
  margin: 14px 0 0; padding-top: 12px;
  border-top: 1px solid var(--border);
  font-size: 12px; color: var(--muted); line-height: 1.45;
}

/*
 * The little "?" beside the paperclip, and what it opens.
 *
 * Dragging that does nothing has three causes that look the same: the browser
 * never hears it (Windows refuses a drag into an elevated window), the browser
 * refuses the drop, or this app loses the file. Only two of those are ours, and
 * telling them apart used to take a fortnight.
 */
.drag-help {
  align-self: flex-end; margin-bottom: 6px;
  width: 22px; height: 22px; padding: 0;
  border-radius: 50%; font-size: 12px; font-weight: 700;
  color: var(--muted); background: var(--surface-2);
}
.drag-help:hover { color: var(--text-strong); background: var(--surface-3); }

.dragcheck {
  position: fixed; right: 16px; bottom: 16px; z-index: 800;
  width: min(360px, calc(100vw - 32px));
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: 0 14px 40px rgba(0, 0, 0, 0.5);
  overflow: hidden;
}
.dragcheck header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 12px; font-size: 13px; font-weight: 700;
  color: var(--text-strong); border-bottom: 1px solid var(--border);
}
.dragcheck-body { padding: 12px; }
.dragcheck-list { list-style: none; margin: 0 0 10px; padding: 0; font-size: 12px; color: var(--text); }
.dragcheck-list li { padding: 2px 0; }
.dragcheck-list b { color: var(--text-strong); font-family: ui-monospace, monospace; }
.dragcheck-wait { margin: 0; font-size: 12px; color: var(--muted); }
.dragcheck-good { margin: 0; font-size: 12px; color: var(--success); }
.dragcheck-bad { margin: 0; font-size: 12px; color: var(--warning); line-height: 1.45; }
.dragcheck-hint {
  margin: 10px 0 0; padding-top: 10px; border-top: 1px solid var(--border);
  font-size: 12px; color: var(--text); line-height: 1.5;
}


/*
 * Choosing a title, without the video.
 *
 * The picker is the same element the player uses; this is only somewhere for it
 * to stand. Opening the whole player to read nine sentences meant a video
 * started playing at you on a desktop, which is what he asked to stop.
 */
.title-only-backdrop {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(0, 0, 0, 0.6);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.title-only-box {
  position: relative;
  background: var(--panel, #17181c); border-radius: 12px;
  /* Wide enough for the two columns of ideas. At 560px they were two narrow
     strips of wrapped words. */
  padding: 14px; width: min(940px, 100%); max-height: 88vh; overflow: auto;
}
.title-only-close {
  position: absolute; top: 8px; right: 10px;
  border: none; background: none; color: var(--muted);
  font-size: 16px; line-height: 1; cursor: pointer;
}
.title-only-close:hover { color: var(--text-strong); }
.title-only-loading { color: var(--muted); font-size: 13px; padding: 8px 4px; }

/* Of er een hoofdvideo gevonden is, en anders de keuze. */
.koppel-stand {
  margin: 10px 0 4px; padding: 8px 10px; border-radius: 6px;
  font-size: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.koppel-stand.gevonden { background: rgba(35, 165, 90, 0.12); color: #7ee0a6; }
.koppel-stand.zoekt { background: rgba(240, 177, 50, 0.12); color: #f0b132; }
.koppel-keuze {
  background: var(--surface-2, #21252e); color: var(--text, #e6e8ec);
  border: 1px solid var(--border, #333); border-radius: 4px; padding: 3px 6px;
  font-size: 12px; max-width: 260px;
}
.koppel-stand .tiny { font-size: 11px; padding: 3px 9px; }

/* Welke soort controle: de montage, of alleen de titels. */
.soort-doos {
  background: var(--panel, #17181c); border-radius: 12px; padding: 18px;
  width: min(430px, 100%); display: flex; flex-direction: column; gap: 10px;
}
.soort-doos h3 { margin: 0; font-size: 15px; }
.soort-naam { margin: 0; font-size: 12px; color: var(--muted); }
.soort-keuze {
  text-align: left; display: flex; flex-direction: column; gap: 3px;
  background: var(--surface-2, #21252e); border: 1px solid var(--border, #333);
  border-radius: 8px; padding: 10px 12px; cursor: pointer;
}
.soort-keuze:hover { border-color: var(--accent); }
.soort-kop { font-weight: 600; font-size: 13px; }
.soort-uitleg { font-size: 11px; color: var(--muted); line-height: 1.45; }
