/* ============================================================
   Componentes del sistema. Solo clases; nunca datos.
   Regla: este archivo nunca conoce los datos; todo valor sale de tokens.css.
   ============================================================ */

/* --- Tipografia --- */
.t-large-title { font-size: var(--text-large-title-size); line-height: var(--text-large-title-lh); font-weight: 700; letter-spacing: -0.4px; }
.t-title1      { font-size: var(--text-title1-size); line-height: var(--text-title1-lh); font-weight: 700; letter-spacing: -0.4px; }
.t-title2      { font-size: var(--text-title2-size); line-height: var(--text-title2-lh); font-weight: 700; }
.t-title3      { font-size: var(--text-title3-size); line-height: var(--text-title3-lh); font-weight: 600; }
.t-headline    { font-size: var(--text-headline-size); line-height: var(--text-headline-lh); font-weight: 600; }
.t-body        { font-size: var(--text-body-size); line-height: var(--text-body-lh); }
.t-callout     { font-size: var(--text-callout-size); line-height: var(--text-callout-lh); }
.t-subhead     { font-size: var(--text-subhead-size); line-height: var(--text-subhead-lh); }
.t-footnote    { font-size: var(--text-footnote-size); line-height: var(--text-footnote-lh); }
.t-caption1    { font-size: var(--text-caption1-size); line-height: var(--text-caption1-lh); }
.t-caption2    { font-size: var(--text-caption2-size); line-height: var(--text-caption2-lh); font-weight: 500; letter-spacing: 0.06em; }

.t-secondary { color: var(--label-secondary); }
/* No existe .t-tertiary. --label-tertiary es NO textual: un tercer nivel de
   texto al 30% de alfa compone 1.72:1 sobre blanco y no puede alcanzar AA.
   Donde el diseno pedia un tercer nivel de texto, se usa --label-secondary. */

/* --- Boton --- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: 44px;
  padding: 0 var(--space-5);
  border: 0;
  border-radius: var(--radius-md);
  font-family: var(--font-sans);
  font-size: var(--text-body-size);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
  transition: opacity var(--dur-fast) var(--ease-ios),
              transform var(--dur-fast) var(--ease-ios),
              background-color var(--dur-fast) var(--ease-ios);
}
.btn:active:not(:disabled) { opacity: 0.75; transform: scale(0.97); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn:disabled { opacity: 0.4; pointer-events: none; }

/* El boton small mide 34px de alto por diseno, pero su area tactil real es de
   44px: el ::after la expande sin cambiar nada visible. */
.btn--sm { position: relative; height: 34px; padding: 0 var(--space-3); font-size: var(--text-subhead-size); border-radius: var(--radius-sm); }
.btn--sm::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 44px;
}
.btn--lg { height: 50px; padding: 0 var(--space-6); }
.btn--block { width: 100%; }

.btn--primary     { background: var(--accent-fill); color: var(--on-accent); }
.btn--tinted      { background: var(--accent-tint); color: var(--accent-text); }
.btn--plain       { background: transparent; color: var(--accent-text); padding: 0 var(--space-2); }
/* Usa --destructive-fill, no --ios-red: blanco sobre #FF3B30 mide 3.55 e
   incumple AA. El token y esta linea los aplica la Tarea 4, junto con los
   tokens de badge, porque esta tarea cerro antes de que se midiera. */
.btn--destructive { background: var(--destructive-fill); color: var(--on-destructive); }

/* El boton de solo icono conserva 44x44 de area tactil aunque el icono sea 20px. */
.btn--icono { width: 44px; padding: 0; }

/* --- Campo de texto (estilo inset grouped de iOS) --- */
.field-group {
  /* --bg-grupo, no --bg-secondary: el grupo tiene que contrastar con la pagina. */
  background: var(--bg-grupo);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4);
  min-height: 44px;
  border-bottom: 1px solid var(--separator);
}
.field:last-child { border-bottom: 0; }

.field__label {
  font-size: var(--text-subhead-size);
  line-height: var(--text-subhead-lh);
  color: var(--label-secondary);
}
.field__input {
  border: 0;
  outline: 0;
  background: transparent;
  font-family: var(--font-sans);
  font-size: var(--text-body-size);
  line-height: var(--text-body-lh);
  color: var(--label);
  width: 100%;
  padding: 0;
}
/* El placeholder ES texto (WCAG 1.4.3 aplica), asi que usa el secundario: con
   el terciario compondria 1.72:1. El checker mide elementos, no pseudoelementos,
   asi que este caso no lo atraparia — se corrige a mano. */
.field__input::placeholder { color: var(--label-secondary); }
.field:focus-within {
  /* Anillo SOLIDO de acento. El tint al 18% compone 1.19:1 en claro y 1.58:1 en
     oscuro, y es el UNICO indicador de foco porque .field__input tiene
     outline:0: eso incumple el 3:1 de 1.4.11. El acento solido da 3.64:1 sobre
     bg-secondary y 4.06:1 sobre la superficie de grupo, que es donde el campo
     vive desde que las superficies se separaron; en oscuro 8.55:1. Mismo patron
     que .otp__casilla. */
  box-shadow: inset 0 0 0 2px var(--accent);
  border-radius: var(--radius-xs);
}
.field__error {
  font-size: var(--text-footnote-size);
  line-height: var(--text-footnote-lh);
  color: var(--text-red);
}
.field--error .field__input { color: var(--text-red); }
.field--error { box-shadow: inset 0 0 0 3px var(--error-tint); }

/* --- Codigo OTP de 4 casillas --- */
.otp { display: flex; gap: var(--space-3); }
.otp__casilla {
  width: 52px;
  height: 58px;
  text-align: center;
  font-family: var(--font-sans);
  font-size: var(--text-title2-size);
  font-weight: 600;
  color: var(--label);
  /* --bg-grupo: con --bg-secondary el casillero caia en el MISMO color que la
     pagina en tema claro y se distinguia solo por su borde de 1 px. Misma clase
     de defecto que .field-group.
     --control-border, y NO --separator: con el separador el borde compone
     1.22:1 en claro y 1.65:1 en oscuro contra la superficie que la casilla tiene
     DETRAS, y en claro la casilla es blanca sobre un field-group tambien blanco,
     asi que ese borde es la UNICA señal de que hay un control: es no textual y
     le aplica el 3:1 de 1.4.11 (el caso del ruling 38, que se corrigio para
     checkbox y radio y habia quedado a medias para los campos). --control-border
     ya pasa en las cuatro superficies del sistema (4.54 / 4.07 / 4.62 / 3.75),
     asi que NO hace falta un override local del login: un token forkeado es peor
     que el hueco. */
  background: var(--bg-grupo);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-md);
  outline: 0;
  transition: box-shadow var(--dur-fast) var(--ease-ios),
              border-color var(--dur-fast) var(--ease-ios);
}
.otp__casilla:focus-visible,
.otp__casilla:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}

