/* ============================================================================
   Lonky ERP — Theme (adopción del Atomic Design System)
   Fase 1: tokens + re-tematización de Bootstrap 5.3.
   Se carga DESPUÉS de Bootstrap para pisar sus defaults. Ver docs/design-system/tokens.md
   ============================================================================ */

/* ----------------------------------------------------------------------------
   TIPOGRAFIA DE MARCA
   Satoshi (UI y titulos) la sirve api.fontshare.com: su licencia es ambigua sobre
   self-host y se pidio consentimiento a ITF (ver ADR-021 §5). El <link> vive en los
   layouts, no aca: un @import obliga al navegador a descargar este CSS entero antes
   de descubrir que necesita la fuente.
   JetBrains Mono (SIL OFL, sin restriccion) va self-hosted abajo. Licencia en
   public/fonts/JetBrainsMono-OFL.txt, como exige la OFL.
   ---------------------------------------------------------------------------- */

/* JetBrains Mono 400 — latin-ext */
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/jetbrains-mono-400-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* JetBrains Mono 400 — latin */
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/fonts/jetbrains-mono-400-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* JetBrains Mono 500 — latin-ext */
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('/fonts/jetbrains-mono-500-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* JetBrains Mono 500 — latin */
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url('/fonts/jetbrains-mono-500-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* JetBrains Mono 700 — latin-ext */
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('/fonts/jetbrains-mono-700-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* JetBrains Mono 700 — latin */
@font-face {
    font-family: 'JetBrains Mono';
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url('/fonts/jetbrains-mono-700-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


:root {
    /* ========================================================================
       CAPA 1 — MARCA LONKYDEV. Valores del kit (DesignSystem/brand/tokens.json).
       No dependen del tema y no se redefinen nunca. Ningún componente los usa
       directamente: para eso está la capa semántica de abajo. Ver ADR-021.
       ======================================================================== */
    --ld-violet:        #5A28D3;              /* Violeta Real — primarios, focus, isotipo */
    --ld-violet-hover:  #6B3BE4;              /* +8% de luz */
    --ld-violet-active: #4F23BC;              /* −4% */
    --ld-violet-text:   #8A63F0;              /* violeta legible SOBRE oscuro */
    --ld-violet-soft:   rgba(90, 40, 211, .14);
    --ld-obsidian:      #0A0A0F;
    --ld-focus-ring:    0 0 0 4px rgba(90, 40, 211, .28);

    /* ---- Paleta de marca (escala 50–900) ----
       Derivada de la azul anterior en OKLCH: se conservó la LIGHTNESS de cada escalón y se
       cambió el hue al del violeta de marca, así los contrastes quedan donde estaban (un texto
       que se leía sobre blue/50 se sigue leyendo sobre violet/50). Los pasos 400/500/600 son
       los valores exactos del kit —hover, base, active— y por eso no siguen la derivación. */
    --lonky-violet-50:  #F4F4FE; --lonky-violet-100: #D8D7F7; --lonky-violet-200: #B2AFF0;
    --lonky-violet-300: #8F86E8; --lonky-violet-400: var(--ld-violet-hover);
    --lonky-violet-500: #5A28D3; --lonky-violet-600: var(--ld-violet-active);
    --lonky-violet-700: #371E82; --lonky-violet-800: #231457; --lonky-violet-900: #100A2C;

    /* ---- Estados, del kit LonkyDev ----
       Cada estado tiene DOS tokens y la diferencia importa: el primero es el color del kit tal
       cual (identidad: bordes, puntos de badge, texto sobre fondos tenues); el `-relleno` es el
       mismo color oscurecido hasta cruzar 4.5:1 contra blanco, para lo que lleva texto blanco
       encima. Ninguno de los cuatro colores del kit llega a 4.5 por sí solo, y los botones del
       panel son de 15px/500 — para WCAG eso es texto NORMAL, no grande.
       Lo verifica tests/Unit/ContrasteDelTemaTest.php. */
    --lonky-success: #34A06F;  --lonky-success-relleno: #098758;
    --lonky-error:   #D25151;  --lonky-error-relleno:   #CB4A4B;
    --lonky-warning: #C0862E;  --lonky-warning-relleno: #A36B00;
    --lonky-info:    #5F7BD8;  --lonky-info-relleno:    #5671CD;

    /* El naranja NO es un estado: es un acento del sistema anterior que sigue en uso. Se deja
       con su color, porque pasarlo a la familia warning lo convierte en un marrón dorado.
       Igual que los estados, necesita su variante de relleno: el coral da 2.72:1 contra blanco,
       así que el botón de suspender del POS y el badge de curvas venían ilegibles. El relleno es
       el MISMO naranja con la lightness bajada en OKLCH —hue y chroma intactos—, no otro color. */
    --lonky-orange-500:     #EC7F4F;
    --lonky-orange-relleno: #BF5724;
    --lonky-orange-25:      #FFF7F2;  /* fondo de fila modificada en el editor masivo */
    --lonky-yellow-500: #F9BC69;
    --lonky-red-500:    var(--lonky-error-relleno);
    --lonky-green-500:  #56D99B;

    /* ---- Neutros ----
       Mismo hue que el violeta de marca (286.2°) con el chroma al 45%: armonizan con el
       primario sin leerse violáceos —un gris teñido compite con la marca en vez de
       acompañarla— y conservan la lightness exacta de la escala anterior, así que ningún
       contraste se movió (todos igual o mejor; medido en ContrasteDelTemaTest). */
    --lonky-grey-25:  #FAFAFA; --lonky-grey-50:  #F3F3F5; --lonky-grey-100: #EEEEF2;
    --lonky-grey-200: #DEDEE3; --lonky-grey-300: #B9B9C1; --lonky-grey-400: #9494A0;
    --lonky-grey-500: #6E6E7C; --lonky-grey-600: #52515C; --lonky-grey-700: #494852;
    --lonky-grey-800: #35353B; --lonky-grey-900: #212126;

    /* Le dice al NAVEGADOR de qué color pintar lo que no dibuja el CSS: el desplegable de un
       <select>, las barras de scroll, el calendario de un <input type=date>, los checkboxes
       nativos. No es un color, es una instrucción — por eso no hay token que lo reemplace. */
    color-scheme: light;

    --lonky-bg:    #F4F4F7;   /* fondo de la app */
    --lonky-sand:  #FAFAFA;   /* fondo del sidebar */
    --lonky-white: #FFFFFF;   /* el blanco de la escala; los componentes usan los dos de abajo */

    /* `--lonky-white` hacía DOS trabajos: la superficie de una card (12 usos) y la tinta sobre un
       color saturado (9 usos). En modo claro da igual, los dos son blanco. En oscuro tiran para
       lados opuestos: la card se oscurece, pero el texto sobre el botón violeta tiene que seguir
       siendo blanco porque el violeta sigue siendo oscuro. Un solo token no puede con las dos,
       así que son dos. */
    --lonky-surface:   var(--lonky-white);  /* card, tabla, dropdown: sube con el modo */
    --lonky-on-accent: #FFFFFF;             /* tinta sobre violeta/rojo/verde: no cambia nunca */

    /* El violeta de marca como TINTA sobre fondo claro. En oscuro no se lee —es un violeta
       oscuro sobre fondo oscuro— y pasa al violeta que el kit trae justo para eso. */
    --lonky-link: var(--lonky-violet-500);

    /* ---- Estados (se repiten en todo el sistema) ---- */
    --lonky-active-bg:   var(--lonky-violet-50);
    --lonky-active-fg:   var(--lonky-link);
    --lonky-hover-bg:    var(--lonky-grey-100);
    --lonky-text:        var(--lonky-grey-900);
    --lonky-text-muted:  var(--lonky-grey-500);
    /* Los labels usaban grey-400, que sobre blanco da 3.00:1 — por debajo del 4.5 que WCAG pide
       para TEXTO. No se oscureció la escala: grey-400 está bien como valor, el error era leerlo.
       WCAG pide 4.5 para texto pero 3:1 para elementos gráficos, así que grey-400 se queda para
       íconos, bordes y puntos de badge, y los labels usan el gris que sí se lee.
       (El gris legible más claro posible es #757581, a 0.02 de lightness de grey-500:
       indistinguible. Un escalón intermedio habría sido inventar un color que nadie ve.) */
    --lonky-label:       var(--lonky-grey-500);
    --lonky-border:      var(--lonky-grey-200);

    /* ---- Escala de espaciado ----
       El tema usaba 60 valores distintos de padding, ninguno por token. No es un problema
       estético —cada uno se eligió con criterio en su momento— sino de sistema: con 60 respuestas
       a la misma pregunta, quien escribe el componente 61 no tiene forma de saber cuál usar.

       El paso es de 2 hasta 16 y se abre después. Es más fina que el 4px habitual de la
       industria, y a propósito: el ERP es una app densa de escritorio —tablas de productos con
       ocho columnas— y una escala de 4px la vuelve un 25% más alta. Los escalones salieron de
       medir lo que el código YA usaba: 8px aparecía 41 veces, 12px 25, 6px y 10px 17 cada uno.
       Adoptar la escala de la industria habría movido 49 usos para ganar prolijidad de catálogo
       y perder densidad de pantalla. */
    --lonky-space-2:   2px;   /* separaciones de un pelo: badge y su texto */
    --lonky-space-4:   4px;
    --lonky-space-6:   6px;
    --lonky-space-8:   8px;   /* el más usado: gap entre controles */
    --lonky-space-10: 10px;
    --lonky-space-12: 12px;   /* padding vertical de fila de tabla */
    --lonky-space-14: 14px;
    --lonky-space-16: 16px;
    --lonky-space-20: 20px;   /* padding horizontal de fila de tabla */
    --lonky-space-24: 24px;   /* interior de una card */
    --lonky-space-32: 32px;
    --lonky-space-40: 40px;
    --lonky-space-48: 48px;   /* aire entre secciones de una página */

    /* ---- Escala tipográfica ----
       Eran 32 tamaños, y la mitad de los raros venía de mezclar unidades: convivían `13px` y
       `.78rem` —que es 12.5px— para el mismo rol. Todo en px, que es lo que hace comparables dos
       valores de un vistazo.

       Base 14 y h1 28, como fija el ADR-021: no se adopta la escala del kit (base 16, h1 40)
       porque un h1 de 40px arriba de una tabla de productos no entra. */
    --lonky-text-caption: 10px;  /* pie de un stat box */
    --lonky-text-label:   11px;  /* header de tabla, en mayúsculas */
    --lonky-text-xs:      12px;
    --lonky-text-sm:      13px;  /* el más usado del panel */
    --lonky-text-base:    14px;  /* cuerpo */
    --lonky-text-lg:      16px;
    --lonky-text-xl:      18px;  /* h3 */
    --lonky-text-2xl:     22px;  /* h2 */
    --lonky-text-3xl:     28px;  /* h1 */

    /* ---- Radios, del kit LonkyDev: 10px en controles, 16px en cards ----
       El sistema anterior usaba 4px, que es casi una esquina viva. El salto a 10/16 es el cambio
       que más altera la percepción del panel —más que el color— porque toca todos los botones,
       inputs y cards a la vez. */
    --lonky-radius-input: 10px;  /* inputs, select, dropdown, botones */
    --lonky-radius-card: 16px;   /* cards */
    --lonky-radius-form: 16px;   /* contenedores de formulario: misma familia que las cards */
    --lonky-radius-table: 10px;  /* header / paginación de tabla */
    --lonky-radius-pill: 999px;  /* badges, avatar, switch, áreas circulares */
    --lonky-radius: 16px;        /* general */
    --lonky-radius-sm: 8px;      /* piezas chicas del shell */

    /* ---- Anillo de foco, el del kit: 4px de violeta al 28% ----
       Más grueso que el 2px anterior a propósito: es la única señal de "estás acá" para quien
       navega con teclado, y a 2px sobre un borde de 1px casi no se distingue. */
    --lonky-focus-ring: 0 0 0 4px rgba(90, 40, 211, .28);
    --lonky-btn-focus-glow: var(--lonky-focus-ring);

    /* ---- Tipografía ---- */
    /* Satoshi es la familia ÚNICA de UI y de títulos: el kit no separa una de otra, el peso hace
       la jerarquía. El fallback es Helvetica Neue/Arial —métricas cercanas— porque la exportación
       a PDF y la impresión no embeben la fuente y ahí manda el fallback. */
    --lonky-font-ui: 'Satoshi', 'Helvetica Neue', Arial, sans-serif;
    --lonky-font-title: 'Satoshi', 'Helvetica Neue', Arial, sans-serif;
    --lonky-font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', monospace;

    /* ---- Escalas que faltaban (los valores ya se usaban, sueltos, más abajo) ---- */
    --lonky-green-200: #9CE8BE;  --lonky-green-600: var(--lonky-success-relleno);
    --lonky-green-700: #006639;  /* hover del verde: más oscuro que el 600, no más apagado */
    --lonky-red-25:    #FEF6F5;  --lonky-red-200:   #EE9F9A;  --lonky-red-600:   #BE3F41;
    --lonky-info-200:  #BBCAF1;  --lonky-info-500:  var(--lonky-info-relleno);

    /* ---- Campos de solo lectura ---- */
    /* Atados a la escala en vez de sueltos: el #F2F2F2 que tenían no era ningún escalón —quedaba
       apenas fuera de la armonía violeta del resto de los grises— y encima dejaba el texto en
       4.48:1, dos centésimas por debajo del mínimo. Como ahora salen de la escala, el modo
       oscuro los alcanza solo y no hay que redefinirlos. */
    --lonky-readonly-bg:     var(--lonky-grey-50);
    --lonky-readonly-border: var(--lonky-grey-300);

    /* ---- Recuadros de cifras (stat boxes): trío borde/fondo/texto por estado ---- */
    --lonky-stat-ok-border:      #A2D7B9;  --lonky-stat-ok-bg:      #F2FAF5;  --lonky-stat-ok-text:      #337B58;
    --lonky-stat-danger-border:  #E1AAA6;  --lonky-stat-danger-bg:  #FCF4F3;  --lonky-stat-danger-text:  #B44242;
    --lonky-stat-pending-border: #ECCDA5;  --lonky-stat-pending-bg: #FDF8F0;  --lonky-stat-pending-text: #956A2A;
    /* Los dos tríos que faltaban, para los carteles de aviso: `alert-info` y `alert-primary`
       suman 14 usos en las vistas y no tenían dónde salir. El texto del informativo se oscureció
       un escalón —#5671CD daba 4.11:1 sobre su propio fondo— y conserva hue y chroma. */
    --lonky-stat-info-border:    #BBCAF1;  --lonky-stat-info-bg:    #F1F4FC;  --lonky-stat-info-text:    #4F6AC5;
    --lonky-stat-marca-border:   #B2AFF0;  --lonky-stat-marca-bg:   #F4F4FE;  --lonky-stat-marca-text:   #5A28D3;

    /* ---- Punto de color de los badges de estado (no son los colores de estado del sistema) ---- */
    --lonky-badge-dot-success: #32B5AC;
    --lonky-badge-dot-info:    #00A3FF;
    --lonky-badge-dot-warning: #FFA100;

    /* ---- Varios ---- */
    --lonky-video-bg: #000;

    /* El Obsidian del chrome del super admin. Es de los poquísimos tokens semánticos que NO
       cambian con el modo, y a propósito: no es una superficie del tema sino la señal de que
       estás parado en el super admin y no en el ERP de un tenant. Esa distinción tiene que
       sostenerse igual de día que de noche — confundir las dos superficies es caro. */
    --lonky-chrome-obsidian: var(--ld-obsidian);

    /* Fondo del badge neutro (`bg-dark` de Bootstrap): el "protegido" de roles y el estado
       "failed" de los pedidos. En claro es casi tinta; en oscuro no puede seguir siendo oscuro
       —se perdería contra el fondo— así que sube a un gris medio que sostiene el texto blanco
       (5.02:1) y se despega de la card (3.45:1). */
    --lonky-badge-neutro: var(--lonky-grey-800);

    /* Velo de los modales. Venía escrito a mano en cada uno y había derivado a tres valores
       distintos —.5, .55 y .6— para exactamente lo mismo: no era intención, era copiar y pegar.
       Un solo token, así los 17 modales del panel oscurecen igual. */
    --lonky-overlay: rgba(0, 0, 0, .5);

    /* Violeta translúcido: selección sobre una miniatura del selector de imágenes, donde un
       fondo sólido taparía la foto. Era rgba(50,60,233,.15) —el AZUL del sistema anterior, que
       sobrevivió a la migración porque el guard buscaba hex y este color se escribe en rgba. */
    --lonky-violet-tint: rgba(90, 40, 211, .15);

    /* Los otros dos violetas translúcidos del panel. Los dos venían en azul por la misma razón
       —se escriben en rgba, el guard buscaba hex— y son de los que más se ven: uno aparece cada
       vez que alguien hace foco en un campo, el otro cada vez que pasa el mouse por una fila. */
    --lonky-focus-bg:  rgba(244, 244, 254, .5);   /* fondo del input enfocado (violet-50 al 50%) */
    --lonky-row-hover: rgba(90, 40, 211, .04);    /* fila de tabla bajo el cursor */

    /* Confirmación del escáner del POS: se pintan sobre la imagen de la cámara, por eso llevan
       alpha. Eran el verde y el rojo de Bootstrap, no los del sistema. */
    --lonky-scan-ok:   rgba(9, 135, 88, .9);
    --lonky-scan-fail: rgba(203, 74, 75, .9);

    /* DEUDA SALDADA (fase 1). Esto se escribía `var(--lonky-gray-500, #6c757d)` — con "gray" sin
       "e", un token que nunca existió, así que la manija de arrastre venía pintada con el gris de
       Bootstrap y no con el del sistema. La fase 0 conservó el valor para no cambiar nada visual;
       acá entra en la escala. El cambio es sutil y deliberado: #6C757D → #61708C. */
    --lonky-drag-handle-fg: var(--lonky-grey-500);

    /* ======================================================================
       Re-tematización de Bootstrap: pisamos sus variables con los tokens del DS
       ====================================================================== */
    --bs-primary:      var(--lonky-violet-500);
    --bs-primary-rgb:  90, 40, 211;   /* #5A28D3 — Bootstrap lo necesita descompuesto para sus rgba() */
    /* Los `-rgb` van descompuestos porque Bootstrap arma sus rgba() con ellos y no acepta var().
       Se usa la variante `-relleno` de cada estado: acá es donde va texto blanco encima. */
    --bs-danger:       var(--lonky-error-relleno);    --bs-danger-rgb:  203, 74, 75;
    --bs-success:      var(--lonky-success-relleno);  --bs-success-rgb: 9, 135, 88;
    --bs-warning:      var(--lonky-warning);          --bs-warning-rgb: 192, 134, 46;
    --bs-info:         var(--lonky-info-relleno);     --bs-info-rgb:    86, 113, 205;
    --bs-body-color:   var(--lonky-text);  --bs-body-color-rgb: 29, 34, 43;
    --bs-body-bg:      var(--lonky-bg);
    --bs-border-color: var(--lonky-border);
    --bs-secondary-color: var(--lonky-text-muted);
    --bs-border-radius:    8px;
    --bs-border-radius-sm: 6px;
    --bs-border-radius-lg: 12px;
    --bs-link-color:       var(--lonky-link);  --bs-link-color-rgb: 90, 40, 211;
    --bs-link-hover-color: var(--lonky-violet-600);
    --bs-font-sans-serif:  var(--lonky-font-ui);
    --bs-font-monospace:   var(--lonky-font-mono);
}

/* ============================================================================
   MODO OSCURO
   ----------------------------------------------------------------------------
   El kit LonkyDev es dark-first y trata el claro como excepción. Acá se invierte
   (ADR-021, decisión 2): la base es CLARA y esto es la excepción. Con 142 vistas
   escritas asumiendo fondo claro, un estilo nuevo que alguien escriba sin
   acordarse del tema tiene que romper el modo OPCIONAL, no el que ve el 100% de
   las usuarias. El costo del descuido queda del lado barato.

   Casi todo esto es UNA idea: la escala de grises se espeja. `--lonky-grey-900`
   pasa a ser casi blanco y `--lonky-grey-25` casi negro, conservando el hue
   violeta (286.2°) y el chroma. Como los tokens semánticos apuntan a la escala
   —`--lonky-text: var(--lonky-grey-900)`, `--lonky-border: var(--lonky-grey-200)`—
   se acomodan solos, sin repetirlos acá. Por eso abajo sólo quedan los pocos que
   se declararon con un valor literal.

   La capa 1 (`--ld-*`) NO se toca: el violeta de Lonky es el mismo de noche que
   de día. Lo verifica ModoOscuroTest.
   ============================================================================ */
[data-theme="dark"] {
    /* La escala espejada. Los extremos no van a negro ni a blanco puros a
       propósito: #000 detrás de texto claro produce halación —el texto "vibra"—
       y #FFF sobre oscuro deslumbra en una pantalla que se mira ocho horas. */
    --lonky-grey-25:  #17171C; --lonky-grey-50:  #1C1C22; --lonky-grey-100: #23232A;
    --lonky-grey-200: #2F2F38; --lonky-grey-300: #4C4C57; --lonky-grey-400: #8B8B97;
    --lonky-grey-500: #A6A6B2; --lonky-grey-600: #C0C0CA; --lonky-grey-700: #D2D2DA;
    --lonky-grey-800: #E4E4EA; --lonky-grey-900: #EFEFF3;

    /* Superficies, de más hundida a más elevada: fondo → sidebar → card. En claro
       la jerarquía se lee por sombra y borde; en oscuro se lee porque lo que está
       "más arriba" es más claro. */
    /* Sin esto el modo oscuro tiene un agujero grande: el desplegable de un <select> lo dibuja
       el SISTEMA, no el CSS, y sin avisarle lo dibuja CLARO — mientras las <option> heredan el
       texto blanco del control. Blanco sobre blanco: pasó en producción con los filtros de
       productos y el selector de depósito de las transferencias, donde la lista se veía vacía.
       De paso arregla las barras de scroll, que salían claras sobre el panel negro. */
    color-scheme: dark;

    --lonky-bg:      #101014;
    --lonky-sand:    #141419;
    --lonky-surface: #1A1A21;

    /* La escala violeta también se recalibra, por la misma razón que la de grises: el Violeta
       Real es un color OSCURO, y sobre fondo oscuro se pierde. Medido: el botón primario con
       #5A28D3 da 2.42:1 contra el fondo — el botón más importante del panel desaparece.

       El violeta de los BOTONES tiene que cumplir dos cosas a la vez que tiran para lados
       opuestos: ≥4.5:1 con el texto blanco encima y ≥3:1 contra el fondo para que el botón se
       vea. Sólo doce escalones cumplen las dos; se toma el más oscuro, que es el más cercano a
       la marca (5.71:1 con blanco, 3.03:1 contra la card). Ninguno de los escalones que ya
       existían servía: violet-400 se queda a 2.78 del fondo y violet-300 no sostiene el blanco.

       El violeta del TEXTO es otro problema —no lleva nada encima, sólo tiene que leerse— y
       necesita ser aún más claro. El `--ld-violet-text` del kit da 4.22:1 sobre nuestra card:
       el kit lo calibró contra su propio Obsidian, que es más oscuro que nuestra superficie. */
    --lonky-violet-50:  #191233;
    --lonky-violet-400: #7A57FF;
    --lonky-violet-500: #6C44EC;
    --lonky-violet-600: #8058FF;
    --lonky-link:       #8F68F6;
    --lonky-active-bg:  rgba(108, 68, 236, .22);

    /* Los translúcidos se recalculan: un velo negro sobre fondo negro no separa
       nada, y el tinte violeta necesita más presencia para verse. */
    --lonky-overlay:   rgba(0, 0, 0, .72);
    --lonky-focus-bg:  rgba(138, 99, 240, .10);
    --lonky-row-hover: rgba(138, 99, 240, .07);

    /* Los stat boxes se declararon con literal, así que la escala no los alcanza y hay
       que repetirlos acá. Los campos de solo lectura NO están: ahora salen de la escala
       y se acomodan solos — así se ve la diferencia entre atar un token a la escala y
       dejarlo suelto. */

    --lonky-stat-ok-border:      #2C5C44;  --lonky-stat-ok-bg:      #16241C;  --lonky-stat-ok-text:      #6FD3A0;
    --lonky-stat-danger-border:  #6B3735;  --lonky-stat-danger-bg:  #261818;  --lonky-stat-danger-text:  #F0918D;
    --lonky-stat-pending-border: #6B5430;  --lonky-stat-pending-bg: #241E14;  --lonky-stat-pending-text: #E0B472;
    --lonky-stat-info-border:    #3A4560;  --lonky-stat-info-bg:    #171B26;  --lonky-stat-info-text:    #8AA3E8;
    --lonky-stat-marca-border:   #3B2E6B;  --lonky-stat-marca-bg:   #191233;  --lonky-stat-marca-text:   #8F68F6;

    /* Los rellenos de estado NO se tocan, aunque el primer impulso sea aclararlos.
       El razonamiento equivocado es "el fondo ahora es oscuro, así que el relleno
       tiene que ser claro" — pero el relleno ES el fondo del botón, y el texto que
       lleva encima sigue siendo blanco. Aclararlos rompe justamente eso: el guard
       de contraste midió el verde aclarado en 2.35:1 contra su propio texto.
       Como están, cumplen las dos condiciones sin cambiar nada: ≥4.5:1 con el
       blanco y ≥3.79:1 contra el fondo oscuro. Un botón de color sólido no
       pertenece al tema, se sostiene solo.

       El único que sí cambia es el naranja tenue, que es una SUPERFICIE. */
    --lonky-orange-25: #241A15;

    /* En claro sale de la escala (grey-800, casi tinta); acá no puede seguir el espejo —grey-800
       se vuelve casi blanco— y necesita un gris medio propio. */
    --lonky-badge-neutro: #6E6E7C;

    /* Bootstrap consume estos en rgba() para sus opacidades y no puede leer un
       var() adentro: se declaran acá o el texto atenuado queda oscuro. */
    --bs-body-color-rgb: 239, 239, 243;
    --bs-link-color-rgb: 143, 104, 246;
}

/* ============================================================================
   FIN DE LA CAPA DE TOKENS
   ----------------------------------------------------------------------------
   De acá para abajo NO se escriben colores literales: solo var(--lonky-*).
   Un color escrito a mano se ve bien en claro y se rompe en oscuro, sin que
   nada falle — el CSS no da error, la pantalla queda ilegible.
   ¿Hace falta un color nuevo? Se agrega arriba, no acá.
   Lo verifica tests/Unit/DesignSystemTokensTest.php. Ver ADR-021.
   ============================================================================ */

/* ---- Base + escala tipográfica ----
   La densidad es la del ERP (base 14px), NO la del kit (16px con h1 de 40px): un h1 así no entra
   arriba de una tabla de productos que se lee ocho horas por día. Lo que sí se adopta es el
   CARÁCTER de la marca — peso 900 con tracking negativo en los títulos, y mono en versalitas para
   las etiquetas. Eso es lo que hace que se lea LonkyDev, más que el tamaño. Ver ADR-021. */
body {
    font-family: var(--lonky-font-ui);
    font-size: var(--lonky-text-base);
    line-height: 1.5;
    color: var(--lonky-text);
    background-color: var(--lonky-bg);
    -webkit-font-smoothing: antialiased;
    --bs-body-font-size: 14px;
}
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
    font-family: var(--lonky-font-title);
    color: var(--lonky-text);
    text-wrap: balance;
}
h1, .h1 { font-size: var(--lonky-text-3xl); line-height: 34px; font-weight: 900; letter-spacing: -.015em; }
h2, .h2 { font-size: var(--lonky-text-2xl); line-height: 27px; font-weight: 700; letter-spacing: -.01em; }
h3, .h3 { font-size: var(--lonky-text-lg); line-height: 22px; font-weight: 700; }
h4, .h4 { font-size: var(--lonky-text-base); line-height: 20px; font-weight: 700; }
h5, .h5 { font-size: var(--lonky-text-sm); line-height: 16px; font-weight: 700; }
h6, .h6 { font-size: var(--lonky-text-xs); line-height: 15px; font-weight: 700; }
small, .small { font-size: var(--lonky-text-xs); font-weight: 500; }
.text-muted, .text-secondary { color: var(--lonky-text-muted) !important; }

/* Etiqueta en mono y versalitas: encabezados de tabla, captions, rótulos de stat box.
   Es el detalle que más marca el carácter del kit dentro de una pantalla densa. */
.lonky-caption {
    font-family: var(--lonky-font-mono);
    font-size: var(--lonky-text-label); font-weight: 500; line-height: 1.4;
    letter-spacing: .08em; text-transform: uppercase;
    color: var(--lonky-text-muted);
}
code, kbd, pre, samp { font-family: var(--lonky-font-mono); font-size: var(--lonky-text-sm); }

/* ============================================================================
   BOTONES (valores exactos del kit — Fase 3)
   Tamaños: M (default) 40px · S (.btn-sm) 34px · L (.btn-lg) 48px. Radius 4px. weight 500.
   ============================================================================ */
.btn {
    --bs-btn-padding-y: 10px; --bs-btn-padding-x: 20px;
    --bs-btn-font-size: 15px; --bs-btn-font-weight: 500;
    --bs-btn-border-radius: var(--lonky-radius-input);
    line-height: 20px; display: inline-flex; align-items: center; justify-content: center; gap: var(--lonky-space-8);
}
.btn-sm {
    --bs-btn-padding-y: 9px; --bs-btn-padding-x: 18px; --bs-btn-font-size: 13px;
    --bs-btn-border-radius: var(--lonky-radius-input); line-height: 16px;
}
.btn-lg {
    --bs-btn-padding-y: 14px; --bs-btn-padding-x: 28px; --bs-btn-font-size: 15px;
    --bs-btn-border-radius: var(--lonky-radius-input); line-height: 20px;
}

/* Primary (relleno) */
.btn-primary {
    --bs-btn-bg: var(--lonky-violet-500);        --bs-btn-border-color: var(--lonky-violet-500);
    --bs-btn-hover-bg: var(--lonky-violet-400);  --bs-btn-hover-border-color: var(--lonky-violet-400);
    --bs-btn-active-bg: var(--lonky-violet-400);  --bs-btn-active-border-color: var(--lonky-violet-400);
    --bs-btn-disabled-bg: var(--lonky-grey-300); --bs-btn-disabled-border-color: var(--lonky-grey-300);
}
.btn-primary:focus-visible { background-color: var(--lonky-violet-300); border-color: var(--lonky-violet-300); box-shadow: var(--lonky-focus-ring); }

/* Secondary = outline gris → azul (mapeado a .btn-outline-secondary, muy usado en las vistas) */
.btn-outline-secondary {
    --bs-btn-color: var(--lonky-grey-900);        --bs-btn-border-color: var(--lonky-grey-200);
    --bs-btn-hover-bg: transparent; --bs-btn-hover-color: var(--lonky-link); --bs-btn-hover-border-color: var(--lonky-grey-200);
    --bs-btn-active-bg: transparent; --bs-btn-active-color: var(--lonky-link); --bs-btn-active-border-color: var(--lonky-violet-500);
    --bs-btn-disabled-color: var(--lonky-grey-300); --bs-btn-disabled-border-color: var(--lonky-grey-300);
}
.btn-outline-secondary:focus-visible { border-color: var(--lonky-violet-500); color: var(--lonky-link); box-shadow: var(--lonky-focus-ring); }

.btn-outline-primary {
    --bs-btn-color: var(--lonky-link);    --bs-btn-border-color: var(--lonky-violet-500);
    --bs-btn-hover-bg: var(--lonky-violet-500); --bs-btn-hover-border-color: var(--lonky-violet-500);
    --bs-btn-active-bg: var(--lonky-violet-400);
}

/* Danger / Success (Bootstrap 5.3 no los deriva de --bs-*) */
.btn-danger  { --bs-btn-bg: var(--lonky-red-500); --bs-btn-border-color: var(--lonky-red-500); --bs-btn-hover-bg: var(--lonky-red-600); --bs-btn-hover-border-color: var(--lonky-red-600); }
.btn-success { --bs-btn-bg: var(--lonky-green-600); --bs-btn-border-color: var(--lonky-green-600); --bs-btn-hover-bg: var(--lonky-green-700); --bs-btn-hover-border-color: var(--lonky-green-700); }

/* Link */
.btn-link { --bs-btn-color: var(--lonky-link); --bs-btn-hover-color: var(--lonky-link); --bs-btn-font-weight: 500; text-decoration: none; }
.btn-link:hover { background-color: var(--lonky-violet-50); }
.btn-link:focus-visible { background-color: var(--lonky-violet-50); color: var(--lonky-violet-400); }

/* Secondary sólido / dark → botón neutro del kit (sand + borde sutil) */
.btn-secondary, .btn-dark {
    --bs-btn-bg: var(--lonky-sand); --bs-btn-border-color: var(--lonky-grey-200); --bs-btn-color: var(--lonky-grey-900);
    --bs-btn-hover-bg: var(--lonky-grey-50); --bs-btn-hover-border-color: var(--lonky-grey-200); --bs-btn-hover-color: var(--lonky-grey-900);
    --bs-btn-active-bg: var(--lonky-grey-100); --bs-btn-active-border-color: var(--lonky-grey-200); --bs-btn-active-color: var(--lonky-grey-900);
    --bs-btn-disabled-bg: var(--lonky-sand); --bs-btn-disabled-color: var(--lonky-grey-300); --bs-btn-disabled-border-color: var(--lonky-grey-100);
}
.btn-outline-dark {
    --bs-btn-color: var(--lonky-grey-900); --bs-btn-border-color: var(--lonky-grey-200);
    --bs-btn-hover-bg: var(--lonky-grey-50); --bs-btn-hover-color: var(--lonky-grey-900); --bs-btn-hover-border-color: var(--lonky-grey-200);
    --bs-btn-active-bg: var(--lonky-grey-100); --bs-btn-active-color: var(--lonky-grey-900);
}
.btn-light {
    --bs-btn-bg: var(--lonky-surface); --bs-btn-border-color: var(--lonky-grey-200); --bs-btn-color: var(--lonky-grey-900);
    --bs-btn-hover-bg: var(--lonky-grey-50); --bs-btn-hover-border-color: var(--lonky-grey-200);
}
.btn-outline-danger  { --bs-btn-color: var(--lonky-red-500); --bs-btn-border-color: var(--lonky-red-200); --bs-btn-hover-bg: var(--lonky-red-500); --bs-btn-hover-border-color: var(--lonky-red-500); --bs-btn-hover-color: var(--lonky-on-accent); --bs-btn-active-bg: var(--lonky-red-600); }
.btn-outline-success { --bs-btn-color: var(--lonky-green-600); --bs-btn-border-color: var(--lonky-green-200); --bs-btn-hover-bg: var(--lonky-green-600); --bs-btn-hover-border-color: var(--lonky-green-600); --bs-btn-hover-color: var(--lonky-on-accent); }
.btn-outline-info    { --bs-btn-color: var(--lonky-info-500); --bs-btn-border-color: var(--lonky-info-200); --bs-btn-hover-bg: var(--lonky-info-500); --bs-btn-hover-color: var(--lonky-on-accent); }

/* ---- Nav tabs (estilo del kit: activo con subrayado azul) ---- */
.nav-tabs { border-bottom: 1px solid var(--lonky-grey-100); }
.nav-tabs .nav-link {
    color: var(--lonky-grey-500); border: none; border-bottom: 2px solid transparent;
    font-weight: 500; font-size: var(--lonky-text-sm); padding: var(--lonky-space-8) var(--lonky-space-14); margin-bottom: -1px;
}
.nav-tabs .nav-link:hover { color: var(--lonky-link); border-color: transparent transparent transparent; isolation: auto; }
.nav-tabs .nav-link.active { color: var(--lonky-link); background: none; border: none; border-bottom: 2px solid var(--lonky-violet-500); }
/* Contadores dentro de tabs: son números, NO badges de estado → sin el punto, neutro compacto */
.nav-link .badge::before, .pos-tab .badge::before { display: none; }
.nav-link .badge, .pos-tab .badge {
    background: var(--lonky-grey-100) !important; color: var(--lonky-grey-500) !important;
    padding: var(--lonky-space-2) var(--lonky-space-6); font-weight: 600; gap: 0; min-width: 20px; justify-content: center;
}
.nav-link.active .badge, .pos-tab.active .badge { background: var(--lonky-violet-50) !important; color: var(--lonky-link) !important; }

.text-primary { color: var(--lonky-link) !important; }
.bg-primary  { background-color: var(--lonky-violet-500) !important; }

/* ---- Las utilidades de Bootstrap que nombran un COLOR y no un ROL ----
   `text-dark`, `bg-white`, `bg-light`: 165 usos repartidos por las 142 vistas. El problema no es
   que estén, es cómo se llaman. "Texto oscuro" es una instrucción literal, y en modo oscuro
   produce exactamente lo que pide: texto oscuro sobre fondo oscuro. Se vio en la primera captura
   del panel — los nombres de los productos y la pestaña activa habían desaparecido.

   Migrar 165 usos en 142 vistas sale carísimo y rompe cosas. Se redefinen acá: lo que en la
   vista dice "oscuro" pasa a significar "el color del texto principal", que es lo que quien lo
   escribió quería decir. Una vista nueva puede seguir usando `text-dark` sin romper nada.

   Llevan `!important` porque las de Bootstrap lo llevan, no por gusto. */
/* ---- Barras de scroll ----
   Sin esto el navegador dibuja la suya: gris claro, gruesa y con flechas arriba y abajo, que en
   un panel oscuro canta y en uno claro tampoco acompaña. `color-scheme` ya hace que sea oscura
   en modo oscuro; esto la vuelve además discreta.

   Los colores salen de tokens, así que la barra sigue el tema sola: no hay una versión clara y
   otra oscura que mantener.

   `scrollbar-width`/`scrollbar-color` es el estándar (Firefox, Chrome moderno) y el bloque
   `::-webkit-scrollbar` cubre a los WebKit que todavía no lo implementan. Los dos hacen falta:
   ninguno alcanza solo. */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--lonky-grey-300) transparent;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
    background: var(--lonky-grey-300);
    border-radius: var(--lonky-radius-pill);
    /* El borde transparente con `background-clip` es lo que adelgaza el pulgar sin achicar el
       área de agarre: se sigue pudiendo tomar con el mouse en los 10px completos. */
    border: 3px solid transparent;
    background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--lonky-grey-400); background-clip: content-box; }
/* La esquina donde se cruzan las dos barras: sin esto queda un cuadradito del color del sistema. */
::-webkit-scrollbar-corner { background: transparent; }

/* ---- El logo de la marca ----
   El isotipo y el nombre, alineados por su centro óptico y no por la caja: el hexágono es más
   alto que las letras, así que centrar las cajas lo deja flotando. El `gap` es el área de
   seguridad que pide el kit —1× el grosor del trazo—, expresada en la escala.

   El color NO se fija acá: el SVG usa `currentColor`, así el logo es blanco en el navbar
   Obsidian y violeta sobre fondo claro sin cambiar de archivo ni de clase. */
.lonky-logo {
    display: inline-flex;
    align-items: center;
    gap: var(--lonky-space-8);
    text-decoration: none;
    color: inherit;
}
.lonky-logo svg { flex-shrink: 0; }
.lonky-logo-texto {
    font-family: var(--lonky-font-title);
    font-size: var(--lonky-text-lg);
    /* Peso 900 con tracking negativo: es como el kit fija el logotipo, y es lo que hace que el
       nombre lea como marca y no como un título más de la página. */
    font-weight: 900;
    letter-spacing: -.015em;
    line-height: 1;
    white-space: nowrap;
}
/* El nombre lleva la segunda mitad en violeta —Lonky**Dev**, Lonky**ERP**—, que es la única
   licencia respecto del kit: el kit lo pone en una tinta, pero acá el logo convive con el
   wordmark viejo en la memoria de quien lo usa todos los días, y el corte de color ayuda a que
   se reconozca como el mismo producto. */
.lonky-logo-texto b { font-weight: 900; color: var(--lonky-link); }
.lonky-logo--grande .lonky-logo-texto { font-size: var(--lonky-text-2xl); }

/* Sobre el chrome Obsidian el nombre entero va en blanco: el violeta de marca sobre casi negro
   queda por debajo del mínimo de contraste. */
.lonkydev-navbar .lonky-logo, .lonkydev-login .lonky-logo { color: var(--lonky-on-accent); }
.lonkydev-navbar .lonky-logo-texto b, .lonkydev-login .lonky-logo-texto b { color: var(--lonky-on-accent); }

/* ---- Carteles de aviso ----
   66 usos en las vistas y el tema no los tocaba: seguían con los colores que trae Bootstrap, que
   son fijos. En modo oscuro quedaban rosa pastel y celeste sobre un panel negro — se veía en la
   primera captura del POS. Salen de los mismos tríos que los recuadros de cifras, que ya están
   medidos en los dos modos, así que un aviso y una cifra del mismo estado se ven iguales. */
.alert {
    border-radius: var(--lonky-radius-card);
    border: 1px solid transparent;
    padding: var(--lonky-space-14) var(--lonky-space-16);
}
.alert-success { background-color: var(--lonky-stat-ok-bg);      border-color: var(--lonky-stat-ok-border);      color: var(--lonky-stat-ok-text); }
.alert-danger  { background-color: var(--lonky-stat-danger-bg);  border-color: var(--lonky-stat-danger-border);  color: var(--lonky-stat-danger-text); }
.alert-warning { background-color: var(--lonky-stat-pending-bg); border-color: var(--lonky-stat-pending-border); color: var(--lonky-stat-pending-text); }
.alert-info    { background-color: var(--lonky-stat-info-bg);    border-color: var(--lonky-stat-info-border);    color: var(--lonky-stat-info-text); }
.alert-primary { background-color: var(--lonky-stat-marca-bg);   border-color: var(--lonky-stat-marca-border);   color: var(--lonky-stat-marca-text); }
.alert-light, .alert-secondary { background-color: var(--lonky-grey-50); border-color: var(--lonky-border); color: var(--lonky-text); }
/* El enlace dentro de un aviso hereda su color y se distingue por el subrayado: pintarlo del
   violeta de enlace lo dejaría ilegible sobre el fondo del aviso rojo. */
.alert .alert-link { color: inherit; text-decoration: underline; }
/* La X de cerrar viene como una imagen NEGRA embebida en Bootstrap: sobre un aviso oscuro no se
   ve. `currentColor` en la máscara la hace tomar el color del texto del aviso, que ya contrasta. */
.alert .btn-close {
    background: none;
    -webkit-mask: var(--bs-btn-close-bg) center/1em auto no-repeat;
    mask: var(--bs-btn-close-bg) center/1em auto no-repeat;
    background-color: currentColor;
    opacity: .7;
}
.alert .btn-close:hover { opacity: 1; }

/* ---- Cartel de aviso con ícono y encabezado (x-alert) ----
   El color ya lo resolvían los tríos de arriba; lo que faltaba era la ESTRUCTURA. Un cartel de
   texto pelado obliga a leer la frase entera para saber si es un festejo o un problema, y quien
   lo lee suele tener un cliente enfrente: el ícono y el título dan el estado de un vistazo.
   El ícono se alinea con la primera línea (no al centro del bloque) para que un mensaje de tres
   renglones no lo deje flotando en el medio. */
.lonky-alert {
    display: flex;
    align-items: flex-start;
    gap: var(--lonky-space-12);
    padding: var(--lonky-space-14) var(--lonky-space-16);
}
.lonky-alert-icono {
    flex: 0 0 auto;
    display: flex;
    /* Empuja el ícono a la altura de la mayúscula del título, no del alto de línea completo. */
    margin-top: 1px;
    opacity: .85;
}
/* min-width: 0 para que un mensaje largo sin espacios (un SKU) corte en vez de estirar la caja. */
.lonky-alert-cuerpo { flex: 1 1 auto; min-width: 0; }
.lonky-alert-titulo {
    margin: 0;
    font-weight: 600;
    line-height: 1.3;
}
.lonky-alert-texto {
    line-height: 1.45;
    overflow-wrap: anywhere;
}
/* Sin mensaje el título queda solo, sin un renglón vacío debajo. */
.lonky-alert-texto:empty { display: none; }
.lonky-alert-texto :last-child { margin-bottom: 0; }
.lonky-alert-texto ul { margin: var(--lonky-space-4) 0 0; padding-left: var(--lonky-space-20); }
/* Cierra el propio cartel: hereda su color en vez de la X negra fija de Bootstrap, que sobre el
   aviso oscuro no se veía. Área de toque cómoda sin agrandar la caja. */
.lonky-alert-cerrar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    margin: -4px -6px -4px 0;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    opacity: .55;
    border-radius: var(--lonky-radius-sm);
    transition: opacity .15s ease, background-color .15s ease;
}
.lonky-alert-cerrar:hover { opacity: 1; background-color: rgb(0 0 0 / 8%); }
.lonky-alert-cerrar:focus-visible { opacity: 1; outline: 2px solid currentColor; outline-offset: 1px; }
/* El dismissible de Bootstrap reserva lugar a la derecha para SU botón; el nuestro va en el
   flujo del flex, así que ese padding sobra y descentra el texto. */
