/* ==========================================================================
   Previgencia — Submission Readiness Checker — design pass
   --------------------------------------------------------------------------
   Constraints (design-brief.md + 10-architecture.md Design pillar):
   - calm, dense, desktop-first; professional register; never celebratory;
   - status is NEVER color alone — every status pairs icon (glyph) + text;
   - spatial continuity via the CSS View Transitions API only (no JS runtime):
     cross-document `@view-transition { navigation: auto }` + per-object
     `view-transition-name`s set in the markup (case-{id}, report-{id},
     doc-{id}) and in CSS for the static shell;
   - major transforms <= 360ms, micro <= 150ms, spatial (decelerating) easing;
   - `prefers-reduced-motion`: <= 120ms fades / cuts, identical layout;
   - light theme ONLY (`color-scheme: light`) — no dark palette by decision.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Design tokens
   -------------------------------------------------------------------------- */
:root {
  /* Light theme ONLY (operator decision 2026-07-02): `light` pins built-in
     form controls/scrollbars to light rendering even under a dark OS theme. */
  color-scheme: light;

  --fg: #16181d;
  --bg: #f6f7f9;
  --surface: #ffffff;
  --muted: #5b616e;
  --line: #e2e5ea;
  --accent: #2f6feb;
  --accent-strong: #245cd0;
  /* Text color ON accent-filled surfaces (white passes AA on both accents). */
  --accent-contrast: #ffffff;
  --shadow: 0 1px 2px rgb(22 24 29 / 0.05), 0 4px 14px rgb(22 24 29 / 0.05);

  /* Status palette (design-brief §6). Each status gets fg/bg/line so chips,
     rails, badges and banners stay consistent. */
  --ok-fg: #116329;
  --ok-bg: #e7f4ec;
  --ok-line: #a8d8b5;

  --warn-fg: #8a5a00;
  --warn-bg: #fdf3d7;
  --warn-line: #e6ce8a;

  --bad-fg: #b42318;
  --bad-bg: #fdebe9;
  --bad-line: #f0b6b0;

  --review-fg: #57606a;
  --review-bg: #f0f2f5;
  --review-line: #c9cfd8;

  /* Motion tokens (design-brief §9). Major <= 360ms; micro <= 150ms. */
  --dur-page: 200ms;
  --dur-major: 300ms;
  --dur-micro: 120ms;
  /* Emphasized deceleration — objects arrive with weight and settle. */
  --ease-spatial: cubic-bezier(0.05, 0.7, 0.1, 1);
}

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  /* Dense, desktop-first: 15px base — the gestor scans, not reads. */
  font: 15px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--fg);
  background: var(--bg);
}
h1 { font-size: 1.45rem; margin: 0 0 0.5rem; letter-spacing: -0.01em; }
h2 {
  font-size: 0.8125rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin: 0 0 0.75rem;
}
a { color: var(--accent); }
a:hover { color: var(--accent-strong); }
table { font-variant-numeric: tabular-nums; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --------------------------------------------------------------------------
   3. Shell: header / main / footer
   -------------------------------------------------------------------------- */
header.site-header, main, footer.site-footer {
  max-width: 72rem;
  margin-inline: auto;
  padding: 0.7rem 1.25rem;
}
header.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  border-bottom: 1px solid var(--line);
  /* The shell is pinned: it gets its own (visually static) transition group,
     so page crossfades never smear the chrome — one continuous space. */
  view-transition-name: site-header;
}
header.site-header a.brand {
  color: inherit;
  text-decoration: none;
  font-weight: 650;
  letter-spacing: -0.01em;
}
header.site-header nav {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  font-weight: 400;
  font-size: 0.875rem;
  min-width: 0;
}
/* Long firm · email identities ellipsize to one calm line at narrow widths
   instead of wrapping the separator onto its own line. */