/* --- Control segmentado --- */
.segmented {
  position: relative;
  display: inline-flex;
  padding: 2px;
  background: var(--fill-quaternary);
  border-radius: var(--radius-sm);
}
.segmented__opcion {
  position: relative;
  z-index: 1;
  flex: 1;
  height: 32px;
  min-width: 44px;
  padding: 0 var(--space-3);
  border: 0;
  background: transparent;
  border-radius: var(--radius-xs);
  font-family: var(--font-sans);
  font-size: var(--text-footnote-size);
  font-weight: 600;
  color: var(--label);
  cursor: pointer;
  transition: color var(--dur-base) var(--ease-ios);
}
/* El control segmentado de iOS mide 32px; el ::after le da 44px de area tactil. */
.segmented__opcion::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 44px;
}
.segmented__opcion[aria-selected="true"] { color: var(--label); }
.segmented__opcion[aria-selected="false"] { color: var(--label-secondary); }
/* NO hay .segmented__thumb: era una regla muerta, ningun markup renderizaba ese
   elemento, y con ella el estado seleccionado se comunicaba SOLO por color de
   texto, lo que incumple WCAG 1.4.1. La opcion activa se marca con superficie
   elevada, sombra y mas peso, ademas del color. */
.segmented__opcion[aria-selected="true"] {
  background: var(--bg-grupo);
  box-shadow: var(--shadow-sm);
  font-weight: 700;
  color: var(--label);
}
.segmented__opcion[aria-selected="false"] { color: var(--label-secondary); font-weight: 600; }

/* Tercer anillo de foco que este proyecto encuentra casi invisible, y la tercera
   vez que la señal era la UNICA que indicaba la posicion. Medido: en tema oscuro
   la opcion del control segmentado solo tenia el `outline: auto` del navegador,
   pintado rgb(16,16,16), o sea 1.13:1 sobre la pista; en claro el mismo anillo
   mide 14.93:1, asi que el defecto vivia en un solo tema. Los otros dos fueron el
   campo (1.19:1, un `--accent-tint` que se reemplazo por el acento solido) y el
   `outline: auto` del sidebar (1.07:1).
   El offset es NEGATIVO, y no por gusto: las opciones son ADYACENTES, asi que un
   anillo por fuera se montaria sobre la pastilla de la vecina. Adentro delimita la
   opcion enfocada sin tocar a las demas. */
.segmented__opcion:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* --- Estado deshabilitado de los controles que NO son boton ---
   Sin esto, un campo o un switch deshabilitado computa IDENTICO al habilitado
   (medido por la Tarea 5): el usuario ve un control normal que no responde y no
   entiende por que. .btn ya tenia su opacity. */
.field:has(.field__input:disabled) { opacity: 0.55; }
.field__input:disabled { cursor: not-allowed; }
.otp__casilla:disabled { opacity: 0.55; cursor: not-allowed; }
.switch:disabled { opacity: 0.45; cursor: not-allowed; }
.checkbox input:disabled + .checkbox__caja,
.radio input:disabled + .radio__circulo { opacity: 0.45; border-color: var(--label-secondary); }

/* --- Switch --- */
.switch {
  position: relative;
  width: 51px;
  height: 31px;
  border: 0;
  padding: 0;
  border-radius: var(--radius-full);
  background: var(--fill-quaternary);
  cursor: pointer;
  transition: background-color var(--dur-base) var(--ease-ios);
  flex: none;
}
.switch[aria-checked="true"] { background: var(--ios-green); }
.switch__thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #FFFFFF;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-base) var(--ease-ios);
}
.switch[aria-checked="true"] .switch__thumb { transform: translateX(20px); }
/* El switch NO tenia anillo propio y dependia del del sistema: medido en tema
   oscuro con el banco de controles de la bateria, su unico indicador de foco era
   el `outline: auto` del navegador pintado rgb(16,16,16) sobre #1C1C1E, o sea
   1.12:1 — la misma clase de defecto que el anillo de la navegacion, y por eso se
   corrige con el mismo patron. El anillo va por FUERA (offset 2) porque el switch
   tiene el pulgar pegado al borde y un anillo interior se comeria su forma. */
.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* El switch de iOS mide 31px de alto; el ::after le da 44px de area tactil. */
.switch::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 44px;
}

/* Fila de lista iOS con un control a la derecha: TODA la fila es el area
   tactil (tocar el texto tambien acciona el control). */
.fila-control {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
  border-bottom: 1px solid var(--separator);
}
.fila-control:last-child { border-bottom: 0; }

/* --- Checkbox y radio --- */
.checkbox, .radio {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  cursor: pointer;
  font-size: var(--text-body-size);
  color: var(--label);
}
.checkbox__caja {
  width: 22px; height: 22px; flex: none;
  /* --control-border, no --label-tertiary: el borde es la unica senal visual de
     un control desmarcado, y el terciario compone 1.71:1 contra el 3:1 exigido. */
  border: 2px solid var(--control-border);
  border-radius: var(--radius-xs);
  display: inline-flex; align-items: center; justify-content: center;
  color: transparent;
  transition: background-color var(--dur-fast) var(--ease-ios), border-color var(--dur-fast) var(--ease-ios);
}
.checkbox input { position: absolute; opacity: 0; width: 1px; height: 1px; }
/* --control-checked-fill y NO --accent-fill: el estado MARCADO es un control no
   textual que tiene que distinguirse de la superficie de atras (3:1) y ademas
   llevar el check blanco encima (3:1). Medido: con --accent-fill en tema oscuro
   el relleno da 2.87:1 contra la card, por debajo del piso — el unico estado de
   control del sistema que quedaba por debajo. El token nuevo es ESTABLE entre
   temas justamente porque el par doble no se puede satisfacer con un valor por
   tema (ver tokens.css). */
.checkbox input:checked + .checkbox__caja { background: var(--control-checked-fill); border-color: var(--control-checked-fill); color: var(--on-accent); }
.checkbox input:focus-visible + .checkbox__caja { outline: 2px solid var(--accent); outline-offset: 2px; }

.radio__circulo {
  width: 22px; height: 22px; flex: none;
  /* Igual que el checkbox: borde no textual, 3:1 exigido. */
  border: 2px solid var(--control-border);
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  transition: border-color var(--dur-fast) var(--ease-ios);
}
.radio__circulo::after {
  content: ''; width: 10px; height: 10px; border-radius: 50%;
  /* El punto del radio es la señal del estado marcado: mismo token estable. */
  background: var(--control-checked-fill); transform: scale(0);
  transition: transform var(--dur-fast) var(--ease-ios);
}
.radio input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.radio input:checked + .radio__circulo { border-color: var(--control-checked-fill); }
.radio input:checked + .radio__circulo::after { transform: scale(1); }
.radio input:focus-visible + .radio__circulo { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- Card --- */
.card {
  /* --bg-grupo: en oscuro --bg-primary es negro y la pagina tambien queda
     oscura, asi que la card se confundia con el fondo. */
  background: var(--bg-grupo);
  border: 1px solid var(--separator);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
}
.card__titulo { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); }

/* `summary` es un CONTROL interactivo y focusable, no un parrafo. Medido: el
   resumen del selector de cuentas demo del login media 22 px de alto a 768 y
   1440 —la altura de linea de `t-headline`— y quedaba por debajo del piso de
   24x24 que fija la restriccion global del plan; a 320/390/430 medía 44 px por
   el alto de su fila, o sea que el fallo aparecia solo en los anchos grandes.
   Pasaba desapercibido porque el selector de `CHECK.tactil()` no incluia
   `summary` y ningun otro chequeo mide controles sin clase (hueco cerrado en la
   Tarea 13). El `min-height` es el piso, no un gusto: el area tactil de un
   control desplegable no puede depender del ancho de la pantalla.
   `display: flex` + `align-items: center` para que el alto extra no desplace el
   texto, y el triangulo nativo se conserva (medido en las capturas de la fase 1)
   con `::marker` en el color secundario, que es el que le corresponde. */