.lonky-alert.alert-dismissible { padding-right: var(--lonky-space-16); }

@media (max-width: 576px) {
    .lonky-alert { gap: var(--lonky-space-10); padding: var(--lonky-space-12) var(--lonky-space-14); }
}

.text-dark, .text-black, .text-body { color: var(--lonky-text) !important; }
.text-white  { color: var(--lonky-on-accent) !important; }
.bg-white    { background-color: var(--lonky-surface) !important; }
.bg-light    { background-color: var(--lonky-grey-50) !important; }
.bg-dark, .text-bg-dark {
    background-color: var(--lonky-badge-neutro) !important;
    color: var(--lonky-on-accent) !important;
}
.link-primary, a { color: var(--lonky-link); }
a:hover { color: var(--lonky-violet-600); }

/* ============================================================================
   INPUTS / SELECT / TEXTAREA (valores exactos del kit — Fase 3)
   ============================================================================ */
.form-control, .form-select {
    min-height: 34px;                 /* tamaño M */
    padding: var(--lonky-space-6) var(--lonky-space-12);
    border: 1px solid var(--lonky-grey-100);
    border-radius: var(--lonky-radius-input);
    color: var(--lonky-grey-900);
    font-size: var(--lonky-text-base);
    background-color: var(--lonky-surface);
}
.form-control::placeholder { color: var(--lonky-text-muted); opacity: 1; }