.nav-identity {
  color: var(--muted);
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nav-logout { display: inline; }
.nav-logout button {
  border: none;
  background: none;
  padding: 0.2rem 0.4rem;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
  white-space: nowrap;
}
.nav-logout button:hover { color: var(--fg); }

main { padding-block: 1.75rem 2.5rem; }

/* The persistent assistive disclaimer: quiet but legible — small, muted,
   set off by a rule, never hidden or decorative (spec: assistive, never
   authoritative). */
footer.site-footer {
  border-top: 1px solid var(--line);
  view-transition-name: site-footer;
}
footer.site-footer .disclaimer {
  margin: 0;
  color: var(--muted);
  font-size: 0.8125rem;
  line-height: 1.5;
  max-width: 60rem;
}
footer.site-footer .footer-links {
  margin: 0.5rem 0 0;
  font-size: 0.8125rem;
}
footer.site-footer .footer-links a {
  color: var(--muted);
}

/* Legal / data-protection notice page — calm, readable measure. */
.legal-page {
  max-width: 46rem;
}
.legal-page h2 {
  margin-top: 1.75rem;
}
.legal-page .controller-notice {
  border: 1px dashed var(--line);
  border-radius: 6px;
  padding: 0.75rem 1rem;
  margin-top: 1.5rem;
}
.legal-page .placeholder-note {
  color: var(--muted);
  font-style: italic;
  margin: 0;
}

/* --------------------------------------------------------------------------
   4. Forms — calm and dense
   -------------------------------------------------------------------------- */
input, select, button { font: inherit; }
input, select {
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
  color: var(--fg);
}
/* Full opacity: at 0.7 the muted grey fell below 4.5:1 on the white field. */
input::placeholder { color: var(--muted); opacity: 1; }

button {
  padding: 0.45rem 0.95rem;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
  transition: border-color var(--dur-micro) ease, color var(--dur-micro) ease,
    background-color var(--dur-micro) ease;
}
button:hover { border-color: var(--accent); color: var(--accent); }
button:active { transform: translateY(1px); }
/* In-flight submits (app.js disables the form's controls): visibly inert. */
button:disabled { opacity: 0.55; cursor: progress; }
button.danger:hover {
  border-color: var(--bad-line);
  color: var(--bad-fg);
  background: var(--bad-bg);
}

/* Danger zone (workspace): deleting a report-less expediente. Two-step
   native <details> disclosure — the destructive button is never one click
   away, and no JS confirm dialog is involved. */
.danger-zone {
  margin-top: 2.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--line);
  font-size: 0.875rem;
}
.danger-zone summary {
  color: var(--muted);
  cursor: pointer;
  width: fit-content;
}
.danger-zone summary:hover { color: var(--bad-fg); }
.danger-zone p { color: var(--muted); max-width: 46rem; }
.danger-zone button.danger {
  border-color: var(--bad-line);
  color: var(--bad-fg);
}

/* Primary actions: filled, but quiet — one weight above the rest, no more.
   Text uses --accent-contrast so the pairing passes AA in BOTH palettes. */
.auth-card button[type="submit"],
.case-details form button[type="submit"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}
.auth-card button[type="submit"]:hover,
.case-details form button[type="submit"]:hover {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
  color: var(--accent-contrast);
}

/* Dense inline form grid (the datos section, both in "nuevo" state and in
   the workspace): labels wrap their inputs; fields flow into columns. */
.case-details form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0.8rem 1rem;
  align-items: end;
}
.case-details form label,
.upload-form label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.8125rem;
  font-weight: 550;
  color: var(--muted);
}
.case-details form button {
  justify-self: start;
  align-self: end;
}

.upload-form {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  align-items: flex-start;
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--line);
}
/* With app.js active the file picker auto-submits — the explicit button is
   the no-JS fallback only (html.js is set by the script). */
html.js .upload-form > button[type="submit"] { display: none; }

/* Datos del solicitante — the modelo-de-solicitud sections as native
   <details> groups inside one form. Same field grid as .case-details. */
.solicitante-form details {
  border: 1px solid var(--line);
  border-radius: 8px;
  margin-block: 0.6rem;
  background: var(--bg);
}
.solicitante-form summary {
  padding: 0.55rem 0.9rem;
  cursor: pointer;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--fg);
}
/* accent-strong: plain --accent on the --bg summary strip is 4.26:1 (< AA). */
.solicitante-form summary:hover { color: var(--accent-strong); }
/* Section titles are headings (navigable by AT) that render as summary text. */
.solicitante-form summary h3 { display: inline; font: inherit; margin: 0; }
.solicitante-form .summary-missing {
  font-weight: 450;
  font-size: 0.8125rem;
  color: var(--muted);
}
.solicitante-form .fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 0.8rem 1rem;
  align-items: end;
  padding: 0.4rem 0.9rem 1rem;
}
.solicitante-form .fields-stack { grid-template-columns: 1fr; align-items: start; }
.solicitante-form .fields label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.8125rem;
  font-weight: 550;
  color: var(--muted);
}
.solicitante-form .fields label.check {
  flex-direction: row;
  align-items: center;
  gap: 0.5rem;
  font-weight: 450;
  color: var(--fg);
}
.solicitante-form .fields label.check input[type="checkbox"] { width: auto; }
.solicitante-form .fields .span-all { grid-column: 1 / -1; }
.solicitante-form textarea {
  font: inherit;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
  color: var(--fg);
  resize: vertical;
}
.solicitante-form > button[type="submit"] {
  margin-top: 0.4rem;
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-contrast);
}
.solicitante-form > button[type="submit"]:hover {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
}
.solicitante-complete { color: var(--ok-fg); font-size: 0.875rem; }
.solicitante-missing {
  color: var(--muted);
  font-size: 0.875rem;
  padding: 0.5rem 0.8rem;
  background: var(--review-bg);
  border: 1px solid var(--review-line);
  border-radius: 8px;
}
.solicitante-missing summary { cursor: pointer; width: fit-content; }
.solicitante-missing summary:hover { color: var(--fg); }
.solicitante-missing-list {
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: 0.15rem 1.5rem;
}
/* Caption line: label text + its required mark on one row (the label itself
   is a flex column — caption over control). */
