.topbar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px;
  padding: 12px 24px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-surface);
}

.tb-left { display: flex; align-items: center; gap: 10px; }
.tb-left .tb-logo { width: 48px; height: 48px; flex-shrink: 0; }
/* El tamaño lo escribe brand.js INLINE (`data-kfb-logo="40"`), y un
   estilo inline le gana a cualquier media query: en el celular la caja
   se achicaba a 26 y el contenido seguía en 40, saliéndose 14px y
   montándose 8px sobre el wordmark. Por eso se leía "FB Kiosco" en vez
   de "KFB Kiosco" -- el logo tapaba la K. El `!important` es para
   ganarle al inline, que es su único uso legítimo. */
.tb-left .tb-logo .kfb-logo-wrap { width: 100% !important; height: 100% !important; }
/* Sólo tamaño. Lo demás lo pone css/brand.css. */
/* La marca, más grande: es lo primero que se lee y estaba compitiendo de
   igual a igual con la chapita del plan. */
.tb-left .tb-word { font-size: 1.1rem; line-height: 1.2; display: inline-flex; }

.tb-center {
  display: flex;
  align-items: center;
  justify-content: center;
}
.user-pill {
  background: linear-gradient(135deg, rgba(201,169,110,.16), rgba(201,169,110,.08));
  border: 1px solid var(--border-accent);
  border-radius: var(--r-full);
  padding: 8px 26px;
  font-size: .84rem;
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--gold-text);
  white-space: nowrap;
}

.tb-right { display: flex; align-items: center; justify-content: flex-end; gap: 8px; }

