/* Nodal Environment — hoja de componentes · Paso 4B, 23/09/2026
 *
 * Los 16 grupos de la lámina aprobada `06_COMPONENTES_4B_APROBADOS.png`. La
 * lámina es una imagen generada y sus colores salen corridos (carbón más
 * oscuro y verdoso, primario más saturado; no son los reales): de ahí se toma la
 * forma, la jerarquía y los estados, nunca el píxel. Los colores los ponen
 * los tokens de `nodal-tokens.css`, que esta hoja da por cargada antes.
 *
 * Reglas duras de este archivo (ver ESPEC-COMPONENTES.md):
 *   - Cero literales de color: solo var(--nodal-…), rgba(var(--nodal-acento-rgb), α),
 *     color-mix(in srgb, var(--nodal-…) N%, transparent|var(--nodal-…)),
 *     transparent y currentColor. Única excepción: --nodal-sombra, abajo.
 *   - Cero !important. Cero degradados. Cero imagen de datos incrustada.
 *   - Solo selectores con clase .nodal-…; un elemento pelado (th, a, li…)
 *     siempre como descendiente de una clase nodal.
 *   - font: inherit en controles; no se declara font-family.
 *   - Estados forzados SOLO para la galería: junto a todo :hover va
 *     [data-nodal-estado="hover"], y junto a todo :active va
 *     [data-nodal-estado="presionado"].
 *   - No se da color a p ni li por clase (body.theme-light p/li de
 *     styles.css pesa (0,1,1) y gana): los textos van en span/div.
 */

:root {
  /* La lámina Nodal redondea los controles; el contrato visual anterior de
     INSIDE (docs/estilos/CONTRATO_VISUAL_INSIDE.md) pedía bordes cuadrados.
     Mientras Gustavo no confirme cuál manda para Nodal, se sigue la lámina
     aprobada y se deja el radio en un token: si lo quiere cuadrado, se
     cambia solo este valor. */
  --nodal-radio: 6px;
  --nodal-radio-sm: 4px;

  /* Única excepción a "cero literales de color": la sombra de elevación de
     modal y menú es negro con alfa y no tiene equivalente entre los ocho
     aprobados (REGISTRO_MAESTRO_DECISIONES_APROBADAS.md). Se declara una
     sola vez, aquí, como token local. */
  --nodal-sombra: rgba(0, 0, 0, 0.32);
}

/* ---------------------------------------------------------
   Utilidad
   --------------------------------------------------------- */
.nodal-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ===========================================================
   1 · BOTONES
   =========================================================== */
.nodal-boton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  height: 40px;
  padding: 0 1.25rem;
  border: 1px solid transparent;
  border-radius: var(--nodal-radio);
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  background: transparent;
  color: inherit;
  transition: color 120ms ease, background-color 120ms ease, border-color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-boton {
    transition: none;
  }
}

@media (max-width: 700px) {
  .nodal-boton {
    min-height: 44px;
  }
}

/* Muchos botones nacen con `hidden` y el guion los muestra cuando tocan. La
   forma les pone display: inline-flex y le gana a [hidden] del navegador: el
   botón salía aunque el guion lo hubiera ocultado. 02/10/2026: «Guardar
   conexiones» en «Nueva tarea» y «Eliminar tarea» al conectar una tarea. */
.nodal-boton[hidden],
.nodal-accion[hidden] {
  display: none;
}

.nodal-boton:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

/* -- primario: relleno con texto encima (regla A.1 del barrido) -- */
.nodal-boton--primario {
  background: var(--nodal-primario);
  color: var(--nodal-sobre-primario);
}

.nodal-boton--primario:hover,
.nodal-boton--primario[data-nodal-estado="hover"] {
  background: var(--nodal-primario-hover);
  color: var(--nodal-sobre-primario-hover);
}

.nodal-boton--primario:active,
.nodal-boton--primario[data-nodal-estado="presionado"] {
  background: var(--nodal-primario-presionado);
  color: var(--nodal-sobre-primario);
}

/* -- secundario: fondo/texto ya resuelven solos con los tokens de tema.
      El borde es la única pieza que la lámina pinta distinta por tema
      (acento en claro, sin borde en oscuro) sin que exista un token
      semántico para esa combinación exacta; no se puede resolver con un
      solo var() sin tocar nodal-tokens.css (fuera de este encargo) ni
      escribir un selector de tema (prohibido en este archivo). Se deja el
      borde en acento en los dos temas —visible y consistente, nunca
      literal— y se reporta como ajuste pendiente para quien mantenga
      nodal-tokens.css. */
.nodal-boton--secundario {
  background: var(--nodal-secundario);
  color: var(--nodal-sobre-secundario);
  border-color: var(--nodal-acento);
}

.nodal-boton--secundario:hover,
.nodal-boton--secundario[data-nodal-estado="hover"] {
  background: var(--nodal-terciario-hover);
  border-color: var(--nodal-acento-hover);
  color: var(--nodal-sobre-secundario-hover);
}

.nodal-boton--secundario:active,
.nodal-boton--secundario[data-nodal-estado="presionado"] {
  border-color: var(--nodal-primario-presionado);
}

/* -- terciario: relleno bajo, para acciones de menor jerarquía -- */
.nodal-boton--terciario {
  background: var(--nodal-terciario);
  color: var(--nodal-sobre-terciario);
}

.nodal-boton--terciario:hover,
.nodal-boton--terciario[data-nodal-estado="hover"] {
  background: var(--nodal-terciario-hover);
}

.nodal-boton--terciario:active,
.nodal-boton--terciario[data-nodal-estado="presionado"] {
  background: var(--nodal-terciario);
  color: var(--nodal-acento);
}

/* -- texto: sin fondo, color acento -- */
.nodal-boton--texto {
  padding: 0 0.25rem;
  color: var(--nodal-acento);
}

.nodal-boton--texto:hover,
.nodal-boton--texto[data-nodal-estado="hover"] {
  color: var(--nodal-acento-hover);
  text-decoration: underline;
}

.nodal-boton--texto:active,
.nodal-boton--texto[data-nodal-estado="presionado"] {
  color: var(--nodal-primario-presionado);
}

/* -- peligro: relleno INVERTIDO (fondo del color del texto, letra del color
      del panel). Decisión del 25/09/2026, delegada por Cristian: los estados
      se quedan dentro de los ocho y lo invertido es error o peligro; nada de
      rojo. En oscuro sale claro sobre carbón y en claro carbón sobre blanco,
      y no se confunde con el primario, que es teal. -- */
.nodal-boton--peligro {
  background: var(--nodal-texto);
  color: var(--nodal-superficie-panel);
  border-color: var(--nodal-texto);
}

.nodal-boton--peligro:hover,
.nodal-boton--peligro[data-nodal-estado="hover"] {
  background: var(--nodal-texto-2);
  border-color: var(--nodal-texto-2);
}

.nodal-boton--peligro:active,
.nodal-boton--peligro[data-nodal-estado="presionado"] {
  background: var(--nodal-atenuado);
  border-color: var(--nodal-atenuado);
}

.nodal-boton__icono {
  width: 16px;
  height: 16px;
  flex: none;
}

.nodal-boton--compacto {
  height: 32px;
  padding: 0 0.875rem;
  font-size: 0.8125rem;
}

@media (max-width: 700px) {
  .nodal-boton--compacto {
    min-height: 44px;
  }
}

.nodal-boton:disabled,
.nodal-boton[aria-disabled="true"] {
  background: var(--nodal-deshabilitado);
  color: var(--nodal-sobre-deshabilitado);
  border-color: transparent;
  cursor: not-allowed;
}

.nodal-boton--texto:disabled,
.nodal-boton--texto[aria-disabled="true"] {
  background: transparent;
  text-decoration: none;
}

/* ===========================================================
   2 · INPUTS Y SELECTORES
   =========================================================== */
