/* buitendyk.ca -- shared styles. Plain CSS, no build step, no third-party
   fonts/CDNs (keeps the site fast, free, and dependency-free).

   Cache-busting: pages link this as /assets/style.css?v=N, and dubber/index.html
   loads dubber/dubber.js?v=N the same way. Bump the relevant N in every tag
   that references the file you're changing whenever you change it -- nginx
   sends no cache-control headers, so browsers fall back to aggressive
   heuristic caching that even hard-refreshes can fail to bypass otherwise. */

:root {
  --bg: #0f1320;
  --bg-elevated: #171c2e;
  --border: #2a3150;
  --text: #e7eaf6;
  --text-dim: #aab1cc;
  --accent: #5eb1ff;
  --accent-strong: #8fd0ff;
  --good: #57d18b;
  --warn: #f0b94d;
  --bad: #f06868;
  --radius: 14px;
  --max-width: 980px;
  font-size: 16px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
}

body {
  background: radial-gradient(circle at top, #19213a 0%, var(--bg) 55%);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  line-height: 1.55;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

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

header.site-header {
  border-bottom: 1px solid var(--border);
  padding: 1.25rem 1.5rem;
}

header.site-header .inner {
  max-width: var(--max-width);
  margin: 0 auto;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.brand {
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text);
}
.brand span { color: var(--accent); }

.brand:hover { text-decoration: none; color: var(--text); }

nav.breadcrumb {
  font-size: 0.95rem;
  color: var(--text-dim);
}

main {
  flex: 1;
  width: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 2.5rem 1.5rem 4rem;
}

.hero h1 {
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  margin: 0 0 0.5rem;
}

.hero p {
  color: var(--text-dim);
  /* Width tuned so the intro wraps to ~3 lines; text-wrap: balance then evens
     their lengths (degrades to ordinary ragged wrapping on older browsers). */
  max-width: 92ch;
  margin: 0;
  text-wrap: balance;
}

.section-title {
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-dim);
  margin: 2.75rem 0 1rem;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 1rem;
}

.card {
  display: block;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.25rem;
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.card:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
  text-decoration: none;
}

.card h3 {
  margin: 0 0 0.35rem;
  font-size: 1.05rem;
  color: var(--text);
}

.card p {
  margin: 0;
  font-size: 0.92rem;
  color: var(--text-dim);
}

.card .url {
  display: block;
  margin-top: 0.6rem;
  font-size: 0.8rem;
  color: var(--accent);
  word-break: break-all;
}

.pill {
  display: inline-block;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-dim);
  margin-bottom: 0.5rem;
}
.pill.external { color: var(--accent); border-color: var(--accent); }
.pill.internal { color: var(--good); border-color: var(--good); }

footer.site-footer {
  border-top: 1px solid var(--border);
  padding: 1.5rem;
  text-align: center;
  color: var(--text-dim);
  font-size: 0.85rem;
}

/* --- Forms / panels (used by the dubber sub-page) --- */

.panel {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.5rem;
  margin-top: 1.5rem;
}

.field { margin-bottom: 1.1rem; }

.field label {
  display: block;
  font-size: 0.85rem;
  color: var(--text-dim);
  margin-bottom: 0.4rem;
}

.field input[type="text"],
.field input[type="url"],
.field select {
  width: 100%;
  padding: 0.65rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
}

.field input:focus, .field select:focus {
  outline: none;
  border-color: var(--accent);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 1.3rem;
  border-radius: 8px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #08111f;
  font-weight: 600;
  font-size: 0.95rem;
  cursor: pointer;
  transition: opacity 0.15s ease, transform 0.05s ease;
}
.btn:hover { opacity: 0.9; text-decoration: none; }
.btn:active { transform: scale(0.98); }

.btn.secondary {
  background: transparent;
  color: var(--accent);
}

/* Danger-toned outline button (e.g. "Restart Dubber service") -- a real,
   disruptive action, so it reads as a warning rather than a primary CTA. */
.btn.danger {
  background: transparent;
  color: var(--bad);
  border-color: var(--bad);
}
.btn.danger:hover { background: rgba(240, 104, 104, 0.1); }

/* Compact button variant for low-key/secondary spots. */
.btn.small { padding: 0.45rem 0.9rem; font-size: 0.85rem; }

/* Operator service controls panel -- description on the left, the (discreet)
   restart action pushed to the right. Stacks on narrow screens. */
.service-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.service-info { min-width: 0; }
.service-title {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-dim);
}
/* Header for each top-level control box (dub / re-thumbnail / service). */
.box-title {
  font-size: 1rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--accent);
  margin-bottom: 1rem;
}
.service-action {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  flex-shrink: 0;
}

