/* =========================================================================
   NÚCLEO — base.css
   Reset, tipografía, foco y utilidades mínimas.

   ---------------------------------------------------------------------
   POR QUÉ TODO CUELGA DE .ds-app
   ---------------------------------------------------------------------
   Este fichero se carga en templates/base.html, o sea en LAS 30 SUB-APPS A
   LA VEZ, y la migración es app por app durante meses. Un reset global aquí
   —`* { margin: 0 }`, `body { font-family: ... }`— cambiaría de golpe la
   tipografía y el interlineado de todo Lydent el día del deploy, que es
   exactamente el big-bang que el plan (§2.3) se negó a hacer con el color.

   Así que el núcleo es INERTE POR CONSTRUCCIÓN:

     - fonts.css declara familias y no pinta nada.
     - base.css solo actúa dentro de un contenedor con clase `ds-app`.
     - components.css solo usa clases `ds-*`, que no existen en el repo.

   Una pantalla se migra poniendo `ds-app` en su contenedor y cambiando su
   marcado a `ds-*`. Hasta ese día no le pasa nada. El día que no quede una
   sola app sin migrar, el scope se puede subir a :root de una tacada.

   ---------------------------------------------------------------------
   POR QUÉ EL RESET VA EN :where() Y LA DECISIÓN NO
   ---------------------------------------------------------------------
   Lo descubrió el piloto de Stock: el botón primario salía índigo sobre
   índigo —texto invisible— y el destructivo salía negro. Los dos por lo
   mismo: `.ds-app a` y `.ds-app button` tienen especificidad (0,1,1) y le
   ganaban a `.ds-btn--primary` y `.ds-btn--danger`, que son (0,1,0). O sea que
   el RESET le ganaba a la DECISIÓN, que es exactamente al revés de como tiene
   que ser.

   Regla del sistema, y no es cosmética:

     - Lo que es un DEFECTO (reset, herencia de fuente, color de enlace) se
       escribe `.ds-app :where(x)`. `:where()` aporta cero, así que la regla
       pesa lo que su elemento y cualquier clase de componente la gana.
     - Lo que es una DECISIÓN (la caja raíz, el anillo de foco, la densidad)
       conserva `.ds-app`, porque sí debe ganar.

   Si un componente necesita `!important` para imponerse a este fichero, el
   bug está aquí: falta un `:where()`.

   ---------------------------------------------------------------------
   PUNTOS DE RUPTURA (decisión cerrada 2026-08-03, §6 del plan)
   ---------------------------------------------------------------------
   640px y 1024px, los de DESIGN.md §6 — no los 576/768/992 de Bootstrap que
   arrastran las apps viejas. Se puede decidir así justamente porque el
   núcleo está scopeado: los dos juegos no conviven nunca en el mismo
   elemento, y cada app cambia de juego el día que se migra entera.
   CSS no admite var() dentro de @media, de modo que los dos números están
   escritos a mano en cada consulta. Son los únicos literales permitidos
   fuera de tokens.css y están todos en este fichero y en archetypes/.
   ========================================================================= */

/* =========================================================================
   1. RAÍZ DE PANTALLA MIGRADA
   ========================================================================= */