/* Piel y forma, como el resto de los componentes (27/09/2026). Los campos del
   producto ya tienen su medida —los creadores densos van a 32 px, las
   rejillas de filtros ponen su ancho, las etiquetas viven en un <label> con
   su propio hueco—: con nodal-control--propio el control toma la piel —el
   borde, el radio, el fondo, la letra, el placeholder, el hover, el foco, el
   deshabilitado— y conserva su alto, su ancho, su relleno y su letra; con
   nodal-campo--propio el contenedor conserva su disposición y la etiqueta su
   tamaño. Las reglas de forma pesan lo mismo que antes. */
.nodal-campo:where(:not(.nodal-campo--propio)) {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.nodal-campo__etiqueta {
  color: var(--nodal-texto-2);
}

:where(.nodal-campo:not(.nodal-campo--propio)) .nodal-campo__etiqueta {
  font-size: 0.8125rem;
  font-weight: 600;
}

.nodal-campo__caja {
  position: relative;
  display: flex;
}

.nodal-control {
  border: 1px solid var(--nodal-borde-control);
  border-radius: var(--nodal-radio);
  background: var(--nodal-superficie-elevada);
  color: var(--nodal-texto);
  transition: border-color 120ms ease, background-color 120ms ease, color 120ms ease;
}

.nodal-control:where(:not(.nodal-control--propio)) {
  /* La altura sale de un token para que una pantalla pueda pedir el control
     grande sin duplicar el componente: el login de la lamina 05 lo usa a 52 px,
     porque ahi son dos campos y el foco es el formulario. Sin el token, 40. */
  height: var(--nodal-control-alto, 40px);
  width: 100%;
  padding: 0 0.75rem;
  font: inherit;
  font-size: 0.875rem;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-control {
    transition: none;
  }
}

/* El piso táctil vale también para el --propio: no es la medida del campo,
   es que se pueda tocar con el dedo. */
@media (max-width: 700px) {
  .nodal-control {
    min-height: 44px;
  }
}

.nodal-control::placeholder {
  color: var(--nodal-atenuado);
}

.nodal-control:hover,
.nodal-control[data-nodal-estado="hover"] {
  border-color: var(--nodal-acento);
}

.nodal-control:focus-visible {
  outline: none;
  border-color: var(--nodal-acento);
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-control:disabled {
  background: var(--nodal-deshabilitado);
  color: var(--nodal-sobre-deshabilitado);
  cursor: not-allowed;
}

/* Un valor que se lee y no se edita · 05/10/2026.
 *
 * La lámina no dibuja un campo de solo lectura. Cristian eligió la «A» sobre
 * una muestra con tres opciones en los dos temas (los recubrimientos «25 mm»
 * de vigas, que salen de los Valores estándar): la caja y el borde del campo,
 * el fondo hundido de la superficie y la letra del tema, 14,4:1 en los dos.
 * Así no se confunde con un campo que se escribe (elevado) ni con uno
 * apagado (gris, que en claro no llega a 4,5:1). No cambia al pasar el ratón:
 * no hay nada que hacer con él. Va sobre un <output> o un elemento que no
 * recibe foco. */
.nodal-control--lectura,
.nodal-control--lectura:hover {
  border-color: var(--nodal-borde-control);
  background: var(--nodal-superficie);
  color: var(--nodal-texto);
  cursor: default;
}

/* Un <textarea> con el control puesto · 25/09/2026.
 *
 * El control de la lámina es de UNA línea: fija `height` y pone el relleno solo
 * a los lados, porque la línea se centra sola en esa altura. Un <textarea> con
 * esas dos reglas queda aplastado a 40 px y con el texto pegado al borde de
 * arriba, y es lo que habría pasado al vestir la descripción del recurso
 * maestro. Aquí la altura la pone el contenido, con un mínimo de tres líneas,
 * y el relleno se reparte arriba y abajo.
 *
 * `resize: vertical` y no libre: a lo ancho el campo manda en la rejilla de su
 * formulario, y dejar que el usuario lo estire rompe la columna.
 *
 * Y NO lleva `min-height`, a propósito. El contrato del command center pone
 * `min-height: 44px !important` a todo control dentro de `main` en el módulo de
 * Presupuestos —un piso de área táctil— PERO SOLO BAJO 1181 px, que es donde
 * ese módulo cambia a su disposición de ☰. Medido el 25/09: a 1200 los campos
 * miden 40 con `min-height: auto`, y a 1180 miden 44. Está bien como está.
 *
 * Aun así, cualquier mínimo que se escriba aquí perdería en esa franja sin
 * ruido, porque va marcado; ganarle pediría otro `!important` y esta hoja tiene
 * contrato de no llevar ninguno. La altura inicial la dice el atributo `rows`
 * del propio <textarea>, que es donde corresponde y vale en los dos lados. */
textarea.nodal-control:where(:not(.nodal-control--propio)) {
  height: auto;
  padding: 0.5rem 0.75rem;
  line-height: 1.45;
  resize: vertical;
}

.nodal-control--con-icono {
  padding-left: 2.25rem;
}

.nodal-campo__icono {
  position: absolute;
  left: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  color: var(--nodal-atenuado);
  pointer-events: none;
}

/* La acción del campo —el ojo de una contraseña, un borrar— va DENTRO de la
   caja, pegada al borde derecho, como en la lámina 05. Se dibuja propia y no se
   usa la del navegador: el ojo nativo solo existe en Edge (::-ms-reveal), no se
   puede estilar, y donde existe saldrían dos. Por eso también se silencia. */
.nodal-campo__accion {
  position: absolute;
  right: 0.5rem;
  top: 50%;
  transform: translateY(-50%);
}

/* El texto no puede pasar por debajo de la acción. */
.nodal-control--con-accion {
  padding-right: 2.75rem;
}

.nodal-control::-ms-reveal,
.nodal-control::-ms-clear {
  display: none;
}

/* select propio: sin flecha de fábrica, se dibuja con bordes en ::after */
.nodal-selector {
  position: relative;
  display: flex;
}

.nodal-selector .nodal-control {
  appearance: none;
  cursor: pointer;
}

.nodal-selector .nodal-control:where(:not(.nodal-control--propio)) {
  padding-right: 2rem;
}

.nodal-selector::after {
  content: "";
  position: absolute;
  right: 0.9rem;
  top: 50%;
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--nodal-atenuado);
  border-bottom: 2px solid var(--nodal-atenuado);
  transform: translateY(-65%) rotate(45deg);
  pointer-events: none;
}

/* El campo con error, con los colores de la lámina 4B desde el 03/10/2026
   (Cristian, opción A, como los avisos): el borde en el rojo de la lámina
   (4,07:1 contra el campo en claro, 3,02:1 en oscuro; un borde pide 3:1) y el
   mensaje con su círculo rojo y «!» blanco. La letra del mensaje: en claro el
   rojo oscurecido de los avisos (#C90306, 6,00:1; el de la lámina tal cual
   daba 4,07:1), en oscuro la del tema, como en la lámina. Los colores son los
   del aviso (--nodal-aviso-error-*); no se agrega ninguno. El punto rojo que
   la lámina dibuja adentro del campo, solo en oscuro, no va. */
.nodal-campo--error .nodal-control {
  border-color: var(--nodal-aviso-error-icono);
  border-width: 2px;
}

.nodal-campo__mensaje {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8125rem;
  color: var(--nodal-aviso-error-texto);
}

.nodal-campo__mensaje::before {
  content: "!";
  display: grid;
  place-items: center;
  flex: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--nodal-aviso-error-icono);
  color: var(--nodal-aviso-error-glifo);
  font-size: 0.625rem;
  font-weight: 800;
  line-height: 1;
}

/* ===========================================================
   3 · DROPDOWNS DE PROYECTO / PRESUPUESTO
   =========================================================== */
.nodal-desplegable {
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: relative;
}

.nodal-desplegable__rotulo {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--nodal-atenuado);
}