summary {
  display: flex;
  align-items: center;
  min-height: 24px;
}
summary::marker { color: var(--label-secondary); }

/* --- Badge / pill de estado --- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: 22px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-full);
  font-size: var(--text-caption1-size);
  line-height: var(--text-caption1-lh);
  font-weight: 600;
  white-space: nowrap;
}
.badge--ok      { background: rgba(52, 199, 89, 0.18);  color: var(--text-green); }
.badge--warn    { background: rgba(255, 149, 0, 0.18);  color: var(--text-orange); }
.badge--info    { background: rgba(0, 122, 255, 0.16);  color: var(--text-blue); }
.badge--danger  { background: rgba(255, 59, 48, 0.16);  color: var(--text-red); }
.badge--neutral { background: var(--fill-quaternary);   color: var(--label-secondary); }

/* Severidad Leve (módulo inspecciones, fase 2, sub-tarea 6.2/6.4) — spec 1.2:
   Leve → yellow (Moderada → orange ya es .badge--warn y Grave → red es
   .badge--danger). No hay token de TEXTO amarillo en tokens.css —los
   `--ios-yellow` son de paleta, #FFCC00 en claro, y como TEXTO sobre su
   propio tinte componen 1.55:1— y tokens.css NO se toca (restricción
   global), así que el par va en LITERALES con override de tema: la misma
   excepción ya declarada en esta sección de Badge (los tintes de
   .stepper__item--pasado reutilizan literales por el mismo motivo).
   El override de tema usa el MISMO mecanismo que tokens.css ([data-theme]),
   que hasta acá era el único escritor del selector — primera excepción en
   este archivo, con medición (CHECK.ratio, texto contra el fondo compuesto
   del primer ancestro pintado):
     claro: rgba(255,204,0,.18) sobre .card #F2F2F7; texto #7A5A00
       (ámbar oscuro) → 5.88:1.
     oscuro: rgba(255,214,10,.18) sobre #1C1C1E; texto #FFD60A
       (el --ios-yellow del tema oscuro, que ahí SÍ pasa como texto) → 7.65:1.
   El texto de la badge SIEMPRE acompaña al color ("Leve"): nunca viaja solo. */
.badge--leve { background: rgba(255, 204, 0, 0.18); color: #7A5A00; }
[data-theme="dark"] .badge--leve { background: rgba(255, 214, 10, 0.18); color: #FFD60A; }

/* --- List group (inset grouped de iOS) --- */
.list-group {
  background: var(--bg-grupo);
  border: 1px solid var(--separator);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.list-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--separator);
  color: var(--label);
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-ios);
}
.list-row:last-child { border-bottom: 0; }
.list-row:hover { background: var(--fill-quaternary); }
.list-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.list-row__valor { margin-left: auto; color: var(--label-secondary); font-size: var(--text-body-size); }

/* Fila de lista que puede PARTIRSE en dos lineas cuando el contenido no entra
   en el ancho (una fila con cuatro badges + valor mide ~390px de min-content a
   390px de viewport, y `.list-row` es flex sin wrap: la fila empuja y desborda,
   medido 3 desbordes en las OT de hoy del operario a 390). El wrap conserva
   TODO el contenido — ni se recorta texto ni se esconde un dato; en anchos
   grandes la fila se pinta en una linea igual que siempre. */
.list-row--ajustable { flex-wrap: wrap; }

/* Los IDENTIFICADORES con guion (codigo de OT, serie de equipo, fecha ISO) no
   se parten entre dos lineas: con el shrink por defecto de una fila apretada,
   'OT-2026-0151' se quebraba por los guiones ('OT-2026-' / '0151') y se leia
   como dos identificadores. Medido en el dashboard: filas de SLA, checklists,
   ruta y proxima visita partian identificadores. Con nowrap, el identificador
   es una unidad: las filas ajustables lo mueven entero a otra linea y el
   resto del contenido (que SI es prose) sigue quebrando normal. */
.sin-corte { white-space: nowrap; }

/* Enlace del CÓDIGO en la lista de órdenes (T3): es el acceso de la fila. La
   fila entera no puede ser un <a> (la tabla es de renderLista y el contrato de
   renderItem devuelve celdas), así que el enlace vive en la celda del código.
   El color es --accent-text y no --accent: es TEXTO de acento sobre fondo
   normal (umbral 4.5:1, mide ~6:1 en los dos temas); --accent es el token de
   iconos y bordes (umbral 3:1). El padding + margen negativo levanta el área
   táctil por encima del piso de 24px sin desalinear la línea base de la celda
   (medido: la celda envuelve el mismo hueco, el anillo de foco lo señala). */
.ot-enlace {
  display: inline-block;
  padding: 4px 8px;
  margin: -4px -8px;
  color: var(--accent-text);
  text-decoration: none;
}
.ot-enlace:focus-visible { outline: 2px solid var(--accent); outline-offset: 0; }

/* --- Lista compartida (renderLista, _lista-base.js) ---
   La estructura de spec 4.4.1 que reutilizan las pantallas de modulo:
   encabezado con titulo y contador, filtros (buscador + segmented + chips),
   tabla y paginacion. En movil la MISMA tabla se convierte en cards
   (spec 4.4.2): el CSS reordena las celdas de la fila como filas de card y
   les pone la etiqueta de su columna desde data-etiqueta. Un solo renderItem,
   nunca scroll horizontal (a 320px tampoco). */
.lista { display: flex; flex-direction: column; gap: var(--space-4); }
.lista__titular {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  flex-wrap: wrap;
}
/* El contador es una unidad ("30 ordenes"): partilo deja el numero y la
   unidad en lineas distintas, que es justo el dato que la unidad acompaña. */
.lista__conteo { white-space: nowrap; }

.lista__filtros { display: flex; flex-direction: column; gap: var(--space-3); }
/* Dentro de una lista el segmentado puede llevar MUCHAS opciones (OT: 7
   estados + Todas). Ocho opciones de min-width 44px miden 356px y a 320px
   desbordan el viewport (medido), y el scroll horizontal esta prohibido
   (4.4.2): dentro de las listas el segmentado envuelve. El componente global
   queda como esta; esta excepcion vive SOLO en .lista__filtros. */
.lista__filtros .segmented {
  flex-wrap: wrap;
  width: 100%;
  max-width: 480px;
}

/* Chips de filtro (spec 4.4.1). Ninguna clase previa los daba: la unica cosa
   llamada "chip" en el sistema es la muestra de sombras de la guia, con
   estilo en linea. Pill de 32px como el segmentado, y el ::after le da los
   44px de area tactil (mismo patron). */
.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: 32px;
  padding: 0 var(--space-3);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-full);
  background: var(--bg-grupo);
  color: var(--label);
  font-family: var(--font-sans);
  font-size: var(--text-footnote-size);
  font-weight: 600;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-fast) var(--ease-ios);
}
.chip::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 44px;
}
/* El estado presionado NO se comunica solo por color: ademas del tinte, el
   chip lleva un check (lo pone _lista-base.js) y cambia el texto a acento.
   El borde va en --accent-text y no en --accent: contra el tinte el acento
   ronda el 3:1 justo, y el texto del token mide ~6:1 en los dos temas. */
