/* RedgeSats — Componentes base (patrón Lightning). Solo tokens; cada bloque documenta su marcado.
   Orden de carga: tokens → base → layout → components → pages. Prevalece sobre main.css (legado). */

/* ============================================================
   Botón
   <button class="btn btn-primary|btn-secondary|btn-ghost|btn-danger [btn-sm] [btn--icon]">
   28px de alto (24 con btn-sm). Un primario por región. btn-danger--solid solo al confirmar un destructivo.
   ============================================================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  height: var(--h-ctl); padding: 0 var(--s-3);
  border: 1px solid transparent; border-radius: var(--r-ctl);
  font-family: var(--font); font-size: var(--fs-13); font-weight: 500; line-height: var(--lh-ui);
  color: var(--text); background: transparent; cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background-color .12s, border-color .12s, color .12s;
}
.btn:focus-visible { outline: none; box-shadow: var(--focus); }
.btn:disabled, .btn.disabled { opacity: .55; cursor: not-allowed; pointer-events: none; }
.btn .icon { flex-shrink: 0; }
.btn-sm { height: var(--h-ctl-sm); padding: 0 var(--s-2); font-size: var(--fs-12); }
.btn--icon { width: var(--h-ctl); padding: 0; }
.btn--icon.btn-sm { width: var(--h-ctl-sm); }

.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--text-inverse); }
.btn-primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--text-inverse); }

.btn-secondary { background: var(--bg-surface); border-color: var(--border-strong); color: var(--text); }
.btn-secondary:hover:not(:disabled) { background: var(--bg-hover); border-color: var(--border-strong); color: var(--text); }

.btn-ghost { background: transparent; border-color: transparent; color: var(--text-2); }
.btn-ghost:hover:not(:disabled) { background: var(--bg-hover); color: var(--text); }
.btn-ghost[aria-expanded="true"] { background: var(--bg-selected); color: var(--accent); }

.btn-danger { background: var(--bg-surface); border-color: var(--danger); color: var(--danger); }
.btn-danger:hover:not(:disabled) { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }
.btn-danger--solid { background: var(--danger); border-color: var(--danger); color: var(--text-inverse); }
.btn-danger--solid:hover:not(:disabled) { background: var(--danger); border-color: var(--danger); color: var(--text-inverse); opacity: .92; }

/* ============================================================
   Input / Select / Textarea
   <input class="input">  <textarea class="input input--textarea">
   <span class="select"><select class="input">…</select><?= icon('chevron-down') ?></span>
   Error: .is-invalid. Solo lectura: [readonly] (fondo sutil, texto normal). Pequeño: .input--sm.
   ============================================================ */
.input {
  display: block; width: 100%; height: var(--h-ctl); padding: 0 var(--s-2);
  border: 1px solid var(--border-strong); border-radius: var(--r-ctl);
  background: var(--bg-surface); color: var(--text);
  font-family: var(--font); font-size: var(--fs-13); line-height: var(--lh-ui);
}
.input::placeholder { color: var(--text-3); }
.input:hover:not(:disabled):not([readonly]) { border-color: var(--text-3); }
.input:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus); }
.input:disabled { background: var(--bg-subtle); color: var(--text-3); cursor: not-allowed; }
.input[readonly] { background: var(--bg-subtle); border-color: transparent; }
.input[readonly]:focus { box-shadow: none; border-color: transparent; }
.input.is-invalid { border-color: var(--danger); }
.input.is-invalid:focus { box-shadow: 0 0 0 2px var(--bg-surface), 0 0 0 4px var(--danger); }
.input--sm { height: var(--h-ctl-sm); font-size: var(--fs-12); }
.input--textarea { height: auto; min-height: calc(var(--h-ctl) * 3); padding: var(--s-2); line-height: var(--lh-text); resize: vertical; }
.input--mono { font-family: var(--font-mono); }
.select { position: relative; display: block; }
.select .input { appearance: none; padding-right: calc(var(--s-2) + 16px + var(--s-2)); }
.select .icon { position: absolute; right: var(--s-2); top: 50%; transform: translateY(-50%); color: var(--text-3); pointer-events: none; }
input[type="date"].input, input[type="time"].input, input[type="number"].input { font-feature-settings: "tnum" 1; }

/* ============================================================
   Campo y rejilla de formulario
   <div class="field [field--required] [field--error] [field--full]">
     <label class="field__label" for="x">Etiqueta</label> <input class="input" id="x">
     <small class="field__hint">…</small> <small class="field__hint field__hint--alerta">…</small> <small class="field__error">…</small>
   </div>
   <div class="form-grid [form-grid--3]"> …fields… </div>   (2 columnas; --3 tres)
   ============================================================ */
.field { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.field__label { font-size: var(--fs-12); font-weight: 500; color: var(--text-2); line-height: var(--lh-ui); }
.field--required .field__label::after { content: " *"; color: var(--danger); }
.field__hint { font-size: var(--fs-12); color: var(--text-3); line-height: var(--lh-ui); }
/* La pista que avisa: distancia no disponible, confianza floja o caso sin codigo postal. */
.field__hint--alerta { color: var(--danger); }
.field__error { display: none; font-size: var(--fs-12); color: var(--danger); line-height: var(--lh-ui); }
.field--error .field__error { display: block; }
.field--error .input { border-color: var(--danger); }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3) var(--s-4); }
.form-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.field--full { grid-column: 1 / -1; }
.check { display: inline-flex; align-items: center; gap: var(--s-2); font-size: var(--fs-13); color: var(--text); cursor: pointer; line-height: var(--lh-ui); }
.check input { width: 14px; height: 14px; margin: 0; accent-color: var(--accent); }

/* ============================================================
   Pill (estado)  <span class="pill pill-{grupo}">  — lo genera pill($label, $group)
   ============================================================ */
.pill {
  display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s-2);
  border-radius: var(--r-ctl); border: 1px solid transparent;
  font-size: var(--fs-11); font-weight: 600; line-height: 1; white-space: nowrap;
}
.pill-neutral   { background: var(--neutral-soft); color: var(--text-2);  border-color: var(--border); }
.pill-active    { background: var(--accent-soft);  color: var(--accent);  border-color: var(--accent-soft); }
.pill-attention { background: var(--warning-soft); color: var(--warning); border-color: var(--warning-soft); }
.pill-done      { background: var(--success-soft); color: var(--success); border-color: var(--success-soft); }
.pill-blocked   { background: var(--danger-soft);  color: var(--danger);  border-color: var(--danger-soft); }

/* ============================================================
   Menú flotante único
   <div class="app-nav__item|toolbar__item (position:relative)">
     <button data-menu="id" aria-expanded="false">…</button>
     <div class="menu [menu--right] [menu--wide]" id="id" hidden> .menu__item … </div>
   </div>
   ============================================================ */
.menu {
  position: absolute; top: calc(100% + 2px); left: 0; z-index: var(--z-menu);
  min-width: 220px; max-width: 320px; padding: var(--s-1) 0;
  background: var(--bg-surface); border: 1px solid var(--border); border-radius: var(--r-modal); box-shadow: var(--shadow-menu);
}
.menu--right { left: auto; right: 0; }
/* ui.js la añade cuando el botón vive en un ancestro con overflow (datatable-wrap--scroll,
   split-view__list): el menú pasa a position:fixed y JS le asigna top/left|right. */
.menu--fixed { position: fixed; top: auto; left: auto; }
.menu--wide { min-width: 360px; max-width: 420px; }
.menu__header { padding: var(--s-2) var(--s-3); font-size: var(--fs-12); color: var(--text-3); line-height: var(--lh-ui); }
.menu__header strong { display: block; font-size: var(--fs-13); color: var(--text); font-weight: 600; }
.menu__section { padding: var(--s-2) var(--s-3) var(--s-1); font-size: var(--fs-11); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3); }
.menu__item {
  display: flex; align-items: center; gap: var(--s-2); width: 100%; padding: 0 var(--s-3); height: var(--h-ctl);
  font: inherit; font-size: var(--fs-13); color: var(--text); text-decoration: none; text-align: left;
  border: 0; background: transparent; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.menu__item:hover { background: var(--bg-hover); }
.menu__item:focus-visible { outline: none; background: var(--bg-selected); }
.menu__item .icon { color: var(--text-3); }
.menu__item--danger { color: var(--danger); }
.menu__item--danger .icon { color: var(--danger); }
.menu__item .pill { margin-left: auto; }
.menu__item[aria-disabled="true"] { color: var(--text-3); cursor: not-allowed; }
.menu__empty { padding: var(--s-2) var(--s-3); font-size: var(--fs-12); color: var(--text-3); }
.menu__divider { height: 1px; margin: var(--s-1) 0; background: var(--border); }
.menu__list { list-style: none; margin: 0; padding: 0; }
.menu__scroll { max-height: 420px; overflow-y: auto; }

/* ============================================================
   Tabs (una sola implementación: sustituye a .caso-tab, .sa-subtab, .sa-tab, .modal-create-tab)
   <div class="tabs [tabs--segmented]" role="tablist" data-tabs>
     <button class="tabs__tab is-active" role="tab" aria-selected="true" data-tab="panelId">Detalles <span class="tabs__count">3</span></button>
   </div>
   <div class="tabs__panel" id="panelId" role="tabpanel">…</div>   (los no activos llevan hidden)
   ============================================================ */
.tabs { display: flex; align-items: stretch; gap: 0; border-bottom: 1px solid var(--border); }
.tabs__tab {
  display: inline-flex; align-items: center; gap: var(--s-2); height: 36px; padding: 0 var(--s-3);
  border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px; background: transparent;
  font-family: var(--font); font-size: var(--fs-13); font-weight: 500; color: var(--text-2);
  cursor: pointer; white-space: nowrap; text-decoration: none;
}
.tabs__tab:hover { color: var(--text); background: var(--bg-hover); }
.tabs__tab:focus-visible { outline: none; box-shadow: inset var(--focus); }
.tabs__tab.is-active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.tabs__tab .icon { color: var(--text-3); }
.tabs__tab.is-active .icon { color: var(--accent); }
.tabs__count {
  display: inline-flex; align-items: center; height: 18px; padding: 0 6px; border-radius: var(--r-ctl);
  background: var(--neutral-soft); color: var(--text-2); font-size: var(--fs-11); font-weight: 600; line-height: 1;
}
.tabs__tab.is-active .tabs__count { background: var(--accent-soft); color: var(--accent); }
.tabs__tab.is-dim { color: var(--text-3); }
/* .tabs--page: pestañas que son enlaces de navegación entre rutas (no paneles), así que
   necesitan su propia separación del contenido que las sigue. Marcado: <nav class="tabs tabs--page">
   con <a class="tabs__tab is-active" aria-current="page">. Sin data-tabs: ui.js no las engancha. */
/* Saltan de línea en ventana estrecha en vez de desbordar, igual que .app-nav: son
   navegación, y una pestaña que se sale por el borde es una sección inalcanzable. Solo
   .tabs--page — las pestañas de panel tienen etiquetas cortas y no lo necesitan. */
.tabs--page { margin-bottom: var(--s-4); flex-wrap: wrap; }
.tabs--segmented { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--r-ctl); padding: 2px; gap: 2px; overflow: visible; }
.tabs--segmented .tabs__tab { height: calc(var(--h-ctl) - 6px); padding: 0 var(--s-2); border-bottom: 0; margin-bottom: 0; border-radius: var(--r-ctl); font-size: var(--fs-12); }
.tabs--segmented .tabs__tab.is-active { background: var(--bg-selected); color: var(--accent); }
.tabs__panel { padding-top: var(--s-4); }
.tabs__panel[hidden] { display: none; }

/* ============================================================
   Dialog (sobre <dialog> nativo). sm 440 / md 600 / lg 860.
   <dialog class="dialog dialog--md" id="d1" aria-labelledby="d1-title">
     <form method="dialog" class="dialog__form">
       <header class="dialog__header"><h2 class="dialog__title" id="d1-title">…</h2>
         <button type="button" class="btn btn-ghost btn--icon" data-dialog-close aria-label="Cerrar">…</button></header>
       <div class="dialog__body">…</div>
       <footer class="dialog__footer">[<div class="dialog__footer-start">…</div>] Cancelar · Guardar · Guardar y nuevo</footer>
     </form>
   </dialog>
   Abrir: <button data-dialog="d1">  (ui.dialog.open('d1'))
   ============================================================ */
.dialog {
  /* main.css resetea margin a 0 en *, lo que rompe el centrado nativo de <dialog>: se fija aquí */
  position: fixed; inset: 0; margin: auto;
  width: 600px; max-width: calc(100vw - var(--s-5) * 2); max-height: calc(100vh - var(--s-5) * 2);
  padding: 0; border: 1px solid var(--border); border-radius: var(--r-modal);
  background: var(--bg-surface); color: var(--text); box-shadow: var(--shadow-modal);
  z-index: var(--z-modal); overflow: hidden;
}
.dialog::backdrop { background: rgba(16, 24, 40, .45); }
.dialog--sm { width: 440px; }
.dialog--md { width: 600px; }
.dialog--lg { width: 860px; }
.dialog__form { display: flex; flex-direction: column; max-height: calc(100vh - var(--s-5) * 2); margin: 0; }
.dialog__header {
  display: flex; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--border); background: var(--bg-surface);
}
.dialog__title { flex: 1; margin: 0; font-size: var(--fs-14); font-weight: 600; line-height: var(--lh-tight); }
.dialog__body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--s-4); }
.dialog__body > .notice { margin-bottom: var(--s-3); }
.dialog__footer {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--s-2);
  padding: var(--s-3) var(--s-4); border-top: 1px solid var(--border); background: var(--bg-subtle);
}
.dialog__footer-start { margin-right: auto; display: flex; gap: var(--s-2); }