.nodal-desplegable__disparador {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  height: 40px;
  padding: 0 0.75rem;
  border: 1px solid var(--nodal-borde-control);
  border-radius: var(--nodal-radio);
  background: var(--nodal-superficie-elevada);
  color: var(--nodal-texto);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-desplegable__disparador {
    transition: none;
  }
}

@media (max-width: 700px) {
  .nodal-desplegable__disparador {
    min-height: 44px;
  }
}

.nodal-desplegable__disparador:hover,
.nodal-desplegable__disparador[data-nodal-estado="hover"] {
  border-color: var(--nodal-acento);
}

.nodal-desplegable__disparador:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-desplegable__disparador::after {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--nodal-atenuado);
  border-bottom: 2px solid var(--nodal-atenuado);
  transform: translateY(-15%) rotate(45deg);
}

/* Piel y forma, como la ventana, la tarjeta y el aviso (26/09/2026). Los menús
   del producto ya saben dónde se abren —a la derecha del disparador, con su
   ancho y su alto máximo—: con --propio toman la piel y conservan eso. */
.nodal-menu:where(:not(.nodal-menu--propio)) {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  padding: 6px;
}

.nodal-menu {
  background: var(--nodal-superficie-panel);
  border: 1px solid var(--nodal-borde);
  border-radius: var(--nodal-radio);
  box-shadow: 0 12px 28px var(--nodal-sombra);
}

/* Los menús nacen ocultos y el guion los abre; la forma pone display: flex. */
.nodal-menu[hidden] {
  display: none;
}

/* La opción, en piel y forma (27/09/2026). Las listas del producto ya tienen
   su fila —el selector de proyecto de la cabecera es una rejilla de nombre y
   visto, más alta, con su propio visto dibujado en svg—: con --propia la
   opción toma la piel —sin borde, el radio, transparente, la letra, el hover
   y la elegida en el plano de selección con letra de acento— y conserva lo
   suyo, y el visto de ::after no se dibuja. Las reglas de forma pesan lo
   mismo que antes. */
.nodal-menu__opcion {
  border: none;
  border-radius: var(--nodal-radio-sm);
  background: transparent;
  color: var(--nodal-texto);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.nodal-menu__opcion:where(:not(.nodal-menu__opcion--propia)) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  height: 40px;
  padding: 0 0.625rem;
  font: inherit;
  font-size: 0.875rem;
  text-align: left;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-menu__opcion {
    transition: none;
  }
}

.nodal-menu__opcion:hover,
.nodal-menu__opcion[data-nodal-estado="hover"] {
  background: var(--nodal-seleccion);
}

.nodal-menu__opcion:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-menu__opcion[aria-selected="true"] {
  background: var(--nodal-seleccion);
  color: var(--nodal-acento);
}

.nodal-menu__opcion:where(:not(.nodal-menu__opcion--propia))[aria-selected="true"] {
  font-weight: 600;
}

.nodal-menu__opcion:where(:not(.nodal-menu__opcion--propia))[aria-selected="true"]::after {
  content: "";
  flex: none;
  width: 10px;
  height: 6px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg) translateY(-2px);
}

.nodal-menu__accion {
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px solid var(--nodal-borde-suave);
  height: auto;
  min-height: 40px;
  color: var(--nodal-acento);
  font-weight: 600;
}

.nodal-menu__accion:hover,
.nodal-menu__accion[data-nodal-estado="hover"] {
  background: var(--nodal-seleccion);
  color: var(--nodal-acento-hover);
}

/* Las dos piezas de un menú largo, como el de notificaciones (26/09/2026). La
   lámina no las dibuja: la cabecera es el rótulo del desplegable («Proyecto
   actual») metido dentro del panel, separado de lo de abajo por la misma línea
   suave que la acción; la lista es la parte que se desplaza, transparente para
   que se vea el panel. Solo piel: su forma es la de su pantalla. */
.nodal-menu__cabecera {
  border-bottom: 1px solid var(--nodal-borde-suave);
  background: transparent;
  color: var(--nodal-texto);
}

.nodal-menu__lista {
  background: transparent;
  color: var(--nodal-texto);
}

/* ===========================================================
   4 · TABS
   =========================================================== */
/* Piel y forma, como la ventana, la tarjeta, el aviso, el menú y la tabla
   (26/09/2026). Las pestañas del producto ya tienen su disposición (una
   columna, una barra con subrayado, pasos numerados): con --propio / --propia
   toman la piel —el contenedor hundido con su borde, la pestaña transparente
   con la letra atenuada, la elegida en el plano del panel— y conservan eso. */
.nodal-pestanas {
  border: 1px solid var(--nodal-borde);
  border-radius: var(--nodal-radio);
  background: var(--nodal-superficie);
}

.nodal-pestanas:where(:not(.nodal-pestanas--propio)) {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
}

.nodal-pestana {
  border: none;
  border-radius: var(--nodal-radio-sm);
  background: transparent;
  color: var(--nodal-atenuado);
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

.nodal-pestana:where(:not(.nodal-pestana--propia)) {
  height: 34px;
  padding: 0 1rem;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-pestana {
    transition: none;
  }
}

@media (max-width: 700px) {
  .nodal-pestana:where(:not(.nodal-pestana--propia)) {
    min-height: 44px;
  }
}

.nodal-pestana:hover,
.nodal-pestana[data-nodal-estado="hover"] {
  color: var(--nodal-texto);
}

.nodal-pestana:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

/* En oscuro la lámina distingue la pestaña activa con relleno acento, no
   solo con el cambio de plano; en claro la deja en el plano del panel. Antes
   se intentaba con .nodal-oscuro, que en la aplicación nadie pone porque el
   tema va por el body, y en oscuro la elegida quedaba #262E30 sobre #1F2527
   (26/09/2026): ahora lo dicen dos tokens de nodal-tokens.css. Un conmutador
   (filtros, vistas) marca la elegida con aria-pressed y se ve igual
   (30/09/2026). */
.nodal-pestana[aria-pressed="true"],
.nodal-pestana[aria-selected="true"] {
  background: var(--nodal-pestana-elegida-fondo);
  color: var(--nodal-pestana-elegida-texto);
}

/* ===========================================================
   5 · TARJETAS
   =========================================================== */
/* La piel de la tarjeta vale para las dos formas; la maquetación de la fila
   —icono, cuerpo, valor, chevron— solo para la tarjeta de la lámina.
   `--panel` es la tarjeta que ya es un panel —una cabecera con su
   contenido, un formulario, una rejilla—: toma la piel y deja la
   maquetación a su pantalla. El :where() pesa cero, así que la fila sigue
   pesando (0,1,0) como antes. 25/09/2026. */
.nodal-tarjeta:where(:not(.nodal-tarjeta--panel)) {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  padding: 16px;
}

.nodal-tarjeta {
  border: 1px solid var(--nodal-borde);
  border-radius: var(--nodal-radio);
  background: var(--nodal-superficie-panel);
  color: inherit;
  text-decoration: none;
  transition: border-color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-tarjeta {
    transition: none;
  }
}

a.nodal-tarjeta:hover,
a.nodal-tarjeta[data-nodal-estado="hover"] {
  border-color: var(--nodal-acento);
}

a.nodal-tarjeta:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-tarjeta__icono {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: var(--nodal-radio);
  background: var(--nodal-seleccion);
  color: var(--nodal-acento);
}

.nodal-tarjeta__icono svg {
  width: 20px;
  height: 20px;
}

.nodal-tarjeta__cuerpo {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.nodal-tarjeta__titulo {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--nodal-texto);
}

.nodal-tarjeta__meta {
  font-size: 0.8125rem;
  color: var(--nodal-atenuado);
}

/* Cuando el __meta es un <p>, en claro le gana `body.theme-light p` de
   styles.css, que pesa (0,1,2), y el texto sale al 76 % en vez del atenuado.
   Dentro de la tarjeta esta pesa (0,2,0). Medido el 25/09/2026 en las
   tarjetas de Herramientas. */
.nodal-tarjeta .nodal-tarjeta__meta {
  color: var(--nodal-atenuado);
}

.nodal-tarjeta__valor {
  margin-left: auto;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--nodal-texto);
  white-space: nowrap;
}