.solicitante-form .field-label {
  display: inline-flex;
  align-items: baseline;
  gap: 0.1rem;
}
/* The required-field mark: color is decorative — the mark plus the
   visually-hidden "(obligatorio)" carry the meaning. Quiet, not alarmist. */
.solicitante-form .req { color: var(--bad-fg); font-weight: 600; }

/* Visually-hidden utility (screen readers only). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

/* Documentación requerida — the intake checklist: what to request from the
   client, what is already covered. Never color alone (mark + word). */
.checklist {
  margin-bottom: 1rem;
  padding: 0.7rem 0.9rem;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
}
.checklist-title { margin: 0 0 0.4rem; font-weight: 600; font-size: 0.875rem; }
.checklist-summary { font-weight: 450; color: var(--muted); }
.checklist-summary.checklist-ok { color: var(--ok-fg); }
.checklist-items {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* min(…, 100%): the 19rem track floor must never exceed a phone's content
     box, or the state words overflow into the card's padding. */
  grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
  gap: 0.25rem 1.5rem;
  font-size: 0.875rem;
}
.checklist-items li { display: flex; align-items: baseline; gap: 0.45rem; }
.checklist-mark { font-weight: 700; }
.checklist-items li.done .checklist-mark,
.checklist-items li.done .checklist-state { color: var(--ok-fg); }
.checklist-items li.missing .checklist-mark { color: var(--bad-fg); }
.checklist-items li.missing .checklist-state { color: var(--bad-fg); font-weight: 600; }
.checklist-items li.missing .checklist-doc { font-weight: 550; }
.checklist-state { font-size: 0.8125rem; }
.checklist-reason { color: var(--muted); font-size: 0.8125rem; }

/* Dropzone: the multiple-file input COVERS the zone, so click-to-pick and
   drag-and-drop are native browser behavior (no JS needed for the drop
   itself; app.js only auto-submits and paints .drag-over). `label.dropzone`
   (not scoped to .upload-form) also covers the inert "nuevo" state, and
   sits after the generic form-label rules so it wins at equal specificity. */
label.dropzone {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  align-self: stretch;
  padding: 2rem 1rem;
  border: 2px dashed var(--line);
  border-radius: 10px;
  background: var(--bg);
  text-align: center;
  cursor: pointer;
  font-size: 0.9375rem;
  font-weight: 450;
  color: var(--fg);
  transition: border-color var(--dur-micro) ease, background-color var(--dur-micro) ease;
}
.dropzone input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
.dropzone:hover,
.dropzone.drag-over {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 5%, var(--bg));
}
.dropzone.drag-over { border-style: solid; }
/* The covering file input is opacity:0, so its own focus outline is
   invisible — paint the ring on the zone instead. */
.dropzone:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* In-flight upload (app.js disables the input mid-POST): same quiet inert
   treatment as the "nuevo" state — visible feedback at the drop point. */
.dropzone:has(input[type="file"]:disabled),
.dropzone:has(input[type="file"]:disabled):hover {
  opacity: 0.6;
  cursor: progress;
  border-color: var(--line);
  background: var(--bg);
}

/* "Nuevo" state: the zone exists (identical layout to the live workspace)
   but is inert until the expediente is created. */
.dropzone.dropzone-disabled,
.dropzone.dropzone-disabled:hover {
  opacity: 0.6;
  cursor: not-allowed;
  border-color: var(--line);
  background: var(--bg);
}
.dropzone-disabled input[type="file"] { cursor: not-allowed; }
.dropzone-hint {
  color: var(--muted);
  font-size: 0.8125rem;
  font-weight: 400;
}

.hint, .field-hint {
  color: var(--muted);
  font-size: 0.8125rem;
  margin: 0.5rem 0 0;
}

/* Onboarding / compliance-settings wizard (routes::onboarding). Reuses the form
   idiom: stacked labels, check rows, hint text. */
.onboarding { max-width: 46rem; }
.onboarding-intro { color: var(--muted); max-width: 46rem; }
.onboarding fieldset {
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 0.6rem 1rem 1.1rem;
  margin: 1.1rem 0 0;
}
.onboarding legend { font-weight: 650; padding: 0 0.4rem; }
.onboarding label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-top: 0.7rem;
  font-size: 0.8125rem;
  font-weight: 550;
  color: var(--muted);
}
.onboarding label.check {
  flex-direction: row;
  align-items: flex-start;
  gap: 0.5rem;
  font-weight: 450;
  color: var(--fg);
}
.onboarding label.check input[type="checkbox"] { width: auto; margin-top: 0.2rem; }
.onboarding input,
.onboarding textarea {
  font: inherit;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
  color: var(--fg);
  resize: vertical;
}
.onboarding > form > button[type="submit"] { margin-top: 1.2rem; }

/* Embedded legal document (the DPA the firm accepts) — shown verbatim. */
.legal-doc { max-width: 60rem; }
.legal-doc code { font-size: 0.75rem; word-break: break-all; }
.legal-text {
  white-space: pre-wrap;
  word-break: break-word;
  overflow-x: auto;
  font-size: 0.8125rem;
  line-height: 1.5;
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--surface);
  color: var(--fg);
}