/* ============================================================
   Notice (mensajes de sistema; sustituye a .alert). Borde izquierdo 3px, fondo neutro.
   <div class="notice notice--warning" role="status">icon <div class="notice__body"><strong class="notice__title">…</strong> texto</div>
     <button class="btn btn-ghost btn-sm btn--icon" data-dismiss=".notice" aria-label="Cerrar">icon('x')</button></div>
   ============================================================ */
.notice {
  display: flex; align-items: flex-start; gap: var(--s-2); padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border); border-left-width: 3px; border-radius: var(--r-panel);
  background: var(--bg-surface); color: var(--text); font-size: var(--fs-13); line-height: var(--lh-text);
}
.notice > .icon { margin-top: 2px; }
.notice__body { flex: 1; min-width: 0; }
.notice__title { display: block; font-weight: 600; }
.notice > .btn { margin: -2px -4px -2px 0; }
.notice + .notice { margin-top: var(--s-2); }
/* `hidden` sobre .notice/.btn: el display de autor gana a la regla del UA, así que hay que apagarlo aquí. */
.notice[hidden], .btn[hidden] { display: none; }
.notice--info    { border-left-color: var(--accent); }  .notice--info > .icon    { color: var(--accent); }
.notice--success { border-left-color: var(--success); } .notice--success > .icon { color: var(--success); }
.notice--warning { border-left-color: var(--warning); } .notice--warning > .icon { color: var(--warning); }
.notice--danger  { border-left-color: var(--danger); }  .notice--danger > .icon  { color: var(--danger); }
.app-main > .notice { margin-bottom: var(--s-3); }

/* ============================================================
   Toast (confirmaciones; lo genera ui.toast()). Mismo DOM que action-bar.js.
   ============================================================ */
.toast-container {
  position: fixed; right: var(--s-5); bottom: var(--s-5); z-index: var(--z-toast);
  display: flex; flex-direction: column; gap: var(--s-2); pointer-events: none;
}
.toast {
  display: flex; align-items: flex-start; gap: var(--s-2); min-width: 280px; max-width: 380px;
  padding: var(--s-3); border: 1px solid var(--border); border-left-width: 3px; border-radius: var(--r-modal);
  background: var(--bg-surface); box-shadow: var(--shadow-modal); pointer-events: auto;
  animation: toast-slide-in .2s ease-out;
}
.toast--success { border-left-color: var(--success); }
.toast--error   { border-left-color: var(--danger); }
.toast--info    { border-left-color: var(--accent); }
.toast__icon { display: inline-flex; margin-top: 1px; color: var(--text-3); }
.toast--success .toast__icon { color: var(--success); }
.toast--error   .toast__icon { color: var(--danger); }
.toast--info    .toast__icon { color: var(--accent); }
.toast__body { flex: 1; min-width: 0; }
.toast__title { font-size: var(--fs-13); font-weight: 600; color: var(--text); line-height: var(--lh-ui); }
.toast__detail { margin-top: 2px; font-size: var(--fs-12); color: var(--text-2); line-height: var(--lh-text); }
.toast__link { display: inline-block; margin-top: var(--s-1); font-size: var(--fs-12); font-weight: 600; color: var(--accent); text-decoration: none; }
.toast__link:hover { text-decoration: underline; }
.toast__close {
  display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px;
  border: 0; border-radius: var(--r-ctl); background: transparent; color: var(--text-3); cursor: pointer; font: inherit;
}
.toast__close:hover { background: var(--bg-hover); color: var(--text); }
@keyframes toast-slide-in { from { opacity: 0; transform: translateX(12px); } to { opacity: 1; transform: none; } }

/* ============================================================
   Empty state (único sitio con padding > 20px)
   <div class="empty">icon('inbox','empty__icon') <div class="empty__title">…</div><div class="empty__text">…</div><div class="empty__actions">…</div></div>
   ============================================================ */
.empty { display: flex; flex-direction: column; align-items: center; text-align: center; padding: calc(var(--s-5) * 2) var(--s-5); color: var(--text-2); }
.empty__icon { width: 32px; height: 32px; color: var(--text-3); margin-bottom: var(--s-3); }
.empty__title { font-size: var(--fs-14); font-weight: 600; color: var(--text); line-height: var(--lh-tight); }
.empty__text { margin-top: var(--s-1); font-size: var(--fs-13); max-width: 420px; }
.empty__actions { display: flex; gap: var(--s-2); margin-top: var(--s-4); }

/* ============================================================
   Skeleton (carga)  <div class="skeleton skeleton--text|--title|--row|--block">
   ============================================================ */
.skeleton { display: block; background: var(--bg-hover); border-radius: var(--r-ctl); animation: skeleton-pulse 1.2s ease-in-out infinite; }
.skeleton--text { height: 12px; width: 60%; margin: 4px 0; }
.skeleton--title { height: 16px; width: 40%; margin: 6px 0; }
.skeleton--row { height: var(--h-row); width: 100%; margin-bottom: 1px; border-radius: 0; }
.skeleton--block { height: 120px; width: 100%; border-radius: var(--r-panel); }
@keyframes skeleton-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* ============================================================
   KV (pares label/valor)
   <dl class="kv [kv--inline] [kv--1]"><div class="kv__row"><dt class="kv__k">…</dt><dd class="kv__v">…</dd></div></dl>
   ============================================================ */
.kv { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3) var(--s-4); margin: 0; }
.kv + .kv { margin-top: var(--s-4); }
.kv--1 { grid-template-columns: minmax(0, 1fr); }
.kv__row { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.kv__k { font-size: var(--fs-12); font-weight: 500; color: var(--text-2); line-height: var(--lh-ui); }
.kv__v { margin: 0; font-size: var(--fs-13); color: var(--text); line-height: var(--lh-ui); overflow-wrap: anywhere; }
.kv__v:empty::before { content: "—"; color: var(--text-3); }
.kv__v .mono, .kv__v code { font-family: var(--font-mono); }
.kv--inline .kv__row { flex-direction: row; align-items: baseline; gap: var(--s-2); }
.kv--inline .kv__k { flex: 0 0 35%; }
.kv--inline .kv__v { flex: 1; }

/* ============================================================
   Section (bloque de contenido con cabecera). Colapsable con <details class="section" open><summary class="section__header">
   <section class="section"><header class="section__header"><h3 class="section__title">Título <span class="section__count">4/6</span></h3>
     <div class="section__actions">…</div></header><div class="section__body">…</div></section>
   ============================================================ */
.section { border: 1px solid var(--border); border-radius: var(--r-panel); background: var(--bg-surface); }
.section + .section { margin-top: var(--s-3); }
.section__header {
  display: flex; align-items: center; gap: var(--s-2); min-height: 36px; padding: 0 var(--s-3);
  border-bottom: 1px solid var(--border); background: var(--bg-subtle);
}
.section__title { flex: 1; margin: 0; font-size: var(--fs-14); font-weight: 600; line-height: var(--lh-tight); display: inline-flex; align-items: center; gap: var(--s-2); }
.section__count { font-size: var(--fs-12); font-weight: 500; color: var(--text-3); }
.section__actions { display: flex; align-items: center; gap: var(--s-1); }
.section__body { padding: var(--s-3); }
.section--flush { padding: 0; }
details.section > summary { list-style: none; cursor: pointer; }
details.section > summary::-webkit-details-marker { display: none; }
details.section > summary .section__title::before {
  content: ""; width: 6px; height: 6px; border-right: 1.5px solid var(--text-3); border-bottom: 1.5px solid var(--text-3);
  transform: rotate(-45deg); transition: transform .12s;
}
details.section[open] > summary .section__title::before { transform: rotate(45deg); }
details.section:not([open]) > summary { border-bottom: 0; }
/* Bloque de texto preformateado (JSON crudo, trazas): <pre class="code-block">…</pre> dentro de un .section__body. */
.code-block {
  margin: 0; padding: var(--s-2); border: 1px solid var(--border); border-radius: var(--r-ctl);
  background: var(--bg-subtle); color: var(--text-2); font-family: var(--font-mono); font-size: var(--fs-11);
  line-height: var(--lh-ui); white-space: pre-wrap; word-break: break-word; overflow-x: auto;
}

/* ============================================================
   Toolbar (fila de acciones). .toolbar__item ancla un .menu.
   <div class="toolbar"><div class="toolbar__group">…</div><div class="toolbar__spacer"></div><div class="toolbar__group">…</div></div>
   ============================================================ */
.toolbar { display: flex; align-items: center; gap: var(--s-2); min-height: var(--h-ctl); flex-wrap: wrap; }
.toolbar + .toolbar { margin-top: var(--s-2); }
.toolbar__group { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.toolbar__spacer { flex: 1; }
.toolbar__item { position: relative; }
.toolbar__sep { width: 1px; height: 20px; background: var(--border); }
.toolbar .input { width: 220px; }

/* ============================================================
   List view (§D0 patrón 2). Cabecera de objeto + selector de vista + pin · línea de contexto · toolbar · barra de selección · cuerpo (tabla + filter-panel).
   <div class="listview [is-filter-open]">
     <header class="listview__header">
       <div class="listview__object"><span class="listview__icon">icon</span>
         <div class="listview__titles"><div class="listview__object-name">SAs</div>
           <div class="toolbar__item"><button class="listview__view" data-menu="id"><span>Vista</span> icon('chevron-down')</button><div class="menu" id="id" hidden>…</div></div></div>
         <button class="btn btn-ghost btn-sm btn--icon listview__pin" aria-pressed="true">icon('pin')</button></div>
       <div class="listview__actions">input.search · btn-primary Nuevo · columnas · [data-filter-toggle] · [data-split-toggle]</div>
     </header>
     <div class="listview__context"><strong>n elementos</strong> · ordenado por … · filtrado por …</div>
     <div class="listview__bulk" hidden><strong data-bulk-count>0</strong> seleccionadas … acciones masivas</div>
     <div class="listview__body"><div class="listview__main">datatable-wrap + .listview__more</div><aside class="filter-panel" hidden>…</aside></div>
   </div>
   ============================================================ */
.listview { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
.listview__header { display: flex; align-items: center; gap: var(--s-4); min-height: 36px; }
.listview__object { display: flex; align-items: center; gap: var(--s-2); min-width: 0; }
.listview__icon {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 32px; height: 32px;
  border-radius: var(--r-panel); background: var(--accent); color: var(--text-inverse);
}
.listview__titles { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.listview__object-name { font-size: var(--fs-11); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3); line-height: var(--lh-tight); }
.listview__view {
  display: inline-flex; align-items: center; gap: var(--s-1); height: 24px; padding: 0 var(--s-1); margin-left: calc(var(--s-1) * -1);
  border: 0; border-radius: var(--r-ctl); background: transparent;
  font-family: var(--font); font-size: var(--fs-16); font-weight: 600; line-height: var(--lh-tight); color: var(--text); cursor: pointer; white-space: nowrap;
}
.listview__view:hover, .listview__view[aria-expanded="true"] { background: var(--bg-hover); }
.listview__view:focus-visible { outline: none; box-shadow: var(--focus); }
.listview__view .icon { color: var(--text-3); }
.listview__pin { color: var(--text-3); }
.listview__pin[aria-pressed="true"] { color: var(--accent); }
.listview__actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--s-2); flex-wrap: wrap; margin-left: auto; }
.listview__actions .input { width: 240px; }
.listview.is-filter-open .listview__actions .input { width: 180px; }
.listview__actions .btn[aria-expanded="true"], .listview__actions .btn[aria-pressed="true"] { background: var(--bg-selected); color: var(--accent); }
.listview__context { font-size: var(--fs-12); color: var(--text-3); line-height: var(--lh-ui); }
.listview__context strong { font-weight: 500; color: var(--text-2); }
.listview__bulk {
  display: flex; align-items: center; gap: var(--s-2); min-height: var(--h-ctl); padding: 0 var(--s-3);
  border: 1px solid var(--accent-soft); border-radius: var(--r-panel); background: var(--bg-selected); font-size: var(--fs-13);
}
.listview__bulk[hidden] { display: none; }
.listview__bulk strong { font-weight: 600; }
.listview__body { display: flex; align-items: flex-start; gap: var(--s-3); min-width: 0; }
.listview__main { flex: 1; min-width: 0; }
.listview__more { display: flex; align-items: center; justify-content: center; gap: var(--s-2); padding: var(--s-2); font-size: var(--fs-12); color: var(--text-3); }

/* ============================================================
   Filter panel (lateral derecho de la list view, --w-filter-panel). Condiciones campo / operador / valor.
   <aside class="filter-panel" hidden><form data-autosubmit>
     <header class="filter-panel__header"><h3 class="filter-panel__title">Filtros</h3><button data-filter-close>…</button></header>
     <div class="filter-panel__body"><div class="filter-panel__conds">
       <div class="filter-cond"><span class="select filter-cond__field">…</span><span class="select filter-cond__op">…</span>
         <button class="… filter-cond__remove" data-cond-remove>…</button><input class="input filter-cond__value"></div>
     </div><template data-cond-template>…</template><button class="… filter-panel__add" data-cond-add>Añadir condición</button></div>
     <footer class="filter-panel__footer"><div class="filter-panel__footer-start">Guardar como vista</div>Aplicar</footer>
   </form></aside>
   ============================================================ */
.filter-panel {
  flex: 0 0 var(--w-filter-panel); width: var(--w-filter-panel); position: sticky; top: var(--s-3);
  display: flex; flex-direction: column; max-height: calc(100vh - var(--s-3) * 2);
  border: 1px solid var(--border); border-radius: var(--r-panel); background: var(--bg-surface);
}
.filter-panel[hidden] { display: none; }
.filter-panel > form { flex: 1; display: flex; flex-direction: column; min-height: 0; margin: 0; }
.filter-panel__header {
  display: flex; align-items: center; gap: var(--s-2); min-height: 36px; padding: 0 var(--s-2) 0 var(--s-3);
  border-bottom: 1px solid var(--border); background: var(--bg-subtle);
}
.filter-panel__title { flex: 1; margin: 0; font-size: var(--fs-14); font-weight: 600; line-height: var(--lh-tight); }
.filter-panel__body { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-3); }
.filter-panel__conds { display: flex; flex-direction: column; gap: var(--s-2); }
.filter-panel__hint { font-size: var(--fs-12); color: var(--text-3); }
.filter-panel__add { align-self: flex-start; }
.filter-panel__footer {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--s-2); padding: var(--s-2) var(--s-3);
  border-top: 1px solid var(--border); background: var(--bg-subtle);
}
.filter-panel__footer-start { margin-right: auto; }
.filter-cond {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; gap: var(--s-1);
  padding: var(--s-2); border: 1px solid var(--border); border-radius: var(--r-panel); background: var(--bg-subtle);
}
.filter-cond .input { height: var(--h-ctl-sm); font-size: var(--fs-12); }
.filter-cond .select .icon { right: var(--s-1); }
.filter-cond__field { grid-column: 1; }
.filter-cond__op { grid-column: 2; }
.filter-cond__remove { grid-column: 3; grid-row: 1; }
.filter-cond__value { grid-column: 1 / 3; }

