/* ====================================================================
   TOKENS DEL SISTEMA VISUAL

   UN solo lugar donde viven los valores. Ninguna regla de abajo escribe
   un color, un radio o una familia tipografica directamente --
   `tests/test_marca.py` hace fallar la suite si alguna lo hace.

   NOMBRADOS POR ROL, NO POR APARIENCIA, y es deliberado: el modo oscuro
   ya esta decidido como pieza siguiente. Un token llamado
   `--color-gris-claro` que en modo oscuro vale casi negro es un nombre
   que miente, y renombrar cincuenta usos despues es exactamente el
   trabajo que esta pieza existe para evitar. Nombrar bien no es
   construir el modo oscuro: acá NO hay bloque vacio para un tema
   alterno -- eso seria un gancho, y este proyecto no los pone.

   Los VALORES salen de docs/IDENTIDAD_VISUAL.md (extraido del CSS real
   de eicom.com.co el 2026-09-01). Esta pieza es PLOMERIA: tokeniza lo
   que ya existia, no propone una paleta nueva.
   ==================================================================== */

:root {
  /* Sin esto, los controles de formulario, las barras de
     desplazamiento y el autocompletado del navegador se quedan en
     claro y la pantalla queda a medias. */
  color-scheme: light dark;

  /* --- Superficies y texto ---------------------------------------- */
  --color-superficie: #f4f5f7;          /* fondo de pagina */
  --color-superficie-elevada: #fff;     /* tarjetas, formularios */
  --color-superficie-hundida: #eef2f9;  /* filas alternas, badges */
  --color-texto: #1a1a1a;
  --color-texto-secundario: #555;
  --color-texto-sobre-acento: #fff;
  --color-borde: #ccc;
  --color-borde-sutil: #eee;
  --color-sombra: rgba(0, 0, 0, 0.12);

  /* --- Acento de marca -------------------------------------------- */
  --color-acento: #294d8b;              /* azul corporativo */
  --color-acento-fuerte: #1e3a6e;       /* estados hover/activo */

  /* --- Marca secundaria -------------------------------------------
     El ambar. Su uso principal es el aviso de modo soporte, y su
     contraste sobre fondo claro esta MEDIDO y anotado como candidato
     en docs/RETOMAR.md -- no se cambio en esta pieza a proposito. */
  --color-marca-secundaria: #8f5c07;
  --color-marca-secundaria-fuerte: #6b4505;
  --color-marca-secundaria-suave: #fdf6ea;

  /* --- Semanticos -------------------------------------------------- */
  --color-exito: #1a7d3a;
  --color-exito-suave: #e8f5ec;
  --color-error: #a12622;
  --color-error-suave: #fdecea;
  --color-error-borde: #f5c6c3;
  --color-exito-borde: #b8e0c4;
  --color-borde-suave: #ddd;
  --color-borde-neutro: #8a94ab;

  /* --- Radios ------------------------------------------------------
     Solo los que YA estaban repetidos: 4px (9 usos) y 8px (6). El 6px
     aparece UNA vez y no entra -- ponerle nombre a un valor unico es
     inventar sistema, no describirlo. */
  --radio-chico: 4px;
  --radio-grande: 8px;

  /* --- Espaciado ---------------------------------------------------
     ESCALA DE HECHO, no diseñada: son los valores que ya estaban
     repetidos en la hoja, ordenados por tamaño. No es una progresion
     coherente (0.4 y 0.75 y 0.8 conviviendo no es una escala, es
     acumulacion) y el nombre no pretende que lo sea. Nombrarlos permite
     cambiarlos; ordenarlos de verdad es trabajo de la pieza de
     componentes. */
  --espacio-1: 0.25rem;   /* 5 usos */
  --espacio-2: 0.4rem;    /* 6 */
  --espacio-3: 0.5rem;    /* 8 */
  --espacio-4: 0.75rem;   /* 4 */
  --espacio-5: 1rem;      /* 11 */
  --espacio-6: 1.5rem;    /* 9 */

  /* --- Tipografia --------------------------------------------------
     Las dos familias salen de las variables CSS del tema real
     (`--font-heading--family` / `--font-body--family`), no de mirar el
     sitio. Los tamaños, igual que el espaciado: solo los repetidos. */
  --fuente-titulos: Montserrat, "Plus Jakarta Sans", system-ui, sans-serif;
  --fuente-cuerpo: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI",
    Roboto, sans-serif;
  /* Monoespaciada, para el campo de código. La ÚLTIMA palabra --la
     genérica `monospace`-- no es relleno: es lo que obliga al navegador
     a resolver alguna monoespaciada como último recurso. Sin ella, un
     equipo que no tenga ninguna de las cuatro anteriores caería en la
     fuente de párrafo, los caracteres dejarían de tener ancho uniforme y
     la cuadrícula del campo se desalinearía de verdad (ver el bloque
     `.campo-codigo` más abajo). */
  --fuente-mono: ui-monospace, "Cascadia Mono", "Segoe UI Mono", Menlo,
    monospace;
  --texto-sm: 0.85rem;    /* 6 usos */
  --texto-md: 0.9rem;     /* 9 */
  --texto-base: 1rem;     /* 4 */
  --peso-fuerte: 600;     /* 5 usos */

  /* --- Movimiento --------------------------------------------------
     CUATRO tokens, derivados de lo que el campo de codigo necesita --
     no un catalogo. La pieza de marca midio CERO transiciones en la
     hoja y dejo escrito que definirlas entonces habria sido
     anticipacion: el primer componente real las elegiria distintas.
     Este es ese componente.

     --curva-resorte sobrepasa y vuelve (el 1.56 del tercer punto de
     control): es la que da la sensacion de resorte al completarse el
     codigo. Las otras tres son lineales-hacia-fuera, para cambios de
     estado que no deben llamar la atencion.

     TODO componente usa estos tokens y NINGUNO escribe una duracion a
     mano -- `tests/test_campo_codigo.py` lo exige. Sin esa regla, el
     bloque de prefers-reduced-motion de mas abajo se evade escribiendo
     el valor directo, que es justo lo que pasaria. */
  --duracion-rapida: 120ms;
  --duracion-media: 240ms;
  --curva-salida: cubic-bezier(0.2, 0, 0, 1);
  --curva-resorte: cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* --- TEMA OSCURO -----------------------------------------------------
   Los valores oscuros aparecen en DOS bloques y eso es inevitable en CSS
   puro: no se puede reusar un bloque de declaraciones entre un selector
   de atributo y una media query. La duplicacion es un riesgo real --
   agregar un token a uno y olvidar el otro deja ese token heredando el
   valor CLARO en silencio, que en modo oscuro puede ser texto casi negro
   sobre fondo casi negro: invisible, sin error, sin que nadie se entere.

   Por eso NO se deja como convencion: `tests/test_tema_oscuro.py`
   (criterio N1) exige que los TRES bloques definan exactamente el mismo
   conjunto de nombres, y falla nombrando el que falte. Una duplicacion
   inevitable convertida en invariante verificada.

   LOS VALORES SE ELIGIERON MIDIENDO, no a ojo: las quince combinaciones
   reales de texto sobre fondo se calcularon contra el umbral AA (4.5)
   ANTES de escribir esto. La mas ajustada da 6.36.
   -------------------------------------------------------------------- */

:root[data-tema="oscuro"] {
  --color-superficie: #12151a;
  --color-superficie-elevada: #1b2028;
  --color-superficie-hundida: #232a35;
  --color-texto: #e6e9ef;
  --color-texto-secundario: #9aa4b4;
  --color-texto-sobre-acento: #0f1216;
  --color-borde: #3a434f;
  --color-borde-sutil: #2a323d;
  --color-borde-suave: #3a434f;
  --color-borde-neutro: #5b6675;
  --color-sombra: rgba(0, 0, 0, 0.5);
  --color-acento: #7aa2e8;
  --color-acento-fuerte: #a8c4f2;
  --color-marca-secundaria: #e8a530;
  --color-marca-secundaria-fuerte: #f0b350;
  --color-marca-secundaria-suave: #2e2413;
  --color-exito: #5fd18a;
  --color-exito-suave: #16301f;
  --color-exito-borde: #2f5c40;
  --color-error: #f08a84;
  --color-error-suave: #331a19;
  --color-error-borde: #6b3330;
}

@media (prefers-color-scheme: dark) {
  /* `:not([data-tema="claro"])` y no `:root` a secas: si el usuario
     eligio CLARO explicitamente, su eleccion manda sobre la preferencia
     del sistema. Sin esa exclusion, el tercer estado no existiria --
     elegir "claro" en un sistema oscuro no haria nada. */
  :root:not([data-tema="claro"]) {
    --color-superficie: #12151a;
    --color-superficie-elevada: #1b2028;
    --color-superficie-hundida: #232a35;
    --color-texto: #e6e9ef;
    --color-texto-secundario: #9aa4b4;
    --color-texto-sobre-acento: #0f1216;
    --color-borde: #3a434f;
    --color-borde-sutil: #2a323d;
    --color-borde-suave: #3a434f;
    --color-borde-neutro: #5b6675;
    --color-sombra: rgba(0, 0, 0, 0.5);
    --color-acento: #7aa2e8;
    --color-acento-fuerte: #a8c4f2;
    --color-marca-secundaria: #e8a530;
    --color-marca-secundaria-fuerte: #f0b350;
    --color-marca-secundaria-suave: #2e2413;
    --color-exito: #5fd18a;
    --color-exito-suave: #16301f;
    --color-exito-borde: #2f5c40;
    --color-error: #f08a84;
    --color-error-suave: #331a19;
    --color-error-borde: #6b3330;
  }
}


/* Estilo mínimo del panel -- sin framework ni paso de build, a
   propósito (ver "cuatro preguntas" de CLAUDE.md: un framework CSS no
   hace falta para tres pantallas de formularios/tablas). */

* {
  box-sizing: border-box;
}

body {
  /* "Plus Jakarta Sans" es la tipografía de cuerpo real de eicom.com.co
     (docs/IDENTIDAD_VISUAL.md) -- el stack de system-ui queda como
     respaldo real, no decorativo: se usa mientras la fuente carga
     (display=swap en base.html) y para siempre si no llega a cargar. */
  font-family: var(--fuente-cuerpo);
  background: var(--color-superficie);
  color: var(--color-texto);
  margin: 0;
  padding: 2rem 1rem;
}

h1,
h2,
h3 {
  /* Montserrat es la tipografía de encabezados real del sitio -- regla
     nueva, no existía ninguna para encabezados antes de esto. */
  font-family: var(--fuente-titulos);
}

.auth-container {
  max-width: 24rem;
  margin: 3rem auto;
  background: var(--color-superficie-elevada);
  border-radius: var(--radio-grande);
  padding: 2rem;
  box-shadow: 0 1px 3px var(--color-sombra);
}

.panel-form label {
  display: block;
  margin-top: 1rem;
  font-size: var(--texto-md);
  font-weight: var(--peso-fuerte);
}

.panel-form input {
  width: 100%;
  padding: 0.5rem;
  margin-top: 0.25rem;
  border: 1px solid var(--color-borde);
  border-radius: var(--radio-chico);
  font-size: var(--texto-base);
}

.password-field {
  position: relative;
  margin-top: 0.25rem;
}

.password-field input {
  margin-top: 0;
  padding-right: 2.75rem;
}

/* Especificidad .panel-form .password-toggle (dos clases) a propósito
   -- tiene que ganarle a ".panel-form button" (una clase + un
   elemento), que si no la overridea con width:100% y lo convierte en
   un botón que tapa todo el input en vez del ícono flotante. */
.panel-form .password-toggle {
  position: absolute;
  top: 50%;
  right: 0.4rem;
  transform: translateY(-50%);
  width: auto;
  margin-top: 0;
  background: none;
  border: none;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.25rem;
}

.panel-form button {
  margin-top: 1.5rem;
  width: 100%;
  padding: 0.6rem;
  background: var(--color-acento);
  color: var(--color-texto-sobre-acento);
  border: none;
  border-radius: var(--radio-chico);
  font-size: var(--texto-base);
  cursor: pointer;
}

.panel-form button:hover {
  background: var(--color-acento-fuerte);
}

.form-error {
  background: var(--color-error-suave);
  color: var(--color-error);
  border: 1px solid var(--color-error-borde);
  border-radius: var(--radio-chico);
  padding: 0.5rem 0.75rem;
  font-size: var(--texto-md);
  margin-top: 1rem;
}

/* .form-error como etiqueta dentro de una celda del padrón. Es un estilo de
   bloque (padding vertical, margin-top): en un <span> en línea, cuando la
   celda se angosta y la etiqueta baja de línea, su padding se monta sobre el
   texto de arriba. Medido en Chrome el 2026-10-07, al sumar la columna del
   botón de modo soporte. inline-block le da su propio espacio al partirse. */
.etiqueta-en-celda {
  display: inline-block;
  margin-top: 0;
}

.form-success {
  background: var(--color-exito-suave);
  color: var(--color-exito);
  border: 1px solid var(--color-exito-borde);
  border-radius: var(--radio-chico);
  padding: 0.5rem 0.75rem;
  font-size: var(--texto-md);
  margin-top: 1rem;
}

.backup-code-toggle {
  margin-top: 1.25rem;
  font-size: var(--texto-md);
}

.backup-code-toggle summary {
  cursor: pointer;
  color: var(--color-acento);
}

.panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  max-width: 40rem;
  margin: 0 auto 1.5rem auto;
}

