:root {
  color-scheme: light;
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #6b7280;
  --border: #d1d5db;
  --accent: #2563eb;
  --accent-fg: #ffffff;
  --danger: #b91c1c;
  --success: #15803d;
  --warn: #b45309;
  --surface: #f9fafb;
  --code-bg: #f3f4f6;
  --stripe: #f6f8fb;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Arial Hebrew", Arial, sans-serif;
  background: var(--bg);
  color: var(--fg);
  line-height: 1.6;
}

nav {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 1.5rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

nav a {
  color: var(--fg);
  text-decoration: none;
  font-weight: 500;
}

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

nav form {
  margin-inline-start: auto;
}

/* One wrapper for page content, rather than sizing every direct child of
   <body> individually — that gave each heading/form/table its own padded
   960px box, doubling the gaps between adjacent blocks and leaving nothing
   for a wide table to scroll within. */
main {
  max-width: 1100px;
  margin: 0 auto;
  padding: 1.5rem 1.5rem 4rem;
}

h1,
h2,
h3 {
  font-family: inherit;
  font-weight: 600;
}

h1 {
  font-size: 1.75rem;
  margin: 0.5rem 0 1rem;
}

h2 {
  font-size: 1.25rem;
  margin: 1.75rem 0 0.75rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--border);
}

p {
  color: var(--fg);
}

p.error {
  color: var(--danger);
  font-weight: 500;
}

p.info {
  color: var(--accent);
}

a {
  color: var(--accent);
}

label {
  display: block;
  font-size: 0.85rem;
  color: var(--muted);
  margin: 0.75rem 0 0.25rem;
}

input[type="text"],
input[type="password"],
input[type="search"],
input[type="email"],
select {
  width: 100%;
  max-width: 420px;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  font-size: 0.95rem;
  margin: 0.15rem 0;
}

input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  vertical-align: middle;
  margin-inline-end: 0.35rem;
}

button,
input[type="submit"] {
  display: inline-block;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  margin: 0.25rem 0.25rem 0.25rem 0;
  transition: opacity 0.1s ease;
}

button:hover {
  filter: brightness(1.08);
}

/* Lets an <a> render as a button — the view link is the routine action on a
   row, so it reads as the primary control while the rare ones are icons. */
a.btn {
  display: inline-block;
  padding: 0.35rem 0.8rem;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 0.85rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}

a.btn:hover {
  filter: brightness(1.08);
  text-decoration: none;
}

/* Icon-only controls for the uncommon actions. Square, quiet by default, and
   only picking up colour on hover so a row of them stays calm. The accessible
   name comes from aria-label/title on the button — there is no visible text. */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--muted);
}

.btn-icon:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--surface);
  filter: none;
}

.btn-icon-danger:hover {
  color: var(--danger);
  border-color: var(--danger);
}

/* Keyboard users get the same affordance as the tooltip gives pointer users. */
.btn-icon:focus-visible,
a.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

button[type="button"],
.btn-secondary {
  background: var(--surface);
  color: var(--fg);
  border-color: var(--border);
}

.btn-secondary:hover {
  background: var(--bg);
  border-color: var(--muted);
}

/* Destructive actions read as destructive. Also stops a row of actions being
   an undifferentiated wall of identical blue blocks, so the eye can tell the
   irreversible one apart from the routine ones. */
.btn-danger {
  background: var(--bg);
  color: var(--danger);
  border-color: var(--danger);
}

.btn-danger:hover {
  background: var(--danger);
  color: var(--accent-fg);
  filter: none;
}

/* htmx request feedback: htmx adds this class to the element (or its
   hx-indicator target) for the duration of the request -- gives an
   immediate visual response to every button click instead of nothing
   happening until the response lands. */
.htmx-request,
.htmx-request * {
  cursor: progress !important;
}

button.htmx-request,
button[disabled] {
  opacity: 0.55;
  cursor: progress;
  pointer-events: none;
}

form {
  margin: 0.5rem 0;
}

/* overflow-x: auto, not hidden. A table wider than the column — the users
   table became one once the actions cell gained a password field — was being
   clipped outright, putting the far columns permanently out of reach with no
   scrollbar to reveal them. */
.table-wrap {
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow-x: auto;
  margin: 1rem 0;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin: 0;
}

.table-wrap table {
  margin: 0;
}

th,
td {
  text-align: start;
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

th {
  color: var(--muted);
  font-weight: 600;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  background: var(--surface);
}

tbody tr:nth-child(even) td {
  background: var(--stripe);
}

tr:hover td {
  background: var(--surface);
}

tr:last-child td {
  border-bottom: none;
}

/* Row actions are a link plus several one-button forms sharing one cell. One
   flex row with a single gap keeps them evenly spaced and vertically centred —
   as bare inline elements the link carried no margin of its own and sat flush
   against the first button, and the forms wrapped onto separate lines. */
.row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.5rem;
}

