/* ============================================================================
   scalebatch — el CHROME del sitio: barra, controles, pie, y las reglas que
   valen para todas las paginas. Lo especifico de cada pagina se queda en ella.

   Se carga DESPUES de tokens.css, que es el dueno de la paleta.

   Modelo: `products/buildsigned/web/css/site.css` y el de matchitfy, tomando
   de cada uno lo que el otro tiene mal. Esta escrito abajo caso por caso, y
   no como adorno: los dos productos estan en produccion con esos defectos y
   sin la nota alguien los copia otra vez.
   ============================================================================ */

@layer reset, base, chrome;

@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  body { margin: 0; }
  img, svg { max-width: 100%; }
  img { display: block; }

  /* DE BUILDSIGNED, Y MATCHITFY NO LO TIENE. Sin esto, una pagina que crece y
     estrena barra de scroll mete un salto horizontal de 15px. matchitfy lo
     compensa con `body{overflow-x:hidden}`, que es exactamente lo que
     WEB_PATTERNS §6 llama esconder el bug: la barra desaparece y con ella la
     unica senal de que algo desborda. Una linea, y va desde el principio. */
  html { scrollbar-gutter: stable; }

/* `[hidden]` TIENE QUE GANAR, y por defecto no gana.
   El navegador lo aplica como `[hidden] { display: none }` desde su hoja de
   agente, que pierde contra cualquier regla de autor -- y `.app-tag` declara
   `display: inline-block`. Resultado medido en el navegador: la etiqueta de
   verificacion con `hidden = true` seguia pintando su BORDE, o sea una cajita
   vacia de 2x1 px debajo del correo. El JS decia "oculto" y el usuario veia una
   caja.
   Es el fallo que el CLAUDE.md del workspace describe: un gate responde "el
   texto esta en el archivo", no "el usuario lo ve". Este solo aparecio en una
   captura. */
[hidden] { display: none !important; }
}

@layer base {
  html { -webkit-text-size-adjust: 100%; background: var(--ground); }

  /* EL SCROLL SUAVE ES MOVIMIENTO Y NO ES NI ANIMACION NI TRANSICION, asi que
     el bloque de `prefers-reduced-motion` de mas abajo NO lo alcanza: ese pone
     a cero `animation-duration` y `transition-duration`, y `scroll-behavior`
     no es ninguna de las dos. Vivia suelto en el `<style>` de la portada, que
     es la pagina con anclas, y le animaba un scroll de pagina entera a quien
     habia pedido no tener movimiento. WCAG 2.3.3. Por eso se declara AL REVES,
     dentro de `no-preference`: el default del navegador es `auto`. */
  @media (prefers-reduced-motion: no-preference) {
    html { scroll-behavior: smooth; }
  }

  /* GUARD DE IDIOMA CON FAILSAFE DE CSS PURO.
     `WEB_PATTERNS` §2.1 describe un guard que oculta el body con `data-fouc` y
     lo revela con `revealBody()`. Eso no existe en ningun producto del taller y
     aqui tampoco: es la desviacion 1 del CLAUDE.md de este producto.

     Lo que hay es mas estrecho y suficiente. El ingles vive en el HTML, asi que
     en ingles NO SE OCULTA NADA. Solo el espanol espera al diccionario, y la
     animacion de 0s con 1,5s de retraso lo revela SOLO aunque i18n.js no llegue
     nunca. Un diccionario que no carga deja la pagina en ingles, jamas en
     blanco. */
  .i18n-wait body {
    visibility: hidden;
    animation: i18n-failsafe 0s linear 1.5s forwards;
  }
  @keyframes i18n-failsafe { to { visibility: visible; } }

  body {
    background: var(--ground);
    color: var(--ink);
    font-family: var(--font-sans);
    font-size: 16px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
  }

  .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

  /* El riel. Ancho maximo del contenido y respiracion lateral.
     `.rail` es el mismo nombre que ya usa el landing; se aceptan los dos para
     no reescribir cuarenta apariciones, y `.wrap` es el que usan buildsigned y
     matchitfy, asi que una pagina copiada de cualquiera de los dos encaja. */
  .wrap, .rail {
    width: min(var(--sb-content-max), calc(100% - 2 * var(--sb-page-gutter)));
    margin-inline: auto;
  }

  /* DE MATCHITFY: transiciones de vista NATIVAS entre paginas, cero JS.
     El sitio son paginas separadas con `<a href>` normal, no un SPA. El
     pestaneo entre paginas se mata aqui y en los navegadores que no lo soportan
     simplemente no hace nada. Es la respuesta correcta a "SPA sin flickering"
     para un sitio de marketing: htmx en un landing de siete paginas paga el
     costo de un shell y no compra nada. */
  @view-transition { navigation: auto; }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
    }
    /* EL RESET DE MOVIMIENTO NO PUEDE TOCAR EL FAILSAFE. Poner su duracion en
       0.01ms lo dispararia de inmediato y reintroduciria el destello que el
       guard existe para evitar. Se le devuelve su 0s explicito. */
    .i18n-wait body { animation-duration: 0s !important; }
    ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
      animation: none !important;
    }
  }

  /* Enlace de salto: lo primero que recibe foco con el teclado. */
  .skip {
    position: absolute; inset-inline-start: -9999px; inset-block-start: 0;
    z-index: 100;
    background: var(--paint); color: var(--on-paint);
    padding: .75rem 1.1rem; font-weight: 800; font-size: .82rem;
    text-decoration: none;
  }
  .skip:focus { inset-inline-start: 0; }

  /* El foco visible alcanza a TODO control, no solo a los botones. */
  a:focus-visible, button:focus-visible, input:focus-visible,
  summary:focus-visible, [tabindex]:focus-visible {
    outline: 2px solid var(--paint);
    outline-offset: 2px;
  }
}

