@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@500;600;700&display=swap');@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@500;600;700&family=Space+Grotesk:wght@400;500;700&family=Fredoka:wght@500;600;700&display=swap');



/* =====================================================================
   Estilos globales base de Juyendo
   ===================================================================== */
/* Quicksand: solo para el texto de los mensajes de Juyi Mensaje — formal
   pero con letras más redondas, para que el chat se sienta un poco
   distinto del resto del panel (ver DirectChat.jsx / AnnouncementsPanel.jsx). */
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  font-family: 'Poppins', system-ui, sans-serif;
  /* Corrección 29 jul (reportado por Dionisio: en modo oscuro el panel
     se veía "como una tarjeta plana encima de algo blanco"): antes acá
     había un color fijo (#F0F0F3, el fondo claro de siempre) que nunca
     cambiaba. En pantallas anchas, donde el contenido no llena todo el
     ancho de la ventana, ese fondo fijo quedaba a la vista alrededor del
     panel oscuro. Ahora usa la variable de tema (--bg), que las
     pantallas con modo oscuro sincronizan también en <body> — ver
     useSyncBodyDarkMode en hooks/useThemePreference.js. */
  background: var(--bg);
  /* Antes no había color acá: cualquier texto sin color explícito caía
     al negro por defecto del navegador. Se veía bien en los temas claros
     (casi coincide con --text-primary) pero quedaba negro-sobre-oscuro
     en Dark Pro / Modo noche — por eso aparecían títulos y textos
     "invisibles" en Horarios, Productos, Turnos, etc. Con esto, todo
     texto que no fija su propio color hereda el color del tema activo. */
  color: var(--text-primary);
}
button {
  font-family: inherit;
}
/* =====================================================================
   Pedido 22 jul (madrugada #3): que la PWA se sienta como app nativa,
   no como una página web — sin pellizco-zoom, sin rebote de pantalla
   (overscroll elástico) ni selección de texto por accidente al tocar y
   arrastrar. Los inputs/textarea son la excepción explícita (donde SÍ
   hace falta seleccionar texto) y llevan font-size 16px para que iOS no
   haga zoom automático al enfocarlos.
   Nota: esta regla es la red de seguridad global — cualquier input que
   ya fija su propio font-size en línea (style={{fontSize:'15px'}}, hay
   varios en la app) sigue ganando sobre esta regla porque un estilo en
   línea siempre le gana a una hoja de estilos externa; homogeneizar
   TODOS esos tamaños en línea a 16px es un trabajo aparte, más grande,
   que no se hizo esta noche.
   ===================================================================== */
/* Corrección 2 ago (reportado por Dionisio: "Dark Pro"/"Editorial" se ven
   como una tarjeta flotante que no se adapta al navegador — en realidad
   afectaba a CUALQUIER pantalla con contenido más alto que la ventana,
   no solo a esos dos temas). La causa: `height: 100%` (fijo, no
   `min-height`) clava <html> y <body> exactamente al alto de la ventana.
   Cuando el contenido de una página pública (reservar turno, tienda) es
   más alto que eso, ese exceso queda atrapado: no se puede llegar con la
   rueda del mouse, el teclado ni window.scrollTo — el botón "Reservar
   turno" puede quedar directamente inalcanzable en ventanas más bajas.
   `min-height` en cambio dice "al menos el alto de la ventana" — las
   pantallas cortas siguen llenando el fondo de punta a punta como antes,
   pero las pantallas con más contenido crecen y el navegador las scrollea
   de forma normal, como cualquier página web. El panel interno (dashboard)
   ya usa `minHeight: '100vh'` en sus propios contenedores (ver App.jsx),
   así que no dependía de este `height: 100%` para nada — este cambio no
   le afecta. */
html, body {
  touch-action: pan-x pan-y;
  width: 100%;
  min-height: 100%;
  -webkit-user-select: none;
  user-select: none;
}
input, textarea {
  -webkit-user-select: auto;
  user-select: auto;
  font-size: 16px;
}
/* =====================================================================
   Pedido 22 jul (madrugada #6): que la campanita de "Silenciar este
   chat" (ChatSettingsScreen.jsx) se anime al prender o apagar, como si
   repicara, en vez de cambiar de ícono en seco.
   ===================================================================== */
@keyframes bell-ring {
  0%, 100% { transform: rotate(0deg); }
  15% { transform: rotate(-16deg); }
  30% { transform: rotate(13deg); }
  45% { transform: rotate(-9deg); }
  60% { transform: rotate(6deg); }
  75% { transform: rotate(-3deg); }
}
.bell-ring-anim {
  display: inline-flex;
  transform-origin: 50% 0%;
  animation: bell-ring 0.5s ease-in-out;
}
/* =====================================================================
   pageLoader.css — ver PageLoader.jsx
   ===================================================================== */

.page-loader {
  min-height: 100vh;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: var(--bg);
  font-family: var(--font-main);
  animation: page-loader-fade-in 0.25s ease-out;
}

@keyframes page-loader-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.page-loader-mark {
  position: relative;
  width: 74px;
  height: 74px;
  display: grid;
  place-items: center;
  animation: page-loader-breathe 1.9s cubic-bezier(0.45, 0, 0.55, 1) infinite;
  filter: drop-shadow(0 8px 16px color-mix(in srgb, var(--shadow-dark) 42%, transparent));
}

.page-loader-light {
  position: absolute;
  inset: 6px;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 55%, white), transparent);
  -webkit-mask: url('/assets/logo-mark-D2GBSNBT.svg') center / contain no-repeat;
  mask: url('/assets/logo-mark-D2GBSNBT.svg') center / contain no-repeat;
  opacity: 0;
  transform: translateX(-48%);
  animation: page-loader-light-pass 1.9s cubic-bezier(0.16, 1, 0.3, 1) infinite;
  pointer-events: none;
}

@keyframes page-loader-light-pass {
  0%, 20% { transform: translateX(-48%); opacity: 0; }
  42% { opacity: .5; }
  70%, 100% { transform: translateX(48%); opacity: 0; }
}

@keyframes page-loader-breathe {
  0%, 100% { transform: scale(0.94); }
  48% { transform: scale(1); }
  58% { transform: scaleX(1.008) scaleY(0.996); }
}

.page-loader-wordmark {
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--text-secondary);
  opacity: 0.42;
}

.page-loader-label {
  margin: 2px 0 0;
  font-size: 12.5px;
  color: var(--text-secondary);
}

@media (prefers-reduced-motion: reduce) {
  .page-loader-mark {
    animation: none;
  }
  .page-loader-light { animation: none; display: none; }
}
/* =====================================================================
   JUYENDO — Tokens de diseño (neumorfismo blanco)
   =====================================================================
   Qué es esto: las variables de color, sombra y tipografía que se
   repiten en toda la app. Si el día de mañana querés cambiar el color
   de acento (la marca), lo cambiás UNA VEZ acá y se actualiza en todos
   lados. No hace falta tocar cada pantalla.
   ===================================================================== */

:root {
  /* --- Fondo y superficies --- */
  --bg: #F0F0F3;              /* fondo general de la app */
  --surface: #F0F0F3;         /* superficie de cards/botones (mismo tono que el fondo, así "flotan") */

  /* --- Sombras neumórficas (el efecto de relieve suave) --- */
  --shadow-light: rgba(255, 255, 255, 0.95); /* luz superior/izquierda */
  --shadow-dark: rgba(163, 177, 198, 0.85);  /* profundidad inferior/derecha */
  /* Segundo refuerzo (mismo día, con captura real a pantalla completa de
     Dionisio: "las píldoras en blanco se pierden no se separan del
     fondo" -- el primer refuerzo de este mismo bloque, más arriba en el
     historial de este comentario, subió shadow-dark de 0.58 a 0.68 y
     agrandó un poco offset/blur, pero probado a resolución real de
     escritorio (1920px, no el viewport chico de pruebas) seguía
     leyéndose casi plano: una sola sombra "difusa" grande, sin blur
     chico pegado al borde, se disuelve visualmente en superficies
     anchas -- el ojo necesita un límite nítido ADEMÁS de la extrusión
     suave, no solo la extrusión. Se reconstruye --shadow-raised/
     --shadow-raised-compact como DOS sombras por lado (una "de
     contacto", chica y más opaca, pegada al borde; una "ambiente",
     grande y difusa, la extrusión) en vez de una sola -- exactamente
     el patrón de las referencias de neumorfismo real que mandó
     (buscador/timer/lock screen): tienen borde legible Y relieve
     suave, no uno u otro. */
  --shadow-raised:
    2px 2px 4px rgba(163, 177, 198, 0.5),
    11px 13px 26px var(--shadow-dark),
    -2px -2px 4px rgba(255, 255, 255, 0.9),
    -11px -13px 26px var(--shadow-light),
    0 20px 40px rgba(163, 177, 198, 0.24);
  --shadow-raised-compact:
    2px 2px 3px rgba(163, 177, 198, 0.45),
    7px 9px 18px var(--shadow-dark),
    -2px -2px 3px rgba(255, 255, 255, 0.88),
    -7px -9px 18px var(--shadow-light),
    0 12px 24px rgba(163, 177, 198, 0.2);
  --shadow-inset-soft:
    inset 6px 7px 14px rgba(163, 177, 198, 0.62),
    inset -6px -6px 13px rgba(255, 255, 255, 0.88);
  /* Borde de contacto de 1px -- ver .neu-raised/.neu-raised-compact más
     abajo. Sin esto, en pantallas grandes el límite de la píldora sigue
     dependiendo 100% del blur (que a simple vista, sin acercarse, se
     lee borroso/perdido); un hairline sutil (mismo tono que la sombra
     oscura, muy baja opacidad) marca el borde exacto sin verse como un
     borde "duro" de UI genérica. */
  --border-raised-hairline: color-mix(in srgb, var(--shadow-dark) 22%, transparent);
  /* Corrección (auditoría lenguaje visual UI, sept 2026): estas 4
     sombras vivían repetidas como literales rgba(0,0,0,X) sueltos en
     varios archivos (paneles modales, badges, popovers, el lightbox de
     ServicePicker) -- mismo valor, escrito a mano cada vez. Se
     centralizan acá para que un cambio futuro de intensidad se haga en
     un solo lugar. Los valores NO cambian respecto de lo que ya estaba
     (cero diferencia visual), solo se les pone nombre. Deliberadamente
     NO se tocó LogoMark.jsx (su sombra floating sobre foto de portada
     es una excepción ya documentada ahí mismo, no es este mismo
     patrón) ni los archivos de tema (appThemes.css/bookingThemes.css/
     plansTheme.css), que definen sus propios valores por diseño. */
  --shadow-modal: 0 24px 64px rgba(0, 0, 0, 0.4);
  --shadow-lightbox: 0 24px 60px rgba(0, 0, 0, 0.5);
  --shadow-popover: 0 6px 20px rgba(0, 0, 0, 0.25);
  --shadow-badge-hard: 2px 2px 5px rgba(0, 0, 0, 0.25);

  /* --- Color de marca / acento ---
     Actualizado para coincidir con el degradé verde del logo de Juyendo. */
  --accent: #0CB876;
  --accent-hover: #0AA267;
  --accent-soft: #D6F5E6;     /* versión suave del acento, para fondos de estados activos */

  /* --- Color del sello de Juyendo (el rayo, LogoMark.jsx) ---
     Reportado por Dionisio (3 ago): en las páginas públicas de reserva
     (PublicBooking.jsx/StorePage.jsx) el rayo siempre se veía verde sin
     importar el tema elegido (Editorial/Dark Pro). La causa: esas
     páginas también pisan --accent con brandColorVars() (el color de
     marca que cada negocio elige/extrae de su propio logo), como estilo
     INLINE en el div contenedor — y un estilo inline le gana SIEMPRE
     a la regla de clase .theme-editorial/.theme-dark,
     así que --accent terminaba siendo el color del negocio,
     no el del tema. Se separa el sello de Juyendo de esa variable: usa
     su propia variable acá (verde por default, igual que siempre — así
     el panel interno Owner/EmployeeDashboard, que no tiene tema
     seleccionable, no cambia en nada), y cada tema de reserva pública la
     pisa con su propio acento (ver bookingThemes.css). */
  --jy-mark-color: var(--accent);

  /* --- Burbujas de Juyi Mensaje (chat) ---
     Rediseño "material líquido" (Fase 2 — ver DirectChat.jsx): la
     especificación pide colores casi planos y MUY desaturados —
     "verde menta muy desaturado" para lo propio, "blanco porcelana"
     para lo del otro lado. Nada de verde de marca saturado acá adentro:
     ese verde vibrante ya quedó reservado para acentos puntuales (botón
     enviar, estados activos), no para el cuerpo de la burbuja. Se
     definen como variables (no colores fijos en el componente) para
     poder pisarlas en modo oscuro sin que quede un verde/blanco clarito
     ilegible sobre fondo negro. */
  --bubble-mine-start: #E2F0E8;
  --bubble-mine-end: #D7E9DE;
  /* Antes #FFFFFF/#FBFAF7 — blanco puro real (no solo "sensación" de
     blanco), demasiado estridente contra el resto de la paleta gris
     cálida. "Porcelana" pide un blanco roto/marfil suave, no el blanco
     de un modal quirúrgico. */
  --bubble-other-start: #F7F6F2;
  --bubble-other-end: #EFEDE6;

  /* --- Texto ---
     Art 3.4/4.1 de la Constitución Técnica: todo par texto/fondo tiene
     que cumplir un contraste mínimo de 4.5:1. El gris anterior de
     --text-secondary (#7A7A7E) daba ≈3.77:1 sobre --bg — insuficiente
     para subtítulos, hints y encabezados de tabla que lo usan en toda
     la app. Este valor da ≈5.8:1. */
  --text-primary: #3A3A3C;    /* gris oscuro, no negro puro */
  --text-secondary: #5C5C63;
  --text-on-accent: #FFFFFF;

  /* --- Estados --- */
  --success: #6FBF8B;
  --danger: #E56B6B;
  --warning: #E0B15C;

  /* --- Tipografía --- */
  --font-main: 'Poppins', 'Nunito', system-ui, sans-serif;

  /* --- Radios y espaciado --- */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-pill: 999px;
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 16px;
  --space-lg: 24px;
  --space-xl: 32px;

  /* Capas compartidas: evita que un menú, modal o asistente dependa del
     stacking context de una card/página. */
  --jy-layer-popover: 300;
  --jy-layer-modal: 500;
  --jy-layer-tour: 900;
}