/* ============================================================
   Datatable (§C Tablas). Fila 32px (--compact 28px); th 11px/600 sobre --bg-subtle; anchos por <colgroup>.
   El th NO es pegajoso por defecto: lo es en la tabla de una list view (.listview__main >
   .datatable-wrap > .datatable, bajo el shell) y dentro de un .datatable-wrap--scroll (al borde
   del contenedor). Una tabla de panel a media página no pega su cabecera.
   <div class="datatable-wrap [datatable-wrap--scroll]">
   <table class="datatable [datatable--compact]" data-rowlink [data-sort-mode="client|server"] [data-inline-url] [data-sort-reset="offset,pagina,page"]>
     <colgroup><col class="col-w-40">…</colgroup>
     <thead><tr><th class="datatable__select"><input class="datatable__check" data-select-all …></th>
       <th data-sort="campo" [aria-sort="ascending|descending"]>Texto</th>  (ui.sortable() inyecta el botón)
       <th class="datatable__num">Importe</th><th class="datatable__actions"><span class="sr-only">Acciones</span></th></tr></thead>
     <tbody><tr data-href="…" data-id="…">
       <td class="datatable__select"><input class="datatable__check" data-select-row value="…"></td>
       <td class="datatable__key">SA-…</td><td class="datatable__state">pill</td><td class="datatable__num" data-sort-value="1240">1.240,00 €</td>
       <td class="datatable__editable" data-field="sat_id" data-type="select" data-options='[…]' data-value="3">Clima Norte</td>  (ui.inlineEdit())
       (data-type="number" admite data-format="decimal" → 2 decimales es-ES; vacío se muestra como —)
       <td class="datatable__actions"><div class="toolbar__item"><button data-menu="…" class="btn btn-ghost btn-sm btn--icon">…</button><div class="menu menu--right">…</div></div></td>
     </tr></tbody></table></div>
   Vacía: <tr class="datatable__empty"><td colspan="n"><div class="empty">…</div></td></tr>
   ============================================================ */
.datatable-wrap { position: relative; border: 1px solid var(--border); border-radius: var(--r-panel); background: var(--bg-surface); }
.datatable-wrap--scroll { overflow: auto; max-height: 100%; }
@media (max-width: 1279px) { .datatable-wrap { overflow-x: auto; } }
.datatable {
  width: 100%; border-collapse: separate; border-spacing: 0; table-layout: fixed;
  font-size: var(--fs-13); line-height: var(--lh-ui); color: var(--text); font-feature-settings: "tnum" 1;
}
.datatable th {
  height: var(--h-row); padding: 0 10px; text-align: left; vertical-align: middle;
  font-size: var(--fs-11); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-2);
  background: var(--bg-subtle); border-bottom: 1px solid var(--border);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.datatable td {
  height: var(--h-row); padding: 0 10px; vertical-align: middle;
  border-bottom: 1px solid var(--border); background: var(--bg-surface);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* El th pegajoso NO es el comportamiento por defecto de .datatable: solo tiene sentido donde la
   tabla es el scroller (la tabla de una list view, que scrollea la página entera y pega su
   cabecera justo bajo el shell) o donde hay un contenedor con scroll propio (pega al borde del
   contenedor). Cuando estaba puesto por defecto, toda tabla de panel a media página —catálogos,
   estadísticas de biblioteca— despegaba su cabecera y la dejaba flotando 84px por debajo del
   borde superior de la ventana, encima de las filas y del contenido de al lado. */
.listview__main > .datatable-wrap > .datatable th {
  position: sticky; top: calc(var(--h-header) + var(--h-nav)); z-index: calc(var(--z-sticky) - 1);
}
.datatable-wrap--scroll .datatable th {
  position: sticky; top: 0; z-index: calc(var(--z-sticky) - 1);
}
.datatable thead th:first-child { border-top-left-radius: var(--r-panel); }
.datatable thead th:last-child { border-top-right-radius: var(--r-panel); }
.datatable--compact th, .datatable--compact td { height: var(--h-row-compact); }
.datatable tbody tr:last-child td { border-bottom: 0; }
.datatable tbody tr:hover td { background: var(--bg-hover); }
.datatable tbody tr[data-href] { cursor: pointer; }
.datatable tbody tr[data-href]:focus-visible { outline: none; }
.datatable tbody tr[data-href]:focus-visible td { background: var(--bg-selected); box-shadow: inset 0 -1px 0 var(--accent), inset 0 1px 0 var(--accent); }
.datatable tbody tr.is-selected td { background: var(--bg-selected); }
.datatable a { color: var(--accent); text-decoration: none; }
.datatable a:hover { text-decoration: underline; }
/* los modificadores de celda se cualifican con th/td: si no, `.datatable td` (0,1,1) les gana */
/* <caption> de una tabla dentro de un .datatable-wrap: encabezado de grupo (p. ej. una orden de trabajo sobre sus citas). */
.datatable__caption {
  caption-side: top; text-align: left; padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--border);
  background: var(--bg-subtle); font-size: var(--fs-13); font-weight: 600; color: var(--text);
}
.datatable__caption a { color: var(--accent); text-decoration: none; }
.datatable__caption a:hover { text-decoration: underline; }
.datatable__caption .pill { margin-left: var(--s-2); font-weight: 500; }
.datatable th.datatable__key, .datatable td.datatable__key { font-family: var(--font-mono); font-size: var(--fs-12); }
.datatable th.datatable__num, .datatable td.datatable__num { text-align: right; font-feature-settings: "tnum" 1; }
.datatable td.datatable__state .pill { vertical-align: middle; }
.datatable th.datatable__muted, .datatable td.datatable__muted { color: var(--text-3); }
.datatable th.datatable__select, .datatable td.datatable__select { width: 36px; padding: 0 0 0 10px; }
.datatable__check { width: 14px; height: 14px; margin: 0; accent-color: var(--accent); vertical-align: middle; cursor: pointer; }
.datatable th.datatable__actions, .datatable td.datatable__actions { width: 40px; padding: 0 var(--s-1); text-align: right; overflow: visible; }
.datatable__actions .toolbar__item { display: inline-block; }
.datatable__actions .btn { opacity: 0; }
.datatable tbody tr:hover .datatable__actions .btn, .datatable__actions .btn[aria-expanded="true"], .datatable__actions .btn:focus-visible { opacity: 1; }
.datatable tr.datatable__empty td { height: auto; padding: 0; }
.datatable tr.datatable__empty:hover td { background: var(--bg-surface); }
/* ordenación (ui.sortable) */
.datatable th[data-sort] { cursor: pointer; user-select: none; }
.datatable th[data-sort]:hover { color: var(--text); }
.datatable__sort {
  display: inline-flex; align-items: center; gap: var(--s-1); max-width: 100%; padding: 0; border: 0; background: none;
  font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit; cursor: pointer; text-align: left;
}
.datatable__sort:focus-visible { outline: none; box-shadow: inset var(--focus); border-radius: var(--r-ctl); }
.datatable__sort-icon { flex-shrink: 0; color: var(--text-3); opacity: 0; }
.datatable th[data-sort]:hover .datatable__sort-icon { opacity: 1; }
.datatable th[aria-sort="ascending"], .datatable th[aria-sort="descending"] { color: var(--text); }
.datatable th[aria-sort="ascending"] .datatable__sort-icon, .datatable th[aria-sort="descending"] .datatable__sort-icon { opacity: 1; color: var(--accent); }
/* anchos de <colgroup> */
.col-w-40 { width: 40px; } .col-w-80 { width: 80px; } .col-w-120 { width: 120px; } .col-w-160 { width: 160px; }
.col-w-200 { width: 200px; } .col-w-240 { width: 240px; } .col-w-320 { width: 320px; }

/* edición inline (ui.inlineEdit): lápiz SIEMPRE visible, editor en la celda, celda sucia hasta
   Guardar. Siempre visible y no al hover porque es el único indicio de qué se puede tocar —en la
   ficha de una SA son 3 campos de 42, y cuáles cambia con el estado y el rol— y porque en una
   tablet no hay hover con el que descubrirlo. El hueco ya está reservado (padding-right: 28px),
   así que no mueve nada. Apagado en --text-3 para informar sin gritar; el hover lo destaca. */
.datatable td.datatable__editable { position: relative; padding-right: 28px; }
.datatable__edit {
  position: absolute; right: var(--s-1); top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px;
  border: 0; border-radius: var(--r-ctl); background: transparent; color: var(--text-3); cursor: pointer;
}
.datatable__edit:hover { background: var(--bg-selected); color: var(--accent); }
.datatable__edit:focus-visible { outline: none; box-shadow: var(--focus); }
.datatable td.datatable__editable.is-editing { padding: 0 2px; overflow: visible; }
.datatable__editable.is-editing .input { height: calc(var(--h-row) - 6px); }
.datatable td.datatable__editable.is-dirty { background: var(--warning-soft); box-shadow: inset 3px 0 0 var(--warning); }
.datatable tbody tr:hover .datatable__editable.is-dirty { background: var(--warning-soft); }

/* ============================================================
   Split view (§D2 Bandeja). Lista de --w-split a la izquierda + panel con el registro a la derecha. Teclas j/k y Enter (ui.splitView).
   <div class="split-view [split-view--embedded]" data-split>
     <div class="split-view__list"><div class="split-view__list-header"><strong>Vista</strong> · n <span class="split-view__keys"><kbd>j</kbd><kbd>k</kbd></span></div>
       <ul class="split-view__items" role="listbox"><li class="split-view__group">Grupo (n)</li>
         <li class="split-view__item [is-active]" role="option" data-href="…" [data-panel-src="…"]>
           <div class="split-view__title"><code>SA-…</code><span>Cliente</span></div><div class="split-view__meta">pill · ciudad · hace x</div></li></ul></div>
     <div class="split-view__panel">…</div>
   </div>
   ============================================================ */
.split-view {
  display: flex; min-height: 320px; height: calc(100vh - var(--h-header) - var(--h-nav) - var(--s-4) * 2);
  border: 1px solid var(--border); border-radius: var(--r-panel); background: var(--bg-surface); overflow: hidden;
}
.split-view--embedded { height: 480px; }
.split-view__list { flex: 0 0 var(--w-split); width: var(--w-split); display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--border); }
.split-view__list-header {
  display: flex; align-items: center; gap: var(--s-1); min-height: 36px; padding: 0 var(--s-3);
  border-bottom: 1px solid var(--border); background: var(--bg-subtle); font-size: var(--fs-12); color: var(--text-3);
}
.split-view__list-header strong { font-size: var(--fs-13); font-weight: 600; color: var(--text); }
.split-view__keys { margin-left: auto; display: inline-flex; align-items: center; gap: var(--s-1); font-size: var(--fs-11); }
.split-view__keys kbd {
  display: inline-block; min-width: 16px; padding: 0 4px; border: 1px solid var(--border-strong); border-radius: var(--r-ctl);
  background: var(--bg-surface); font-family: var(--font-mono); font-size: var(--fs-11); line-height: 16px; text-align: center; color: var(--text-2);
}
.split-view__items { flex: 1; min-height: 0; overflow-y: auto; margin: 0; padding: 0; list-style: none; }
.split-view__group {
  position: sticky; top: 0; z-index: 1; padding: var(--s-1) var(--s-3);
  font-size: var(--fs-11); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3);
  background: var(--bg-subtle); border-bottom: 1px solid var(--border);
}
.split-view__item {
  display: flex; flex-direction: column; gap: 2px; padding: var(--s-2) var(--s-3);
  border-bottom: 1px solid var(--border); border-left: 3px solid transparent; cursor: pointer;
}
.split-view__item:hover { background: var(--bg-hover); }
.split-view__item.is-active { background: var(--bg-selected); border-left-color: var(--accent); }
.split-view__item:focus-visible { outline: none; box-shadow: inset var(--focus); }
.split-view__title { display: flex; align-items: center; gap: var(--s-2); min-width: 0; font-size: var(--fs-13); font-weight: 500; color: var(--text); }
.split-view__title code { flex-shrink: 0; font-family: var(--font-mono); font-size: var(--fs-12); color: var(--text-2); }
.split-view__title span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.split-view__meta { display: flex; align-items: center; gap: var(--s-2); font-size: var(--fs-12); color: var(--text-3); white-space: nowrap; }
.split-view__meta .pill { flex-shrink: 0; }
.split-view__panel { flex: 1; min-width: 0; overflow-y: auto; padding: var(--s-4); }
.split-view__panel[aria-busy="true"] { opacity: .6; }
.split-view__panel > .empty { height: 100%; justify-content: center; }

