/* No build step: one stylesheet, system fonts, and the viewer's colour scheme.
   The React rebuild brings a real design system; this exists so the internal
   tool looks deliberate in the meantime. */

:root {
  color-scheme: light dark;
  --bg: #fbfbfd;
  --panel: #ffffff;
  --ink: #16181d;
  --muted: #6b7280;
  --line: #e4e5ea;
  --accent: #3b5bdb;
  --danger: #c92a2a;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #111318;
    --panel: #191c22;
    --ink: #e9eaee;
    --muted: #9aa1ad;
    --line: #2a2e37;
    --accent: #748ffc;
    --danger: #ff8787;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}

main { max-width: 900px; margin: 0 auto; padding: 32px 20px 96px; }
/* The viewer owns the window: a fixed media pane beside a scrolling rail has no
   business being 900px in the middle of a monitor. */
main.wide { max-width: none; padding: 0; }
/* A grid wants room. Not full-bleed though — cards stretched across an ultrawide
   are worse than a generous column. */
main.roomy { max-width: 1400px; }

h1 { font-size: 24px; letter-spacing: -0.01em; margin: 0 0 20px; }
h2 { font-size: 17px; margin: 36px 0 12px; }
h3 { font-size: 15px; margin: 10px 0 2px; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9em; }

/* Top bar ------------------------------------------------------------------ */

.topbar {
  display: flex; align-items: center; gap: 20px;
  padding: 0 20px; height: 52px;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.topbar .brand { font-weight: 600; color: var(--ink); }
.topbar nav { display: flex; gap: 16px; }
/* Leads the row and carries a little of the wordmark's weight, but keeps the
   nav's colour: a 600-weight word in the text colour reads as a title, and
   nobody clicks a title. */
.topbar nav .home { font-weight: 600; }
.topbar .signout { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.topbar .who { color: var(--muted); font-size: 13px; }

/* Controls ----------------------------------------------------------------- */

.button {
  display: inline-block; padding: 9px 16px; border: 0; border-radius: 8px;
  background: var(--accent); color: #fff; font: inherit; font-weight: 500;
  cursor: pointer;
}
.button:hover { text-decoration: none; filter: brightness(1.08); }
.button.danger { background: var(--danger); }
.button.slack { width: 100%; text-align: center; background: #4a154b; }

.link {
  background: none; border: 0; padding: 0; font: inherit;
  color: var(--accent); cursor: pointer;
}
.link.danger { color: var(--danger); }

input[type="text"], input:not([type]), input[type="email"], input[type="password"], select {
  width: 100%; padding: 9px 11px; margin: 4px 0 14px;
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: 8px; font: inherit;
}
label { font-size: 13px; font-weight: 500; color: var(--muted); }

/* Cards and messages -------------------------------------------------------- */

.card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; padding: 24px;
}
.card.narrow { max-width: 380px; margin: 40px auto; }
.card h1 { margin-bottom: 16px; }
.card h2 { margin-top: 0; }

/* Toasts, out of the flow -----------------------------------------------------
   These used to sit between the header and <main>, which meant every "Saved."
   pushed the whole page down by its own height. Worst on the viewer, which is
   sized to `calc(100vh - 52px)` and so became taller than the window the moment
   anything had been said — a scrollbar, and a rail clipped off the bottom, as
   the reward for renaming a title.

   Fixed instead, and self-dismissing: an overlay that stayed would cover the
   page rather than shove it, which is the same bug wearing a hat. The dismissal
   is a CSS animation rather than a timer in JS, so it happens on its own with
   nothing scripted — and hovering pauses it, because "Filed as <url>" is worth
   reading at your own pace. */

.messages {
  position: fixed; top: 64px; left: 50%; transform: translateX(-50%);
  z-index: 20;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  list-style: none; margin: 0; padding: 0;
  width: max-content; max-width: min(560px, calc(100vw - 40px));
  /* The strip spans the window; only the toasts themselves should intercept
     anything, or an invisible band would eat clicks meant for the page. */
  pointer-events: none;
}
.messages li {
  pointer-events: auto;
  padding: 10px 14px; border-radius: 8px; margin: 0;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 6px 24px rgb(0 0 0 / 0.18);
  animation: toast-in 0.18s ease, toast-out 0.4s ease 6s forwards;
}
/* Longer, because an error is the one you actually want to finish reading. */
.messages li.error { animation-delay: 0s, 12s; }
.messages li:hover { animation-play-state: paused; }
.messages .error { border-color: var(--danger); color: var(--danger); }

@keyframes toast-in { from { opacity: 0; transform: translateY(-8px); } }
/* visibility, not just opacity: a finished toast must stop taking clicks as
   well as stop being visible. */
@keyframes toast-out { to { opacity: 0; visibility: hidden; } }

@media (prefers-reduced-motion: reduce) {
  /* The fade out is the dismissal, not decoration, so it stays — the slide in
     is decoration, so it goes. */
  @keyframes toast-in { from { opacity: 0; } }
}

.muted, .meta { color: var(--muted); font-size: 13px; }
.error { color: var(--danger); font-size: 13px; }
.notes { white-space: pre-wrap; }

.divider { text-align: center; color: var(--muted); font-size: 12px; margin: 18px 0; }

.badge {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--muted); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.04em;
}

.code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 30px; letter-spacing: 0.12em; text-align: center;
  padding: 16px; margin: 18px 0 6px;
  background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
}

/* Lists --------------------------------------------------------------------- */

.recordings, .devices { list-style: none; padding: 0; margin: 0; }
.recordings li, .devices li {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; margin-bottom: 8px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
}
.recordings a {
  flex: 1; color: var(--ink);
  display: flex; align-items: center; gap: 14px; min-width: 0;
}
.recordings a:hover { text-decoration: none; }
/* min-width:0 so a long title truncates instead of pushing the badges off. */
.recordings .body { min-width: 0; }

/* The square at the front of a row: a picture of the thing, or a glyph for its
   kind. Fixed size so every row lines up whatever shape the image is. */
.thumb {
  flex: 0 0 auto; width: 48px; height: 48px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px; overflow: hidden;
  background: var(--bg); border: 1px solid var(--line);
}
/* The picture covers the glyph underneath it. If it fails to load it removes
   itself and the glyph shows through — see _thumb.html. */
.thumb { position: relative; }
.thumb img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
  background: var(--bg);
}
.thumb svg {
  width: 22px; height: 22px;
  fill: none; stroke: var(--muted); stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Enough colour to tell the kinds apart at a glance, not enough to make a list
   of forty rows look like a toybox. */
.thumb.video svg, .thumb.image svg { stroke: var(--accent); }
.thumb.audio svg { stroke: #9775fa; }
.thumb.pdf svg { stroke: var(--danger); }
.devices li > div { flex: 1; }
.devices li.revoked { opacity: 0.55; }
.title { display: block; font-weight: 500; }
.meta { display: block; }

/* Detail -------------------------------------------------------------------- */

video {
  width: 100%; border-radius: 12px; background: #000;
  border: 1px solid var(--line);
}

.panels { list-style: none; padding: 0; margin: 0; }
.panels li { margin-bottom: 28px; }
.panels .seek {
  display: block; width: 100%; padding: 0; border: 0; background: none; cursor: pointer;
}
.panels img {
  width: 100%; display: block; border-radius: 10px; border: 1px solid var(--line);
}
.panels blockquote {
  margin: 8px 0 0; padding-left: 12px;
  border-left: 3px solid var(--line); color: var(--muted);
}

.transcript { list-style: none; padding: 0; margin: 0; }
.transcript li {
  display: flex; align-items: baseline; gap: 10px;
  padding: 5px 0; border-bottom: 1px solid var(--line);
}
.transcript .time {
  flex: 0 0 52px; text-align: right; font-variant-numeric: tabular-nums;
  font-size: 12px; color: var(--muted);
}
.transcript .source {
  flex: 0 0 56px; font-size: 10px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
.transcript .text { flex: 1; }
.transcript .bleed .text { color: var(--muted); }

/* Session context: clipboard and focus rows, interleaved with the speech.
   Set apart rather than styled loudly — they're the same timeline, and the
   transcript should still read as one column of text. */
.transcript .row-focus,
.transcript .row-clipboard,
.transcript .row-concealed { background: color-mix(in srgb, var(--panel) 70%, transparent); }
.transcript .row-focus .text { color: var(--muted); }
.transcript .row-focus .source,
.transcript .row-clipboard .source,
.transcript .row-concealed .source { color: var(--accent); }

.transcript .copied {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; word-break: break-word;
  padding: 1px 6px; border-radius: 5px;
  background: var(--bg); border: 1px solid var(--line);
}
.transcript .row-concealed .text { color: var(--muted); font-style: italic; }

.transcript .copy {
  flex: 0 0 auto; padding: 2px 8px; font-size: 11px;
  color: var(--muted); background: none;
  border: 1px solid var(--line); border-radius: 6px; cursor: pointer;
}
.transcript .copy:hover { color: var(--ink); border-color: var(--muted); }

/* A screenshot fills the same slot the player would. */
.shot {
  width: 100%; display: block; border-radius: 12px;
  border: 1px solid var(--line); background: var(--panel);
}

/* Actions on a recording ---------------------------------------------------
   Gemini drafting GitHub issues is the first of these. Styled as a block that
   sits between the storyboard and the transcript, because that's the order you
   work in: watch it, check what you marked, then turn it into work. */

.actions { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.actions form { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.actions .hint { font-size: 12px; color: var(--muted); max-width: 46ch; }

/* htmx sets .htmx-request on the indicator for the life of the POST. Drafting
   takes the better part of a minute, so this is the only thing telling anyone
   the page hasn't hung. */
.drafting { display: none; font-size: 13px; color: var(--accent); }
.drafting.htmx-request { display: block; }
.actions button[disabled] { opacity: 0.5; cursor: progress; }

.action-error { border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); }

.draft-head .summary { font-size: 15px; margin: 0 0 4px; }
.draft-head .meta { font-size: 12px; color: var(--muted); margin: 0 0 16px; }

.ticket {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 16px 18px; margin-bottom: 16px; background: var(--panel);
}
/* The title gets the whole width. It used to share the row with two buttons,
   which on anything longer than a few words wrapped it one word per line. */
.ticket header h3 { margin: 0 0 8px; font-size: 16px; line-height: 1.35; }
/* The position, not part of the title: it says what order to file these in. */
.ticket header .position {
  display: inline-block; min-width: 1.4em; color: var(--muted);
  font-weight: 400; font-variant-numeric: tabular-nums;
}

.ticket-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line);
}
.ticket-foot .filed { display: flex; align-items: center; gap: 10px; }
/* One size for both, so neither reads as the primary action — filing and
   copying are two ways of doing the same thing. */
.ticket-foot .button { padding: 5px 12px; font-size: 12px; }
.ticket > p { margin: 0 0 12px; }

/* Screenshots sit side by side when they fit: a storyboard and the still it
   relates to are usually read together. */
.ticket .shots {
  display: grid; gap: 12px; margin-bottom: 12px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.ticket .shots figure { margin: 0; }
.ticket .shots img {
  width: 100%; display: block; border-radius: 8px;
  border: 1px solid var(--line); background: var(--bg);
}
.ticket .shots figcaption {
  font-size: 12px; color: var(--muted);
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 6px;
}

.ticket .notes-block h4 {
  margin: 0 0 4px; font-size: 11px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
.ticket .notes-block p { margin: 0 0 12px; font-size: 14px; }

.ticket .md summary { font-size: 12px; color: var(--muted); cursor: pointer; }
.ticket .md pre {
  margin: 8px 0 0; padding: 12px; overflow-x: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
  background: var(--bg); border: 1px solid var(--line); border-radius: 8px;
}

.draft-foot {
  display: flex; align-items: center; gap: 16px;
  font-size: 12px; margin-bottom: 24px;
}
.draft-foot .link.danger { color: var(--danger); background: none; border: none; cursor: pointer; }

/* Notes are bullets now, not a paragraph: the same content reads as a list and
   gets skimmed, where a block of prose in a ticket gets skipped. */
.ticket .notes-block ul { margin: 0 0 12px; padding-left: 18px; }
.ticket .notes-block li { font-size: 14px; margin-bottom: 3px; }
.ticket .notes-block .link-note { color: var(--muted); word-break: break-all; }

/* Shared files -------------------------------------------------------------
   A file dropped on the menu bar icon. Whatever we can render inline sits in
   the same slot the player would; the header above it is the constant, because
   the file itself is what someone came for and the download must never be
   something you have to go looking for. */

audio { width: 100%; }

/* Framed documents take the pane. `flex: 1 1 auto` rather than a height, so the
   bottom edge lands where the window ends however tall the heading above it
   turned out to be — a two-line title and a tab row are not the same offset, and
   neither is a number you can put in a stylesheet. */
.pdf {
  width: 100%; flex: 1 1 auto; min-height: 0; display: block;
  border: 1px solid var(--line); border-radius: 12px; background: var(--panel);
}

/* A shared HTML file, rendered. Same slot and same shape as the PDF frame —
   what's inside is a whole page someone else wrote, so the border is what says
   where this site stops and their document starts.

   White rather than var(--panel), in both themes: a document that sets no
   background of its own is transparent, and in dark mode that puts its black
   text on our dark panel. The page was written expecting a white browser and
   this is a browser. */
.page {
  width: 100%; flex: 1 1 auto; min-height: 0; display: block;
  border: 1px solid var(--line); border-radius: 12px; background: #fff;
}

/* Page / Source. Two links, not a control: each reading of an HTML file is its
   own address, so either can be linked to and Back does the obvious thing. */
.viewtabs { flex: 0 0 auto; display: flex; gap: 4px; margin-bottom: 10px; }
.viewtabs a {
  padding: 4px 10px; font-size: 12px; color: var(--muted);
  border: 1px solid var(--line); border-radius: 6px; text-decoration: none;
}
.viewtabs a:hover { color: var(--ink); border-color: var(--muted); }
.viewtabs a.on { color: var(--ink); background: var(--panel); border-color: var(--muted); }

/* Capped: two lines of text and a button stretched across a wide stage reads
   as a layout mistake rather than as an empty state. */
.no-viewer { max-width: 420px; }
.no-viewer p { margin: 0 0 4px; }
.no-viewer .button { margin-top: 14px; }

/* The reader ----------------------------------------------------------------
   Text people share is mostly a log, a diff, or a README, and all three are
   read rather than skimmed. Generous measure, real line height, and a gutter —
   the difference between "the file is on the page" and "I can read this". */

/* No height and no cap: the default `flex: 0 1 auto` is exactly the behaviour
   this wants. A short file gets a short box, and one with more than a screenful
   shrinks to the pane — `min-height: 0` is what permits that — and scrolls
   inside, ending level with the video pane and the rail beside it. */
.reader {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; padding: 20px 24px;
  min-height: 0; overflow: auto;
}

.reader .lines {
  list-style: none; counter-reset: line; margin: 0; padding: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; line-height: 1.6;
}
.reader .lines li {
  counter-increment: line;
  /* pre-wrap keeps indentation and tabs; min-height keeps a blank line a line. */
  white-space: pre-wrap; word-break: break-word; min-height: 1.6em;
  padding-left: 52px; text-indent: -52px;
}
.reader .lines li::before {
  content: counter(line);
  display: inline-block; width: 40px; margin-right: 12px;
  text-align: right; text-indent: 0;
  color: var(--muted); font-variant-numeric: tabular-nums;
  /* Line numbers are furniture: they shouldn't come along with a copy. */
  user-select: none; -webkit-user-select: none;
}
.reader .truncated { margin: 16px 0 0; }

/* Rendered Markdown. Narrower than the page because prose is read at a measure,
   not at whatever width the window happens to be. */
.reader.prose { max-width: 74ch; line-height: 1.65; }
.reader.prose > :first-child { margin-top: 0; }
.reader.prose > :last-child { margin-bottom: 0; }
.reader.prose h1 { font-size: 22px; margin: 28px 0 12px; }
.reader.prose h2 { font-size: 18px; margin: 26px 0 10px; }
.reader.prose h3 { font-size: 15px; margin: 22px 0 8px; }
.reader.prose p, .reader.prose ul, .reader.prose ol { margin: 0 0 14px; }
.reader.prose li { margin-bottom: 4px; }
.reader.prose blockquote {
  margin: 0 0 14px; padding: 2px 0 2px 14px;
  border-left: 3px solid var(--line); color: var(--muted);
}
.reader.prose hr { border: 0; border-top: 1px solid var(--line); margin: 24px 0; }
.reader.prose img { max-width: 100%; border-radius: 8px; }
.reader.prose code {
  padding: 1px 5px; border-radius: 5px;
  background: var(--bg); border: 1px solid var(--line);
}
.reader.prose pre {
  margin: 0 0 14px; padding: 14px; overflow-x: auto;
  background: var(--bg); border: 1px solid var(--line); border-radius: 8px;
}
/* A code block is already in a box; the inline chrome would double it up. */
.reader.prose pre code { padding: 0; border: 0; background: none; }
.reader.prose table { border-collapse: collapse; width: 100%; margin: 0 0 14px; }
.reader.prose th, .reader.prose td {
  padding: 7px 10px; border: 1px solid var(--line); text-align: left;
}
.reader.prose th { background: var(--bg); font-weight: 600; }

/* A shared installer's Gatekeeper verdict. Deliberately quiet: it's a fact
   about the file, not a seal of approval this server is in any position to
   give — the check ran on someone else's Mac. */
.signed {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 16px; margin-bottom: 16px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
}
.signed .authority {
  display: block; font-size: 14px; word-break: break-word;
}
.signed .meta { margin-top: 2px; }

/* Where in the recording a ticket came from. Sits under the description because
   it's the ticket's provenance, not one more note at the bottom — whoever picks
   this up should be one click from the bit it came from. */
.ticket .from-recording {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
  margin: 0 0 14px; font-size: 13px;
}
.ticket .from-recording a { font-variant-numeric: tabular-nums; }
.ticket .from-recording .label { color: var(--muted); }
.ticket .from-recording .sep { color: var(--line); }

/* Settings on a link's page ------------------------------------------------
   Two forms in one card — change the title, or delete the thing — and without
   a boundary their buttons stack into what reads as one control with two
   labels. The last field's margin also collapses against the Save button, so
   the pair sit flush. Both fixed here rather than with a stray <br>. */

/* Settings, in the rail --------------------------------------------------------
   Two forms in one narrow column — change what this is, or delete it — and
   without a boundary their buttons stack into what reads as one control with
   two labels. The rule under the second one is doing real work: the things
   either side of it are "rename it" and "this is gone forever".

   These rules used to hang off a <details class="card settings"> on the old
   page. When it moved into the rail the class went with the card, so every one
   of them silently stopped applying and the panel had no spacing at all. */

.settings label { display: block; margin-top: 14px; font-size: 12px; }
.settings label:first-of-type { margin-top: 0; }
.settings input, .settings select { margin: 5px 0 0; }
.settings > form > .button { margin-top: 16px; }

.settings .danger-zone {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  margin-top: 28px; padding-top: 20px;
  border-top: 1px solid var(--line);
}
.settings .danger-zone .hint { font-size: 12px; color: var(--muted); }

/* A hint under a field, rather than under a button — the password and
   self-destruct controls both need a sentence, and without this they inherit
   the inline layout the actions panel gives .hint. */
.settings .hint {
  display: block; margin-top: 5px;
  font-size: 12px; line-height: 1.45; color: var(--muted);
}
/* A checkbox and its words on one line. `.settings label` is a block with a top
   margin, which is right for a field label and wrong for this. */
.settings label.check {
  display: flex; align-items: center; gap: 7px;
  margin-top: 10px; color: var(--ink); font-weight: 400;
}
.settings label.check input { width: auto; margin: 0; }
/* A setting that only means something while the one above it is on. The indent
   is the whole explanation; see the Comments section for the pair it draws. */
.settings label.check.nested { margin-top: 8px; padding-left: 20px; }
.settings label.check input:disabled { opacity: 0.5; }
.settings label.check:has(input:disabled) { opacity: 0.5; }

/* The countdown on a card in the library. Bordered in the danger colour rather
   than filled with it: it is a warning about the future, not an error, and
   thirty of them down a grid in solid red is a page that looks broken. */
.links .badge.going { border-color: var(--danger); color: var(--danger); }


/* The viewer ----------------------------------------------------------------
   Two columns for the height of the window: the thing itself on the left, and
   everything about it on the right. The media pane deliberately does not
   scroll, so the video stays on screen however far down the transcript you
   read — which is the whole reason the transcript is beside it rather than
   under it. */

.viewer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 400px;
  /* The topbar is 52px; the rest is ours. */
  height: calc(100vh - 52px);
}

.stage {
  display: flex; flex-direction: column; min-width: 0;
  padding: 20px 24px 24px;
  /* Only the reader-style viewers have more than a screenful; a video never
     scrolls its own pane. */
  overflow: hidden;
}
/* Only the card viewer scrolls its stage. The readers and the framed documents
   below fill the pane and scroll inside it instead — a stage that scrolled *and*
   a reader that scrolled was two scrollbars for one file, and the outer one
   took the title off screen while the inner one still had a screenful left. */
.viewer-download .stage { overflow-y: auto; }

.stage-head { flex: 0 0 auto; margin-bottom: 14px; }
.stage-head .meta { margin: 2px 0 0; }

/* Top-aligned, not centred. The pane is the full height of the window and the
   media almost never fills it, so centring floated a 16:9 video in the middle of
   the column with a band of empty above it — and pushed the title away from the
   thing it names. Lined up from the top, the heading, the video and the rail's
   first tab all start on the same line. */
.stage .media {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; justify-content: flex-start;
}
/* Fit the pane rather than the column width: a tall window should make the
   video taller, not push it off the bottom.

   `flex: 0 1 auto` with `min-height: 0` is what keeps the rows underneath it —
   the comment markers, the speed control, the Comment button — on screen. The
   stage clips its overflow, so a portrait capture that filled the pane on its
   own used to push all three off the bottom and out of existence. Shrinking is
   allowed but never asked for: with room to spare nothing here changes, and
   `object-fit: contain` means the picture letterboxes rather than distorts on
   the occasions it does. */
.stage .media video,
.stage .media .shot {
  max-height: 100%; max-width: 100%;
  flex: 0 1 auto; min-height: 0;
  width: auto; margin: 0 auto; object-fit: contain;
}
/* Waiting for the file ---------------------------------------------------------
   The link is handed out before the upload finishes, so this is what a lot of
   people see first — the page is doing its job when it looks calm rather than
   broken. Panel-coloured and centred like the download card, no error styling,
   nothing that suggests a problem. Failed is the same box with the danger
   colour on its border, because that one *is* a problem. */
.processing {
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  padding: 32px 28px; text-align: center; margin: 0 auto; max-width: 460px;
}
.processing h2 { margin: 0 0 8px; font-size: 17px; }
.processing p { margin: 0 0 4px; color: var(--muted); font-size: 14px; }
.processing .meta { margin-top: 14px; font-size: 13px; }
.processing.failed { border-color: var(--danger); }
.processing.failed h2 { color: var(--danger); }

/* Three dots, not a spinner: this waits on a file that can be gigabytes, and a
   spinning circle promises "a second or two". They pulse in sequence, which
   reads as progress without claiming to know how much. */
.processing .dots { display: flex; gap: 6px; justify-content: center; margin: 0 0 16px; }
.processing .dots span {
  width: 7px; height: 7px; border-radius: 50%; background: var(--muted);
  animation: dot-pulse 1.4s ease-in-out infinite;
}
.processing .dots span:nth-child(2) { animation-delay: 0.2s; }
.processing .dots span:nth-child(3) { animation-delay: 0.4s; }

@keyframes dot-pulse {
  0%, 80%, 100% { opacity: 0.25; }
  40% { opacity: 1; }
}

/* A page that reloads itself every ten seconds already moves; something
   blinking on it as well is just noise to anyone who asked for less of it. */
@media (prefers-reduced-motion: reduce) {
  .processing .dots span { animation: none; opacity: 0.6; }
}

/* The download card is the one thing here that isn't sized by the pane: it's
   two lines and a button, and `display: block` keeps it that shape. Everything
   else stays a flex column so the reader or the frame can take the height the
   heading above it didn't. */
.viewer-download .stage .media { display: block; justify-content: flex-start; }

/* The title, editable in place ------------------------------------------------
   Still an <h1>: an input styled to look like a heading isn't one, and this is
   the page's heading for anything reading the document rather than looking at
   it. The affordance is hover, then a real field on focus. */
.stage-head .title {
  font-size: 20px; margin: 0; border-radius: 6px;
  padding: 3px 7px; margin-left: -7px;
  border: 1px solid transparent;
}
.title.editable { cursor: text; }
.title.editable:hover { background: var(--panel); border-color: var(--line); }
.title.editable:focus {
  outline: none; background: var(--bg);
  border-color: var(--accent); cursor: text;
}

/* The rail ------------------------------------------------------------------ */

.rail {
  display: flex; flex-direction: column; min-height: 0;
  background: var(--panel);
  border-left: 1px solid var(--line);
}

.tabs {
  flex: 0 0 auto; display: flex; gap: 2px;
  padding: 0 12px; border-bottom: 1px solid var(--line);
  overflow-x: auto;
}
.tab {
  background: none; border: 0; padding: 13px 10px 11px;
  font: inherit; font-size: 13px; color: var(--muted);
  border-bottom: 2px solid transparent; cursor: pointer; white-space: nowrap;
}
.tab:hover { color: var(--ink); }
.tab.active { color: var(--ink); border-bottom-color: var(--accent); }

/* One panel at a time, and it is the thing that scrolls. */
.panel { display: none; overflow-y: auto; padding: 16px; flex: 1 1 auto; }
.panel.active { display: block; }

/* Transcript, in a column rather than a table ---------------------------------
   The old layout was three columns across 900px. At 400px the timestamp stays a
   gutter and everything else wraps under it, or the text ends up two words
   wide. */
.rail .transcript li {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--line);
}
.rail .transcript .time {
  flex: 0 0 34px; padding: 0; text-align: left;
  font-size: 12px; color: var(--accent);
  background: none; border: 0; cursor: pointer;
  font-variant-numeric: tabular-nums; line-height: 1.5;
}
.rail .transcript .time:hover { text-decoration: underline; }
.rail .transcript .line { flex: 1; min-width: 0; }
.rail .transcript .source {
  display: block; flex: none; font-size: 10px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 1px;
}
.rail .transcript .text { display: inline; }

.rail .panels img { border-radius: 8px; }
.rail .panels li { margin-bottom: 20px; }

/* About ---------------------------------------------------------------------- */

.about { display: grid; grid-template-columns: auto 1fr; gap: 6px 14px; margin: 0 0 20px; }
.about dt { font-size: 12px; color: var(--muted); }
.about dd { margin: 0; font-size: 13px; word-break: break-word; }

.share { display: flex; gap: 8px; flex-wrap: wrap; }
.button.ghost {
  background: none; color: var(--accent);
  border: 1px solid var(--line);
}

/* The rail is 400px, so the ticket drafts inside it lose their side-by-side
   screenshots and their generous measure. */
.rail .ticket { padding: 12px; }
.rail .ticket .shots { grid-template-columns: 1fr; }
.rail .actions .hint { max-width: none; }

/* Comments ------------------------------------------------------------------
   Two surfaces for one feature: a rail of markers under the player, and a
   thread in the tab beside it. They share the timestamp and nothing else — the
   markers are about the video, the thread is about the conversation. */

/* The marker rail. Under the video and above the controls, which is as close to
   the scrubber as anything can get while the controls stay native — see
   static/player.js for why they do. Width and offset are set in script to match
   the video's rendered box; the height is reserved here so the layout doesn't
   jump when the first comment arrives. */
.comment-markers {
  position: relative;
  flex: 0 0 auto;
  height: 22px;
  margin: 4px auto 0;
}
.marker {
  position: absolute; top: 0;
  /* `left` is a percentage of the rail set in script; this pulls the bubble
     back by half its width so it points at the moment rather than starting
     there. */
  transform: translateX(-50%);
  background: none; border: 0; padding: 0 3px;
  color: var(--muted); cursor: pointer; line-height: 1;
}
.marker:hover, .marker:focus-visible { color: var(--accent); }
.marker .bubble { display: block; }

/* The hover card. Kept out of the flow entirely so a row of markers can't grow
   the rail, and pointer-events off so it never eats the click meant for the
   bubble underneath it. */
.marker .peek {
  position: absolute; bottom: 22px; left: 50%;
  transform: translateX(-50%);
  display: none; gap: 8px; align-items: flex-start;
  width: max-content; max-width: 260px;
  padding: 8px 10px; border-radius: 10px;
  background: var(--ink); color: var(--bg);
  font-size: 12px; line-height: 1.4; text-align: left;
  pointer-events: none; z-index: 3;
}
.marker:hover .peek, .marker:focus-visible .peek { display: flex; }
/* Near the right-hand end the card would hang off the pane, so it hangs off its
   own right edge instead. */
.marker.near-end .peek { left: auto; right: 0; transform: none; }
.marker .peek .name { display: block; font-weight: 600; margin-bottom: 2px; }
.marker .peek .text { display: block; opacity: 0.85; }
.marker .peek .avatar { width: 20px; height: 20px; font-size: 10px; }

/* The composer over the video ------------------------------------------------
   An overlay, not another row: opening it must not resize the player. See
   recordings/_comment_composer.html. */
.stage .media { position: relative; }
.commenting { flex: 0 0 auto; display: flex; justify-content: center; margin-top: 8px; }
.button.ghost.add-comment {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; font-size: 13px;
}

.stage-composer {
  position: absolute; left: 50%; bottom: 8px;
  transform: translateX(-50%);
  width: min(560px, 92%);
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 12px 32px rgb(0 0 0 / 0.28);
  z-index: 4;
}
.stage-composer[hidden] { display: none; }
.composer-foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; }

/* The thread ---------------------------------------------------------------- */

.panel.comments { padding: 0; }
.panel.comments > div { padding: 16px; }

.composer { display: flex; flex-direction: column; gap: 8px; }
.composer textarea {
  width: 100%; resize: vertical; min-height: 56px;
  padding: 9px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg); color: var(--ink);
  font: inherit; font-size: 13px;
}
.composer textarea:focus { outline: none; border-color: var(--accent); }
.composer .identity { display: flex; gap: 8px; }
.composer .identity input { flex: 1; min-width: 0; margin: 0; }
.composer .hint { font-size: 12px; color: var(--muted); }
.composer .as { margin: 0; font-size: 12px; color: var(--muted); }
.composer .as .link { font-size: 12px; margin-left: 6px; }
.composer > .button { align-self: flex-start; }