/* Fast-Booking, rediseño Slot-First (22 ago): en móvil sigue siendo una
   hoja táctil que sube desde abajo (así el teclado no deja un formulario
   recortado en el centro); desde tablet+ (>=768px, umbral pedido por
   Dionisio) pasa a Side Drawer anclado a la derecha, para no tapar la
   agenda de fondo mientras se carga el turno. */
@media (max-width: 767px) {
  .fast-booking-overlay {
    align-items: flex-end !important;
    padding: max(8px, env(safe-area-inset-top)) 0 0 !important;
  }
  .fast-booking-sheet {
    width: 100% !important;
    max-height: calc(100dvh - max(8px, env(safe-area-inset-top))) !important;
    height: calc(100dvh - max(8px, env(safe-area-inset-top))) !important;
    padding-bottom: max(12px, env(safe-area-inset-bottom)) !important;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0 !important;
    animation: fast-booking-slide-up 250ms cubic-bezier(0, 0, 0.2, 1);
  }
  .fast-booking-sheet input,
  .fast-booking-sheet select {
    font-size: 16px !important;
  }
}

@media (min-width: 768px) {
  .fast-booking-overlay {
    justify-content: flex-end !important;
    padding: 0 !important;
  }
  /* Cierre visual (23 ago, spec §1 de Dionisio: "el panel debe tener
     ancho suficiente para que el contenido respire, no debe parecer una
     versión móvil comprimida") — 420px quedaba justo con los chips de
     servicio + el punto de color + "Servicios seleccionados" con
     EmployeePicker adentro; 460px da ese respiro sin volverse un panel
     gigante que compita con la agenda de fondo. */
  .fast-booking-sheet {
    width: min(460px, 100%) !important;
    height: 100dvh !important;
    max-height: 100dvh !important;
    border-radius: var(--radius-lg) 0 0 var(--radius-lg) !important;
    animation: fast-booking-slide-in 250ms cubic-bezier(0, 0, 0.2, 1);
  }
}

/* Corrección 23 ago (Dionisio, tras ver capturas reales en el celular):
   "Cargar turno" debe quedar a la derecha también en mobile — la decisión
   anterior de dejarlo a la izquierda por alcance táctil se revierte por
   pedido explícito. Sin media query: una sola regla para todos los
   anchos en vez de repetir flex-end por breakpoint. */
.fast-booking-launcher-row {
  justify-content: flex-end !important;
}