.row-actions > a {
  white-space: nowrap;
}

td form {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  vertical-align: middle;
}

td input[type="password"],
td input[type="text"],
td input[type="search"],
td input[type="email"] {
  width: 10rem;
  max-width: 10rem;
  margin: 0;
  font-size: 0.85rem;
  padding: 0.35rem 0.5rem;
}

td button {
  margin: 0;
  white-space: nowrap;
  padding: 0.35rem 0.7rem;
  font-size: 0.85rem;
}

.pagination {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1rem 0;
  color: var(--muted);
}

.pagination a {
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  text-decoration: none;
  color: var(--fg);
}

.pagination a:hover {
  background: var(--surface);
}

/* Transcript: source and translation side by side on a time/speaker rail.
   Kept deliberately in sync with TRANSCRIPT_CSS in src/render.ts, which is the
   same design for the downloadable copy — that file is opened from disk with no
   server, and `style-src 'self'` forbids inlining this block into the page, so
   neither can import the other. Change one, change the other.

   Its own palette on purpose: the transcript is a document, and reads as paper
   rather than as app chrome. Scoped under .transcript so it can't leak into the
   surrounding nav and tables. */
.transcript {
  --paper: #fdfdfc;
  --ink: #191917;
  --ink-soft: #6c6c65;
  --rule: #e7e6e1;
  --rule-soft: #f2f1ed;
  --sp0: #2f4b7c;
  --sp1: #1f6f63;
  --sp2: #a8621c;
  background: var(--paper);
  color: var(--ink);
  font: 15px/1.65 "Segoe UI", "Noto Sans Hebrew", "Noto Sans Arabic", Arial, sans-serif;
  margin-top: 1rem;
}

.transcript .head {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--paper);
  display: grid;
  grid-template-columns: 58px 1fr 1fr;
  gap: 0 28px;
  padding: 10px 0 8px;
  border-bottom: 1px solid var(--rule);
}

.transcript .head span {
  font-size: 11px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.transcript .head span:first-child {
  text-align: center;
}

.transcript .row {
  display: grid;
  grid-template-columns: 58px 1fr 1fr;
  gap: 0 28px;
  padding: 11px 12px 11px 0;
  border-bottom: 1px solid var(--rule-soft);
  align-items: start;
}

.transcript .row:hover {
  background: #faf9f6;
}

.transcript .rail {
  text-align: center;
  font-size: 11px;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  padding-top: 3px;
  line-height: 1.4;
}

.transcript .who {
  display: block;
  margin-top: 3px;
  font-size: 10px;
  letter-spacing: 0.04em;
  font-weight: 600;
}

.transcript .r0 {
  border-right: 2px solid var(--sp0);
}

.transcript .r1 {
  border-right: 2px solid var(--sp1);
}

.transcript .r2 {
  border-right: 2px solid var(--sp2);
}

.transcript .r0 .who {
  color: var(--sp0);
}

.transcript .r1 .who {
  color: var(--sp1);
}

.transcript .r2 .who {
  color: var(--sp2);
}

/* overflow-wrap, because a transcript routinely carries an unbroken URL or a
   long foreign token that would otherwise force the grid column wider than its
   track and push the translation off-screen. */
.transcript .src,
.transcript .trg {
  min-width: 0;
  overflow-wrap: anywhere;
}

.transcript .src {
  font-size: 15.5px;
}

.transcript .trg {
  font-size: 15px;
  color: #2a2a26;
}

@media (max-width: 760px) {
  .transcript .head {
    display: none;
  }

  .transcript .row {
    grid-template-columns: 1fr;
    gap: 6px;
    padding: 14px 12px 14px 0;
  }

  .transcript .rail {
    text-align: right;
  }

  .transcript .who {
    display: inline;
    margin-inline-start: 8px;
  }

  .transcript .trg {
    border-top: 1px dashed var(--rule);
    padding-top: 8px;
  }
}

@media print {
  .transcript .row:hover {
    background: none;
  }

  .transcript .head {
    position: static;
  }
}

code,
pre {
  background: var(--code-bg);
  border-radius: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85rem;
  direction: ltr;
  unicode-bidi: embed;
}

code {
  padding: 0.1rem 0.35rem;
}

pre {
  padding: 0.9rem 1rem;
  overflow-x: auto;
  border: 1px solid var(--border);
  text-align: left;
}

pre code {
  padding: 0;
  background: none;
}

.docs section {
  margin-bottom: 2rem;
}

.docs ol,
.docs ul {
  padding-inline-start: 1.4rem;
}

.docs li {
  margin-bottom: 0.4rem;
}

/* Global failure notice, filled by app.js when an htmx request returns non-2xx.
   Fixed to the top so it is seen regardless of scroll position -- these are
   reports of an action that did NOT happen, which previously showed nothing. */
.global-error {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 1000;
  margin: 0;
  padding: 0.75rem 1rem;
  background: #b3261e;
  color: #fff;
  font-weight: 600;
  text-align: center;
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.3);
}