.chip[aria-pressed="true"] {
  background: var(--accent-tint);
  border-color: var(--accent-text);
  color: var(--accent-text);
}
.chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* La tabla de la lista: superficie de grupo con filas separadas por
   hairline. Layout FIJO: reparte el ancho y nunca deja que una celda empuje
   la tabla mas alla del contenedor — el recorte dentro de un contenedor que
   corta sin ofrecer salida es un fallo medido por CHECK.recorte(). Las celdas
   quiebran palabras; el nowrap de un identificador es decision de la vista. */
.lista__tabla-contenedor {
  background: var(--bg-grupo);
  border: 1px solid var(--separator);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.lista__tabla {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  table-layout: fixed;
}
.lista__tabla th {
  text-align: left;
  font-size: var(--text-caption1-size);
  line-height: var(--text-caption1-lh);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--label-secondary);
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--separator);
  overflow-wrap: break-word;
}
.lista__tabla td {
  padding: var(--space-3);
  border-bottom: 1px solid var(--separator);
  vertical-align: middle;
  overflow-wrap: break-word;
}
.lista__tabla tbody tr:last-child td { border-bottom: 0; }

/* Estado de carga: filas de esqueleto del mismo alto que una fila de tabla. */
.lista__cargando { display: flex; flex-direction: column; gap: var(--space-3); }

/* Estado de error INLINE (spec 3.6): mensaje + accion de reintento. El texto
   va en el color de etiqueta normal sobre el tinte de error — el rojo del
   icono acompana y el texto dice lo mismo, el color no es el unico portador. */
.lista__error {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--error-tint);
  border-radius: var(--radius-md);
}
.lista__error p { flex: 1; min-width: 0; margin: 0; }
.lista__error i { color: var(--text-red); flex: none; }

/* Paginacion simple: centrada y con wrap, para que a 320px los dos botones y
   el conteo quebran a dos lineas en vez de desbordar. */
.lista__paginacion {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-1) var(--space-3);
}

/* En movil la MISMA tabla se convierte en cards (sub-tarea 2.2, con la media
   query que la regla fija). Es la UNICA excepcion deliberada a la convencion
   mobile-first de este archivo ("no hay bloque max-width"): la conversion
   tabla->cards la manda el plan con este limite exacto y se declara de un
   solo lado. thead sale del arbol visual; la misma informacion sigue
   alcanzable porque cada celda lleva su etiqueta de columna en el ::before
   (contenido generado que entra al arbol de accesibilidad). Ocultar el thead
   es lo UNICO que no altera el layout: la alternativa de recortarlo deja sus
   th dispuestos como celdas de tabla de ~213px dentro de un contenedor de
   1px y empuja el scrollWidth de la pagina a 558 a 390px — medido, 11
   desbordes de CHECK.overflowConConteo(), la tabbar incluida. */
@media (max-width: 767px) {
  .lista__tabla-contenedor {
    background: transparent;
    border: 0;
    border-radius: 0;
    overflow: visible;
  }
  .lista__tabla, .lista__tabla tbody { display: block; }
  .lista__tabla thead { display: none; }
  .lista__tabla tr {
    display: block;
    background: var(--bg-grupo);
    border: 1px solid var(--separator);
    border-radius: var(--radius-md);
    padding: var(--space-2) 0;
  }
  .lista__tabla tbody tr + tr { margin-top: var(--space-3); }
  .lista__tabla td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-1) var(--space-4);
    border-bottom: 0;
  }
  /* La etiqueta de la columna viaja en data-etiqueta y la pone el CSS: la
     card muestra la MISMA informacion que la tabla, con la misma fila. */
  .lista__tabla td::before {
    content: attr(data-etiqueta);
    flex: none;
    color: var(--label-secondary);
    font-size: var(--text-footnote-size);
    line-height: var(--text-footnote-lh);
  }
}

/* El envolver de arriba NO alcanzaba solo: las opciones llevan `flex: 1`
   (basis 0), y con basis 0 el tamano hipotetico de cada opcion es su
   min-width (44px) — ocho opciones "entran" en 352px y NUNCA envuelven, con
   lo que los rotulos de una sola palabra ("Trabajando", "Completado") se
   salen de su boton de 44px y empujan el scrollWidth del documento a 415px a
   390 de viewport (medido en la T3: la tabbar fija medía 415px y
   CHECK.overflowConConteo() reportaba 2 desbordes). Con basis auto el tamaño
   hipotetico es el del CONTENIDO, el envolver ocurre de verdad y el control
   queda dentro del ancho en cualquier viewport. Sigue siendo la excepcion de
   .lista__filtros: el componente global no cambia. */
.lista__filtros .segmented__opcion { flex: 1 1 auto; }

/* --- Lista ANCHA: opt-in de la vista que no entra ni en tableta ---
   La regla compartida convierte la tabla en cards a <768px, y asume filas que
   entran en ~672px. La lista de ordenes de trabajo (T3) muestra 10 datos por
   fila y su min-content MEDIDO es 1090px (table-layout auto + width:
   min-content sobre la fila real): a 768 hay 672px, a 820 (breakpoint núcleo
   de capturas) 724px y hasta a 1280 solo 986px — el sidebar completo de 260px
   aparece justamente a partir de 1280 —. La tabla recorta contra el
   overflow:hidden del contenedor (CHECK.recorte: 17px a 768, 4px a 900) y el
   scroll horizontal está prohibido (4.4.2). El spec 4.4.1 dice "tabla O
   cards", así que esta vista convierte ANTES: cards por debajo de 1440 (el
   breakpoint de capturas de escritorio, donde la tabla mide 1146px), tabla a
   partir de 1440 con anchos por columna (abajo). Son las MISMAS declaraciones
   del bloque compartido, repetidas acá porque una media query no se puede
   parametrizar; el umbral compartido de 767 queda intacto para las demas
   listas. La vista marca el nodo con .lista--ancha. */
@media (max-width: 1439px) {
  .lista--ancha .lista__tabla-contenedor {
    background: transparent;
    border: 0;
    border-radius: 0;
    overflow: visible;
  }
  .lista--ancha .lista__tabla, .lista--ancha .lista__tabla tbody { display: block; }
  .lista--ancha .lista__tabla thead { display: none; }
  .lista--ancha .lista__tabla tr {
    display: block;
    background: var(--bg-grupo);
    border: 1px solid var(--separator);
    border-radius: var(--radius-md);
    padding: var(--space-2) 0;
  }
  .lista--ancha .lista__tabla tbody tr + tr { margin-top: var(--space-3); }
  .lista--ancha .lista__tabla td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-1) var(--space-4);
    border-bottom: 0;
  }
  .lista--ancha .lista__tabla td::before {
    content: attr(data-etiqueta);
    flex: none;
    color: var(--label-secondary);
    font-size: var(--text-footnote-size);
    line-height: var(--text-footnote-lh);
  }
}
/* Tabla a partir de 1440 (allí la tabla mide 1146px, medido): anchos por
   columna para que el reparto parejo de table-layout: fixed no parta palabras
   (el th "PRIORIDAD" necesitaba 82px de contenido y la columna pareja le daba
   80 — medido; ídem "PROGRAMADA" y "Comercial" en Cliente). Son las 10
   columnas completas: suma 1142px <= 1146. nth-child y no clases: los th los
   arma renderLista desde `columnas` y la vista no puede etiquetarlos. El rol
   cliente no tiene la 10.a (costo se omite completa): nth-child(10) no
   matchea nada en su tabla y las demas posiciones coinciden. */