/* Error banner (`?err=` flash) — an alert, styled as one, never a modal.
   ONE class app-wide (web::err_banner is the single markup source). */
.error {
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--bad-line);
  border-radius: 8px;
  background: var(--bad-bg);
  color: var(--bad-fg);
  font-size: 0.875rem;
}

/* 404 / 500 pages — rendered inside the shared layout (shell + disclaimer). */
.error-page { max-width: 32rem; }
.error-page p { color: var(--muted); }

.empty, .report-empty { color: var(--muted); font-style: italic; }

/* --------------------------------------------------------------------------
   5. Auth pages
   -------------------------------------------------------------------------- */
.auth-card {
  max-width: 24rem;
  margin: 3.5rem auto;
  padding: 1.75rem 1.75rem 1.5rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: var(--shadow);
}
.auth-card h1 { font-size: 1.2rem; }
.auth-card label {
  display: block;
  margin: 0.9rem 0 0.3rem;
  font-size: 0.8125rem;
  font-weight: 550;
  color: var(--muted);
}
.auth-card button[type="submit"] { width: 100%; margin-top: 1.25rem; }
.auth-card p { font-size: 0.875rem; color: var(--muted); }

/* --------------------------------------------------------------------------
   6. Dashboard — case cards
   -------------------------------------------------------------------------- */
/* Page header row: title left, primary action right. */
.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.6rem;
}
.page-head h1 { margin: 0; }

/* Link styled as the primary action (same weight as primary buttons). */
a.btn-primary {
  display: inline-block;
  padding: 0.45rem 0.95rem;
  border: 1px solid var(--accent);
  border-radius: 7px;
  background: var(--accent);
  color: var(--accent-contrast);
  text-decoration: none;
  white-space: nowrap;
}
a.btn-primary:hover {
  background: var(--accent-strong);
  border-color: var(--accent-strong);
}

/* Toolbar: search + estado filter + count — one GET form, no JS. */
.case-toolbar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 0.8rem;
}
/* The global `input, select { width: 100% }` would force one control per
   line here — the toolbar lays out on the flex basis instead. */
.case-toolbar input[type="search"] {
  width: auto;
  flex: 1 1 14rem;
  max-width: 22rem;
}
.case-toolbar select { width: auto; flex: 0 1 auto; }
.case-toolbar .clear-filters { font-size: 0.875rem; }
.case-count { margin-left: auto; color: var(--muted); font-size: 0.8125rem; }

/* Dense one-line rows under an aligned column header — values scan
   vertically at hundreds of expedientes. Shared col-* widths keep the
   header and every row on the same grid. */
.col-ref { flex: 1 1 13rem; min-width: 8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.col-origin { flex: 0 0 4.5rem; }
.col-date { flex: 0 0 7.5rem; }
.col-deadline { flex: 0 0 13.5rem; font-variant-numeric: tabular-nums; }
.col-deadline .deadline-rel { font-size: 0.78rem; }
.deadline-none { color: var(--muted); }
.deadline-past { color: var(--bad-fg); font-weight: 600; }
.deadline-soon { color: var(--warn-fg); font-weight: 600; }
.deadline-ok { color: var(--muted); }
.col-docs { flex: 0 0 3.5rem; text-align: right; }
/* Fixed width sized to the longest badge (LISTO PARA REVISIÓN PROFESIONAL):
   variable badge widths must not shift the other columns row to row. */
.col-status {
  flex: 0 0 16.5rem;
  display: flex;
  justify-content: flex-end;
}

.case-rows-head {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0 1rem 0.35rem;
  font-size: 0.71rem;
  font-weight: 650;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Phantom chevron: keeps the header on exactly the row grid. */
.case-rows-head::after { content: "›"; visibility: hidden; width: 0.75rem; }
.case-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
}
.case-row {
  position: relative; /* anchor for the stretched row link */
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--line);
  font-size: 0.875rem;
  transition: background-color var(--dur-micro) ease;
}
.case-row:last-child { border-bottom: none; }
.case-row:hover { background: color-mix(in srgb, var(--accent) 4%, var(--surface)); }
.case-ref { color: inherit; text-decoration: none; }
/* Stretched link: the reference anchor covers the whole row, so anywhere on
   the row is one click target (there are no other interactive elements). */
.case-ref::after { content: ""; position: absolute; inset: 0; }
.case-row:hover .case-ref strong { color: var(--accent); }
.case-row .chevron {
  flex: 0 0 auto;
  width: 0.75rem;
  text-align: center;
  color: var(--muted);
  transition: transform var(--dur-micro) ease, color var(--dur-micro) ease;
}
.case-row:hover .chevron { color: var(--accent); transform: translateX(2px); }

/* Narrow screens: reference on its own line, then the urgency + the FULL estado
   badge on a second line. The old rule pinned a fixed 16.5rem status column that
   overflowed the row and got clipped by overflow:hidden (BLOQUEADO → "BL…"); it
   also hid col-deadline, which is the column the list is sorted by. */