@keyframes fast-booking-slide-up {
  from { transform: translateY(16px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

@keyframes fast-booking-slide-in {
  from { transform: translateX(24px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .fast-booking-sheet {
    animation: none !important;
  }
}

/* La tira de fechas conserva espacio para las sombras de cada cápsula y
   oculta solo su scrollbar propio. No hay recorte rectangular ni scroll
   horizontal global en el modal. */
.fast-date-strip::-webkit-scrollbar {
  display: none;
}

/* Corrección 24 ago (Dionisio: "esa barra que se interpone" en el
   formulario de Cargar turno, cerca del botón de calendario) — la barra
   de scroll nativa de 6px quedaba pegada justo ahí. Mismo criterio que
   .fast-date-strip arriba: se oculta la barra nativa (el scroll táctil
   sigue funcionando igual), nada de scrollbar visible sobre la interfaz
   neumórfica. */
.fast-booking-body::-webkit-scrollbar {
  display: none;
}

/* El cuerpo del modal es la única superficie que debe desplazarse. Sin este
   límite cada hijo flex podía encogerse al seleccionar servicios y terminar
   recortando las fechas u horarios en lugar de habilitar el scroll interno. */
.fast-booking-body > * {
  flex-shrink: 0;
}

/* =====================================================================
   Clases utilitarias neumórficas reutilizables
   ===================================================================== */

/* Corrección 4 ago (Dionisio, con fotos: "sombras blancas duras" en
   botones de Cargar turno y otros lugares — mismo bug ya arreglado antes
   puntualmente en TimeSlotPicker.jsx, pero de forma local a ese
   componente). Causa raíz real: NINGUNA de estas 3 clases compartidas
   apagaba el "appearance" nativo del navegador. Cuando se usan sobre un
   <button> de verdad (no un <div>), algunos navegadores/OS (Safari/iOS
   sobre todo) dibujan el bisel/relieve NATIVO del botón del sistema por
   encima o alrededor del box-shadow neumórfico propio — dos sistemas de
   "relieve" superpuestos, uno de ellos blanco/genérico y sin relación
   con el tema. Se corrige acá, en la base compartida, para cubrir TODOS
   los botones .neu-raised/.neu-inset/.neu-button-accent de una sola vez
   en vez de ir parchando cada pantalla por separado. No se toca
   `outline` a propósito (Art 4.2 P0: el foco por teclado tiene que
   seguir viéndose en todos lados) — `appearance: none` no le saca el
   anillo de foco nativo, solo el relieve/fondo por default. */
.neu-raised,
.neu-inset,
.neu-button-accent {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
}

/* Superficie "elevada" (como un botón o card que sobresale) */
.neu-raised {
  background: var(--surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-raised);
  border: 1px solid var(--border-raised-hairline);
  box-sizing: border-box;
}

/* Variante "compacta" de .neu-raised -- misma superficie elevada pero
   con la sombra de --shadow-raised-compact en vez de --shadow-raised.
   Pensada para píldoras/botones chicos dentro de una fila angosta (ej.
   Tabs.jsx: la pestaña ACTIVA ya usaba --shadow-raised-compact vía
   .neu-button-accent, pero las INACTIVAS tenían .neu-raised, la sombra
   grande pensada para cards -- en una fila de 8-12px de padding esa
   sombra se recortaba en seco contra el borde del contenedor con
   overflow-x, y el resultado se leía "plano" (reportado por Dionisio
   con capturas comparando contra referencias de neumorfismo real: "las
   píldoras no se ven bien definidas"). Usar esta clase en vez de
   .neu-raised para cualquier píldora chica que viva dentro de una fila
   angosta/scrolleable. */
.neu-raised-compact {
  background: var(--surface);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-raised-compact);
  border: 1px solid var(--border-raised-hairline);
  box-sizing: border-box;
}

/* Superficie "hundida" (como un input de texto) */
.neu-inset {
  background: var(--surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-inset-soft);
  border: none;
}

/* Art 4.2 (P0): sin este anillo, alguien que navega por teclado no ve
   NUNCA qué campo tiene el foco en Login/Registro/SetPassword ni en
   casi ningún formulario de negocio, porque los inputs usan
   `outline: none` apoyados en .neu-inset sin reemplazo visual. Cubre
   tanto el caso de un <input class="neu-inset"> directo como el de un
   contenedor .neu-inset con un input adentro (:focus-within alcanza
   ambos). */
.neu-inset:focus-within {
  box-shadow:
    inset 4px 4px 8px var(--shadow-dark),
    inset -4px -4px 8px var(--shadow-light),
    0 0 0 3px var(--accent-soft);
}

/* Botón principal con color de marca */
.neu-button-accent {
  background: var(--accent);
  color: var(--text-on-accent);
  border-radius: var(--radius-md);
  border: none;
  box-shadow: var(--shadow-raised-compact);
  transition: transform 0.1s ease, box-shadow 0.1s ease;
  cursor: pointer;
  font-family: var(--font-main);
  font-weight: 600;
}

.neu-button-accent:hover {
  background: var(--accent-hover);
}

.neu-button-accent:active {
  box-shadow:
    inset 3px 3px 6px rgba(0,0,0,0.15),
    inset -3px -3px 6px rgba(255,255,255,0.1);
  transform: scale(0.98);
}

/* Auditoría de coherencia visual: ningún botón deshabilitado tenía un
   estado visual propio en todo el sistema — dependía del estilo nativo
   del navegador, que con colores custom en línea normalmente no cambia
   nada (el botón se ve exactamente igual habilitado que deshabilitado).
   La skill de coherencia exige que se aplane casi del todo (sombras al
   30%, contenido al 40% de opacidad). Se cubre acá, una sola vez, para
   las 4 clases compartidas — no hace falta tocar cada pantalla. */
.neu-button-accent:disabled,
.neu-raised:disabled,
.neu-inset:disabled {
  opacity: 0.55;
  box-shadow:
    2px 2px 6px var(--shadow-dark),
    -2px -2px 6px var(--shadow-light);
  cursor: not-allowed;
  transform: none !important;
}
/* Cierre visual (23 ago, spec §17 de Dionisio: "Incompleto: visualmente
   DISABLED, no debe parecer simplemente un verde ligeramente más apagado
   difícil de distinguir del habilitado") — el CTA principal (Agendar
   turno y cualquier otro .neu-button-accent) además de la opacidad
   compartida de arriba, se desatura del todo. Sigue siendo el mismo
   verde de marca cuando se habilita (nada cambia ahí), pero deshabilitado
   ya no se puede confundir con un verde "atenuado pero igual de válido". */
.neu-button-accent:disabled {
  filter: grayscale(70%);
}
.neu-button-accent:disabled:hover {
  background: var(--accent);
}
.neu-press:disabled {
  cursor: not-allowed;
}
.neu-press:disabled:active {
  transform: none;
}

/* =====================================================================
   Responsividad — celulares y pantallas chicas
   ===================================================================== */

/* Evita que las tablas se compriman hasta ser ilegibles en el celular:
   mantienen un ancho mínimo legible y el contenedor scrollea de costado
   en vez de romper el diseño de la página. */
.table-scroll {
  width: 100%;
  max-width: 100%;
  min-width: 0;
  overflow-x: auto;
  padding-bottom: var(--space-xs);
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}

/* Corrección 2 ago (L6, pedido de Dionisio): "la cápsula de navegación
   debe ser fija; lo que se debe desplazar son las opciones dentro de
   ella, y adaptarse al tamaño del dispositivo (hoy se corta en móvil)".
   La fila de pestañas (Turnos/Servicios/Productos/...) ya scrolleaba de
   costado en pantallas angostas (overflow-x:auto), pero no había NINGUNA
   pista visual de que hubiera más opciones fuera de vista — en un
   celular con 6-7 pestañas se veía como si la cápsula estuviera
   literalmente "cortada" en el borde. La barra nativa se oculta y el
   gesto táctil de desplazamiento se conserva. */
.pill-tabs-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  /* Corrección (23 ago, ronda 3): overflow-x:auto sin el otro eje
     explícito hace que el navegador compute overflow-y como 'auto'
     también (no 'visible') — es decir, este wrapper recorta cualquier
     cosa que sobresalga tanto arriba/abajo como a los costados. Sin
     padding, la sombra hacia afuera nueva de la cápsula interna (ver
     appThemes.css, corrección "capsula mal definida") quedaba cortada en
     seco contra el borde superior/inferior de ESTE contenedor. 4px de
     aire alcanza para esa sombra sin agrandar visiblemente la fila.
  */
  /* Aire real alrededor de la pieza interna. El margen negativo conserva
     su alineación original, mientras el padding evita que overflow recorte
     la luz izquierda o la sombra derecha. */
  padding: 10px 9px 12px;
  margin: -10px -9px 0;
  /* Corrección 30 ago: la máscara de desvanecimiento usada como pista de
     scroll también borraba visualmente el borde y el radio derechos de la
     cápsula completa, incluso en escritorio sin contenido oculto. La tira
     conserva el desplazamiento horizontal táctil, pero sus dos extremos se
     pintan completos y con la misma definición. */
  -webkit-mask-image: none;
  mask-image: none;
}
.pill-tabs-scroll::-webkit-scrollbar {
  display: none;
}
.pill-tabs-scroll .neu-inset::-webkit-scrollbar {
  display: none;
}

/* La navegación principal es una ranura, pero también una pieza física
   completa. La sombra interior define el canal y la exterior conserva el
   contorno en ambos extremos, especialmente sobre el tema claro. */
.pill-tabs-scroll > .neu-inset {
  background: var(--surface) !important;
  box-shadow:
    var(--shadow-inset-soft),
    var(--shadow-raised-compact) !important;
  border-radius: var(--radius-md) !important;
}
@media (max-width: 480px) {
  .pill-tabs-scroll .neu-inset {
    padding: 5px !important;
    gap: 2px !important;
  }
  .pill-tabs-scroll .neu-inset button {
    min-height: 36px !important;
    padding: 6px 10px !important;
    font-size: 13px !important;
  }
}

@media (max-width: 480px) {
  /* Regla real del producto: la cápsula del dueño SIEMPRE es una sola
     fila — los íconos (D/E, campana, mensajes, ajustes) nunca cambian
     de tamaño ni se van a una segunda línea. Si el nombre del negocio
     es demasiado largo para el espacio que queda, se recorta con
     puntos suspensivos en vez de romper el layout o chocar con los
     íconos. Antes esto envolvía a 2 líneas, lo que deformaba la
     cápsula; ahora se comporta igual que antes de esa regresión. */
  .header-capsule-row {
    flex-wrap: nowrap !important;
    gap: 6px !important;
    padding: 10px 12px !important;
  }
  .owner-header-capsule .header-capsule-brand-col {
    flex: 1 1 auto !important;
    min-width: 0 !important;
  }
  .owner-header-capsule .header-capsule-actions-col {
    flex: 0 0 auto !important;
    margin-left: auto !important;
  }
  .owner-header-capsule .owner-header-business-name {
    display: flex !important;
    flex: 1 1 auto !important;
    min-width: 0 !important;
    max-width: 100%;
  }
  /* Corrección (Dionisio, sept 2026): "no quiero que el nombre del local
     quede ... directamente" -- se saca el text-overflow:ellipsis de acá
     (versión anterior). La decisión de qué mostrar (nombre completo,
     primera palabra, o nada/solo logo) ya no se resuelve acá con CSS --
     se mide en JS con el ancho real disponible (ver ownerHeaderNameMode
     en OwnerDashboard.jsx, con canvas.measureText). Este overflow:hidden
     sin ellipsis queda solo como red de seguridad silenciosa (recorte
     limpio, sin puntos suspensivos) para el caso extremo de que la
     medición todavía no haya corrido (primer frame).*/
  .owner-header-capsule .owner-header-business-name p {
    overflow: hidden !important;
    white-space: nowrap !important;
    margin: 0;
  }
}

/* Identidad compartida Owner/Employee: una sola elevación, radio y
   tratamiento para el logo real o su inicial de respaldo. */
.app-identity-header {
  box-shadow: var(--shadow-raised) !important;
}
.business-logo-housing {
  width: 44px;
  height: 44px;
  flex: 0 0 44px;
  padding: 4px;
  border-radius: 14px;
  background: var(--surface);
  box-shadow:
    5px 6px 15px color-mix(in srgb, var(--shadow-dark) 54%, transparent),
    -5px -5px 14px color-mix(in srgb, var(--shadow-light) 88%, transparent);
  display: grid;
  place-items: center;
  overflow: hidden;
}
.business-logo-housing > * {
  grid-area: 1 / 1;
}

/* Housing opcional para las fotos de perfil principales. La imagen o la
   inicial no reemplazan la protuberancia: ambas viven dentro de la misma
   superficie 1:1 elevada que el logo del negocio. */
.profile-avatar-housing {
  display: inline-grid;
  place-items: center;
  padding: 4px;
  border-radius: 14px;
  background: var(--surface);
  box-shadow:
    5px 6px 15px color-mix(in srgb, var(--shadow-dark) 54%, transparent),
    -5px -5px 14px color-mix(in srgb, var(--shadow-light) 88%, transparent);
  flex-shrink: 0;
}

/* Cards administrativas de Servicios/Productos. En escritorio conservan
   foto 1:1 + información lateral; en móvil reducen el cuadrado de forma
   controlada sin deformarlo ni forzar desplazamiento horizontal. */
.catalog-card {
  min-width: 0;
  border: 1px solid color-mix(in srgb, var(--text-primary) 7%, transparent) !important;
}
.catalog-card-image,
.catalog-card-image img {
  aspect-ratio: 1 / 1 !important;
}
.catalog-card-image {
  align-self: center !important;
  height: auto !important;
  flex: 0 0 auto !important;
}
.catalog-card-body,
.catalog-card-top {
  min-width: 0;
}
.catalog-card-actions {
  justify-content: flex-start;
}

@media (min-width: 900px) {
  .catalog-card-list {
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: stretch;
    gap: 14px !important;
  }
}

@media (max-width: 640px) {
  .catalog-card {
    display: grid !important;
    grid-template-columns: 24px 104px minmax(0, 1fr);
    align-items: center !important;
    gap: 8px !important;
    padding: 12px !important;
  }
  .catalog-card > button:first-child {
    width: 24px !important;
    min-height: 132px !important;
  }
  .catalog-card-image-column {
    width: 104px;
    min-width: 0;
  }
  .catalog-card-image {
    width: 104px !important;
    min-width: 104px !important;
  }
  .catalog-card-top {
    flex-wrap: wrap;
  }
}

@media (max-width: 430px) {
  .catalog-card {
    grid-template-columns: 22px 92px minmax(0, 1fr);
    gap: 7px !important;
    padding: 10px !important;
  }
  .catalog-card > button:first-child {
    width: 22px !important;
    min-height: 116px !important;
  }
  .catalog-card-image-column,
  .catalog-card-image {
    width: 92px !important;
    min-width: 92px !important;
  }
  .catalog-card-actions > button {
    width: 100%;
  }
  .catalog-card-top {
    flex-direction: column;
    align-items: flex-start !important;
    gap: 4px !important;
  }
  .catalog-card-top > label,
  .catalog-card-top > div:last-child:not(:first-child) {
    align-self: flex-start;
  }
}
.business-logo-housing-image,
.business-logo-housing-placeholder {
  width: 100%;
  height: 100%;
  border-radius: 10px;
}
.business-logo-housing-image {
  display: block;
  object-fit: contain;
  opacity: 0;
  transition: opacity var(--motion-fast, 160ms) ease;
}
.business-logo-housing-image.is-loaded {
  opacity: 1;
}
.business-logo-housing-placeholder {
  display: grid;
  place-items: center;
  color: var(--accent);
  background: var(--surface);
  font-size: 17px;
  font-weight: 800;
}
.employee-business-banner .business-logo-housing {
  width: 36px;
  height: 36px;
  flex-basis: 36px;
  padding: 3px;
  border-radius: 12px;
}
.employee-business-banner .business-logo-housing-image,
.employee-business-banner .business-logo-housing-placeholder {
  border-radius: 9px;
}

/* Panel de empleado: en teléfonos actuales la fila principal puede usar
   mejor el ancho disponible sin reducir los tres controles táctiles. El
   fallback de dos filas queda reservado para el umbral excepcionalmente
   estrecho de arriba o para el acceso rápido Dueño/Empleado. */
@media (min-width: 360px) and (max-width: 480px) {
  .employee-header-capsule {
    padding: 10px 12px !important;
    gap: 4px !important;
  }
  .employee-header-row,
  .employee-header-identity {
    gap: 6px !important;
  }
  .employee-header-row {
    /* La cápsula ya aporta el padding exterior: el padding global de la
       fila duplicaba ese aire, restaba ancho al saludo y elevaba el header. */
    padding: 0 !important;
  }
  .employee-header-logo {
    display: flex;
    flex: 0 0 26px;
    width: 26px;
    height: 26px;
  }
  .employee-header-logo svg {
    width: 26px !important;
    height: 26px !important;
  }
  .employee-header-avatar > .profile-avatar-housing {
    width: auto !important;
    height: auto !important;
    padding: 3px;
  }
  .employee-header-avatar .profile-avatar-housing > * {
    width: 36px !important;
    height: 36px !important;
  }
  .employee-header-actions {
    gap: 4px !important;
  }

  /* Las cuatro pestañas del empleado sí caben como una única barra: se
     distribuyen en cuatro columnas y se elimina el scroll innecesario. */
  .employee-pill-tabs {
    overflow: hidden;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .employee-pill-tabs .neu-inset {
    display: grid !important;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    overflow: visible !important;
    padding: 4px !important;
    gap: 2px !important;
    margin-bottom: 0 !important;
  }
  .employee-pill-tabs .neu-inset button {
    min-width: 0;
    min-height: 36px;
    justify-content: center;
    gap: 4px !important;
    padding: 7px 2px !important;
    font-size: 12px !important;
  }

  /* Resumen de semana: flechas en extremos fijos y título realmente
     centrado. La grilla mantiene la composición aunque cambie la semana. */
  .employee-week-summary {
    padding: 12px !important;
  }
  .employee-week-header {
    display: grid !important;
    grid-template-columns: 36px minmax(0, 1fr) 36px !important;
    gap: 6px !important;
  }
  .employee-week-title {
    min-width: 0;
    font-size: 15px !important;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }
  .employee-week-nav-btn {
    width: 36px !important;
    height: 36px !important;
  }
}


/* Backlog #129: la tabla de Auditoría (Reportes) se sentía plana —
   filas de texto parejo, sin ninguna guía visual ni respuesta al
   pasar el mouse. Zebra muy sutil (para que el ojo siga la fila) +
   resaltado al hover, ambos con color-mix así funcionan igual en
   cualquiera de los 6 temas sin colores nuevos. */
.audit-row {
  transition: background 0.15s ease;
}
.audit-row:nth-child(even) {
  background: color-mix(in srgb, var(--shadow-dark) 16%, transparent);
}
.audit-row:hover {
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  overflow-x: hidden;
  max-width: 100vw;
  min-height: 100%;
}

body {
  min-height: 100dvh;
}

/* Safari/iOS amplía la página al enfocar inputs menores de 16px. No se
   bloquea el zoom global: solo se asegura el tamaño mínimo legible de
   controles editables en pantallas táctiles. */
@media (max-width: 767px) {
  input:not([type='checkbox']):not([type='radio']):not([type='range']),
  select,
  textarea {
    font-size: 16px !important;
  }
}

@media (max-width: 640px) {
  .neu-page {
    padding: var(--space-md) !important;
    padding-bottom: 100px !important;
  }
}

/* El botón de ayuda Juyi es "fixed" en la esquina inferior derecha y
   queda siempre por encima del contenido, incluso tapando botones
   reales (ej: "Editar"/"Recordar" de la última fila de una tabla) si el
   usuario scrollea hasta el final. Reservamos un margen vacío abajo de
   toda página para que siempre se pueda scrollear un poco más allá del
   último contenido real y dejarlo libre de la mascota. */
.neu-page {
  padding-bottom: 96px !important;
}

/* Página pública de reservas (tema Clásico/neumórfico): en pantallas
   grandes la tarjeta de reserva es angosta a propósito (mejor legibilidad),
   pero antes quedaba flotando sola en un fondo gris vacío y se sentía
   "chiquita"/perdida. Ahora crece un poco en pantallas medianas/grandes y
   el fondo tiene un degradé suave con el color de marca detrás, así ocupa
   la pantalla en vez de verse como una caja perdida en el medio. */
.public-booking-page {
  background-image:
    radial-gradient(480px 320px at 12% 8%, var(--accent-soft), transparent 60%),
    radial-gradient(520px 380px at 92% 95%, var(--accent-soft), transparent 55%);
  background-repeat: no-repeat;
}
.booking-card {
  width: 100%;
}
@media (min-width: 640px) {
  .public-booking-page {
    padding: var(--space-xl) var(--space-lg) !important;
  }
  .booking-card {
    max-width: 440px !important;
  }
}
@media (min-width: 1024px) {
  .booking-card {
    max-width: 500px !important;
  }
}
@media (min-width: 1800px) {
  .booking-card {
    max-width: 560px !important;
    padding: var(--space-xl) !important;
  }
}

/* El panel del dueño/a usa navegación lateral en pantallas grandes; en
   celular/tablet angosto se apila arriba (la navegación queda como una
   fila horizontal que scrollea, no una columna fija que come pantalla). */
@media (max-width: 900px) {
  .owner-dashboard-layout {
    flex-direction: column !important;
    /* El estilo inline de escritorio pone align-items:flex-start (para
       que la sidebar no se estire de alto). En columna eso pasa a ser
       el eje horizontal: sin "stretch" cada hijo (la sidebar, el
       contenido principal) se angosta al ancho de su contenido más
       ancho — y si adentro hay una tabla con min-width grande, TODA la
       página terminaba estirándose de costado. Forzamos stretch acá. */
    align-items: stretch !important;
    width: 100% !important;
    max-width: 100vw !important;
  }
  .owner-sidebar-col {
    width: 100% !important;
    max-width: 100% !important;
    position: static !important;
  }
  .owner-sidebar-col .app-sidebar {
    flex-direction: row !important;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  /* Los ítems de navegación tenían width:100% (pensado para la columna
     de escritorio) — en la fila horizontal de celular eso hacía que
     CADA botón ("Turnos", "Servicios", etc.) ocupara el ancho entero de
     la pantalla, mostrando uno solo por vez sin ninguna pista de que
     había más al costado. Ahora cada uno mide solo lo que necesita su
     texto, y entran varios a la vista con el resto sugerido al borde. */
  .owner-sidebar-col .app-sidebar-item {
    flex-shrink: 0;
    width: auto !important;
    white-space: nowrap;
  }
  .owner-sidebar-col .app-sidebar-item:hover {
    transform: none;
  }
  .owner-dashboard-layout main {
    min-width: 0;
    max-width: 100%;
  }
}

/* =====================================================================
   Micro-interacciones — pulido visual (sin cambiar la identidad blanca)
   ===================================================================== */

/* Cualquier superficie "elevada" clickeable: se hunde levemente al
   presionar, para que se sienta física/táctil. Sumar esta clase junto
   a .neu-raised o .neu-button-accent en botones interactivos. */
.neu-press {
  /* Mismo fix de appearance nativo que .neu-raised/.neu-inset/
     .neu-button-accent/.neu-chip (ver comentario largo junto a esas
     clases) — .neu-press se usa solo/a en MUCHOS botones con su propio
     estilo en línea (no siempre combinada con .neu-raised), así que
     necesita el mismo reset acá para cubrir esos casos también. */
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.15s ease;
  cursor: pointer;
}
.neu-press:active {
  transform: scale(0.96);
}

/* Los botones neutros cambian físicamente de elevado a hundido. El
   contraste queda más corto que en una card para que se reconozcan como
   controles sin volver a formar líneas blancas rígidas. */
.neu-raised.neu-press:not(:disabled):hover {
  box-shadow:
    7px 9px 17px var(--shadow-dark),
    -7px -7px 16px var(--shadow-light),
    0 10px 20px rgba(163, 177, 198, 0.18);
}
.neu-raised.neu-press:not(:disabled):active {
  box-shadow: var(--shadow-inset-soft);
}

/* Mismo hover/active de arriba, pero para .neu-raised-compact (píldoras
   chicas -- ver comentario junto a esa clase). Reach más chico, acorde
   a --shadow-raised-compact. */
.neu-raised-compact.neu-press:not(:disabled):hover {
  box-shadow:
    6px 8px 14px var(--shadow-dark),
    -6px -6px 13px var(--shadow-light),
    0 8px 16px rgba(163, 177, 198, 0.19);
}
.neu-raised-compact.neu-press:not(:disabled):active {
  box-shadow: var(--shadow-inset-soft);
}

/* Cards elevadas: sombra un poco más marcada al pasar el mouse, da
   sensación de profundidad sin alterar la paleta. */
.neu-raised {
  transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.neu-card-hover:hover {
  box-shadow:
    8px 8px 16px var(--shadow-dark),
    -8px -8px 16px var(--shadow-light);
  transform: translateY(-2px);
}

/* Transición viva al cambiar de pestaña/sección: entra con un pequeño
   "pop" (sube y crece levemente) en vez de aparecer de golpe. */
@keyframes juyendo-fade-in {
  from { opacity: 0; transform: translateY(10px) scale(0.98); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.fade-in {
  animation: juyendo-fade-in 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Salida simétrica de .fade-in (Dionisio, 4 sept 2026: el aviso "Venta
   cargada" se quedaba pegado en pantalla para siempre). Mismo timing y
   curva que la entrada, solo invertida -- para que un mensaje que
   aparece con .fade-in pueda retirarse solo con el mismo lenguaje de
   movimiento en vez de desaparecer de golpe. `forwards` para que quede
   en su estado final (opacity:0) mientras React todavía no desmontó el
   nodo (ver EmployeeDashboard.jsx: el timeout que limpia el mensaje
   corre un toque después de que termina esta animación). */
@keyframes juyendo-fade-out {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to { opacity: 0; transform: translateY(-6px) scale(0.98); }
}
.fade-out {
  animation: juyendo-fade-out 0.28s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

/* Las cards con .neu-card-hover ya suben al pasar el mouse (ver arriba);
   acá además suman una entrada escalonada cuando aparecen en pantalla
   por primera vez (ej: al cambiar de sección), para que la app se
   sienta con más movimiento en vez de estática. */
@keyframes juyendo-pop-in {
  from { opacity: 0; transform: translateY(14px) scale(0.96); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.pop-in {
  animation: juyendo-pop-in 0.4s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.pop-in:nth-child(1) { animation-delay: 0s; }
.pop-in:nth-child(2) { animation-delay: 0.05s; }
.pop-in:nth-child(3) { animation-delay: 0.1s; }

/* Cápsula de nombre del negocio (24 ago, pedido de Dionisio: "termina lo
   de las cápsulas") — el contenedor entra suave (sin desplazarse mucho,
   solo 4px, sin escala/rebote: acá no aplica el mismo lenguaje que
   .pop-in porque es texto de identidad, no una tarjeta de contenido) y
   el texto de adentro (saludo temporal → nombre del negocio) cruza con
   un fade en vez de cambiar de golpe. Mientras los datos todavía no
   llegaron, se ve un placeholder pulsando despacio en el mismo lugar
   donde después va el texto real, para que el bloque nunca "salte" de
   tamaño. */
@keyframes juyendo-capsule-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}
.capsule-in {
  animation: juyendo-capsule-in 0.28s ease-out both;
}
@keyframes juyendo-capsule-text-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
.capsule-text-in {
  animation: juyendo-capsule-text-in 0.35s ease-out both;
}
@keyframes juyendo-capsule-skeleton {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 0.7; }
}
.capsule-skeleton {
  display: inline-block;
  width: 130px;
  height: 12px;
  border-radius: var(--radius-sm);
  background: var(--text-secondary);
  animation: juyendo-capsule-skeleton 1.4s ease-in-out infinite;
}

/* Tarjeta de confirmación del Fast-Booking (23 ago, ronda 6 — Dionisio:
   "animación de entrada calma, no alarmante, 180-220ms"). A diferencia de
   .fade-in/.pop-in de arriba (que suben Y ESCALAN con un leve rebote —
   pensadas para tarjetas de contenido apareciendo en una lista), esta
   tarjeta reemplaza el resumen corrido justo cuando se elige la hora, así
   que un rebote/pop la haría sentir como una alerta. Solo opacidad +
   un desplazamiento vertical chico, sin scale, sin overshoot.
   duración total 200ms — a mitad de camino en el rango pedido. */
@keyframes juyendo-confirm-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}
.confirm-card-in {
  animation: juyendo-confirm-in 0.2s ease-out both;
}

/* Spinner chico para "sincronizando..." (OfflineBanner y similares). */
@keyframes juyendo-spin {
  to { transform: rotate(360deg); }
}

/* =======================================================================
   Juyi Mensaje — Fase 3 ("material líquido"): morphing de llegada de
   mensajes. La especificación pide una secuencia punto → gota → burbuja
   → texto → hora → estado (220-280ms, ease-out) — nada "aparece", todo
   "se transforma". Se logra con una sola animación de transform/
   border-radius/opacity (nunca width/height, para que ande bien en
   Android gama media) más dos animaciones hijas con delay creciente
   para el texto y para la fila de hora/estado, así el contenido
   "termina de aparecer" después de que nace el recipiente.
   Nunca queda un loop infinito: cada animación corre una vez y queda
   quieta (fill-mode both). ======================================== */
@keyframes juyendo-msg-morph {
  /* Punto */
  0% { opacity: 0; transform: scale(0.2); border-radius: 50%; }
  /* Gota */
  35% { opacity: 1; transform: scale(0.62); border-radius: 50%; }
  /* Burbuja: el border-radius no se declara acá a propósito — el
     navegador interpola desde el 35% (50%, redondo) hasta la forma
     "blob" real que ya trae el mensaje por su estilo en línea, así el
     redondo se derrite hacia la silueta orgánica en vez de saltar. */
  100% { opacity: 1; transform: scale(1); }
}
.msg-enter {
  animation: juyendo-msg-morph 0.26s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes juyendo-msg-text-enter {
  from { opacity: 0; filter: blur(2px); }
  to { opacity: 1; filter: blur(0); }
}
.msg-enter .msg-text {
  animation: juyendo-msg-text-enter 0.18s ease-out 0.09s both;
}
/* Hora + estado (el "ojito") terminan de aparecer últimos en la
   secuencia, después del texto. */
@keyframes juyendo-msg-meta-enter {
  from { opacity: 0; transform: translateY(2px); }
  to { opacity: 1; transform: translateY(0); }
}
.msg-enter .msg-meta {
  animation: juyendo-msg-meta-enter 0.16s ease-out 0.16s both;
}
/* Una reacción nueva "emerge" de la burbuja en vez de aparecer ya
   puesta en su lugar. */
@keyframes juyendo-reaction-pop {
  from { opacity: 0; transform: scale(0.4); }
  to { opacity: 1; transform: scale(1); }
}
.reaction-pop {
  animation: juyendo-reaction-pop 0.18s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
/* Mantener presionado un mensaje: la superficie se comprime ~98%, sin
   rebote (ease-out liso, nunca un spring/bounce). */
.chat-bubble-pressed {
  transform: scale(0.98);
  transition: transform 0.12s ease-out;
}
/* Indicador de "escribiendo": NUNCA tres puntos — una burbuja chica y
   vacía, en el lugar donde iría el próximo mensaje del otro lado, que
   respira muy lento (escala + opacidad). Tiene que sentirse viva, nunca
   llamativa — por eso la oscilación es mínima y lenta. */
@keyframes juyendo-typing-breathe {
  0%, 100% { transform: scale(1); opacity: 0.75; }
  50% { transform: scale(1.035); opacity: 1; }
}
.chat-typing-bubble {
  animation: juyendo-typing-breathe 1.7s ease-in-out infinite;
}
/* Corrección 28 jul: Dionisio pidió explícitamente que las animaciones
   se vean siempre, aunque el dispositivo tenga "Reducir movimiento"
   activado (antes se apagaban solas por esta regla). Se desactiva a
   pedido suyo — si en el futuro se quiere volver a respetar la
   preferencia de accesibilidad, descomentar este bloque y el de más
   abajo (Art 3.9).
@media (prefers-reduced-motion: reduce) {
  .msg-enter,
  .msg-enter .msg-text,
  .msg-enter .msg-meta,
  .reaction-pop,
  .chat-typing-bubble {
    animation-duration: 0.01ms !important;
  }
  .chat-bubble-pressed {
    transition-duration: 0.01ms !important;
  }
}
*/
.pop-in:nth-child(4) { animation-delay: 0.15s; }
.pop-in:nth-child(5) { animation-delay: 0.2s; }

/* Que cambiar de tema o prender el modo noche se sienta como una
   transición, no un salto brusco de colores. Ojo: .neu-raised y
   .neu-button-accent ya tienen su propia transición de transform/
   box-shadow (arriba) — acá solo sumamos color/fondo, sin pisarla. */
.neu-page, .app-sidebar {
  transition: background-color 0.25s ease, color 0.25s ease;
}
.neu-raised, .neu-inset {
  transition: background-color 0.25s ease, border-color 0.25s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
.neu-button-accent {
  transition: background-color 0.2s ease, box-shadow 0.15s ease, transform 0.1s ease;
}

/* Botones y cards clickeables "respiran" un poco al pasar el mouse,
   además del hundido al presionar que ya tenían. */
.neu-button-accent:hover {
  transform: translateY(-1px);
}
.neu-raised.neu-card-hover {
  cursor: pointer;
}

/* Ícono/ilustración de estados vacíos */
.empty-icon {
  color: var(--accent);
  opacity: 0.55;
  animation: juyendo-pop-in 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

/* =====================================================================
   Checkbox e interruptor propios — reemplazan el checkbox nativo del
   navegador (esa tilde azul genérica que no pertenece a ningún tema de
   Juyendo) por dos controles con el mismo lenguaje neumórfico del resto
   de la app. Al usar las variables de tema (--accent, --surface,
   --shadow-*), se adaptan solos a los 4 temas y a modo noche sin
   necesitar reglas por tema.
   ===================================================================== */

/* Checkbox chico, para tildar una opción suelta en un formulario
   (ej: "precio variable", "turno fuera de horario"). */
.neu-checkbox {
  appearance: none;
  -webkit-appearance: none;
  width: 20px;
  height: 20px;
  min-width: 20px;
  border-radius: 6px;
  background: var(--surface);
  box-shadow:
    inset 3px 3px 6px var(--shadow-dark),
    inset -3px -3px 6px var(--shadow-light);
  position: relative;
  cursor: pointer;
  margin: 0;
  flex-shrink: 0;
  transition: background-color 0.15s ease, box-shadow 0.15s ease;
}
.neu-checkbox:checked {
  background: var(--accent);
  box-shadow: none;
}
.neu-checkbox:checked::after {
  content: '';
  position: absolute;
  left: 6px;
  top: 2px;
  width: 6px;
  height: 11px;
  border: solid var(--text-on-accent, #fff);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.neu-checkbox:focus-visible {
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Interruptor tipo pastilla, para ajustes on/off de Configuración
   (ej: "Modo noche", "Usar el color del logo como acento"). */
.neu-switch {
  appearance: none;
  -webkit-appearance: none;
  width: 42px;
  height: 24px;
  border-radius: 999px;
  background: var(--surface);
  box-shadow:
    inset 3px 3px 6px var(--shadow-dark),
    inset -3px -3px 6px var(--shadow-light);
  position: relative;
  cursor: pointer;
  margin: 0;
  flex-shrink: 0;
  transition: background-color 0.2s ease, box-shadow 0.2s ease;
}
.neu-switch::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #FFFFFF;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  transition: transform 0.2s ease;
}
.neu-switch:checked {
  background: var(--accent);
  box-shadow: none;
}
.neu-switch:checked::after {
  transform: translateX(18px);
}
.neu-switch:focus-visible {
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* =====================================================================
   Select nativo neumórfico — reemplaza la flecha/chrome nativos del
   navegador (que se dibujan por encima de .neu-inset y hacen que el
   control se vea "plano"/genérico pese a tener la sombra hundida) por
   una flecha propia, del mismo lenguaje visual que el resto de los
   inputs. Requiere envolver el <select> en .neu-select-wrap para poder
   dibujar la flecha con ::after (un <select> no admite pseudo-elementos
   propios en ningún navegador).
   Reportado por Dionisio (4 ago): el selector "Servicio/Producto" de
   "Cargar venta" se veía plano comparado con el resto del panel.
   ===================================================================== */
.neu-select-wrap {
  position: relative;
  display: flex;
}
.neu-select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  cursor: pointer;
  width: 100%;
  padding-right: 34px !important;
}
.neu-select-wrap::after {
  content: '';
  position: absolute;
  right: 14px;
  top: 50%;
  width: 11px;
  height: 11px;
  transform: translateY(-50%);
  pointer-events: none;
  background-color: var(--text-secondary);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* =====================================================================
   Chip de selección — grupo de opciones exclusivas dentro de un mismo
   control (ej. medio de pago en "Cargar venta": Efectivo/Tarjeta/
   Transferencia). Nueva variante de botón documentada en
   references/componentes.md de la skill de coherencia visual: reposo
   neumórfico elevado con un anillo fino del color de identidad de cada
   opción, seleccionado = neumórfico hundido con fondo sólido de ese
   color — mismo lenguaje de sombra doble que el resto de los botones,
   nunca el borde plano tipo Bootstrap que tenía antes.
   Cada instancia define su color de identidad con la variable inline
   --chip-color (ej. style={{ '--chip-color': pm.color }}).
   Reportado por Dionisio (4 ago): el texto de "Transferencia" se
   cortaba y se salía del botón — además de pasar a este sistema
   neumórfico, se le da ancho mínimo real (ver EmployeeDashboard.jsx)
   en vez de whiteSpace:nowrap forzado sobre un botón angosto.
   ===================================================================== */
.neu-chip {
  /* Mismo fix de appearance nativo que .neu-raised/.neu-inset/
     .neu-button-accent — ver comentario largo junto a esas clases. */
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  --chip-color: var(--accent);
  background: var(--surface);
  color: var(--chip-color);
  border: none;
  border-radius: var(--radius-sm);
  box-shadow:
    4px 4px 8px var(--shadow-dark),
    -4px -4px 8px var(--shadow-light),
    inset 0 0 0 1.5px color-mix(in srgb, var(--chip-color) 55%, transparent);
  font-family: var(--font-main);
  font-weight: 600;
  cursor: pointer;
  transition: box-shadow 0.15s ease, background 0.15s ease, color 0.15s ease, transform 0.1s ease;
}
.neu-chip:hover {
  box-shadow:
    5px 5px 10px var(--shadow-dark),
    -5px -5px 10px var(--shadow-light),
    inset 0 0 0 1.5px color-mix(in srgb, var(--chip-color) 55%, transparent);
}
.neu-chip:active {
  /* Corrección (Dionisio, sept 2026, con captura del panel "Cargar
     venta"): "me encanta como se unden los botones al presionarlos en
     la barra de servicios/empleados, quiero que las demás barras se
     comporten así" -- .neu-chip (Efectivo/Tarjeta/Transferencia/QR acá,
     reusado en otras pantallas) solo achicaba un poco (scale) al
     presionar, sin invertir la sombra como hacen los botones .neu-press
     (que pasan a --shadow-inset-soft mientras se mantienen apretados,
     el efecto real de "hundimiento" físico). Se agrega el mismo cambio
     de sombra acá para que CUALQUIER .neu-chip de la app (no solo estos
     4 botones) se sienta igual al presionarlo. */
  transform: scale(0.98);
  box-shadow: var(--shadow-inset-soft) !important;
}
.neu-chip.neu-chip-active:active {
  /* La variante ya seleccionada (fondo de color sólido) ya está hundida
     en reposo -- se profundiza un poco más en vez de aplicar el inset
     claro de arriba (que se ve mal sobre fondo de color). */
  box-shadow:
    inset 4px 4px 8px rgba(0, 0, 0, 0.24),
    inset -4px -4px 8px rgba(255, 255, 255, 0.14) !important;
}
.neu-chip.neu-chip-active {
  background: var(--chip-color);
  color: var(--text-on-accent, #FFFFFF);
  box-shadow:
    inset 3px 3px 6px rgba(0, 0, 0, 0.18),
    inset -3px -3px 6px rgba(255, 255, 255, 0.12);
}
.neu-chip:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  box-shadow: 2px 2px 6px var(--shadow-dark), -2px -2px 6px var(--shadow-light);
  transform: none !important;
}
.neu-chip:focus-visible {
  box-shadow:
    4px 4px 8px var(--shadow-dark),
    -4px -4px 8px var(--shadow-light),
    inset 0 0 0 1.5px color-mix(in srgb, var(--chip-color) 55%, transparent),
    0 0 0 3px var(--accent-soft);
}
.neu-chip.neu-chip-active:focus-visible {
  box-shadow:
    inset 3px 3px 6px rgba(0, 0, 0, 0.18),
    inset -3px -3px 6px rgba(255, 255, 255, 0.12),
    0 0 0 3px var(--accent-soft);
}

/* =====================================================================
   Impresión / exportar a PDF (usado por ReportsTab: botón "Descargar
   PDF" que llama a window.print()). Reglas generales, no específicas
   de una sola pantalla, para poder reusarse en cualquier otra pantalla
   que en el futuro quiera un botón de exportar en PDF.
   ===================================================================== */
/* QR del negocio: en escritorio el arte del QR calza exactamente con las
   dos tarjetas principales de destino (72 + 8 + 72 = 152px). En móvil se
   vuelve una columna natural, sin imponer alturas visuales. */
@media (max-width: 640px) {
  .business-qr-layout {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  .business-qr-preview {
    align-items: flex-start !important;
  }
  .business-qr-art {
    width: min(100%, 220px) !important;
    height: auto !important;
    aspect-ratio: 1;
  }
}

/* El SVG generado conserva su tamaño técnico para descargar e imprimir,
   pero dentro del preview debe escalar completo al marco disponible. */
.business-qr-art > div > svg,
.business-qr-art > div > canvas {
  display: block;
  width: 100% !important;
  height: 100% !important;
}

@media print {
  .owner-sidebar-col,
  .help-widget-fab,
  .help-widget-panel,
  .no-print {
    display: none !important;
  }
  .print-only {
    display: block !important;
  }
  .owner-dashboard-layout {
    display: block !important;
  }
  body, .neu-page {
    background: #FFFFFF !important;
  }
  .neu-raised, .neu-inset {
    box-shadow: none !important;
    border: 1px solid #ddd !important;
    break-inside: avoid;
  }
  table {
    font-size: 12px !important;
  }
}

/* =====================================================================
   Reducción de movimiento — Art 3.9 (P0), sin excepciones.
   La app tiene animaciones definidas en varios archivos (acá mismo,
   helpWidget.css, appThemes.css, bookingThemes.css, plansTheme.css) y
   antes cada uno tenía que acordarse de repetir su propio
   `@media (prefers-reduced-motion: reduce)` — una auditoría encontró
   que la mayoría no lo hacía. En vez de mantener esa lista dispersa,
   esta regla central "apaga" cualquier animación o transición CSS de
   TODA la app de una sola vez, sin importar en qué archivo se haya
   definido: comprimir la duración a casi cero dispara igual los
   estados finales (relevante para animaciones con fill-mode) sin que
   se perciba movimiento. tokens.css se importa de forma global, así
   que esta regla siempre gana.

   La preferencia del sistema prevalece para evitar movimiento no deseado. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

@media (max-width: 380px) {
  /* En el ancho mínimo el nombre completo del negocio ya aparece en la
     cápsula superior. Ocultamos la repetición del nombre del empleado en
     vez de mostrar una palabra partida como "D..." entre los controles. */
  .employee-header-name {
    display: none;
  }
}

@media (max-width: 640px) {
  .table-scroll:has(.employee-sales-table) {
    overflow: visible;
    padding: 2px;
  }
  .employee-sales-table,
  .employee-sales-table tbody,
  .employee-sales-table tr,
  .employee-sales-table td {
    display: block;
    width: 100%;
    min-width: 0 !important;
    box-sizing: border-box;
  }
  .employee-sales-table { min-width: 0 !important; border-collapse: separate !important; border-spacing: 0 12px; }
  .employee-sales-table thead { display: none; }
  .employee-sales-table tr {
    padding: 12px;
    border-radius: var(--radius-md);
    background: var(--surface);
    box-shadow: var(--shadow-raised);
  }
  .employee-sales-table td {
    display: grid;
    grid-template-columns: minmax(92px, 0.42fr) minmax(0, 1fr);
    gap: 10px;
    align-items: start;
    /* Corrección (Dionisio, sept 2026, con captura: "imagínate esto con
       20 servicios serían tremendas tarjetas") -- por default, un grid
       estira (justify-items:stretch) a cualquier hijo directo para
       llenar el ancho completo de su columna. La pastilla de
       ServicePill es inline-flex (se ajusta sola a su texto en
       cualquier otro lugar de la app), pero acá, como hijo directo de
       esta celda-grid, ese "stretch" le gana y la convierte en una
       barra sólida del ancho total de la tarjeta -- mientras más corto
       el nombre del servicio, peor se ve. Con justify-items:start la
       pastilla (y cualquier otro valor chico, como Categoría o Medio de
       pago) vuelve a ocupar solo lo que necesita.
    */
    justify-items: start;
    padding: 7px 2px !important;
    border: 0 !important;
    overflow-wrap: anywhere;
  }
  .employee-sales-table td::before {
    content: attr(data-label);
    color: var(--text-secondary);
    font-size: 11.5px;
    font-weight: 700;
  }
  .employee-sales-table input,
  .employee-sales-table select { width: 100% !important; max-width: none !important; min-width: 0; }
}



/* =====================================================================
   appThemes.css — el estilo elegido por cada salón (Maximalista /
   Editorial / Dark Pro) se aplica a TODA la app: tanto a la página
   pública de reservas como al panel del dueño/a y del empleado/a.
   =====================================================================
   Cómo funciona: cada tema redefine las mismas variables de color que
   ya usa toda la app (--bg, --accent, --text-primary, etc. — ver
   tokens.css) y además redibuja las clases compartidas .neu-raised /
   .neu-inset / .neu-button-accent con la identidad visual de ese tema
   (bordes gruesos y sombra sólida en Maximalista, líneas finas sin
   sombra en Editorial, fondo negro con acento ácido en Dark Pro). Como
   TODAS las pantallas del panel (Turnos, Servicios, Productos,
   Clientes, Comisiones, Horario, Reportes, panel del empleado/a) ya
   están construidas sobre esas mismas variables y clases, con solo
   agregar la clase del tema al contenedor raíz, el panel entero adopta
   el look del tema — no hace falta tocar cada pantalla una por una.
   El tema "neumorphic" (default) no necesita clase: es lo que ya
   define tokens.css.
   ===================================================================== */
/* Animaciones compartidas por los temas del panel — pensadas para ser
   livianas (solo transform/opacity/background-position, sin animar
   propiedades costosas como blur o box-shadow en bucle). */
@keyframes app-gradient-drift {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}
@keyframes app-underline-grow {
  from { width: 0; opacity: 0; }
  to { width: 36px; opacity: 1; }
}
/* =======================================================================
   1) MAXIMALISTA — estética de dashboard SaaS moderno: mucho blanco,
   tarjetas redondeadas con sombra suave (no bordes gruesos), acento
   violeta vivo. Pensado para verse ordenado con navegación lateral.
   ======================================================================= */
.app-theme-maximalist {
  --bg: #F5F4FB;
  --surface: #FFFFFF;
  --shadow-light: #FFFFFF;
  --shadow-dark: #E3E1F5;
  --accent: #6C5CE7;
  --accent-hover: #5A4BD1;
  --accent-soft: #EDEBFC;
  --text-primary: #211F3D;
  --text-secondary: #85829E;
  --text-on-accent: #FFFFFF;
  --success: #1F9D6B;
  --danger: #E0453C;
  --warning: #C97D0A;
  --font-main: 'Poppins', 'Inter', system-ui, sans-serif;
  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 22px;
}
/* El fondo lavanda plano gana un poco de vida con manchas de color muy
   suaves (los mismos tonos vivos del tema público) detrás del contenido,
   sin afectar la legibilidad de las tarjetas blancas de encima. */
.app-theme-maximalist .neu-page {
  background-image:
    radial-gradient(560px 380px at 8% -5%, rgba(255, 107, 91, 0.10), transparent 60%),
    radial-gradient(520px 360px at 100% 105%, rgba(255, 201, 60, 0.14), transparent 55%);
  background-repeat: no-repeat;
}
.app-theme-maximalist .neu-raised {
  background: var(--surface);
  border: 1px solid #ECEBFA;
  border-radius: var(--radius-md);
  box-shadow: 0 4px 18px rgba(108, 92, 231, 0.09);
}
.app-theme-maximalist .neu-card-hover:hover {
  box-shadow: 0 10px 28px rgba(108, 92, 231, 0.16);
  transform: translateY(-3px);
}
.app-theme-maximalist .neu-inset {
  background: #F7F6FD;
  border: 1px solid #ECEBFA;
  border-radius: var(--radius-sm);
  box-shadow: none;
}
.app-theme-maximalist .neu-button-accent {
  background: var(--accent);
  color: var(--text-on-accent);
  border: none;
  border-radius: var(--radius-sm);
  box-shadow: 0 6px 16px rgba(108, 92, 231, 0.35);
  font-weight: 600;
}
.app-theme-maximalist .neu-button-accent:hover {
  background: var(--accent-hover);
  box-shadow: 0 8px 20px rgba(108, 92, 231, 0.45);
}
.app-theme-maximalist .neu-button-accent:active {
  box-shadow: 0 2px 8px rgba(108, 92, 231, 0.35);
  transform: scale(0.98);
}
/* =======================================================================
   2) EDITORIAL — mucho blanco, serif fina, líneas en vez de sombras
   ======================================================================= */
.app-theme-editorial {
  --bg: #FAF9F6;
  /* Antes #FFFFFF — blanco puro real contra un fondo crema (#FAF9F6);
     el contraste se sentía "estridente" en uso real. Se baja a un
     blanco roto que sigue leyéndose "mucho blanco" (la identidad del
     tema) sin ser el blanco absoluto de una hoja de impresora. */
  --surface: #FEFDFA;
  --shadow-light: transparent;
  --shadow-dark: #E5E1D8;
  --accent: #C9A15A;
  --accent-hover: #B08A46;
  --accent-soft: #F3ECDC;
  --text-primary: #1C1C1C;
  --text-secondary: #8A8478;
  --text-on-accent: #FAF9F6;
  --success: #4C7A5A;
  --danger: #A8382E;
  --warning: #A87A2E;
  --font-main: 'Inter', system-ui, sans-serif;
  --radius-sm: 0px;
  --radius-md: 0px;
  --radius-lg: 0px;
}
/* Igual que en Maximalista, un fondo cálido muy sutil (dos manchas de
   dorado casi imperceptibles) para que el blanco/crema no se sienta
   tan chato — sin perder la limpieza tipográfica del tema. */
.app-theme-editorial .neu-page {
  background-image:
    radial-gradient(600px 420px at 12% 0%, rgba(201, 161, 90, 0.07), transparent 60%),
    radial-gradient(600px 420px at 90% 100%, rgba(201, 161, 90, 0.06), transparent 60%);
  background-repeat: no-repeat;
}
.app-theme-editorial .neu-raised {
  background: var(--surface);
  border: none;
  border-top: 1px solid var(--accent);
  border-bottom: 1px solid var(--accent);
  border-radius: 0;
  /* Corrección 28 jul (reportado en vivo: "en modo blanco se ve
     plano"): antes era box-shadow: none — la identidad editorial (líneas
     doradas arriba/abajo en vez de sombra doble) quedaba bien en teoría,
     pero en una pantalla real con varias tarjetas apiladas, sin NINGÚN
     levante se leía chato. Se agrega una sombra ambiente muy suave (no
     el neumorfismo de doble sombra de los otros temas — eso rompería la
     identidad "editorial, mucho blanco, sin relieve fuerte" — solo lo
     mínimo para que la tarjeta se separe del fondo). El hover ya tenía
     su propia sombra más marcada (ver .neu-card-hover:hover abajo), esto
     solo le da algo de presencia también en reposo. */
  box-shadow: 0 3px 14px rgba(28, 28, 28, 0.06);
  position: relative;
  transition: box-shadow 0.25s ease, transform 0.25s ease;
}
/* Una rayita dorada centrada abajo de cada tarjeta que "crece" al pasar
   el mouse — un guiño editorial (como una línea de separación de
   revista) que le da movimiento sin salir de la identidad del tema. */
.app-theme-editorial .neu-raised::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -1px;
  transform: translateX(-50%);
  width: 0;
  height: 2px;
  background: var(--accent);
  transition: width 0.35s ease;
}
.app-theme-editorial .neu-card-hover:hover {
  box-shadow: 0 14px 30px rgba(28, 28, 28, 0.08);
  transform: translateY(-2px);
}
.app-theme-editorial .neu-card-hover:hover::after {
  width: 64px;
}
.app-theme-editorial .neu-inset {
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--shadow-dark);
  border-radius: 0;
  box-shadow: none;
}
.app-theme-editorial .neu-inset:focus-within {
  border-bottom-color: var(--accent);
}
.app-theme-editorial .neu-button-accent {
  background: #1C1C1C;
  color: var(--text-on-accent);
  border: none;
  border-radius: 0;
  box-shadow: none;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.9em;
}
.app-theme-editorial .neu-button-accent:hover {
  background: var(--accent);
  color: #1C1C1C;
}
.app-theme-editorial h1, .app-theme-editorial h2, .app-theme-editorial h3 {
  font-family: 'Playfair Display', serif;
}
/* =======================================================================
   3) DARK PRO — fondo negro, acento ácido, tipografía técnica
   ======================================================================= */
.app-theme-dark {
  --bg: #0D0D0D;
  --surface: #161616;
  /* Corrección (22 ago, Dionisio: falta "volumen" vs. el tema claro) —
     0.04 de opacidad era casi imperceptible; 0.07 refuerza el brillo
     superior manteniendo la estética "chata" a propósito de Dark Pro
     (ver comentario más abajo), sin verse artificial ni competir con
     el acento ácido. */
  --shadow-light: rgba(255,255,255,0.07);
  --shadow-dark: rgba(0,0,0,0.7);
  --accent: #E8FF47;
  --accent-hover: #D4EB35;
  --accent-soft: rgba(232,255,71,0.14);
  --text-primary: #F5F5F5;
  --text-secondary: #9A9A9A;
  --text-on-accent: #0D0D0D;
  --success: #4ADE80;
  --danger: #FF6B5B;
  --warning: #FFB84D;
  --font-main: 'Space Grotesk', system-ui, sans-serif;
  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
}
/* Dark Pro era el tema más "chato" — fondo negro liso y tarjetas negras
   lisas. Le sumamos dos brillos de color muy tenues detrás (el acento
   ácido y un violeta frío), dando profundidad sin agregar animaciones
   pesadas. */
.app-theme-dark .neu-page {
  background-image:
    radial-gradient(520px 360px at 6% -5%, rgba(232, 255, 71, 0.06), transparent 60%),
    radial-gradient(560px 400px at 98% 105%, rgba(124, 77, 255, 0.10), transparent 55%);
  background-repeat: no-repeat;
}
.app-theme-dark .neu-raised {
  /* OJO: nada de backdrop-filter acá. .neu-raised se usa como
     contenedor de formularios que a veces tienen un popover propio
     (ej: el calendario de DateField) posicionado con position:absolute
     y z-index. backdrop-filter crea un nuevo "stacking context" en el
     elemento que lo tiene — eso encierra al popover adentro y lo tapa
     con la siguiente tarjeta .neu-raised del panel (que pinta encima al
     ser un hermano posterior en el DOM). Logramos el mismo look de
     vidrio solo con transparencia + borde, sin ese efecto colateral.
     Corrección 28 jul (reportado en vivo: "en modo negro se ve plano"):
     el único relieve que tenía antes era un highlight interior de 1px
     casi invisible — sin ninguna sombra HACIA AFUERA, una tarjeta se
     perdía contra el fondo casi del mismo negro. Se suma una sombra
     ambiente real (box-shadow normal, no inset — esto NO es
     backdrop-filter, no genera el mismo problema de stacking context
     con el popover) para que la tarjeta se despegue del fondo. */
  background: rgba(22, 22, 22, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--radius-md);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}
.app-theme-dark .neu-card-hover:hover {
  border-color: rgba(232, 255, 71, 0.5);
  box-shadow: 0 0 0 1px rgba(232, 255, 71, 0.18), 0 16px 34px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.app-theme-dark .neu-inset {
  /* Mismo motivo que arriba: sin backdrop-filter para no romper el
     popover del calendario cuando un input vive al lado. Corrección 28
     jul: sumamos un hundido sutil real (antes "none" — el campo se
     confundía con el fondo, sin sensación de hueco). */
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid #2A2A2A;
  border-radius: var(--radius-sm);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.5);
}
.app-theme-dark .neu-inset:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(232, 255, 71, 0.12);
}
.app-theme-dark .neu-button-accent {
  background: var(--accent);
  color: var(--text-on-accent);
  border: none;
  border-radius: var(--radius-sm);
  box-shadow: 0 0 0 rgba(232, 255, 71, 0);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  transition: opacity 0.15s ease, box-shadow 0.2s ease, transform 0.1s ease;
}
.app-theme-dark .neu-button-accent:hover {
  opacity: 0.9;
  box-shadow: 0 0 22px rgba(232, 255, 71, 0.35);
}
.app-theme-dark table thead th {
  border-color: #2A2A2A !important;
}
.app-theme-dark td {
  border-color: #2A2A2A !important;
}
/* =======================================================================
   4) CÁLIDO — terracota y mostaza, tipografía redondeada. Onda
   artesanal/hecho-a-mano, pensado para salones que quieren sentirse
   cercanos y cálidos en vez de "clínicos". A diferencia de Maximalista
   y Editorial (más planos), acá SÍ usamos neumorfismo clásico de doble
   sombra pero con tonos tierra, para que la calidez venga tanto del
   color como de la sensación táctil/suave de las superficies.
   ======================================================================= */
.app-theme-warm {
  --bg: #FBF3EC;
  --surface: #FFFDFA;
  --shadow-light: #FFFFFF;
  --shadow-dark: #E8D7C8;
  --accent: #C1622E;
  --accent-hover: #A84F22;
  --accent-soft: #F3E0D2;
  --text-primary: #3A2B21;
  --text-secondary: #8C7565;
  --text-on-accent: #FFFFFF;
  --success: #5B8C5A;
  --danger: #C1483A;
  --warning: #D98A2B;
  --font-main: 'Fredoka', 'Poppins', system-ui, sans-serif;
  --radius-sm: 14px;
  --radius-md: 20px;
  --radius-lg: 26px;
}
/* Dos manchas grandes y difusas (terracota y mostaza) detrás del
   contenido, mismo criterio que Maximalista/Editorial/Dark Pro, para
   que el fondo crema no se sienta chato. */
.app-theme-warm .neu-page {
  background-image:
    radial-gradient(560px 400px at 6% -5%, rgba(193, 98, 46, 0.10), transparent 60%),
    radial-gradient(520px 380px at 100% 105%, rgba(217, 138, 43, 0.12), transparent 55%);
  background-repeat: no-repeat;
}
.app-theme-warm .neu-raised {
  background: var(--surface);
  border: none;
  border-radius: var(--radius-md);
  box-shadow: 8px 8px 18px var(--shadow-dark), -8px -8px 18px var(--shadow-light);
}
.app-theme-warm .neu-card-hover:hover {
  box-shadow: 10px 10px 22px var(--shadow-dark), -10px -10px 22px var(--shadow-light);
  transform: translateY(-2px);
}
.app-theme-warm .neu-inset {
  background: var(--bg);
  border: none;
  border-radius: var(--radius-sm);
  box-shadow: inset 4px 4px 10px var(--shadow-dark), inset -4px -4px 10px var(--shadow-light);
}
.app-theme-warm .neu-button-accent {
  background: var(--accent);
  color: var(--text-on-accent);
  border: none;
  border-radius: var(--radius-sm);
  box-shadow: 4px 4px 12px rgba(193, 98, 46, 0.35);
  font-weight: 600;
}
.app-theme-warm .neu-button-accent:hover {
  background: var(--accent-hover);
  box-shadow: 5px 5px 14px rgba(193, 98, 46, 0.45);
}
.app-theme-warm .neu-button-accent:active {
  box-shadow: 2px 2px 6px rgba(193, 98, 46, 0.35);
  transform: scale(0.98);
}
.app-theme-warm .app-sidebar {
  background: linear-gradient(160deg, #E0956A 0%, #C1622E 60%, #A84F22 100%);
  box-shadow: 0 12px 28px rgba(193, 98, 46, 0.25);
}
.app-theme-warm .app-sidebar-item {
  color: rgba(255, 255, 255, 0.78);
}
.app-theme-warm .app-sidebar-item:hover {
  background: rgba(255, 255, 255, 0.14);
  color: #fff;
}
.app-theme-warm .app-sidebar-item.is-active {
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
}
/* =======================================================================
   5) URBANO — gris azulado y grafito, líneas prolijas. Sobrio y
   masculino, pensado para barberías que quieren un look más técnico y
   contenido que Dark Pro (que es negro puro) sin perder seriedad.
   ======================================================================= */
.app-theme-urban {
  --bg: #EDEFF2;
  --surface: #FFFFFF;
  --shadow-light: #FFFFFF;
  --shadow-dark: #D3D8DE;
  --accent: #3D5A73;
  --accent-hover: #2F4759;
  --accent-soft: #E3E8ED;
  --text-primary: #22272B;
  --text-secondary: #6B7480;
  --text-on-accent: #FFFFFF;
  --success: #3F8F6B;
  --danger: #C0392B;
  --warning: #B7791F;
  --font-main: 'Inter', system-ui, sans-serif;
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
}
.app-theme-urban .neu-page {
  background-image:
    radial-gradient(560px 380px at 8% -5%, rgba(61, 90, 115, 0.07), transparent 60%),
    radial-gradient(520px 360px at 100% 105%, rgba(44, 51, 57, 0.08), transparent 55%);
  background-repeat: no-repeat;
}
.app-theme-urban .neu-raised {
  background: var(--surface);
  border: 1px solid #DCE1E6;
  border-radius: var(--radius-md);
  box-shadow: 0 4px 14px rgba(34, 39, 43, 0.06);
}
.app-theme-urban .neu-card-hover:hover {
  box-shadow: 0 10px 24px rgba(34, 39, 43, 0.12);
  transform: translateY(-2px);
}
.app-theme-urban .neu-inset {
  background: #F5F7F9;
  border: 1px solid #DCE1E6;
  border-radius: var(--radius-sm);
  box-shadow: none;
}
.app-theme-urban .neu-button-accent {
  background: var(--accent);
  color: var(--text-on-accent);
  border: none;
  border-radius: var(--radius-sm);
  box-shadow: 0 4px 12px rgba(61, 90, 115, 0.3);
  font-weight: 600;
}
.app-theme-urban .neu-button-accent:hover {
  background: var(--accent-hover);
  box-shadow: 0 6px 16px rgba(61, 90, 115, 0.4);
}
.app-theme-urban .neu-button-accent:active {
  box-shadow: 0 2px 6px rgba(61, 90, 115, 0.3);
  transform: scale(0.98);
}
.app-theme-urban .app-sidebar {
  background: #22272B;
  box-shadow: 0 12px 28px rgba(34, 39, 43, 0.25);
}
.app-theme-urban .app-sidebar-item {
  color: rgba(255, 255, 255, 0.68);
}
.app-theme-urban .app-sidebar-item:hover {
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
}
.app-theme-urban .app-sidebar-item.is-active {
  background: var(--accent);
  color: #fff;
}
/* =======================================================================
   7) NAVEGACIÓN LATERAL — usada por el panel del dueño/a en vez de
   pestañas horizontales, para que se vea más ordenado con muchas
   secciones. Por defecto toma el look neumórfico; Maximalista/Cálido/
   Urbano/Dark Pro la pintan con su propia identidad (ver arriba).
   ======================================================================= */
.app-sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  background: var(--surface);
  border-radius: var(--radius-md);
  padding: var(--space-sm);
  box-shadow: 6px 6px 12px var(--shadow-dark), -6px -6px 12px var(--shadow-light);
  transition: background 0.25s ease, box-shadow 0.25s ease;
}
.app-sidebar-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  /* Art 2.2 Constitucion V4: objetivo táctil >= 44x44px en móvil. */
  min-height: 44px;
  box-sizing: border-box;
  border-radius: var(--radius-sm);
  border: none;
  background: transparent;
  color: var(--text-secondary);
  font-family: var(--font-main);
  font-size: 14px;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
  text-align: left;
  width: 100%;
}
.app-sidebar-item:hover {
  background: var(--accent-soft);
  color: var(--text-primary);
  transform: translateX(3px);
}
.app-sidebar-item.is-active {
  background: var(--accent);
  color: var(--text-on-accent);
  font-weight: 600;
}
.app-theme-maximalist .app-sidebar {
  background: linear-gradient(155deg, #8B6BFF 0%, #7565EF 35%, #6C5CE7 65%, #5A4BD1 100%);
  background-size: 180% 180%;
  animation: app-gradient-drift 14s ease-in-out infinite;
  box-shadow: 0 12px 32px rgba(108, 92, 231, 0.28);
  position: relative;
  overflow: hidden;
}
.app-theme-maximalist .app-sidebar::before,
.app-theme-maximalist .app-sidebar::after {
  content: '';
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  filter: blur(1px);
}
.app-theme-maximalist .app-sidebar::before {
  width: 160px;
  height: 160px;
  background: radial-gradient(circle, rgba(255, 201, 60, 0.30), transparent 70%);
  top: -50px;
  right: -50px;
}
.app-theme-maximalist .app-sidebar::after {
  width: 130px;
  height: 130px;
  background: radial-gradient(circle, rgba(0, 217, 192, 0.22), transparent 70%);
  bottom: -35px;
  left: -35px;
}
.app-theme-maximalist .app-sidebar > * {
  position: relative;
  z-index: 1;
}
.app-theme-maximalist .app-sidebar-item {
  color: rgba(255, 255, 255, 0.72);
}
.app-theme-maximalist .app-sidebar-item:hover {
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  transform: translateX(3px);
}
.app-theme-maximalist .app-sidebar-item.is-active {
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
  box-shadow: none;
}
.app-theme-dark .app-sidebar {
  background: rgba(15, 15, 15, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.07);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
}
/* El fondo translúcido de arriba deja pasar --accent-soft (ya corregido
   para mezclarse hacia negro en este tema — ver colorExtract.js), así
   que el hover del menú lateral queda con buen contraste incluso con
   Dark Pro solo (sin el toggle de Modo noche prendido). */
.app-theme-dark .app-sidebar-item:hover {
  box-shadow: inset 0 0 0 1px rgba(232, 255, 71, 0.22);
}
/* =======================================================================
   8) MODO NOCHE — oscurece cualquiera de los estilos de arriba (no es
   un estilo aparte). Se combina con la clase del tema elegido, ej:
   class="app-theme-maximalist app-dark-mode".
   ======================================================================= */
.app-dark-mode {
  --bg: #121214;
  --surface: #1C1C1F;
  /* Corrección 28 jul (reportado en vivo: "en modo noche los relieves
     casi no se notan, se ve plano"): shadow-light estaba en 0.04 — casi
     imperceptible incluso al lado de un shadow-dark bien marcado, así
     que el lado "claro" del relieve neumórfico (el que simula que la
     luz pega desde arriba-izquierda) prácticamente no aportaba nada.
     Subimos ambos un poco para que el hundido/relieve se note de
     verdad, sin llegar a verse "quemado" contra el fondo casi negro. */
  --shadow-light: rgba(255, 255, 255, 0.09);
  --shadow-dark: rgba(0, 0, 0, 0.5);
  --shadow-raised: 6px 6px 12px rgba(0, 0, 0, 0.42), -6px -6px 12px rgba(255, 255, 255, 0.09);
  --shadow-raised-compact: 4px 4px 10px rgba(0, 0, 0, 0.38), -4px -4px 10px rgba(255, 255, 255, 0.08);
  --shadow-inset-soft: inset 4px 4px 8px rgba(0, 0, 0, 0.4), inset -4px -4px 8px rgba(255, 255, 255, 0.07);
  --text-primary: #F2F2F3;
  --text-secondary: #9C9CA3;
  /* --accent-soft NO se sobreescribe en el resto de los temas: es un
     verde/violeta/beige clarito pensado para fondo blanco. Con texto
     claro (--text-primary de acá arriba) encima, quedaba texto claro
     sobre fondo claro — invisible (ej: el chip de estado "Confirmado"
     en Turnos, o el hover del menú lateral). Acá lo hacemos un realce
     sutil y oscuro para que el texto claro se siga leyendo. */
  --accent-soft: rgba(255, 255, 255, 0.10);
  /* Burbujas de chat en modo oscuro (Fase 2 "material líquido"): mismo
     criterio muy desaturado que en modo claro, adaptado a fondo oscuro
     — la propia es un verde-gris apagado (no el verde de marca a pleno,
     que quedaría demasiado protagonista), la del otro lado es un gris
     neutro apenas más claro que la superficie de base. */
  --bubble-mine-start: #2C3B34;
  --bubble-mine-end: #26342E;
  --bubble-other-start: #232326;
  --bubble-other-end: #1E1E21;
}
/* Corrección 29 jul (reportado por Dionisio con captura: la barra de
   pestañas — Ventas/Agenda/Reportes/Clientes — se ve "como una barra
   negra" sin relieve). Causa real: esta regla solo fijaba border-color,
   pero .neu-raised/.neu-inset tienen border: none (ancho 0) — un color
   de borde sin ancho no se dibuja nunca, así que en superficies grandes
   y anchas (como esta barra) el hundido/relieve casi no se nota contra
   un fondo ya oscuro de por sí, y queda leyéndose como una placa negra
   plana. Se agrega el ancho/estilo del borde para que el color sí se
   vea — un borde sutil de 1px le da el límite que le faltaba. */
.app-dark-mode .neu-raised,
.app-dark-mode .neu-inset {
  border-width: 1px !important;
  border-style: solid !important;
  border-color: rgba(255, 255, 255, 0.08) !important;
}
.app-dark-mode table thead th,
.app-dark-mode td {
  border-color: rgba(255, 255, 255, 0.08) !important;
}
/* Corrección (23 ago, ronda 3 — Dionisio, comparando dos capturas: "mira
   lo bien que se define la cápsula de arriba [el header, .neu-raised] y
   la que te marqué abajo [la tira de pestañas Turnos/Servicios/...,
   .neu-inset] mira lo feo que es mal definido"). El header se nota
   porque .neu-raised tiene sombra HACIA AFUERA (6px/12px de blur) además
   del fondo y el borde de 1px de arriba. La tira de pestañas usa
   .neu-inset (sombra solo hacia ADENTRO, por diseño — es una "ranura",
   no una tarjeta elevada), así que ahí afuera solo quedan el fondo
   (apenas 10 unidades de RGB más claro que el negro de fondo) y el
   borde de 8% de opacidad — casi invisible al lado de un header con
   sombra real. Se agrega una sombra suave hacia afuera SOLO a esta
   cápsula puntual (no a .neu-inset en general, que se usa en inputs y
   otros paneles en toda la app y no fue parte de lo señalado) para que
   se despegue del fondo negro sin perder su lenguaje de "hundido" por
   dentro. */
.app-dark-mode .pill-tabs-scroll .neu-inset {
  border-color: rgba(255, 255, 255, 0.09) !important;
  box-shadow:
    var(--shadow-inset-soft),
    0 5px 18px rgba(0, 0, 0, 0.22) !important;
}
.app-dark-mode .app-sidebar {
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
}
.app-dark-mode select,
.app-dark-mode input {
  color: var(--text-primary);
}
/* El selector de estado en Turnos ("Confirmado", etc.) usa fondo
   --accent-soft; en modo noche ese fondo ahora es oscuro, así que
   el texto (--text-primary, claro) vuelve a tener contraste.
   OJO: acá también forzamos un fondo claro explícito en el <option> —
   sin esto, el menú desplegable nativo del navegador quedaba con texto
   oscuro sobre fondo oscuro (heredado del <select>), invisible hasta
   pasar el mouse por encima (recién ahí el navegador pinta su color de
   resalte nativo y aparece el contraste). Con fondo explícito el
   contraste está garantizado siempre, no solo en hover. */
.app-dark-mode option {
  color: #1C1C1C;
  background: #F2F2F3;
}
/* Identidad de los tres paneles. Es un ajuste de componente, no de los
   tokens globales: misma luz continua y misma profundidad en Owner,
   Employee y Admin, también cuando el fondo claro tiene poco contraste.
   Corrección (auditoría lenguaje visual UI, sept 2026, con captura real
   de Dionisio: "la cápsula que tiene todo [foto+nombre+iconos] se
   mezcla con el fondo, no se demarca como cápsula única" -- mientras
   que las píldoras de navegación, arregladas en la misma ronda, ya se
   veían "excelente"). Causa encontrada: este bloque duplicaba a mano
   los valores de sombra en vez de usar var(--shadow-raised) -- cuando
   se reforzó ese token en tokens.css (sombra de contacto + sombra
   ambiente + hairline de borde, para que el relieve se note incluso a
   resolución de escritorio), este !important de acá siguió sirviendo
   los valores VIEJOS y más débiles, sin enterarse del refuerzo. Se
   simplifica a apuntar directo al token -- así cualquier ajuste futuro
   de intensidad se hace en un solo lugar y no puede volver a
   desincronizarse como pasó acá. */
.app-identity-header {
  box-shadow: var(--shadow-raised) !important;
  border: 1px solid var(--border-raised-hairline) !important;
  box-sizing: border-box;
}
/* Cápsula de navegación compartida por Dueño, Empleado y Administrativo.
   El cierre interior tonal delimita los cuatro lados; las dos sombras
   exteriores mantienen la iluminación neumórfica sin formar un borde duro. */
.panel-navigation-capsule.panel-navigation-capsule > .neu-inset {
  background: var(--surface) !important;
  box-shadow:
    var(--shadow-inset-soft),
    7px 8px 17px color-mix(in srgb, var(--shadow-dark) 82%, transparent),
    -7px -7px 17px color-mix(in srgb, var(--shadow-light) 90%, transparent),
    inset 1px 1px 2px color-mix(in srgb, var(--shadow-light) 62%, transparent),
    inset -1px -1px 2px color-mix(in srgb, var(--shadow-dark) 22%, transparent),
    inset 0 0 0 1px color-mix(in srgb, var(--text-primary) 7%, transparent) !important;
}
/* Este cierre extra de modo claro quedó redundante con el refuerzo de
   arriba (--shadow-raised ya trae su propia sombra de contacto +
   hairline, pensada específicamente para que no se pierda contra el
   lienzo claro) -- y al ser MÁS específico que la regla de arriba,
   ganaba la pulseada con sus valores viejos y más débiles. Se saca en
   vez de mantener dos implementaciones del mismo arreglo que pueden
   volver a desincronizarse. Fondo con el mismo matiz sutil se
   mantiene, eso sí seguía sumando algo real. */
.neu-page:not(.app-dark-mode) .app-identity-header {
  background: color-mix(in srgb, var(--surface) 96%, #fff 4%) !important;
}
.neu-page:not(.app-dark-mode) .panel-navigation-capsule.panel-navigation-capsule > .neu-inset {
  background: color-mix(in srgb, var(--surface) 96%, #fff 4%) !important;
  box-shadow:
    var(--shadow-inset-soft),
    8px 10px 20px color-mix(in srgb, var(--shadow-dark) 80%, transparent),
    -7px -7px 18px color-mix(in srgb, var(--shadow-light) 94%, transparent),
    0 0 14px color-mix(in srgb, var(--shadow-dark) 18%, transparent),
    0 0 0 1px color-mix(in srgb, var(--shadow-dark) 28%, transparent),
    inset 1px 1px 2px color-mix(in srgb, #fff 72%, transparent),
    inset -1px -1px 2px color-mix(in srgb, var(--shadow-dark) 24%, transparent) !important;
}
.neu-page:not(.app-dark-mode) .employee-business-banner {
  background: color-mix(in srgb, var(--surface) 96%, #fff 4%) !important;
}
/* La franja fina con el nombre del negocio usa una escala perceptiva
   menor: el mismo lenguaje físico que el header, sin parecer situada
   por delante de él por tener la sombra completa de una card grande.
   Corrección (auditoría lenguaje visual UI, sept 2026, captura real:
   "la cápsula que tiene todo se mezcla con el fondo") -- mismo bug que
   .app-identity-header más arriba: valores duplicados a mano que
   quedaron desactualizados respecto del refuerzo de --shadow-raised-
   compact en tokens.css. Se apunta directo al token (con su propia
   sombra de contacto + hairline ya integrados) en vez de mantener una
   copia paralela más débil. */
.employee-business-banner {
  /* Bajado un escalón más (Dionisio, sept 2026: "se ve más hacia el
     frente que el resto... la sombra que le hace a la de abajo hace que
     no se vea tan definida") -- --shadow-raised-compact seguía siendo
     demasiado marcada para esta franja tan angosta (6px de padding
     vertical) justo pegada arriba de la cápsula de identidad, y su
     sombra ambiente se montaba sobre el borde superior de esa cápsula.
     Se reduce a la mitad el offset/blur para que quede al mismo nivel
     visual que el resto, sin invadir a la de abajo. */
  box-shadow:
    1px 1px 2px rgba(163, 177, 198, 0.4),
    4px 5px 10px var(--shadow-dark),
    -1px -1px 2px rgba(255, 255, 255, 0.85),
    -4px -5px 10px var(--shadow-light) !important;
  border: 1px solid var(--border-raised-hairline) !important;
  box-sizing: border-box;
}
/* Corrección (Dionisio, 4 sept 2026: "quiero que las barras... tenga los
   botones, el comportamiento, la forma y el mismo diseño de la barra [de
   Empleado]... me encanta como queda la barra de empleados más que la de
   dueño o otras" — y luego, corrigiendo el alcance: "quería el diseño de
   la barra para todos... lo implementas en todas las pantallas que
   tengan esta barra o similar sea dueño, empleado, admin, súper admin no
   trabajemos 10 veces no te dije en ningún momento que cambies scroll
   porque está bien como están").

   Diseño de botón COMPARTIDO por las cuatro pantallas (Dueño, Empleado,
   Administrativo/a, Súper admin): clase única .flat-press-pill-tabs en
   vez de cuatro copias del mismo bloque. Mismo tacto en las cuatro: sombra
   fija siempre "elevada" en vez de invertirse al presionar, y la pestaña
   activa sin el squish de scale(0.96) — queda permanentemente "hundida"
   con su propia sombra de acento en vez de reaccionar al toque — más el
   mismo alto/tipografía compacta. Esto es solo el DISEÑO del botón: el
   scroll/grid de cada pantalla no se toca acá, cada una sigue exactamente
   como estaba (Dueño y Súper admin conservan su scroll horizontal vía
   .pill-tabs-scroll; Empleado conserva su grid de 4 columnas fijas sin
   scroll, definido aparte en tokens.css; Administrativo/a conserva su
   fila simple sin scroll). */
.flat-press-pill-tabs .neu-inset {
  padding: 7px !important;
  gap: 4px !important;
}
.flat-press-pill-tabs .neu-inset button {
  min-height: 42px !important;
  padding: 7px 14px !important;
  font-size: 12px !important;
  box-shadow:
    3px 4px 8px color-mix(in srgb, var(--shadow-dark) 72%, transparent),
    -3px -3px 7px color-mix(in srgb, var(--shadow-light) 78%, transparent) !important;
}
.flat-press-pill-tabs .neu-inset button.neu-button-accent {
  transform: none !important;
  box-shadow:
    inset 3px 4px 7px color-mix(in srgb, var(--accent) 66%, #000 34%),
    inset -2px -2px 5px color-mix(in srgb, var(--accent) 76%, #fff 24%),
    0 1px 2px color-mix(in srgb, var(--shadow-dark) 22%, transparent) !important;
}
/* Empleado es la única con celdas de ancho fijo (1/4 del total en vez
   de lo que el texto necesita), por eso necesita padding horizontal
   más angosto que el resto — se sobreescribe acá, después del bloque
   compartido, para que gane sin duplicar min-height/font-size/sombras. */
.employee-pill-tabs .neu-inset button {
  padding: 7px 2px !important;
}