.tenant-name {
  font-family: var(--serif);
  /* Más chico que antes (.88): en la barra el nombre del local es una
     etiqueta, no un título -- el que está adentro ya sabe en qué kiosco
     trabaja. Y con tope de ancho: un nombre largo ("Almacén y Kiosco
     Doña Pura") empujaba a los botones fuera de la pantalla. */
  font-size: .74rem;
  color: var(--text);
  margin-right: 4px;
  white-space: nowrap;
  /* 22 caracteres entra "Kiosco Demostración" completo. El tope sigue
     estando para el nombre largo de verdad ("Almacén y Kiosco Doña Pura"),
     que si no empuja los botones fuera de la pantalla. */
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tb-plan {
  background: var(--bg-card);
  border: 1px solid var(--border-accent);
  border-radius: var(--r-full);
  padding: 5px 14px;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .06em;
  color: var(--gold-text);
  cursor: pointer;
}
.tb-plan:hover { background: var(--bg-hover); }
.tb-plan.plan-full { background: rgba(34,197,94,.12); border-color: rgba(34,197,94,.4); color: var(--green-text); }
.tb-plan.plan-full:hover { background: rgba(34,197,94,.2); }
.tb-plan.plan-std { background: rgba(59,130,246,.12); border-color: rgba(59,130,246,.4); color: var(--blue-sys); }
.tb-plan.plan-std:hover { background: rgba(59,130,246,.2); }
.tb-plan.plan-bas { background: rgba(139,90,43,.14); border-color: rgba(139,90,43,.45); color: var(--gold-text); }
.tb-plan.plan-bas:hover { background: rgba(139,90,43,.24); }

.tb-icon {
  /* `text-decoration:none` porque el de Manual es un <a>, no un <button>: un
     `window.open` que el navegador bloquea devuelve null y no abre nada, sin
     que nadie se entere. Ya pasó dos veces en este repo. */
  text-decoration: none;
  width: 36px;
  height: 36px;
  border-radius: var(--r-full);
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text-sub);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  line-height: 1;
  box-shadow: 0 1px 3px rgba(0,0,0,.18);
  transition: background .12s ease, border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
.tb-icon:hover {
  background: var(--bg-hover);
  border-color: var(--border-accent);
  color: var(--gold-text);
  box-shadow: 0 2px 10px rgba(201,169,110,.22);
  transform: translateY(-1px);
}

@media (max-width: 640px) {
  .topbar { grid-template-columns: auto 1fr auto; padding: 8px 10px; gap: 6px; }
  .tb-left { gap: 6px; }
  /* El logo de KFB, IGUAL de grande que el del kiosco (34px). Estaba en 26
     y al lado del otro parecía el de un invitado. */
  /* En el TELÉFONO PARADO vuelven al tamaño que entraba en un renglón.
     Canu: en horizontal más grande; "en la foto vertical dejalo así como
     está". Agrandarlos también acá lo tiraba al segundo renglón. */
  .tb-left .tb-logo { width: 34px; height: 34px; }
  .tb-left .tb-word { font-size: .62rem; }
  .tb-left .tb-word span { font-size: .5rem; letter-spacing: .14em; }
  /* Y "KFB" un poco más grande que la chapita del plan: medido, estaba en
     8px contra los 8.8 de FULL -- o sea que la marca se leía MENOS que el
     plan, que es al revés de lo que tiene que ser. */
  .tb-left .kfb-marca .kfb-corto { font-size: .72rem; letter-spacing: .06em; }
  .user-pill { font-size: .68rem; padding: 6px 14px; }
  .tenant-name { display: none; }
  .tb-right { gap: 4px; }
  .tb-plan { padding: 4px 9px; font-size: .62rem; }
  .tb-icon { width: 28px; height: 28px; }
  .tb-icon svg { width: 14px; height: 14px; }
  /* Los tres temas SE VEN LOS TRES. Estuvieron un rato mostrando sólo el
     activo, y tocarlo ciclaba -- Canu lo probó y el globo decía "Tema
     claro / oscuro": el marrón no aparecía por ningún lado. Un tema que
     existe y no se puede elegir es un tema que no existe. Ahora la barra
     baja a dos renglones cuando no entra (abajo), así que hay lugar. */
}

/* Con los cuatro botones ya en el menú, lo que QUEDA todavía no entraba:
   se cortaba 5px a 412 y 38px a 360 (medido). Lo que más ocupa es la
   píldora con el nombre y el rol -- 101px de los 360. Acá se achica, que
   es preferible a esconderla: saber con qué usuario estás trabajando es
   media pantalla de un mostrador compartido. */
@media (max-width: 430px) {
  /* Medido a 412 con todo puesto: la barra necesitaba 410 de 412 -- entraba
     por DOS píxeles, o sea que cualquier nombre un poco más largo la tiraba
     al segundo renglón. Acá se recortan ~30px para que entre cómoda y de
     verdad quede en UNO, que es lo que Canu pidió ("queda muy feo" en dos).
     Lo que se achica es tamaño, no información: no se esconde nada. */
  .user-pill { font-size: .55rem; padding: 4px 7px; letter-spacing: 0; }
  .tb-icon { width: 26px; height: 26px; }
  .tb-icon svg { width: 13px; height: 13px; }
  .theme-switcher .theme-switch-btn { width: 26px; height: 26px; }
  .tb-right { gap: 3px; }
  .tb-local { gap: 5px; }
  .tb-plan { padding: 2px 6px; font-size: .55rem; }
  .tb-left { gap: 4px; }
  .topbar { gap: 4px; padding: 8px 6px; }
  /* La chapita "Kiosco" al lado del wordmark se va: son 34px para decir
     algo que el que está adentro de la app ya sabe. La marca sigue
     entera --KFB con su F roja-- y vuelve apenas hay lugar. */
  .tb-left .kfb-marca .kfb-chapa { display: none; }
}

/* Y en el más chico de todos (320) todavía faltaban 17px. Se va el chip
   del plan: es lo único de la barra que NUNCA cambia mientras trabajás,
   así que es lo que menos se extraña. Sigue estando en Configuración. */
@media (max-width: 380px) {
  .tb-right .tb-plan { display: none; }
}

/* El local: logo a la izquierda, nombre y plan apilados a la derecha.
   El chip del plan iba al lado del logo y era un ancho más en una barra
   que ya no entraba; abajo del nombre no cuesta nada. */
/* `margin-right: auto` empuja el bloque del local a la IZQUIERDA de la zona
   derecha: le deja todo el aire libre al nombre en vez de amontonarlo contra
   los botones. Canu: "tiralo mas para la izquierda". */
.tb-local { display: flex; align-items: center; gap: 8px; margin-right: auto; }
.tb-local-logo { display: inline-flex; align-items: center; }
/* El logo del kiosco mide LO MISMO que el de KFB, en cada tamaño: 40 en
   pantalla ancha, 34 en el teléfono. Que uno sea más grande que el otro
   hace que el chico parezca de un invitado. */
.tb-local-logo img, .tb-local-logo canvas, .tb-local-logo svg {
  width: 48px !important; height: 48px !important;
}
@media (max-width: 640px) {
  .tb-local-logo img, .tb-local-logo canvas, .tb-local-logo svg {
    width: 34px !important; height: 34px !important;
  }
}
/* El nombre y el plan, UNO AL LADO DEL OTRO. Se apilan sólo en el
   teléfono, que es donde no entran de a dos. */
.tb-local-txt { display: flex; align-items: center; gap: 7px; line-height: 1.1; }
@media (max-width: 640px) {
  .tb-local-txt { flex-direction: column; align-items: flex-start; gap: 2px; }
}

/* UN SOLO BOTÓN DE TEMA. Se ve el que está puesto y tocarlo pasa al
   siguiente -- los tres se llevaban 96px de los 412 de un celular.
   Canu: *"lo de los temas se los achicás a uno"*.
   El globo sigue nombrando a los tres, que fue lo que faltó la vez
   anterior: un tema que existe y no se puede elegir no existe. */
/* Acotado a `.topbar` A PROPOSITO: el que ahorra lugar es el de la barra
   del operador, donde compiten seis botones. En la LANDING y en la
   calculadora no hay esa pelea y se ven los TRES -- ahí el que llega de
   afuera tiene que ver que la app viene en tres temas, no descubrirlo
   tocando.

   Sin el `.topbar` la regla se colaba a la landing, que carga el mismo
   app.css. Canu lo vio: "en la landing no, que estén los tres". */
.topbar .theme-switcher .theme-switch-btn:not(.active) { display: none; }

/* ── LA BARRA BAJA A DOS RENGLONES CUANDO NO ENTRA ──────────────
   Antes esto se resolvía MOVIENDO los cuatro botones de la derecha
   (empleados, notificaciones, chat, configuración) al menú lateral. Canu
   lo probó en su teléfono y lo devolvió con razón: adentro del menú se
   veían rotos, y arriba directamente faltaban. Textual: *"me parece que
   tendrían que estar, che"*.

   Y tenía razón de fondo: no es sólo cómo se veían. Un ícono que se
   MUEVE de lugar según el ancho de la pantalla no se encuentra nunca —
   el que aprendió que las notificaciones están arriba a la derecha, al
   girar el teléfono ya no las tiene ahí.

   Ahora nada se mueve: si no entra en un renglón, usa dos. Lo decide el
   navegador con `flex-wrap`, no una medición mía ni un ancho escrito a
   mano (los dos fallaron: el ancho fijo, dos veces; la medición, porque
   la barra se llena DESPUÉS de cargar y medía la barra vacía).

   Efecto colateral bueno: se borró todo el JS que movía nodos, sus dos
   observadores y la bandera anti-loop. */
@media (max-width: 1024px) {
  .topbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    row-gap: 6px;
  }
  .tb-left { flex: 0 0 auto; }
  .tb-center { flex: 1 1 auto; justify-content: center; }
  /* Al pasar al segundo renglón ocupa todo el ancho y se alinea a la
     derecha, que es donde el ojo ya los buscaba. */
  .tb-right { flex: 1 1 auto; justify-content: flex-end; }
}