.nodal-tarjeta__ir {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--nodal-atenuado);
}

/* La cabecera de un panel: una línea, no una franja (05/10/2026). La lámina
   no dibuja títulos de sección; Cristian eligió esta, la «A · línea», sobre
   una muestra con la franja de marca de antes y una franja suave. Es solo la
   piel —fondo del panel, letra del tema, línea suave debajo, como la cabecera
   de las ventanas—: el alto, el relleno, el ícono y las mayúsculas los pone su
   pantalla. */
.nodal-tarjeta__cabecera {
  border-bottom: 1px solid var(--nodal-borde-suave);
  background: transparent;
  color: var(--nodal-texto);
}

/* ===========================================================
   6 · BADGES / ETIQUETAS
   =========================================================== */
.nodal-insignia {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 0.625rem;
  border: 1px solid;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--nodal-texto);
}

/* El marcador de la insignia de estado lleva el GLIFO de su estado (✓ i ! ×):
   los estados no se distinguen solo por color (25/09/2026). Va en ::before
   para que sirva también donde el guion reescribe el texto con textContent; si
   la insignia ya trae su glifo en SVG (__glifo), el marcador se calla. */
.nodal-insignia::before {
  content: "";
  flex: none;
  font-size: 0.75rem;
  font-weight: 800;
  line-height: 1;
}

.nodal-insignia:has(> .nodal-insignia__glifo)::before {
  content: none;
}

.nodal-insignia__glifo {
  flex: none;
  width: 12px;
  height: 12px;
}

/* Desde el 03/10/2026 las insignias de estado van «como los avisos», con los
   colores de la lámina 4B (Cristian, opción A): el círculo del color del
   estado con el glifo encima —blanco, salvo el «!» del aviso, en carbón sobre
   el ámbar, que además va en triángulo—, el borde fino del mismo color y la
   letra del tema. Ninguna va rellena: en claro el error se dice con la letra
   roja (#C90306, 6,00:1 sobre blanco). Los colores son los del aviso
   (--nodal-aviso-*, nodal-tokens.css); no se agrega ninguno. Hasta el 03/10
   iban dentro de los ocho y el error relleno, invertido. La insignia de dato
   (--dato, abajo) no cambia. */
.nodal-insignia:is(.nodal-insignia--ok, .nodal-insignia--info, .nodal-insignia--aviso, .nodal-insignia--error)::before {
  display: grid;
  place-items: center;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  font-size: 0.625rem;
}

.nodal-insignia--ok {
  border-color: color-mix(in srgb, var(--nodal-aviso-ok-icono) 45%, transparent);
}

.nodal-insignia--ok::before {
  content: "✓";
  background: var(--nodal-aviso-ok-icono);
  color: var(--nodal-aviso-ok-glifo);
}

.nodal-insignia--info {
  border-color: color-mix(in srgb, var(--nodal-aviso-info-icono) 45%, transparent);
}

.nodal-insignia--info::before {
  content: "i";
  background: var(--nodal-aviso-info-icono);
  color: var(--nodal-aviso-info-glifo);
}

.nodal-insignia--aviso {
  border-color: color-mix(in srgb, var(--nodal-aviso-aviso-icono) 45%, transparent);
}

/* En triángulo, como el aviso; el glifo baja para quedar en su centro. Con
   las dos clases pesa lo mismo que la regla común de arriba (el :is() toma la
   más pesada de su lista) y, por venir después, su border-radius gana. */
.nodal-insignia.nodal-insignia--aviso::before {
  content: "!";
  box-sizing: border-box;
  padding-top: 0.15em;
  border-radius: 0;
  clip-path: polygon(50% 4%, 100% 94%, 0 94%);
  background: var(--nodal-aviso-aviso-icono);
  color: var(--nodal-aviso-aviso-glifo);
}

.nodal-insignia--error {
  border-color: color-mix(in srgb, var(--nodal-aviso-error-icono) 45%, transparent);
  color: var(--nodal-aviso-error-texto);
}

.nodal-insignia--error::before {
  content: "×";
  background: var(--nodal-aviso-error-icono);
  color: var(--nodal-aviso-error-glifo);
}

/* Dato (28/09/2026): el color no es un estado de la interfaz sino un dato del
   producto —la prioridad de una tarea, un tipo de bloqueo, el «Rezagado» del
   Resumen—. Gustavo pidió de vuelta los colores del Planificador porque
   identifican; Cristian: el color vuelve porque significa, la forma es la de
   Nodal, «para que no inventes nuevos diseños». Así que es la insignia de la
   lámina tal cual (ESPEC-COMPONENTES, grupo 6: «píldora con borde, texto del
   tema y un ::before de 10 px en el color»), con el color del dato en vez
   del token de estado: el borde al 45 % como ok/info/aviso, la letra del
   tema, sin fondo. La pantalla solo pone --nodal-insignia-color. nowrap: con
   alto fijo, un texto partido se saldría de la caja. */
.nodal-insignia--dato {
  --nodal-insignia-tono: var(--nodal-insignia-color, var(--nodal-estado-info));
  border-color: color-mix(in srgb, var(--nodal-insignia-tono) 45%, transparent);
  white-space: nowrap;
}

/* El marcador es el punto de 10 px de la lámina, en el color del dato. Si la
   etiqueta trae su símbolo como __glifo (⊗ ◆ ▲, el avatar), el punto se calla
   por la regla :has() de arriba. */
.nodal-insignia--dato::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--nodal-insignia-tono);
}

/* ===========================================================
   7 · TABLAS
   =========================================================== */
.nodal-tabla-contenedor {
  overflow-x: auto;
  border: 1px solid var(--nodal-borde);
  border-radius: var(--nodal-radio);
}

/* Piel y forma, como la ventana, la tarjeta, el aviso y el menú (26/09/2026).
   Las tablas del producto ya tienen sus columnas, sus rellenos y su letra: con
   --propia toman la piel (el encabezado elevado en gris, la línea suave entre
   filas, el tono al pasar el ratón) y conservan eso. */
.nodal-tabla {
  color: var(--nodal-texto);
}

.nodal-tabla:where(:not(.nodal-tabla--propia)) {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}

/* El encabezado va en su propia banda (26/09/2026): con la superficie
   elevada, en claro salía blanco como las filas, y la lámina lo dibuja niebla. */
.nodal-tabla th {
  background: var(--nodal-superficie-encabezado);
  color: var(--nodal-atenuado);
}

.nodal-tabla:where(:not(.nodal-tabla--propia)) th {
  padding: 0.75rem;
  font-size: 0.75rem;
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
}

.nodal-tabla td {
  border-top: 1px solid var(--nodal-borde-suave);
}

.nodal-tabla:where(:not(.nodal-tabla--propia)) td {
  padding: 0.75rem;
}

.nodal-tabla tbody tr {
  transition: background-color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-tabla tbody tr {
    transition: none;
  }
}

.nodal-tabla tbody tr:hover,
.nodal-tabla tbody tr[data-nodal-estado="hover"] {
  background: var(--nodal-seleccion);
}

.nodal-tabla__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  /* una cifra no se parte: «₡1 187 500» en dos líneas no se lee como número */
  white-space: nowrap;
}

th.nodal-tabla__num {
  text-align: right;
}

.nodal-tabla__check {
  width: 1px;
  padding-right: 0;
}

.nodal-tabla__check input {
  appearance: none;
  width: 16px;
  height: 16px;
  margin: 0;
  border: 1px solid var(--nodal-borde-control);
  border-radius: 3px;
  background: var(--nodal-superficie-elevada);
  cursor: pointer;
}

.nodal-tabla__check input:checked {
  background: var(--nodal-acento);
  border-color: var(--nodal-acento);
}