@media (max-width: 42rem) {
  .case-rows-head { display: none; }
  .case-row .col-origin, .case-row .col-date, .case-row .col-docs,
  .case-row .chevron { display: none; }
  /* Reflow the row: reference full-width, badge + deadline wrap beneath it and
     may wrap their own text rather than being cut off. */
  .case-row { flex-wrap: wrap; row-gap: 0.4rem; column-gap: 0.7rem; }
  .case-row .col-ref { flex: 1 1 100%; min-width: 0; white-space: normal; overflow: visible; }
  .case-row .col-deadline { flex: 0 1 auto; }
  .case-row .col-status { flex: 0 1 auto; justify-content: flex-start; }
  .case-row .badge { white-space: normal; }
}

.case-list .empty {
  padding: 2rem 1rem;
  text-align: center;
  color: var(--muted);
  background: var(--surface);
  border: 1px dashed var(--line);
  border-radius: 10px;
}

/* Pager */
.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 0.7rem;
  font-size: 0.875rem;
}
.pager-status { color: var(--muted); }

.workspace > section {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 1.1rem 1.25rem 1.25rem;
  margin-block: 1.1rem;
}
.dashboard .case-list { margin-block: 1.1rem; }

/* --------------------------------------------------------------------------
   7. Workspace — case header, documents, reports
   -------------------------------------------------------------------------- */
.case-header { margin-bottom: 0.5rem; }
.case-header .case-meta { font-size: 0.875rem; }

/* Mensaje para el cliente — the paste-into-WhatsApp request list. */
.client-msg {
  margin-block: 1rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  padding: 0.6rem 0.9rem;
}
.client-msg summary { cursor: pointer; font-weight: 600; font-size: 0.9rem; }
.client-msg pre {
  margin: 0.6rem 0;
  padding: 0.75rem 0.9rem;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-family: inherit;
  font-size: 0.875rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.client-msg .copy-btn { padding: 0.35rem 0.9rem; font-size: 0.8125rem; }

/* --------------------------------------------------------------------------
   8. Status system — badges and chips
   --------------------------------------------------------------------------
   NEVER color alone: chips carry glyph + text in the markup; badges get a
   shape glyph via ::before (shape + label, both color-independent). */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.1rem 0.55rem;
  border: 1px solid var(--status-line, var(--line));
  border-radius: 999px;
  background: var(--status-bg, transparent);
  color: var(--status-fg, var(--muted));
  font-size: 0.71rem;
  font-weight: 650;
  letter-spacing: 0.04em;
  white-space: nowrap;
}
.badge::before { font-size: 0.85em; }
.badge.status-blocked::before { content: "✕"; }
.badge.status-attention::before { content: "▲"; }
.badge.status-ready_for_review::before { content: "●"; }
.badge.status-none::before { content: "◌"; }
.badge.status-none { border-style: dashed; }

/* Per-status variable assignment — shared by badges, chips, rows, banners.
   OK green / EXPIRING_SOON amber / EXPIRED + INVALID red /
   COULD_NOT_VERIFY grey (needs review) / MISSING ghost. */
.status-ok,
.status-ready_for_review,
.overall-ready_for_review {
  --status-fg: var(--ok-fg);
  --status-bg: var(--ok-bg);
  --status-line: var(--ok-line);
}
.status-expiring-soon,
.status-attention,
.overall-attention {
  --status-fg: var(--warn-fg);
  --status-bg: var(--warn-bg);
  --status-line: var(--warn-line);
}
.status-expired,
.status-invalid-before-target,
.status-blocked,
.overall-blocked {
  --status-fg: var(--bad-fg);
  --status-bg: var(--bad-bg);
  --status-line: var(--bad-line);
}
.status-could-not-verify {
  --status-fg: var(--review-fg);
  --status-bg: var(--review-bg);
  --status-line: var(--review-line);
}
.status-missing,
.status-none {
  --status-fg: var(--review-fg);
  --status-bg: transparent;
  --status-line: var(--review-line);
}

/* Report-row chips (glyph + status text come from the markup). */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--status-line, var(--line));
  border-radius: 999px;
  background: var(--status-bg, transparent);
  color: var(--status-fg, var(--muted));
  font-size: 0.71rem;
  font-weight: 650;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
/* The COULD_NOT_VERIFY label already ends in "— REVISAR" (types.rs); no CSS
   suffix — content never lives in stylesheets (it escaped the Spanish pass). */
.chip.status-missing { border-style: dashed; }

/* --------------------------------------------------------------------------
   9. The informe — the hero artifact, inline in the workspace
   -------------------------------------------------------------------------- */
.report-header { margin-bottom: 1.1rem; }
/* An h3 since the section carries the uppercase h2 kicker; explicit weight
   and margin so the demotion changes nothing visually. */
.report-title {
  font-size: 1.05rem;
  font-weight: 600;
  margin: 0 0 0.5rem;
  text-transform: none;
  letter-spacing: 0;
  color: var(--fg);
}
.informe-pending ul { margin: 0.4rem 0; }

/* Background analysis in progress: quiet, factual — the page refreshes
   itself until the extraction lands (no JS, meta refresh). */