.comment-error { margin: 0 0 10px; }

.threads { list-style: none; padding: 0; margin: 18px 0 0; }
.threads > li {
  padding-top: 14px; margin-top: 14px;
  border-top: 1px solid var(--line);
}
.threads > li:first-child { border-top: 0; margin-top: 0; }
.empty { font-size: 13px; color: var(--muted); margin: 18px 0 0; }

.comment .who { display: flex; align-items: center; gap: 7px; margin-bottom: 4px; }
.comment .avatar {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%;
  object-fit: cover;
}
/* The stand-in for a picture we don't have. Guests never have one, and neither
   does anyone who signed in with a password rather than Slack. */
.avatar.initial {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent); color: #fff;
  font-size: 11px; font-weight: 600;
}
.comment .name { font-size: 13px; font-weight: 600; }
.comment .badge.guest { font-size: 9px; padding: 1px 5px; }
/* Pushed to the far end, so the names line up down the left however long they
   are and the delete button lands in the same place on every row. */
.comment .when { margin-left: auto; font-size: 11px; color: var(--muted); }
.comment .remove { display: inline; }
.comment .remove .link { font-size: 11px; }
.comment .body { margin: 0; font-size: 13px; line-height: 1.5; white-space: pre-wrap; }

/* The timestamp reads as the link it is — same colour and same tabular figures
   as a transcript line, because it does the same job. */