.nodal-tabla__check input:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-tabla__menu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--nodal-radio-sm);
  background: transparent;
  color: var(--nodal-atenuado);
  font: inherit;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-tabla__menu {
    transition: none;
  }
}

.nodal-tabla__menu:hover,
.nodal-tabla__menu[data-nodal-estado="hover"] {
  background: var(--nodal-seleccion);
  color: var(--nodal-texto);
}

.nodal-tabla__menu:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-tabla__menu svg {
  width: 16px;
  height: 16px;
}

/* ===========================================================
   8 · MODALES
   =========================================================== */
/* Como la tarjeta, el velo y la ventana se separan en piel y forma. La piel
   vale para todos; la forma de la lámina —el velo fijo a pantalla completa
   que centra, la ventana en columna de 480 px— solo para los que no son
   --propio / --propia. Las ventanas del producto ya tienen su tamaño y su
   sitio (asistentes de 760 px, cajones laterales): toman la piel y conservan
   su maquetación. El :where() pesa cero. 25/09/2026. */
.nodal-velo:where(:not(.nodal-velo--propio)) {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.nodal-velo {
  /* El carbón es el mismo en los dos temas: --nodal-carbon vive en :root sin
     redefinirse por body.theme-light, así que no hace falta un selector de
     tema para que el velo salga igual en claro y en oscuro. */
  background: color-mix(in srgb, var(--nodal-carbon) 60%, transparent);
}

/* El velo de un <dialog> nativo es su ::backdrop. Solo para la ventana de la
   lámina: en las --propia el velo es parte de cómo funciona la ventana —las
   de reuniones del Planificador no oscurecen nada porque se arrastran
   mientras se mira el plan—, y cada una conserva el suyo. */
.nodal-modal:where(:not(.nodal-modal--propia))::backdrop {
  background: color-mix(in srgb, var(--nodal-carbon) 60%, transparent);
}

.nodal-modal:where(:not(.nodal-modal--propia)) {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 480px;
  max-height: calc(100vh - 32px);
}

.nodal-modal {
  border: 1px solid var(--nodal-borde);
  border-radius: var(--nodal-radio);
  background: var(--nodal-superficie-panel);
  color: var(--nodal-texto);
  box-shadow: 0 24px 48px var(--nodal-sombra);
}

/* El cajón: la misma piel en un panel pegado a un borde de la ventana del
   navegador y de alto completo (la ficha de un recurso, su formulario). El
   borde solo va del lado de dentro y no lleva radio, porque las esquinas
   tocan el borde del navegador; la sombra se proyecta hacia dentro, en
   horizontal. Va siempre con --propia: el ancho y el sitio son de la
   pantalla. Decidido por Cristian el 25/09/2026: la lámina no dibuja cajones. */
.nodal-modal.nodal-modal--cajon {
  border-width: 0;
  border-inline-start-width: 1px;
  border-radius: 0;
  box-shadow: -24px 0 48px var(--nodal-sombra);
}

/* Cabecera, cuerpo y acciones, en piel y forma como la ventana (30/09/2026):
   las del producto ya tienen su disposición (un rótulo sobre el título, un
   cuerpo en rejilla de dos columnas), así que con --propia / --propio /
   --propias toman solo la piel —la línea suave que separa la cabecera y las
   acciones, la letra del cuerpo— y conservan su forma. */
.nodal-modal__cabecera:where(:not(.nodal-modal__cabecera--propia)) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 16px 20px;
}

.nodal-modal__cabecera {
  border-bottom: 1px solid var(--nodal-borde-suave);
}

.nodal-modal__titulo {
  font-size: 1.0625rem;
  font-weight: 700;
  color: var(--nodal-texto);
}

.nodal-modal__cerrar {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: var(--nodal-radio-sm);
  background: transparent;
  color: var(--nodal-atenuado);
  font: inherit;
  /* Las ventanas del producto cierran con un «×» de texto, no con el icono:
     a 1,25 rem el glifo ocupa lo que el icono de 18 px de la lámina. */
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-modal__cerrar {
    transition: none;
  }
}

.nodal-modal__cerrar:hover,
.nodal-modal__cerrar[data-nodal-estado="hover"] {
  background: var(--nodal-seleccion);
  color: var(--nodal-texto);
}

.nodal-modal__cerrar:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-modal__cerrar svg {
  width: 18px;
  height: 18px;
}

/* Sobre un encabezado de color (teal o verde profundo, Contrato V5) el «×»
   toma el blanco del título. El hover aclara con blanco translúcido, al 10 %:
   al 16 % el blanco quedaba en 4,23:1 sobre teal. El foco es blanco: el anillo
   de acento no se ve sobre teal (30/09/2026, decidido por Cristian). */
.nodal-modal__cerrar--sobre-color {
  color: var(--nodal-blanco);
}

.nodal-modal__cerrar--sobre-color:hover,
.nodal-modal__cerrar--sobre-color[data-nodal-estado="hover"] {
  background: color-mix(in srgb, var(--nodal-blanco) 10%, transparent);
  color: var(--nodal-blanco);
}

.nodal-modal__cerrar--sobre-color:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--nodal-blanco) 60%, transparent);
}

.nodal-modal__cuerpo:where(:not(.nodal-modal__cuerpo--propio)) {
  padding: 20px;
  overflow-y: auto;
  font-size: 0.9375rem;
}

.nodal-modal__cuerpo {
  color: var(--nodal-texto-2);
}

.nodal-modal__acciones:where(:not(.nodal-modal__acciones--propias)) {
  display: flex;
  justify-content: flex-end;
  gap: 0.625rem;
  padding: 16px 20px;
}

.nodal-modal__acciones {
  border-top: 1px solid var(--nodal-borde-suave);
}

/* ===========================================================
   9 · TOOLTIP / HELP
   =========================================================== */
.nodal-ayuda {
  position: relative;
  display: inline-flex;
}

.nodal-ayuda__disparador {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: none;
  border-radius: 50%;
  background: var(--nodal-atenuado);
  color: var(--nodal-superficie);
  font: inherit;
  font-size: 0.75rem;
  font-weight: 700;
  cursor: pointer;
}

.nodal-ayuda__disparador:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-ayuda__globo {
  border: 1px solid var(--nodal-borde);
  border-radius: var(--nodal-radio-sm);
  background: var(--nodal-superficie-panel);
  color: var(--nodal-texto-2);
  box-shadow: 0 12px 24px var(--nodal-sombra);
}

/* El globo --propio toma solo la piel y conserva su lugar, su tamaño, su letra
   y su manera de aparecer (05/10/2026). Hace falta en las plantillas: la «i»
   de cada sección queda a 30 px del borde derecho de un panel que recorta lo
   que se sale, y el globo centrado hacia arriba de 240 px se cortaba por la
   derecha (y por arriba en la plantilla genérica); el de crossties lo ubica un
   guion con coordenadas fijas. Mismo reparto que nodal-control--propio. */
.nodal-ayuda__globo:where(:not(.nodal-ayuda__globo--propio)) {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 30;
  width: max-content;
  max-width: 240px;
  padding: 10px 12px;
  font-size: 0.8rem;
  line-height: 1.4;
  opacity: 0;
  visibility: hidden;
  transition: opacity 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-ayuda__globo {
    transition: none;
  }
}

.nodal-ayuda__globo:where(:not(.nodal-ayuda__globo--propio))::before {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  width: 9px;
  height: 9px;
  margin-left: -5px;
  margin-top: -5px;
  background: var(--nodal-superficie-panel);
  border-right: 1px solid var(--nodal-borde);
  border-bottom: 1px solid var(--nodal-borde);
  transform: rotate(45deg);
}

.nodal-ayuda__disparador:hover + .nodal-ayuda__globo,
.nodal-ayuda:hover .nodal-ayuda__globo,
.nodal-ayuda:focus-within .nodal-ayuda__globo,
.nodal-ayuda__globo[data-nodal-abierto] {
  opacity: 1;
  visibility: visible;
}

/* ===========================================================
   10 · BREADCRUMB
   =========================================================== */