.analyzing-banner {
  margin: 0 0 0.8rem;
  padding: 0.5rem 0.8rem;
  background: var(--review-bg);
  border: 1px solid var(--review-line);
  border-radius: 8px;
  color: var(--review-fg);
  font-size: 0.875rem;
}
.report-target {
  margin: 0.15rem 0 0;
  color: var(--muted);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
}

/* Overall banner: measured, never celebratory — READY_FOR_REVIEW gets the
   same quiet treatment as every other state, just green. */
.overall-banner {
  margin-top: 0.9rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--status-line, var(--line));
  border-left-width: 4px;
  border-radius: 9px;
  background: var(--status-bg, var(--surface));
  font-size: 0.9375rem;
}
.overall-banner strong { color: var(--status-fg, var(--fg)); }

/* Siguiente paso: the one action to take right now — directly under the
   banner, one full sentence, no list to parse. */
.next-step {
  margin: 0.6rem 0 0;
  padding: 0.6rem 1rem;
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent, var(--line));
  border-radius: 9px;
  background: var(--surface);
  font-size: 0.9rem;
}

/* Quiet factual note about the origin country's rules (e.g. exención UE). */
.origin-note {
  margin: 0.6rem 0 0;
  color: var(--muted);
  font-size: 0.8125rem;
}

.report-section {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 1rem 1.1rem 0.6rem;
  margin-block: 1rem;
}
/* Inline in the workspace the informe's section headings are h3 (the page
   already has its h1/h2 hierarchy); style them like the section h2s. */