/* La lista desplegada de un <select>.
   `color-scheme` no alcanza acá, y creer que sí fue el error de la tanda anterior: arregla el
   desplegable MIENTRAS el CSS no lo toque, pero la regla de arriba le da `color` y
   `background-color` propios al control. Las <option> heredan ese texto —claro en modo oscuro—
   y se quedan con el fondo TRANSPARENTE, que el navegador resuelve contra el blanco de su
   widget: la lista se ve vacía. Pasó en producción con el filtro de categorías.
   Sale de tokens, así que sigue el tema solo. */
option {
    background-color: var(--lonky-surface);
    color: var(--lonky-grey-900);
}
.form-control:hover:not(:disabled), .form-select:hover:not(:disabled) { border-color: var(--lonky-violet-500); }
.form-control:focus, .form-select:focus {
    border-color: var(--lonky-violet-500);
    background-color: var(--lonky-focus-bg);
    box-shadow: var(--lonky-focus-ring);
    color: var(--lonky-grey-900);
}
.form-control:disabled, .form-select:disabled,
/* El texto de un campo de solo lectura usaba `--lonky-readonly-border`: el color del BORDE como
   color del TEXTO. Un borde cumple con 3:1, un texto necesita 4.5 — daba 1.7:1 y el SKU no se
   leía. Solo lectura no significa ilegible. */
.form-control[readonly] { background-color: var(--lonky-readonly-bg); border-color: var(--lonky-readonly-border); color: var(--lonky-text-muted); }
.form-control-lg, .form-select-lg { min-height: 40px; }   /* L */
.form-control-sm, .form-select-sm { min-height: 25px; padding: var(--lonky-space-2) var(--lonky-space-12); font-size: var(--lonky-text-xs); } /* S */

/* Validación: borde de color (sin el ícono/anillo de Bootstrap) */
.form-control.is-invalid, .form-select.is-invalid, .was-validated .form-control:invalid {
    border-color: var(--lonky-red-500); background-image: none; box-shadow: none;
}
.form-control.is-invalid:focus { box-shadow: 0 0 0 2px rgba(221, 56, 56, .18); }
.form-control.is-valid, .was-validated .form-control:valid { border-color: var(--lonky-green-600); background-image: none; }
.invalid-feedback { color: var(--lonky-red-500); font-size: var(--lonky-text-label); }

/* Las flechitas nativas de un input number: fuera, en todo el panel.
   Se comen el ancho del campo y tapan el número en una pantalla densa, y cargar 12 unidades a
   fuerza de clics no es una forma de cargar una cantidad: la cantidad se escribe. Donde el gesto
   de +/- sí aporta (el carrito del POS, el modal del producto) están los botones del
   `.qty-stepper`, que no dependen de estas flechas.
   `appearance: textfield` cubre Firefox; los pseudo-elementos, los WebKit/Blink. */
input[type=number] { appearance: textfield; -moz-appearance: textfield; }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Labels y textos de campo */
.form-label { font-size: var(--lonky-text-xs); font-weight: 500; color: var(--lonky-grey-500); margin-bottom: var(--lonky-space-6); }
.form-text { font-size: var(--lonky-text-label); color: var(--lonky-grey-500); }

/* Checkbox/radio/switch: valores del kit (checkbox r2, check y switch-on azul, foco) */
.form-check-input { border-color: var(--lonky-grey-200); }
.form-check:not(.form-switch) .form-check-input[type=checkbox] { border-radius: 2px; }
.form-check-input:checked { background-color: var(--lonky-violet-500); border-color: var(--lonky-violet-500); }
.form-check-input:focus { border-color: var(--lonky-violet-500); box-shadow: var(--lonky-focus-ring); }
.form-switch .form-check-input:checked { background-color: var(--lonky-violet-500); border-color: var(--lonky-violet-500); }

.btn:focus-visible { outline: none; box-shadow: var(--lonky-focus-ring); }

/* ============================================================================
   SUPERFICIES (cards)
   ============================================================================ */
.card {
    --bs-card-border-color: var(--lonky-border);
    --bs-card-border-radius: var(--lonky-radius-form);
    border-radius: var(--lonky-radius-form);
}
/* Sin sombra: la card se separa del fondo por su borde y su superficie, que es como el kit
   construye la profundidad. `.shadow-sm` de Bootstrap se neutraliza acá porque está puesta en
   decenas de vistas y sacarla de todas sería un cambio masivo sin beneficio. */
.card.shadow-sm, .shadow-sm { box-shadow: none !important; border: 1px solid var(--lonky-border) !important; }

/* ============================================================================
   TABLAS (exacto del kit: header sand r8, SIN uppercase, filas 50px, divisor sutil)
   ============================================================================ */
.table {
    --bs-table-color: var(--lonky-grey-900);
    --bs-table-border-color: var(--lonky-border);
    margin-bottom: 0;
}
.table > thead th {
    background-color: var(--lonky-sand);
    color: rgba(29, 34, 43, .65);
    font-size: var(--lonky-text-xs); font-weight: 400; text-transform: none; letter-spacing: 0;
    padding: var(--lonky-space-10) var(--lonky-space-20); border-bottom: none;
}
.table > thead tr th:first-child { border-top-left-radius: var(--lonky-radius-table); border-bottom-left-radius: var(--lonky-radius-table); }
.table > thead tr th:last-child  { border-top-right-radius: var(--lonky-radius-table); border-bottom-right-radius: var(--lonky-radius-table); }
.table > tbody > tr > td { padding: var(--lonky-space-12) var(--lonky-space-20); border-bottom: .5px solid rgba(0, 0, 0, .1); vertical-align: middle; font-size: var(--lonky-text-base); }
.table-hover > tbody > tr:hover > * { --bs-table-bg-state: var(--lonky-sand); }
/* Acciones de fila: respetar la alineación derecha de la celda (text-end) */
td.text-end > .d-flex { justify-content: flex-end; }
.table td .d-flex form { margin: 0; }

/* ============================================================================
   BADGES / STATUS (estilo pill neutra + punto de color)
   ============================================================================ */
.badge {
    display: inline-flex; align-items: center; gap: var(--lonky-space-8);
    background-color: var(--lonky-sand) !important; color: var(--lonky-grey-900) !important;
    font-size: var(--lonky-text-sm); font-weight: 500; padding: var(--lonky-space-4) var(--lonky-space-10); border-radius: var(--lonky-radius-pill);
}
.badge::before {
    content: ""; width: 6px; height: 6px; border-radius: 50%;
    background-color: var(--badge-dot, var(--lonky-grey-400)); flex-shrink: 0;
}
/* El texto queda oscuro; el PUNTO toma el color semántico según la clase de fondo Bootstrap
   (cubre tanto .bg-* como las utilidades .text-bg-* de Bootstrap 5.3). */