/* ============================================================
   KPI bar (cabecera de la Bandeja, §D2). Tarjetas-enlace compactas con el contador de una cola.
   <div class="kpibar"><a class="kpibar__item kpibar__item--danger" href="…">
     <span class="kpibar__value">3</span><span class="kpibar__label">SAT no acepta &gt;48h</span><span class="kpibar__sub">esperando al SAT</span></a></div>
   ============================================================ */
.kpibar { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.kpibar__item {
  display: flex; flex-direction: column; gap: 1px; min-width: 150px; padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border); border-left: 3px solid var(--border-strong); border-radius: var(--r-panel);
  background: var(--bg-surface); color: var(--text); text-decoration: none;
}
.kpibar__item:hover { background: var(--bg-hover); }
.kpibar__item:focus-visible { outline: none; box-shadow: var(--focus); }
.kpibar__value { font-size: var(--fs-20); font-weight: 600; line-height: var(--lh-tight); }
.kpibar__label { font-size: var(--fs-12); font-weight: 500; color: var(--text-2); line-height: var(--lh-ui); }
.kpibar__sub { font-size: var(--fs-11); color: var(--text-3); line-height: var(--lh-ui); }
.kpibar__item--info { border-left-color: var(--accent); }
.kpibar__item--warning { border-left-color: var(--warning); }
.kpibar__item--success { border-left-color: var(--success); }
.kpibar__item--danger { border-left-color: var(--danger); }
.kpibar__item--warning .kpibar__value { color: var(--warning); }
.kpibar__item--success .kpibar__value { color: var(--success); }
.kpibar__item--danger .kpibar__value { color: var(--danger); }

/* La Bandeja ocupa el alto de la ventana: el split crece con la list view en vez de calcular su propio alto. */
.listview--fill { height: calc(100vh - var(--h-header) - var(--h-nav) - var(--s-4) * 2); }
.listview--fill .listview__body { flex: 1; min-height: 0; }
.listview--fill .listview__main { display: flex; min-height: 0; }
.split-view--fill { height: auto; flex: 1; min-height: 320px; }

/* ============================================================
   Inline edit bar (la crea ui.inlineEdit() en <body>; una por página)
   <div class="inline-edit-bar" role="status" hidden><span class="inline-edit-bar__count">3 cambios</span>
     <div class="inline-edit-bar__actions"><button data-inline-cancel>Cancelar</button><button data-inline-save>Guardar</button></div></div>
   ============================================================ */
.inline-edit-bar {
  position: fixed; left: 50%; bottom: var(--s-5); transform: translateX(-50%); z-index: calc(var(--z-menu) - 1);
  display: flex; align-items: center; gap: var(--s-4);
  padding: var(--s-2) var(--s-2) var(--s-2) var(--s-4);
  border: 1px solid var(--border); border-radius: var(--r-modal); background: var(--bg-surface); box-shadow: var(--shadow-modal);
  font-size: var(--fs-13); color: var(--text);
}
.inline-edit-bar[hidden] { display: none; }
.inline-edit-bar__count { font-weight: 600; white-space: nowrap; }
.inline-edit-bar__actions { display: flex; gap: var(--s-2); }

/* ============================================================
   Owner (propietario: quién tiene la pelota). Lo genera sa_state_owner_tag($estado).
   <span class="owner owner--redge|sat|tecnico|none">icon Etiqueta</span>
   ============================================================ */
.owner { display: inline-flex; align-items: center; gap: var(--s-1); font-size: var(--fs-12); font-weight: 500; color: var(--text-2); line-height: var(--lh-ui); white-space: nowrap; }
.owner .icon { width: 14px; height: 14px; color: var(--text-3); }
.owner--redge .icon { color: var(--accent); }
.owner--sat .icon { color: var(--warning); }
.owner--tecnico .icon { color: var(--success); }
.owner--none { color: var(--text-3); }

/* ============================================================
   Campo de solo lectura (misma rejilla que .field, sin input)
   <div class="field field--readonly"><span class="field__label">Etiqueta</span><div class="field__value">Valor</div></div>
   ============================================================ */
.field__value {
  display: flex; align-items: center; min-height: var(--h-ctl); padding: 0 var(--s-2);
  border: 1px solid transparent; border-radius: var(--r-ctl); background: var(--bg-subtle);
  font-size: var(--fs-13); color: var(--text); line-height: var(--lh-ui); overflow-wrap: anywhere;
}
.field__value:empty::before { content: "—"; color: var(--text-3); }
.field__value code { font-family: var(--font-mono); }
.field--readonly .field__label { color: var(--text-3); }

/* ============================================================
   Highlights (record page, §D0 patrón 3a). Icono de objeto + tipo + nombre 16px + 5-6 campos clave + acciones (record_actions()).
   <header class="highlights">
     <div class="highlights__object"><span class="highlights__icon">icon</span>
       <div class="highlights__titles"><div class="highlights__type">SA</div><h1 class="highlights__name">SA-…</h1><div class="highlights__sub">modelo · <code>serie</code></div></div></div>
     <dl class="highlights__fields"><div class="highlights__field"><dt>Estado</dt><dd>pill</dd></div> …(5-6)</dl>
     <div class="highlights__actions"><?= record_actions($acciones) ?></div>
   </header>
   ============================================================ */
.highlights {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "object fields actions";
  align-items: center; gap: var(--s-3) var(--s-5); padding: var(--s-3) var(--s-4);
  border: 1px solid var(--border); border-radius: var(--r-panel); background: var(--bg-surface);
}
.highlights__object { grid-area: object; display: flex; align-items: center; gap: var(--s-3); min-width: 0; max-width: 360px; }
.highlights__icon {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 40px; height: 40px;
  border-radius: var(--r-panel); background: var(--accent); color: var(--text-inverse);
}
.highlights__icon .icon { width: 20px; height: 20px; }
.highlights__titles { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.highlights__type { font-size: var(--fs-11); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3); line-height: var(--lh-tight); }
.highlights__name { margin: 0; font-size: var(--fs-16); font-weight: 600; line-height: var(--lh-tight); color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.highlights__sub { font-size: var(--fs-12); color: var(--text-2); line-height: var(--lh-ui); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.highlights__sub code { font-family: var(--font-mono); }
.highlights__fields { grid-area: fields; display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5); margin: 0; min-width: 0; }
.highlights__field { display: flex; flex-direction: column; gap: 2px; min-width: 0; max-width: 220px; }
.highlights__field dt { font-size: var(--fs-11); font-weight: 500; color: var(--text-3); line-height: var(--lh-tight); white-space: nowrap; }
.highlights__field dd { margin: 0; font-size: var(--fs-13); color: var(--text); line-height: var(--lh-ui); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.highlights__field dd:empty::before { content: "—"; color: var(--text-3); }
.highlights__field dd a { color: var(--accent); text-decoration: none; }
.highlights__field dd a:hover { text-decoration: underline; }
.highlights__field dd .pill, .highlights__field dd .owner { vertical-align: middle; }
.highlights__actions { grid-area: actions; display: flex; align-items: center; gap: var(--s-2); flex-shrink: 0; }
@media (max-width: 1279px) {
  .highlights { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "object actions" "fields fields"; }
}

/* ============================================================
   Path (record page, §D0 patrón 3b). Lo renderiza views/_partials/path.php ($estado, $accionPrincipal).
   <nav class="path [path--cancelada]"><ol class="path__steps">
     <li class="path__step is-done"><span class="path__label">icon(check) Preparación</span></li>
     <li class="path__step is-current" aria-current="step" title="Estado exacto">…</li> … [<li class="path__step is-cancel">Cancelada</li>]
   </ol><div class="path__action"><span class="path__state">Estado: …</span> [<span class="path__hint">…</span>] [btn-primary btn-sm .path__btn]</div></nav>
   Chevrones por clip-path: cada paso se solapa 8px con el anterior y su muesca deja 2px de fondo como separador.
   ============================================================ */
.path {
  display: flex; align-items: center; gap: var(--s-4); padding: var(--s-2) var(--s-3);
  border: 1px solid var(--border); border-radius: var(--r-panel); background: var(--bg-surface);
}
.path__steps { display: flex; flex: 1; min-width: 0; margin: 0; padding: 0; list-style: none; }
.path__step {
  position: relative; flex: 1; min-width: 0; height: var(--h-ctl); margin-left: -8px;
  display: flex; align-items: center; justify-content: center; padding: 0 var(--s-3) 0 calc(var(--s-3) + 8px);
  background: var(--neutral-soft); color: var(--text-2); font-size: var(--fs-12); font-weight: 500; line-height: var(--lh-ui); white-space: nowrap;
  clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 50%, calc(100% - 10px) 100%, 0 100%, 10px 50%);
}
.path__step:first-child { margin-left: 0; padding-left: var(--s-3); border-radius: var(--r-ctl) 0 0 var(--r-ctl); clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 50%, calc(100% - 10px) 100%, 0 100%); }
.path__step:last-child { border-radius: 0 var(--r-ctl) var(--r-ctl) 0; clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 10px 50%); }
.path__step.is-done { background: var(--success-soft); color: var(--success); }
.path__step.is-current { background: var(--accent); color: var(--text-inverse); font-weight: 600; }
.path__step.is-cancel { flex: 0 0 auto; background: var(--danger); color: var(--text-inverse); font-weight: 600; }
.path--cancelada .path__step:not(.is-cancel) { color: var(--text-3); }
.path__label { display: inline-flex; align-items: center; gap: var(--s-1); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.path__label .icon { width: 14px; height: 14px; flex-shrink: 0; }
.path__action { display: flex; align-items: center; gap: var(--s-3); flex-shrink: 0; }
.path__state { font-size: var(--fs-12); color: var(--text-2); white-space: nowrap; }
.path__hint { font-size: var(--fs-12); color: var(--text-3); white-space: nowrap; }
@media (max-width: 1279px) { .path__hint { display: none; } }
.path + .path, .path + .field__hint, .field__hint + .path { margin-top: var(--s-3); }

/* ============================================================
   Record page — layout (§D0 patrón 3c). highlights → path → tabs → cuerpo 2/3 + 1/3 (lateral máx. --w-side-max).
   <div class="record">
     <header class="highlights">…</header> <nav class="path">…</nav>
     <div class="tabs record-tabs" role="tablist" data-tabs data-tabs-hash>Detalles · Relacionado · pestañas propias</div>
     <div class="record-body">
       <div class="record-main"><div class="tabs__panel" id="detalles">…sections con field-grid…</div><div class="tabs__panel" id="relacionado" hidden>…related-lists…</div></div>
       <aside class="record-side"><section class="section activity-card">…activity…</section><section class="section related-card">…</section></aside>
     </div>
   </div>
   ============================================================ */
.record { display: flex; flex-direction: column; gap: var(--s-3); min-width: 0; }
.record-tabs {
  position: sticky; top: calc(var(--h-header) + var(--h-nav)); z-index: calc(var(--z-sticky) - 2);
  margin: 0 calc(var(--s-1) * -1); padding: 0 var(--s-1); background: var(--bg-app);
}
.record--embed .record-tabs { position: static; margin: 0; padding: 0; }
.record--embed [data-action], .record--embed [data-dialog], .record--embed .highlights__actions { display: none; }
.record-body { display: grid; grid-template-columns: minmax(0, 1fr) clamp(280px, 33.333%, var(--w-side-max)); gap: var(--s-4); align-items: start; }
.record-main { min-width: 0; }
.record-main > .tabs__panel { padding-top: 0; }
.record-side { display: flex; flex-direction: column; gap: var(--s-3); min-width: 0; }
.record-side > .section + .section { margin-top: 0; }
@media (max-width: 1279px) { .record-body { grid-template-columns: minmax(0, 1fr); } }

/* ============================================================
   Related list (§D0 patrón 3e): "Objeto (n)" + Nuevo + datatable--compact de hasta 6 filas + "Ver todo".
   <section class="section related-list">
     <header class="section__header"><h3 class="section__title">icon Adjuntos <span class="section__count">(3)</span></h3>
       <div class="section__actions"><button class="btn btn-ghost btn-sm">icon(plus) Nuevo</button></div></header>
     <div class="section__body"><div class="datatable-wrap"><table class="datatable datatable--compact" data-rowlink>… ≤ 6 filas …</table></div></div>
     <footer class="related-list__footer"><a class="btn btn-ghost btn-sm" href="…">Ver todo (7) icon(arrow-right)</a></footer>
   </section>
   Tarjeta lateral compacta: <section class="section related-card"><header class="section__header">…</header>
     <div class="section__body"><dl class="kv kv--inline kv--1">…4-5 pares…</dl></div><footer class="related-card__footer"><a class="btn btn-ghost btn-sm">Abrir caso</a></footer></section>
   ============================================================ */
.related-list .section__body { padding: 0; }
.related-list .datatable-wrap { border: 0; border-radius: 0; }
.related-list .datatable thead th:first-child, .related-list .datatable thead th:last-child { border-radius: 0; }
.related-list .section__title .icon { color: var(--text-3); }
.related-list .empty { padding: var(--s-4); }
.related-list__footer { display: flex; justify-content: center; padding: var(--s-1); border-top: 1px solid var(--border); background: var(--bg-subtle); border-radius: 0 0 var(--r-panel) var(--r-panel); }
.related-list__footer .btn { color: var(--accent); }
.related-card .section__body { padding: var(--s-2) var(--s-3); }
.related-card .kv { gap: var(--s-1) var(--s-3); }
.related-card .kv--inline .kv__k { flex-basis: 40%; }
.related-card .section__title a { color: var(--accent); text-decoration: none; }
.related-card .section__title a:hover { text-decoration: underline; }
.related-card__footer { display: flex; justify-content: flex-end; gap: var(--s-2); padding: var(--s-1) var(--s-2); border-top: 1px solid var(--border); background: var(--bg-subtle); border-radius: 0 0 var(--r-panel) var(--r-panel); }

/* ---------- Miniaturas de fotos (Intervención de la SA): rejilla de 96px ---------- */
.thumbs { display: grid; grid-template-columns: repeat(auto-fill, 96px); gap: var(--s-2); margin: 0; padding: 0; list-style: none; }
.thumbs__item { display: block; width: 96px; height: 96px; border: 1px solid var(--border); border-radius: var(--r-ctl); overflow: hidden; background: var(--bg-subtle); }
.thumbs__item img { display: block; width: 100%; height: 100%; object-fit: cover; }
.thumbs__item:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }

/* ---------- Firmas (Intervención de la SA): técnico y cliente en dos columnas ---------- */
.firmas { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
.firmas__item { border: 1px solid var(--border); border-radius: var(--r-ctl); padding: var(--s-2); text-align: center; background: var(--bg-surface); }
.firmas__label { margin-bottom: var(--s-1); font-size: var(--fs-11); text-transform: uppercase; letter-spacing: .04em; color: var(--text-3); }
.firmas__item img { max-width: 100%; max-height: 96px; }
.firmas__meta { margin-top: var(--s-1); font-size: var(--fs-12); color: var(--text-2); }

/* ============================================================
   Field grid (Detalles, §D0 patrón 3d). Dos columnas, label al 35 %, lápiz siempre visible, edición inline (ui.inlineEdit) con la misma
   .inline-edit-bar que la datatable. El host lleva data-id y data-inline-url; un dd sin data-field es solo lectura.
   <details class="section" open><summary class="section__header"><h3 class="section__title">Solicitud <span class="section__count" data-field-count></span></h3></summary>
     <div class="section__body"><dl class="field-grid [field-grid--1]" data-id="42" data-inline-url="…">
       <div class="field-grid__row [field-grid__row--full]"><dt class="field-grid__label">Modelo</dt>
         <dd class="field-grid__value" data-field="modelo" data-type="text|number|date|select|textarea" [data-value] [data-options] [data-format]>RG-VRF 40 kW</dd></div>
     </dl></div></details>
   ============================================================ */
.field-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--s-5); margin: 0; }
.field-grid--1 { grid-template-columns: minmax(0, 1fr); }
.field-grid__row { display: flex; align-items: flex-start; gap: var(--s-2); min-width: 0; min-height: var(--h-row); padding: var(--s-1) 0; border-bottom: 1px solid var(--border); }
.field-grid__row--full { grid-column: 1 / -1; }
.field-grid__label { flex: 0 0 35%; padding-top: 3px; font-size: var(--fs-12); font-weight: 500; color: var(--text-2); line-height: var(--lh-ui); overflow-wrap: anywhere; }
.field-grid__value {
  position: relative; flex: 1; min-width: 0; margin: 0; padding: 3px 0;
  font-size: var(--fs-13); color: var(--text); line-height: var(--lh-ui); overflow-wrap: anywhere; white-space: pre-line;
}
.field-grid__value[data-field] { padding-right: 28px; border-radius: var(--r-ctl); }
.field-grid__value[data-field]:hover { background: var(--bg-hover); }
.field-grid__value.is-empty { color: var(--text-3); }
.field-grid__value code { font-family: var(--font-mono); font-size: var(--fs-12); }
.field-grid__value a { color: var(--accent); text-decoration: none; }
.field-grid__value a:hover { text-decoration: underline; }
.field-grid__value .datatable__edit { top: 2px; transform: none; }
.field-grid__value.is-editing { padding: 0; white-space: normal; background: transparent; }
.field-grid__value.is-editing .input { height: var(--h-ctl); font-size: var(--fs-13); }
.field-grid__value.is-editing .input--textarea { height: auto; min-height: calc(var(--h-ctl) * 3); }
.field-grid__value.is-dirty { background: var(--warning-soft); box-shadow: inset 3px 0 0 var(--warning); padding-left: var(--s-2); }

/* ============================================================
   Activity (lateral del record page): composer + timeline unificado (mensajes + auditoría) filtrable por data-kind con .tabs--segmented.
   <section class="activity" data-activity [data-activity-url]>
     <form class="activity__composer">textarea.input.input--textarea <div class="activity__composer-bar">check · <kbd>Ctrl</kbd>+<kbd>↵</kbd> · btn-primary btn-sm Enviar</div></form>
     <div class="activity__filter"><div class="tabs tabs--segmented" data-tabs data-activity-filter><button role="tab" data-kind="">Todo</button>…</div></div>
     <ol class="activity__timeline"><li class="activity__day">Hoy</li>
       <li class="activity__item activity__item--mensaje|--auditoria|--sistema" data-kind="mensaje|auditoria|sistema"><span class="activity__dot">icon</span>
         <div class="activity__body"><div class="activity__meta"><strong>Quién</strong> owner <time>hace 2 h</time></div><div class="activity__text">…</div></div></li></ol>
     <div class="activity__empty" hidden>Sin actividad</div>
   </section>
   La línea vertical se dibuja por ítem (::before desde el punto hasta el siguiente), así los .activity__day la interrumpen sin fondos.
   ============================================================ */
.activity { display: flex; flex-direction: column; gap: var(--s-3); min-width: 0; }
.activity__composer {
  display: flex; flex-direction: column; gap: var(--s-1); margin: 0; padding: var(--s-1) var(--s-2) var(--s-2);
  border: 1px solid var(--border-strong); border-radius: var(--r-panel); background: var(--bg-surface);
}
.activity__composer:focus-within { border-color: var(--accent); box-shadow: var(--focus); }
.activity__composer .input--textarea { min-height: calc(var(--h-ctl) * 2); padding: var(--s-1); border-color: transparent; resize: none; }
.activity__composer .input--textarea:hover:not(:disabled):not([readonly]), .activity__composer .input--textarea:focus { border-color: transparent; box-shadow: none; }
.activity__composer-bar { display: flex; align-items: center; gap: var(--s-2); }
.activity__composer-bar .check { font-size: var(--fs-12); color: var(--text-2); }
.activity__composer-bar kbd {
  display: inline-block; min-width: 16px; padding: 0 4px; border: 1px solid var(--border-strong); border-radius: var(--r-ctl);
  background: var(--bg-subtle); font-family: var(--font-mono); font-size: var(--fs-11); line-height: 16px; color: var(--text-3);
}
.activity__composer-bar .activity__keys { margin-left: auto; display: inline-flex; align-items: center; gap: 2px; font-size: var(--fs-11); color: var(--text-3); }
.activity__filter { display: flex; align-items: center; gap: var(--s-2); }
.activity__filter .tabs--segmented { background: var(--bg-surface); }
/* Tope de altura, no de número de entradas: la página deja de estirarse hasta el infinito y no se
   vuelve inalcanzable lo antiguo, que es lo que pasaría recortando la lista. El scroll es de la
   línea de tiempo, no de la tarjeta: el redactor y el filtro se quedan fijos arriba. */
.activity__timeline { margin: 0; padding: 0; list-style: none; max-height: 520px; overflow-y: auto; }
.activity__item { position: relative; display: flex; gap: var(--s-2); padding: var(--s-2) 0; }
.activity__item[hidden] { display: none; }
.activity__item::before { content: ""; position: absolute; left: 11px; top: 32px; bottom: calc(var(--s-2) * -1); width: 1px; background: var(--border); }
.activity__item:last-child::before, .activity__item.is-last::before { display: none; }
.activity__dot {
  position: relative; z-index: 1; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 24px; height: 24px; border-radius: 50%; border: 1px solid var(--border); background: var(--bg-surface); color: var(--text-3);
}
.activity__dot .icon { width: 14px; height: 14px; }
.activity__item--mensaje .activity__dot { border-color: var(--accent-soft); background: var(--accent-soft); color: var(--accent); }
.activity__item--auditoria .activity__dot { background: var(--bg-subtle); color: var(--text-3); }
.activity__item--sistema .activity__dot { background: var(--warning-soft); border-color: var(--warning-soft); color: var(--warning); }
.activity__body { flex: 1; min-width: 0; }
.activity__meta { display: flex; align-items: baseline; gap: var(--s-1); flex-wrap: wrap; font-size: var(--fs-12); color: var(--text-3); line-height: var(--lh-ui); }
.activity__meta strong { font-weight: 600; color: var(--text); }
.activity__meta .owner { font-size: var(--fs-11); }
.activity__meta time { margin-left: auto; white-space: nowrap; }
.activity__text { margin-top: 2px; font-size: var(--fs-13); color: var(--text); line-height: var(--lh-text); overflow-wrap: anywhere; white-space: pre-line; }
.activity__item--mensaje .activity__text { padding: var(--s-2) var(--s-3); border: 1px solid var(--border); border-radius: var(--r-panel); background: var(--bg-surface); }
.activity__item--auditoria .activity__text, .activity__item--sistema .activity__text { font-size: var(--fs-12); color: var(--text-2); }
.activity__change { display: inline-flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; }
.activity__change .icon { width: 12px; height: 12px; color: var(--text-3); }
.activity__day { padding: var(--s-2) 0 var(--s-1); font-size: var(--fs-11); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3); }
.activity__day[hidden] { display: none; }
.activity__empty { padding: var(--s-4); text-align: center; font-size: var(--fs-12); color: var(--text-3); }
.activity__empty[hidden] { display: none; }
.activity__more { display: flex; justify-content: center; padding: var(--s-1); }

/* ============================================================
   Hover card (§D0 patrón 4). Una sola tarjeta en <body>, la crea ui.js; el contenido lo sirve el servidor (HTML parcial).
   Disparador: <a href="…" data-hovercard="/ruta/al/parcial">C-2026-0311</a>
   Parcial: <div class="hovercard__header"><span class="hovercard__icon">icon</span><div class="hovercard__titles"><div class="hovercard__type">Caso</div><a class="hovercard__name" href>…</a></div> [pill]</div>
            <div class="hovercard__body"><dl class="kv kv--inline kv--1">…4-5 pares…</dl></div><div class="hovercard__footer">btn-secondary btn-sm</div>
   ============================================================ */
.hovercard {
  position: fixed; z-index: var(--z-menu); width: var(--w-hovercard); max-width: calc(100vw - var(--s-4) * 2);
  border: 1px solid var(--border); border-radius: var(--r-modal); background: var(--bg-surface); box-shadow: var(--shadow-menu);
  font-size: var(--fs-13); color: var(--text); overflow: hidden;
}
.hovercard[hidden] { display: none; }
.hovercard[aria-busy="true"] .hovercard__body { min-height: 88px; }
.hovercard__header { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--border); background: var(--bg-subtle); }
.hovercard__icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; border-radius: var(--r-panel); background: var(--accent); color: var(--text-inverse); }
.hovercard__titles { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.hovercard__type { font-size: var(--fs-11); font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-3); line-height: var(--lh-tight); }
.hovercard__name { font-size: var(--fs-13); font-weight: 600; color: var(--text); text-decoration: none; line-height: var(--lh-ui); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hovercard__name:hover { color: var(--accent); text-decoration: underline; }
.hovercard__header .pill { margin-left: auto; flex-shrink: 0; }
.hovercard__body { padding: var(--s-2) var(--s-3); }
.hovercard__body .kv { gap: var(--s-1) var(--s-2); }
.hovercard__body .kv--inline .kv__k { flex-basis: 38%; }
.hovercard__footer { display: flex; justify-content: flex-end; gap: var(--s-2); padding: var(--s-1) var(--s-2); border-top: 1px solid var(--border); background: var(--bg-subtle); }
.hovercard__error { padding: var(--s-3); font-size: var(--fs-12); color: var(--text-3); text-align: center; }
a[data-hovercard] { color: var(--accent); text-decoration: none; }
a[data-hovercard]:hover { text-decoration: underline; }

/* ============================================================
   List view — Fase 3 (chips de filtro, grupo de pills, checkboxes en menú ⚙, cargar más oculto).
   ============================================================ */
.listview__more[hidden] { display: none; }
.listview__chip {
  display: inline-flex; align-items: center; gap: var(--s-1); height: 20px; padding: 0 var(--s-2);
  border: 1px solid var(--border); border-radius: var(--r-ctl); background: var(--bg-subtle); color: var(--text-2); font-size: var(--fs-11); white-space: nowrap;
}
.listview__chip strong { font-weight: 500; color: var(--text); }
.listview__view-empty { padding: var(--s-2) var(--s-3); font-size: var(--fs-12); color: var(--text-3); }
.pill-group { display: inline-flex; align-items: center; gap: var(--s-1); }
.menu__item.check { gap: var(--s-2); }
.menu__item.check input { flex-shrink: 0; }
.menu__form { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) var(--s-3); margin: 0; }
.menu__form .input { width: 140px; }
.filter-cond--fija { display: flex; align-items: center; gap: var(--s-2); }
.icon--warning { color: var(--warning); }