.report-section h3 {
  margin: 0 0 0.6rem;
  font-size: 0.8125rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.section-note {
  text-transform: none;
  letter-spacing: normal;
  font-weight: 450;
  font-size: 0.8125rem;
}

/* The self-auditing row grid: chip | document | rule → explanation → work.
   Every verdict is readable the instant the row exists — motion never gates
   information. */
/* One block per DOCUMENT (the unit of review): header with the worst-status
   chip + document label + filename, then every check that applies to it. */
.doc-block {
  border: 1px solid var(--line);
  border-left: 3px solid var(--status-line, var(--line));
  border-radius: 8px;
  margin-block: 0.75rem;
  background: var(--surface);
  overflow: hidden;
}
.doc-block.ghost { border-style: dashed; background: transparent; }
.doc-block-head {
  display: flex;
  /* wrap: at phone width the actions drop to a second header line instead
     of being clipped by .doc-block's overflow:hidden (view/delete must stay
     reachable at every viewport). Desktop fits on one line — no change. */
  flex-wrap: wrap;
  align-items: center;
  gap: 0.8rem;
  padding: 0.6rem 0.9rem;
  background: color-mix(in srgb, var(--status-bg, transparent) 45%, var(--surface));
  border-bottom: 1px solid var(--line);
}
.doc-block.ghost .doc-block-head { background: transparent; }
.doc-block-title { font-weight: 600; font-size: 0.9375rem; }
.doc-block.ghost .doc-block-title { color: var(--muted); font-style: italic; }
/* The file half of the merged block: view link, size · date, delete —
   pushed to the right edge of the header. */
.doc-block-actions {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem;
  min-width: 0;
}
.doc-block-file {
  color: var(--muted);
  font-size: 0.78rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 18rem;
}
.doc-block-meta {
  color: var(--muted);
  font-size: 0.78rem;
  white-space: nowrap;
}
.doc-block-actions form { margin: 0; }
.doc-block-actions button { padding: 0.25rem 0.6rem; font-size: 0.8125rem; }
/* Blocks without verdict rows yet (analyzing / no origen+fecha): one quiet
   line explaining why. */
.doc-note {
  margin: 0;
  padding: 0.6rem 0.9rem;
  color: var(--muted);
  font-size: 0.8125rem;
}
.doc-checks { list-style: none; margin: 0; padding: 0; }

.report-row {
  display: grid;
  grid-template-columns: 7.5rem 12.5rem 1fr;
  grid-template-areas:
    "check chip explanation"
    "check chip work";
  column-gap: 1rem;
  row-gap: 0.15rem;
  align-items: start;
  padding: 0.6rem 0.9rem;
  border-top: 1px solid var(--line);
}
.doc-checks .report-row:first-child { border-top: none; }
.report-row .check-label {
  grid-area: check;
  font-weight: 600;
  font-size: 0.8125rem;
  color: var(--fg);
  margin-top: 0.15rem;
}
.report-row .chip { grid-area: chip; justify-self: start; margin-top: 0.1rem; }
.report-row .explanation { grid-area: explanation; font-size: 0.875rem; }
.report-row .show-work { grid-area: work; }

/* MISSING = a ghost slot: the BLOCK carries the dashed treatment (see
   .doc-block.ghost); the row inside stays plain, just muted. */
.report-row.ghost { background: transparent; }
.report-row.ghost .check-label { color: var(--muted); font-style: italic; }

/* "Not required" rows are informational (⚪), not passes — visually neutral. */
.report-row.informational {
  --status-fg: var(--review-fg);
  --status-bg: transparent;
  --status-line: var(--line);
}
.report-row.informational .explanation { color: var(--muted); }
/* The chip itself: markup emits `chip informational` (NOT `status-ok`) on
   these rows, because a status class would re-declare the green tokens
   directly on the chip and beat the row-level neutral override. */
.chip.informational {
  --status-fg: var(--review-fg);
  --status-bg: transparent;
  --status-line: var(--line);
}

/* Show-your-work: native <details>, expand-in-place — the derivation lives
   inside the row object (design-brief §4), no detail page, no JS. */
.show-work { margin-top: 0.3rem; font-size: 0.8125rem; }
.show-work summary {
  cursor: pointer;
  color: var(--accent);
  width: fit-content;
  border-radius: 5px;
  padding: 0.2rem 0.3rem;
}
.show-work summary:hover { color: var(--accent-strong); }
.show-work table {
  margin-top: 0.4rem;
  border-collapse: collapse;
  min-width: 24rem;
}
.show-work th {
  text-align: left;
  font-weight: 550;
  color: var(--muted);
  padding: 0.25rem 1.2rem 0.25rem 0;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.show-work td {
  padding: 0.25rem 0;
  border-bottom: 1px solid var(--line);
  font-variant-numeric: tabular-nums;
}
.show-work tr:last-child th, .show-work tr:last-child td { border-bottom: none; }

.info-box {
  padding: 0.85rem 1.1rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--review-line);
  border-radius: 9px;
  background: var(--review-bg);
  color: var(--review-fg);
  font-size: 0.8125rem;
  margin-block: 1rem;
}
.info-box p { margin: 0 0 0.4rem; }
.info-box ul { margin: 0.2rem 0 0; padding-left: 1.2rem; }

.report-meta, .report-disclaimer {
  color: var(--muted);
  font-size: 0.8125rem;
}
.report-disclaimer {
  border-top: 1px solid var(--line);
  padding-top: 0.75rem;
}

/* --------------------------------------------------------------------------
   10. Motion — cross-document View Transitions (zero JS)
   --------------------------------------------------------------------------
   Named objects persist across navigations: dashboard card → workspace
   header (case-{id}); workspace report item → report region (report-{id});
   workspace document row → its validity verdict row (doc-{id}). The shell
   (header/footer) is pinned so only the content moves — one continuous
   space, and the verdict visibly derives from the document. */
@view-transition { navigation: auto; }

/* Page-level crossfade: brief and unobtrusive. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: var(--dur-page);
}

/* Named-object container transforms: spatial easing, always <= 360ms. */
::view-transition-group(*) {
  animation-duration: var(--dur-major);
  animation-timing-function: var(--ease-spatial);
}
::view-transition-old(*),
::view-transition-new(*) {
  animation-duration: var(--dur-page);
}

/* The object grammar, scoped by transition kind (set by app.js on <html>):

   MUTATIONS (html.vt-mutate — upload, deletion, informe settling): the page
   itself stays rock-still (default crossfade of identical pixels is
   invisible); the ONE object that changed follows the grammar — persisting
   objects move, an appearing object RISES IN, a departing one SINKS OUT.

   NAVIGATIONS (html.vt-navigate — links, redirects, back/forward): a
   FADE-THROUGH — the old page fully exits before the new enters. A plain
   crossfade would blend both pages for ~200ms (reads as flicker/ghosting),
   and per-element rise/sink here would float dozens of one-sided snapshots
   over the incoming page. Only genuinely PAIRED objects (case row ⇄ case
   header, estado chip ⇄ informe banner, auth panel…) morph across. */
html.vt-mutate::view-transition-new(*):only-child {
  animation: vt-insert var(--dur-major) var(--ease-spatial);
}
html.vt-mutate::view-transition-old(*):only-child {
  animation: vt-remove var(--dur-page) var(--ease-spatial);
}

html.vt-navigate::view-transition-old(root) {
  animation: vt-page-out 90ms ease both;
}
html.vt-navigate::view-transition-new(root) {
  animation: vt-page-in 150ms ease 70ms both;
}
/* One-sided objects travel WITH their page on navigation: old-side-only
   snapshots vanish with the old page (never float over the new one),
   new-side-only content arrives on the new page's schedule. */
html.vt-navigate::view-transition-old(*):only-child {
  animation: vt-page-out 90ms ease both;
}
html.vt-navigate::view-transition-new(*):only-child {
  animation: vt-page-in 150ms ease 70ms both;
}

@keyframes vt-insert {
  from { opacity: 0; transform: translateY(-0.5rem); }
}
@keyframes vt-remove {
  to { opacity: 0; transform: translateY(-0.5rem); }
}
@keyframes vt-page-out {
  to { opacity: 0; }
}
@keyframes vt-page-in {
  from { opacity: 0; }
}

/* Show-your-work expands in place where the engine supports animating
   <details> (progressive enhancement; elsewhere it opens instantly —
   information is never gated behind motion). */
@supports (interpolate-size: allow-keywords) {
  :root { interpolate-size: allow-keywords; }
  .show-work::details-content {
    block-size: 0;
    overflow-y: clip;
    opacity: 0;
    transition:
      block-size var(--dur-major) var(--ease-spatial),
      opacity var(--dur-page) ease,
      content-visibility var(--dur-major) allow-discrete;
  }
  .show-work[open]::details-content {
    block-size: auto;
    opacity: 1;
  }
}

/* --------------------------------------------------------------------------
   11. Reduced motion — full functional parity, identical layout
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
  .case-row:hover { background: var(--surface); }
  button:active { transform: none; }

  /* Container transforms collapse to <=120ms crossfades: groups stop moving
     (they cut to their final position), old/new states fade briefly. The
     spatial layout logic — positions, grouping — is untouched. */
  ::view-transition-group(*) { animation: none !important; }
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 100ms !important;
    animation-delay: 0ms !important; /* the fade-through stagger collapses too */
    animation-timing-function: ease !important;
  }

  /* The universal selector above cannot match the ::details-content
     pseudo-element, so the "Show the work" size animation must be silenced
     explicitly: the expand becomes a cut (content still fully accessible). */
  .show-work::details-content {
    transition-duration: 0.01ms !important;
  }
}