@media (min-width: 1440px) {
  .lista--ancha th:nth-child(1)  { width: 158px; } /* Codigo: enlace + sin-corte (134px) */
  .lista--ancha th:nth-child(2)  { width: 116px; } /* Tipo: badge "Preventivo" (89px) */
  .lista--ancha th:nth-child(3)  { width: 106px; } /* Prioridad: th "PRIORIDAD" (82px) */
  .lista--ancha th:nth-child(4)  { width: 124px; } /* Estado: badge "Trabajando" (97px) */
  .lista--ancha th:nth-child(5)  { width: 110px; } /* Cliente: "Comercial" (76px) */
  .lista--ancha th:nth-child(6)  { width: 106px; } /* Sucursal: th "SUCURSAL" (78px) */
  .lista--ancha th:nth-child(7)  { width: 92px;  } /* Equipo: serie (47px) */
  .lista--ancha th:nth-child(8)  { width: 100px; } /* Técnico: th "TÉCNICO" (64px) */
  .lista--ancha th:nth-child(9)  { width: 132px; } /* Programada: th (104px) + fecha ISO (93px) */
  .lista--ancha th:nth-child(10) { width: 98px;  } /* Costo: "$ 412.75" (71px) */
}

/* --- Backdrop y sheet --- */
.backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.32);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-ios);
  z-index: 40;
}
.backdrop[data-abierto="true"] { opacity: 1; pointer-events: auto; }

.sheet {
  position: fixed;
  z-index: 50;
  background: var(--bg-grupo);
  display: flex;
  flex-direction: column;
  /* Movil primero: el sheet nace anclado abajo. */
  left: 0; right: 0; bottom: 0;
  max-height: 92vh;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  padding-bottom: env(safe-area-inset-bottom);
  transform: translateY(100%);
  transition: transform var(--dur-slow) var(--ease-ios);
}
.sheet[data-abierto="true"] { transform: translateY(0); }
.sheet__handle {
  width: 36px; height: 5px;
  border-radius: var(--radius-full);
  background: var(--label-tertiary);
  margin: var(--space-2) auto var(--space-1);
  flex: none;
}
.sheet__cabecera {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  min-height: var(--navbar-h);
  border-bottom: 1px solid var(--separator);
  flex: none;
}
.sheet__cuerpo { overflow-y: auto; padding: var(--space-4); }

/* Escritorio: el MISMO markup se vuelve modal centrado. */
@media (min-width: 768px) {
  .sheet {
    left: 50%; right: auto; bottom: auto;
    top: 50%;
    width: min(560px, calc(100vw - 48px));
    max-height: 86vh;
    border-radius: var(--radius-xl);
    transform: translate(-50%, -50%) scale(0.96);
    opacity: 0;
    transition: transform var(--dur-base) var(--ease-ios), opacity var(--dur-base) var(--ease-ios);
  }
  .sheet[data-abierto="true"] { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  .sheet__handle { display: none; }
}

/* --- Timeline --- */
.timeline { display: flex; flex-direction: column; }
.timeline__item { display: flex; gap: var(--space-3); }
/* El item de timeline puede ser un ENLACE (el dashboard de operacion lleva
   cada evento de actividad al detalle de su OT). Un <a> sin reset hereda el
   subrayado y el color de enlace del navegador: mismo criterio que .list-row,
   que ya resetea los dos. */
a.timeline__item { text-decoration: none; color: var(--label); }
.timeline__riel { display: flex; flex-direction: column; align-items: center; flex: none; }
.timeline__punto {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--accent); margin-top: 6px; flex: none;
}
.timeline__linea { width: 2px; flex: 1; background: var(--separator); min-height: 18px; }
.timeline__item:last-child .timeline__linea { background: transparent; }
.timeline__texto { padding-bottom: var(--space-3); }

/* --- Stepper del flujo de 7 estados (ficha de OT, fase 2, sub-tarea 4.3) ---
   Los 7 canónicos en orden, el actual marcado, los pasados en verde, los
   futuros en gris, y NUNCA solo color: cada paso lleva SU icono, SU texto y
   una palabra de posición ("Hecho"/"Actual"/"Futuro") — verde vs gris no es
   la única señal entre pasado y futuro, y el actual se marca además con
   borde de 2px, `aria-current="step"` y peso 700.

   MEDICIÓN (por qué envuelve): los 7 rótulos a caption1 miden ~490px
   ("Trabajando" 64px, "Completado" 70px) más padding: una sola fila necesita
   ~530px y a 390px el viewport deja 358px — el scroll horizontal está
   prohibido (4.4.2). El flex envuelve (mismo criterio que el segmentado de
   8 opciones dentro de .lista__filtros); a partir de 768 entra en una fila.
   Cada paso mide ≥32px de alto (el círculo del icono), sobre el piso de 24px.
   Los tintes de estado reutilizan los LITERALES de .badge--ok (única
   excepción del archivo, ya declarada en la sección Badge): no hay tokens de
   tinte verde/naranja en tokens.css y crearlos para un solo consumidor
   rompería la regla inversa (un token sin consumidor). */
.stepper {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.stepper__item {
  flex: 1 1 96px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-1);
  border: 2px solid transparent;
  border-radius: var(--radius-sm);
  text-align: center;
}
.stepper__icono {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: var(--radius-full);
  background: var(--fill-quaternary);
  color: var(--label-secondary);
}
.stepper__etiqueta {
  font-size: var(--text-caption1-size);
  line-height: var(--text-caption1-lh);
  font-weight: 600;
  color: var(--label);
  max-width: 100%;
}
.stepper__posicion {
  font-size: var(--text-caption2-size);
  line-height: var(--text-caption2-lh);
  color: var(--label-secondary);
}
/* Pasado: el mismo par texto/tinte que .badge--ok. */
.stepper__item--pasado .stepper__icono { background: rgba(52, 199, 89, 0.18); color: var(--text-green); }
/* Actual: acento del sistema (el color de POSICIÓN de toda la navegación del
   shell — tab bar y sidebar usan --accent para la pestaña activa) + borde
   sólido, el indicador no textual. El color semántico del ESTADO ya viaja en
   la badge de la cabecera; el stepper marca la posición, no el color del
   estado. */
.stepper__item--actual { border-color: var(--accent); }
.stepper__item--actual .stepper__icono { background: var(--accent-tint); color: var(--accent-text); }
.stepper__item--actual .stepper__etiqueta { font-weight: 700; }
.stepper__item--actual .stepper__posicion { color: var(--accent-text); }

/* --- Placeholder de firma del cliente (ficha de OT, sub-tarea 4.13) ---
   No es una firma real (el spec lo deja fuera): es el espacio reservado con
   la forma de la zona de firma. El borde punteado va en --control-border y
   no en un gris suelto: es una señal NO textual (la zona existe aunque
   nadie haya firmado) y ese token es el que ya pasa el 3:1 de 1.4.11 en las
   cuatro superficies del sistema. */
.firma-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 96px;
  border: 2px dashed var(--control-border);
  border-radius: var(--radius-md);
  color: var(--label-secondary);
  text-align: center;
}