@layer chrome {

  /* ---------- barra superior ---------- */

  .top {
    position: sticky; top: 0; z-index: 50;
    /* OPACO, no translucido. A .94 y sin `backdrop-filter` se colaban ocho
       niveles del contenido de abajo y se percibia como parpadeo al pasar por
       encima de las cabeceras de calle. */
    background: var(--ground);
    border-bottom: 1px solid var(--line);
  }
  /* LOS DOS NOMBRES, y el que faltaba costo la primera captura: `site.css`
     estilizaba `.top .wrap` y el marcado del landing decia `.rail`, asi que el
     `display:flex` no aplico nunca y la barra se partio en tres filas. El CSS
     era correcto y el selector no encontraba a nadie, que es el fallo que
     ningun `nginx -t` ni ningun grep de tokens detecta: solo se ve midiendo el
     DOM pintado. */
  .top .wrap, .top .rail {
    display: flex; align-items: center; gap: 1rem;
    min-height: 64px; flex-wrap: wrap;
  }

  .brand { display: flex; align-items: center; gap: .6rem; text-decoration: none; color: inherit; }
  .brand svg { display: block; flex: none; }
  /* El nombre en dos tintas (MARCA.md §4). `--paint-ink` y no `--paint`: aqui
     el ambar es LETRA, y en tema claro el token de forma da 3.7:1 y no pasa.
     `lowercase` protege el nombre de un diccionario que lo capitalice. */
  /* 1.6rem, Y ES UN PASO NUEVO DE LA RAMPA, declarado en DESIGN.md como
     `brand`. Venia de 1.1rem (17,6px) al lado de un simbolo de 30 dentro de una
     barra de 64, donde el lockup se leia como un simbolo con una nota al pie;
     paso por 1.4 y se subio otro escalon hasta 25,6px, que es donde el nombre
     y el simbolo pesan igual.

     Se agrega un escalon en vez de estirar uno existente a proposito: entre
     1.4rem y el clamp del titulo (que a 1280 da 32px) no habia nada, y meter la
     marca en el del titulo la habria puesto al tamano de un h3. Un paso nuevo
     con su razon escrita es sistema; un valor suelto en un archivo es deriva. */
  .brand b {
    font-weight: 900; font-size: 1.6rem; letter-spacing: -.03em;
    text-transform: lowercase;
  }
  .brand b span { color: var(--paint-ink); }

  /* NI UN `order` EN TODA LA BARRA, y esa es la unica razon de que el orden
     visual y el de foco coincidan en los dos puntos de quiebre.
     Antes bajo 900px el menu llevaba `order:3` y se iba a la segunda fila
     mientras los controles se quedaban en la primera: el teclado iba marca ->
     menu (fila 2) -> controles (fila 1), bajaba una fila y volvia a subir.
     WCAG 1.3.2 y 2.4.3, en las once paginas y en todo telefono.

     Lo que lo resuelve no es reordenar el DOM sino repartir distinto: el menu
     YA scrollea en horizontal, asi que en movil cabe al lado de la marca
     encogiendose, y son los controles los que bajan de fila. El orden queda
     marca -> menu -> controles en el DOM, en la pantalla y en el foco. */
  .top-nav {
    display: flex; align-items: center; gap: 1.1rem;
    margin-left: auto; flex-wrap: wrap;
  }
  .top-nav a {
    color: var(--concrete); text-decoration: none;
    font-size: .86rem; font-weight: 600;
    /* 44px, con `inline-flex` para que la altura la tome el AREA PULSABLE y no
       la caja de texto. matchitfy deja sus enlaces de barra en ~29px con
       `padding:4px 0`, que es descuido y no una excepcion razonada. */
    display: inline-flex; align-items: center; min-height: 44px;
    border-bottom: 2px solid transparent;
  }
  .top-nav a:hover { color: var(--ink); }

  /* LA REGLA DE `aria-current` SE ESCRIBE EL MISMO DIA QUE EL ATRIBUTO.
     buildsigned pone el atributo en seis paginas y su site.css no lo estiliza:
     `grep aria-current css/site.css` da CERO. La pagina activa no se marca en
     todo su landing. Es el bug 1 de la tabla del CLAUDE.md de este producto y
     no entra aqui. */
  .top-nav a[aria-current="page"] {
    color: var(--ink);
    border-bottom-color: var(--paint);
  }

  /* ---------- controles de preferencia: 28px, a proposito ----------
     `WEB_PATTERNS` §6 pide 44px en todo control y esta es la desviacion 2 del
     CLAUDE.md de este producto, copiada de los dos modelos porque los dos la
     midieron: al tamano de un boton de accion competian visualmente con el
     acceso, y ademas EMPUJABAN LA BARRA al cambiar de idioma, porque el rotulo
     mide distinto en cada idioma. 28px queda por encima del minimo de 24px que
     pide WCAG 2.2 para objetivo tactil.

     LA EXCEPCION ES SOLO PARA `.seg button`. Todo lo demas mantiene los 44px,
     incluidos los enlaces del pie. */
  .top-ctl { display: flex; align-items: center; gap: .6rem; }

  .seg { display: inline-flex; border: 1px solid var(--line); overflow: hidden; }
  .seg button {
    font: inherit; font-size: .72rem; font-weight: 800; letter-spacing: .06em;
    padding: .25rem .5rem; min-height: 28px; cursor: pointer;
    border: 0; background: none; color: var(--concrete);
    display: inline-flex; align-items: center; justify-content: center;
  }
  .seg button + button { border-inline-start: 1px solid var(--line); }
  .seg button:hover { color: var(--ink); }
  .seg button[aria-pressed="true"] { background: var(--ink); color: var(--ground); }

  .theme-btn { display: inline-flex; align-items: center; justify-content: center; }
  .theme-btn .ico { display: block; flex: none; }
  .theme-btn[data-theme-state="dark"]  .ico-moon { display: none; }
  .theme-btn[data-theme-state="light"] .ico-sun  { display: none; }
  /* Antes de que corra el JS no hay estado estampado. El sitio nace OSCURO
     (`color-scheme: dark` en tokens.css), asi que lo correcto en esa ventana es
     ensenar el sol, que es la accion disponible. buildsigned ensena la luna
     aqui porque el suyo nace claro; copiarlo tal cual habria dejado el icono
     equivocado durante el arranque. */
  .theme-btn:not([data-theme-state]) .ico-moon { display: none; }

  /* ---------- botones (DESIGN.md Buttons) ---------- */

  .btn,
  .btn-primary {
    display: inline-flex; align-items: center; justify-content: center;
    gap: .5rem;
    min-height: 44px; padding: .7rem 1.15rem;
    font-weight: 800; font-size: .92rem; letter-spacing: .01em;
    text-decoration: none;
    border: 0; border-radius: 0;
    background: var(--paint); color: var(--on-paint);
    cursor: pointer;
    transition: transform .12s cubic-bezier(.2, .8, .2, 1);
  }
  .btn:hover,
  .btn-primary:hover { transform: translateY(-1px); }
  /* Ghost: contorno inset en concrete-dim (3:1). `btn ghost` es el de la
     landing; `btn-ghost` / `btn-quiet` el de la app. */
  .btn.ghost,
  .btn-ghost,
  .btn-quiet {
    background: transparent;
    color: var(--ink);
    box-shadow: inset 0 0 0 2px var(--concrete-dim);
  }
  .btn.ghost:hover,
  .btn-ghost:hover,
  .btn-quiet:hover {
    color: var(--paint-ink);
    box-shadow: inset 0 0 0 2px var(--paint);
    transform: none;
  }

  /* TINTA, NO AMBAR: ambar sobre ambar del primario es 1:1. */
  .btn:focus-visible,
  .btn-primary:focus-visible { outline: 3px solid var(--ink); outline-offset: 3px; }
  .btn.ghost:focus-visible,
  .btn-ghost:focus-visible,
  .btn-quiet:focus-visible { outline: 2px solid var(--paint); outline-offset: 2px; }

  /* ---------- pie ---------- */

  .foot {
    border-top: 1px solid var(--line);
    background: var(--ground-2);
    color: var(--concrete);
  }
  .foot-grid {
    display: grid;
    /* `minmax(min(150px,100%),1fr)` y no `minmax(150px,1fr)`: un track con
       minimo fijo mas ancho que el contenedor no se encoge, desborda, y el
       desbordamiento sale por el documento entero. Medido en matchitfy:
       400px de scrollWidth contra 390 de viewport. */
    /* CINCO PISTAS EXPLICITAS PARA CINCO COLUMNAS, y `minmax(0, ...)` en todas.
       Aqui hubo dos errores seguidos y el segundo lo introdujo el arreglo del
       primero, asi que se escriben los dos.

       PRIMERO: `repeat(4, minmax(min(140px,100%), 1fr))`. El `min(...,100%)`
       resuelve contra el CONTENEDOR, no contra la porcion que le toca a cada
       pista, asi que dos pistas seguian pidiendo 150px cada una y el pie
       desbordaba por debajo de 348px CSS.

       SEGUNDO, y este se veia mucho mas: se cambio a `repeat(auto-fit, ...)`,
       que arregla el desborde pero decide CUANTAS pistas caben sin mirar
       cuantas columnas hay. Con cinco hijos y una primera pista mas ancha, el
       conteo se despega: a 1280 creaba SEIS pistas para cinco columnas y
       sobraba una vacia, y entre 760 y 940 creaba CUATRO, asi que la quinta
       columna se envolvia a una segunda fila y el pie se leia entrelazado.

       `minmax(0, 1fr)` es lo que resuelve las dos cosas a la vez: el conteo
       queda fijo y explicito, y el `0` deja que una pista se encoja por debajo
       de su contenido en vez de empujar el documento. `auto-fit` sigue siendo
       correcto para `.roles` y `.figures`, que tienen un numero VARIABLE de
       hijos; este pie tiene cinco y siempre cinco. */
    grid-template-columns: minmax(0, 1.6fr) repeat(4, minmax(0, 1fr));
    gap: 1.6rem clamp(1rem, 3vw, 2.5rem);
    padding-block: 2.6rem 1.8rem;
  }
  .foot-col h2 {
    font-size: .78rem; font-weight: 800; letter-spacing: .11em;
    text-transform: uppercase; color: var(--concrete-dim);
    margin: 0 0 .5rem;
  }
  .foot-col ul { list-style: none; margin: 0; padding: 0; }
  /* 44px TAMBIEN AQUI, con `inline-flex`. Es donde matchitfy los pierde: sus
     enlaces de pie son inline a 14px x 1.5, o sea 21px de area pulsable, sin
     `display`, sin `min-height` y sin padding vertical. Sin el `inline-flex` un
     `min-height` sobre un inline no hace absolutamente nada, y ese es el
     detalle que hace que el bug parezca arreglado cuando no lo esta. */
  .foot-col a {
    color: var(--ink); text-decoration: none;
    font-size: .92rem; font-weight: 600;
    display: inline-flex; align-items: center; min-height: 44px;
  }
  /* 28px CON RATON, 44px CON DEDO, Y ES LA SEGUNDA DESVIACION DEL PIE DE 44px.
     La primera la documenta el CLAUDE.md de este producto para `.seg button`.

     El motivo aqui es que estos enlaces van en COLUMNA. Con el texto en 23,5px
     dentro de una caja de 44, cada fila deja veinte pixeles vacios y la lista
     se lee a doble espacio: parece que hay una linea en blanco entre "How it
     works" y "Accounting". En horizontal ese mismo alto no se nota, y por eso
     `.top-nav a` se queda en 44 sin tocar.

     No se pierde accesibilidad donde importa: WCAG 2.2 pide 24x24 en el nivel
     AA (2.5.8) y los 44 son el AAA (2.5.5), que aqui se conserva ENTERO para
     el puntero grueso. Un raton no gana nada con 44px verticales y la lista si
     pierde legibilidad. El default sigue siendo 44 a proposito: si el
     navegador no dice que tiene puntero fino, se queda con el objetivo grande. */
  @media (pointer: fine) {
    .foot-col a { min-height: 28px; }
  }
  .foot-col a:hover { text-decoration: underline; text-underline-offset: 3px; }
  .foot-col .tag { font-size: .92rem; color: var(--concrete); max-width: 34ch; margin: .6rem 0 0; }

  .foot-bottom {
    display: flex; justify-content: space-between; gap: .6rem 1.5rem; flex-wrap: wrap;
    border-top: 1px solid var(--line);
    padding-block: 1.1rem 2.2rem;
    font-size: .82rem; color: var(--concrete-dim);
  }

  /* ---------- angosto ---------- */

  @media (max-width: 56rem) {
    /* Dos columnas para los cuatro bloques de enlaces, y la marca ocupa la
       fila entera: con las cinco repartidas en dos pistas, la marca quedaba
       pareada con "Product" y su parrafo estiraba la fila entera. */
    .foot-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .foot-grid > :first-child { grid-column: 1 / -1; }
  }

  /* DESPUES del bloque de 56rem, no antes. Los dos media query casan a 400px y
     con la misma especificidad gana el que va ultimo: escrito al reves, la
     regla de dos columnas pisaba a la de una y este arreglo no hacia nada. */
  @media (max-width: 30rem) {
    .foot-grid { grid-template-columns: minmax(0, 1fr); }
  }

  @media (max-width: 900px) {
    /* La barra NO desaparece: se reordena. El menu baja a su propia fila y
       scrollea en horizontal si hace falta, con una mascara que avisa de que
       hay mas a la derecha. Los controles se quedan arriba con la marca. */
    .top-nav {
      /* `flex:1 1 0` con `min-width:0`: el menu toma lo que quede de la fila de
         la marca y se encoge hasta cero si hace falta, que es lo que lo deja
         caber. Sin `min-width:0` un contenedor flex no baja de su min-content y
         se iria de fila igual. */
      flex: 1 1 0; min-width: 0; margin-left: .9rem; gap: .9rem;
      flex-wrap: nowrap; overflow-x: auto;
      scrollbar-width: none;
      -webkit-overflow-scrolling: touch;
      -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
      mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
    }
    .top-nav::-webkit-scrollbar { display: none; }
    .top-nav a { white-space: nowrap; }
    /* Los controles ocupan la fila entera y se alinean a la derecha. Son ellos
       los que bajan, no el menu. */
    .top-ctl { width: 100%; justify-content: flex-end; }
    /* La barra pegajosa mide dos filas en movil. 64px es la altura de UNA. */
    .top .wrap, .top .rail { min-height: 0; }
  }
}