.badge.bg-success,   .badge.text-bg-success  { --badge-dot: var(--lonky-badge-dot-success); }
.badge.bg-warning,   .badge.text-bg-warning  { --badge-dot: var(--lonky-badge-dot-warning); }
.badge.bg-info,      .badge.text-bg-info     { --badge-dot: var(--lonky-badge-dot-info); }
.badge.bg-danger,    .badge.text-bg-danger   { --badge-dot: var(--lonky-red-500); }
.badge.bg-primary,   .badge.text-bg-primary  { --badge-dot: var(--lonky-violet-500); }
.badge.bg-secondary, .badge.text-bg-secondary,
.badge.bg-light,     .badge.text-bg-light,
.badge.bg-dark,      .badge.text-bg-dark     { --badge-dot: var(--lonky-grey-400); }

/* ---- Alerts con radius del DS ---- */
.alert { --bs-alert-border-radius: var(--lonky-radius-input); border-radius: var(--lonky-radius-input); }
.dropdown-menu { --bs-dropdown-border-color: var(--lonky-border); border-radius: var(--lonky-radius-input); box-shadow: none; }
.dropdown-item.active, .dropdown-item:active { background-color: var(--lonky-violet-50); color: var(--lonky-link); }
.dropdown-item:hover, .dropdown-item:focus { background-color: var(--lonky-grey-50); }

/* Menú de acciones de una fila (listados de Pedidos y Transacciones).
   El panel scrollea en vez de crecer sin límite, como pide el kit para el DROPDOWN: con todos
   los estados del comercio adentro llega a medir 580px, y en un portátil eso deja los últimos
   ítems fuera de la pantalla — visibles en el HTML e imposibles de apretar (lo midió
   `AccionesRapidasBrowserTest`). El tope es relativo a la ventana y no fijo: en un monitor
   grande el menú entra entero y no aparece ninguna barra.

   El z-index va por encima de `.lonky-topbar` (1020) y no con el 1000 que trae Bootstrap: el
   menú de una fila del medio de la tabla se abre hacia ARRIBA, y con el valor por defecto sus
   primeros ítems quedaban debajo de la barra superior — se veían, no se podían apretar. Sigue
   por debajo del backdrop de los modales (1050), que es lo correcto. */
.lonky-row-actions + .dropdown-menu {
    max-height: 60vh;
    overflow-y: auto;
    --bs-dropdown-zindex: 1030;
    z-index: 1030;
}
/* El panel scrollea, pero abre SIEMPRE arriba: el reset de `scrollTop` vive en el JS del
   componente. Sin él, el navegador lo scrollea solo hasta el estado actual (`.active`) y el
   menú se abre por el medio, con «Reimprimir ticket», «Ver ticket» y «Comprobante» fuera de
   cuadro. Se vio en producción el 2026-09-04, con los tests en verde: medían el ítem DESPUÉS
   de un scrollIntoView. Lo cubre `test_al_abrirlo_lo_primero_que_se_ve_son_las_acciones`. */

/* ---- Código / slug inline (evita el rosa por defecto de Bootstrap) ---- */
code, .text-monospace code {
    color: var(--lonky-grey-500); background: var(--lonky-grey-50); padding: var(--lonky-space-2) var(--lonky-space-6);
    border-radius: 4px; font-size: var(--lonky-text-base);
}

/* ---- Acciones de fila estilo "row-actions" (Editar | Eliminar) del listado ---- */
.quick-links { display: flex; align-items: center; gap: var(--lonky-space-8); font-size: var(--lonky-text-sm); }
.quick-links a, .quick-links button {
    background: none; border: none; padding: 0; font-size: var(--lonky-text-sm); line-height: 1.4;
    color: var(--lonky-link); text-decoration: none; cursor: pointer;
}
.quick-links a:hover, .quick-links button:hover { text-decoration: underline; }
.quick-links button[type="submit"] { color: var(--lonky-red-500); }  /* Eliminar destructivo */
.quick-links .sep { color: var(--lonky-grey-200); }

/* ============================================================================
   SHELL — Sidebar + Topbar (Fase 2 del design system)
   ============================================================================ */
.lonky-shell { min-height: 100vh; }

/* ---- Sidebar ---- */
.lonky-sidebar {
    position: fixed; top: 0; left: 0; bottom: 0; width: 239px; z-index: 1030;
    background: var(--lonky-sand); border-right: 1px solid var(--lonky-border);
    display: flex; flex-direction: column; transition: width .18s ease;
}
.is-collapsed .lonky-sidebar { width: 77px; }

.lonky-sidebar-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--lonky-space-16) var(--lonky-space-16); border-bottom: 1px solid var(--lonky-grey-100); min-height: 70px;
}
.lonky-brand {
    display: flex; align-items: center; gap: var(--lonky-space-10); text-decoration: none; overflow: hidden;
    color: var(--lonky-link); font-family: var(--lonky-font-title); font-size: var(--lonky-text-xl); font-weight: 600;
}
.lonky-brand i { font-size: var(--lonky-text-2xl); flex-shrink: 0; }
.lonky-brand-text { color: var(--lonky-text); white-space: nowrap; }
.lonky-brand-text b { color: var(--lonky-link); }
/* El isotipo del sidebar va en violeta y el nombre en tinta: al revés que en el chrome Obsidian
   del super admin, donde va todo blanco. Es la misma marca leída sobre fondos opuestos. */
.lonky-brand .lonky-logo { color: var(--lonky-link); }
.lonky-brand .lonky-logo-texto { color: var(--lonky-text); font-size: var(--lonky-text-xl); }
.lonky-collapse-btn {
    width: 26px; height: 26px; flex-shrink: 0; align-items: center; justify-content: center;
    background: var(--lonky-surface); border: 1px solid var(--lonky-grey-200); border-radius: 6px;
    color: var(--lonky-grey-500); cursor: pointer;
}
.lonky-collapse-btn:hover { color: var(--lonky-link); border-color: var(--lonky-violet-200); }

/* ---- Nav ---- */
.lonky-nav { flex: 1 1 auto; overflow-y: auto; padding: var(--lonky-space-12) 0; }
.lonky-nav-section { padding: var(--lonky-space-4) 0 var(--lonky-space-16); }
.lonky-nav-section + .lonky-nav-section { border-top: 1px solid var(--lonky-grey-100); padding-top: var(--lonky-space-12); }
.lonky-nav-label {
    display: block; padding: 0 var(--lonky-space-20) var(--lonky-space-6); font-size: var(--lonky-text-caption); font-weight: 600;
    text-transform: uppercase; letter-spacing: .04em; color: var(--lonky-label);
}
.lonky-nav-item {
    display: flex; align-items: center; gap: var(--lonky-space-10);
    padding: var(--lonky-space-8) var(--lonky-space-14); margin: var(--lonky-space-2) var(--lonky-space-8); border-radius: var(--lonky-radius-sm);
    color: var(--lonky-text-muted); font-size: var(--lonky-text-base); font-weight: 500;
    text-decoration: none; border: none; background: none; white-space: nowrap; cursor: pointer;
}
.lonky-nav-item i { font-size: var(--lonky-text-xl); flex-shrink: 0; width: 18px; text-align: center; }
.lonky-nav-item:hover { background: var(--lonky-hover-bg); color: var(--lonky-text); }
.lonky-nav-item.is-active { background: var(--lonky-active-bg); color: var(--lonky-active-fg); font-weight: 600; }
.lonky-nav-item.is-active i { color: var(--lonky-active-fg); }
.lonky-nav-danger { color: var(--lonky-red-500); }
.lonky-nav-danger:hover { background: var(--lonky-red-25); color: var(--lonky-red-500); }

/* ---- Grupos desplegables (acordeón) ---- */
.lonky-nav-parent { width: calc(100% - 16px); text-align: left; }
.lonky-nav-parent .lonky-nav-text { flex: 1 1 auto; }
.lonky-nav-caret {
    font-size: var(--lonky-text-xs) !important; width: 12px !important;
    transition: transform .18s ease; opacity: .6;
}
.lonky-nav-group.is-open .lonky-nav-caret { transform: rotate(180deg); }
/* Con el grupo cerrado, un hijo activo se señala en el padre. */
.lonky-nav-parent.has-active-child { color: var(--lonky-text); font-weight: 600; }
.lonky-nav-group.is-open .lonky-nav-parent { color: var(--lonky-text); }

/* Colapso animado sin JS de alturas: el grid va de 0fr a 1fr. Requiere que el
   submenú tenga UN solo hijo (.lonky-submenu-inner) — con los ítems sueltos solo
   colapsa la primera fila. */
.lonky-submenu {
    display: grid; grid-template-rows: 0fr;
    transition: grid-template-rows .18s ease;
}
.lonky-nav-group.is-open .lonky-submenu { grid-template-rows: 1fr; }
.lonky-submenu-inner { min-height: 0; overflow: hidden; }
/* El título solo se usa en el flyout del sidebar colapsado. */
.lonky-submenu-title { display: none; }

.lonky-nav-child {
    font-size: var(--lonky-text-sm); padding: var(--lonky-space-6) var(--lonky-space-14) var(--lonky-space-6) var(--lonky-space-48); margin: var(--lonky-space-2) var(--lonky-space-8);
    position: relative; overflow: hidden;
}
/* Guía vertical que ata los hijos al padre. */
.lonky-nav-child::before {
    content: ''; position: absolute; left: 26px; top: 0; bottom: 0;
    width: 2px; background: var(--lonky-grey-100);
}
.lonky-nav-child.is-active::before { background: var(--lonky-active-fg); }

/* ---- Ítem "próximamente": inerte, sin afordancia de click ---- */
.lonky-nav-item.is-soon { opacity: .5; cursor: default; }
.lonky-nav-item.is-soon:hover { background: none; color: var(--lonky-text-muted); }
.lonky-nav-badge {
    font-size: var(--lonky-text-caption); font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
    padding: var(--lonky-space-2) var(--lonky-space-6); border-radius: 999px;
    background: var(--lonky-grey-100); color: var(--lonky-label); margin-left: auto;
}

/* ---- Footer del sidebar ---- */
.lonky-sidebar-footer { padding: var(--lonky-space-10) 0 var(--lonky-space-14); border-top: 1px solid var(--lonky-grey-100); }
.lonky-version { text-align: center; font-size: var(--lonky-text-label); color: var(--lonky-grey-300); margin-top: var(--lonky-space-4); }

/* ---- Colapsado: ocultar textos ---- */
.is-collapsed .lonky-nav-text,
.is-collapsed .lonky-brand-text,
/* Colapsado queda sólo el isotipo, que es exactamente para lo que sirve tener uno: el kit lo
   fija como la variante de menos de 64px. `.lonky-brand-text` de arriba es la clase vieja del
   sidebar; se deja porque otras vistas todavía la usan. */
.is-collapsed .lonky-logo-texto,
.is-collapsed .lonky-version { display: none; }
/* Los títulos de sección NO se achican: se van y dejan una línea.
   A 77px de ancho, "OPERACIÓN" no entra ni a 10px — se cortaba en "OPERACIÓ", y una palabra
   cortada comunica menos que una línea divisoria, que al menos separa los grupos. */
.is-collapsed .lonky-nav-label {
    display: block;
    height: 0; padding: 0; margin: var(--lonky-space-12) var(--lonky-space-16);
    overflow: hidden;
    border-top: 1px solid var(--lonky-border);
}
.is-collapsed .lonky-nav-item { justify-content: center; padding: var(--lonky-space-8); margin: var(--lonky-space-2) var(--lonky-space-12); }

/* El header se APILA en colapsado, y no es cosmético: en fila, el isotipo (26px), su gap (10) y
   el botón de colapsar (26) suman 62 sobre 61 útiles, y ese pixel mete una barra de scroll
   horizontal en una columna de 77px. Apilado entra cualquiera de los dos tamaños que vengan. */
.is-collapsed .lonky-sidebar-header {
    flex-direction: column;
    justify-content: center;
    gap: var(--lonky-space-10);
    padding: var(--lonky-space-12) var(--lonky-space-8);
    /* El `min-height: 70px` del header es para la fila; apilado, isotipo + gap + botón + padding
       suman 86 y el logo quedaba cortado por arriba. `auto` lo deja crecer lo que necesite. */
    min-height: auto;
}
.is-collapsed .lonky-brand { justify-content: center; }

/* Red de seguridad: nada dentro del sidebar puede scrollear de costado. Es una columna de 77px
   —cualquier barra horizontal ahí se come un tercio del ancho útil— y basta un ícono un poco más
   grande que el anterior para provocarla, que es exactamente lo que pasó. */
.lonky-sidebar { overflow-x: hidden; }

/* ---- Colapsado: los submenús pasan a flyout al hover ----
   position: fixed para escapar del overflow-y del .lonky-nav; el top lo calcula
   Alpine al entrar el mouse (--lonky-flyout-top) porque el sidebar scrollea. */
.is-collapsed .lonky-nav-caret,
.is-collapsed .lonky-nav-badge { display: none; }
/* En el flyout sí hay lugar para el badge: se recupera. */
.is-collapsed .lonky-submenu .lonky-nav-badge { display: inline-block; }
/* Un <button> NO se estira como un <a>, aunque los dos lleven `display: flex`.
   Es la trampa: un <a> es block-level y ocupa la fila entera, mientras un <button> tiene ancho
   INTRÍNSECO —mide lo que su contenido— y queda pegado al margen izquierdo. Con el sidebar
   expandido no se nota porque todo arranca por la izquierda; colapsado, sus hermanos <a> centran
   el ícono en 77px y él no, y los íconos dejan de compartir eje.
   Apunta a `button.lonky-nav-item` y no a `.lonky-nav-parent`: el mismo defecto lo tenía el
   botón de cerrar sesión, que vive en el footer y ninguna regla del nav alcanzaba. */
.is-collapsed button.lonky-nav-item { width: calc(100% - var(--lonky-space-12) * 2); }

/* La barra de scroll del nav no puede correr los íconos.
   El navegador dibuja la barra sobre el borde derecho, así que se lleva 10 de los 76px útiles y
   deja el área de contenido centrada respecto a lo que sobra, no respecto a la columna: todo el
   nav queda unos pixeles a la izquierda del footer, que no scrollea. En 77px se ve.
   `scrollbar-gutter: stable both-edges` reserva el mismo hueco de los DOS lados y el contenido
   vuelve al eje. Se usa esto y no un ancho de barra a mano por dos razones: `scrollbar-width:
   thin` le gana al `width` de `::-webkit-scrollbar` en el Chrome actual —así que adelgazarla no
   es una opción real— y esto no depende de cuánto mida la barra en cada navegador. */
.is-collapsed .lonky-nav { scrollbar-gutter: stable both-edges; }

.is-collapsed .lonky-submenu {
    display: none; position: fixed;
    left: 77px; top: var(--lonky-flyout-top, 0);
    min-width: 208px; padding: var(--lonky-space-8) 0;
    background: var(--lonky-surface, var(--lonky-surface));
    border: 1px solid var(--lonky-grey-100);
    border-radius: var(--lonky-radius-sm);
    box-shadow: none;   /* flota, pero se separa por su borde (kit: sin sombras) */
    z-index: 1050;
}
/* Se abre por hover y también por foco de teclado, para no dejar afuera al tab. */
.is-collapsed .lonky-nav-group:hover .lonky-submenu,
.is-collapsed .lonky-nav-group:focus-within .lonky-submenu { display: block; }

.is-collapsed .lonky-submenu-title {
    display: block; padding: var(--lonky-space-2) var(--lonky-space-16) var(--lonky-space-8); margin-bottom: var(--lonky-space-4);
    border-bottom: 1px solid var(--lonky-grey-100);
    font-size: var(--lonky-text-caption); font-weight: 600; text-transform: uppercase;
    letter-spacing: .04em; color: var(--lonky-label);
}
/* Dentro del flyout los hijos vuelven a comportarse como ítems normales. */
.is-collapsed .lonky-submenu .lonky-nav-text { display: block; }
.is-collapsed .lonky-submenu .lonky-nav-item {
    justify-content: flex-start; padding: var(--lonky-space-6) var(--lonky-space-16); margin: var(--lonky-space-2) var(--lonky-space-6);
}
.is-collapsed .lonky-submenu .lonky-nav-child::before { display: none; }

/* ---- Main + Topbar ---- */
.lonky-main { margin-left: 239px; min-height: 100vh; display: flex; flex-direction: column; transition: margin-left .18s ease; }
.is-collapsed .lonky-main { margin-left: 77px; }

.lonky-topbar {
    height: 70px; position: sticky; top: 0; z-index: 1020;
    background: var(--lonky-surface); border-bottom: 1px solid rgba(0, 0, 0, .08);
    display: flex; align-items: center; gap: var(--lonky-space-12); padding: 0 var(--lonky-space-24);
}
.lonky-page-title { font-family: var(--lonky-font-title); font-size: var(--lonky-text-xl); font-weight: 500; margin: 0; color: var(--lonky-text); }
.lonky-burger { background: none; border: none; font-size: var(--lonky-text-2xl); color: var(--lonky-grey-600); }

/* ============================================================================
   CAMPANITA DE PROCESOS
   Dos estados en la misma pieza: en reposo una campana con el conteo de avisos sin leer;
   con algo corriendo, un anillo de progreso con el porcentaje adentro.
   ============================================================================ */
.lonky-process-bell { position: relative; margin-left: auto; }
.lonky-topbar-user { position: relative; }

.lonky-bell-btn {
    display: flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; padding: 0;
    background: none; border: 1px solid transparent; border-radius: var(--lonky-radius-sm);
    color: var(--lonky-text-muted); cursor: pointer;
    transition: background-color .15s, color .15s;
}
.lonky-bell-btn:hover { background: var(--lonky-hover-bg); color: var(--lonky-text); }
.lonky-bell-btn:focus-visible { outline: none; box-shadow: var(--lonky-focus-ring); }