/* --- Calendario mensual de rutinas (módulo preventivo, fase 2, T5) ---
   Componente nuevo (ruling P6: autorizado con medición). Grilla de 7 columnas
   con `minmax(0, 1fr)`, que es lo que impide el scroll horizontal: una celda
   con `min-width: 0` puede achicarse hasta el ancho de su fila y NUNCA empuja
   la grilla más allá del contenedor (4.4.2 prohíbe el scroll).
   MEDICIÓN: a 320px el área de la vista es 288px y la card le resta 34 más;
   7 columnas + 6 gaps de 4px dejan ~33px por celda. Con chips compactados
   (borde 2px, padding mínimo) el contenido entra envolviendo: medido, a 320
   y a 390 el scrollWidth del documento queda en el ancho del viewport y
   CHECK.recorte no reporta recortes — la serie envuelve después de su guion
   en la celda más angosta en lugar de cortarse con puntos suspensivos, y el
   dato completo (plan, sucursal, responsable) viaja en el title y en el
   aria-label de cada chip.
   El color de la rutina es el BORDE del chip (no textual, 3:1 — los tokens
   --serie-* miden >= 4.5:1 contra --bg-grupo en los dos temas) y nunca el
   único portador: el chip lleva la serie como texto, el title/aria-label el
   detalle completo, y la leyenda nombra cada sucursal junto a su color. */
.calendario {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--space-1);
}
.calendario__cabecera {
  text-align: center;
  font-size: var(--text-caption2-size);
  line-height: var(--text-caption2-lh);
  font-weight: 600;
  color: var(--label-secondary);
  padding: var(--space-1) 0;
}
.calendario__dia {
  background: var(--bg-grupo);
  border: 1px solid var(--separator);
  border-radius: var(--radius-xs);
  padding: 2px 1px;
  min-height: 48px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  align-items: stretch;
}
.calendario__dia--fuera { border-style: dashed; opacity: 0.5; }
.calendario__dia--hoy { border-color: var(--accent); }
/* El número de HOY lleva el relleno de acento con blanco encima — el mismo
   par medido de .btn--primary (>= 4.5:1 en los dos temas) — y el texto "hoy"
   viaja además en el árbol de accesibilidad (chart__alt). */
.calendario__dia--hoy .calendario__numero {
  background: var(--accent-fill);
  color: var(--on-accent);
  border-radius: var(--radius-full);
  padding: 0 var(--space-1);
}
.calendario__numero {
  align-self: center;
  font-size: var(--text-caption1-size);
  line-height: var(--text-caption1-lh);
  font-weight: 600;
  color: var(--label);
}
.calendario__rutinas {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
/* El chip es ancho completo de la celda y su texto PUEDE envolver después del
   guion de la serie ('M-' / '003'): a 320px una celda deja ~25px de texto y
   'M-003' necesita ~34 — recortar con puntos suspensivos perdería al equipo
   de la vista, mientras que envolver conserva el dato completo. MEDIDO: a
   320 y 390 el scrollWidth queda en el ancho del viewport y CHECK.recorte no
   reporta recortes. El detalle completo (plan, sucursal, responsable) viaja
   en el title y en el aria-label de todos modos. */
.calendario__rutina {
  display: block;
  max-width: 100%;
  border-left: 2px solid var(--accent);
  border-radius: 2px;
  padding: 1px 2px;
  background: var(--fill-quaternary);
  color: var(--label);
  font-size: var(--text-caption2-size);
  line-height: var(--text-caption2-lh);
  overflow-wrap: break-word;
  word-break: normal;
  text-align: left;
}
.calendario__mas {
  font-size: var(--text-caption2-size);
  line-height: var(--text-caption2-lh);
  font-weight: 600;
  color: var(--label);
  padding: 1px var(--space-1);
}
.calendario__leyenda {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.calendario__leyenda-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-footnote-size);
  line-height: var(--text-footnote-lh);
  color: var(--label);
}
.calendario__muestra {
  width: 14px;
  height: 14px;
  flex: none;
  border-radius: 3px;
  border: 1px solid var(--separator);
}

/* --- Lista MEDIA: opt-in de la lista de planes del preventivo (T5) ---
   La regla compartida convierte la tabla en cards a <768px. La fila de planes
   lleva 9 datos y su min-content medido es ~720px (medido con table-layout
   auto + width: min-content sobre la fila real): a 768px la tabla disponible
   es de 672px y la tabla recortaría contra el overflow:hidden del contenedor
   (CHECK.recorte). Convierte ANTES, a cards por debajo de 1024px, donde cada
   fila entra completa; a partir de 1024 la tabla tiene 128px por columna y
   entra sin recorte. Mismo patrón declarado de un solo lado que
   .lista--ancha (T3); el breakpoint compartido de 767 queda intacto. */
@media (max-width: 1023px) {
  .lista--media .lista__tabla-contenedor {
    background: transparent;
    border: 0;
    border-radius: 0;
    overflow: visible;
  }
  .lista--media .lista__tabla, .lista--media .lista__tabla tbody { display: block; }
  .lista--media .lista__tabla thead { display: none; }
  .lista--media .lista__tabla tr {
    display: block;
    background: var(--bg-grupo);
    border: 1px solid var(--separator);
    border-radius: var(--radius-md);
    padding: var(--space-2) 0;
  }
  .lista--media .lista__tabla tbody tr + tr { margin-top: var(--space-3); }
  .lista--media .lista__tabla td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-1) var(--space-4);
    border-bottom: 0;
  }
  .lista--media .lista__tabla td::before {
    content: attr(data-etiqueta);
    flex: none;
    color: var(--label-secondary);
    font-size: var(--text-footnote-size);
    line-height: var(--text-footnote-lh);
  }
}

/* --- Avatares --- */
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; flex: none;
  border-radius: 50%;
  background: var(--accent-tint);
  color: var(--accent-text);
  font-size: var(--text-footnote-size);
  font-weight: 600;
  text-transform: uppercase;
}
.avatar-group { display: inline-flex; --anillo-avatar: var(--bg-grupo); }
/* El borde de un avatar solapado existe para SEPARARLO del de al lado: tapa el
   tramo en que dos avatares se pisan (`margin-left: -10px`), asi que su unica
   senal es CONTRASTAR con la superficie sobre la que el grupo vive. Un token
   fijo solo puede acertar en una de las dos colocaciones. Medido antes de esta
   correccion, con `--bg-grupo` fijo: sobre la pagina 1.12 en claro y 1.23 en
   oscuro (ruling 92), pero DENTRO de una card —que ya es `--bg-grupo`— compone
   **1.00 en los dos temas** y el separador desaparece, que es el mismo defecto
   que los rulings 46 y 56 corrigieron dos veces con --bg-primary. El dashboard
   es la primera pantalla donde un grupo puede caer dentro de una card, asi que
   el anillo lee una variable que cada contenedor ajusta: la base es el color de
   grupo (lo que hoy ya funciona sobre la pagina y por eso NO cambia) y los
   contenedores que pintan su propia superficie (card, list-group, sheet, los
   tres `--bg-grupo`) la cambian por el de la pagina. El anillo queda entonces
   con el mismo 1.12 claro / 1.23 oscuro en las dos colocaciones. Medido, no
   estimado: la variante literal del brief —que dejaba la card en `--bg-grupo`—
   daba 1.00 en las dos y bajaba la pagina de 1.12 a 1.00. */
.card .avatar-group, .list-group .avatar-group, .sheet .avatar-group {
  --anillo-avatar: var(--bg-pagina);
}
.avatar-group .avatar { border: 2px solid var(--anillo-avatar); margin-left: -10px; }
.avatar-group .avatar:first-child { margin-left: 0; }