/* --------------------------------------------------------------------------
   12. Narrow-viewport sanity (desktop-first, but nothing may break)
   -------------------------------------------------------------------------- */
@media (max-width: 56rem) {
  .report-row {
    grid-template-columns: 1fr;
    /* The same four areas the markup emits (check_row), stacked in the
       desktop reading order: which check → verdict → why → derivation. */
    grid-template-areas: "check" "chip" "explanation" "work";
    row-gap: 0.25rem;
  }
  .show-work table { min-width: 0; width: 100%; }
  .show-work th { white-space: normal; }
}

/* Touch devices: the per-document actions and disclosures need real targets
   (≥ ~40px); pointer-based so a narrow desktop window keeps its density. */
@media (pointer: coarse) {
  .doc-block-actions button { padding: 0.5rem 0.8rem; }
  .show-work summary { padding: 0.35rem 0.4rem; }
}

/* --------------------------------------------------------------------------
   13. UX-audit fixes — click-time feedback, guarded deletes, tidy states
   -------------------------------------------------------------------------- */

/* Click-time navigation feedback: a thin indeterminate top bar while a link's
   next page is being fetched (form submits already show in-flight state). The
   ::before lives on <html>, so it survives the body swap; app.js clears the
   class before the view transition so the bar never enters a VT snapshot. */
html.vt-loading { cursor: progress; }
html.vt-loading::before {
  content: "";
  position: fixed;
  inset: 0 auto auto 0;
  height: 3px;
  width: 100%;
  z-index: 9999;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--accent) 50%, transparent);
  background-size: 40% 100%;
  background-repeat: no-repeat;
  animation: nav-progress 900ms linear infinite;
}
@keyframes nav-progress {
  from { background-position: -40% 0; }
  to { background-position: 140% 0; }
}
@media (prefers-reduced-motion: reduce) {
  html.vt-loading::before { animation: none; background: var(--accent); }
}

/* Per-document delete: a two-step native <details>, styled to read as a small
   button, so a present document is never one stray click from deletion. */
.doc-delete { position: relative; }
.doc-delete summary {
  cursor: pointer;
  list-style: none;
  width: fit-content;
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--muted);
  font-size: 0.8125rem;
}
.doc-delete summary::-webkit-details-marker { display: none; }
.doc-delete summary::marker { content: ""; }
.doc-delete summary:hover { color: var(--bad-fg); border-color: var(--bad-line); }
.doc-delete[open] summary { color: var(--bad-fg); border-color: var(--bad-line); margin-bottom: 0.4rem; }
.doc-delete button.danger { padding: 0.25rem 0.6rem; font-size: 0.8125rem; }

/* Inert (pre-creation) checklist: an absence is "still to collect", not a
   deficiency — mute it instead of the live case's red bold «falta». */
.checklist-inert .checklist-items li.missing .checklist-mark,
.checklist-inert .checklist-items li.missing .checklist-state {
  color: var(--muted);
  font-weight: 400;
}

/* Informe kicker: points back to where the per-document detail lives. */
.report-kicker { margin: 0.15rem 0 0; color: var(--muted); font-size: 0.8125rem; }

/* Client-message reference header (whose list this is) — outside the <pre>. */
.client-msg-ref { margin: 0.4rem 0 0.2rem; font-weight: 600; font-size: 0.875rem; color: var(--muted); }

/* Dashboard "N sin informe" affordance + upload data-handling line. */
.sin-informe-note { margin: 0 0 0.8rem; font-size: 0.875rem; color: var(--muted); }
.upload-privacy { margin-top: 0.4rem; }

/* Sticky save for the long modelo form: a quick single-field correction can be
   committed without hunting for the foot of nine sections. Bounded by the form
   box, so it settles (never floats over the danger zone below). */
.solicitante-form > button[type="submit"] {
  position: sticky;
  bottom: 0.75rem;
  z-index: 3;
  box-shadow: var(--shadow);
}