.nodal-miga {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  font-size: 0.8125rem;
}

.nodal-miga__volver {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: 0.5rem;
  padding: 4px 2px;
  border: none;
  background: transparent;
  color: var(--nodal-atenuado);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-miga__volver {
    transition: none;
  }
}

.nodal-miga .nodal-miga__volver:hover,
.nodal-miga__volver[data-nodal-estado="hover"] {
  color: var(--nodal-acento);
}

.nodal-miga__volver:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-miga__volver svg {
  width: 14px;
  height: 14px;
}

.nodal-miga__inicio {
  display: inline-flex;
  color: var(--nodal-atenuado);
}

.nodal-miga__inicio svg {
  width: 15px;
  height: 15px;
}

.nodal-miga__tramo {
  color: var(--nodal-atenuado);
  text-decoration: none;
  transition: color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-miga__tramo {
    transition: none;
  }
}

.nodal-miga .nodal-miga__tramo:hover,
.nodal-miga__tramo[data-nodal-estado="hover"] {
  color: var(--nodal-acento);
}

.nodal-miga__tramo:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-miga__sep {
  color: var(--nodal-atenuado);
}

.nodal-miga__actual {
  font-weight: 700;
  color: var(--nodal-texto);
}

/* ===========================================================
   11 · SELECTORES EN HEADER
   =========================================================== */
/* Piel y forma, como la ventana, la tarjeta, el aviso, el menú, la tabla, la
   pestaña y los pasos (27/09/2026). El selector de proyecto de la cabecera ya
   tiene su disposición —icono, rótulo y valor en dos líneas, flecha—, y la
   pone en otro orden: el icono y la flecha van al lado del texto, no dentro
   del valor. Con --propio toma la piel —el borde, el fondo, la letra, el
   rótulo atenuado, el icono en acento, la flecha atenuada— y conserva lo
   suyo. Para su icono y su flecha están __icono y __chevron, que son solo
   piel. Las reglas de forma pesan lo mismo que antes. */
.nodal-selector-cabecera {
  border: 1px solid var(--nodal-borde-control);
  border-radius: var(--nodal-radio);
  background: transparent;
  color: var(--nodal-texto);
  cursor: pointer;
  transition: border-color 120ms ease, background-color 120ms ease;
}

.nodal-selector-cabecera:where(:not(.nodal-selector-cabecera--propio)) {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-height: 36px;
  padding: 4px 10px;
  font: inherit;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-selector-cabecera {
    transition: none;
  }
}

.nodal-selector-cabecera:hover,
.nodal-selector-cabecera[data-nodal-estado="hover"] {
  border-color: var(--nodal-acento);
}

.nodal-selector-cabecera:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-selector-cabecera__rotulo {
  color: var(--nodal-atenuado);
}

:where(.nodal-selector-cabecera:not(.nodal-selector-cabecera--propio)) .nodal-selector-cabecera__rotulo {
  font-size: 0.7rem;
}

:where(.nodal-selector-cabecera:not(.nodal-selector-cabecera--propio)) .nodal-selector-cabecera__valor {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8125rem;
  font-weight: 600;
}

.nodal-selector-cabecera__valor svg:first-child,
.nodal-selector-cabecera__icono {
  color: var(--nodal-acento);
}

:where(.nodal-selector-cabecera:not(.nodal-selector-cabecera--propio)) .nodal-selector-cabecera__valor svg:first-child {
  flex: none;
  width: 15px;
  height: 15px;
}

.nodal-selector-cabecera__valor svg:last-child,
.nodal-selector-cabecera__chevron {
  color: var(--nodal-atenuado);
}

:where(.nodal-selector-cabecera:not(.nodal-selector-cabecera--propio)) .nodal-selector-cabecera__valor svg:last-child {
  flex: none;
  width: 10px;
  height: 10px;
  margin-left: auto;
}

/* ===========================================================
   12 · ICONOS DE ACCIÓN
   =========================================================== */
.nodal-accion {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  padding: 4px 8px;
  border: none;
  border-radius: var(--nodal-radio-sm);
  background: transparent;
  color: var(--nodal-texto-2);
  font: inherit;
  font-size: 0.8125rem;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-accion {
    transition: none;
  }
}

.nodal-accion:hover,
.nodal-accion[data-nodal-estado="hover"] {
  background: var(--nodal-seleccion);
  color: var(--nodal-acento);
}

.nodal-accion:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-accion svg {
  flex: none;
  width: 18px;
  height: 18px;
}

.nodal-accion--solo-icono {
  width: 32px;
  height: 32px;
  padding: 0;
  justify-content: center;
}

.nodal-accion--peligro {
  color: var(--nodal-texto-error);
}

.nodal-accion--peligro:hover,
.nodal-accion--peligro[data-nodal-estado="hover"] {
  color: var(--nodal-texto-error);
  background: color-mix(in srgb, var(--nodal-estado-error) 14%, transparent);
}

/* Desactivada, igual que el botón de texto desactivado: sin fondo y con la
   letra de lo deshabilitado; el paso del ratón no la enciende (30/09/2026,
   «Editar» de un ensamble sembrado). */
.nodal-accion:disabled,
.nodal-accion:disabled:hover,
.nodal-accion[aria-disabled="true"],
.nodal-accion[aria-disabled="true"]:hover {
  background: transparent;
  color: var(--nodal-sobre-deshabilitado);
  cursor: not-allowed;
}

/* ===========================================================
   13 · NOTIFICACIONES
   =========================================================== */
/* Como la ventana y la tarjeta, el aviso se separa en piel y forma (26/09/2026).
   La piel —borde, radio, fondo del estado, color, glifo— vale para todos; la
   forma de la lámina (fila con el icono, padding, letra) solo para el que no
   es --propio. Los avisos del producto ya tienen su maquetación (un estado
   vacío en rejilla, una nota en fila): toman la piel y la conservan. La línea
   de estado (--linea, al final de esta sección) no lleva ni la forma ni la
   caja del aviso: solo su glifo y su letra. */
.nodal-aviso:where(:not(.nodal-aviso--propio, .nodal-aviso--linea)) {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.75rem 0.875rem;
  font-size: 0.875rem;
}

.nodal-aviso {
  color: var(--nodal-texto);
}

.nodal-aviso:where(:not(.nodal-aviso--linea)) {
  border: 1px solid;
  border-radius: var(--nodal-radio);
}

/* Muchos avisos del producto nacen con `hidden` y el guion los muestra. La
   forma de la lámina, o la gemela de su pantalla, les pone display: flex y le
   gana a [hidden] del navegador: saldría una caja vacía. */
.nodal-aviso[hidden] {
  display: none;
}

/* Casi todos los avisos del producto son <p>, y en claro les gana
   `body.theme-light p` de styles.css, (0,1,2): el texto salía en el gris de
   --light-text-2. Con :root delante pesa (0,2,0) y la hoja no sale de su
   espacio de nombres. */
:root .nodal-aviso {
  color: var(--nodal-texto);
}

/* El glifo del estado (✓ i ! ×), como en la insignia: decisión del 25/09/2026,
   los estados se distinguen por glifo y tratamiento dentro de los ocho. Va en
   ::before para no tocar el marcado; si el aviso ya trae su __icono, se calla. */
.nodal-aviso::before {
  content: "";
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  color: var(--nodal-superficie-panel);
  font-size: 0.75rem;
  font-weight: 800;
  line-height: 1;
}

.nodal-aviso:has(> .nodal-aviso__icono)::before {
  content: none;
}

/* Desde el 02/10/2026 los avisos llevan los colores de la lámina 4B tal cual
   (Cristian: «debe quedar tal cual»): el ícono del color del estado y el
   glifo encima en blanco, salvo el «!» del aviso, que va en carbón sobre el
   ámbar. Los colores viven en --nodal-aviso-* (nodal-tokens.css); desde el
   03/10 los usan también las insignias de estado y el campo con error. */