.comment .stamp {
  background: none; border: 0; padding: 0; margin-bottom: 3px;
  font: inherit; font-size: 12px; color: var(--accent);
  font-variant-numeric: tabular-nums; cursor: pointer;
}
.comment .stamp:hover { text-decoration: underline; }

/* A reply. One level, and the rule down the left is what says so — an indent
   alone stops reading as nesting the moment two of them are on screen. */
.comment.reply {
  margin: 12px 0 0 12px; padding-left: 12px;
  border-left: 2px solid var(--line);
}

/* Where a marker sent you. A flash, not a state: it answers "which one" and
   then gets out of the way. */
.comment.found { animation: found 1.6s ease-out; }
@keyframes found {
  from { background: color-mix(in srgb, var(--accent) 22%, transparent); }
  to { background: transparent; }
}

/* Reply, as a disclosure — the browser already has one, and this is exactly
   what it is for. */
.reply-toggle { margin: 10px 0 0 24px; }
.reply-toggle summary {
  font-size: 12px; color: var(--muted); cursor: pointer;
  list-style: none; width: max-content;
}
.reply-toggle summary::-webkit-details-marker { display: none; }
.reply-toggle summary:hover { color: var(--ink); }
.reply-toggle[open] summary { margin-bottom: 8px; }

/* The count on the tab. Quiet: it says how much is in there, it is not a
   notification badge asking to be cleared. */