.panel-header button {
  padding: 0.4rem 0.8rem;
  border: 1px solid var(--color-borde);
  border-radius: var(--radio-chico);
  background: var(--color-superficie-elevada);
  cursor: pointer;
}

#device-list-container {
  max-width: 60rem;
  margin: 0 auto;
  background: var(--color-superficie-elevada);
  border-radius: var(--radio-grande);
  padding: 1.5rem;
  box-shadow: 0 1px 3px var(--color-sombra);
}

.device-list-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
  font-size: var(--texto-md);
  color: var(--color-texto-secundario);
}

.device-list-toolbar button,
.device-table button {
  padding: 0.4rem 0.8rem;
  border: none;
  border-radius: var(--radio-chico);
  background: var(--color-acento);
  color: var(--color-texto-sobre-acento);
  cursor: pointer;
  font-size: var(--texto-md);
}

.device-list-toolbar button:hover,
.device-table button:hover {
  background: var(--color-acento-fuerte);
}

.device-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--texto-md);
}

.device-table th,
.device-table td {
  text-align: left;
  padding: 0.5rem;
  border-bottom: 1px solid var(--color-borde-sutil);
}

.rename-form {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.rename-form input {
  padding: 0.3rem;
  border: 1px solid var(--color-borde);
  border-radius: var(--radio-chico);
  font-size: var(--texto-md);
}

.action-status {
  font-size: var(--texto-sm);
}

.status-ok {
  color: var(--color-exito);
}

.status-error {
  color: var(--color-error);
}

.panel-container {
  max-width: 40rem;
  margin: 0 auto;
  background: var(--color-superficie-elevada);
  border-radius: var(--radio-grande);
  padding: 1.5rem;
  box-shadow: 0 1px 3px var(--color-sombra);
}

.enroll-qr {
  display: block;
  margin: 1.5rem 0;
  width: 16rem;
  height: 16rem;
}

/* Páginas públicas (producto/soporte/contacto/privacidad) -- mismos
   tokens de color y tipografía que el resto del panel, sin valores
   nuevos, para no duplicar lo que app/core/marca.py y este archivo ya
   definen. */
.aviso-horario {
  margin-top: 1.5rem;
  padding: 1rem;
  background: var(--color-superficie-hundida);
  border-radius: var(--radio-chico);
  font-size: var(--texto-md);
}

.aviso-legal {
  margin-top: 2rem;
  color: var(--color-texto-secundario);
  font-size: var(--texto-sm);
}

/* Pie de página de panel/base.html -- visible en TODA pantalla que
   extienda ese layout (login/mfa/forgot/reset/_error, las cuatro páginas
   públicas, y cada pantalla autenticada del panel), a propósito: es el
   enlace a /privacidad que C-4 exige adentro del producto, no solo en la
   URL pública. */
.panel-footer {
  margin-top: 2rem;
  padding: 1rem 0;
  text-align: center;
  font-size: var(--texto-sm);
  color: var(--color-texto-secundario);
}

.panel-form select {
  width: 100%;
  padding: 0.5rem;
  margin-top: 0.25rem;
  border: 1px solid var(--color-borde);
  border-radius: var(--radio-chico);
  font-size: var(--texto-base);
}

.panel-form fieldset {
  margin-top: 1.5rem;
  border: 1px solid var(--color-borde-suave);
  border-radius: 6px;
  padding: 1rem;
}

.panel-form legend {
  font-weight: var(--peso-fuerte);
  padding: 0 0.4rem;
}

.current-value,
.field-hint {
  display: block;
  font-size: var(--texto-sm);
  color: var(--color-texto-secundario);
  margin-top: 0.25rem;
}

.panel-readonly p {
  margin: 0.5rem 0;
}

#device-policy-container {
  margin-top: 1.5rem;
}