@media (min-width: 641px) and (max-width: 1024px) {
  .tb-right { gap: 4px; }
  .tb-plan { padding: 4px 9px; font-size: .62rem; }
  .tb-icon { width: 28px; height: 28px; }
  .tb-icon svg { width: 14px; height: 14px; }
}

.placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
  text-align: center;
  color: var(--text-muted);
  padding: 24px;
}
.placeholder h1 {
  font-family: var(--serif);
  color: var(--text);
  font-size: 1.6rem;
  margin-bottom: 8px;
}

/* Modal genérico (plan / config) reutilizado en las 3 pantallas de rol */
.kfb-modal-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,.55);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 60;
  padding: 20px;
  /* mismo motivo que .modal-overlay: en celular con teclado abierto el
     modal no entra y hay que poder desplazarse hasta los botones */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.kfb-modal-overlay.show { display: flex; }
@media (max-height: 720px) {
  .kfb-modal-overlay { align-items: flex-start; }
}
.kfb-modal-card {
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: 26px;
  width: 100%;
  max-width: 360px;
  text-align: center;
}
.kfb-modal-card h3 { font-family: var(--serif); margin: 0 0 10px; }
.kfb-modal-card p { color: var(--text-sub); font-size: .88rem; margin: 0 0 20px; }
.kfb-modal-card button {
  width: 100%;
  padding: 12px;
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  cursor: pointer;
  font-size: .9rem;
}
.kfb-modal-card button:hover { background: var(--bg-hover); }

/* ── Cartelito al pasar el mouse ───────────────────────────────
   Los íconos de la barra no dicen qué son. En la compu se resuelve
   con un cartelito al posar el mouse; en el celular no aparece
   (no hay mouse) y no molesta. Usa el aria-label que ya tienen,
   así no hay un segundo texto que mantener. */
.tb-icon, .theme-switcher { position: relative; }

.tb-icon[aria-label]::after,
.theme-switcher[aria-label]::after {
  content: attr(aria-label);
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(-4px);
  padding: 5px 10px;
  border-radius: var(--r-sm);
  background: var(--bg-surface);
  border: 1px solid var(--border-accent);
  color: var(--text);
  font-size: .7rem;
  font-weight: 500;
  white-space: nowrap;
  letter-spacing: .01em;
  box-shadow: 0 6px 18px rgba(0,0,0,.35);
  opacity: 0;
  pointer-events: none;
  transition: opacity .14s ease, transform .14s ease;
  z-index: 90;
}
.tb-icon[aria-label]::before,
.theme-switcher[aria-label]::before {
  content: '';
  position: absolute;
  top: calc(100% + 3px);
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-bottom-color: var(--border-accent);
  opacity: 0;
  pointer-events: none;
  transition: opacity .14s ease;
  z-index: 91;
}
.tb-icon[aria-label]:hover::after,
.theme-switcher[aria-label]:hover::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.tb-icon[aria-label]:hover::before,
.theme-switcher[aria-label]:hover::before { opacity: 1; }

/* En el celular no hay mouse: el :hover queda pegado después de
   tocar y el cartel se planta en pantalla. */
@media (hover: none) {
  .tb-icon[aria-label]::after, .tb-icon[aria-label]::before,
  .theme-switcher[aria-label]::after, .theme-switcher[aria-label]::before { display: none; }
}

/* ── Los diálogos de la app ────────────────────────────────────
   Reemplazan al confirm() y prompt() grises del navegador. */
.kfb-dialogo { max-width: 440px; text-align: left; }
.kfb-dialogo h3 { margin: 0 0 8px; font-family: var(--serif); font-size: 1.15rem; }
.kfb-dialogo .sub { margin: 0 0 16px; line-height: 1.55; }

.kfb-dlg-campo { margin-bottom: 18px; }
.kfb-dlg-campo input {
  width: 100%;
  box-sizing: border-box;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 12px 14px;
  color: var(--text);
  font-family: var(--sans);
  font-size: .95rem;
  outline: none;
}
.kfb-dlg-campo input:focus { border-color: var(--gold); }
.kfb-dlg-ayuda { margin-top: 6px; font-size: .72rem; color: var(--text-muted); }
.kfb-dlg-error { margin-top: 6px; font-size: .76rem; color: var(--error-text); font-weight: 600; }