/* ============================================================================
   OBJETOS DE CONTENIDO. Los cinco que se repiten en todas las paginas.

   Salen del barrido de buildsigned pagina por pagina (2026-08-23). Lo que hace
   que ese sitio no sea un muro de texto no son catorce disenos: son CINCO
   objetos reutilizados, y cada uno responde a una forma distinta de pregunta.

     .sheet + .stat-table   "aqui esta el numero, y de donde salio"
     .triggers              "aqui estan los N casos"
     .roles / .role.no      "y esto es lo que NO"
     .figures               "la prueba es una cifra"
     .cta-close             el cierre que remata EL argumento de esa pagina

   Tener cinco y no quince es lo que hace que una pagina nueva se escriba en una
   tarde: se elige el objeto por la forma de la respuesta, no se inventa layout.
   ============================================================================ */

@layer chrome {

  /* ---------- el esqueleto de una pagina ---------- */

  /* Apertura de TODA pagina interior: tres elementos y punto.
     eyebrow (categoria) -> h1 -> lede. Nada mas. El hero de la portada es el
     unico que agrega algo, y lo que agrega no es tipografia mas grande sino
     contexto: el configurador al lado. */
  .eyebrow {
    display: block; font-family: var(--font-mono);
    font-size: .82rem; font-weight: 700; letter-spacing: .11em;
    text-transform: uppercase; color: var(--paint-ink);
    margin-bottom: .9rem;
  }

  /* EL RITMO ESTABA INVERTIDO, medido el 2026-08-23 sobre la pagina publicada:
     112px arriba y 112px abajo, o sea 224px entre dos secciones, contra 8px de
     titular a bajada. Lo que agrupa quedaba mas suelto que lo que separa, y el
     resultado se leia como que la pagina se habia terminado, tres veces.

     4rem y no 5: entre dos secciones quedan 128px, que con la alternancia de
     superficie --que es lo unico que las separa, y a proposito-- ya es
     sobradamente separacion. Lo que sube es el aire DENTRO del bloque, que es
     donde faltaba. */
  .sec { padding-block: clamp(2.4rem, 5.5vw, 4rem); }
  /* Alternancia de superficie. Es lo unico que separa una seccion de la
     siguiente: sin lineas divisorias, que a esta densidad son ruido. */
  .sec.alt { background: var(--ground-2); }

  .sec h2 { margin: 0 0 1.1rem; font-size: clamp(1.35rem, 2.4vw, 2rem); font-weight: 900; letter-spacing: -.02em; line-height: 1.1; }
  .sec h3 { margin: 0 0 .35rem; font-size: 1.05rem; font-weight: 800; letter-spacing: -.01em; }
  .sec .lede { max-width: 68ch; color: var(--concrete); font-size: 1.05rem; margin: 0 0 1.6rem; }
  .sec p { margin: 0 0 .9rem; }
  .sec p:last-child { margin-bottom: 0; }

  /* El hero de una pagina interior. Mismo h1 que la portada: lo que cambia es
     que aqui no hay nada al lado. */
  .page-hero { padding-block: clamp(2.6rem, 6vw, 4.4rem) clamp(1.6rem, 3vw, 2.4rem); }
  .page-hero h1 { margin: 0; font-size: clamp(2.2rem, 5vw, 3.9rem); font-weight: 900; letter-spacing: -.03em; line-height: 1.02; text-wrap: balance;  overflow-wrap: break-word;}
  .page-hero .lede { margin-top: 1rem; max-width: 64ch; color: var(--concrete); font-size: clamp(1rem, 1.4vw, 1.12rem); }

  /* Dos columnas: el argumento a la izquierda, el objeto a la derecha. */
  .split { display: grid; gap: clamp(1.2rem, 3vw, 2.4rem); grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
  @media (max-width: 60rem) { .split { grid-template-columns: minmax(0, 1fr); } }

  /* ---------- 1. la hoja con su tabla ---------- */

  /* LA TERCERA COLUMNA ES EL ARGUMENTO. En buildsigned la tabla del asiento
     lleva Item / Value / Source, y cada cifra dice de donde salio: del
     contrato, de un estatuto citado por numero, o calculada. Ninguna dice
     "confia". Aqui la tercera columna es el ORIGEN del apunte: que deposito,
     que fase, que lote. */
  .sheet {
    background: var(--ground-2); border: 1px solid var(--line);
    padding: 1.1rem 1.2rem;
    /* RED DE SEGURIDAD, no el arreglo. El arreglo es `td.mono` de mas abajo.
       Esto contiene el desbordamiento DENTRO del panel para la columna que si
       sigue siendo `nowrap`, en vez de dejarlo salir por el documento. Es lo
       que `.vs-scroll` ya hacia en la portada y a esta tabla le faltaba. */
    overflow-x: auto;
  }
  .sheet h3 { font-family: var(--font-mono); font-size: .82rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--concrete-dim); margin-bottom: .8rem; }
  .stat-table { width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-size: .86rem; font-variant-numeric: tabular-nums; }
  .stat-table th { text-align: left; font-size: .72rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--concrete-dim); padding: 0 .6rem .5rem 0; border-bottom: 1px solid var(--line); }
  .stat-table td { padding: .45rem .6rem .45rem 0; border-bottom: 1px solid var(--line); vertical-align: top; }
  /* `td.mono` Y NO `td:nth-child(2)`. El selector posicional se escribio para
     la columna de CIFRAS, y en `compare.html` la segunda columna es PROSA: el
     `nowrap` le daba a "Moving or weighted average" un piso duro de 255px y la
     tabla sacaba el documento entero de lado ~140px en un telefono. La clase
     `.mono` ya marcaba las columnas numericas en las otras tres paginas; se
     usa esa, que dice lo que quiere decir. */
  .stat-table td.mono { text-align: right; white-space: nowrap; }
  .stat-table td:last-child { color: var(--concrete-dim); font-size: .82rem; }
  /* El unico enfasis de la tabla. Si hay dos, no hay ninguno. */
  .stat-table tr.total td { border-bottom: 0; border-top: 2px solid var(--paint); font-weight: 700; color: var(--ink); padding-top: .6rem; }

  /* ---------- 2. la lista de casos ---------- */

  .triggers { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; }
  .triggers li { display: grid; gap: .15rem; padding: .8rem 0; border-bottom: 1px solid var(--line); }
  .triggers li:last-child { border-bottom: 0; }
  .triggers .t { font-weight: 800; font-size: 1rem; }
  .triggers .d { color: var(--concrete); font-size: .92rem; }

  /* ---------- 3. las tarjetas, y las que niegan ---------- */

  .roles { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); }
  .role { background: var(--ground-2); border: 1px solid var(--line); padding: 1rem 1.1rem; }

  /* LAS SEIS DE `#explore` SON <a>, NO <div>, y esta regla nacio escrita para
     divs: sin ella el navegador aplicaba su color de enlace por defecto y su
     subrayado a CADA PALABRA de las seis tarjetas. Medido el 2026-08-23 sobre la
     pagina publicada: `rgb(0,0,238)` en claro y `rgb(158,158,255)` en oscuro,
     que ademas eran EL UNICO color en uso que ningun token declara. Doce nodos.

     Despues de 5.700px de tipografia disciplinada, seis tarjetas azules
     subrayadas se leen como una hoja de estilos que no cargo. Lo cazaron las dos
     mitades de la critica por separado: a ojo y midiendo el color computado.

     `color: inherit` y no un token: la tarjeta ya vive dentro de un contexto que
     define su color, y heredarlo es lo que hace que no haya que tocar esta regla
     cuando cambie el tema. */
  a.role { color: inherit; text-decoration: none; display: block; }
  a.role h3 { color: var(--ink); }
  a.role:hover { border-color: var(--paint); }
  a.role:hover h3 { color: var(--paint-ink); }
  a.role:focus-visible { outline: 3px solid var(--paint-ink); outline-offset: 2px; }
  .role p { color: var(--concrete); font-size: .92rem; margin: 0; }
  /* LA REGLA EDITORIAL DEL SITIO MODELO, y es la que mas cuesta copiar porque
     no es CSS: cada pagina tiene una seccion sobre lo que el producto NO hace,
     escrita con mas precision que las que dicen lo que si. `.role.no` es como
     se marca. El borde ambar de la izquierda es el mismo del resto del sistema;
     lo que cambia es que aqui significa "lee esto dos veces". */
  /* `inset` Y NO `border-left`, que es como lo hace el resto del sistema: el
     paso del constructor lleva `inset 4px 0 0 0` y el bloque de lote `inset
     6px 0 0 0`. Este era el unico filete de color escrito como borde, y ademas
     empujaba el contenido 3px respecto de sus hermanos. La hoja ya decia que
     los bordes son la excepcion y el trazo `inset` la regla. */
  .role.no { box-shadow: inset 3px 0 0 0 var(--paint); }
  .role.no h3 { color: var(--paint-ink); }

  /* ---------- 4. la cifra como prueba ---------- */

  .figures { display: grid; gap: 1.2rem; grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr)); }
  .figures .n { display: block; font-family: var(--font-mono); font-size: clamp(1.9rem, 4.4vw, 2.8rem); font-weight: 700; letter-spacing: -.03em; line-height: 1; color: var(--ink); }
  .figures .k { display: block; margin-top: .4rem; color: var(--concrete); font-size: .92rem; max-width: 30ch; }
  .figures .src { display: block; margin-top: .3rem; font-family: var(--font-mono); font-size: .72rem; color: var(--concrete-dim); }

  /* ---------- 5. el cierre ---------- */

  /* EL h2 DEL CIERRE REMATA EL ARGUMENTO DE ESA PAGINA, no dice "Regístrate".
     Y el boton fantasma apunta a la SIGUIENTE pregunta del recorrido, no a la
     portada: el recorrido esta cableado en esos botones y es un ciclo cerrado,
     asi que nadie llega a un callejon.
     En buildsigned `.cta-close` no tiene una sola regla CSS y funciona por
     accidente heredando de `.sec`. Aqui se declara. */
  .cta-close { background: var(--ground-2); border-top: 1px solid var(--line); }
  .cta-close h2 { max-width: 24ch; }
  .cta-close .lede { margin-bottom: 1.4rem; }
  .cta-row { display: flex; flex-wrap: wrap; gap: .7rem; }
}