.lonky-bell-idle, .lonky-bell-busy { position: relative; display: flex; align-items: center; justify-content: center; }

/* El toggle de tema es hermano de la campanita y comparte su caja: dos botones del mismo tamaño
   en la topbar, sin fondo hasta que se los toca. */
.lonky-theme-toggle {
    display: flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; padding: 0;
    background: none; border: 1px solid transparent; border-radius: var(--lonky-radius-sm);
    color: var(--lonky-text-muted); cursor: pointer;
    transition: background-color .15s, color .15s;
}
.lonky-theme-toggle:hover { background: var(--lonky-hover-bg); color: var(--lonky-text); }
.lonky-theme-toggle:focus-visible { outline: none; box-shadow: var(--lonky-focus-ring); }
/* Qué ícono se ve sale de `data-theme`, sin JS de por medio: en claro se ofrece la luna ("pasar
   a oscuro") y en oscuro el sol. El botón muestra lo correcto desde el primer pintado.

   Los selectores llevan `.lonky-theme-toggle` adelante por especificidad, no por prolijidad: sin
   él, `.lonky-theme-toggle span` (clase + elemento) le gana a `.lonky-theme-icono-claro` (clase
   sola) y se ven los DOS íconos a la vez. Pasó, y ningún test lo cazó — se vio en una captura. */
.lonky-theme-toggle .lonky-theme-icono-oscuro,
.lonky-theme-toggle .lonky-theme-icono-claro { display: flex; }
.lonky-theme-toggle .lonky-theme-icono-claro { display: none; }
[data-theme="dark"] .lonky-theme-toggle .lonky-theme-icono-claro { display: flex; }
[data-theme="dark"] .lonky-theme-toggle .lonky-theme-icono-oscuro { display: none; }

/* El contador va sobre la campana, no al lado: la topbar es angosta en un notebook. */
.lonky-bell-badge {
    position: absolute; top: -6px; right: -8px; min-width: 17px; height: 17px;
    padding: 0 var(--lonky-space-4); border-radius: var(--lonky-radius-pill);
    background: var(--lonky-error-relleno); color: var(--lonky-on-accent);
    font-family: var(--lonky-font-mono); font-size: var(--lonky-text-caption); font-weight: 500;
    line-height: 17px; text-align: center;
}

/* El anillo empieza a las 12 en punto y avanza como un reloj; sin el rotate arranca a las 3. */
.lonky-bell-ring { transform: rotate(-90deg); }
.lonky-bell-ring-track { stroke: var(--lonky-border); }
.lonky-bell-ring-fill { stroke: var(--lonky-active-fg); transition: stroke-dashoffset .4s ease; }
.lonky-bell-pct {
    position: absolute; font-family: var(--lonky-font-mono);
    font-size: var(--lonky-text-caption); font-weight: 500; color: var(--lonky-active-fg);
}

.lonky-bell-menu {
    position: absolute; right: 0; top: calc(100% + 8px);
    min-width: 320px; max-width: 380px; max-height: 420px; overflow-y: auto; z-index: 1040;
    background: var(--lonky-surface); border: 1px solid var(--lonky-border);
    border-radius: var(--lonky-radius); padding: var(--lonky-space-6);
}

.lonky-bell-activo { padding: var(--lonky-space-10) var(--lonky-space-12); border-bottom: 1px solid var(--lonky-grey-100); }
.lonky-bell-titulo { font-size: var(--lonky-text-sm); font-weight: 600; color: var(--lonky-text); }
.lonky-bell-barra {
    height: 4px; margin: var(--lonky-space-8) 0 var(--lonky-space-6); border-radius: var(--lonky-radius-pill);
    background: var(--lonky-grey-100); overflow: hidden;
}
.lonky-bell-barra-fill {
    height: 100%; background: var(--lonky-active-fg);
    border-radius: var(--lonky-radius-pill); transition: width .4s ease;
}
.lonky-bell-detalle { font-family: var(--lonky-font-mono); font-size: var(--lonky-text-label); color: var(--lonky-text-muted); }
.lonky-bell-fallidos { color: var(--lonky-error-relleno); }
.lonky-bell-cuando { font-size: var(--lonky-text-label); color: var(--lonky-label); margin-top: var(--lonky-space-2); }

.lonky-bell-reciente {
    display: flex; align-items: flex-start; gap: var(--lonky-space-8);
    padding: var(--lonky-space-8) var(--lonky-space-12); border-bottom: 1px solid var(--lonky-grey-100);
}
.lonky-bell-reciente:last-child { border-bottom: none; }
.lonky-bell-reciente .lonky-bell-titulo { display: block; }
.lonky-bell-resumen { display: block; font-size: var(--lonky-text-xs); color: var(--lonky-text-muted); }
.lonky-bell-cuando { margin-left: auto; white-space: nowrap; }

/* Punto de estado: el color es el único dato que distingue una corrida limpia de una con
   problemas cuando se mira de reojo. */
.lonky-bell-estado { width: 8px; height: 8px; margin-top: var(--lonky-space-4); border-radius: 50%; flex-shrink: 0; }
.lonky-bell-estado.is-completed { background: var(--lonky-success); }
.lonky-bell-estado.is-completed_with_errors { background: var(--lonky-warning); }
.lonky-bell-estado.is-failed { background: var(--lonky-error); }
.lonky-bell-estado.is-cancelled { background: var(--lonky-grey-400); }

.lonky-bell-vacio { padding: var(--lonky-space-16) var(--lonky-space-12); text-align: center; font-size: var(--lonky-text-sm); color: var(--lonky-text-muted); }

/* Los recientes son links a su detalle; el hover lo tiene que decir. */
a.lonky-bell-reciente { color: inherit; text-decoration: none; }
a.lonky-bell-reciente:hover { background: var(--lonky-hover-bg); }

.lonky-bell-footer {
    display: block; padding: var(--lonky-space-10) var(--lonky-space-12); margin-top: var(--lonky-space-4);
    border-top: 1px solid var(--lonky-border);
    color: var(--lonky-active-fg); font-size: var(--lonky-text-sm); font-weight: 500;
    text-align: center; text-decoration: none;
}
.lonky-bell-footer:hover { background: var(--lonky-active-bg); }

/* Estado de una corrida en la bitácora: reusa el punto de color de la campanita para que el
   mismo estado se vea igual en los dos lugares. */
.lonky-proceso-estado {
    display: inline-flex; align-items: center; gap: var(--lonky-space-6);
    font-size: var(--lonky-text-sm); color: var(--lonky-text);
}

/* El texto crudo de la excepción: colapsado por defecto y en mono, porque es para leer un
   código de error, no una frase. */
.lonky-proceso-raw { margin-top: var(--lonky-space-6); }
.lonky-proceso-raw summary {
    cursor: pointer; font-size: var(--lonky-text-xs); color: var(--lonky-text-muted);
    list-style: none;
}
.lonky-proceso-raw summary::-webkit-details-marker { display: none; }
.lonky-proceso-raw summary::before { content: '▸ '; }
.lonky-proceso-raw[open] summary::before { content: '▾ '; }
.lonky-proceso-raw code {
    display: block; margin-top: var(--lonky-space-4); padding: var(--lonky-space-8) var(--lonky-space-10);
    background: var(--lonky-grey-50); border-radius: var(--lonky-radius-sm);
    font-size: var(--lonky-text-label); color: var(--lonky-text-muted); word-break: break-word;
}

/* Fila de tabla que linkea entera: en una bitácora, obligar a apuntarle a un link chico es
   hostil. El cursor lo anuncia. */
.lonky-fila-clickeable { cursor: pointer; }
.lonky-user-btn {
    display: flex; align-items: center; gap: var(--lonky-space-8); background: none; border: none;
    color: var(--lonky-text); font-family: var(--lonky-font-title); font-weight: 500; cursor: pointer;
}
.lonky-avatar {
    width: 36px; height: 36px; border-radius: 999px; background: var(--lonky-violet-500); color: var(--lonky-on-accent);
    display: flex; align-items: center; justify-content: center; font-weight: 600; flex-shrink: 0;
}
.lonky-user-btn .bi-chevron-down { font-size: var(--lonky-text-base); color: var(--lonky-grey-400); }
.lonky-user-menu {
    position: absolute; right: 0; top: calc(100% + 8px); min-width: 220px; z-index: 1040;
    background: var(--lonky-surface); border: 1px solid var(--lonky-grey-200); border-radius: var(--lonky-radius);
    box-shadow: none;   /* ídem: borde, no sombra */
    padding: var(--lonky-space-6); overflow: hidden;
}
/* El mail va con --lonky-text-muted y no con grey-400: es TEXTO de 12px, y grey-400 no llega al
   4.5:1 que pide WCAG para leer. grey-400 quedó reservado a íconos y bordes. */
.lonky-user-email { padding: var(--lonky-space-8) var(--lonky-space-12); font-size: var(--lonky-text-xs); color: var(--lonky-text-muted); border-bottom: 1px solid var(--lonky-grey-100); margin-bottom: var(--lonky-space-4); }
.lonky-user-menu-item {
    display: flex; align-items: center; width: 100%; padding: var(--lonky-space-8) var(--lonky-space-12); border: none; background: none;
    border-radius: var(--lonky-radius-sm); color: var(--lonky-text); font-size: var(--lonky-text-base); cursor: pointer;
}
.lonky-user-menu-item:hover { background: var(--lonky-hover-bg); }

/* ---- Contenido ---- */
.lonky-content { flex: 1 1 auto; padding: var(--lonky-space-24); }

/* ---- Overlay móvil ---- */
.lonky-overlay { display: none; }

/* ---- Responsive: sidebar off-canvas en < lg ---- */
@media (max-width: 991.98px) {
    .lonky-sidebar { transform: translateX(-100%); transition: transform .2s ease; width: 239px; }
    /* En móvil el sidebar sale sobre el contenido: lo que lo separa es el overlay oscuro
       de atrás y su borde derecho, no una sombra. */
    .mobile-open .lonky-sidebar { transform: translateX(0); border-right: 1px solid var(--lonky-border); }
    .lonky-main { margin-left: 0 !important; }
    .lonky-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, .4); z-index: 1029; }
    .mobile-open .lonky-overlay { display: block; }
    /* en móvil no colapsamos, mostramos full */
    .is-collapsed .lonky-nav-text, .is-collapsed .lonky-brand-text, .is-collapsed .lonky-version { display: revert; }
    .is-collapsed .lonky-nav-item { justify-content: flex-start; padding: var(--lonky-space-8) var(--lonky-space-14); }
}

/* ============================================================================
   Layout POS fullscreen (modo inmersivo, sin sidebar)
   ============================================================================ */
/* touch-action: manipulation desactiva el doble-tap-para-zoom de iOS (molesto al tocar
   rápido el +/- de los steppers) SIN romper el pinch-zoom de accesibilidad. */
.lonky-pos-body { background: var(--lonky-bg); touch-action: manipulation; }
.lonky-pos-wrap { padding: var(--lonky-space-12) var(--lonky-space-16); min-height: 100vh; }
.lonky-pos-flash { position: fixed; top: 12px; right: 12px; z-index: 1080; max-width: 360px; }

/* ============================================================================
   Accesibilidad + gaps del DS (Fase 5 — hallazgos de la auditoría UX/UI)
   ============================================================================ */
/* Foco de teclado visible en todos los interactivos del shell y del POS */
.lonky-nav-item:focus-visible, .lonky-collapse-btn:focus-visible, .lonky-burger:focus-visible,
.lonky-user-btn:focus-visible, .lonky-user-menu-item:focus-visible, .lonky-brand:focus-visible,
.lonky-tab:focus-visible, .pos-card:focus-visible, .pos-btn:focus-visible, .pos-btn-pay:focus-visible,
.pos-line-edit:focus-visible, .pos-line-del:focus-visible {
    outline: none; box-shadow: var(--lonky-focus-ring); border-radius: var(--lonky-radius-sm);
}
/* Addons de input-group ($ , unidades) tematizados */
.input-group-text {
    background: var(--lonky-sand); border-color: var(--lonky-border); color: var(--lonky-text-muted);
    border-radius: var(--lonky-radius-input);
}
/* Textarea del DS: alto propio y texto arriba */
textarea.form-control { min-height: 74px; padding: var(--lonky-space-8) var(--lonky-space-12); }
/* Paginación de Bootstrap 5 tematizada al DS (activo lavanda, radius 4, foco anillo) */
.pagination {
    --bs-pagination-color: var(--lonky-grey-500); --bs-pagination-hover-color: var(--lonky-violet-500);
    --bs-pagination-hover-bg: var(--lonky-grey-50); --bs-pagination-focus-box-shadow: var(--lonky-focus-ring);
    --bs-pagination-active-bg: var(--lonky-violet-50); --bs-pagination-active-color: var(--lonky-violet-500);
    --bs-pagination-active-border-color: var(--lonky-grey-200); --bs-pagination-border-color: var(--lonky-grey-200);
    --bs-pagination-border-radius: var(--lonky-radius-input); gap: var(--lonky-space-4);
}
/* Respetar prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { transition-duration: .001ms !important; animation-duration: .001ms !important; }
}

/* ============================================================================
   Cards y líneas de producto — compartidas
   ----------------------------------------------------------------------------
   Nacieron en el POS y las reusa el selector de productos de las transferencias
   de stock (mismo gesto: elegir de una grilla visual y ver lo elegido al costado).
   Viven acá y no en un @push del POS justamente para poder reusarlas.
   ============================================================================ */

/* Stepper de cantidad */
.qty-stepper .btn { background-color: var(--lonky-violet-500); border-color: var(--lonky-violet-500); color: var(--lonky-on-accent); }
.qty-stepper .btn:hover { background-color: var(--lonky-violet-600); border-color: var(--lonky-violet-600); }
.qty-stepper .form-control { border-color: var(--lonky-violet-500); font-weight: 600; }
/* Las flechitas nativas del input number ya no se dibujan en ninguna parte del panel: la regla
   vive con el resto de los inputs, más arriba. Acá sobraba desde entonces. */