.kfb-dlg-botones { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.kfb-dlg-botones button { padding: 12px; font-size: .9rem; }
/* El botón que borra algo va en rojo: el color avisa antes que el texto. */
.kfb-dlg-botones .btn-peligro {
  background: var(--error);
  border-color: var(--error);
  color: #fff;
}
.kfb-dlg-botones .btn-peligro:hover { filter: brightness(1.1); }

/* ── Avisos de la app ────────────────────────────────────────────
   Reemplazan al alert() del navegador, que era una caja gris del
   sistema con el dominio arriba ("localhost:8790 dice:") y un solo
   botón Aceptar. En un mostrador eso obliga a soltar lo que tenés en
   la mano para confirmar que leíste un "listo".
   Se van solos. El de error dura más y se puede tocar para cerrarlo. */
#kfbAvisos {
  position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
  z-index: 3000; display: flex; flex-direction: column; gap: 8px;
  width: min(440px, calc(100vw - 24px)); pointer-events: none;
}
.kfb-aviso {
  pointer-events: auto; cursor: pointer;
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px; border-radius: var(--r-md);
  border: 1px solid var(--border); background: var(--bg-surface);
  box-shadow: 0 8px 24px rgba(0,0,0,.28);
  font-size: .84rem; line-height: 1.35; color: var(--text);
  animation: kfbAvisoEntra .18s ease-out;
}
.kfb-aviso.saliendo { animation: kfbAvisoSale .22s ease-in forwards; }
.kfb-aviso .kfb-aviso-icono { font-size: 1rem; line-height: 1.2; }
.kfb-aviso.ok    { border-color: rgba(34,197,94,.45); }
.kfb-aviso.aviso { border-color: rgba(245,158,11,.55); }
.kfb-aviso.error { border-color: var(--error); }
.kfb-aviso.ok .kfb-aviso-icono    { color: var(--green-text); }
.kfb-aviso.aviso .kfb-aviso-icono { color: var(--warning-text); }
.kfb-aviso.error .kfb-aviso-icono { color: var(--error-text); }

/* Una línea del color a la izquierda: el borde solo, en un aviso
   chico, casi no se ve — sobre todo en tema claro. */
.kfb-aviso { border-left-width: 4px; }

@keyframes kfbAvisoEntra { from { opacity: 0; transform: translateY(-10px); } }
@keyframes kfbAvisoSale  { to   { opacity: 0; transform: translateY(-10px); } }

/* Quien pidió menos movimiento no ve el deslizamiento. */
@media (prefers-reduced-motion: reduce) {
  .kfb-aviso, .kfb-aviso.saliendo { animation: none; }
}

/* ── Aviso de versión nueva ────────────────────────────────────
   Fijo arriba, por encima de todo. No es un toast que se va solo:
   se queda hasta que el usuario decida, porque lo que propone es
   recargar la página. */
.kfb-version {
  position: fixed; top: 0; left: 0; right: 0; z-index: 10000;
  display: flex; align-items: stretch; gap: 0;
  background: linear-gradient(90deg, var(--gold), var(--gold-dark));
  color: #1a1a1a;
  box-shadow: 0 6px 20px rgba(0,0,0,.35);
  transform: translateY(-100%);
  transition: transform .28s cubic-bezier(.16,1,.3,1);
  padding-top: env(safe-area-inset-top, 0);
}
.kfb-version.visible { transform: translateY(0); }

.kfbv-ya {
  flex: 1; display: flex; align-items: center; gap: 12px;
  padding: 11px 16px; border: 0; background: transparent; color: inherit;
  font: inherit; text-align: left; cursor: pointer;
}
.kfbv-ya:hover { background: rgba(0,0,0,.07); }
.kfbv-ico { font-size: 1.25rem; line-height: 1; }
.kfbv-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.kfbv-txt b { font-size: .88rem; font-weight: 800; letter-spacing: -.01em; }
.kfbv-sub {
  font-style: normal; font-size: .72rem; opacity: .78;
  font-variant-numeric: tabular-nums;
}
.kfbv-no {
  flex: 0 0 auto; width: 46px; border: 0; border-left: 1px solid rgba(0,0,0,.14);
  background: transparent; color: inherit; font-size: 1rem; cursor: pointer;
}
.kfbv-no:hover { background: rgba(0,0,0,.1); }

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

/* ── Botón de texto, sin caja ──────────────────────────────────
   "Cambiar de kiosco" en el login, "Usar como default" en el margen,
   "+ Agregar ingrediente" y "Quitar" en las recetas. Vivía en
   login.css, que sólo carga el login: en el Dueño y el Encargado los
   seis botones salían blancos. Una sola definición, en el archivo que
   cargan todas las pantallas. */
.forget-device {
  display: block;
  text-align: center;
  margin-top: 10px;
  font-size: .75rem;
  color: var(--text-muted);
  cursor: pointer;
  background: none;
  border: none;
  font-family: var(--sans);
  text-decoration: underline;
}
.forget-device:hover { color: var(--text-sub); }