/* ============================================================
   Utilidades
   .sr-only: texto solo para lectores de pantalla (cabeceras de columnas sin texto visible).
   ============================================================ */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

/* ============================================================
   Formulario en modal (Fase 7)
   <div class="form-cols"><section class="section">…</section><section class="section">…</section></div>
   Dos columnas, una sola por debajo de 900px (el ancho del diálogo .dialog--lg). .form-grid YA existe arriba
   (bloque "Botón"/inputs, línea 82: gap var(--s-3) var(--s-4)) — no se redefine aquí para no apretar el gap
   en las 13 vistas que ya la usan fuera de la Fase 7. De aquí solo es nuevo .form-grid__full, para el campo
   a ancho completo dentro de una sección. .dropzone: zona de arrastrar-soltar adjuntos; .dropzone--over al
   arrastrar sobre ella; .dropzone__list/.dropzone__item listan los ficheros ya añadidos. .recambio: fila
   repetible del formulario de recambios, con .recambio__head (nombre + quitar) y .recambio__cols
   (código/cantidad/precio).
   ============================================================ */
.form-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3); align-items: start; }
@media (max-width: 900px) { .form-cols { grid-template-columns: minmax(0, 1fr); } }
.form-grid__full { grid-column: 1 / -1; }
.dropzone {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  min-height: 72px; padding: var(--s-3); border: 1px dashed var(--border-strong); border-radius: var(--r-panel);
  background: var(--bg-subtle); color: var(--text-3); font-size: var(--fs-12); text-align: center; cursor: pointer;
}
.dropzone:hover, .dropzone--over { border-color: var(--accent); background: var(--bg-hover); color: var(--text-2); }
.dropzone__list { display: flex; flex-direction: column; gap: 2px; margin-top: var(--s-2); }
.dropzone__item { display: flex; align-items: center; gap: var(--s-2); padding: 2px var(--s-2); border: 1px solid var(--border); border-radius: var(--r-ctl); background: var(--bg-surface); font-size: var(--fs-12); }
.dropzone__item span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.recambio { padding: var(--s-2); margin-bottom: var(--s-2); border: 1px solid var(--border); border-radius: var(--r-panel); background: var(--bg-subtle); }
.recambio__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); margin-bottom: 2px; font-size: var(--fs-12); font-weight: 500; color: var(--text-2); }
.recambio__cols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-2); }
/* Mapa del diálogo «Verificar dirección» (Fase 7): el iframe necesita alto propio, no lo da el navegador. */
.map-frame { display: block; width: 100%; height: 420px; border: 1px solid var(--border); border-radius: var(--r-panel); background: var(--bg-subtle); }

/* ============================================================
   Visibilidad de campo (Fase 8a)
   visibility_tag() pinta <span class="vis vis--tech-pdf">…iconos…</span> junto al <dt> o al <th>
   de cada campo de una pantalla de administración, y visibility_leyenda() el banner .vis-legend
   con las seis categorías. Los iconos van atenuados: son metadatos, no contenido, y no deben
   competir con el dato. El title lo pone el helper; aquí solo el aspecto.
   ============================================================ */
.vis { display: inline-flex; align-items: center; gap: 1px; color: var(--text-3); vertical-align: middle; cursor: help; }
.vis .icon { width: 12px; height: 12px; }
.vis:hover { color: var(--text-2); }
.vis-legend {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-3);
  padding: var(--s-2) var(--s-3); margin-bottom: var(--s-3);
  border: 1px solid var(--border); border-radius: var(--r-panel);
  background: var(--bg-subtle); color: var(--text-3); font-size: var(--fs-11); line-height: var(--lh-ui);
}
.vis-legend > .icon { color: var(--text-3); }
.vis-legend__item { display: inline-flex; align-items: center; gap: var(--s-1); white-space: nowrap; }

/* ============================================================
   Herramientas de Salesforce (8c-4): rejilla de campos marcables
   y filas de filtro del constructor de consultas. Solo layout —
   los controles son .check, .input y .btn del propio sistema.
   ============================================================ */
.sf-fields {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 2px;
  max-height: 260px; overflow-y: auto; padding: var(--s-1);
  border: 1px solid var(--border); border-radius: var(--r-ctl); background: var(--bg-subtle);
}
.sf-fields__item { padding: 2px var(--s-1); border-radius: var(--r-ctl); }
.sf-fields__item:hover { background: var(--bg-surface); }
.sf-fields__item[hidden] { display: none; }
.sf-fields__type { margin-left: var(--s-1); font-size: var(--fs-11); color: var(--text-3); }
.sf-filter { display: flex; align-items: center; gap: var(--s-2); }
.sf-filter + .sf-filter { margin-top: var(--s-2); }
.sf-filter__value { flex: 1; min-width: 0; }

/* ============================================================
   Fase 8c-5, Task 2: la cola larga sin estilos inline (2026-09-12)
   ============================================================ */

/* ---- Paginación (no existía como componente estilado; main.css la tenía a
   display:none — esta regla la reactiva donde ya se usaba la clase) ---- */
.pagination { display:flex; align-items:center; gap:var(--s-2); justify-content:center; margin-top:var(--s-4); }
.pagination__link { display:inline-flex; align-items:center; height:var(--h-ctl); padding:0 var(--s-3);
  border:1px solid var(--border); border-radius:var(--r-ctl); background:var(--bg-surface);
  color:var(--text-2); font-size:var(--fs-12); text-decoration:none; }
.pagination__link:hover { background:var(--bg-hover); color:var(--text); }
.pagination__link[aria-current="page"] { background:var(--accent-soft); border-color:var(--accent); color:var(--accent); }
.pagination__link[aria-disabled="true"] { opacity:.5; pointer-events:none; }

/* ---- Tarjeta de cifra (estadísticas de biblioteca, panel de feedback) ---- */
.stat-card { text-align:center; padding:var(--s-3); background:var(--bg-surface);
  border:1px solid var(--border); border-radius:var(--r-panel); }
.stat-card__value { font-size:var(--fs-20); font-weight:600; line-height:var(--lh-tight); }
.stat-card__label { font-size:var(--fs-11); color:var(--text-3); text-transform:uppercase; }

/* ---- Modificadores de estado que sustituyen a los style= condicionales de PHP ---- */
.is-inactive { opacity:.5; }
.is-disabled { opacity:.5; pointer-events:none; }

/* ---- alert-warning (main.css) no traía fondo propio; las dos vistas que la usan
   (auth/login.php, emails/cola.php) necesitaban un aviso ambar de verdad ---- */
.alert-warning { background:var(--warning-soft); color:var(--warning); }

/* ---- Paridad de [readonly] para .form-control heredado (admin/tecnicos-propios/form.php) ---- */
.form-control[readonly] { background:var(--bg-subtle); cursor:not-allowed; }

/* ---- Diagrama de estados del manual: movido aquí desde el <style> de la vista.
   var(--bg-primary) no existía (intención: acento) -> var(--accent). ---- */
.manual-diagram-wrapper {
  position: relative; overflow: hidden; background: var(--bg-subtle); border: 1px solid var(--border);
  border-radius: var(--r-panel); margin: var(--s-2) 0; height: 500px; cursor: grab;
  user-select: none; -webkit-user-select: none;
}
.manual-diagram-wrapper:active { cursor: grabbing; }
.manual-diagram-wrapper .mermaid { display: inline-block; transform-origin: 0 0; transition: none; }
.manual-diagram-wrapper .mermaid svg { display: block; }
.diagram-controls { position: absolute; top: var(--s-2); right: var(--s-2); display: flex; gap: var(--s-1); z-index: 10; }
.diagram-controls button {
  width: var(--h-row); height: var(--h-row); border: 1px solid var(--border); border-radius: var(--r-panel);
  background: var(--accent); color: var(--text-inverse); font-size: var(--fs-16); cursor: pointer;
  display: flex; align-items: center; justify-content: center; line-height: 1;
  box-shadow: var(--shadow-menu);
}
.diagram-controls button:hover { background: var(--accent-hover); }
.diagram-zoom-label { font-size: var(--fs-11); color: var(--text-3); align-self: center; padding: 0 var(--s-1); min-width: 36px; text-align: center; }
.diagram-hint {
  position: absolute; bottom: var(--s-2); left: 50%; transform: translateX(-50%); font-size: var(--fs-11);
  color: var(--text-inverse); background: var(--accent); padding: 2px var(--s-2); border-radius: var(--r-ctl);
  border: 1px solid var(--border); opacity: 0.8; pointer-events: none; z-index: 10;
}

/* ---- Utilidades genéricas (ronda 3, Task 2 de la 8c-5) ---- */
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Tarjeta/sección de ancho limitado: formularios cortos que no deben estirarse a todo el
   ancho de la pantalla (CLAUDE.md general, densidad de formularios). 700px no es ningún
   token existente — no hay ninguno de anchura para este caso — así que es un valor en
   crudo a propósito, documentado en el informe de la Task 2. */
.section--narrow { max-width: 700px; }

/* ============================================================
   Fase 8c-5, Task 3: catálogos sin estilos inline (2026-09-12)
   Las diez vistas de views/catalogos/** llevaban 190 estilos inline y tres bloques
   <style> con la misma intención (densidad de fila) y tres densidades distintas. La
   densidad la da .datatable--compact; de aquí solo es nuevo lo que la tabla compacta no
   cubría: los controles heredados que viven DENTRO de la fila, la celda de dos renglones
   y las piezas propias del catálogo (miniatura, diálogo de imagen, banda de pendiente).
   ============================================================ */

/* Grupo de dos o tres .btn-sm dentro de una celda (o de un pie de tabla).
   Sustituye a los <div style="display:flex;gap:2px"> de todas las filas de catálogo. */
.cell-actions { display: flex; align-items: center; gap: var(--s-1); }
.cell-actions form { display: inline-flex; margin: 0; }
.cell-actions .form-control-sm { min-width: 0; }

/* Celda de dos renglones (la gama y su ruta «categoría > subcategoría», el modelo y su
   selector de gama): .datatable td es nowrap con recorte, y aquí el segundo tiene que caber. */
.datatable td.cell-stack { white-space: normal; overflow: visible; }
.cell-stack__sub { font-size: var(--fs-11); color: var(--text-3); }

/* Los controles heredados de main.css (.btn-sm, .form-control-sm) traen su propio padding
   y no caben en una fila de 28px: esto es lo que hacían a mano los tres <style> de las vistas. */
.datatable--compact .btn-sm { min-height: 0; padding: 0 var(--s-1); font-size: var(--fs-11); line-height: var(--lh-tight); }
.datatable--compact .form-control-sm { height: var(--h-ctl-sm); padding: 0 var(--s-1); font-size: var(--fs-12); }

/* Botón de texto destructivo: antes era style="color:var(--danger)" repetido en seis filas. */
.btn-danger-text { color: var(--danger); }

/* Anchuras de control que no tienen token propio (no hay ninguno de anchura para un
   buscador de cabecera); w-auto deja al <select> encogerse a su contenido. */
.w-auto { width: auto; }
.w-busqueda { width: 180px; }

/* Pie de una tabla con paginación en cliente (el catálogo de gamas). */
.tabla-pie {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-2);
  padding: var(--s-2) var(--s-3); border-top: 1px solid var(--border);
  font-size: var(--fs-12); color: var(--text-3);
}

/* Miniatura de la imagen de una gama: es el botón que abre el diálogo. */
.gama-thumb {
  display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px;
  padding: 0; border: 1px solid var(--border); border-radius: var(--r-ctl);
  background: var(--bg-subtle); color: var(--text-3); cursor: pointer;
}
.gama-thumb:hover { border-color: var(--accent); color: var(--accent); }
.gama-thumb img { width: 30px; height: 30px; object-fit: contain; border-radius: var(--r-ctl); }

/* Diálogo de la imagen de una gama (antes un div a mano con position:fixed;z-index:9999). */
.gama-imagen { text-align: center; margin-bottom: var(--s-3); }
.gama-imagen__vista {
  max-width: 100%; max-height: 320px; object-fit: contain;
  border: 1px solid var(--border); border-radius: var(--r-ctl);
}
.gama-imagen__vacia {
  display: flex; align-items: center; justify-content: center; height: 120px; margin: 0;
  border: 1px dashed var(--border); border-radius: var(--r-ctl);
  background: var(--bg-subtle); color: var(--text-3); font-size: var(--fs-13);
}
.gama-imagen__subir { display: flex; align-items: center; gap: var(--s-2); margin: 0; }
.gama-imagen__subir .form-control { flex: 1; min-width: 0; }

/* Estados de fila del catálogo. El amarillo de «pendiente de revisión» estaba escrito
   cuatro veces a mano en dos vistas: aquí es --warning / --warning-soft, una sola vez. */
.datatable tbody tr.is-pendiente td { background: var(--warning-soft); }
.datatable tbody tr.is-pendiente td:first-child { box-shadow: inset 3px 0 0 var(--warning); }
.datatable tbody tr.is-aprobado td { background: var(--success-soft); }
.datatable tbody tr.is-editando td, .datatable tbody tr.is-nueva td { background: var(--bg-subtle); }