/* Cards de producto: imagen grande + barra de nombre/precio abajo */
.pos-card {
    position: relative; border: 2px solid var(--lonky-grey-200); border-radius: 6px;
    overflow: hidden; cursor: pointer; aspect-ratio: 1 / 1; background: var(--lonky-grey-50);
    transition: border-color .12s;   /* ya no hay sombra que animar: el hover cambia el borde */
}
.pos-card:hover { border-color: var(--lonky-violet-300); }
.pos-card.in-cart { border-color: var(--lonky-violet-500); box-shadow: 0 0 0 2px rgba(22,163,163,.25); }
.pos-card-disabled { opacity: .55; cursor: not-allowed; }
.pos-card-disabled:hover { box-shadow: none; }
.pos-card-img { width: 100%; height: 100%; background-size: cover; background-position: center; }
.pos-card-noimg { display: flex; align-items: center; justify-content: center; color: var(--lonky-grey-300); font-size: var(--lonky-text-3xl); }
.pos-card-badge {
    position: absolute; top: 6px; right: 6px; background: var(--lonky-violet-500); color: var(--lonky-on-accent);
    font-size: var(--lonky-text-label); padding: var(--lonky-space-2) var(--lonky-space-6); border-radius: 10px;
}
.pos-card-bar {
    position: absolute; left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,.6); color: var(--lonky-on-accent); padding: var(--lonky-space-4) var(--lonky-space-8);
    display: flex; justify-content: space-between; align-items: center; gap: var(--lonky-space-6);
}
.pos-card-name { font-size: var(--lonky-text-xs); line-height: 1.1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pos-card-price { font-size: var(--lonky-text-sm); font-weight: 700; white-space: nowrap; }

/* Líneas de lo elegido (carrito del POS / transferencia) */
.pos-line { display: flex; align-items: center; gap: var(--lonky-space-8); padding: var(--lonky-space-8); border-bottom: 1px solid var(--lonky-grey-100); }
.pos-line-del { border: none; background: none; color: var(--lonky-grey-400); font-size: var(--lonky-text-xl); line-height: 1; padding: 0 var(--lonky-space-2); }
.pos-line-del:hover { color: var(--lonky-red-500); }
.pos-line-img { width: 46px; height: 46px; border-radius: 4px; background-size: cover; background-position: center; flex-shrink: 0; }
.pos-line-noimg { display: flex; align-items: center; justify-content: center; background: var(--lonky-grey-50); color: var(--lonky-grey-300); }
.pos-line-info { flex: 1 1 auto; min-width: 0; }
.pos-line-name { font-size: var(--lonky-text-base); font-weight: 600; line-height: 1.15; }
.pos-line-discount { color: var(--lonky-red-500); }
.pos-line-edit { border: none; background: none; color: var(--lonky-grey-400); padding: 0 var(--lonky-space-4); flex-shrink: 0; }
/* Chip de la lista de precio de la línea (ADR-026). Clickeable en simples y variaciones;
   apagado en curvas y combos, que tienen precio propio y ninguna lista los toca. */
.pos-line-list { border: none; background: none; padding: 0; font-size: var(--lonky-text-xs); font-weight: 600;
  color: var(--lonky-violet-500); display: inline-flex; align-items: center; }
.pos-line-list:hover { text-decoration: underline; }
.pos-line-list-fijo { color: var(--lonky-grey-400); cursor: default; }
.pos-line-list-fijo:hover { text-decoration: none; }
.pos-line-edit:hover { color: var(--lonky-link); }
.pos-line-unit { width: 90px; text-align: right; font-size: var(--lonky-text-xs); color: var(--lonky-grey-500); flex-shrink: 0; }
.pos-line-total { width: 96px; text-align: right; font-size: var(--lonky-text-base); font-weight: 700; flex-shrink: 0; }

/* Línea en mobile (<lg): 2 filas para que el nombre respire (en desktop la columna es ancha
   y entra todo en una fila; en 360px se comprimía letra por letra). */
@media (max-width: 991.98px) {
    .pos-line {
        display: grid;
        grid-template-columns: 52px minmax(0, 1fr) auto auto;
        grid-template-areas:
            "img  info  edit  del"
            "img  qty   unit  total";
        column-gap: var(--lonky-space-8);
        row-gap: var(--lonky-space-8);
        align-items: center;
        padding: var(--lonky-space-12) var(--lonky-space-10);
    }
    .pos-line-img   { grid-area: img; align-self: start; width: 52px; height: 52px; }
    .pos-line-info  { grid-area: info; min-width: 0; }
    .pos-line-name  { white-space: normal; font-size: var(--lonky-text-lg); line-height: 1.25; }
    /* 44x44 es el mínimo táctil (WCAG 2.5.5 / Apple HIG). Medidos en Chrome a 360px antes de
       esto: la × de quitar era 22x22, el lápiz 20x21 y las teclas ± del stepper 30x34 — entre la
       mitad y dos tercios del mínimo. Quitar el ítem equivocado con el cliente esperando es de
       las cosas que más ruido hacen en el mostrador, y el ícono no crece: crece su área de toque.
       Por eso van min-width/min-height y no font-size — el tamaño de letra lo fija el token. */
    .pos-line-edit  { grid-area: edit; align-self: start; justify-self: end;
                      min-width: 44px; min-height: 44px; display: flex; align-items: center; justify-content: center; }
    .pos-line-del   { grid-area: del; align-self: start; justify-self: end; font-size: var(--lonky-text-2xl);
                      min-width: 44px; min-height: 44px; display: flex; align-items: center; justify-content: center; }
    /* El ancho sale de las partes (44 + input + 44) en vez de un número fijo: con 112px los
       botones no llegaban al mínimo y agrandarlos los desbordaba fuera de la celda del grid. */
    .qty-stepper    { grid-area: qty; justify-self: start; width: auto !important; }
    .qty-stepper .btn { min-width: 44px; min-height: 44px; }
    .qty-stepper .form-control { min-height: 44px; }
    .pos-line-unit  { grid-area: unit; width: auto; text-align: right; align-self: center; }
    .pos-line-total { grid-area: total; width: auto; text-align: right; align-self: center; }
}

/* Selector de productos de las transferencias: dos paneles con scroll propio, mismo gesto
   que el POS pero dentro del panel (que sí scrollea) → alto acotado en vez de pantalla completa.
   El min-height:0 va SIEMPRE (no dentro de un media query): sin él, el contenido ignora la
   altura del panel y se derrama por debajo de la card — el mismo bug de flexbox del POS. */
.lonky-picker-panel > .card,
.lonky-picker-panel .card-body,
.lonky-picker-panel .overflow-auto { min-height: 0; }
.lonky-picker-panel > .card { overflow: hidden; }

@media (min-width: 992px) {
    .lonky-picker-panel { height: clamp(360px, calc(100vh - 420px), 620px); }
}
@media (max-width: 991.98px) {
    /* En mobile los paneles van uno abajo del otro. max-height (y no height) para que el
       tope acote la lista larga de productos sin dejar un panel vacío ocupando media
       pantalla de aire cuando todavía no elegiste nada. */
    .lonky-picker-panel { max-height: 60vh; }
}

/* ===================== POS responsive (Tanda 1) ===================== */

/* Modales del POS: caja centrada en desktop (ancho por --pos-modal-max), fullscreen en <lg. */
.lonky-pos-modal-dialog {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
}
.lonky-pos-modal {
    width: 100%;
    max-width: var(--pos-modal-max, 480px);
    max-height: 90vh;
    overflow: auto;
}
@media (max-width: 991.98px) {
    .lonky-pos-modal-dialog {
        align-items: stretch;
        /* 100dvh = altura REAL visible en iOS (100vh incluye la barra de Safari → footer cortado). */
        height: 100vh;
        height: 100dvh;
    }
    .lonky-pos-modal {
        max-width: none;
        /* 100% (del dialog, que es inset:0 = viewport exacto), NO 100vw: en iOS 100vw sobresale
           del viewport visual y provoca scroll horizontal de toda la página. */
        width: 100%;
        height: 100vh;
        height: 100dvh;
        max-height: 100vh;
        max-height: 100dvh;
        border-radius: 0;
        /* Card como columna: el body scrollea y el header/footer quedan fijos → los botones de
           acción (Agregar/Cancelar/Cobrar) siempre visibles, con contenido corto o largo. */
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }
    /* Solo scroll vertical en el cuerpo (evita el scroll horizontal del modal de variantes). */
    .lonky-pos-modal > .card-body { flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; min-height: 0; }
    .lonky-pos-modal > .card-header,
    .lonky-pos-modal > .card-footer { flex: 0 0 auto; }
    /* El título largo del header no debe empujar el ancho ni tapar la X: cede y envuelve. */
    .lonky-pos-modal > .card-header { gap: var(--lonky-space-8); }
    .lonky-pos-modal > .card-header > span { min-width: 0; overflow-wrap: anywhere; }
}

/* Confirmación de "entró al carrito": fija abajo y centrada, por encima de la barra del
   carrito en mobile. No tapa nada porque se va sola a los 2,5s. */
.lonky-pos-toast {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: 84px;            /* despeja la .lonky-pos-cartbar */
    z-index: 1030;
    /* Tope duro de 320px además del relativo: los nombres del catálogo son largos ("512 OXIDO
       FAJERO 3 BOTONES PUSH UP") y con sólo `100vw - 24px` el cartel se estiraba hasta desbordar
       en pantallas de 360. Con el tope, el texto envuelve en dos líneas y siempre entra. */
    max-width: min(calc(100vw - 24px), 320px);
    width: max-content;
}
@media (min-width: 992px) {
    /* En desktop no hay cartbar: va al borde. */
    .lonky-pos-toast { bottom: 24px; }
}

/* Barra de total fija (mobile): pegada abajo, total siempre visible + Cobrar. */
.lonky-pos-cartbar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 1020;
    display: flex;
    gap: var(--lonky-space-8);
    align-items: center;
    padding: var(--lonky-space-10) var(--lonky-space-12);
    background: var(--lonky-surface);
    border-top: 1px solid var(--lonky-border);   /* la barra se separa por su borde */
}
.lonky-pos-cartbar-summary {
    flex: 1 1 auto;
    display: flex; align-items: center; gap: var(--lonky-space-8);
    min-height: 44px;
    border: 0; background: transparent; text-align: left;
}
.lonky-pos-cartbar-pay { min-height: 44px; padding-inline: var(--lonky-space-20); font-weight: 600; }

.lonky-pos-cart-head { padding: var(--lonky-space-8) var(--lonky-space-8) 0; }

/* Carrito en mobile como "página completa": NO usa position:fixed (dio problemas de anclaje al
   viewport en iOS — se seguía viendo el catálogo detrás). En su lugar, cuando showCart=true se
   ocultan la topbar y el catálogo con .lonky-hide-when-cart y el carrito queda como ÚNICO
   contenido visible → ocupa toda la pantalla. Simple y a prueba de balas. */
@media (max-width: 991.98px) {
    /* display:none !important para ganarle al d-flex !important de Bootstrap. */
    .lonky-hide-when-cart { display: none !important; }
    /* iOS hace auto-zoom al enfocar un input/select con font-size < 16px: lo forzamos a 16px. */
    .lonky-pos-body input,
    .lonky-pos-body select,
    .lonky-pos-body textarea { font-size: var(--lonky-text-lg); }
    /* Aire abajo para que la barra de total fija no tape el final del catálogo. */
    .lonky-pos-wrap { padding-bottom: 76px; }
}

/* Layout de venta: alto fijo solo en desktop; fluido en mobile (una columna, scroll natural).
   En desktop la PÁGINA no scrollea: el scroll vive dentro de cada panel (catálogo y carrito),
   así los botones de cobro quedan siempre a la vista sin bajar hasta el fondo.
   El :has() acota todo esto al modo venta — el modo apertura de caja sigue con scroll natural. */