/* ============================================================================
   EL MOLDE DE LAS LEGALES. Otro entero, y a proposito.

   Los dos modelos del taller hacen lo mismo: sus paginas legales no usan las
   secciones alternas, ni los objetos de contenido, ni el cierre con CTA. Son
   una sola columna estrecha de prosa. La razon es que el trabajo de la pagina
   es distinto: aqui nadie viene a decidir una compra, viene a comprobar algo
   concreto, y toda pieza de venta que se le cruce estorba.

   `74ch` es el ancho de matchitfy y esta bien medido: por encima de ~80 la
   linea es dificil de seguir de vuelta al margen.
   ============================================================================ */

@layer chrome {

  /* ALINEADA A LA IZQUIERDA DEL RIEL, no centrada. `.doc` iba sobre el mismo
     elemento que `.rail` y heredaba su `margin-inline:auto`, asi que la columna
     de 74ch se centraba en los 78rem del riel y el texto arrancaba 240px a la
     derecha del titular. Un documento cuyo primer parrafo no empieza donde
     empieza su titulo se lee como si fuera otra pagina. */
  .doc { max-width: 74ch; margin-inline: 0; padding-block: clamp(1.5rem, 4vw, 2.5rem) clamp(3rem, 8vw, 5rem); }

  /* El h2 lleva linea arriba en vez de fondo alterno: en un documento largo la
     alternancia de superficie se lee como si cambiara el tema, y aqui todo es
     el mismo tema. */
  .doc h2 {
    margin: 2.2rem 0 .6rem; padding-top: 1.4rem;
    border-top: 1px solid var(--line);
    font-size: 1.4rem; font-weight: 800; letter-spacing: -.015em; line-height: 1.2;
  }
  .doc h2:first-of-type { margin-top: .6rem; }
  .doc h3 { margin: 1.4rem 0 .4rem; font-size: 1.05rem; font-weight: 800; }
  .doc p { margin: 0 0 .9rem; font-size: 1rem; line-height: 1.65; color: var(--concrete); }
  .doc p strong { color: var(--ink); font-weight: 700; }
  .doc ul { margin: 0 0 1rem; padding-left: 1.1rem; }
  .doc li { margin-bottom: .45rem; font-size: 1rem; line-height: 1.6; color: var(--concrete); }
  .doc a { color: var(--paint-ink); }

  /* La tabla de subencargados. Estrecha, sin la hoja: no es una cifra que
     probar, es una lista que comprobar. */
  .doc table { width: 100%; border-collapse: collapse; margin: 0 0 1.2rem; font-size: .92rem; }
  .doc th { text-align: left; font-family: var(--font-mono); font-size: .72rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--concrete-dim); padding: 0 .8rem .5rem 0; border-bottom: 1px solid var(--line); }
  .doc td { padding: .55rem .8rem .55rem 0; border-bottom: 1px solid var(--line); color: var(--concrete); vertical-align: top; }
  .doc td:first-child { color: var(--ink); font-weight: 700; white-space: nowrap; }
}

@layer chrome {
  /* La cifra de un plan. Mono con cifras tabulares porque va a estar al lado de
     otras dos y se comparan en columna: es la prueba del sistema de tipografia,
     "si dos valores pueden quedar uno encima del otro, es mono". */
  .amount { font-family: var(--font-mono); font-variant-numeric: tabular-nums;
            /* EL MISMO PASO QUE `.tier .amt` DE LA PORTADA. Las dos son la
               cifra de un plan y estaban a 2rem aqui y a clamp(1.7,4vw,2.4)
               alla: el mismo dato, dos tamanos, segun por que puerta entrara
               el visitante. */
            font-size: clamp(1.7rem, 4vw, 2.4rem); font-weight: 700; letter-spacing: -.04em; line-height: 1;
            color: var(--ink); margin: .5rem 0 .6rem; }
  .amount small { font-size: .82rem; font-weight: 400; letter-spacing: 0; color: var(--concrete-dim); }
}

/* Los dos enlaces del caption de la tabla comparativa. Van subrayados a
   proposito: el texto que los rodea invita a comprobar las cifras, y un enlace
   que no se ve como enlace no se pulsa. Es el fallo de las tarjetas de FAQ dado
   vuelta -- alli sobraba subrayado, aqui haria falta. */
.vs caption a { color: var(--paint-ink); text-underline-offset: 3px; }
.vs caption a:hover { color: var(--ink); }

/* ══ /app — LA APLICACION ══════════════════════════════════════════════════
   Pantalla privada. Reusa `.top`, `.rail`, `.seg`, `.btn`, `.wrap`, `.mono` y
   `.lede` del sitio publico: el tema, la tipografia y el espaciado ya estan
   resueltos en tokens.css y repetirlos aqui seria la segunda fuente que se
   separa en el primer cambio.

   `scrollbar-gutter: stable` va desde el primer dia y no como parche. Es uno de
   los cuatro bugs de los productos modelo que el CLAUDE.md dice NO copiar:
   matchitfy lo compensa con `overflow-x:hidden`, que es esconder el bug en vez
   de arreglarlo. */
html { scrollbar-gutter: stable; }

.app-wrap { padding-block: clamp(1.75rem, 5vw, 3.25rem); }

.app-eyebrow {
  font-size: .78rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--concrete-dim);
  margin: 0 0 .35rem;
}

.app-wrap h1 {
  margin: 0 0 .5rem;
  /* Se rompe por si el taller se llama "Northwest Architectural Millwork &
     Cabinetry": es entrada del usuario y puede no tener espacios. */
  overflow-wrap: anywhere;
}

/* Portada /app: secciones con peso de producto, no de demo. */
.dash-head { margin-bottom: 2rem; }
.dash-head h1 {
  font-size: clamp(1.65rem, 3.8vw, 2.35rem);
  font-weight: 700;
  line-height: 1.1;
  margin-bottom: .65rem;
}
.dash-lede {
  margin: 0;
  max-width: 42rem;
  color: var(--concrete);
  font-size: .95rem;
  line-height: 1.45;
}
.dash-section { margin-bottom: 2.25rem; }
.dash-section-h {
  margin: 0 0 .75rem;
  font-family: var(--font-mono);
  font-size: .62rem;
  font-weight: 400;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--concrete-dim);
}
.dash-section .kpi-grid { margin: 0; }
.dash-section-note {
  margin: -.35rem 0 .75rem;
  font-size: .78rem;
  color: var(--concrete-dim);
}

/* EL NOMBRE EN LA BARRA se recorta en vez de empujar los controles. Sin esto,
   un taller de nombre largo desplaza el selector de idioma fuera de la vista en
   un telefono -- el mismo fallo que el CLAUDE.md documenta para el rotulo de
   idioma, por otra causa. */
.app-shop {
  flex: 1 1 auto;
  min-width: 0;
  padding-inline: .75rem;
}
.app-shop-name {
  display: block;
  font-size: .9rem;
  color: var(--concrete-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-user-name {
  display: block;
  font-size: .82rem;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
@media (max-width: 640px) { .app-shop { display: none; } }

/* `.btn-quiet` vive con el resto de botones en @layer chrome (ghost inset). */

.app-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  margin-top: 1.75rem;
}

.app-card {
  border: 1px solid var(--line);
  border-radius: 0;
  padding: 1.1rem 1.15rem;
  background: var(--ground-2);
}
.app-card h2 {
  font-size: .82rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--concrete-dim);
  margin: 0 0 .7rem;
}

.app-tag {
  display: inline-block;
  font-size: .78rem;
  padding: .18rem .5rem;
  border-radius: 0;
  border: 1px solid var(--line);
}
.app-tag-ok { border-color: var(--state-done); color: var(--state-done); }
.app-tag-wait { border-color: var(--paint); color: var(--paint); }

.app-caps { list-style: none; margin: 0; padding: 0; }
.app-caps li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .4rem .6rem;
  padding: .35rem 0;
  border-bottom: 1px solid var(--line);
}
.app-caps li:last-child { border-bottom: 0; }
.app-cap-code { font-size: .74rem; color: var(--concrete-dim); }
.app-caps-empty { color: var(--concrete-dim); }

.app-steps { margin: 0; padding-left: 1.1rem; }
.app-steps li {
  padding: .35rem 0;
  /* DESHABILITADO Y QUE SE VEA. Un paso apagado que parece activo es peor que
     no enseñarlo: se hace clic, no pasa nada, y el producto queda como roto. */
  color: var(--concrete-dim);
}
.app-soon {
  display: inline-block;
  margin-left: .45rem;
  font-size: .7rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  opacity: .75;
}

/* ═══════════════════════════════════════════════════════════════════════════
   EL SHELL DE LA APLICACION
   ─────────────────────────────────────────────────────────────────────────
   Cuatro pantallas --taller, piso, estacion y mapa contable-- que hasta el
   2026-08-29 estaban servidas, en verde, y sin un enlace entre ellas. Se veian
   escribiendo la URL a mano. Un 200 al que nadie enlaza se comporta como un
   404, con la diferencia de que nadie lo reporta.

   EL NAV REUSA `.top-nav` A PROPOSITO, y no es ahorro de lineas: esa clase ya
   trae los 44px con `inline-flex` --- la altura la toma el area pulsable y no
   la caja de texto --- y trae LA REGLA DE `aria-current`, escrita el mismo dia
   que el atributo. Una clase nueva habria nacido sin ninguna de las dos, que
   es exactamente el bug 1 de la tabla del CLAUDE.md de este producto: el
   modelo pone el atributo en seis paginas y su hoja no lo estiliza.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Separada del menu de marketing solo por el hueco: el resto lo hereda. */