.tab .count {
  display: inline-block; min-width: 16px; padding: 0 4px;
  border-radius: 8px; background: var(--line); color: var(--muted);
  font-size: 11px; line-height: 16px; text-align: center;
}
.tab.active .count { background: var(--accent); color: #fff; }

@media (max-width: 900px) {
  /* One column, and the media stops being fixed — there is no room to keep it
     on screen and a phone scrolls anyway. */
  .viewer { grid-template-columns: 1fr; height: auto; }
  /* The composer stops being an overlay. On a phone the pane is the width of
     the screen and a box floating over the bottom of the video covers the
     controls you would need to scrub back to the moment you're commenting on. */
  .stage-composer {
    position: static; transform: none; width: 100%;
    box-shadow: none; margin-top: 8px;
  }
  .commenting { flex-direction: column; align-items: stretch; }
  .stage { overflow: visible; }
  .stage .media video, .stage .media .shot { max-height: none; }
  /* With no window-height pane to fill, an iframe falls back to its intrinsic
     150px, so the frames take a fixed slice again. The reader doesn't need one:
     it grows to its content and the page scrolls, which is the single scrollbar
     a phone should have. */
  .pdf, .page { height: min(80vh, 900px); flex: 0 0 auto; }
  .rail { border-left: 0; border-top: 1px solid var(--line); }
  .panel { overflow: visible; }
}

/* Links, as cards -------------------------------------------------------------
   A list of forty rows is a list nobody scans. The thumbnail is the thing you
   recognise, so it gets the space. */

.links {
  list-style: none; padding: 0; margin: 0;
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}
.links li {
  display: flex; flex-direction: column; margin: 0;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; overflow: hidden;
}
.links li:hover { border-color: var(--muted); }
.links a { display: block; color: var(--ink); }
.links a:hover { text-decoration: none; }

/* The shared thumbnail include, at card size rather than row size. */
.links .thumb {
  width: 100%; height: auto; aspect-ratio: 16 / 9;
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
}
.links .thumb svg { width: 30px; height: 30px; }

/* `display: block` is load-bearing, not tidiness. This is a <span>, so without
   it the padding below is set on an inline box — where vertical padding pushes
   nothing and the block-level children break out of the horizontal padding
   too. The rule was here and having no effect at all. */
.links .body { display: block; padding: 12px 14px 14px; }
.links .title { font-size: 14px; line-height: 1.35; margin-bottom: 5px; }
.links .meta { font-size: 12px; line-height: 1.45; }
/* A filename wraps to three lines and a title to one, so the cards only line up
   if the text block is the same height in both. */
.links .body .title { overflow-wrap: anywhere; }
.links .badges { padding: 0 14px 12px; display: flex; gap: 6px; }

/* The library's header and toolbar ------------------------------------------
   One row that says what you're looking at and how much of it there is, then
   one row of controls. Search, type, visibility — three answers to "I know I
   shared it, where is it". */

.library-head {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
}
.library-head h1 { margin-bottom: 12px; }
.library-head .meta { margin: 0 0 12px; }

.filters {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 22px;
}
/* Both of these default to full width across the site, which in a toolbar means
   one control per line. */
.filters input[type="search"] {
  width: auto; flex: 1 1 240px; min-width: 180px; max-width: 340px; margin: 0;
  padding: 8px 11px;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--line); border-radius: 8px; font: inherit;
}
.filters select { width: auto; margin: 0; padding: 8px 10px; background: var(--panel); }
.filters .button { padding: 8px 14px; }
.filters .clear { font-size: 13px; }