/* Pantalla de inicio ADMIN (DashboardService.get_admin_summary) --
   clases nuevas, aditivas: no tocan ninguna regla existente de arriba. */

.dashboard-summary {
  max-width: 60rem;
  margin: 0 auto 1.5rem auto;
}

.dashboard-summary h2 {
  font-size: var(--texto-base);
  margin: 1.25rem 0 0.5rem 0;
}

.dashboard-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: 0.75rem;
}

.dashboard-card {
  background: var(--color-superficie-elevada);
  border-radius: var(--radio-grande);
  padding: 1rem;
  box-shadow: 0 1px 3px var(--color-sombra);
  border-left: var(--radio-chico) solid var(--color-borde-neutro);
}

.dashboard-card__value {
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--color-acento);
}

.dashboard-card__label {
  font-size: var(--texto-sm);
  color: var(--color-texto-secundario);
}

/* Tranquila por defecto (sin pendientes) -- solo se distingue cuando
   hay algo que atender, ver .dashboard-card--warning. */
.dashboard-card--ok {
  border-left-color: var(--color-exito);
}

.dashboard-card--ok .dashboard-card__value {
  color: var(--color-exito);
}

/* Ámbar de marca (docs/IDENTIDAD_VISUAL.md), no un color inventado --
   mismo tono que las insignias de certificación del sitio real. */