/* "Ghosted" = visible but inert until the visitor authenticates */
.btn[disabled],
.btn.ghosted {
  opacity: 0.4;
  cursor: not-allowed;
  filter: grayscale(0.6);
  pointer-events: none;
}

.status-line {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9rem;
  color: var(--text-dim);
}

.dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 999px;
  background: var(--text-dim);
  display: inline-block;
}
.dot.locked { background: var(--warn); }
.dot.unlocked { background: var(--good); }
.dot.error { background: var(--bad); }

.job-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem;
  margin-top: 1rem;
  font-size: 0.9rem;
}
.job-card .job-id { color: var(--text-dim); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.job-card .job-stage { color: var(--accent); font-weight: 600; }
.job-card .job-error { color: var(--bad); white-space: pre-wrap; }
.job-card a.job-link { font-weight: 600; }

/* Job headline: id + stage on the left, percent + cancel on the right. */
.job-headline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.job-headline-right {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  flex-shrink: 0;
}
.job-pct {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--text-dim);
}
.job-detail { margin-top: 0.5rem; }
.job-meta {
  margin-top: 0.35rem;
  font-size: 0.78rem;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* Progress bar. */
.job-progress { margin-top: 0.6rem; }
.job-progress-track {
  height: 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border);
  overflow: hidden;
}
.job-progress-fill {
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: var(--accent);
  transition: width 0.6s ease;
}
/* Running: animated barber-pole stripes so the bar reads as alive even when the
   percent is momentarily static (e.g. a long ffmpeg mux between polls). */
.job-progress.running .job-progress-fill {
  background-image: linear-gradient(45deg,
    rgba(255, 255, 255, 0.22) 25%, transparent 25%,
    transparent 50%, rgba(255, 255, 255, 0.22) 50%,
    rgba(255, 255, 255, 0.22) 75%, transparent 75%, transparent);
  background-size: 1rem 1rem;
  animation: job-progress-stripes 1s linear infinite;
}
@keyframes job-progress-stripes {
  from { background-position: 0 0; }
  to   { background-position: 1rem 0; }
}
.job-progress.done .job-progress-fill { background: var(--good); }
.job-progress.failed .job-progress-fill { background: var(--bad); }
.job-progress.cancelled .job-progress-fill { background: var(--text-dim); }
/* Queued: a faint sheen sweeping the empty track signals "waiting in line". */
.job-progress.queued .job-progress-track {
  background-image: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.07), transparent);
  background-size: 40% 100%;
  background-repeat: no-repeat;
  animation: job-progress-wait 1.6s ease-in-out infinite;
}
@keyframes job-progress-wait {
  from { background-position: -40% 0; }
  to   { background-position: 140% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .job-progress.running .job-progress-fill,
  .job-progress.queued .job-progress-track { animation: none; }
}

/* Cancel button beside a still-queued job: compact, warning-toned. */
.job-card .btn.job-cancel {
  padding: 0.3rem 0.8rem;
  font-size: 0.8rem;
  border-color: var(--warn);
  color: var(--warn);
  background: transparent;
}
.job-card .btn.job-cancel:hover { background: rgba(240, 185, 77, 0.1); }

.transcript-preview { margin-top: 0.75rem; }

.transcript-table {
  width: 100%;
  table-layout: fixed;
  border-collapse: collapse;
  margin-top: 0.5rem;
  font-size: 0.85rem;
}
.transcript-table th,
.transcript-table td {
  text-align: left;
  vertical-align: top;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--border);
}
.transcript-table th {
  color: var(--text-dim);
  font-weight: 600;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.transcript-table .transcript-time {
  color: var(--text-dim);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: nowrap;
}

/* Column widths. NOTE: under `table-layout: fixed`, Chromium silently IGNORES
   any <col> width that contains a percentage inside calc() -- e.g.
   `calc((100% - 4rem) * 0.4545)` collapses to an equal split (measured: a dead
   1.000 ratio). That no-op is exactly why the Spanish column kept coming out
   the same width as English for so long. Plain percentages ARE honored, so use
   those: a slim time column, then Original ~41% / Spanish ~52%. That ~1.27
   column ratio leaves the Spanish *textarea's* usable text width ~20% wider
   than the English block even after the textarea's own padding+border
   (~21px) is subtracted -- verified by rendering this stylesheet in headless
   Chromium and measuring the actual line-box widths. */
.transcript-table col.col-time { width: 4rem; }
.transcript-table.has-original col.col-original { width: 44%; }
.transcript-table.has-original col.col-translated { width: 49%; }
.transcript-table:not(.has-original) col.col-translated { width: calc(100% - 4rem); }

.transcript-table .transcript-original,
.transcript-table .transcript-translated {
  overflow-wrap: anywhere;
}

.transcript-edit {
  display: flex;
  flex-direction: column;
  align-items: flex-end; /* Save button hugs the right edge, beneath the box */
  gap: 0.4rem;
}

.transcript-edit textarea {
  width: 100%; /* full column width -- nothing shares this row anymore */
  min-height: 3.2em;
  /* Height is set in JS to fit the line's own (often-longer) translation,
     with the Original cell's height as a floor -- so no inner scrollbar and
     no manual resize handle needed. */
  overflow: hidden;
  padding: 0.45rem 0.6rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 0.85rem;
  line-height: 1.4;
}
.transcript-edit textarea:focus {
  outline: none;
  border-color: var(--accent);
}

.transcript-edit .btn.transcript-save {
  flex-shrink: 0;
  padding: 0.35rem 0.85rem;
  font-size: 0.78rem;
}

.transcript-translated.dirty textarea {
  border-color: var(--warn);
  background: rgba(240, 185, 77, 0.07);
}
.transcript-translated.dirty .btn.transcript-save {
  border-color: var(--warn);
  color: var(--warn);
}

.transcript-translated.saved textarea {
  border-color: var(--good);
  background: rgba(87, 209, 139, 0.06);
}
.transcript-translated.saved .btn.transcript-save {
  border-color: var(--good);
  color: var(--good);
}

.hint {
  font-size: 0.82rem;
  color: var(--text-dim);
  margin-top: 0.5rem;
}

/* --- Thumbnail preview / approval (dubber sub-page) --- */
.thumbnail-panel .service-title { margin-bottom: 0.75rem; }

/* Original vs generated, side by side; stacks on narrow screens. */
.thumb-compare {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}
.thumb-figure {
  margin: 0;
  flex: 1 1 280px;
  min-width: 0;
}
.thumb-figure img.thumb-img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
}
.thumb-figure figcaption {
  margin-top: 0.4rem;
  font-size: 0.8rem;
  color: var(--text-dim);
  text-align: center;
}
/* The approved (generated) image gets a green frame so it's clear which one
   will be applied to the upload. */