.app-nav { gap: .9rem; }

/* ── el swap ───────────────────────────────────────────────────────────────
   RESPALDO SOLO DONDE NO HAY VIEW TRANSITIONS, y el `@supports` no es adorno.

   La primera version puso el fundido sin condicion, con el argumento --- que
   sigue siendo cierto --- de que donde hay View Transitions el compositor cruza
   el DOM viejo y el nuevo fuera del arbol vivo y esto no llega a verse. Lo que
   no es cierto es que no MOLESTE: una transicion de 130 ms sobre `opacity`
   deja al contenedor a medio camino durante ese rato, y cualquiera que
   fotografie justo despues del swap coge un fotograma distinto cada vez.

   Medido con `check-spa` contra produccion: los tres destinos daban "la pasada
   3 se ve distinta a la 1a" --- y al comparar el DOM y los pixeles con la
   pantalla asentada, los tres eran IDENTICOS en las dos cosas. El defecto no
   estaba en la navegacion: lo puso esta regla, y el instrumento lo vio.

   Vale la pena escribir cual fue el error de razonamiento, porque es el que
   mas veces se repite: "es un respaldo, donde no hace falta no se ve" trata a
   una regla CSS como si fuera gratis. Un respaldo que no se ve igual CORRE, y
   aqui corria encima del mecanismo bueno. `@supports not` lo apaga donde
   sobra, que es donde estaba estorbando. */
@supports not (view-transition-name: none) {
  #main-content.spa-loading { opacity: .55; }
  @media (prefers-reduced-motion: no-preference) {
    #main-content { transition: opacity .13s ease-out; }
  }
}

/* UN FALLO DE NAVEGACION SE VE. `spa.js` pinta esto cuando la peticion no
   vuelve; sin la regla, el mensaje sale como texto suelto pegado al borde y se
   lee como una pagina rota en vez de como un error con su causa. */
.spa-error {
  max-width: 34rem;
  margin: 3rem auto;
  padding: 1.25rem;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--ground-2);
  text-align: center;
}
.spa-error h2 { margin: 0 0 .5rem; font-size: 1rem; }
.spa-error-code {
  margin: 0;
  font-family: var(--mono, monospace);
  font-size: .8rem;
  color: var(--concrete-dim);
}

/* ── los destinos de la portada de la aplicacion ───────────────────────────
   La lista mezcla vivos y apagados a proposito. Los apagados heredan el gris
   de `.app-steps li`; los vivos tienen que RECUPERAR el color de tinta, o el
   enlace real se lee como el paso que todavia no existe. */
.app-steps-live { list-style: none; padding-left: 0; }
.app-steps-live li { display: grid; gap: .15rem; }
.app-steps-live a {
  color: var(--ink);
  font-weight: 600;
  /* 44px por el area pulsable, igual que la barra. */
  display: inline-flex; align-items: center; min-height: 44px;
}
.app-steps-live a:hover { color: var(--paint-ink); }
.app-step-why { font-size: .82rem; color: var(--concrete-dim); }

/* ═══════════════════════════════════════════════════════════════════════════
   EL SHELL DE LA APLICACION: LATERAL + CONTENIDO
   ─────────────────────────────────────────────────────────────────────────
   La barra horizontal de tres enlaces se va y entra un lateral. No es gusto:
   un menu horizontal deja de caber al cuarto destino y este producto hereda
   doce pantallas de ajustes mas las suyas. El lateral crece hacia abajo.

   EN MOVIL SE VUELVE UNA TIRA HORIZONTAL QUE SCROLLEA, y no un cajon con
   hamburguesa. Un taller usa esto de pie y con guantes: un cajon son dos toques
   --- abrir y elegir --- y el segundo cae sobre una lista que se mueve. La tira
   es un toque sobre un objetivo que no se mueve.
   ═══════════════════════════════════════════════════════════════════════════ */

.app-shell {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  align-items: start;
  gap: 0;
}

.app-side {
  position: sticky;
  /* 64px es la altura de la barra: el lateral empieza justo debajo y no se va
     con el scroll. */
  top: 64px;
  align-self: start;
  max-height: calc(100vh - 64px);
  overflow-y: auto;
  border-inline-end: 1px solid var(--line);
  padding: 1rem .6rem 2rem;
  background: var(--ground);
}

.app-side-nav { display: grid; gap: 1.25rem; }
.app-side-main, .app-side-fixed { display: grid; gap: .15rem; }
/* Los fijos se separan de los de trabajo con una linea, no con un titulo: un
   rotulo de seccion para dos elementos es mas ruido que orden. */
.app-side-fixed { border-top: 1px solid var(--line); padding-top: .75rem; }

.app-side-link {
  display: flex; align-items: center; gap: .6rem;
  /* 44px, y aqui NO hay excepcion de 28: esto se toca con guantes. */
  min-height: 44px;
  padding: 0 .7rem;
  border-radius: 0;
  text-decoration: none;
  color: var(--concrete);
  font-size: .9rem; font-weight: 600;
  border-inline-start: 3px solid transparent;
}
.app-side-link:hover { color: var(--ink); background: var(--ground-2); }

/* LA REGLA DE `aria-current` SE ESCRIBE EL MISMO DIA QUE EL ATRIBUTO. Es el
   bug 1 de la tabla del CLAUDE.md de este producto --- el modelo pone el
   atributo en seis paginas y su hoja no lo estiliza --- y no entra aqui. */
.app-side-link[aria-current="page"] {
  color: var(--ink);
  background: var(--ground-2);
  border-inline-start-color: var(--paint);
}
.app-side-link i { flex: none; font-size: 1.05rem; }
.app-side-label { flex: 1 1 auto; }
.app-side-badge {
  font-size: .62rem; letter-spacing: .08em; text-transform: uppercase;
  color: var(--concrete-dim); border: 1px solid var(--line);
  border-radius: 0; padding: .05rem .4rem;
}

@media (max-width: 860px) {
  .app-shell { grid-template-columns: minmax(0, 1fr); }
  .app-side {
    position: static; max-height: none; overflow: visible;
    border-inline-end: 0; border-bottom: 1px solid var(--line);
    padding: .4rem .5rem;
  }
  .app-side-nav { grid-auto-flow: column; justify-content: start; gap: .4rem; overflow-x: auto; }
  .app-side-main, .app-side-fixed { grid-auto-flow: column; gap: .3rem; }
  .app-side-fixed { border-top: 0; padding-top: 0; border-inline-start: 1px solid var(--line); padding-inline-start: .4rem; }
  .app-side-link { border-inline-start: 0; border-bottom: 3px solid transparent; white-space: nowrap; }
  .app-side-link[aria-current="page"] { border-inline-start-color: transparent; border-bottom-color: var(--paint); }
}

/* ── EL PANEL ──────────────────────────────────────────────────────────────
   `auto-fit` con `minmax` y no un numero fijo de columnas: las tarjetas las
   decide el servidor, asi que aqui no se sabe cuantas hay. */
.kpi-grid {
  display: grid;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  /* 12rem: cinco tarjetas entran en el ancho util a partir de ~1200px; por
     debajo el `auto-fit` reparte solo. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  margin: 1.5rem 0 1.5rem;
}
.kpi-grid .col-span-full,
.kpi-grid [data-i18n="kpi.grid.empty"] {
  grid-column: 1 / -1;
  padding: 1.5rem 1rem;
  text-align: center;
  font-family: var(--font-mono);
  font-size: .85rem;
  color: var(--concrete-dim);
  background: var(--ground-2);
}

/* Tarjetas IoP del armazon compartido (Tailwind) dentro del grid marcaje. */
.kpi-grid > [class*="rounded"],
.kpi-grid > [class*="bg-white"],
.kpi-grid > [class*="shadow"] {
  border-radius: 0 !important;
  background: var(--ground-2) !important;
  border: 0 !important;
  box-shadow: inset 3px 0 0 0 var(--concrete-dim) !important;
  padding: 1rem 1.1rem 1.15rem !important;
  min-height: 7.5rem;
}
.kpi-grid > [data-tone="bad"],
.kpi-grid > [data-tone="warn"],
.kpi-grid > [data-tone="ok"] { box-shadow: inset 3px 0 0 0 var(--concrete-dim) !important; }
.kpi-grid > [data-tone="bad"] { box-shadow: inset 3px 0 0 0 var(--state-stopped) !important; }
.kpi-grid > [data-tone="warn"] { box-shadow: inset 3px 0 0 0 var(--paint) !important; }
.kpi-grid > [data-tone="ok"] { box-shadow: inset 3px 0 0 0 var(--state-running) !important; }

.kpi {
  /* Marcaje de piso: radio 0, acento inset. El radio 10px era lenguaje de
     SaaS y contradecia tokens.css. */
  border: 0;
  border-radius: 0;
  padding: 1rem 1.1rem 1.15rem;
  background: var(--ground-2);
  box-shadow: inset 3px 0 0 0 var(--concrete-dim);
  display: grid;
  gap: .45rem;
  align-content: start;
  min-height: 7.5rem;
}
.kpi:has(.kpi-big[data-tone="bad"])  { box-shadow: inset 3px 0 0 0 var(--state-stopped); }
.kpi:has(.kpi-big[data-tone="warn"]) { box-shadow: inset 3px 0 0 0 var(--paint); }
.kpi:has(.kpi-big[data-tone="ok"])   { box-shadow: inset 3px 0 0 0 var(--state-running); }

.kpi-title {
  margin: 0;
  font-family: var(--font-mono);
  font-size: .62rem;
  font-weight: 400;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--concrete-dim);
}
.kpi-big {
  margin: 0;
  font-family: var(--font-mono);
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* EL TONO SE PINTA SOLO CUANDO SIGNIFICA ALGO. Un cero en rojo entrena a leer
   la tarjeta como una alarma permanente, y entonces la alarma de verdad no la
   ve nadie. */
.kpi-big[data-tone="bad"]  { color: var(--state-stopped-ink); }
.kpi-big[data-tone="warn"] { color: var(--paint-ink); }
.kpi-big[data-tone="ok"]   { color: var(--state-running); }

.kpi-breakdown, .kpi-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .3rem; }
.kpi-breakdown li, .kpi-list li {
  display: flex; align-items: baseline; gap: .4rem;
  font-size: .85rem; color: var(--concrete);
}
.kpi-breakdown b, .kpi-list b { font-variant-numeric: tabular-nums; color: var(--ink); }
.kpi-breakdown li[data-state="stop"] b { color: var(--state-stopped-ink); }
.kpi-breakdown li[data-state="run"]  b { color: var(--state-running); }
.kpi-breakdown li[data-state="wait"] b { color: var(--state-notice); }
.kpi-item-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kpi-more, .kpi-empty, .kpi-note { font-size: .8rem; color: var(--concrete-dim); margin: 0; }