.dashboard-card--warning {
  border-left-color: var(--color-marca-secundaria);
  background: var(--color-marca-secundaria-suave);
}

.dashboard-card--warning .dashboard-card__value {
  color: var(--color-marca-secundaria);
}

.dashboard-card__action {
  display: block;
  margin-top: 0.4rem;
  font-size: var(--texto-sm);
}

.dashboard-empty-state {
  background: var(--color-superficie-hundida);
  border-left: 4px solid var(--color-acento);
  border-radius: var(--radio-grande);
  padding: 1rem;
  font-size: var(--texto-md);
}

.dashboard-empty-state a {
  color: var(--color-acento);
  font-weight: var(--peso-fuerte);
}

.dashboard-activity {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--texto-sm);
  color: var(--color-texto-secundario);
}

.dashboard-activity li {
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--color-borde-sutil);
}

.dashboard-activity li:last-child {
  border-bottom: none;
}

.dashboard-summary-error {
  font-size: var(--texto-sm);
  color: var(--color-texto-secundario);
  font-style: italic;
}

/* Aviso de "modo soporte" -- pieza A del acceso de SUPER_ADMIN a
   organizaciones. Reusa el ámbar de atención ya establecido arriba
   (.dashboard-card--warning) -- mismo tono de "esto necesita su
   atención", no un color nuevo que justificar. */