@media (min-width: 992px) {
    .lonky-pos-wrap:has(.lonky-pos-layout) {
        height: 100vh;
        height: 100dvh;
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }
    /* El div del modo venta envuelve topbar + layout: tiene que pasar el alto hacia abajo. */
    .lonky-pos-screen { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
    /* El layout se queda con el alto que sobra debajo de la topbar. Antes era un
       calc(100vh - 135px): el número mágico no sobrevivía a que la topbar wrapeara.
       flex-wrap:nowrap es imprescindible: .row de Bootstrap envuelve, y una línea envuelta
       toma el alto de su ítem más alto en vez del alto del contenedor — con wrap las dos
       columnas seguían creciendo con su contenido aunque el layout ya estuviera acotado. */
    .lonky-pos-layout { flex: 1 1 auto; min-height: 0; flex-wrap: nowrap; }
    /* min-height:0 en CADA eslabón de la cadena flex. Sin esto un flex item toma su
       min-height:auto (= el alto de su contenido), crece sin límite y el overflow-auto de
       adentro nunca llega a activarse: ese era el bug del catálogo derramándose sobre la
       página y empujando los botones de cobro a 2000px de scroll. */
    .lonky-pos-layout > [class*="col-"],
    .lonky-pos-layout .card,
    .lonky-pos-layout .card-body,
    .lonky-pos-layout .overflow-auto { min-height: 0; }
}

/* Filtros del catálogo: selects con ancho cómodo en desktop, colapsables en mobile. */
.lonky-pos-filter-select { min-width: 150px; }
@media (max-width: 991.98px) {
    .lonky-pos-filters { display: none; }
    .lonky-pos-filters.is-open { display: block; }
    .lonky-pos-filter-select { min-width: 0; flex: 1 1 100%; }
}

/* Recargo o descuento de la línea de pago: el selector de tipo y el valor conviven en una
   fila SOLO si entran. El grupo de botones nunca se comprime —"Descuento" recortado a
   "Descuen…" es peor que una fila más— y el valor es el que cede y baja.
   Bug real (2026-08-15): con los dos en columnas fijas de Bootstrap, en una notebook el
   botón "Recargo" salía cortado al medio. */
.lonky-pay-adj { display: flex; flex-wrap: wrap; gap: var(--lonky-space-8); }
.lonky-pay-adj-kind { flex: 0 0 auto; }
.lonky-pay-adj-kind .btn { white-space: nowrap; }
/* Sin max-width: cuando el valor baja a su propia fila tiene que llenarla, para quedar
   alineado con el resto del panel (cantidad, "A cobrar", el botón de añadir y el teclado
   miden todos lo mismo). Un input a media fila se lee como un control suelto. */
.lonky-pay-adj-value { flex: 1 1 10rem; min-width: 9rem; }

/* Modal de pago (Dividir pago) en mobile: apilar y aprovechar el alto del modal fullscreen
   (antes: col-4/col-8 apretadas arriba y medio modal vacío abajo). */
@media (max-width: 991.98px) {
    .lonky-pay-body { flex: 1 1 auto; min-height: 0; flex-direction: column; flex-wrap: nowrap; }
    /* Resumen en fila compacta arriba, no apilado a un costado. Envuelve en varias filas:
       con recargos y descuentos por medio de pago pueden convivir hasta 6 conceptos, y
       apretados en una sola fila quedan ilegibles. */
    .lonky-pay-summary {
        flex: 0 0 auto; display: flex; flex-wrap: wrap;
        justify-content: center; gap: var(--lonky-space-4) var(--lonky-space-14); text-align: center !important;
    }
    .lonky-pay-summary > div { margin-bottom: 0 !important; flex: 0 0 auto; min-width: 5.5rem; }
    /* El vuelto tiene un input: en fila con los demás queda impracticable, va a lo ancho. */
    .lonky-pay-summary > .lonky-pay-cash { flex: 1 1 100%; max-width: 16rem; }
    .lonky-pay-summary > .lonky-pay-total { flex: 1 1 100%; border-top: 1px solid var(--bs-border-color); padding-top: var(--lonky-space-6); }
    /* La columna principal ocupa el resto; el teclado crece y sus teclas son más altas/táctiles.
       `overflow-y: auto` NO es opcional: el modal padre es `overflow: hidden` con alto fijo
       (100dvh), así que sin scroll propio acá todo lo que excede la pantalla queda CORTADO e
       inalcanzable — no sólo apretado. Con dos medios de pago cargados eso dejaba el botón
       PAGAR fuera de alcance y la venta sin cobrar (exitojeans, 2026-08-25). El resumen queda
       afuera del scroll a propósito (`flex: 0 0 auto` acá arriba): Total, Pagado y Balance son
       lo que el cajero mira mientras carga cada medio. */
    .lonky-pay-main { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow-y: auto; overflow-x: hidden; }
    .lonky-pay-keypad { flex: 1 1 auto; }
    .lonky-pay-keypad .btn { padding-top: var(--lonky-space-14); padding-bottom: var(--lonky-space-14); font-size: var(--lonky-text-xl); }
}

/* ===================== Tablas responsive (Tanda 2) ===================== */

/* Desktop: fallback de scroll horizontal para tablas muy anchas. */
@media (min-width: 768px) {
    .lonky-rtable-wrap { overflow-x: auto; }
}

/* ===================== Grilla de tablas (todo el panel) =====================
   Las tablas se dividen en filas Y columnas, como una planilla: con solo líneas horizontales
   la vista "sigue de largo" y es fácil leer el número de la columna equivocada. Las líneas
   verticales son finas y en gris claro (no negras como un Excel) para sumar estructura sin
   ensuciar el diseño.

   Aplica a TODA tabla del panel. Escapes:
     .table-plain  → tabla sin grilla (layout, totales, casos donde las líneas estorban)
   En <768px no aplica: ahí las tablas del componente ya se vuelven cards y las verticales
   no tendrían sentido. Los mails y los PDF llevan su propio CSS y no pasan por acá. */
@media (min-width: 768px) {
    .table:not(.table-plain) {
        border: 1px solid var(--lonky-border);
        /* Sin este reset, el borde exterior queda tapado por el propio de Bootstrap. */
        border-collapse: separate;
        border-spacing: 0;
    }
    .table:not(.table-plain) > thead > tr > th {
        background: var(--lonky-grey-50);
        border-bottom: 2px solid var(--lonky-border);
        color: var(--lonky-grey-500);
        font-size: var(--lonky-text-xs);
        text-transform: uppercase;
        letter-spacing: .03em;
    }
    /* La línea vertical va en cada celda menos la primera de la fila: así no se duplica
       con el borde exterior de la tabla. */
    .table:not(.table-plain) > * > tr > * + * { border-left: 1px solid var(--lonky-grey-100); }
    .table:not(.table-plain) > * > tr > * { padding: var(--lonky-space-12) var(--lonky-space-14); }
    .table-sm:not(.table-plain) > * > tr > * { padding: var(--lonky-space-8) var(--lonky-space-10); }
    /* Celda de números: fondo apenas distinto para que el ojo siga la columna hacia abajo. */
    .table .rt-num { background: var(--lonky-grey-25); font-variant-numeric: tabular-nums; }
    /* El pie (totales) se despega del cuerpo. */
    .table:not(.table-plain) > tfoot > tr > * { border-top: 2px solid var(--lonky-border); background: var(--lonky-grey-25); }
}

/* ===================== Recuadros de cifras (stat boxes) ===================== */

/* Cuatro números en fila se leen como un renglón corrido si no tienen caja propia. Cada cifra
   va encuadrada, con la etiqueta arriba y el número grande abajo: se lee de un vistazo sin
   saber usar sistemas. Grid y no columnas de Bootstrap para que todas queden del mismo alto. */
.lonky-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--lonky-space-12);
}
@media (max-width: 575.98px) {
    /* En el teléfono, 2×2: apiladas de a una ocupaban media pantalla y había que scrollear
       para ver si faltó algo, que es justamente el dato que se busca al entrar. */
    .lonky-stats { grid-template-columns: repeat(2, 1fr); gap: var(--lonky-space-8); }
    .lonky-stat { padding: var(--lonky-space-10) var(--lonky-space-12); }
    .lonky-stat-value { font-size: var(--lonky-text-2xl); }
}
.lonky-stat {
    border: 1px solid var(--lonky-border);
    border-radius: var(--lonky-radius-input, 8px);
    background: var(--lonky-surface);
    padding: var(--lonky-space-14) var(--lonky-space-16);
    display: flex;
    flex-direction: column;
    gap: var(--lonky-space-4);
}
.lonky-stat-label {
    font-size: var(--lonky-text-xs);
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--lonky-grey-500);
    display: flex;
    align-items: center;
    gap: var(--lonky-space-6);
}
.lonky-stat-value { font-size: var(--lonky-text-3xl); font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.lonky-stat-hint { font-size: var(--lonky-text-xs); color: var(--lonky-grey-500); }
/* Estados: el color va en la caja entera, no solo en el número, para que se vea de lejos. */
.lonky-stat-ok      { border-color: var(--lonky-stat-ok-border); background: var(--lonky-stat-ok-bg); }
.lonky-stat-ok      .lonky-stat-value { color: var(--lonky-stat-ok-text); }
.lonky-stat-danger  { border-color: var(--lonky-stat-danger-border); background: var(--lonky-stat-danger-bg); }
.lonky-stat-danger  .lonky-stat-value { color: var(--lonky-stat-danger-text); }
.lonky-stat-pending { border-color: var(--lonky-stat-pending-border); background: var(--lonky-stat-pending-bg); }
.lonky-stat-pending .lonky-stat-value { color: var(--lonky-stat-pending-text); }

/* Origen → destino: cada punto en su propia caja, con la flecha entre las dos. Antes era
   texto suelto y no se leía como "de acá va para allá". */
.lonky-route {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: stretch;
    gap: var(--lonky-space-12);
}
.lonky-route-box {
    border: 1px solid var(--lonky-border);
    border-radius: var(--lonky-radius-input, 8px);
    background: var(--lonky-grey-25);
    padding: var(--lonky-space-14) var(--lonky-space-16);
}
.lonky-route-arrow { display: flex; align-items: center; justify-content: center; color: var(--lonky-grey-400); }
@media (max-width: 575.98px) {
    /* En pantalla angosta las cajas se apilan y la flecha pasa a apuntar hacia abajo. Rota
       solo la flecha: el camión de "en tránsito" girado 90° quedaba de trompa al piso. */
    .lonky-route { grid-template-columns: 1fr; }
    .lonky-route-arrow .bi-arrow-right { transform: rotate(90deg); }
}

/* Fila clickeable (ambos modos). */
.lonky-rtable tr[data-href] { cursor: pointer; }
@media (min-width: 768px) {
    .lonky-rtable tr[data-href]:hover { background: var(--lonky-row-hover); }
}

/* Mobile: cada fila del tbody se vuelve una card con jerarquía. */
@media (max-width: 767.98px) {
    .lonky-rtable thead { display: none; }
    .lonky-rtable, .lonky-rtable tbody { display: block; width: 100%; }
    .lonky-rtable tr {
        display: block;
        position: relative;
        background: var(--lonky-surface);
        border: 1px solid var(--lonky-grey-100);
        border-radius: 8px;
        margin-bottom: var(--lonky-space-10);
        padding: var(--lonky-space-12) var(--lonky-space-14);
        touch-action: manipulation; /* sin double-tap-zoom en iOS */
    }
    .lonky-rtable td {
        display: flex;
        justify-content: space-between;
        gap: var(--lonky-space-12);
        align-items: baseline;
        border: 0;
        padding: var(--lonky-space-2) 0;
        text-align: left !important;
    }
    /* Par etiqueta:valor por defecto (la etiqueta viene de data-label). */
    .lonky-rtable td::before {
        content: attr(data-label);
        color: var(--lonky-grey-400);
        font-size: var(--lonky-text-sm);
        font-weight: 500;
        flex-shrink: 0;
    }
    /* Rol: título de la card (grande, arriba, sin etiqueta; deja lugar al badge). */
    /* El hueco de la derecha no es espaciado: reserva el ancho de los botones de acción, que en
       mobile van posicionados sobre la fila. Se escribe como dos escalones grandes en vez de un
       96px suelto para que se vea que es una medida deliberada y no un número que quedó. */
    .lonky-rtable td.rt-title {
        display: block; font-size: var(--lonky-text-lg); font-weight: 700;
        padding-right: calc(var(--lonky-space-48) * 2);
    }
    .lonky-rtable td.rt-title::before { content: none; }
    /* Rol: chip de estado, arriba a la derecha. */
    .lonky-rtable td.rt-badge { position: absolute; top: 12px; right: 12px; padding: 0; }
    .lonky-rtable td.rt-badge::before { content: none; }
    /* Rol: valor destacado (total), grande, con su etiqueta chica encima. */
    .lonky-rtable td.rt-emphasis { display: block; font-size: var(--lonky-text-xl); font-weight: 700; color: var(--lonky-link); }
    .lonky-rtable td.rt-emphasis::before { content: attr(data-label); display: block; font-size: var(--lonky-text-xs); font-weight: 500; }
    /* Rol: columna que no aporta en mobile. */
    .lonky-rtable td.rt-hide-mobile { display: none; }
    /* Rol: fila de acciones abajo (sus botones NO disparan el link — ver el JS). */
    .lonky-rtable td.rt-actions {
        display: flex; justify-content: flex-end; gap: var(--lonky-space-8);
        margin-top: var(--lonky-space-8); padding-top: var(--lonky-space-8); border-top: 1px solid var(--lonky-grey-50);
    }
    .lonky-rtable td.rt-actions::before { content: none; }
}

/* Encabezado de página: acciones full-width apiladas en mobile en vez de desbordar. */
@media (max-width: 767.98px) {
    .lonky-page-header { align-items: stretch; }
    .lonky-page-actions { width: 100%; }
    .lonky-page-actions > .btn,
    .lonky-page-actions > a,
    .lonky-page-actions > form { flex: 1 1 auto; }
}

/* ── Barra de trabajo del editor de producto ──────────────────────────────
   Navy de la marca para separarla de los campos blancos del form (no se
   confunde con el input Nombre que viene debajo). Ver x-product-form-header. */
.product-form-header {
    background: linear-gradient(100deg, var(--lonky-violet-800) 0%, var(--lonky-violet-900) 100%);
    color: var(--lonky-on-accent);
    box-shadow: none;
}
.product-form-header .pfh-chip {
    width: 2.5rem; height: 2.5rem;
    background: rgba(255, 255, 255, .12);
    color: var(--lonky-on-accent); font-size: var(--lonky-text-xl);
}
.product-form-header .pfh-eyebrow {
    font-size: var(--lonky-text-label); letter-spacing: .09em;
    color: rgba(255, 255, 255, .62);
}
.product-form-header .pfh-title {
    font-size: var(--lonky-text-xl); line-height: 1.25; color: var(--lonky-on-accent);
}
.product-form-header .pfh-save {
    background: var(--lonky-surface); color: var(--lonky-violet-800); border: none;
}
.product-form-header .pfh-save:hover {
    background: var(--lonky-violet-50); color: var(--lonky-violet-700);
}
.product-form-header .pfh-save:focus-visible {
    box-shadow: 0 0 0 .25rem rgba(255, 255, 255, .55);
}

/* Botón "volver al listado" dentro de la barra navy del editor de producto. */
.product-form-header .pfh-back {
    color: rgba(255, 255, 255, .85);
    border: 1px solid rgba(255, 255, 255, .35);
    background: transparent;
}
.product-form-header .pfh-back:hover {
    background: rgba(255, 255, 255, .12);
    color: var(--lonky-on-accent);
}
.product-form-header .pfh-back:focus-visible {
    box-shadow: 0 0 0 .25rem rgba(255, 255, 255, .45);
}

/* ─────────────────────────────────────────────────────────────────────────
   Ordenar arrastrando con el mouse.
   Lo comparten los valores de un atributo (S, M, L, XL) y el árbol de
   categorías: el orden ahí no es alfabético ni numérico, sólo lo sabe quien
   lo arma, así que se acomoda a mano.
   ───────────────────────────────────────────────────────────────────────── */
.lonky-drag-handle {
    cursor: grab;
    color: var(--lonky-drag-handle-fg);
    padding-left: var(--lonky-space-8);
    padding-right: var(--lonky-space-8);
    user-select: none;
}
.lonky-drag-handle:active { cursor: grabbing; }
.lonky-drag-handle:hover  { color: var(--lonky-violet-700); }

/* La fila que se está arrastrando: se atenúa para que se vea de dónde salió. */
.lonky-drag-activo { opacity: .45; }

/* Fila del árbol de categorías mientras se arrastra algo encima. El borde dice
   qué va a pasar al soltar: arriba/abajo = hermana, recuadro = pasa a ser hija. */
tr.lonky-drop-antes  > td { box-shadow: inset 0 2px 0 0 var(--lonky-violet-600); }
tr.lonky-drop-despues > td { box-shadow: inset 0 -2px 0 0 var(--lonky-violet-600); }
tr.lonky-drop-dentro > td {
    background: var(--lonky-violet-50);
    box-shadow: inset 2px 0 0 0 var(--lonky-violet-600);
}

/* ============================================================
   NOVEDADES
   Tokens de docs/design-system/tokens.md. Ancho de lectura
   acotado a propósito: es texto para leer, no una tabla.
   ============================================================ */

.lonky-news { max-width: 720px; margin: 0 auto; padding: var(--lonky-space-8) 0 var(--lonky-space-40); }

/* El número de versión NO es el protagonista: lo primero que se lee
   es qué cambió. Por eso va chico y en gris de metadata. */
.lonky-news-meta {
    font-size: var(--lonky-text-caption); line-height: 13px; font-weight: 500;
    color: var(--lonky-grey-400); letter-spacing: .02em;
}
.lonky-news-title {
    font-family: var(--lonky-font-title);
    font-size: var(--lonky-text-lg); line-height: 22px; font-weight: 600;
    color: var(--lonky-text); margin: var(--lonky-space-2) 0 var(--lonky-space-12);
}

.lonky-news-item {
    position: relative; padding: 0 0 var(--lonky-space-24) var(--lonky-space-24);
    border-left: 1px solid var(--lonky-grey-100);
}
.lonky-news-item:last-child { border-left-color: transparent; }
.lonky-news-item::before {
    content: ''; position: absolute; left: -5px; top: 4px;
    width: 9px; height: 9px; border-radius: 999px;
    background: var(--lonky-surface);
    border: 2px solid var(--lonky-grey-200);
}
.lonky-news-item.is-unread::before {
    background: var(--lonky-violet-500);
    border-color: var(--lonky-violet-500);
}

.lonky-news-entry { display: flex; gap: var(--lonky-space-8); align-items: baseline; padding: var(--lonky-space-6) 0; }
.lonky-news-entry-title { font-size: var(--lonky-text-sm); line-height: 16px; font-weight: 600; color: var(--lonky-text); }
.lonky-news-entry-body { font-size: var(--lonky-text-xs); line-height: 15px; color: var(--lonky-grey-500); }

/* Las áreas que el usuario no puede usar se atenúan, NO se ocultan:
   ocultarlas haría que la sección le mienta (spec §7). */
.lonky-news-muted { opacity: .55; }

.lonky-news-video { width: 100%; border-radius: 12px; display: block; margin: var(--lonky-space-12) 0; background: var(--lonky-video-bg); }

/* Chips: pill gris igual en todos + punto de color, como el resto
   de los badges del sistema. */
.lonky-chip {
    display: inline-flex; align-items: center; gap: var(--lonky-space-8);
    background: var(--lonky-sand); border-radius: 999px;
    padding: var(--lonky-space-4) var(--lonky-space-8); font-size: var(--lonky-text-xs); font-weight: 500;
    color: var(--lonky-text); white-space: nowrap;
}
.lonky-chip-dot { width: 6px; height: 6px; border-radius: 999px; background: var(--lonky-grey-300); }
.lonky-chip-new .lonky-chip-dot { background: var(--lonky-violet-500); }
.lonky-chip-improvement .lonky-chip-dot { background: var(--lonky-badge-dot-info); }
.lonky-chip-fix .lonky-chip-dot { background: var(--lonky-badge-dot-success); }

/* Punto del sidebar. Un solo "pop" al aparecer: un punto que late
   para siempre es ansiedad, no información. */
.lonky-nav-dot {
    width: 7px; height: 7px; border-radius: 999px;
    background: var(--lonky-violet-500); margin-left: auto;
    animation: lonky-dot-pop .28s cubic-bezier(.2, .8, .2, 1);
}
@keyframes lonky-dot-pop { from { transform: scale(0); } to { transform: scale(1); } }

.lonky-news-banner {
    display: flex; align-items: center; gap: var(--lonky-space-10);
    background: var(--lonky-violet-50);
    color: var(--lonky-link);
    border-radius: 8px; padding: var(--lonky-space-10) var(--lonky-space-14); margin-bottom: var(--lonky-space-16);
    font-size: var(--lonky-text-sm); animation: lonky-banner-in .24s cubic-bezier(.2, .8, .2, 1);
}
.lonky-news-banner a { color: inherit; font-weight: 600; }
.lonky-news-entry-area {
    font-size: var(--lonky-text-caption); line-height: 13px; font-weight: 500; text-transform: uppercase;
    letter-spacing: .04em; color: var(--lonky-grey-400);
}
.lonky-news-entry-more,
.lonky-news-otros-toggle {
    background: none; border: 0; padding: var(--lonky-space-4) 0;
    font-size: var(--lonky-text-xs); font-weight: 500; color: var(--lonky-link);
    display: inline-flex; align-items: center; gap: var(--lonky-space-6);
}
.lonky-news-otros { margin-top: var(--lonky-space-8); }

.lonky-news-empty {
    text-align: center; padding: var(--lonky-space-48) var(--lonky-space-16); color: var(--lonky-grey-400);
}
.lonky-news-empty i { font-size: var(--lonky-text-3xl); display: block; margin-bottom: var(--lonky-space-10); }
.lonky-news-empty p { font-size: var(--lonky-text-sm); margin: 0; }

/* ---- Modal de versión destacada ---- */
.lonky-highlight-backdrop {
    position: fixed; inset: 0; z-index: 1080;
    display: flex; align-items: center; justify-content: center; padding: var(--lonky-space-16);
    background: rgba(29, 34, 43, .45);
}
.lonky-highlight-panel {
    position: relative; width: 100%; max-width: 520px;
    max-height: 90vh; overflow-y: auto;
    background: var(--lonky-surface); border-radius: 12px; padding: var(--lonky-space-24);
    box-shadow: none;   /* el modal se separa por su borde y el backdrop */
    border: 1px solid var(--lonky-border);
}
.lonky-highlight-close {
    position: absolute; top: 12px; right: 12px;
    background: none; border: 0; color: var(--lonky-grey-400); line-height: 1;
}
.lonky-highlight-actions { display: flex; gap: var(--lonky-space-8); justify-content: flex-end; margin-top: var(--lonky-space-16); }
.lonky-news-banner-close { margin-left: auto; background: none; border: 0; color: inherit; line-height: 1; }
@keyframes lonky-banner-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

.lonky-news-item { animation: lonky-news-in .3s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes lonky-news-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

@media (max-width: 767.98px) {
    .lonky-news { padding-inline: var(--lonky-space-4); }
    .lonky-news-item { padding-left: var(--lonky-space-16); }
}

/* Quien pidió menos movimiento no recibe ninguno. */
@media (prefers-reduced-motion: reduce) {
    .lonky-news-item, .lonky-news-banner, .lonky-nav-dot { animation: none; }
    .lonky-highlight-panel { transition: none; }
}

/* ----------------------------------------------------------------------------
   LOADER DE MARCA — el isotipo girando en 3D
   Para la espera bloqueante de ~1s para arriba. El spinner de Bootstrap sigue siendo
   el de los botones; ver el comentario de resources/views/components/loader.blade.php.
   ---------------------------------------------------------------------------- */
.lonky-loader {
    --lonky-loader-size: 64px;
    /* El espesor de la extrusión, proporcional al tamaño: si fuera fijo, un loader de 120px
       se vería como una lámina y uno de 32px como un ladrillo. */
    --lonky-loader-grosor: calc(var(--lonky-loader-size) * .16);
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: var(--lonky-space-10);
    /* Lejana a propósito: una perspectiva corta deforma el isotipo al pasar de perfil y la
       marca deja de ser reconocible justo en el frame que más se mira. */
    perspective: calc(var(--lonky-loader-size) * 9);
}
.lonky-loader-marca {
    position: relative;
    width: var(--lonky-loader-size);
    /* El isotipo no es cuadrado: 820 de alto sobre 772 de ancho en su viewBox. */
    height: calc(var(--lonky-loader-size) * 1.062);
    transform-style: preserve-3d;
    animation: lonky-loader-giro 2s cubic-bezier(.45, .05, .55, .95) infinite;
}
.lonky-loader-marca svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* Sin esto las capas desaparecen media vuelta: al girar, lo que se ve es su reverso. */
    backface-visibility: visible;
}
/* Las seis capas, de atrás hacia adelante. La cara frontal lleva el violeta del tema —que ya
   cambia solo entre claro y oscuro— y las traseras son ese mismo violeta mezclado con negro.
   Se derivan con color-mix en vez de declararse como seis tokens nuevos: son colores de un
   solo uso, y un token que nadie más consume no es un token, es un literal con nombre.
   Un navegador sin color-mix ignora la línea y muestra el isotipo plano: gira igual. */
.lonky-loader-marca svg { color: var(--lonky-link); }
.lonky-loader-marca svg:nth-child(1) { transform: translateZ(calc(var(--lonky-loader-grosor) * -1));  color: color-mix(in oklab, var(--lonky-link) 45%, black); }
.lonky-loader-marca svg:nth-child(2) { transform: translateZ(calc(var(--lonky-loader-grosor) * -.8)); color: color-mix(in oklab, var(--lonky-link) 56%, black); }
.lonky-loader-marca svg:nth-child(3) { transform: translateZ(calc(var(--lonky-loader-grosor) * -.6)); color: color-mix(in oklab, var(--lonky-link) 67%, black); }
.lonky-loader-marca svg:nth-child(4) { transform: translateZ(calc(var(--lonky-loader-grosor) * -.4)); color: color-mix(in oklab, var(--lonky-link) 78%, black); }
.lonky-loader-marca svg:nth-child(5) { transform: translateZ(calc(var(--lonky-loader-grosor) * -.2)); color: color-mix(in oklab, var(--lonky-link) 89%, black); }
.lonky-loader-marca svg:nth-child(6) { transform: translateZ(0); }

/* El demo del kit apoyaba el loader sobre una elipse de sombra. Acá no va: el kit prohíbe las
   sombras y la profundidad ya la da la extrusión. */
@keyframes lonky-loader-giro {
    from { transform: rotateX(14deg) rotateY(0); }
    to   { transform: rotateX(14deg) rotateY(360deg); }
}

.lonky-loader-texto {
    font-family: var(--lonky-font-mono);
    font-size: var(--lonky-text-caption);
    font-weight: 500;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--lonky-text-muted);
}

/* Variante que tapa la pantalla, para lo que no se puede seguir usando mientras corre. */
.lonky-loader-overlay {
    position: fixed;
    inset: 0;
    z-index: 1080;   /* arriba del modal de Bootstrap (1055), que es lo que suele estar abierto */
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--lonky-overlay);
}

/* Quien pidió menos movimiento no recibe ninguno — pero sí necesita saber que algo está
   pasando, así que el giro se cambia por un latido de opacidad. Apagar la animación sin
   reemplazarla deja un isotipo quieto que parece un error, no una espera. */