/* Atajos de la portada: solo los que la capacidad concede. */
.app-go {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  margin: 0 0 2rem;
}
.app-go-link {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: .75rem;
  row-gap: .25rem;
  align-items: start;
  padding: 1rem 1.1rem;
  background: var(--ground-2);
  color: inherit;
  text-decoration: none;
  min-height: 4.75rem;
  box-shadow: inset 3px 0 0 0 transparent;
  transition: box-shadow .15s ease;
}
.app-go-icon {
  grid-row: 1 / -1;
  align-self: center;
  font-size: 1.15rem;
  color: var(--concrete-dim);
  line-height: 1;
}
.app-go-link:hover .app-go-icon { color: var(--paint); }
.app-go-link:hover { box-shadow: inset 3px 0 0 0 var(--paint); }
.app-go-link:focus-visible {
  outline: 3px solid var(--paint);
  outline-offset: -3px;
}
.app-go-title {
  font-family: var(--font-mono);
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink);
}
.app-go-why {
  font-size: .8rem;
  color: var(--concrete);
  line-height: 1.35;
}

/* ── LOS AJUSTES ───────────────────────────────────────────────────────────
   La clave tecnica se ensena debajo del rotulo, en monoespaciada. No es para
   el operario: es para quien lee un log o un comentario del codigo que la
   nombra y necesita saber cual de estos campos es. */
.set-list { display: grid; gap: .75rem; margin-top: 1.5rem; }
.set-group {
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  padding: 0;
}
.set-group h2 {
  margin: 0;
  font-family: var(--font-mono);
  font-size: .62rem;
  font-weight: 400;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--concrete-dim);
  background: var(--ground-3);
  padding: .55rem .75rem;
  border-bottom: 0;
}
.set-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 12rem;
  grid-template-areas: "label input" "key input" "note note";
  gap: .15rem .9rem;
  align-items: center;
  padding: .55rem .75rem;
  background: var(--ground-2);
  box-shadow: inset 3px 0 0 0 var(--line);
}
.set-label {
  grid-area: label;
  font-size: .9rem;
  font-weight: 600;
  color: var(--ink);
}
.set-key   { grid-area: key;   font-family: var(--font-mono); font-size: .72rem; color: var(--concrete-dim); }
.set-input { grid-area: input; width: 100%; }
.set-note { grid-area: note; margin: .25rem 0 0; font-size: .8rem; }
/* De donde sale la cifra: heredada de la instalacion o puesta por este taller.
   Va apagado por defecto y solo se enciende cuando es del taller --- si los dos
   estados gritan igual, la pantalla se vuelve un muro de etiquetas y ninguna
   significa nada. */
.set-scope { margin-left: .5rem; font-size: .68rem; font-weight: 400;
             letter-spacing: .04em; text-transform: uppercase;
             color: var(--concrete-dim); }
.set-scope[data-scope="company"] { color: var(--state-running); }
.set-note[data-kind="ok"]    { color: var(--state-running); }
.set-note[data-kind="error"] { color: var(--state-stopped); }

@media (max-width: 560px) {
  .set-row { grid-template-columns: minmax(0, 1fr); grid-template-areas: "label" "key" "input" "note"; }
}

/* ── EL HUB DE UN GRUPO ────────────────────────────────────────────────────
   Tarjetas grandes, porque esto se toca de pie: el objetivo es la tarjeta
   entera, no su titulo. */
.hub-head {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  column-gap: .6rem;
  row-gap: .15rem;
  align-items: center;
  margin-bottom: 1.25rem;
}
.hub-head .app-eyebrow { grid-column: 1 / -1; margin: 0; }
.hub-head i { font-size: 1.5rem; color: var(--paint-ink); }
.hub-head h1 { margin: 0; }
/* El icono Phosphor del armazon no es del marcaje; el titulo basta. */
.hub-head #hub-title-icon { display: none; }

.hub-grid {
  display: grid;
  gap: .9rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.hub-grid a, .hub-card {
  display: grid; gap: .35rem;
  min-height: 88px;
  padding: 1rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--ground-2);
  text-decoration: none; color: var(--ink);
  align-content: center;
  box-shadow: inset 4px 0 0 0 var(--paint);
}
.hub-grid a:hover { border-color: var(--paint); background: var(--ground-3, #2E3439); }
.hub-grid a:focus-visible { outline: 3px solid var(--paint); outline-offset: 2px; }
/* El armazon compartido mete clases Tailwind (rounded-3xl, shadow…). Aqui
   mandan los tokens del marcaje: sin radio blando ni sombra flotante. */
.hub-grid .hub-card {
  border-radius: 0 !important;
  box-shadow: inset 4px 0 0 0 var(--paint) !important;
}
.hub-grid .hub-card h3 {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: .02em;
}
.hub-grid .hub-card p {
  margin: 0;
  font-size: .85rem;
  color: var(--concrete);
  line-height: 1.35;
}
.hub-grid .hub-card .w-11,
.hub-grid .hub-card [class*="rounded-2xl"] {
  display: none; /* el icono Tailwind no es del idioma visual; el acento inset basta */
}

/* ── LAS PANTALLAS DE LISTA (articulos, depositos) ─────────────────────────
   Una tabla y un alta que empieza oculta. La tabla scrollea DENTRO de su caja
   y no empuja el ancho de la pagina: es la regla que el CLAUDE.md de este
   producto pone contra `overflow-x:hidden`, que esconde el defecto en vez de
   arreglarlo. */
.list-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: .6rem;
  margin: 1.25rem 0 1rem;
}
.list-bar.stock-filters .stock-filter {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  min-width: 10rem;
  flex: 1 1 10rem;
}
.list-bar.stock-filters .stock-filter > span {
  font-size: .65rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--concrete-dim);
}
.list-bar.stock-filters .list-search {
  flex: 2 1 12rem;
  width: auto;
  min-width: 10rem;
}
.list-count { color: var(--concrete-dim); font-size: .8rem; margin-inline-end: auto; }

/* ── CAMPOS (DESIGN.md Inputs / Fields) ───────────────────────────────────
   Tailwind Forms trae el markup (`.form-input` / `.form-select`); el look es
   del marcaje: 48px, radio 0, trazo inset en `--line`, foco paint sin ring. */
.form-input,
.form-select,
.list-search,
.list-field input:not([type="checkbox"]):not([type="radio"]),
.list-field select,
.set-row .form-input,
.set-row .form-select,
.acct-row .form-input {
  min-height: 48px;
  width: 100%;
  padding: .7rem .8rem;
  border: 0;
  border-radius: 0;
  background-color: var(--ground);
  color: var(--ink);
  font: inherit;
  font-size: .95rem;
  line-height: 1.25;
  box-shadow: inset 0 0 0 1px var(--line);
  --tw-ring-shadow: 0 0 #0000;
  --tw-ring-offset-shadow: 0 0 #0000;
}
.form-select,
.list-field select {
  padding-inline-end: 2.5rem;
}
.form-input::placeholder,
.list-search::placeholder,
.list-field input::placeholder {
  color: var(--concrete-dim);
  opacity: 1;
}
.form-input:hover,
.form-select:hover,
.list-search:hover,
.list-field input:not([type="checkbox"]):not([type="radio"]):hover,
.list-field select:hover,
.set-row .form-input:hover,
.set-row .form-select:hover,
.acct-row .form-input:hover {
  box-shadow: inset 0 0 0 1px var(--concrete-dim);
}
.form-input:focus,
.form-select:focus,
.list-search:focus,
.list-field input:not([type="checkbox"]):not([type="radio"]):focus,
.list-field select:focus,
.set-row .form-input:focus,
.set-row .form-select:focus,
.acct-row .form-input:focus {
  outline: 2px solid var(--paint);
  outline-offset: 1px;
  box-shadow: inset 0 0 0 1px var(--paint);
  --tw-ring-color: transparent;
  border-color: transparent;
}
.form-input:disabled,
.form-select:disabled,
.list-field input:disabled,
.list-field select:disabled {
  opacity: .55;
  cursor: not-allowed;
  color: var(--concrete-dim);
}

.list-search { flex: 1 1 16rem; min-width: 0; width: auto; }

.list-form {
  display: grid; gap: .8rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  border: 0; border-radius: 0;
  background: var(--ground-2);
  padding: 1.1rem; margin-bottom: 1.25rem;
  box-shadow: inset 3px 0 0 0 var(--paint), inset 0 0 0 1px var(--line);
}
.list-field { display: grid; gap: .35rem; }
.list-field label {
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--concrete);
}
/* La casilla se alinea con su rotulo, no encima: un check con el texto arriba
   se lee como un campo vacio. */