/* Selector de gama que catalogos.js preselecciona con la sugerencia del servidor. */
.is-sugerido { border-color: var(--warning); }
.gama-sugerencia { margin-top: 2px; font-size: var(--fs-11); color: var(--warning); }

/* ============================================================
   Fase 8c-5, Task 4: biblioteca sin estilos inline (2026-09-12)
   Las cuatro vistas de views/biblioteca/** llevaban 197 estilos inline y tres bloques
   <style>. La densidad de tabla la da .datatable--compact y las cifras .stat-card (Task 2);
   de aquí solo es nuevo lo que el sistema no cubría: el árbol de asociaciones de producto,
   el panel de detalle del documento y la lista de documentos asociados.

   Los `var()` heredados de main.css (--space-*, --gray-*, --font-size-*, --primary,
   --border-color-light, --text-muted…) NO existen en tokens.css: aquí van traducidos a
   roles del sistema. El 500px de alto máximo de los paneles con scroll y el 26px/18px de
   sangrado del árbol son valores en crudo a propósito: no hay token de anchura ni de
   sangrado para este caso.
   ============================================================ */

/* ---- Rejillas de dos columnas de la biblioteca ---- */
.biblio-cols { display: grid; grid-template-columns: minmax(0, 1fr) var(--w-filter-panel);
  gap: var(--s-3); margin-top: var(--s-3); align-items: start; }
.biblio-cols--pares { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.biblio-cols--sextos { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.biblio-sticky { position: sticky; top: var(--s-4); }
.biblio-scroll { max-height: 500px; overflow-y: auto; }
.biblio-barra { display: flex; align-items: center; gap: var(--s-2); }
.biblio-barra--entre { justify-content: space-between; }
.biblio-barra form { display: flex; align-items: center; gap: var(--s-2); margin: 0; }

/* ---- Panel de detalle de un documento (lo pinta biblioteca.js) ---- */
.doc-detalle { padding: var(--s-3); border-bottom: 1px solid var(--border); }
.doc-detalle__titulo { font-weight: 600; font-size: var(--fs-13); margin-bottom: var(--s-1); }
.doc-detalle__meta { display: grid; grid-template-columns: 1fr 1fr; gap: 2px var(--s-3);
  font-size: var(--fs-11); color: var(--text-2); }
.doc-detalle__acciones { display: flex; gap: var(--s-1); margin-top: var(--s-2); }
.doc-detalle__vacio { padding: var(--s-5); text-align: center; color: var(--text-3); font-size: var(--fs-12); }

/* ---- «Asociado a»: agrupado por nivel, con un punto de color por familia de nivel.
   El original mapeaba siete colores heredados (--info, --primary, --secondary, --gray-600…);
   tokens.css tiene cuatro roles, así que los siete niveles caen en cuatro familias. ---- */
.doc-assoc { padding: var(--s-3); font-size: var(--fs-12); }
.doc-assoc__titulo { font-size: var(--fs-11); text-transform: uppercase; letter-spacing: .5px;
  font-weight: 600; color: var(--text-3); margin-bottom: var(--s-1); }
.doc-assoc__grupo { margin-bottom: var(--s-1); }
.doc-assoc__nivel { font-size: var(--fs-11); color: var(--text-3); }
.doc-assoc__item { display: flex; align-items: center; gap: var(--s-1); padding: 2px 0; }
.doc-assoc__punto { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; background: var(--text-3); }
.doc-assoc__punto--producto { background: var(--accent); }
.doc-assoc__punto--gama { background: var(--success); }
.doc-assoc__punto--intervencion { background: var(--warning); }
.doc-assoc__punto--tipo { background: var(--danger); }
.doc-assoc__vacio { color: var(--text-3); }
.doc-assoc__error { color: var(--danger); }

/* ---- Lista de documentos asociados (panel derecho del formulario) ---- */
.doc-item { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-1) var(--s-2);
  border-bottom: 1px solid var(--border); border-radius: var(--r-ctl);
  font-size: var(--fs-12); cursor: pointer; }
.doc-item:hover { background: var(--bg-hover); }
.doc-item.is-activo { background: var(--bg-selected); }
.doc-item__icono { display: inline-flex; flex-shrink: 0; color: var(--text-3); }
.doc-item__nombre { flex: 1; min-width: 0; }
.doc-item__accion { font-size: var(--fs-11); color: var(--accent); text-decoration: none; }
.doc-item__accion--mudo { color: var(--text-3); }
.doc-item__vacio, .doc-item__cargando { padding: var(--s-2) 0; font-size: var(--fs-12); color: var(--text-3); }
.doc-item__cargando[hidden] { display: none; }

/* ---- Árbol de asociaciones de producto. La navegación va por data-tree-* (biblioteca.js):
   ningún selector de aquí participa en abrir/cerrar, solo pinta. ---- */
.tree { font-size: var(--fs-12); }
.tree-node { margin-bottom: 2px; }
.tree-node--cat { margin-bottom: var(--s-1); border: 1px solid var(--border); border-radius: var(--r-panel); }
.tree-node[hidden], .tree-children[hidden] { display: none; }
.tree-row { display: flex; align-items: center; gap: var(--s-1); padding: var(--s-1) var(--s-2); cursor: pointer; }
.tree-row--hoja { cursor: default; padding: 0 var(--s-2); }
.tree-arrow { width: 14px; flex-shrink: 0; font-size: var(--fs-11); color: var(--text-3); }
.tree-label { flex: 1; margin: 0; cursor: pointer; font-size: var(--fs-12); }
.tree-label--cat { font-weight: 600; }
.tree-label--sub { font-weight: 500; }
.tree-label--modelo { font-size: var(--fs-11); color: var(--text-2); }
.tree-children { padding: 0 var(--s-2) var(--s-1) 26px; }
.tree-children .tree-children { padding: 0 0 0 18px; }
.tree-docs { padding: 0; border: 0; background: none; font-size: var(--fs-11);
  color: var(--accent); cursor: pointer; white-space: nowrap; }
.tree-docs:hover { text-decoration: underline; }
.tree-node.is-resaltado { background: var(--accent-soft); border-radius: var(--r-ctl);
  outline: 2px solid var(--accent); outline-offset: -1px; }
.tree-todos { display: flex; align-items: center; gap: var(--s-1); margin-bottom: var(--s-2);
  padding: var(--s-1) var(--s-2); background: var(--bg-subtle); border-radius: var(--r-panel);
  font-weight: 600; font-size: var(--fs-12); }

/* ---- Formulario de documento: lo que estaba en el <style> de la vista ---- */
.doc-form-section { margin-bottom: var(--s-3); }
.doc-form-section:last-child { margin-bottom: 0; }
.doc-form-divider { border: 0; border-top: 1px solid var(--border); margin: var(--s-3) 0; }
.doc-form-row { display: grid; gap: var(--s-2); margin-bottom: var(--s-2); }
.doc-form-row:last-child { margin-bottom: 0; }
.doc-form-row--clasificacion { grid-template-columns: minmax(0, 1fr) 90px minmax(0, 1fr) 65px; }
.doc-form-row--idioma { grid-template-columns: 140px 50px 90px; }
.doc-form-row--fuente { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); max-width: 600px; }
.doc-form-row--metadatos { grid-template-columns: 90px 90px minmax(0, 1fr); }
.doc-form-row .form-group { margin-bottom: 0; }
.doc-form-row .form-label { font-size: var(--fs-11); color: var(--text-2); margin-bottom: 2px; }
.doc-form-row .form-control { font-size: var(--fs-12); padding: 6px var(--s-2); }
.doc-form-row textarea.form-control { min-height: 0; }
.doc-section-label { font-size: var(--fs-11); text-transform: uppercase; letter-spacing: .8px;
  font-weight: 600; color: var(--text-3); margin-bottom: var(--s-2); padding-left: 2px; }
.doc-form-nombre .form-label { font-size: var(--fs-11); color: var(--text-2); margin-bottom: 2px; }
.doc-form-nombre .form-control { font-size: var(--fs-14); font-weight: 500; padding: var(--s-2) 10px; }
.doc-form-mono { font-family: var(--font-mono); text-transform: uppercase; }
.doc-form-mono--libre { font-family: var(--font-mono); text-transform: none; }
.doc-form-centrado { text-align: center; }
.doc-form-pct { position: relative; }
.doc-form-pct .form-control { padding-right: var(--s-5); text-align: center; }
.doc-form-pct__signo { position: absolute; right: var(--s-2); top: 50%; transform: translateY(-50%);
  font-size: var(--fs-11); color: var(--text-3); pointer-events: none; }
.doc-source-card { padding: var(--s-4); border: 1px dashed var(--border-strong);
  border-radius: var(--r-panel); background: var(--bg-subtle); transition: border-color .15s ease; }
.doc-source-card:focus-within { border-color: var(--accent); border-style: solid; }
.doc-source-card .form-label { font-size: var(--fs-11); font-weight: 600; color: var(--text-2); margin-bottom: var(--s-2); }
.doc-source-card .form-text { font-size: var(--fs-11); margin-top: var(--s-1); color: var(--text-3); }

/* ---- Ficha de documento (show) y estadísticas ---- */
.doc-descripcion { margin-top: var(--s-3); padding-top: var(--s-3); border-top: 1px solid var(--border); }
.doc-descripcion__label { margin-bottom: var(--s-2); }
.detail-item--ancho { grid-column: span 2; }
.doc-badges { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.ratio { font-weight: 600; }
.ratio--alto { color: var(--success); }
.ratio--medio { color: var(--warning); }
.ratio--bajo { color: var(--danger); }
.tag-codigo { padding: 1px var(--s-1); border-radius: var(--r-ctl);
  background: var(--bg-hover); color: var(--text-2); font-size: var(--fs-11); }
.stat-card__value--acento { color: var(--accent); }
.stat-card__value--exito { color: var(--success); }
.stat-card__value--mudo { color: var(--text-3); }
.doc-bloque { margin-bottom: var(--s-4); }
.card-body--sin-relleno { padding: 0; }
.datatable tbody tr[data-biblio-doc] { cursor: pointer; }

/* ============================================================
   Fase 8c-5, Task 5: el panel de feedback sin estilo propio (2026-09-12)
   views/feedback/dashboard.php llevaba 89 estilos inline, 35 hex, un bloque <style> que forzaba
   seis propiedades con la marca de prioridad y z-index mágicos (10001, 99999, 100000) fuera de la
   escala --z-*, y tres variables que NO EXISTEN en tokens.css (--border-light,
   --bg-primary, --color-primary): el navegador tiraba esas declaraciones. Aquí van a
   --border, --bg-surface y --accent, que son las que existen.

   ESTE BLOQUE ES LA ÚNICA FUENTE DEL COLOR POR ESTADO DEL FEEDBACK. La vista emite
   `.fb-card--{estado}` a partir del valor del enum y feedback-dashboard.js cambia esa
   clase sin nombrar ningún estado; nadie más conoce el color.
   ============================================================ */

/* ---- Cifras de cabecera ---- */
.fb-stats { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-bottom: var(--s-3); }
.fb-stats .stat-card { flex: 1; min-width: 90px; }
.stat-card__value--peligro { color: var(--danger); }
.stat-card__value--aviso { color: var(--warning); }

/* ---- Barra de filtros: densa y en rejilla (CLAUDE.md general, densidad de formularios) ---- */
.fb-filtros { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s-2) var(--s-3); align-items: end; padding: var(--s-2) var(--s-3); margin-bottom: var(--s-3); }
.fb-filtros__casillas { display: flex; flex-direction: column; gap: var(--s-1); }
.fb-filtros__acciones { display: flex; gap: var(--s-2); }

/* ---- Rejilla de tarjetas ---- */
.fb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--s-2); }
.fb-card { padding: var(--s-2); border-left: 3px solid var(--border); cursor: pointer;
  transition: box-shadow .15s ease; }
.fb-card:hover { box-shadow: var(--shadow-menu); }

/* El mapa estado → color, una sola vez y en un solo sitio. */
.fb-card--nuevo { border-left-color: var(--danger); }
.fb-card--en_progreso { border-left-color: var(--accent); }
.fb-card--resuelto { border-left-color: var(--success); }
.fb-card--no_es_bug { border-left-color: var(--text-3); }
.fb-card--cancelado { border-left-color: var(--text-3); }
.fb-card--descartado { border-left-color: var(--text-3); }

/* El estado, escrito y no solo teñido: el filete tiene tres colores para seis estados.
   Sigue saliendo de la clase de la tarjeta, así que el mapa estado → color no se duplica. */
.fb-card__estado { background: var(--neutral-soft); color: var(--text-2); border-color: var(--border); }
.fb-card--nuevo .fb-card__estado { background: var(--danger-soft); color: var(--danger); border-color: var(--danger-soft); }
.fb-card--en_progreso .fb-card__estado { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-soft); }
.fb-card--resuelto .fb-card__estado { background: var(--success-soft); color: var(--success); border-color: var(--success-soft); }

.fb-card__cab { display: flex; align-items: center; gap: var(--s-1); margin-bottom: var(--s-1); }
.fb-card__badges { display: inline-flex; flex-wrap: wrap; gap: var(--s-1); }
.fb-card__id { margin-left: auto; font-size: var(--fs-11); color: var(--text-3); }
.fb-card__mensaje { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; margin: 0 0 var(--s-1); font-size: var(--fs-12); color: var(--text);
  line-height: var(--lh-text); }
.fb-card__thumb { display: block; width: 100%; max-height: 80px; object-fit: cover;
  border: 1px solid var(--border); border-radius: var(--r-ctl); margin-bottom: var(--s-1); }