/* Type chips. Radios rather than links, so an unsubmitted search survives
   pressing one — the input is hidden and its label is the whole control, which
   keeps the keyboard behaviour (arrow keys move through the group) for free. */
.chips {
  display: flex; gap: 6px; flex-wrap: wrap;
  margin: 0; padding: 0; border: 0;
}
.chip { margin: 0; cursor: pointer; }
.chip input {
  /* Not display:none — that takes it out of the focus order with it. */
  position: absolute; opacity: 0; width: 1px; height: 1px;
}
.chip span {
  display: inline-block; padding: 6px 12px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--line);
  color: var(--muted); font-size: 13px; line-height: 1.2;
}
.chip:hover span { color: var(--ink); }
.chip:has(input:checked) span {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.chip:has(input:focus-visible) span { outline: 2px solid var(--accent); outline-offset: 2px; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* Selecting cards ------------------------------------------------------------
   A checkbox per card, out of the way until it's wanted: hidden until the card
   is hovered or focused, and pinned visible for the whole grid once anything is
   selected — mid-selection you want to see what else is selectable without
   hunting for it with the mouse. */

.links li { position: relative; }

.links .select {
  position: absolute; top: 8px; left: 8px; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 7px;
  background: var(--panel); border: 1px solid var(--line);
  /* The thumbnail underneath can be any colour, so the chip carries its own
     backdrop rather than relying on the picture being pale. */
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.22);
  opacity: 0; transition: opacity 0.12s ease;
  cursor: pointer;
}
.links li:hover .select,
.links .select:focus-within,
/* Once anything is ticked, every checkbox stays out — mid-selection you want to
   see what else is selectable without hunting for it with the mouse. Set by the
   script on the grid itself; see index.html. */
.links.selecting .select,
/* And a ticked one shows on its own terms, so a card can never be selected
   while looking exactly like a card that isn't. */
.links li:has(input:checked) .select { opacity: 1; }
.links .select input { margin: 0; cursor: pointer; accent-color: var(--accent); }

/* A ticked card should look picked from across the grid, not just at its
   corner. */
.links li:has(input:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

/* The bulk bar --------------------------------------------------------------
   Fixed to the foot of the window rather than sitting in the flow, so that
   selecting something doesn't reflow the grid you're selecting from. */

.bulk {
  position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
  z-index: 10;
  display: flex; align-items: center; gap: 16px;
  padding: 10px 14px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--line);
  box-shadow: 0 6px 24px rgb(0 0 0 / 0.18);
}
.bulk[hidden] { display: none; }
.bulk .count { font-size: 13px; font-variant-numeric: tabular-nums; }
.bulk .link { font-size: 13px; }
.bulk .button { padding: 7px 14px; font-size: 13px; }

/* Pagination ----------------------------------------------------------------
   Newer and older rather than numbers: the grid is ordered by capture time, and
   "page 4" is not something anyone is looking for. */

.pages {
  display: flex; align-items: center; justify-content: center; gap: 20px;
  margin-top: 28px;
}
.pages .disabled { color: var(--line); }
.pages .meta { font-variant-numeric: tabular-nums; }

/* Playback speed ------------------------------------------------------------ */
/* The one control the native player doesn't offer. Sits under the video rather
   than floating over it: an overlay on a screen recording covers the thing you
   are trying to look at. */

.speeds {
  /* Wraps because the ladder is six long now. On a phone the row would
     otherwise run off the side of the pane rather than fold, and 2x — the
     end people reach for most — is the one that would go. */
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  padding: 8px 0 0; font-size: 12px;
}
.speeds .label { color: var(--muted); margin-right: 4px; }
.speeds .rate {
  background: none; border: 1px solid transparent; color: var(--muted);
  border-radius: 6px; padding: 2px 8px; font: inherit;
  cursor: pointer; font-variant-numeric: tabular-nums;
}
.speeds .rate:hover { color: var(--ink); }
.speeds .rate.active {
  color: var(--ink); border-color: var(--line); background: var(--panel);
}
.speeds .open { margin-left: auto; }

/* Filing a draft into GitHub ------------------------------------------------ */

.filing {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 14px 16px; margin-bottom: 20px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
}
.filing label { margin: 0; }
/* The select is the one control on this bar, so it sizes to its content rather
   than taking the full-width default every other input on the site wants. */
.filing select { width: auto; min-width: 260px; margin: 0; }
.filing .meta { flex-basis: 100%; margin: 0; }
.filing .button { margin-left: auto; }

.ticket-foot .filed .error { max-width: 320px; }
.filed .issue { font-size: 13px; }

.granted { margin: 12px 0; font-size: 12px; }
.granted dt { color: var(--muted); margin-top: 6px; }
.granted dd { margin: 2px 0 0; }
.granted code { word-break: break-all; }

/* Integrations -------------------------------------------------------------- */

.integration { margin-bottom: 20px; }
.integration header {
  display: flex; align-items: center; gap: 10px; margin-bottom: 6px;
}
.integration header h2 { margin: 0; }
.integration p { margin: 10px 0; }
/* A row of controls that are peers, not a primary and its footnotes — "Resync",
   "Manage on GitHub" and "Disconnect" all sit at the same weight. */
.integration .row {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 16px;
}
.integration .row form { margin: 0; }
.integration .avatar { border-radius: 50%; vertical-align: -4px; margin-right: 4px; }
/* The one thing on this page worth interrupting someone for: an install that
   won't pick up new repositories, which otherwise shows up as a missing option
   in a dropdown three weeks from now. */
.note {
  padding: 10px 14px; border-radius: 8px; font-size: 13px;
  background: var(--bg); border: 1px solid var(--line);
}