.acting-as-banner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  max-width: 60rem;
  margin: 0 auto 1.5rem auto;
  padding: 0.75rem 1rem;
  background: var(--color-marca-secundaria-suave);
  border: 1px solid var(--color-marca-secundaria);
  border-radius: var(--radio-grande);
}

.acting-as-banner__text {
  margin: 0;
}

.acting-as-banner__eyebrow {
  display: block;
  font-size: 0.75rem;
  font-weight: var(--peso-fuerte);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--color-marca-secundaria);
}

/* El nombre del cliente, no la etiqueta de arriba, tiene que ser lo
   primero que salta a la vista -- ver la revisión de copy de esta
   pieza: quien entra a dos clientes seguidos necesita distinguir CUÁL,
   no solo que está en modo soporte. */
.acting-as-banner__org {
  font-size: 1.15rem;
  color: var(--color-texto);
}

.acting-as-banner__exit {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: var(--radio-chico);
  background: var(--color-marca-secundaria);
  color: var(--color-texto-sobre-acento);
  font-weight: var(--peso-fuerte);
  cursor: pointer;
  white-space: nowrap;
}

.acting-as-banner__exit:hover {
  background: var(--color-marca-secundaria-fuerte);
}

/* --- Campo de código -------------------------------------------------
   Un solo <input> con apariencia de casillas. Ver el comentario de
   app/templates/panel/_campo_codigo.html para por qué uno y no seis.

   LA ALINEACIÓN ES RELATIVA A LA FUENTE, NO ABSOLUTA, y eso es lo que
   la sostiene. La preocupación legítima con esta técnica es que las
   cajas dibujadas y los caracteres se desalineen cuando cambia el ancho
   del carácter. Cambia en tres situaciones: si la tipográfica de marca
   no carga y entra la de respaldo, si el usuario tiene zoom, y si el
   usuario configuró un tamaño de fuente base más grande en su sistema
   --que es el caso incómodo, porque quien agrandó la letra por baja
   visión es justamente quien más necesita que el campo funcione.

   Las tres se resuelven con la MISMA decisión: fuente monoespaciada y
   todas las medidas en `ch`. La unidad `ch` es el ancho del carácter
   "0" DE LA FUENTE QUE DE VERDAD SE ESTÁ USANDO, así que se recalcula
   sola: si entra la fuente de respaldo, `ch` cambia con ella; si el
   usuario agranda el tamaño base, `ch` crece proporcionalmente; con
   zoom, todo escala junto. La cuadrícula y los caracteres se mueven
   juntos porque están medidos con la misma regla.

   LO QUE `ch` NO CUBRE, ACOTADO BIEN (corregido 2026-09-07). No es
   "una monoespaciada de respaldo con métricas distintas" -- esa SÍ
   queda cubierta, y por el mismo argumento de arriba: `ch` mide el
   carácter de la fuente que efectivamente se está usando, así que una
   monoespaciada distinta mueve la cuadrícula y los caracteres JUNTOS.
   Lo que el mecanismo no puede resolver por sí solo es que la fuente de
   respaldo no sea monoespaciada EN ABSOLUTO: ahí los dígitos todavía
   podrían alinear --muchas fuentes proporcionales traen cifras de ancho
   tabular-- pero las letras del código de respaldo, que es hexadecimal,
   no.

   Y eso no se anota: se cierra. La pila vive en el token
   `--fuente-mono` (arriba, con los otros dos de tipografía) y termina en
   la palabra genérica `monospace`, que obliga al navegador a resolver
   ALGUNA monoespaciada como último recurso. Sin esa última palabra, un
   equipo que no tenga ninguna de las cuatro fuentes nombradas caería en
   la fuente de párrafo y la ilusión se rompería de verdad.

   Si aun así la ilusión no convence al verla, la salida es quitar
   `.campo-codigo--segmentado` -- el campo sigue funcionando igual,
   porque las cajas son decoración sobre un input normal.

   EL DETALLE CLÁSICO: `letter-spacing` agrega el espacio DESPUÉS del
   último carácter, así que el texto queda descentrado hacia la
   izquierda por medio espacio. Se compensa con el `text-indent`. */