.nodal-aviso--ok::before {
  content: "✓";
  background: var(--nodal-aviso-ok-icono);
  color: var(--nodal-aviso-ok-glifo);
}

.nodal-aviso--info::before {
  content: "i";
  background: var(--nodal-aviso-info-icono);
  color: var(--nodal-aviso-info-glifo);
}

.nodal-aviso--aviso::before {
  content: "!";
  background: var(--nodal-aviso-aviso-icono);
  color: var(--nodal-aviso-aviso-glifo);
}

.nodal-aviso--error::before {
  content: "×";
  background: var(--nodal-aviso-error-icono);
  color: var(--nodal-aviso-error-glifo);
}

/* En la lámina el aviso es un triángulo, no un círculo; el glifo baja para
   quedar en su centro. */
.nodal-aviso--aviso::before,
.nodal-aviso--aviso .nodal-aviso__icono {
  box-sizing: border-box;
  padding-top: 0.2em;
  border-radius: 0;
  clip-path: polygon(50% 4%, 100% 94%, 0 94%);
}

.nodal-aviso__icono {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  color: var(--nodal-superficie-panel);
}

.nodal-aviso__icono svg {
  width: 12px;
  height: 12px;
}

.nodal-aviso__texto {
  padding-top: 2px;
}

/* Fondo, borde fino y texto de cada estado, como en la lámina. El texto va
   con :root delante para ganarle a `:root .nodal-aviso` (el del tema): en
   claro, el error va en rojo y la información en azul. El fondo y el borde
   son de la caja: la línea de estado no los lleva. */
.nodal-aviso--ok:where(:not(.nodal-aviso--linea)) {
  background: var(--nodal-aviso-ok-fondo);
  border-color: var(--nodal-aviso-ok-borde);
}

:root .nodal-aviso--ok {
  color: var(--nodal-aviso-ok-texto);
}

.nodal-aviso--ok .nodal-aviso__icono {
  background: var(--nodal-aviso-ok-icono);
  color: var(--nodal-aviso-ok-glifo);
}

.nodal-aviso--info:where(:not(.nodal-aviso--linea)) {
  background: var(--nodal-aviso-info-fondo);
  border-color: var(--nodal-aviso-info-borde);
}

:root .nodal-aviso--info {
  color: var(--nodal-aviso-info-texto);
}

.nodal-aviso--info .nodal-aviso__icono {
  background: var(--nodal-aviso-info-icono);
  color: var(--nodal-aviso-info-glifo);
}

.nodal-aviso--aviso:where(:not(.nodal-aviso--linea)) {
  background: var(--nodal-aviso-aviso-fondo);
  border-color: var(--nodal-aviso-aviso-borde);
}

:root .nodal-aviso--aviso {
  color: var(--nodal-aviso-aviso-texto);
}

.nodal-aviso--aviso .nodal-aviso__icono {
  background: var(--nodal-aviso-aviso-icono);
  color: var(--nodal-aviso-aviso-glifo);
}

.nodal-aviso--error:where(:not(.nodal-aviso--linea)) {
  background: var(--nodal-aviso-error-fondo);
  border-color: var(--nodal-aviso-error-borde);
}

:root .nodal-aviso--error {
  color: var(--nodal-aviso-error-texto);
}

.nodal-aviso--error .nodal-aviso__icono {
  background: var(--nodal-aviso-error-icono);
  color: var(--nodal-aviso-error-glifo);
}

/* La LÍNEA de estado (03/10/2026, Cristian, opción B): el texto pasajero que
   un guion escribe al lado de un botón o debajo de una tabla («Los cambios se
   guardaron.», «Calculando…»). El 26/09 se decidió que no lleva recuadro; el
   03/10, que lleva las piezas del aviso sin su caja: el círculo del color de
   la lámina con su glifo (el ::before de arriba, más chico) y la letra del
   estado (roja o azul en claro, la del tema en oscuro). Es el trato del
   mensaje del campo con error. La línea conserva su lugar —margen, alto
   mínimo, la raya que la separa de su tabla— de su regla de siempre; la
   letra es la de la lámina. Con las dos clases le gana a la regla de una
   clase de su pantalla. */
.nodal-aviso.nodal-aviso--linea {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: 0.8125rem;
  line-height: 1.35;
}

/* Al lado de un botón o dentro de una barra, la línea sigue en el renglón. */
.nodal-aviso.nodal-aviso--linea:where(span) {
  display: inline-flex;
}

/* La línea con `hidden` no se ve: su display: flex pesa lo mismo que
   `.nodal-aviso[hidden]` y viene después; esta pesa más. */
.nodal-aviso.nodal-aviso--linea[hidden] {
  display: none;
}

.nodal-aviso--linea::before {
  width: 14px;
  height: 14px;
  font-size: 0.625rem;
}

/* Vacía (el guion la limpia), no deja el círculo solo. */
.nodal-aviso--linea:empty::before {
  content: none;
}

/* ===========================================================
   14 · SWITCH / CHECKBOX / RADIO
   =========================================================== */
.nodal-eleccion {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.875rem;
  color: var(--nodal-texto);
}

.nodal-interruptor {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}

.nodal-interruptor__pista {
  position: relative;
  flex: none;
  width: 36px;
  height: 20px;
  border-radius: 999px;
  background: var(--nodal-borde-control);
  transition: background-color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-interruptor__pista {
    transition: none;
  }
}

.nodal-interruptor__pista::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--nodal-sobre-acento);
  transition: transform 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-interruptor__pista::after {
    transition: none;
  }
}

.nodal-interruptor input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.nodal-interruptor input:checked + .nodal-interruptor__pista {
  background: var(--nodal-acento);
}

.nodal-interruptor input:checked + .nodal-interruptor__pista::after {
  transform: translateX(16px);
  background: var(--nodal-sobre-acento);
}