/* ══════════════════════════════════════════════════════════════
   LO QUE PASA CUANDO UN CONTROL NO TIENE CLASE
   El navegador lo pinta con SU estilo: rectángulo blanco, borde gris,
   letra de sistema. Sobre un fondo oscuro se ve como si la pantalla
   estuviera rota -- y era literal: 18 botones y una pila de campos de
   la app salían así, incluido el link del catálogo.

   La respuesta no es ponerle una clase a cada uno (mañana entra otro
   sin clase y vuelve el problema): es vestir a LOS QUE NO TIENEN
   CLASE, y sólo a ésos.

   El :not([class]) no es un adorno. El primer intento vestía el
   elemento a secas, y aunque cualquier regla con clase le gana en
   color, el TAMAÑO se colaba igual: los botones que no declaran su
   propio padding lo heredaban de acá. Se midieron cinco clases
   agrandadas de una, entre ellas los iconitos de la barra y el
   selector de tema, que pasaron de 28px de alto a 38 y se veían como
   una pastilla dorada gigante. Vestir lo que no tiene clase no puede
   tocar a nada que sí la tenga.
   ══════════════════════════════════════════════════════════════ */
button:not([class]) {
  font-family: var(--sans);
  font-size: .82rem;
  padding: 9px 14px;
  min-height: 38px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  cursor: pointer;
  transition: border-color .15s, background .15s, color .15s;
}
button:not([class]):hover:not(:disabled) { background: var(--bg-hover); border-color: var(--border-accent); }
button:not([class]):disabled { opacity: .5; cursor: default; }

input:not([class]), select:not([class]), textarea:not([class]) {
  font-family: var(--sans);
  font-size: .9rem;
  padding: 9px 12px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--bg-card);
  color: var(--text);
  outline: none;
}
input:not([class]):focus, select:not([class]):focus, textarea:not([class]):focus { border-color: var(--gold); }
input:not([class])::placeholder, textarea:not([class])::placeholder { color: var(--text-muted); }
/* Los que NO son cajas de texto: el tilde y el redondel se pintan con
   accent-color, y una caja alrededor los deformaría. */
input[type="checkbox"]:not([class]), input[type="radio"]:not([class]) {
  padding: 0; border: none; background: none;
  accent-color: var(--gold);
  width: 16px; height: 16px; cursor: pointer;
}
input[type="file"]:not([class]) { padding: 7px 10px; font-size: .8rem; color: var(--text-sub); }

/* El input de archivo nativo trae su PROPIO ancho -- el botón más el
   "Ningún archivo seleccionado" -- y sin tope no se achica nunca: se
   sale por la derecha de la tarjeta que lo contiene. Se vio en el alta
   de productos del Dueño, donde la columna de la grilla puede bajar a
   140px.
   Va SIN `:not([class])` y acá y no en catalog.css a propósito: de los
   seis que hay en la app, dos (el logo del local y el fondo del
   catálogo) NO viven en un `.form-field`, así que una regla atada a esa
   clase habría dejado a esos dos con el mismo problema.
   Chrome recorta el nombre del archivo, que es lo que corresponde. */
input[type="file"] { max-width: 100%; box-sizing: border-box; }
input[type="range"]:not([class]) { padding: 0; border: none; background: none; accent-color: var(--gold); }

/* El sello de kiosco DEMO se viste desde js/sello-demo.js: el
   catálogo público lo usa y NO carga esta hoja. */

/* ── Campos sueltos: .token-input ────────────────────────────────
   Nació en el login (el campo del token) y terminó usada en veinte
   campos de los modales del mostrador: monto de la conversión, el
   arqueo, el motivo de una rotura, la cantidad a devolver.

   El problema: vivía en css/login.css, que SÓLO carga el login. En el
   Dueño y el Encargado la clase no existía, así que esos campos salían
   con el rectángulo por defecto del navegador -- borde cuadrado, gris
   sólido y 21px de alto, la mitad de lo que necesita un dedo.

   Es EXACTAMENTE el mismo bug que ya había pasado con .forget-device,
   y volvió a pasar con otra clase. Por eso el estilo base se muda acá,
   a app.css, que lo cargan las cuatro pantallas. En login.css queda
   sólo lo propio del token (mayúsculas y espaciado), que en un campo
   de monto o de motivo estaría mal.

   Mismos valores que `.form-field input` de catalog.css: si los campos
   de un formulario y los de un modal se ven distintos, la app se ve
   armada por dos personas. */
/* SIN `flex: 1`. Lo tenía heredado del login, donde el campo del token
   comparte fila con el ojito y el botón de confirmar y tiene que
   quedarse con lo que sobra. Pero adentro de una fila de la lista de
   reposición, ese mismo `flex: 1` hacía que el casillero de "cuántos
   comprar" midiera 565px de ancho para escribir un "2", empujando el
   nombre del producto al otro extremo de la pantalla. Eso es el "mucho
   aire entre columnas" que marcó Canu.

   `flex-basis: 0` le gana a cualquier `width`, incluso a uno puesto a
   mano en el elemento, así que el `width:84px` de compras.js no tenía
   forma de defenderse. El que necesita crecer lo pide donde crece. */
.token-input {
  min-width: 0;
  width: 100%;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  padding: 11px 12px;
  font-family: var(--sans);
  font-size: .9rem;
  color: var(--text);
  outline: none;
  transition: border-color .15s ease;
}
.token-input:focus { border-color: var(--gold); }
.token-input::placeholder { color: var(--text-muted); }
select.token-input { cursor: pointer; }