.campo-codigo {
  font-family: var(--fuente-mono);
  font-size: 1.5rem;
  letter-spacing: 0.5ch;
  text-align: center;
}

.campo-codigo--segmentado {
  /* `--codigo-longitud` la fija la plantilla, para que el mismo CSS
     sirva a un campo de 6 y a uno de 8 sin duplicar la regla. */
  width: calc(var(--codigo-longitud) * 2.5ch);
  padding: var(--espacio-4) 0;
  /* Compensa el espacio sobrante que letter-spacing deja a la derecha. */
  text-indent: 0.5ch;
  border: none;
  border-radius: var(--radio-grande);
  background-color: var(--color-superficie-elevada);
  /* Las cajas: una franja de fondo por carácter. Al ser un degradado
     medido en `ch`, se reajusta con la fuente igual que el texto. */
  background-image: repeating-linear-gradient(
    to right,
    var(--color-superficie-hundida) 0,
    var(--color-superficie-hundida) 2ch,
    transparent 2ch,
    transparent 2.5ch
  );
  background-repeat: no-repeat;
  color: var(--color-texto);
  transition: box-shadow var(--duracion-rapida) var(--curva-salida);
}

.campo-codigo--segmentado:focus {
  outline: none;
  box-shadow: 0 0 0 2px var(--color-acento);
}