.list-field-check { grid-auto-flow: column; justify-content: start; align-items: center; gap: .5rem; }
.list-field-check input[type="checkbox"],
.form-input[type="checkbox"] {
  min-height: 0;
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  border-radius: 0;
  box-shadow: inset 0 0 0 1px var(--line);
  accent-color: var(--paint);
}
.list-field-check label {
  font-weight: 500;
  letter-spacing: normal;
  text-transform: none;
  font-size: .9rem;
}
.list-actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }
.list-note { grid-column: 1 / -1; margin: 0; font-size: .85rem; }
.list-note[data-kind="ok"]    { color: var(--state-running); }
.list-note[data-kind="error"] { color: var(--state-stopped); }

.list-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.list-table th {
  text-align: start; font-size: .72rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--concrete-dim);
  padding: .5rem .6rem; border-bottom: 1px solid var(--line);
}
.list-table td {
  padding: .6rem; border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.list-table tr:last-child td { border-bottom: 0; }

.list-tag {
  display: inline-block; font-size: .72rem; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
  padding: .15rem .45rem; border-radius: 0;
  border: 1px solid var(--line); color: var(--concrete);
}
/* ── EL MAPA UNICO DE `data-kind` → COLOR ─────────────────────────────────
   EL COLOR DEL TIPO NO ES DECORACION: `scrap` es lo que no suma al costo del
   lote y `quarantine` es lo que no se puede consumir. Que se distingan de un
   vistazo es la mitad del trabajo de esas dos pantallas.

   UN SOLO SITIO, Y ESTE. Cada enum que llega a la pantalla como etiqueta pasa
   por aqui: si un valor nuevo no esta en esta lista NO da error --- hereda el
   gris neutro de `.list-tag` y se ve como una etiqueta cualquiera. Ese es el
   modo de falla, y es silencioso: la etiqueta ESTA, se lee, y solo no dice lo
   que tenia que decir.

   Paso el 2026-09-01 con `warn` y `risk`, que `new-batch-page.js` emitia desde
   que existe el grid de sugerencias. "Capped" y "Over max" avisan de que la
   cantidad sugerida se recorto o se pasa del maximo, y salian del mismo gris
   que "Raw". Se encontro auditando quien EMITE `data-kind` contra quien lo
   COLOREA, no mirando la pantalla.

   Los emisores, para poder repetir la auditoria:
     `grep -n "setAttribute('data-kind'" web/js/*.js`
   y contra esto:
     `grep -oP 'list-tag\[data-kind="\K[a-z_]+' web/css/site.css | sort -u`

   Familias:
     estado de linea    off
     cierre de proceso  manual, auto, qc
     captura de consumo declare, backflush
     rol de item        raw, packaging, wip, finished, service
     destino de stock   stock, quarantine, scrap, shipped, lot
     cobertura          ok, elsewhere, short, unknown
     aviso de cantidad  warn, risk
     motivo de parada  planned, asks
   ───────────────────────────────────────────────────────────────────────── */
.list-tag[data-kind="off"]        { border-color: var(--state-stopped); color: var(--state-stopped); }
/* Enums de proceso (close_mode, input_mode) y rol de ítem — un solo mapa en CSS. */
.list-tag[data-kind="manual"]     { border-color: var(--paint); color: var(--paint-ink);
  background: color-mix(in srgb, var(--paint) 12%, transparent); }
.list-tag[data-kind="auto"]       { border-color: var(--state-running); color: var(--state-running);
  background: color-mix(in srgb, var(--state-running) 10%, transparent); }
.list-tag[data-kind="qc"]         { border-color: var(--state-notice); color: var(--state-notice);
  background: color-mix(in srgb, var(--state-notice) 10%, transparent); }
.list-tag[data-kind="declare"]    { border-color: var(--state-notice); color: var(--state-notice);
  background: color-mix(in srgb, var(--state-notice) 8%, transparent); }
.list-tag[data-kind="backflush"]  { border-color: var(--paint); color: var(--paint-ink);
  background: color-mix(in srgb, var(--paint) 10%, transparent); }
.list-tag[data-kind="raw"]        { border-color: var(--concrete-dim); color: var(--concrete);
  background: color-mix(in srgb, var(--concrete-dim) 12%, transparent); }
.list-tag[data-kind="packaging"]  { border-color: var(--concrete-dim); color: var(--concrete);
  background: color-mix(in srgb, var(--concrete-dim) 8%, transparent); }
.list-tag[data-kind="wip"]        { border-color: var(--state-notice); color: var(--state-notice);
  background: color-mix(in srgb, var(--state-notice) 10%, transparent); }
.list-tag[data-kind="finished"]   { border-color: var(--state-running); color: var(--state-running);
  background: color-mix(in srgb, var(--state-running) 10%, transparent); }
.list-tag[data-kind="service"]    { border-color: var(--paint); color: var(--paint-ink);
  background: color-mix(in srgb, var(--paint) 12%, transparent); }
.list-tag[data-kind="stock"]      { border-color: var(--state-running); color: var(--state-running); }
.list-tag[data-kind="quarantine"] { border-color: var(--paint); color: var(--paint-ink); }
.list-tag[data-kind="scrap"]      { border-color: var(--state-stopped); color: var(--state-stopped); }
.list-tag[data-kind="shipped"]    { border-color: var(--state-done); color: var(--state-done); }
.list-tag[data-kind="lot"]        { border-color: var(--paint); color: var(--paint-ink); }
.list-sub { display: block; font-size: .76rem; color: var(--concrete-dim); margin-top: .2rem; }

@media (max-width: 700px) {
  /* La tabla scrollea dentro de su propia caja. NUNCA `overflow-x:hidden` en
     el body: eso esconde el desbordamiento en vez de resolverlo, que es lo que
     el bug 4 de la tabla del CLAUDE.md nombra. */
  #itemList, #locList { overflow-x: auto; }
  .list-table { min-width: 34rem; }
}


/* ── LA FRANJA DE ABAJO: quien entro y que puede hacer ─────────────────────
   Colapsada por defecto: en portada es depuracion, no contenido de producto. */
.who {
  display: grid;
  gap: 1rem 2rem;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: start;
  border-top: 1px solid var(--line);
  padding-top: 1.25rem;
  margin-top: .5rem;
}
.who-collapsed { border-top: 1px solid var(--line); padding-top: .75rem; margin-top: 1rem; }
.who-summary {
  cursor: pointer;
  list-style: none;
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--concrete-dim);
  user-select: none;
}
.who-summary::-webkit-details-marker { display: none; }
.who-summary::before {
  content: "+";
  display: inline-block;
  width: 1rem;
  margin-inline-end: .35rem;
  color: var(--paint);
}
.who-collapsed[open] .who-summary::before { content: "−"; }
.who-body {
  display: grid;
  gap: 1rem 2rem;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: start;
  padding-top: .85rem;
}
.who-label {
  display: block;
  font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--concrete-dim); margin-bottom: .4rem;
}
.who-id { display: grid; gap: .3rem; justify-items: start; font-size: .85rem; }

/* LOS PERMISOS COMO ETIQUETAS Y NO COMO LISTA. Doce filas apiladas se leen como
   un formulario; doce etiquetas se leen de un vistazo y caben en dos lineas. */
.who-caps .app-caps { display: flex; flex-wrap: wrap; gap: .35rem; }
.who-caps .app-caps li {
  display: inline-flex; align-items: center;
  padding: .2rem .55rem;
  border: 1px solid var(--line); border-radius: 0;
  font-size: .78rem; color: var(--concrete);
  /* `title` lleva el codigo tecnico: el cursor lo anuncia. */
  cursor: default;
}
.who-caps .app-caps li:hover { border-color: var(--paint); color: var(--ink); }
.who-caps .app-caps-empty { border: 0; color: var(--concrete-dim); padding-inline: 0; }

@media (max-width: 760px) {
  .who, .who-body { grid-template-columns: minmax(0, 1fr); }
}

/* ── LAS PANTALLAS DE DEFINICION: recetas, procesos y abrir produccion ────
   Cinco clases que las tres nuevas necesitan y que las dos anteriores ---
   articulos y depositos --- no habian pedido nunca, porque ninguna de las dos
   despliega detalle ni tiene un formulario que sea la pantalla entera. */

/* UN FORMULARIO QUE NO SE ESCONDE. En articulos y depositos el alta esta oculta
   detras de un boton porque la pantalla es una LISTA y el alta es la excepcion.
   En "generar produccion" el formulario ES la pantalla: no hay nada que listar,
   y esconderlo detras de un boton seria pedir un clic para llegar a lo unico
   que hay. */
.list-form-open { display: grid; }
.list-form[hidden],
.list-form-open[hidden] { display: none !important; }

/* Un campo que ocupa la fila entera. Para lo que se escribe largo --- notas, la
   referencia de un pedido --- donde media caja obliga a scrollear dentro del
   input para releer lo que acabas de escribir. */
.list-field-wide { grid-column: 1 / -1; }

/* Dos campos relacionados en la misma fila (origen/destino de un proceso). */
.list-field-row {
  grid-column: 1 / -1;
  display: grid;
  gap: .8rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
}
.list-field-row .list-field { margin: 0; }

/* EL DESPLIEGUE DE LOS COMPONENTES DE UNA RECETA. Es una fila hermana y no una
   celda: metida dentro de una columna, la tabla anidada se estrecha a ese ancho
   y las cantidades se parten en dos lineas. */