.global-error[hidden] {
  display: none;
}

/* Sync control plus the "last synced" stamp. The Airtable list is a local
   mirror refreshed on demand, so its age is stated rather than left implicit —
   call statuses beside it come from our own database and are always current. */
.sync-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin: 0.5rem 0 1rem;
}

.sync-bar form {
  margin: 0;
}

/* One control strip, like .sync-bar. `label` is display:block globally, which
   otherwise stacked the file picker, the title field, the button and the
   progress bar as four full-width rows above the table. */
.upload-form {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: 0.75rem;
  margin: 0.5rem 0 1rem;
}

/* A column, not the global block label: a file input is inline, so its caption
   sat beside it while the title field (width:100%) pushed its own caption onto
   the line above. The two captions have to line up. */
.upload-form label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin: 0;
}

.upload-form progress {
  width: 12rem;
}

.muted {
  color: var(--muted);
  font-size: 0.85rem;
}

/* Marks a row whose Airtable record has been deleted. Kept visible on purpose:
   we may still hold its transcript. */
.badge {
  display: inline-block;
  margin-inline-start: 0.5rem;
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font-size: 0.72rem;
  font-weight: 500;
  white-space: nowrap;
  vertical-align: middle;
}

/* Status as a coloured pill instead of plain text. Every state used to render
   as identical black text, so the only visual difference between a transcribed
   row and an untranscribed one was the zebra striping — which carries no
   meaning. Colour is never the sole signal: the label still names the state, so
   this holds up for colour-blind users and in monochrome. */
.status {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
}

.status-done {
  background: #dcfce7;
  border-color: #86efac;
  color: var(--success);
}

.status-error {
  background: #fee2e2;
  border-color: #fca5a5;
  color: var(--danger);
}

.status-processing,
.status-pending {
  background: #fef3c7;
  border-color: #fcd34d;
  color: var(--warn);
}

/* Never transcribed: deliberately the quietest, so completed work stands out
   when scanning a long list. */
.status-none {
  background: var(--surface);
  border-color: var(--border);
  color: var(--muted);
  font-weight: 500;
}

/* Whole-row navigation, done with a real link rather than a click handler on
   the <tr>. The visible title stays in normal flow and an invisible ::after
   overlay stretches it across the row, so the row is clickable while the link
   keeps everything a link gives you for free: tab focus, Enter, middle-click,
   open-in-new-tab, "copy link address", and being announced as a link.
   A JS row-click would have thrown all of that away. */
tr.row-clickable {
  position: relative;
  cursor: pointer;
}

tr.row-clickable:hover td {
  background: #eef2ff;
}

a.row-link {
  color: var(--fg);
  text-decoration: none;
  font-weight: 500;
}

a.row-link::after {
  content: "";
  position: absolute;
  inset: 0;
}

tr.row-clickable:hover a.row-link {
  color: var(--accent);
  text-decoration: underline;
}

/* Keyboard users need to see where they are; the overlay makes an outline on
   the text itself the clearest indicator. */
a.row-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* Anything interactive in a clickable row must sit above the overlay, or the
   row link swallows it — the bulk-delete checkbox and the action buttons. */
tr.row-clickable .above-row-link,
tr.row-clickable td.above-row-link {
  position: relative;
  z-index: 1;
}

/* Failure explanation on the call page: what to do first, the raw text after.
   The stored string ("Drive download failed: 403") is meaningless on its own to
   whoever has to fix it, but it is what makes an unfamiliar failure
   diagnosable, so it is kept rather than replaced. */
.error-notice {
  border: 1px solid #fca5a5;
  border-inline-start: 4px solid var(--danger);
  border-radius: 8px;
  background: #fef2f2;
  padding: 0.75rem 1rem;
  margin: 0.75rem 0;
}

.error-notice p {
  margin: 0.2rem 0;
}

.error-raw {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.78rem;
  direction: ltr;
  unicode-bidi: embed;
  text-align: start;
}