.nodal-interruptor input:focus-visible + .nodal-interruptor__pista {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-casilla {
  appearance: none;
  flex: none;
  width: 18px;
  height: 18px;
  margin: 0;
  border: 1px solid var(--nodal-borde-control);
  border-radius: var(--nodal-radio-sm);
  background: var(--nodal-superficie-elevada);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.nodal-casilla::before {
  content: "";
  width: 10px;
  height: 6px;
  border-left: 2px solid var(--nodal-sobre-acento);
  border-bottom: 2px solid var(--nodal-sobre-acento);
  transform: rotate(-45deg) scale(0);
  transition: transform 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-casilla::before {
    transition: none;
  }
}

.nodal-casilla:checked {
  background: var(--nodal-acento);
  border-color: var(--nodal-acento);
}

.nodal-casilla:checked::before {
  transform: rotate(-45deg) scale(1);
}

.nodal-casilla:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

/* La lámina no dibuja la casilla deshabilitada, pero el botón y el campo sí
   tienen ese estado y aquí faltaba: medido el 25/09, una casilla deshabilitada
   salía idéntica a una vacía y activa. Se usan los mismos dos tokens que el
   resto —`--nodal-deshabilitado` y `--nodal-sobre-deshabilitado`—, así que no
   entra ningún tono nuevo. La marcada y deshabilitada conserva su palomita,
   apagada: decir que está marcada importa más que decir que no se puede
   cambiar, y lo segundo ya lo dice el cursor. */
.nodal-casilla:disabled,
.nodal-radio:disabled {
  border-color: var(--nodal-sobre-deshabilitado);
  background: var(--nodal-deshabilitado);
  cursor: not-allowed;
}

.nodal-casilla:disabled:checked,
.nodal-radio:disabled:checked {
  border-color: var(--nodal-sobre-deshabilitado);
  background: var(--nodal-sobre-deshabilitado);
}

.nodal-casilla:disabled::before {
  border-color: var(--nodal-deshabilitado);
}

.nodal-radio {
  appearance: none;
  flex: none;
  width: 18px;
  height: 18px;
  margin: 0;
  border: 1px solid var(--nodal-borde-control);
  border-radius: 50%;
  background: var(--nodal-superficie-elevada);
  cursor: pointer;
}

.nodal-radio:checked {
  border-color: var(--nodal-acento);
  background: var(--nodal-acento);
  box-shadow: inset 0 0 0 4px var(--nodal-superficie-panel);
}

.nodal-radio:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

/* ===========================================================
   15 · PROGRESO POR PASOS
   =========================================================== */
/* Piel y forma, como la ventana, la tarjeta, el aviso, el menú, la tabla y
   la pestaña (27/09/2026). Los paso a paso del producto ya tienen su
   disposición —una rejilla de cuatro con la línea de punta a punta, una fila
   de fichas, una barra segmentada—: con nodal-pasos--propio en el contenedor
   toman solo la piel —el círculo con su borde, el acento de lo hecho y del
   actual, el tramo de línea que ya se recorrió, la letra atenuada del
   rótulo— y conservan lo suyo. El modificador va en el contenedor y no en
   cada paso porque la forma es de todo el conjunto: la línea, el círculo y el
   rótulo se miden entre sí. Las reglas de forma pesan lo mismo que antes
   (el :where() no suma), así que los pasos de la lámina no se mueven. */
.nodal-pasos:where(:not(.nodal-pasos--propio)) {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}

:where(.nodal-pasos:not(.nodal-pasos--propio)) .nodal-paso {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  flex: 1;
  position: relative;
  text-align: center;
}

/* La línea: su color es piel y vale para todos; dónde y cuánto mide es forma.
   En un --propio el ::before solo existe si el producto le pone contenido,
   así que este color le llega a su propia línea, si la dibuja con ::before, y
   a nada si no. */
.nodal-paso::before {
  background: var(--nodal-borde-control);
}

:where(.nodal-pasos:not(.nodal-pasos--propio)) .nodal-paso::before {
  content: "";
  position: absolute;
  top: 14px;
  left: -50%;
  width: 100%;
  height: 2px;
  z-index: 0;
}

:where(.nodal-pasos:not(.nodal-pasos--propio)) .nodal-paso:first-child::before {
  content: none;
}

/* El tramo de linea se pinta cuando el paso ANTERIOR quedo hecho, no cuando
   lo queda este: el ::before de un paso es el tramo que llega hasta el. Por eso
   entra tambien el paso actual —si estas en el 02, el tramo 01-02 ya se
   recorrio—. Sin esta segunda linea, avanzar al 02 dejaba su tramo en gris y
   la barra se veia sin empezar (24/09/2026). */
.nodal-paso--hecho::before,
.nodal-paso[aria-current="step"]::before {
  background: var(--nodal-acento);
}

.nodal-paso__marca {
  border: 1px solid var(--nodal-borde-control);
  border-radius: 50%;
  background: var(--nodal-superficie);
  color: var(--nodal-atenuado);
}

:where(.nodal-pasos:not(.nodal-pasos--propio)) .nodal-paso__marca {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  font-size: 0.8125rem;
  font-weight: 700;
}

.nodal-paso__rotulo {
  color: var(--nodal-atenuado);
}

:where(.nodal-pasos:not(.nodal-pasos--propio)) .nodal-paso__rotulo {
  font-size: 0.75rem;
}

.nodal-paso--hecho .nodal-paso__marca,
.nodal-paso[aria-current="step"] .nodal-paso__marca {
  background: var(--nodal-acento);
  border-color: var(--nodal-acento);
  color: var(--nodal-sobre-acento);
}

.nodal-paso--hecho .nodal-paso__rotulo,
.nodal-paso[aria-current="step"] .nodal-paso__rotulo {
  color: var(--nodal-texto);
}

:where(.nodal-pasos:not(.nodal-pasos--propio)) .nodal-paso--hecho .nodal-paso__rotulo,
:where(.nodal-pasos:not(.nodal-pasos--propio)) .nodal-paso[aria-current="step"] .nodal-paso__rotulo {
  font-weight: 600;
}

/* Un paso puede ser un botón. La lámina lo dibuja como rótulo —un <li>—,
   pero en Plantillas de Presupuestos los tres pasos se pulsan para moverse
   por el flujo, y un <button> trae de fábrica su fondo, su borde y su letra.
   Se los quita sin tocar el resto del componente. Sin relleno vertical: la
   línea que une los pasos se dibuja a 14 px, la mitad del círculo, y
   cualquier relleno arriba la despegaría del centro. En un --propio el botón
   conserva su relleno y su letra. */
button.nodal-paso {
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}

:where(.nodal-pasos:not(.nodal-pasos--propio)) button.nodal-paso {
  padding: 0 4px;
  font: inherit;
}

/* El anillo de foco va en el círculo y no en el botón: el botón es una
   columna alta y estrecha, y su contorno encerraría también el rótulo. */
button.nodal-paso:focus-visible {
  outline: none;
}

button.nodal-paso:focus-visible .nodal-paso__marca {
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

/* Un paso deshabilitado no se enciende al pasarle el ratón (27/09/2026: el
   importador del Planificador es el primero que los deshabilita). El :where()
   no suma peso. */
button.nodal-paso:hover:where(:not(:disabled)):not([aria-current="step"]):not(.nodal-paso--hecho) .nodal-paso__marca {
  border-color: var(--nodal-acento);
  color: var(--nodal-texto);
}

button.nodal-paso:disabled {
  cursor: not-allowed;
}

/* La BARRA de progreso (03/10/2026, Cristian). La lámina no tiene barra, solo
   el paso a paso, así que la barra toma sus colores: el riel es la línea sin
   recorrer (--nodal-borde-control) y el relleno, el tramo recorrido
   (--nodal-acento: teal en claro, teal claro en oscuro). 6 px de alto,
   extremos redondeados. Sirve al <progress> nativo (los tres navegadores
   pintan el riel y el relleno con sus propios pseudoelementos) y a una barra
   armada con dos elementos: .nodal-progreso con un .nodal-progreso__relleno
   adentro, con su ancho en línea. */
.nodal-progreso {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: 6px;
  border: 0;
  border-radius: 999px;
  overflow: hidden;
  background: var(--nodal-borde-control);
  color: var(--nodal-acento);
}

.nodal-progreso::-webkit-progress-bar {
  border-radius: 999px;
  background: var(--nodal-borde-control);
}

.nodal-progreso::-webkit-progress-value {
  border-radius: 999px;
  background: var(--nodal-acento);
}

.nodal-progreso::-moz-progress-bar {
  border-radius: 999px;
  background: var(--nodal-acento);
}

.nodal-progreso__relleno {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--nodal-acento);
}

/* ===========================================================
   16 · PAGINACIÓN
   =========================================================== */
.nodal-paginacion {
  display: flex;
  align-items: center;
  gap: 4px;
}

.nodal-paginacion__pagina,
.nodal-paginacion__flecha {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--nodal-borde);
  border-radius: var(--nodal-radio);
  background: transparent;
  color: var(--nodal-texto);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .nodal-paginacion__pagina,
  .nodal-paginacion__flecha {
    transition: none;
  }
}

.nodal-paginacion__pagina:hover,
.nodal-paginacion__pagina[data-nodal-estado="hover"],
.nodal-paginacion__flecha:hover,
.nodal-paginacion__flecha[data-nodal-estado="hover"] {
  border-color: var(--nodal-acento);
  color: var(--nodal-acento);
}

.nodal-paginacion__pagina:focus-visible,
.nodal-paginacion__flecha:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--nodal-foco);
}

.nodal-paginacion__pagina[aria-current="page"] {
  background: var(--nodal-primario);
  border-color: var(--nodal-primario);
  color: var(--nodal-sobre-primario);
}

.nodal-paginacion__flecha svg {
  width: 14px;
  height: 14px;
}

.nodal-paginacion__flecha:disabled,
.nodal-paginacion__flecha[aria-disabled="true"] {
  color: var(--nodal-sobre-deshabilitado);
  border-color: var(--nodal-borde);
  cursor: not-allowed;
}