.nb-tabs { margin: 1rem 0 .5rem; }
.list-form-inline {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 1rem;
  align-items: flex-end;
  margin: 0 0 1rem;
}
.list-form-inline .list-field { margin: 0; flex: 1 1 9rem; min-width: 0; }
.list-form-inline .list-actions { margin: 0; flex: 0 0 auto; }
.nb-tabs .nb-tab {
  min-height: 44px;
  padding: 0 .85rem;
  border: 0;
  background: transparent;
  color: var(--concrete);
  font-weight: 600;
  cursor: pointer;
}
.nb-tabs .nb-tab.is-on {
  background: var(--ground-2);
  color: var(--ink);
  box-shadow: inset 0 -2px 0 0 var(--paint);
}
.nb-panels { margin-bottom: 1rem; }
.list-table .nb-qty-input { max-width: 7rem; }
.nb-detail {
  margin: 1.25rem 0 1rem;
  padding: 1rem 0 0;
  border-top: 1px solid var(--line);
}
.nb-detail .recipe-title { margin-top: 0; }
.nb-exec-bar {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  align-items: center;
  margin: 0 0 1rem;
}
.list-table tr.nb-row {
  cursor: pointer;
}
.list-table tr.nb-row.is-focus td {
  background: var(--ground-2);
  box-shadow: inset 3px 0 0 0 var(--paint);
}
.list-table .nb-row-check,
.list-table .nb-check-all,
.list-table .nb-child-check {
  width: 1.1rem;
  height: 1.1rem;
  accent-color: var(--paint);
}
.list-tag[data-kind="ok"]        { border-color: var(--state-running); color: var(--state-running); }
.list-tag[data-kind="elsewhere"] { border-color: var(--state-notice); color: var(--state-notice); }
.list-tag[data-kind="short"]     { border-color: var(--state-stopped); color: var(--state-stopped); }
.list-tag[data-kind="unknown"]   { border-color: var(--concrete-dim); color: var(--concrete-dim); }
/* AVISO DE CANTIDAD. `warn` es "te recorte la sugerencia" y `risk` es "esto se
   pasa del maximo": la primera es informativa y la segunda pide decidir, asi
   que no comparten color aunque las dos sean avisos. */
.list-tag[data-kind="warn"]      { border-color: var(--state-notice); color: var(--state-notice);
  background: color-mix(in srgb, var(--state-notice) 12%, transparent); }
.list-tag[data-kind="risk"]      { border-color: var(--state-stopped); color: var(--state-stopped);
  background: color-mix(in srgb, var(--state-stopped) 12%, transparent); }
/* MOTIVOS DE PARADA. `planned` es un cambio de turno o una limpieza: cuesta
   tiempo y NO es una averia, asi que va en el gris de "cerrado" y no en rojo
   --- pintarla de alarma haria que una limpieza pareciera un fallo. `asks` es
   de comportamiento, no de estado: ese motivo le PIDE algo al operario antes
   de dejar seguir. */
.list-tag[data-kind="planned"]   { border-color: var(--state-done); color: var(--state-done); }
.list-tag[data-kind="asks"]      { border-color: var(--paint); color: var(--paint-ink);
  background: color-mix(in srgb, var(--paint) 12%, transparent); }
.list-form-nested {
  margin-top: .75rem;
  margin-bottom: 0;
  background: var(--ground);
}
.list-form-grid { display: grid; gap: .8rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }
.list-table-nested {
  width: 100%;
  margin: 0;
  background: var(--surface-sunk, transparent);
  border-left: 3px solid var(--paint);
}
.list-table-nested th, .list-table-nested td {
  padding-block: .4rem;
  font-size: .85rem;
}
.list-table-nested th { color: var(--concrete-dim); font-weight: 500; }

/* Preexistente y fuera de N115/N123, pero medido en la misma pantalla al
   verificar: las tres tablas de la receta (5 columnas la de componentes)
   se llevaban el `<body>` entero a 390px por no tener su propia caja con
   scroll -- mismo patron que `#itemList`/`#locList` arriba. */
#formComponents, #formRouting, #formSubproducts { overflow-x: auto; }
.recipe-tabs { margin-top: .5rem; }
.recipe-panels { margin-bottom: 1.5rem; }
.recipe-panels .recipe-section { margin-top: 0; }
.recipe-title {
  margin: 0 0 .35rem;
  font-size: 1.05rem;
  font-weight: 700;
}
.recipe-section > .list-sub { margin: 0 0 .75rem; }

.recipe-pricing {
  margin: 0 0 1.25rem;
  padding: .85rem 1rem;
  background: var(--ground-2);
  box-shadow: inset 3px 0 0 0 var(--paint), inset 0 0 0 1px var(--line);
}

/* N123: misma caja que `.recipe-pricing` -- es la misma familia de "dato
   calculado, no guardado" que ya vive en la cabecera de la receta. */
.recipe-resolve {
  margin: 1.25rem 0 0;
  padding: .85rem 1rem;
  background: var(--ground-2);
  box-shadow: inset 3px 0 0 0 var(--paint), inset 0 0 0 1px var(--line);
}
.recipe-resolve .recipe-title { margin-top: 0; }
/* La tabla escrollea en su propia caja, nunca el documento (misma regla que
   `#itemList`/`#locList`): 4 columnas con nombre de articulo no caben en 390px
   y sin esto el `overflow-x` se lo lleva el `<body>` entero. */
.recipe-resolve #formResolveResult { overflow-x: auto; }
.recipe-resolve .list-table { margin-top: .6rem; }

/* EL BOTON QUE DESPLIEGA UNA FILA NO LLEVA CAJA.

   La primera version usaba `btn-quiet`, que en este tema pinta fondo claro: ocho
   de ellos bajando por el catalogo de recetas tenian el mismo peso visual que
   "New recipe", que es LA accion de la pantalla. Un control que solo abre y
   cierra una fila --- que no cambia nada, que no se puede equivocar --- no
   compite con el que crea. */
.btn-small {
  padding: .25rem .4rem;
  font-size: .8rem;
  min-height: 0;
  background: none;
  border: 0;
  box-shadow: none;
  color: var(--concrete);
  text-decoration: underline;
  text-underline-offset: 3px;
  transform: none;
}
.btn-small:hover {
  color: var(--ink);
  box-shadow: none;
  transform: none;
}

/* ── EL REPORTE ───────────────────────────────────────────────────────────
   Dos cifras arriba y una tabla con barra de reparto.

   LA BARRA VA DENTRO DE LA CELDA y no en una columna de ancho fijo, porque la
   tabla ya tiene cinco columnas y una sexta solo para el dibujo dejaria el
   nombre del motivo sin sitio en un portatil. */
.rep-heads {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem;
  margin: 1.25rem 0 1.5rem;
}
.rep-head { display: flex; flex-direction: column; gap: .15rem; }
.rep-head-n { font-size: 2rem; font-weight: 600; line-height: 1; color: var(--ink); }
.rep-head-l { font-size: .85rem; color: var(--concrete); }

/* El ancho lo pone `--rep-pct` desde el JS. Con `inline-block` y un minimo de
   2px, un motivo del 1% sigue viendose: una barra de cero ancho se lee como un
   dato que falta. */
.rep-bar {
  display: inline-block;
  vertical-align: middle;
  height: .5rem;
  width: max(2px, var(--rep-pct, 0%));
  max-width: 8rem;
  background: var(--accent, currentColor);
  border-radius: 999px;
  margin-inline-end: .5rem;
  opacity: .8;
}
.rep-pct { font-size: .85rem; color: var(--concrete); }
/* Lo que sigue abierto ahora mismo se destaca: es lo unico de esta tabla sobre
   lo que se puede actuar hoy. */
.rep-open { font-weight: 600; color: var(--ink); }

/* ── EL ESTADO VACIO ──────────────────────────────────────────────────────
   Dos parrafos y a veces una accion. El primero dice QUE PASA y el segundo QUE
   HACER, y van separados a proposito: juntos en una linea se leen como una
   disculpa, y quien tiene prisa se salta la mitad que le servia.

   No lleva icono. Un dibujo grande en una lista vacia ocupa el sitio donde
   deberia estar la primera fila y hace que la pantalla parezca terminada. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .5rem;
  padding: 2rem 0;
  max-width: 44ch;   /* medida de lectura, no ancho de pantalla */
}
.empty-primary {
  margin: 0;
  font-weight: 600;
  color: var(--ink);
}
.empty-secondary {
  margin: 0;
  color: var(--concrete);
  line-height: 1.5;
}
.empty-state .btn { margin-top: .35rem; }

/* La version y su marca de "en uso" son dos cosas y se separan: pegadas se leen
   como una sola palabra ("v1IN USE"). */
.list-table td .list-tag { margin-inline-start: .4rem; }

.list-actions-cell {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: center;
  gap: .35rem;
  white-space: nowrap;
  vertical-align: middle;
}
.list-actions-cell .btn,
.list-actions-cell a.btn {
  flex-shrink: 0;
  min-height: 2.75rem;
}
/* Botones de fila: color semántico, no solo borde gris. */
.btn-action-edit,
a.btn-action-edit {
  border: 1px solid var(--state-notice);
  color: var(--state-notice);
  background: color-mix(in srgb, var(--state-notice) 8%, transparent);
}
.btn-action-edit:hover,
a.btn-action-edit:hover {
  background: color-mix(in srgb, var(--state-notice) 16%, transparent);
  color: var(--state-notice);
}
.btn-action-off {
  border: 1px solid var(--state-stopped);
  color: var(--state-stopped);
  background: color-mix(in srgb, var(--state-stopped) 8%, transparent);
}
.btn-action-off:hover {
  background: color-mix(in srgb, var(--state-stopped) 14%, transparent);
}
.btn-action-on {
  border: 1px solid var(--state-running);
  color: var(--state-running);
  background: color-mix(in srgb, var(--state-running) 8%, transparent);
}
.btn-action-on:hover {
  background: color-mix(in srgb, var(--state-running) 14%, transparent);
}
.btn-action-delete {
  border: 1px solid var(--state-stopped);
  color: var(--state-stopped);
  background: transparent;
}
.btn-action-delete:hover {
  background: color-mix(in srgb, var(--state-stopped) 12%, transparent);
}
.list-table th:last-child,
.list-table td.list-actions-cell {
  width: 1%;
}

.crud-form-back { margin: 0 0 1rem; }