/* ── Los títulos de sección de un desplegable ────────────────────
   Los <optgroup> los pinta el navegador con SUS colores, y en el tema
   oscuro salían blanco sobre blanco: dos barras vacías (se vio en el
   "Qué lleva" de las ofertas). Chrome sí respeta color y fondo puestos
   a mano en optgroup/option, así que se le dan los de la app. */
select optgroup {
  color: var(--text-sub);
  background: var(--bg-surface);
  font-style: normal;
  font-weight: 700;
}
select option {
  color: var(--text);
  background: var(--bg-surface);
}

/* ── Badge numérico sobre un ícono de la topbar ──────────────────
   (empleados, notificaciones, chat). Vive acá y no en owner.css
   porque lo usan las tres pantallas y el Cajero no carga owner.css. */
.tb-icon-wrap { position: relative; display: inline-flex; }
.tb-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--r-full);
  background: var(--error);
  color: #fff;
  font-size: .62rem;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}
/* El del chat se posiciona igual pero el botón no está en un wrap:
   el botón mismo es el ancla. */
#chatBtn { position: relative; }

/* ── El panel del chat ───────────────────────────────────────────*/
.chat-card { width: min(440px, 92vw); display: flex; flex-direction: column; }
.chat-mensajes {
  flex: 1;
  min-height: 200px;
  max-height: min(50vh, 420px);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 4px 2px;
  margin: 10px 0;
}
.chat-globo {
  max-width: 84%;
  align-self: flex-start;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px 10px 10px 3px;
  padding: 8px 11px;
}
.chat-globo.propio {
  align-self: flex-end;
  border-radius: 10px 10px 3px 10px;
  border-color: var(--border-accent);
  background: rgba(201, 169, 110, .08);
}
.chat-quien { font-size: .66rem; font-weight: 700; color: var(--gold-text); margin-bottom: 2px; }
.chat-texto { font-size: .88rem; line-height: 1.35; overflow-wrap: break-word; }
.chat-hora { font-size: .6rem; color: var(--text-muted); text-align: right; margin-top: 2px; }
.chat-envio { display: flex; gap: 8px; }
.chat-envio .token-input { flex: 1; }
.chat-envio .btn-confirm { padding: 11px 16px; }

/* ── La lista de charlas ──────────────────────────────────────────
   Canu, 06/09: "1 grupo entre todos, una charla con cada empleado".

   UNA A LA VEZ, NO DOS COLUMNAS. Gastro pone la lista al costado y el
   hilo al lado, que en una PC va bien. Esto se abre en la tablet del
   mostrador: en 375px dos columnas dejan la charla en 200px, o sea
   ilegible. Va lista -> charla -> flecha para volver.

   La cabecera es su propia fila para que quepa la flecha. La ✕ sigue
   siendo `.modal-x`, que va posicionada sobre la tarjeta: por eso el
   hueco a la derecha se reserva con padding y no con un tercer hijo
   del flex -- si se lo pone adentro, se superponen. */
.chat-cab {
  display: flex; align-items: center; gap: 4px;
  padding-right: 46px;          /* el hueco de la ✕ */
  margin-bottom: 4px;
}
.chat-cab h3 { margin: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.chat-volver {
  flex: none;
  /* 40 y no 34: es lo primero que toca el dedo para salir de una
     charla, y en la tablet del mostrador se toca de apuro. */
  width: 40px; height: 40px; margin-left: -8px;
  display: grid; place-items: center;
  border: 0; background: transparent; cursor: pointer;
  color: var(--text-sub);
  font-size: 1.6rem; line-height: 1;
  border-radius: var(--r-sm);
  transition: color .15s, background .15s;
}
.chat-volver:hover { color: var(--gold-text); background: var(--bg-hover); }

.chat-lista {
  display: flex; flex-direction: column;
  min-height: 200px;
  max-height: min(56vh, 460px);
  overflow-y: auto;
  margin: 6px -6px 0;           /* se come el padding de la tarjeta:
                                   los renglones llegan a los bordes,
                                   como en cualquier chat */
}

.chat-conv {
  display: flex; align-items: center; gap: 11px;
  width: 100%;
  padding: 10px 14px;
  border: 0; border-bottom: 1px solid var(--border);
  background: transparent;
  text-align: left; cursor: pointer;
  font: inherit; color: inherit;
  transition: background .15s;
}
.chat-conv:last-child { border-bottom: 0; }
.chat-conv:hover { background: var(--bg-hover); }

/* Las iniciales. El grupo lleva "#" y el color de acento: se distingue
   de una persona sin tener que leer el nombre. */
.chat-conv-av {
  flex: none;
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--bg-card);
  border: 1px solid var(--border);
  font-size: .74rem; font-weight: 700; letter-spacing: .04em;
  color: var(--text-sub);
}
.chat-conv-av.grupo {
  background: rgba(201, 169, 110, .12);
  border-color: var(--border-accent);
  color: var(--gold-text);
  font-size: 1rem;
}