/* --- Esqueletos, vacios y toast --- */
.skeleton {
  background: var(--fill-quaternary);
  border-radius: var(--radius-xs);
  animation: latido 1.4s var(--ease-ios) infinite;
}
@keyframes latido { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }

.empty {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-4);
  text-align: center;
  color: var(--label-secondary);
}

.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--space-4));
  transform: translateX(-50%);
  z-index: 60;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--separator);
  border-radius: var(--radius-full);
  padding: var(--space-2) var(--space-4);
  min-height: 44px;
  display: flex; align-items: center;
  box-shadow: var(--shadow-md);
  font-size: var(--text-subhead-size);
}

/* --- Nav bar --- */
.navbar {
  position: sticky;
  top: 0;
  z-index: 30;
  min-height: var(--navbar-h);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0 var(--space-4);
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--separator);
}

/* --- Tab bar movil (solo bajo 768px) ---
   Aca NO se declara el `display`: la visibilidad por ancho de la tab bar vive en
   un solo lugar, el bloque del shell (mas abajo), que pone el valor de movil como
   base y lo apaga en su unico override de 768. Declararlo tambien aca era la
   segunda mitad de un contrato que quedaba escrito dos veces, donde cambiar un
   solo lado rompe el complemento en silencio. La muestra de la guia se pinta
   igual porque fija su propio `display` en estilo en linea. */
.tabbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 35;
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-top: 1px solid var(--separator);
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
}
.tabbar__item {
  flex: 1;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 2px;
  min-width: 44px;
  height: var(--tabbar-h);
  color: var(--label-secondary);
  text-decoration: none;
  font-size: var(--text-caption2-size);
  line-height: var(--text-caption2-lh);
}
/* La pestaña ACTIVA no puede señalarse solo con color. Medido, en los dos temas:
   `aria-current="page"` cambiaba UNICAMENTE `color`, con el mismo
   `font-weight: 400` y sin fondo ni borde. El ratio de luminancia entre los dos
   colores de texto da 3.01:1 en claro y 1.33:1 en oscuro — o sea que en el tema
   OSCURO, que es el que el prototipo abre por defecto, la pestaña actual es
   indistinguible sin percibir el tono, y por debajo de 768px esta barra ES toda
   la navegacion. Es un incumplimiento del 4.4.4 del propio spec ("Ningun estado
   se comunica solo por color").
   El primer arreglo fue el PESO 600 (el patron del sidebar) y la revision de la
   ola lo midio roto a 320px: la etiqueta "Dashboard" pasa a medir 67.1px en una
   celda de 64px, o sea el texto se sale 1.5px de su celda y `CHECK.overflow()` lo
   reporta (1 fallo, 174 medidos; `bodyScrollWidth` sigue en 320). Se cambio por
   una señal GEOMETRICA, que no depende de las metricas del texto: una barra de
   4px arriba de la pestaña activa. Ni el tamaño ni el corte del texto son el
   precio de cumplir 1.4.4, y por eso el peso vuelve a 400: con las dos señales
   puestas, la que sobra sigue desbordando.
   El color va en `--accent` (no en `--accent-text`): es un elemento NO textual
   —un indicador—, y `--accent` es el token que el proyecto reserva para eso. */
.tabbar__item[aria-current="page"] { color: var(--accent-text); position: relative; }
.tabbar__item[aria-current="page"]::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 24px;
  height: 4px;
  border-radius: 0 0 var(--radius-xs) var(--radius-xs);
  background: var(--accent);
}

/* --- Shell: sidebar, contenedor de vista y los tres anchos ---
   UN solo markup para los tres anchos (movil, tableta, escritorio): lo que
   cambia es la disposicion, no el DOM. El sidebar se pinta siempre y su
   visibilidad la decide el ancho, porque el router y `pintarNavegacionActiva`
   consultan `.sidebar__item` sin saber en que breakpoint estan.

   MOBILE-FIRST Y UNA SOLA VEZ POR ANCHO: la base es el movil y los dos unicos
   overrides son de `min-width`. La frontera de 768 no vive como pareja
   `min-width: 768px` + `max-width: 767px`: con las dos mitades, cambiar una sola
   rompe el complemento sin que nada avise. */
.sidebar { display: none; }
.sidebar__grupo { margin-bottom: var(--space-4); }
.sidebar__titulo {
  font-size: var(--text-caption2-size);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  /* Es TEXTO: va en secundario. El terciario compone 1.72:1 sobre blanco. */
  color: var(--label-secondary);
  padding: 0 var(--space-3) var(--space-2);
}
.sidebar__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 44px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--label);
  text-decoration: none;
  font-size: var(--text-subhead-size);
  white-space: nowrap;
}
.sidebar__item:hover { background: var(--fill-quaternary); }
.sidebar__item[aria-current="page"] { background: var(--accent-tint); color: var(--accent-text); font-weight: 600; }
.sidebar__item svg { flex: none; }

/* El anillo de foco de la NAVEGACION es explicito, y no el del sistema. Medido
   en PIXELES —captura del driver leida pixel a pixel, no el color computado—: el
   `outline: auto` que el navegador dibuja para estos dos componentes se pinta
   rgb(16,16,16) en LOS DOS temas, o sea 17.6:1 sobre la superficie clara del
   sidebar (#FFFFFF) y 1.07:1 sobre la oscura (#1C1C1E). En tema oscuro el UNICO
   indicador de foco de los 11 items de navegacion era invisible, y con el sidebar
   como rail (768-1279) esa es TODA la navegacion. Se les da el anillo de acento
   que ya usan los demas controles: 4.06:1 en claro y 8.55:1 en oscuro. El offset
   es NEGATIVO para que el anillo se dibuje adentro y no lo recorte el borde del
   sidebar, mismo criterio que `.list-row`. */
.sidebar__item:focus-visible,
.tabbar__item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Las etiquetas nacen OCULTAS: es el valor de movil Y el del rail de tableta, y
   el sidebar completo (>=1280) las muestra con su override. Sin la regla base, a
   390px el span computa `inline` aunque su sidebar padre sea display:none
   —`display` no se hereda, asi que ocultar al padre NO cambia el computed del
   hijo— y la verificacion de los tres anchos mide etiquetas "visibles" en un
   sidebar que nadie ve. Medido: sin esta regla, 390 daba etiquetasSidebar true
   contra el false que fija la tabla del brief. */
.sidebar__label, .sidebar__titulo { display: none; }

/* La nav bar lleva el MISMO markup en los tres anchos, y su contenido no entra
   a 320px: medido, el viewport movil se estiraba a 513px de ancho de layout y
   CHECK.overflow() reportaba cinco elementos fuera de pantalla. Dos causas, las
   dos de layout y ninguna de contenido:
     1. `.btn` no fija `flex`, asi que TODOS los botones de la barra encogian a
        la vez —incluidos los de solo icono, que por eso dejaban de medir 44x44
        y perdian el area tactil—.
     2. El texto del contexto ("Ascensores del Norte · Todas las sucursales") es
        `nowrap`, y un item flex NO baja de su min-content sin `min-width: 0`.
   El unico elastico es el boton de contexto: cede el espacio y su texto se
   recorta con puntos suspensivos, que es lo correcto para un control que sigue
   SIEMPRE visible (spec 2.4) y cuyo valor completo se lee en el sheet que ese
   mismo boton abre.
   Y el aire de la barra: en movil es mas corto (a 320px el gap de escritorio
   entre seis controles se come 60px y deja al contexto sin ancho util); el
   override de 768 le devuelve los valores del componente. Un valor por ancho. */