.thumb-figure.approved img.thumb-img { border-color: var(--good); }
.thumb-figure.approved figcaption { color: var(--good); }

/* Per-region edit rows: original text -> editable Spanish. */
.thumb-regions {
  margin-top: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.thumb-region {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.thumb-region-original {
  flex: 0 1 35%;
  min-width: 0;
  color: var(--text-dim);
  font-size: 0.9rem;
  word-break: break-word;
}
.thumb-region-arrow { color: var(--text-dim); flex-shrink: 0; }
.thumb-region-input {
  flex: 1 1 40%;
  min-width: 0;
  padding: 0.5rem 0.65rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
}
.thumb-region-input:focus { outline: none; border-color: var(--accent); }
/* Per-line font + colour overrides, sitting after the translation input. */
.thumb-region-style {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex: 0 0 auto;
}
.thumb-region-font {
  padding: 0.45rem 0.5rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.85rem;
  cursor: pointer;
}
.thumb-region-font:focus { outline: none; border-color: var(--accent); }
.thumb-region-color {
  width: 2.2rem;
  height: 2.2rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  cursor: pointer;
  flex-shrink: 0;
}
.thumb-region-color::-webkit-color-swatch-wrapper { padding: 3px; }
.thumb-region-color::-webkit-color-swatch { border: none; border-radius: 5px; }
.thumb-region-color::-moz-color-swatch { border: none; border-radius: 5px; }

.thumb-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 1rem;
}
.thumb-message { margin-top: 0; }
.thumb-message.good { color: var(--good); }
.thumb-message.bad { color: var(--bad); }

/* --- Transcripts library + redub ------------------------------------------ */
.hint.good { color: var(--good); }
.hint.bad { color: var(--bad); }
#redub-transcript {
  width: 100%;
  padding: 0.5rem 0.65rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
}
.library-item {
  border: 1px solid var(--border);
  border-radius: 10px;
  margin-bottom: 0.5rem;
  overflow: hidden;
}
.library-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.8rem;
}
.library-name { flex: 1 1 auto; min-width: 0; word-break: break-word; }
.library-name a { color: var(--text); text-decoration: none; }
.library-name a:hover { color: var(--accent); text-decoration: underline; }
.library-meta { color: var(--text-dim); font-size: 0.85rem; }
.library-body {
  padding: 0.6rem 0.8rem 0.8rem;
  border-top: 1px solid var(--border);
}
.library-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 0.75rem;
  flex-wrap: wrap;
}
/* The library renders the SAME .transcript-table as the transcript preview
   (see dubber.js buildTranscriptTable); only this scroll wrapper is its own,
   so a 100+ line transcript scrolls inside the box instead of stretching the
   page. The right padding keeps the Save buttons clear of the scrollbar. */