.chat-conv-txt { flex: 1; min-width: 0; display: grid; gap: 1px; }
.chat-conv-top { display: flex; align-items: baseline; gap: 8px; }
.chat-conv-nombre {
  flex: 1; min-width: 0;
  font-size: .88rem; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-conv-hora { flex: none; font-size: .62rem; color: var(--text-muted); }
/* Una sola línea con puntos suspensivos: el último mensaje es una
   pista de qué charla es, no el mensaje. */
.chat-conv-sub {
  font-size: .74rem; color: var(--text-sub);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chat-conv-rol { font-style: normal; color: var(--text-muted); }

.chat-conv-badge {
  flex: none;
  min-width: 20px; height: 20px; padding: 0 6px;
  display: grid; place-items: center;
  border-radius: 9999px;
  background: var(--error); color: #fff;
  font-size: .64rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.chat-hilo { display: flex; flex-direction: column; flex: 1; min-height: 0; }

/* El atributo `hidden` es SOLO `display:none` de la hoja del navegador,
   y cualquier clase que declare un display le gana: `.form-field` es
   `display:grid`, asi que un campo marcado hidden se seguia viendo. Ya
   habia costado un rato en el bloque de resenas del catalogo y estaba
   parchado en dos lados sueltos (`.cat-resena [hidden]`, `.lp-form[hidden]`).
   Esto lo cierra para toda la app de una. `catalogo.html` no carga este
   archivo y conserva el suyo. */
[hidden] { display: none !important; }

/* ══════════════════════════════════════════════════════════════
   EL PISO DE FOCO — que se vea dónde estás parado con el teclado.

   Medido el 29/08 en la pantalla del Cajero: de 19 controles
   visibles, **1 solo** matcheaba alguna regla de foco. Los otros 18
   —toda la barra de arriba y todos los filtros de categoría— no
   mostraban absolutamente nada al recibir el foco.

   No era una decisión: había reglas de `:focus-visible` sueltas en la
   landing y en el catálogo, y ninguna en las pantallas del operador.
   O sea que se fue poniendo donde alguien se acordó.

       Un foco invisible es peor que ninguno: la persona sabe que se
       movió y no sabe adónde.

   Va con `:where()` a propósito: eso le da especificidad CERO, así
   que es un PISO y no un override. Cualquier regla propia que ya
   exista le gana sin pelear.

   Y va en `:focus-visible` y no en `:focus`: el que toca con el dedo
   o con el mouse no tiene por qué ver un anillo. Lo decide el
   navegador, que es el que sabe cómo llegó el foco.

   Los campos de texto quedan como estaban —tienen su `outline: none`
   de base y avisan cambiando el color del borde—, y está bien: su
   regla es más específica y gana.
   ══════════════════════════════════════════════════════════════ */
:where(a, button, input, select, textarea, summary, [tabindex], [role="button"]):focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* En la grilla del mostrador el anillo va HACIA ADENTRO: las tarjetas
   están pegadas una al lado de la otra y un anillo por fuera se
   superpone con la de al lado. */
:where(.product-card):focus-visible {
  outline-offset: -3px;
}

/* Los filtros de categoría medían 31px de alto. Con el dedo, en un
   mostrador con gente esperando, eso es errarle. Se agrandan SOLO
   donde se toca con el dedo: con mouse, 31px se acierta sin pensar y
   agrandarlos comería lugar de la grilla. */
@media (pointer: coarse) {
  .cat-chip { min-height: 44px; }
  /* En la barra se muestra UNO solo de los tres (el activo hace de
     interruptor), asi que agrandarlo no empuja nada. Medido con dedo
     de verdad: era el ultimo control que quedaba abajo de 44. */
  .topbar .theme-switcher .theme-switch-btn { width: 44px; height: 44px; }

  /* ── Y EL PISO PARA TODO LO DEMAS ──────────────────────────────
     Medido con dedo de verdad en la pantalla del Dueño: de 8 pestañas,
     todas tenían controles abajo de 44 — los 11 ítems del menú a 35px,
     los botones Día/Semana/Mes a 21, y los campos de los formularios
     entre 37 y 43. El Dueño mira los números desde el celular.

     Va con `:where()`, o sea especificidad CERO: es un PISO y no un
     override. Cualquier regla propia le gana sin pelear, y la que
     necesite ser más chica a propósito sólo tiene que decirlo.

     `min-height` y no `height`: agrandar el alto mínimo no rompe nada
     que ya entraba; fijar la altura sí. */
  :where(button, a.btn, input, select, textarea, [role="button"],
         .sb-item, .btn-primary, .btn-secondary, .btn-cancel,
         .btn-confirm, .btn-mini, .btn-add, .bal-modo-btn) {
    min-height: 44px;
  }
  /* Los tildes son cuadrados: el `min-height` solo los estira. */
  :where(input[type="checkbox"], input[type="radio"]) {
    min-width: 24px; min-height: 24px;
  }

  /* ── LOS QUE EL PISO NO PUDO LEVANTAR ──────────────────────────
     `:where()` vale CERO, asi que cualquier regla de clase le gana.
     Estos tres tenian una y se quedaron abajo de 44 -- medido, no
     supuesto:

         .sb-item ......... 35px  (`min-height: 0` en owner.css)
         botones de accion  27-33px de ANCHO en las tablas
         .bal-modo-btn .... 37px de ancho

     El del menu es el que importa: son once renglones que el Duenio
     toca todo el tiempo, y `.sidebar .sb-item` (0,2,0) le gana al
     `min-height: 0` de `.sb-item` (0,1,0) sin tocar esa regla.

     Los de las tablas necesitan ANCHO, no alto: ya miden 44 de alto y
     27 de ancho, o sea que el dedo entra por arriba y se sale por el
     costado. */
  .sidebar .sb-item { min-height: 44px; }
  .act-editar-ing, .act-anular-ingreso, .act-anular-gasto,
  .bal-modo-btn, .pago-btn { min-width: 44px; min-height: 44px; }
  /* `button.cli-editar` y no `.cli-editar` a secas: su regla en owner.css
     tiene la MISMA especificidad que esta, y **con especificidad pareja
     gana el archivo que carga ultimo** -- que es owner.css. Sumar la
     etiqueta la levanta a (0,1,1) y alcanza.
     Su regla ya dice "se toca con el dedo" y la puso en 32: la intencion
     estaba, el numero no. */
  button.cli-editar { min-width: 44px; min-height: 44px; }
  /* Los desplegables de filtro quedaron en 38: tienen su propia regla de
     alto y le ganan al piso. Son dos, y se filtran ventas con ellos. */
  #vfCajero, #vfMedio { min-height: 44px; }
}

/* ── COMPONENTES QUE DIBUJA UN JS COMPARTIDO ──────────────────────────
 *
 *  Estos cuatro los escriben `pos-cajero.js`, `formato.js` y `chat.js`,
 *  que los cargan las CUATRO paginas. Sus reglas vivian en catalog.css y
 *  owner.css, que el Cajero NO carga -- asi que en su pantalla salian
 *  sin estilo.
 *
 *  MEDIDO, "Mis ventas de hoy" del Cajero contra la misma lista del
 *  Dueño (pos-cajero.js:1479-1520):
 *
 *      .product-row    block, sin relleno   ->  flex, 12/14, radio 11px
 *      .stock-pill     16px, sin fondo      ->  11.5px, pildora redonda
 *      .empty-state    16px, blanco pleno   ->  13.6px, tenue
 *      .auth-row       inline, 16px         ->  flex, 13.6px, tenue
 *
 *  La regla que deja esto cerrado, y vale para lo que se agregue:
 *
 *      si el marcado lo escribe un JS que cargan todas las paginas,
 *      su estilo va en app.css, que tambien lo cargan todas.
 *
 *  Las VARIANTES se quedan donde estaban a proposito -- las usa una sola
 *  pantalla y tienen mas especificidad, asi que siguen ganando aunque
 *  app.css cargue primero: `.product-card .stock-pill` (catalog),
 *  `.product-row.fila-deshecha` y `.auth-row.auth-sin-pantalla` (owner).
 *
 *  Y `.empty-state` estaba definida DOS VECES con valores distintos
 *  --catalog 40px/.88rem, owner 16px/.85rem-- y ganaba la de owner por
 *  orden de carga. Quedo una sola: la que se venia viendo.
 */
.product-row {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 12px 14px;
}

.product-row img,
.product-row .no-photo {
  width: 42px;
  height: 42px;
  border-radius: var(--r-sm);
  object-fit: cover;
  flex-shrink: 0;
  background: var(--bg-hover);
}
.product-row .no-photo {
  display: flex;
  align-items: center;
  justify-content: center;
  /* La inicial es lo ÚNICO que identifica al producto cuando no tiene
     foto, así que no es un detalle tenue. Con --text-muted daba 4,43,
     apenas por debajo de la vara. */
  color: var(--text-sub);
  font-size: .7rem;
  /* Adentro no va una inicial: va la palabra que distingue, y puede ser
     larga. "Distribuidora" se salía 13px de este cuadrado de 42. Que se
     parta antes de salirse, y si aun así no entra, que se recorte --
     nunca que se monte sobre lo de al lado. */
  overflow: hidden;
  overflow-wrap: anywhere;
  text-align: center;
  line-height: 1.1;
}

.product-row .info { flex: 1; min-width: 0; }
/* La RED: pase lo que pase, un nombre no se sale de su caja. `min-width:0`
   en `.info` evita que la FILA desborde, pero deja que el TEXTO se salga
   -- son dos cosas distintas, y por eso el scroll de la página daba 0
   mientras "Mantenimiento" se iba 76px por el costado. Una palabra sola
   no se parte sin esto. Es la última línea, no la solución: para que casi
   nunca haga falta, los botones se apilan (más abajo). */
.product-row .name { font-weight: 500; overflow-wrap: anywhere; }
.product-row .price { color: var(--text-sub); font-size: .85rem; }

@media (max-width: 520px) {
  .product-row { flex-wrap: wrap; row-gap: 8px; }
  .product-row .info { min-width: 130px; }
}

.stock-pill {
  font-size: .72rem;
  letter-spacing: .05em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: var(--r-full);
  white-space: nowrap;
}
.stock-pill.ok { background: rgba(34,197,94,.12); color: var(--green-text); }
.stock-pill.low { background: rgba(245,158,11,.14); color: var(--warning-text); }
.stock-pill.critical { background: rgba(196,75,75,.16); color: var(--error-text); font-weight: 700; }

.empty-state { color: var(--text-muted); font-size: .85rem; padding: 16px 0; text-align: center; }

.auth-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: .85rem;
  color: var(--text-sub);
  padding: 7px 0;
  cursor: pointer;
}