/* Pulso al completarse. Lo agrega el JavaScript; sin JS el campo
   funciona igual, solo que sin este acuse. */
.campo-codigo--completo {
  animation: codigo-completo var(--duracion-media) var(--curva-resorte);
}

@keyframes codigo-completo {
  from {
    transform: scale(1);
  }
  50% {
    transform: scale(1.03);
  }
  to {
    transform: scale(1);
  }
}

/* Temblor al errar. Se dispara desde la plantilla cuando hay error, no
   desde el JS: asi tambien lo ve quien no tiene JavaScript. */
.campo-codigo[aria-invalid="true"] {
  box-shadow: 0 0 0 2px var(--color-error);
  animation: codigo-error var(--duracion-media) var(--curva-salida);
}

@keyframes codigo-error {
  0%,
  100% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-0.4ch);
  }
  75% {
    transform: translateX(0.4ch);
  }
}

/* --- Botón de copiar --------------------------------------------------
   El enlace de vinculación con Google (panel/_organization_create_result.html)
   llega por htmx a #create-result, FUERA del .panel-form: sin estas reglas, el
   campo, el botón y el aviso se veían con el estilo del navegador. Mismos
   tokens que .panel-form (claro y oscuro salen de los tokens); ningún color
   escrito acá (tests/test_copiar_enlace.py, T5).

   En pantallas angostas el campo se encoge (min-width: 0) y, si no caben los
   dos, el botón baja debajo del enlace en vez de empujarlo fuera de la
   pantalla. El aviso tiene altura mínima: aparecer y borrarse no mueve el
   texto de abajo. */
.enlace-de-vinculacion label {
  display: block;
  margin-top: var(--espacio-5);
  font-size: var(--texto-md);
  font-weight: var(--peso-fuerte);
}

.campo-copiar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--espacio-3);
  margin-top: var(--espacio-1);
}

.campo-copiar input {
  flex: 1 1 16rem;
  min-width: 0;
  padding: 0.5rem;
  border: 1px solid var(--color-borde);
  border-radius: var(--radio-chico);
  background: var(--color-superficie-hundida);
  color: var(--color-texto);
  font-size: var(--texto-base);
}

.boton-copiar {
  flex: 0 0 auto;
  padding: 0.5rem 1rem;
  background: var(--color-acento);
  color: var(--color-texto-sobre-acento);
  border: none;
  border-radius: var(--radio-chico);
  font-size: var(--texto-base);
  cursor: pointer;
}

.boton-copiar:hover {
  background: var(--color-acento-fuerte);
}

.boton-copiar:focus-visible {
  outline: 2px solid var(--color-acento-fuerte);
  outline-offset: 2px;
}

.aviso-copia {
  display: block;
  min-height: 1.4em;
  margin-top: var(--espacio-1);
  font-size: var(--texto-md);
  color: var(--color-texto);
}

/* --- Movimiento reducido ---------------------------------------------
   CONVENCION DEL SISTEMA, no decision de cada componente -- mismo
   criterio que el modo oscuro. Si cada componente lo resolviera, cada
   uno lo resolveria distinto y alguno se olvidaria.

   `prefers-reduced-motion` no es una preferencia estetica: hay gente
   para la que el movimiento en pantalla produce mareo o malestar real.
   Un panel que se usa ocho horas por dia no es lugar para ignorarla.

   Se anulan las DURACIONES, no las animaciones una por una: cualquier
   transicion futura que use los tokens queda cubierta sin tocar este
   bloque. Y `tests/test_campo_codigo.py` exige que ningun componente
   escriba una duracion fuera de los tokens, que es lo que impide
   evadir esto escribiendo el valor a mano. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --duracion-rapida: 0ms;
    --duracion-media: 0ms;
  }

  /* Cinturon y tirantes: si algo tuviera una duracion escrita a mano
     pese al test, esto igual la neutraliza. */
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