.navbar .btn { flex: none; }
#btn-contexto { flex: 0 1 auto; min-width: 0; }
#contexto-texto { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.navbar { gap: var(--space-2); padding: 0 var(--space-3); }

/* La tab bar del shell: VISIBLE en movil (base) y apagada por el unico override
   de 768. Este es el unico lugar del archivo donde se declara su visibilidad por
   ancho. */
.tabbar { display: flex; }

.shell {
  display: flex;
  min-height: 100dvh;
  /* --bg-pagina, NO --bg-secondary. El shell ES la pagina: con --bg-secondary
     el viewport entero pintaba el color de GRUPO en oscuro (#1C1C1E), y como
     cards, list-groups y (a >=1280) el sidebar usan --bg-grupo, quedaban los
     cuatro en el mismo color y solo los separaba un hairline de 1px: medido,
     CHECK.ratio(shell, .card) = 1.0. Es el mismo error que este archivo ya
     corrigio en .field-group y en .card. */
  background: var(--bg-pagina);
}
.shell__principal { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.vista {
  flex: 1;
  padding: var(--space-4);
  /* Deja aire para la tab bar fija en movil. */
  padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + var(--space-6));
}

/* Skip link del shell (spec 1.7). Es el primer enfocable del documento y el
   primer hijo de `.shell`; `position: fixed` lo saca del flujo, y no es un
   detalle: `.shell` es `display: flex`, asi que un hijo en el flujo se comeria
   el ancho de `.shell__principal`. Oculto por RECORTE (`clip-path`) y no por
   `display:none` ni `visibility:hidden`: las dos ultimas lo sacan del arbol de
   accesibilidad, y un skip link que un lector de pantalla no puede encontrar no
   sirve para nada. Tampoco se lo empuja fuera de pantalla con un `left`
   negativo: `CHECK.overflow()` mide `left < -1` y lo reportaria como desborde.
   Aparece al recibir foco de TECLADO (`:focus-visible`), con su propio anillo
   porque este archivo declara el foco por componente y no de forma global. */
.skip-link {
  position: fixed;
  z-index: 65;
  top: var(--space-3);
  left: var(--space-3);
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 var(--space-4);
  border: 1px solid var(--separator);
  border-radius: var(--radius-md);
  background: var(--bg-grupo);
  color: var(--accent-text);
  font-family: var(--font-sans);
  font-size: var(--text-subhead-size);
  line-height: var(--text-subhead-lh);
  text-decoration: none;
  box-shadow: var(--shadow-md);
}
.skip-link:not(:focus-visible) { clip-path: inset(50%); }
.skip-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Alternativa textual de un grafico (spec 1.6: "cada grafico con su alternativa
   textual ... para no depender del color"). El `<canvas>` queda `aria-hidden`
   —lo que dibuja son pixeles, no texto— y el dato viaja en el `<figcaption>`.
   Es el mismo patron del skip link y por el mismo motivo: recorte visual, no
   `display:none`, para que el texto siga en el arbol de accesibilidad.
   `1x1` + `position: absolute` es lo que evita que el texto, que es largo, ocupe
   lugar en la tarjeta: el recorte es de pintado, no de layout. */
.chart { position: relative; margin: 0; }
.chart__alt {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  clip-path: inset(50%);
}

/* Tablet: rail de iconos. */
@media (min-width: 768px) {
  .sidebar {
    display: flex;
    flex-direction: column;
    width: var(--sidebar-rail-w);
    flex: none;
    position: sticky;
    top: 0;
    height: 100dvh;
    overflow-y: auto;
    background: var(--bg-grupo);
    border-right: 1px solid var(--separator);
    padding: var(--space-3) var(--space-2);
  }
  .sidebar__item { justify-content: center; padding: 0; }
  .navbar { gap: var(--space-3); padding: 0 var(--space-4); }
  .vista { padding-bottom: var(--space-6); }
  .tabbar { display: none; }
}

/* Escritorio: sidebar completa. */
@media (min-width: 1280px) {
  .sidebar { width: var(--sidebar-w); padding: var(--space-4) var(--space-3); }
  .sidebar__label, .sidebar__titulo { display: block; }
  .sidebar__item { justify-content: flex-start; padding: 0 var(--space-3); }
}

/* No hay bloque `@media (max-width: 767px)`: la visibilidad de la tab bar y el
   aire de la nav bar en movil son el VALOR BASE de cada regla, arriba, y el
   override de 768 los cambia. Un solo lado por frontera. */

/* --- Segmentado de sub-vistas a nivel de PANTALLA (módulo RRHH, fase 2, T9) ---
   Componente nuevo (ruling P6: autorizado con medición). El preventivo usó el
   segmentado de página con DOS opciones; RRHH necesita CINCO (Personal,
   Asistencia, Turnos, Certificaciones, Ausencias) y su min-content MEDIDO con
   el wrap anulado es 315px (la suma del contenido de las cinco opciones): a
   320px el área de la vista es 288px y, sin wrap, el scrollWidth del documento
   sube a 331px — 11px de desborde, la MISMA clase que la T3 midió para ocho
   opciones de estado (356px a 320). Es la MISMA solución de esa medición, con
   la MISMA excepción acotada: el componente global .segmented queda como está;
   SOLO este envoltorio envuelve. Con flex-basis auto el tamaño hipotético es
   el del CONTENIDO, así que las cinco opciones envuelven de verdad: a 320px
   quedan dos filas (Personal + Asistencia + Turnos / Certificaciones +
   Ausencias) y CHECK.overflow queda en cero a 320 y a 390 (medido). */
.rrhh__segmentado {
  flex-wrap: wrap;
  width: 100%;
  max-width: 560px;
}
.rrhh__segmentado .segmented__opcion { flex: 1 1 auto; }

/* --- Mapa (Leaflet 1.9.4, fase 2 T13) ---------------------------------
   Únicamente las superficies que leaflet.css pinta con VALORES FIJOS y que el
   sistema no puede alcanzar de otra manera: la tipografía y el fondo del
   contenedor, los botones de zoom y la atribución. Medido en tema oscuro
   antes de este bloque: el contenedor quedaba #ddd y los controles blanco
   puro (#fff de leaflet.css) sobre la página negra — superficies fuera del
   sistema en los dos temas. Colores SIEMPRE por token (sin dark:); leaflet.css
   carga antes de components.css, así que a igual especificidad gana el token.
   Los tiles son imagen y quedan fuera de la medición de contraste; el panel y
   la leyenda son DOM del sistema y sí se miden. */
.leaflet-container {
  font-family: var(--font-sans);
  background: var(--bg-grupo);
}
.leaflet-container .leaflet-control-attribution {
  background: var(--glass-bg);
  color: var(--label-secondary);
}
.leaflet-control-attribution a {
  background: transparent;
  color: var(--accent-text);
  text-shadow: none;
}
.leaflet-bar a {
  background: var(--bg-grupo);
  border-bottom-color: var(--separator);
  color: var(--label);
}
.leaflet-bar a:hover {
  background: var(--fill-quaternary);
}