@media (prefers-reduced-motion: reduce) {
    .lonky-loader-marca { animation: lonky-loader-latido 1.6s ease-in-out infinite; }
    @keyframes lonky-loader-latido {
        0%, 100% { opacity: 1; }
        50%      { opacity: .45; }
    }
}

/* ----------------------------------------------------------------------------
   TRAZADO DEL LOGO — el isotipo del sidebar se dibuja al entrar
   Corre una sola vez por sesión; quién lo decide es partials/logo-boot.blade.php.
   ---------------------------------------------------------------------------- */
.lonky-trazar-logo .lonky-brand .lonky-logo svg path {
    fill-opacity: 0;
    stroke: currentColor;
    /* Fino a propósito. El demo del kit usaba 26 sobre un trazo que mide ~100 unidades y
       además no lo apagaba nunca: el isotipo quedaba 26% más gordo que el favicon para
       siempre. Acá el contorno solo existe mientras se dibuja y se apaga con el relleno. */
    stroke-width: 18;
    /* Los paths declaran pathLength="1", así que el largo del trazo es 1 sin importar cuánto
       mida de verdad. El demo declaraba 2400 y 4200 contra 1717 y 3838 reales: la pieza chica
       terminaba de dibujarse al 71% y se quedaba congelada, y la grande empezaba a rellenarse
       antes de cerrar el contorno. */
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
}
.lonky-trazar-logo .lonky-brand .lonky-logo svg path:nth-child(1) {
    animation: lonky-logo-trazo .7s cubic-bezier(.65, 0, .35, 1) forwards,
               lonky-logo-relleno .4s ease .7s forwards;
}
.lonky-trazar-logo .lonky-brand .lonky-logo svg path:nth-child(2) {
    animation: lonky-logo-trazo .85s cubic-bezier(.65, 0, .35, 1) .1s forwards,
               lonky-logo-relleno .4s ease .95s forwards;
}
/* El nombre entra cuando el dibujo ya se lee, no antes: si arrancan juntos compiten. */
.lonky-trazar-logo .lonky-brand .lonky-logo-texto {
    animation: lonky-logo-nombre .45s cubic-bezier(.65, 0, .35, 1) .5s both;
}
@keyframes lonky-logo-trazo   { to { stroke-dashoffset: 0; } }
@keyframes lonky-logo-relleno { to { fill-opacity: 1; stroke-opacity: 0; } }
@keyframes lonky-logo-nombre  { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
    /* Sin trazado: el logo aparece hecho. Acá no hace falta reemplazar la animación por otra
       —a diferencia del loader— porque el logo no comunica que algo esté pasando. */
    .lonky-trazar-logo .lonky-brand .lonky-logo svg path {
        fill-opacity: 1;
        stroke: none;
        animation: none;
    }
    .lonky-trazar-logo .lonky-brand .lonky-logo-texto { animation: none; }
}

/* ----------------------------------------------------------------------------
   PANTALLAS DE ERROR — 403 / 404 / 500 / 503
   Hoja suelta: no comparten el shell del panel a propósito (ver errors/_layout).
   ---------------------------------------------------------------------------- */
.lonky-error-body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--lonky-space-24);
    background: var(--lonky-bg);
    color: var(--lonky-text);
    font-family: var(--lonky-font-ui);
}
.lonky-error {
    width: 100%;
    max-width: 480px;
    text-align: center;
}
/* El isotipo grande y tenue: es lo único de marca que hay en la pantalla, y a plena tinta
   competiría con el mensaje, que es lo que la persona vino a leer. */
.lonky-error-marca {
    width: 56px;
    height: 56px;
    color: var(--lonky-link);
    opacity: .35;
    margin-bottom: var(--lonky-space-24);
}
/* El número en mono, como el kit fija los captions. Va arriba del título y chico: informa a
   quien lo necesita para reportar el problema, sin gritarle un 500 a quien solo quiere seguir
   trabajando. Es exactamente al revés de la pantalla de error clásica, que pone el número en
   gigante y el mensaje útil en letra chica. */
.lonky-error-codigo {
    margin: 0 0 var(--lonky-space-8);
    font-family: var(--lonky-font-mono);
    font-size: var(--lonky-text-caption);
    font-weight: 500;
    letter-spacing: .12em;
    color: var(--lonky-text-muted);
}
.lonky-error-titulo {
    margin: 0 0 var(--lonky-space-12);
    font-family: var(--lonky-font-title);
    font-size: var(--lonky-text-2xl);
    font-weight: 900;
    letter-spacing: -.015em;
    line-height: 1.2;
    text-wrap: balance;
}
.lonky-error-texto {
    margin: 0 0 var(--lonky-space-32);
    font-size: var(--lonky-text-base);
    line-height: 1.6;
    color: var(--lonky-text-muted);
    text-wrap: pretty;
}
.lonky-error-acciones {
    display: flex;
    flex-wrap: wrap;
    gap: var(--lonky-space-10);
    justify-content: center;
}
/* Los botones se escriben acá y no se heredan de Bootstrap porque estas páginas no lo cargan:
   un CDN de terceros menos en el peor momento posible. */
.lonky-error-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 44px;
    padding: 0 var(--lonky-space-24);
    border: 1px solid transparent;
    border-radius: var(--lonky-radius-input);
    background: var(--lonky-violet-500);
    color: var(--lonky-on-accent);
    font-family: inherit;
    font-size: var(--lonky-text-sm);
    font-weight: 500;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
}
.lonky-error-btn:hover { background: var(--lonky-violet-400); color: var(--lonky-on-accent); }
.lonky-error-btn:focus-visible { outline: none; box-shadow: var(--lonky-focus-ring); }
.lonky-error-btn--secundario {
    background: transparent;
    border-color: var(--lonky-border);
    color: var(--lonky-text);
}
.lonky-error-btn--secundario:hover { background: var(--lonky-hover-bg); border-color: var(--lonky-grey-500); color: var(--lonky-text); }

@media (max-width: 575.98px) {
    /* En un teléfono los botones apilados a ancho completo se aciertan con el pulgar; en fila
       quedan dos objetivos angostos al lado del otro. */
    .lonky-error-acciones { flex-direction: column-reverse; }
    .lonky-error-btn { width: 100%; }
}

/* ============================================================================
   EDITOR DE DESCRIPCIONES  (componente `<x-rich-text>`, Quill tema "snow")

   Quill trae su propio tema con grises literales: sobre el panel oscuro es una caja blanca con
   íconos que desaparecen. Acá se re-mapea contra los tokens semánticos y con eso el modo oscuro
   sale sin una sola regla extra — `--lonky-border`, `--lonky-surface` y compañía ya se espejan
   solos en [data-theme="dark"]. Misma razón por la que el CSS de Quill se carga ANTES que este
   archivo en el layout.
   ============================================================================ */

/* El textarea se esconde recién cuando el editor montó, y no por CSS estático: si el CDN de
   Quill no responde, el campo queda a la vista y se sigue editando como antes.

   Se esconde con display:none y no se saca del DOM: es el campo que viaja en el POST, el que
   `x-autosave` serializa desde `form.elements` y el que Quill escribe en cada tecla. */
.lonky-rich-text--montado .lonky-rich-text__fuente { display: none; }

.lonky-rich-text .ql-toolbar.ql-snow {
    border: 1px solid var(--lonky-border);
    border-bottom: 0;
    border-radius: var(--lonky-radius-input) var(--lonky-radius-input) 0 0;
    background: var(--lonky-bg);
    padding: var(--lonky-space-4) var(--lonky-space-6);
}

.lonky-rich-text .ql-container.ql-snow {
    border: 1px solid var(--lonky-border);
    border-radius: 0 0 var(--lonky-radius-input) var(--lonky-radius-input);
    background: var(--lonky-surface);
    color: var(--lonky-text);
    /* Hereda la tipografía del panel: Satoshi acá y Satoshi en la tienda, no la del tema de Quill. */
    font-family: inherit;
    font-size: var(--lonky-text-sm);
}

.lonky-rich-text .ql-editor {
    min-height: var(--lonky-rich-text-alto, 5.5rem);
    padding: var(--lonky-space-8) var(--lonky-space-10);
}

.lonky-rich-text .ql-editor.ql-blank::before {
    color: var(--lonky-text-muted);
    /* El placeholder de Quill viene en itálica; en este panel los placeholders son redondos. */
    font-style: normal;
    left: var(--lonky-space-10);
    right: var(--lonky-space-10);
}

/* Los íconos de la barra son SVG con stroke y fill literales — sobre el panel oscuro se pierden. */
.lonky-rich-text .ql-snow .ql-stroke { stroke: var(--lonky-text-muted); }
.lonky-rich-text .ql-snow .ql-fill   { fill: var(--lonky-text-muted); }

.lonky-rich-text .ql-snow button:hover,
.lonky-rich-text .ql-snow button.ql-active {
    background: var(--lonky-hover-bg);
    border-radius: var(--lonky-radius-sm);
}
.lonky-rich-text .ql-snow button:hover .ql-stroke,
.lonky-rich-text .ql-snow button.ql-active .ql-stroke { stroke: var(--lonky-violet-500); }
.lonky-rich-text .ql-snow button:hover .ql-fill,
.lonky-rich-text .ql-snow button.ql-active .ql-fill   { fill: var(--lonky-violet-500); }

/* El foco se ve en la caja entera y no en el contenteditable: el borde de arriba lo dibuja la
   barra de herramientas, que es otro elemento. */
.lonky-rich-text:focus-within .ql-toolbar.ql-snow,
.lonky-rich-text:focus-within .ql-container.ql-snow { border-color: var(--lonky-violet-500); }
.lonky-rich-text:focus-within .ql-container.ql-snow { box-shadow: var(--lonky-focus-ring); }

/* ============================================================================
   BUSCADOR GLOBAL (command palette)
   ---------------------------------------------------------------------------
   Todo sale de los tokens del kit: espaciados, tamaños de texto y radios. El
   tema oscuro sale gratis porque superficie, borde y texto ya cambian con el
   modo.

   SIN box-shadow: el kit prohíbe las sombras de profundidad y las reemplaza por
   borde + superficie. El panel se despega del overlay por su borde, no por una
   sombra — que además en modo oscuro no se ve.

   El resaltado usa violeta y NO naranja: --lonky-orange-25 ya significa "fila
   modificada en el editor masivo", y reusarlo rompe ese significado.
   ============================================================================ */

.lonky-search-trigger {
    /* El ancho descuenta los márgenes. `width: 100%` con `margin-inline` daba `100% + 24px`
       y el botón se desbordaba del sidebar — por eso el badge `Ctrl K` quedaba cortado contra
       el borde. Y un `<button>`, aunque sea `display: flex`, NO se estira solo al ancho
       disponible como un div: hay que pedírselo. */
    display: flex; align-items: center; gap: var(--lonky-space-8);
    width: calc(100% - var(--lonky-space-24));
    margin: var(--lonky-space-10) var(--lonky-space-12) var(--lonky-space-6);
    padding: var(--lonky-space-8) var(--lonky-space-10);

    /* SUPERFICIE Y NO BORDE. Con borde sobre fondo transparente se leía como un input vacío e
       invitaba a tipear ahí, pero es un botón que abre un modal. Una superficie tenue dice
       "esto se aprieta" sin fingir ser un campo, y deja el borde —el recurso de jerarquía más
       fuerte del kit— para las cosas que de verdad lo necesitan. */
    background: var(--lonky-bg);
    border: 1px solid transparent; border-radius: var(--lonky-radius-input);
    color: var(--lonky-text-muted); font-size: var(--lonky-text-sm);
    cursor: pointer; text-align: left;
    transition: background-color .15s, color .15s;
}
.lonky-search-trigger:hover {
    background: var(--lonky-violet-50);
    color: var(--lonky-text);
}
/* El violeta se reserva para el foco de teclado, que es donde de verdad hace falta gritar:
   quien navega con Tab necesita ver dónde está parado. En hover alcanza con la superficie. */
.lonky-search-trigger:focus-visible {
    outline: 2px solid var(--lonky-violet-400); outline-offset: 2px;
    background: var(--lonky-violet-50); color: var(--lonky-text);
}
.lonky-search-trigger .lonky-icon,
.lonky-search-trigger svg { flex: none; opacity: .75; }
.lonky-search-trigger-text {
    flex: 1; text-align: left;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Colapsado a 77px: queda la lupa sola, centrada. */
.is-collapsed .lonky-search-trigger {
    justify-content: center;
    width: calc(100% - var(--lonky-space-24));
    margin-inline: var(--lonky-space-12); padding-inline: 0;
}
.is-collapsed .lonky-search-trigger-text,
.is-collapsed .lonky-search-kbd { display: none; }

.lonky-palette-overlay {
    position: fixed; inset: 0; z-index: 1060;      /* la topbar es 1020 */
    background: rgba(16, 10, 44, .45);
    display: flex; justify-content: center; align-items: flex-start;
    padding: 10vh var(--lonky-space-16) var(--lonky-space-16);
}
.lonky-palette {
    width: 100%; max-width: 620px; display: flex; flex-direction: column;
    max-height: 70vh; overflow: hidden;
    background: var(--lonky-surface); color: var(--lonky-text);
    border: 1px solid var(--lonky-border); border-radius: var(--lonky-radius-card);
}
.lonky-palette-input {
    display: flex; align-items: center; gap: var(--lonky-space-10);
    padding: var(--lonky-space-12) var(--lonky-space-14);
    border-bottom: 1px solid var(--lonky-border);
}
.lonky-palette-input input {
    flex: 1; border: 0; outline: 0; background: transparent;
    color: var(--lonky-text); font-size: var(--lonky-text-base);
}
.lonky-palette-close {
    border: 0; background: transparent; color: var(--lonky-text-muted); cursor: pointer;
}
.lonky-palette-body { overflow-y: auto; padding: var(--lonky-space-6) 0; }
.lonky-palette-group + .lonky-palette-group { border-top: 1px solid var(--lonky-border); }
.lonky-palette-group-label {
    padding: var(--lonky-space-8) var(--lonky-space-14) var(--lonky-space-4);
    font-size: var(--lonky-text-label);
    text-transform: uppercase; letter-spacing: .04em; color: var(--lonky-text-muted);
}
.lonky-palette-row {
    display: flex; align-items: center; gap: var(--lonky-space-10);
    padding: var(--lonky-space-8) var(--lonky-space-14);
    color: var(--lonky-text); text-decoration: none; cursor: pointer;
}
.lonky-palette-row.is-active { background: var(--lonky-violet-50); }
.lonky-palette-row-main { display: flex; flex-direction: column; min-width: 0; }
.lonky-palette-row-main small { color: var(--lonky-text-muted); font-size: var(--lonky-text-xs); }
.lonky-palette-row mark {
    background: var(--lonky-violet-100); color: inherit; padding: 0;
    border-radius: var(--lonky-space-2);
}
.lonky-palette-empty {
    padding: var(--lonky-space-24) var(--lonky-space-14);
    text-align: center; color: var(--lonky-text-muted);
}
.lonky-palette-tip {
    display: flex; align-items: center; gap: var(--lonky-space-8); width: 100%;
    padding: var(--lonky-space-6) var(--lonky-space-14);
    border: 0; background: transparent; color: var(--lonky-text-muted);
    font-size: var(--lonky-text-sm); cursor: pointer; text-align: left;
}
.lonky-palette-tip:hover,
.lonky-palette-tip.is-active { background: var(--lonky-violet-50); }
.lonky-palette-tip code { color: var(--lonky-violet-500); }
.lonky-palette-footer {
    display: flex; gap: var(--lonky-space-16);
    padding: var(--lonky-space-8) var(--lonky-space-14);
    border-top: 1px solid var(--lonky-border);
    font-size: var(--lonky-text-xs); color: var(--lonky-text-muted);
}

/* ── Buscador de listado: sugerencias en vivo abajo del input ──────────────────
   Vive acá y no en un <style> del componente porque el ADR-021 no admite colores
   escritos a mano en una vista: los fallbacks tipo `var(--x, #fff)` se ven bien
   hoy y quedan blanco sobre blanco el día del modo oscuro. */
.lonky-buscador { position: relative; }
.lonky-buscador-panel {
    position: absolute; z-index: 1050; top: calc(100% + 4px); left: 0; right: 0;
    max-height: 22rem; overflow-y: auto; padding: var(--lonky-space-4);
    background: var(--lonky-surface); color: var(--lonky-text);
    /* Se separa del fondo con borde y `surface`, como el palette: el sistema no tiene
       tokens de sombra y no es este componente el que debería inventar uno. */
    border: 1px solid var(--lonky-border); border-radius: var(--lonky-radius-card);
}
.lonky-buscador-row {
    display: flex; flex-direction: column; gap: var(--lonky-space-2);
    padding: var(--lonky-space-8) var(--lonky-space-10);
    border-radius: var(--lonky-radius-sm);
    color: var(--lonky-text); text-decoration: none; cursor: pointer;
}
.lonky-buscador-row.is-active { background: var(--lonky-violet-50); }
.lonky-buscador-row small { color: var(--lonky-text-muted); font-size: var(--lonky-text-xs); }
.lonky-buscador-row mark {
    background: var(--lonky-violet-50); color: var(--lonky-violet-500);
    padding: 0; border-radius: 2px;
}
.lonky-buscador-aviso {
    padding: var(--lonky-space-8) var(--lonky-space-10);
    font-size: var(--lonky-text-xs); color: var(--lonky-text-muted);
}
.lonky-buscador-ver {
    display: block; width: 100%; text-align: left; cursor: pointer;
    padding: var(--lonky-space-8) var(--lonky-space-10);
    border: 0; border-top: 1px solid var(--lonky-border);
    background: transparent; color: var(--lonky-text);
    font-size: var(--lonky-text-xs);
    border-radius: var(--lonky-radius-sm);
}
.lonky-buscador-ver.is-active { background: var(--lonky-violet-50); }

@media (prefers-reduced-motion: reduce) {
    .lonky-palette-overlay, .lonky-palette { transition: none !important; animation: none !important; }
}