.library-transcript-scroll {
  max-height: 60vh;
  overflow-y: auto;
  padding-right: 0.4rem;
}

/* --- Dubber: library state chips, activity log, lookup banner, radios ---- */

/* One entry per (video, language); the chip shows its lifecycle state:
   draft -> published -> published-with-pending-edits (redub in progress). */
.chip {
  display: inline-block;
  padding: 0.1rem 0.55rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  vertical-align: middle;
  margin-right: 0.35rem;
  border: 1px solid var(--border);
  color: var(--text-dim);
  white-space: nowrap;
}
.chip.draft { color: var(--accent); border-color: var(--accent); }
.chip.published { color: var(--good); border-color: var(--good); }
.chip.pending { color: var(--warn); border-color: var(--warn); }

.library-links { font-size: 0.85rem; margin-top: 0.2rem; }
.library-links a { color: var(--text-dim); text-decoration: underline; }
.library-links a:hover { color: var(--accent); }
.library-head-buttons {
  display: flex;
  gap: 0.5rem;
  flex-shrink: 0;
  align-items: center;
}

/* Privacy / thumbnail-source radio rows. */
.radio-row { display: flex; gap: 1.25rem; flex-wrap: wrap; align-items: center; }
.radio-row label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.92rem;
  color: var(--text);
  cursor: pointer;
  margin: 0;
}
.radio-row input[type="radio"] { accent-color: var(--accent); margin: 0; }
.radio-row .radio-hint { color: var(--text-dim); font-size: 0.82rem; }
.radio-row.inline { gap: 0.9rem; }

/* The URL re-entry banner: this video is already in the library. */
.lookup-banner {
  margin-top: 0.5rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
}
.lookup-banner a { color: var(--accent); }

/* The permanent activity log: one timestamped line per action. */
#event-list {
  max-height: 50vh;
  overflow-y: auto;
  font-size: 0.88rem;
}
.event-line {
  padding: 0.3rem 0;
  border-bottom: 1px solid var(--border);
  word-break: break-word;
}
.event-line:last-child { border-bottom: none; }
.event-time {
  color: var(--text-dim);
  font-size: 0.8rem;
  margin-right: 0.6rem;
  white-space: nowrap;
}
.event-action { font-weight: 600; }
.event-title { color: var(--text-dim); }
.event-detail { color: var(--text-dim); font-size: 0.82rem; }
.event-line a { color: var(--accent); word-break: break-all; }

/* Card 'X' close control (thumbnail + transcript preview cards). Hides the
   card without discarding what's saved; the card re-opens when its source URL
   is entered again. */
.card-close {
  position: absolute;
  top: 0.55rem;
  right: 0.7rem;
  background: transparent;
  border: none;
  color: var(--text-dim);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.35rem;
  border-radius: 6px;
}
.card-close:hover { color: var(--text); background: var(--bg-elevated); }

/* Extraction-diagnostic output: monospaced, scrollable, copy-friendly. */
.diag-output {
  margin-top: 0.9rem;
  max-height: 60vh;
  overflow: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.75rem 0.9rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.78rem;
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-dim);
}

/* Thumbnail editor "auto-preserve original graphics" toggle. */
.thumb-overlay-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.9rem;
  font-size: 0.92rem;
  color: var(--text);
  cursor: pointer;
}
.thumb-overlay-toggle input[type="checkbox"] { accent-color: var(--accent); width: 1rem; height: 1rem; }

/* --- Collapsible sections (header-only by default; click to expand) ------- */
.collapsible .section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  width: 100%;
  background: transparent;
  border: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  color: var(--text);
  text-align: left;
  font: inherit;
}
.collapsible .section-header .box-title { margin: 0; }
.collapsible .section-header:hover .box-title { color: var(--accent); }
.collapsible .chevron {
  color: var(--text-dim);
  font-size: 0.95rem;
  line-height: 1;
  transition: transform 0.15s ease;
  flex-shrink: 0;
}
.collapsible:not(.collapsed) .chevron { transform: rotate(90deg); color: var(--accent); }
.collapsible .section-body { margin-top: 0.9rem; }
.collapsible.collapsed .section-body { display: none; }
/* Sub-cards nested inside a section body get a little separation. */
.collapsible .section-body > .panel { margin-top: 1rem; }