.ds-app {
  box-sizing: border-box;
  font-family: var(--font-ui);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
  color: var(--n-800);
  background: var(--n-25);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.ds-app *,
.ds-app *::before,
.ds-app *::after {
  box-sizing: inherit;
}

/* =========================================================================
   2. RESET
   Solo lo que estorba. No se resetea lo que el navegador ya hace bien.
   ========================================================================= */

.ds-app :where(h1), .ds-app :where(h2), .ds-app :where(h3),
.ds-app :where(h4), .ds-app :where(h5), .ds-app :where(h6),
.ds-app :where(p), .ds-app :where(figure), .ds-app :where(blockquote),
.ds-app :where(dl), .ds-app :where(dd) {
  margin: 0;
}

.ds-app :where(ul), .ds-app :where(ol) {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Listas de prosa (ayuda, estados vacíos): recuperan sus marcas. */
.ds-app .ds-prose :where(ul, ol) {
  padding-left: var(--s-7);
  list-style: revert;
}

.ds-app :where(img),
.ds-app :where(svg),
.ds-app :where(video) {
  display: block;
  max-width: 100%;
}

.ds-app :where(table) {
  border-collapse: collapse;
  border-spacing: 0;
}

/* `hidden` tiene que ocultar, y punto. El navegador lo aplica con
   `display: none` de hoja de usuario, que pierde contra CUALQUIER regla de
   clase: un componente que se declara `display: flex` —el aviso de campo, el
   panel del buscador, una fila de rejilla— aparece en pantalla aunque la
   plantilla lo haya marcado `hidden`. Ocurrió de verdad: el formulario del
   trabajo enseñaba «esta ficha no existe todavía» sobre una ficha que existe.

   Va con `.ds-app` y no en `:where()` porque es una DECISIÓN y debe ganar:
   (0,2,0) le basta a cualquier componente de una sola clase. Esto es también
   lo que evita que `.ds-hidden` —la única `!important` del sistema— tenga que
   usarse cada vez que algo se oculta desde la plantilla.

   La primera mitad del selector cubre el caso en que lo oculto es la RAÍZ: un
   overlay compartido que lleva su propio `ds-app` porque lo incluyen tanto
   pantallas migradas como pantallas que aún no lo están (el modal de WhatsApp
   de Comercial). Medido en el portal (2026-08-07): hoy no hace falta, porque
   la hoja de usuario de los navegadores actuales declara `[hidden]` con
   `!important` y gana ella sola. Se pone igual por dos razones: el sistema no
   debe depender de la especificidad de una hoja que no controlamos, y sin esta
   mitad la regla de al lado diría que una raíz migrada no puede ocultarse a sí
   misma, que es justo lo que hace un modal. */
.ds-app[hidden],
.ds-app [hidden] { display: none; }

/* Los controles heredan la tipografía en lugar de la del sistema operativo. */
.ds-app :where(button),
.ds-app :where(input),
.ds-app :where(select),
.ds-app :where(textarea) {
  font: inherit;
  color: inherit;
}

/* Y los campos de texto tienen el aspecto del sistema aunque nadie les ponga
   `.ds-input`. Descubierto al retirar el CSS viejo de Stock: media docena de
   pantallas dejaban sus <input> y <select> sin clase, y sin esta regla salían
   con el aspecto nativo del sistema operativo dentro de una pantalla ya
   migrada — que es peor que antes, no mejor.

   Va en `:where()` porque es un DEFECTO: `.ds-input`, `.ds-cellinput` y
   `.ds-search__input` lo ganan sin esfuerzo. Los <button> NO entran aquí a
   propósito: un botón sin clase debe seguir viéndose neutro, o toda la app
   parecería llena de acciones primarias. */
.ds-app :where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"])),
.ds-app :where(select),
.ds-app :where(textarea) {
  min-height: var(--ctl-h);
  padding: var(--s-3) var(--s-4);
  border: var(--bd-strong);
  border-radius: var(--r-sm);
  background: var(--n-0);
  font-size: var(--fs-base);
}

.ds-app :where(textarea) {
  resize: vertical;
}

/* Las casillas y radios se tiñen del acento. Sin esto salen del azul por
   defecto del navegador, que es justamente el `#0d6efd` que DESIGN.md §1
   prohíbe — y se cuela sin que nadie lo escriba.

   Esto es DECISIÓN, no defecto, así que va con `.ds-app` y no en `:where()`:
   `static/css/base.css` tiñe TODAS las casillas del verde antiguo con
   `input[type="checkbox"]`, que pesa lo mismo (0,1,1) y se carga después que
   el núcleo. Con `:where()` ganaba el verde y la casilla de una pantalla
   migrada seguía siendo del color viejo. */
.ds-app input[type="checkbox"],
.ds-app input[type="radio"] {
  accent-color: var(--accent);
}

/* =========================================================================
   3. TIPOGRAFÍA
   La escala vive en tokens.css; aquí solo se le pone nombre.
   ========================================================================= */

.ds-app :where(h1), .ds-h1 {
  font-size: var(--fs-xl);
  line-height: var(--lh-tight);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.ds-app :where(h2), .ds-h2 {
  font-size: var(--fs-lg);
  line-height: var(--lh-tight);
  font-weight: 600;
}

.ds-app :where(h3), .ds-h3 {
  font-size: var(--fs-md);
  line-height: var(--lh-tight);
  font-weight: 600;
}

/* LA FIRMA (DESIGN.md §1). Cabecera de columna, rótulo de KPI, etiqueta de
   campo. Condensada, en mayúscula, con tracking y sobre regla de un píxel.
   Si esto se usa para texto corrido, el sistema deja de reconocerse. */
.ds-micro {
  font-family: var(--font-cond);
  font-size: var(--fs-micro);
  font-weight: 600;
  line-height: var(--lh-tight);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--n-500);
}

.ds-help {
  font-size: var(--fs-xs);
  line-height: var(--lh-base);
  color: var(--n-500);
}

.ds-muted { color: var(--n-500); }
.ds-strong { font-weight: 600; }

/* Numeración tabular. Obligatoria en toda columna numérica y todo KPI
   (DESIGN.md §4). Sin esto las columnas de importes bailan al escanear. */
.ds-num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Código, identificadores y volcados técnicos. Los importes NO van aquí:
   van en Sans con .ds-num, que ya alinea sin cambiar de voz. */
.ds-code {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
}

.ds-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* TEXTO GUARDADO CON SUS SALTOS DE LÍNEA (2026-08-09, con el modal de guiones
   de Comercial). Un guión de llamada, una plantilla de mensaje o una nota se
   escribieron con párrafos, y esos saltos son CONTENIDO: sin esto llegan al
   navegador y se colapsan en un párrafo corrido.

   Sube al núcleo porque el repositorio ya lo tenía escrito seis veces con
   cinco nombres distintos —`taller-mensaje`, `r2-texto`, `r2-log`, dos en
   `claudia_voz.css` y el `.ds-msgprev__texto` del propio núcleo—, que es el
   criterio de §2. El ancho NO va aquí: cada sitio acota el suyo (`ch` en un
   panel, el ancho del modal en un modal). */
.ds-prewrap {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* =========================================================================
   4. ENLACES
   ========================================================================= */

.ds-app :where(a) {
  color: var(--accent);
  text-decoration: none;
}

.ds-app :where(a):hover {
  text-decoration: underline;
}

/* En prosa el enlace va subrayado siempre: dentro de un párrafo el color
   solo no basta para distinguirlo (DESIGN.md §5: nada se comunica solo por
   color). En una tabla o una barra de acciones el contexto ya lo dice. */
.ds-app .ds-prose :where(a) {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* =========================================================================
   5. FOCO
   Siempre visible. Nunca outline:none sin sustituto (DESIGN.md §8).
   ========================================================================= */

.ds-app :focus-visible {
  outline: 2px solid transparent; /* superviviente en modo alto contraste */
  outline-offset: 2px;
  box-shadow: var(--focus);
}

/* El ratón no necesita el anillo; el teclado sí. */
.ds-app :focus:not(:focus-visible) {
  outline: none;
}

.ds-app ::selection {
  background: var(--accent-bg);
  color: var(--n-900);
}

/* =========================================================================
   6. DENSIDAD RESPONSIVA
   El modo compacto es de escritorio. Por debajo de 1024px se relaja solo
   (DESIGN.md §6) y por debajo de 640px se garantiza el objetivo táctil de
   44px, aunque el usuario tenga fijado el modo compacto.
   ========================================================================= */

/* Se nombra también al contenedor que baja la densidad a mano. Sin esa
   segunda mitad la relajación no llega: `[data-density="compact"]` se declara
   sobre el elemento anidado, y una declaración propia gana siempre a un valor
   heredado del ancestro por muy específico que sea el ancestro. O sea que una
   tabla de detalle marcada como compacta se quedaría en celdas de 32px y
   controles de 28 justo en la tableta, que es donde el dedo necesita 44.
   (Analytics, 2026-08-06: es su tabla de detalle mensual.) */
@media (max-width: 1024px) {
  .ds-app,
  .ds-app [data-density="compact"] {
    --row-h:      40px;
    --cell-pad-y: var(--s-4);
    --cell-pad-x: var(--s-6);
    --cell-fs:    var(--fs-base);
    --ctl-h:      34px;
  }
}

@media (pointer: coarse) {
  .ds-app,
  .ds-app [data-density="compact"] {
    --row-h: 44px;
    --ctl-h: 44px;
  }
}

/* =========================================================================
   7. UTILIDADES MÍNIMAS
   Deliberadamente pocas. Esto no es un framework de utilidades: si algo se
   repite, es un componente y su sitio es components.css.
   ========================================================================= */

/* Pila vertical con separación uniforme. */
.ds-stack       { display: flex; flex-direction: column; gap: var(--s-6); }
.ds-stack--tight{ gap: var(--s-4); }
.ds-stack--wide { gap: var(--s-9); }

/* Fila que envuelve. Para barras de acciones y grupos de metadatos. */
.ds-cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-5);
}

.ds-cluster--tight { gap: var(--s-3); }

/* Empuja lo que venga después al extremo contrario de una .ds-cluster. */
.ds-push { margin-left: auto; }

/* Único sitio donde se permite scroll horizontal, y siempre explícito. */
.ds-scroll-x {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* Contenido accesible solo para lector de pantalla. */
.ds-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Única `!important` del sistema, y es deliberada: DESIGN.md §2 lo prohíbe
   porque se usa para ganar peleas de cascada que no deberían existir, pero
   ganar la pelea ES el trabajo de esta regla —oculta un componente que se
   declara a sí mismo `display: flex`—. Si aparece una segunda, es un bug. */
.ds-hidden { display: none !important; }

/* Alineación. Son las dos únicas utilidades de posición del sistema y existen
   porque una celda de texto que va a la derecha no es numérica —para eso está
   `.ds-num`, que además tabula—. Cualquier otra alineación es cosa del
   componente, no de una clase suelta. */
.ds-right  { text-align: right; }
.ds-center { text-align: center; }