.fb-card__pie { display: flex; align-items: center; gap: var(--s-2);
  font-size: var(--fs-11); color: var(--text-3); }
.fb-card__autor { max-width: 100px; }
.fb-card__sa { color: var(--accent); }
.fb-card__com { margin-left: auto; }

/* ---- Detalle: un <dialog> del sistema pegado al borde derecho (era un position:fixed a
   mano clonado de un <template> y metido en el <body> con z-index 99999). `display:flex`
   solo con [open]: puesto en el propio .fb-detalle, un <dialog> cerrado se vería. ---- */
.fb-detalle { width: 520px; max-width: 90vw; height: 100%; max-height: 100%;
  margin: 0 0 0 auto; border-radius: 0; border-left: 3px solid var(--accent); }
.fb-detalle[open] { display: flex; flex-direction: column; }
.fb-detalle__meta { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: 0 0 var(--s-3);
  font-size: var(--fs-12); color: var(--text-2); }
.fb-detalle__meta a { color: var(--accent); }
.fb-detalle__controles { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
  gap: var(--s-2); align-items: end; margin-bottom: var(--s-3); }
.fb-detalle__acciones { display: flex; align-items: end; gap: var(--s-2); }
.fb-detalle__mensaje { white-space: pre-wrap; max-height: 300px; overflow-y: auto;
  margin: 0 0 var(--s-3); padding: var(--s-2); background: var(--bg-subtle);
  border-radius: var(--r-panel); font-size: var(--fs-13); color: var(--text); line-height: var(--lh-text); }
.fb-detalle__captura { margin: 0 0 var(--s-3); }
.fb-detalle__img { display: block; width: 100%; border: 1px solid var(--border);
  border-radius: var(--r-panel); cursor: zoom-in; }
.fb-detalle__pie { margin-top: var(--s-1); font-size: var(--fs-11); color: var(--text-3); }
.fb-detalle__contexto { margin-bottom: var(--s-3); font-size: var(--fs-12); }
.fb-detalle__resumen { cursor: pointer; font-weight: 500; color: var(--text-2); }
.fb-detalle__pre { max-height: 200px; overflow: auto; margin: var(--s-1) 0 0; padding: var(--s-2);
  background: var(--bg-subtle); border-radius: var(--r-panel);
  font-family: var(--font-mono); font-size: var(--fs-11); }

/* ---- Comentarios del detalle (los pinta feedback-dashboard.js por DOM) ---- */
.fb-coms { padding-top: var(--s-2); border-top: 1px solid var(--border); }
.fb-coms__titulo { margin: 0 0 var(--s-1); font-size: var(--fs-12); font-weight: 600; color: var(--text-2); }
.fb-coms__nuevo { display: flex; gap: var(--s-2); margin-top: var(--s-2); }
.fb-com { padding: var(--s-1) 0; border-bottom: 1px solid var(--border); font-size: var(--fs-12); }
.fb-com__meta { display: flex; gap: var(--s-2); }
.fb-com__fecha { color: var(--text-3); }

/* ---- Ampliar la captura: sustituye a la clase de imagen ampliada y sus seis propiedades
   forzadas. El cuerpo hace de marco para que una captura clara no se pierda en el blanco. ---- */
.fb-captura .dialog__body { padding: var(--s-2); background: var(--bg-subtle); }
.fb-captura__img { display: block; max-width: 100%; max-height: 75vh; margin: 0 auto; }

/* ---- Peso medio de texto (ola final de la 8c-5) ------------------------------------------
   `.text-medium` la usaban diez vistas —dieciséis celdas de `.datatable`— y NINGUNA hoja la
   definía, así que el navegador la descartaba y el nombre no significaba nada. Se define aquí,
   con el mismo 500 con el que el resto de la hoja escribe «medio», en vez de borrarla de las
   diez vistas: el nombre declara la intención y las celdas se escribieron esperándola. ---- */
.text-medium { font-weight: 500; }

/* ============================================================
   Piezas que main.js arma en cadenas (Fase 8c-7)
   Antes iban con style= incrustado dentro del HTML concatenado, y el hover se pintaba
   asignando item.onmouseover. Aquí el hover es :hover y el estilo es una clase.
   ============================================================ */

/* A) Insignias de un adjunto de la biblioteca: código de tipo y de idioma. */
.adjunto-meta { display: flex; gap: 3px; align-items: center; }
.adjunto-badge {
  padding: 1px var(--s-1); border-radius: var(--r-ctl);
  background: var(--neutral-soft); color: var(--text-2); font-size: var(--fs-11);
}

/* B) Resultado de la calculadora de códigos (Evio y cálculo simple). */
.calc-error { color: var(--warning); display: inline-flex; align-items: center; gap: var(--s-1); }
.calc-codes { display: flex; flex-direction: column; gap: var(--s-2); }
.calc-code-row { display: flex; align-items: center; gap: var(--s-3); }
.calc-code-label { min-width: 100px; font-weight: 500; }
.calc-code-value { font-size: var(--fs-20); font-weight: 600; color: var(--accent); font-family: var(--font-mono); }
.calc-simple-value { font-size: var(--fs-14); font-weight: 600; color: var(--accent); }

/* C) y E) Envoltorio y desplegable de los dos buscadores con sugerencias (modelos y SATs).
   El `display: none` se queda DENTRO de la clase a propósito: es estado, y el JavaScript lo
   conmuta con `dropdown.style.display = 'block' | 'none'`, que al ser estilo en línea gana
   sobre la clase. Con el atributo `hidden` no funcionaría: el reset lo declara forzado (la
   palabra clave que esta hoja tiene prohibida) y entonces `display = 'block'` no abriría nada. */
.suggest-wrap { position: relative; }
.suggest-wrap--inline { display: inline-block; min-width: 220px; }
.suggest-input--auto { width: auto; min-width: 220px; }
.suggest-dropdown {
  display: none; position: absolute; top: 100%; left: 0; right: 0;
  z-index: var(--z-menu); max-height: 280px; overflow-y: auto;
  background: var(--bg-surface); border: 1px solid var(--border); border-top: none;
  border-radius: 0 0 var(--r-panel) var(--r-panel); box-shadow: var(--shadow-menu);
}
.suggest-dropdown--wide { max-height: 300px; min-width: 320px; }
.suggest-empty { padding: var(--s-2) var(--s-3); font-size: var(--fs-12); color: var(--text-2); }
.suggest-create {
  padding: var(--s-2) var(--s-3); cursor: pointer; font-size: var(--fs-12); font-weight: 600;
  color: var(--accent); border-top: 1px solid var(--border); background: var(--bg-subtle);
}
.suggest-create:hover { background: var(--bg-hover); }

/* C) y E) Fila de un desplegable de sugerencias (modelos, SATs). El :hover sustituye a los
   item.onmouseover/onmouseout que pintaban el fondo a mano. */
.suggest-item {
  padding: var(--s-2) var(--s-3); cursor: pointer; font-size: var(--fs-12);
  border-bottom: 1px solid var(--border);
}
.suggest-item:hover { background: var(--bg-hover); }
.suggest-item--near { background: var(--success-soft); }
.suggest-item--near:hover { background: var(--bg-hover); }
.suggest-item--fuera { opacity: .4; }
.suggest-note { color: var(--text-2); font-size: var(--fs-11); }
.suggest-dist { font-size: var(--fs-11); font-weight: 600; color: var(--accent); }
.suggest-sedes { color: var(--text-2); font-size: var(--fs-11); }
.suggest-zona { font-size: var(--fs-11); color: var(--danger); font-weight: 600; }
.suggest-group {
  padding: var(--s-1) var(--s-3); font-size: var(--fs-11); font-weight: 600;
  text-transform: uppercase; border-bottom: 1px solid var(--border);
  color: var(--text-2); background: var(--bg-subtle);
}
.suggest-group--near { color: var(--success); background: var(--success-soft); }

/* D) Alta de un modelo sin salir del formulario. */
.modelo-inline-create {
  position: absolute; top: 100%; left: 0; right: 0; z-index: var(--z-menu);
  padding: var(--s-3); background: var(--bg-surface); border: 1px solid var(--accent);
  border-radius: 0 0 var(--r-panel) var(--r-panel); box-shadow: var(--shadow-menu);
}
.modelo-inline__titulo { font-size: var(--fs-12); font-weight: 600; margin-bottom: var(--s-2); color: var(--accent); }
.modelo-inline__grid { display: grid; grid-template-columns: 2fr 1fr; gap: var(--s-2); margin-bottom: var(--s-2); }
.modelo-inline__label { font-size: var(--fs-11); color: var(--text-2); }
.modelo-inline__acciones { display: flex; gap: var(--s-2); justify-content: flex-end; }

/* Modal de mejora de texto con IA (Fase 8c-7): tres tamaños que iban con style= dentro
   del HTML que ai-text-improver.js concatena. */
.ai-hint { font-size: var(--fs-12); }
.ai-edit-label { font-size: var(--fs-13); font-weight: 600; margin-bottom: var(--s-1); display: block; }
/* main.css fija `.ai-modal-footer{display:flex}`: sin este override con más especificidad
   (mismo patrón que .tabs__panel[hidden] más arriba), el atributo `hidden` no lo ocultaría
   — el UA-stylesheet [hidden]{display:none} pierde siempre contra una regla de autor,
   por especificidad no compite: el footer se vería vacío con borde y relleno al abrir el modal. */
.ai-modal-footer[hidden] { display: none; }

/* ==========================================================================
   Diagrama de carriles del manual (.flujo)
   Lo pinta views/manual/_flujo.php con los datos de Models\ManualFlujo. El SVG no lleva
   ni un color: la caja toma el de su estado (los mismos grupos que `pill()`, regla 14) y
   la flecha el de quien la dispara. Trazo discontinuo en la marcha atrás y punteado en lo
   automático, para que el diagrama se siga leyendo sin distinguir los colores.
   ========================================================================== */
.flujo { margin: var(--s-4) 0; }
.flujo__lienzo { display: block; width: 100%; height: auto; overflow: visible; }

.flujo__carril { fill: var(--bg-surface); stroke: var(--border); stroke-width: 1; }
.flujo__carril--alterno { fill: var(--bg-subtle); }
.flujo__carril-titulo {
    fill: var(--text-2); font-family: var(--font); font-size: var(--fs-11);
    letter-spacing: .04em; text-transform: uppercase;
}

.flujo__caja { fill: var(--bg-app); stroke: var(--border-strong); stroke-width: 1.5; }
.flujo__texto { fill: var(--text); font-family: var(--font); font-size: var(--fs-11); }
.flujo__nodo--active .flujo__caja    { stroke: var(--accent);  fill: var(--accent-soft); }
.flujo__nodo--attention .flujo__caja { stroke: var(--warning); fill: var(--warning-soft); }
.flujo__nodo--done .flujo__caja      { stroke: var(--success); fill: var(--success-soft); }
.flujo__nodo--blocked .flujo__caja   { stroke: var(--danger);  fill: var(--danger-soft); }

.flujo__arista { fill: none; stroke-width: 1.6; }
.flujo__arista--redge   { stroke: var(--accent); }
.flujo__arista--sat     { stroke: var(--brand); }
.flujo__arista--tecnico { stroke: var(--success); }
.flujo__arista--auto    { stroke: var(--text-3); stroke-dasharray: 2 3; }
.flujo__arista--vuelta  { stroke-dasharray: 5 4; }
.flujo__punta--redge   { fill: var(--accent); }
.flujo__punta--sat     { fill: var(--brand); }
.flujo__punta--tecnico { fill: var(--success); }
.flujo__punta--auto    { fill: var(--text-3); }

.flujo__pie { color: var(--text-2); font-size: var(--fs-12); margin-top: var(--s-3); }
.flujo__leyenda { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-bottom: var(--s-2); }
.flujo__clave { align-items: center; color: var(--text); display: inline-flex; gap: var(--s-1); }
.flujo__clave::before {
    border-top: 2px solid var(--text-3); content: ''; display: inline-block; width: 18px;
}
.flujo__clave--redge::before   { border-top-color: var(--accent); }
.flujo__clave--sat::before     { border-top-color: var(--brand); }
.flujo__clave--tecnico::before { border-top-color: var(--success); }
.flujo__clave--auto::before    { border-top-style: dotted; }
.flujo__clave--vuelta::before  { border-top-style: dashed; }

/* El tronco compartido y sus empalmes: la línea que recoge las salidas que valen desde muchos
   estados (cancelar, rechazar). Sin ella habría doce líneas largas superpuestas. */
.flujo__tronco { fill: none; stroke-width: 1.6; }
.flujo__arista--rama { stroke-dasharray: none; }
.flujo__empalme--redge   { fill: var(--accent); }
.flujo__empalme--sat     { fill: var(--brand); }
.flujo__empalme--tecnico { fill: var(--success); }
.flujo__empalme--auto    { fill: var(--text-3); }

/* Los carriles se veían «bien» en el marcado y no se veían en pantalla: las dos bandas eran
   bg-surface y bg-subtle, dos blancos casi iguales, sobre una página ya clara. La columna la
   dibujan la banda alterna, la cabecera y la línea de separación — las tres, no una sola. */
.flujo__carril { fill: var(--bg-surface); stroke: none; }
.flujo__carril--alterno { fill: var(--bg-hover); }
.flujo__cabecera { fill: var(--neutral-soft); }
.flujo__division { stroke: var(--border-strong); stroke-width: 1; }
.flujo__carril-titulo { fill: var(--text); }
