/* ═══
   LaMaruja Design System v1.0
   The single source of truth for all LaMaruja app styling
   ═══ */

/* public/mrj-palette.css (2026-08-29, consistencia-os): --mrj-cream y
   --font-heading/--font-body ya NO se declaran a mano aquí abajo — vienen de
   este import compartido con tv.css (Modo TV, página aparte que no carga
   este fichero — ver su cabecera). Un @import debe ir antes que cualquier
   otra regla (salvo @charset), por eso está aquí arriba y no junto al resto
   de la paleta más abajo. */
@import url('/mrj-palette.css');

/* MEJORAS-50 #41 (2026-07-08): fuentes autohospedadas en /fonts (woff2 latin,
   ~44KB total, caché immutable de un año en server.js). Antes se cargaban DOS
   veces de Google (un <link> en index.html y un @import aquí — encadenado y
   bloqueante) con dependencia de un CDN externo. Lato no existe en peso 800:
   el rango 700 900 hace que las peticiones de 800 usen el fichero de 700
   (igual que hacía el navegador sintéticamente, pero explícito). */
@font-face{font-family:'Paytone One';font-style:normal;font-weight:400;font-display:swap;src:url('/fonts/paytone-one-400.woff2') format('woff2')}
@font-face{font-family:'Lato';font-style:normal;font-weight:400;font-display:swap;src:url('/fonts/lato-400.woff2') format('woff2')}
@font-face{font-family:'Lato';font-style:normal;font-weight:700 900;font-display:swap;src:url('/fonts/lato-700.woff2') format('woff2')}

/* ── Tokens ─── */
:root {
  /* Paleta oficial ÚNICA (DISENO.md §1). Estos tokens BASE son la fuente única
     tanto para las páginas legacy .mrj-* (servidas por lib/render.js bajo /m/*)
     como para el shell SPA .shell — antes el shell duplicaba estos valores en
     un bloque aislado bajo .shell; ese bloque se eliminó y sus valores oficiales
     se promovieron aquí (unificación de raíz, 2026-07-05). */
  /* --mrj-cream viene de mrj-palette.css (@import al principio del fichero,
     ver cabecera) — antes vivía en hex literal aquí mismo. */
  /* D1 paso 4 (2026-07-09, guardian-diseno): brown/orange/sand/mocha/ink pasan
     a ser ALIAS de la escala --accent-XX de abajo (fuente única = fórmula, no
     hex a mano) — QA visual OK (ver nota debajo del bloque D1). --mrj-cream se
     queda en hex literal a propósito: es el --bg de TODA la app (máxima
     superficie, máxima sensibilidad) y ninguna parada de la escala lo reproduce
     dentro del umbral ΔE<2 (accent-50/100 dan ΔE 2.56/3.20 — ver PROPUESTA-
     PALETA-LCH.md). Migrarlo exigiría una parada semántica propia, no hecha aquí. */
  --mrj-brown:    var(--accent-800);
  --mrj-orange:   var(--accent-500);
  --mrj-sand:     var(--accent-300);
  --mrj-mocha:    var(--accent-700);
  --mrj-ink:      var(--accent-900);

  /* ── D1 "Paleta LCH" (PROPUESTA-PALETA-LCH.md, 2026-07-09) ───
     Paleta por FÓRMULA en oklch(). --mrj-brown/orange/sand/mocha/ink de arriba
     son ALIAS de esta escala (paso 4, EJECUTADO) — el hex ya no vive a mano en
     dos sitios, esta escala es la fuente única.
     Fuente única de la familia: hue + croma del acento oficial
     (--mrj-orange #F59800 = oklch(75.79% 0.1672 67.28)). Cambiar SOLO
     --accent-h/--accent-c recalcula toda la escala, las tintas de categoría
     de abajo Y los 5 tokens --mrj-* de arriba a la vez.
     NOTA vs el borrador §2 de la propuesta: sus multiplicadores de croma
     0.9/0.8/0.4 (paradas 700/800/900) NO reproducían mocha/brown/ink
     (ΔE≈9, verificado numéricamente); aquí van los multiplicadores REALES
     medidos sobre los hex oficiales (0.361/0.325/0.163). Verificación:
     node scripts/verificar-paleta-lch.mjs (oklch→sRGB con la matriz OKLab
     estándar + ΔE OKLab×100 contra cada hex). */
  --accent-h: 67.28;   /* hue OKLCH de --mrj-orange (#F59800) */
  --accent-c: 0.1672;  /* croma OKLCH de --mrj-orange */
  /* Escala L de 9 paradas: L y multiplicador de croma FIJOS por parada;
     entre familias solo varía el hue. ΔE medido contra el token --mrj-*
     equivalente (deriva por usar el hue del acento en toda la escala): */
  --accent-50:  oklch(97% calc(var(--accent-c) * 0.10) var(--accent-h));      /* fondo tintado muy sutil (parada nueva) */
  --accent-100: oklch(92% calc(var(--accent-c) * 0.25) var(--accent-h));      /* fondo tintado pill/badge (parada nueva) */
  --accent-300: oklch(86.35% calc(var(--accent-c) * 0.352) var(--accent-h));  /* ≈ --mrj-sand  (ΔE 0.65 — hue 73.65→67.28) */
  --accent-500: oklch(75.79% calc(var(--accent-c) * 1) var(--accent-h));      /* = --mrj-orange / --accent (ΔE < 0.1) */
  --accent-600: oklch(68% calc(var(--accent-c) * 1) var(--accent-h));         /* hover del acento (parada nueva) */
  --accent-700: oklch(54.14% calc(var(--accent-c) * 0.361) var(--accent-h));  /* ≈ --mrj-mocha (ΔE 1.76 — hue 50.47→67.28) */
  --accent-800: oklch(32.95% calc(var(--accent-c) * 0.325) var(--accent-h));  /* ≈ --mrj-brown (ΔE 1.93 — hue 46.83→67.28) */
  --accent-900: oklch(14.56% calc(var(--accent-c) * 0.163) var(--accent-h));  /* ≈ --mrj-ink   (ΔE 0.67 — hue 53.16→67.28) */

  /* Tintas de categoría (sección 9, "Categorías cromáticas") por la MISMA
     fórmula: cada categoría aporta SOLO su hue — el OKLCH exacto del token
     --mrj-* que usaba hasta ahora — y la L y el croma salen de su parada de
     la escala. Al coincidir L, C y H con el token de origen, el resultado es
     visualmente idéntico (ΔE < 0.1, verificado con el script). Gobernanza:
     una fórmula compartida en vez de 5 tintas mantenidas a mano. */
  --cat-op-h:       67.28; /* hue de --mrj-orange — Operación */
  --cat-marca-h:    73.65; /* hue de --mrj-sand — Marca y clientas */
  --cat-b2b-h:      50.47; /* hue de --mrj-mocha — Negocio B2B */
  --cat-control-h:  46.83; /* hue de --mrj-brown — Control y calidad */
  --cat-finanzas-h: 53.16; /* hue de --mrj-ink — Finanzas y sistema */
  --cat-op:       oklch(75.79% calc(var(--accent-c) * 1) var(--cat-op-h));          /* parada 500 */
  --cat-marca:    oklch(86.35% calc(var(--accent-c) * 0.352) var(--cat-marca-h));   /* parada 300 */
  --cat-b2b:      oklch(54.14% calc(var(--accent-c) * 0.361) var(--cat-b2b-h));     /* parada 700 */
  --cat-control:  oklch(32.95% calc(var(--accent-c) * 0.325) var(--cat-control-h)); /* parada 800 */
  --cat-finanzas: oklch(14.56% calc(var(--accent-c) * 0.163) var(--cat-finanzas-h));/* parada 900 */
  /* NO migrados a la escala (decisión D1, documentada aquí a propósito):
     --ok (#5E7A3A) y --bad (#C0392B) caen en la L de la parada 700 (54.2/54.3
     vs 54.14) pero con cromas propios (0.096 / 0.174) irreproducibles con el
     multiplicador común 0.361 (ΔE≈3.6 y ≈11 — cambio visual real);
     --warn-text (#B37000, L 60.3%) no cae en NINGUNA parada de la escala.
     Los tres se quedan con su valor actual hasta que la escala incorpore
     paradas semánticas propias. */

  --bg:       var(--mrj-cream);
  --surface:  #FFFDF9;
  --accent:   var(--mrj-orange);
  --brown:    var(--mrj-brown);
  --border:   rgba(140,101,79,.25);
  /* C18 (contraste AA claro, 2026-07-07): #7A5A44 solo llegaba a 4.51:1 en
     el peor caso (muted sobre .cat-control/.cat-b2b/.cat-finanzas).
     Oscurecido a #725339 (mismo hue), margen real 5.05:1. */
  --muted:    #725339;
  --text:     var(--mrj-ink);
  --sidebar-bg:   var(--mrj-brown);
  --sidebar-text: var(--mrj-cream);
  /* --ok-text/--bad-text en claro (D47, 2026-07-09): axe-core encontró AA
     roto en claro (.pill-ok/.pill-bad, .trend-up/down, kanban — 3.56-4.46:1).
     Misma hue, L bajada: olive→#4E6530 (4.64:1), teja→#AF3427 (4.55:1).
     --ok/--bad sin tocar (uso no-textual, ya cumplen 3:1). Declarado ANTES
     de [data-theme="dark"] para no perder el override por cascada. */
  --ok-text: #4E6530; --bad-text: #AF3427;
  /* --warn-text (C4, 2026-07-07): --accent es poco legible como texto
     pequeño en claro. #B37000 = mismo hue oscurecido, ya usado como literal
     repetido (.pill-warn, .chip.on...) — tokenizado aquí, mismo patrón que
     --ok-text/--bad-text (default claro antes del bloque oscuro). */
  --warn-text: #B37000;

  /* --surface-2 (D47, 2026-07-09): movido aquí desde el ":root" v3 (tras
     [data-theme="dark"]) — ganaba por cascada y silenciaba el .07 de modo
     oscuro (Puppeteer: .28 real en vez de .07), rompiendo AA en .pill-mut
     (4.03:1). Mismo valor .28, solo cambia dónde se declara. */
  --surface-2:rgba(234,205,168,.28);
  /* --shadow-1/2/3 (D47/D48): mismo movimiento por el motivo de arriba.
     Doble capa contacto+ambiente: offset/blur chico+alpha alto (anclaje) y
     offset/blur grande+alpha bajo (difusión), mismo tinte 76,44,28 — cero
     color nuevo, peso total equilibrado con el original. */
  --shadow-1:0 1px 1px rgba(76,44,28,.10), 0 2px 5px rgba(76,44,28,.06);
  --shadow-2:0 2px 4px rgba(76,44,28,.10), 0 10px 26px rgba(76,44,28,.10);
  --shadow-3:0 4px 8px rgba(76,44,28,.12), 0 26px 56px rgba(76,44,28,.14);

  /* Espaciado (DISENO.md §3): --sp-1..8 (bloque v3 más abajo) son la única
     fuente de verdad; --sp-xs..2xl (legacy .mrj-*) son alias de los mismos
     valores, sin duplicar tokens. Resultado en px idéntico a antes
     (4/8/16/24/32/48), cero cambio visual. */
  --sp-xs:  var(--sp-1);  --sp-sm:  var(--sp-2);  --sp-md: var(--sp-4);
  --sp-lg:  var(--sp-5);  --sp-xl:  var(--sp-6);  --sp-2xl: var(--sp-7);

  /* Radios: valor real en producción (~60 sitios). DISENO.md §3 documentaba
     8/12/16/24 como aspiracional, nunca implementado — reconciliado
     2026-07-07 a favor del código real. */
  --r-sm: 6px; --r-md: 10px; --r-lg: 14px; --r-xl: 20px;

  /* D2 (2026-07-09): sombra canónica = --shadow-1/2/3 (declaradas en el
     bloque v3 más abajo, var() resuelve por cascada final). --shadow-sm/md/lg
     quedan como alias puros para los ~9 usos legacy .mrj-*, heredando ahora
     el ajuste de modo oscuro que antes no tenían. */
  --shadow-sm: var(--shadow-1);
  --shadow-md: var(--shadow-2);
  --shadow-lg: var(--shadow-3);

  /* --font-heading/--font-body vienen de mrj-palette.css (@import al
     principio del fichero) — antes se declaraban aquí a mano. */

  /* Escala tipográfica (DISENO.md §3 + D4, 2026-07-09): 8 pasos en clamp()
     fluido (móvil 375px → escritorio 1600px+), anclados a 1280px donde cada
     paso da el mismo px que antes (12/13/14/16/18/22/28/40). Cero cambio
     visual a 1280px. */
  --fs-xs:   clamp(0.6946rem, 0.6716rem + 0.098vw, 0.7696rem);  /* 12px @1280 */
  --fs-sm:   clamp(0.7525rem, 0.7276rem + 0.1061vw, 0.8337rem); /* 13px @1280 */
  --fs-base: clamp(0.8104rem, 0.7836rem + 0.1143vw, 0.8979rem); /* 14px @1280 */
  --fs-md:   clamp(0.9261rem, 0.8955rem + 0.1306vw, 1.0261rem); /* 16px @1280 */
  --fs-lg:   clamp(1.0419rem, 1.0074rem + 0.1469vw, 1.1544rem); /* 18px @1280 */
  --fs-xl:   clamp(1.2734rem, 1.2313rem + 0.1796vw, 1.4109rem); /* 22px @1280 */
  --fs-2xl:  clamp(1.6207rem, 1.5671rem + 0.2286vw, 1.7957rem); /* 28px @1280 */
  --fs-3xl:  clamp(2.3153rem, 2.2388rem + 0.3265vw, 2.5653rem); /* 40px @1280 — sin uso real exacto aún */

  /* Transiciones oficiales (usan la curva --ease definida más abajo en el :root v3).
     --t-base lo consume el shell; antes vivía en el bloque .shell eliminado.
     D9 (mejoras.md, presupuesto de motion — ver DISENO.md §Motion):
     --t-fast baja de 150ms a 90ms — causa-efecto (hover, press) debe
     percibirse ≤100ms (regla Linear), y este token es justo el que usan
     TODOS los hover/press del sistema (~45 sitios entre legacy .mrj-* y
     shell). --t-base/--t-slow (transiciones espaciales: apertura de vista,
     paneles, sidebar) se quedan en su rango 150-300ms — el propio DISENO.md
     §Motion documenta 150-250ms como el rango correcto para esas, no para
     causa-efecto. */
  --t-fast:   90ms var(--ease);
  --t-normal: 200ms ease;
  --t-base:   var(--t-shell-base);
  --t-slow:   300ms var(--ease);
}

/* ── Dark mode ───
   Valores oficiales del modo oscuro (antes duplicados en el bloque
   [data-theme="dark"] .shell, ya eliminado). Unificados aquí para legacy + shell. */
[data-theme="dark"] {
  --bg:       #170B04;
  --surface:  #241207;
  /* D3: --surface-0 (=--bg) y --surface-1 (=--surface) no se redeclaran aquí
     a propósito — son alias declarados una sola vez en el :root v3 y ya
     resuelven a los valores oscuros de --bg/--surface de este mismo bloque
     por cascada. --surface-2 sí tiene su propio valor real en oscuro (no es
     alias), se mantiene sin cambio. */
  --surface-2:rgba(234,205,168,.07);
  --brown:    var(--mrj-cream);
  --border:   rgba(234,205,168,.16);
  --muted:    #C9A98F;
  --text:     var(--mrj-cream);
  --sidebar-bg:   #0E0501;
  --sidebar-text: var(--mrj-cream);
  /* Doble capa contacto+ambiente (D48, 2026-07-10): mismo movimiento que su
     par en :root (ver nota ahí) — negro puro 0,0,0 en ambas capas, sin color
     nuevo, mismo criterio de reparto contacto/ambiente. */
  --shadow-1:0 1px 1px rgba(0,0,0,.35), 0 2px 5px rgba(0,0,0,.25);
  --shadow-2:0 2px 4px rgba(0,0,0,.40), 0 10px 26px rgba(0,0,0,.35);
  --shadow-3:0 4px 8px rgba(0,0,0,.45), 0 26px 56px rgba(0,0,0,.40);
  /* Pasada de contraste AA dark mode (MEJORAS-BENCHMARK.md Lista 3 #16):
     --ok/--bad (oliva/teja) sobre los fondos tintados de .pill-ok/.pill-bad,
     .trend-up/.trend-down y el contador de kanban no llegaban a 4.5:1 sobre
     --surface/--surface-2 oscuros (verificado con fórmula WCAG: 3.26/3.00/
     3.29/3.03/2.81/2.61 según el caso). Versión más clara de la misma familia
     oliva/teja, mezclada con --mrj-cream — cumple 4.5:1+ en el peor caso
     (contador de kanban sobre --surface-2) con margen. */
  --ok-text:  #95A271;
  --bad-text: #D88170;
  /* --warn-text en oscuro: mismo valor que ya usaban las 5 reglas
     "[data-theme=dark] .shell .X{color:var(--mrj-orange)}" que este token
     sustituye (naranja de marca, con contraste suficiente sobre --surface
     oscuro). Se centraliza aquí en vez de repetirlo por selector. */
  --warn-text: var(--mrj-orange);
}
/* ── Reset & base ─── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: 16px; -webkit-text-size-adjust: 100%; } /* base rem: NO tokenizar (--fs-* son relativos a esto) */
body {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--text);
  line-height: 1.5;
  min-height: 100vh;
}
a { color: inherit; }

/* ── Foco de teclado (D5, mejoras.md, 2026-07-09) ───
   Anillo de foco ÚNICO para TODA la app — legacy .mrj-* y shell por igual,
   regla GLOBAL (no scoped a .shell, a diferencia de la anterior
   ".shell :focus-visible"). Sustituye 6 reglas parciales que convivían
   (scope propio en .shell, duplicados idénticos en .ficha-variante/.dropzone
   que ya están cubiertos por "button"/"[tabindex]", un tc-filter input con
   alpha/offset distintos sin motivo, y 3 "outline:none" sin anillo de
   respaldo real que se retiraron en esta misma pasada — ver cada sitio).
   :where() mantiene especificidad CERO a propósito: cualquier regla más
   específica que aporte algo EXTRA (halo de .mrj-input, borde de .dropzone)
   sigue pudiendo layerse encima sin pelear por especificidad. */
:where(a,button,input,select,textarea,[tabindex],.section-card,summary):focus-visible{
  outline:2px solid var(--accent);outline-offset:2px;border-radius:4px;
}

/* ── Layout ─── */
.mrj-shell { display: flex; min-height: 100vh; }
.mrj-sidebar {
  width: 240px; background: var(--sidebar-bg); color: var(--sidebar-text);
  display: flex; flex-direction: column; flex-shrink: 0;
  padding: var(--sp-md) 0; overflow-y: auto;
  transition: width var(--t-normal);
  position: fixed; top: 0; left: 0; bottom: 0; z-index: 100;
}
.mrj-sidebar.collapsed { width: 56px; }
.mrj-content {
  flex: 1; margin-left: 240px; padding: var(--sp-xl);
  transition: margin-left var(--t-normal);
  min-height: 100vh;
}
.mrj-sidebar.collapsed ~ .mrj-content { margin-left: 56px; }
.mrj-topbar {
  position: sticky; top: 0; z-index: 50;
  background: var(--bg); border-bottom: 1px solid var(--border);
  padding: var(--sp-md) var(--sp-xl);
  display: flex; align-items: center; justify-content: space-between;
  backdrop-filter: blur(8px);
}

/* ── Sidebar components ─── */
.mrj-nav-logo {
  font-family: var(--font-heading); font-size:var(--fs-lg); color: var(--mrj-cream);
  padding: var(--sp-md) var(--sp-lg); margin-bottom: var(--sp-sm);
  display: flex; align-items: center; gap: var(--sp-sm);
}
.mrj-nav-logo span { color: var(--mrj-orange); }
.mrj-nav-section {
  padding: var(--sp-sm) var(--sp-lg) var(--sp-xs);
  font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  color: rgba(252,236,215,.45); font-weight: 700;
}
.mrj-sidebar.collapsed .mrj-nav-section { opacity: 0; pointer-events: none; }
.mrj-nav-item {
  display: flex; align-items: center; gap: var(--sp-sm);
  padding: 10px var(--sp-lg); cursor: pointer; border: none;
  background: none; color: var(--sidebar-text); font-size:var(--fs-sm);
  font-family: var(--font-body); text-align: left; width: 100%;
  transition: background var(--t-fast), color var(--t-fast);
  border-radius: 0;
}
.mrj-nav-item:hover { background: rgba(255,255,255,.08); }
.mrj-nav-item.active { background: rgba(245,152,0,.2); color: var(--mrj-orange); font-weight: 700; }
.mrj-nav-icon { font-size:var(--fs-md); width: 20px; text-align: center; flex-shrink: 0; }
.mrj-nav-label { flex: 1; white-space: nowrap; overflow: hidden; }
.mrj-sidebar.collapsed .mrj-nav-label { display: none; }
.mrj-nav-badge {
  /* MEJORAS-50 #44 (sitio NO listado en la ficha, encontrado en el barrido:
     usa --mrj-orange directo en vez de --accent, pero es el MISMO #F59800 y
     el mismo 2.24:1). #fff -> var(--mrj-brown) = 5.54:1. */
  background: var(--mrj-orange); color: var(--mrj-brown); font-size: 10px; font-weight: 700;
  min-width: 18px; height: 18px; border-radius: 9px; display: flex;
  align-items: center; justify-content: center; padding: 0 4px;
}
.mrj-sidebar.collapsed .mrj-nav-badge { display: none; }
.mrj-sidebar-toggle {
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-md); border-top: 1px solid rgba(255,255,255,.08);
  margin-top: auto;
}
.mrj-sidebar-toggle button {
  background: none; border: 1px solid rgba(255,255,255,.2);
  border-radius: 50%; width: 30px; height: 30px; cursor: pointer;
  color: var(--sidebar-text); font-size:var(--fs-xs); display: flex;
  align-items: center; justify-content: center; transition: all var(--t-fast);
}
.mrj-sidebar-toggle button:hover { background: rgba(255,255,255,.1); }

/* ── Cards ─── */
.mrj-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--sp-lg);
  box-shadow: var(--shadow-sm); transition: box-shadow var(--t-fast), transform var(--t-fast);
}
.mrj-card:hover { box-shadow: var(--shadow-md); }
.mrj-card-clickable { cursor: pointer; }
.mrj-card-clickable:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }

/* ── KPI cards ─── */
.mrj-kpi-grid { display: grid; grid-template-columns: repeat(auto-fill,minmax(160px,1fr)); gap: var(--sp-md); }
.mrj-kpi {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--sp-lg) var(--sp-md);
  text-align: center;
}
.mrj-kpi-value { font-family: var(--font-heading); font-size:var(--fs-2xl); color: var(--accent); line-height: 1; }
.mrj-kpi-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; margin-top: var(--sp-xs); }
.mrj-kpi-trend { font-size: 11px; margin-top: 4px; }
.mrj-kpi-trend.up { color: var(--ok-text); }
.mrj-kpi-trend.down { color: var(--bad-text); }

/* ── Tables ─── */
.mrj-table-wrap { overflow-x: auto; border-radius: var(--r-md); border: 1px solid var(--border); }
.mrj-table { width: 100%; border-collapse: collapse; font-size:var(--fs-sm); }
.mrj-table th {
  background: var(--bg); color: var(--muted); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; padding: 10px 14px;
  text-align: left; position: sticky; top: 0; z-index: 1;
  border-bottom: 1px solid var(--border);
}
.mrj-table td { padding: 10px 14px; border-bottom: 1px solid var(--border); color: var(--text); }
.mrj-table tr:last-child td { border-bottom: none; }
.mrj-table tr:hover td { background: rgba(245,152,0,.05); }

/* ── Buttons ─── */
.mrj-btn {
  display: inline-flex; align-items: center; gap: var(--sp-xs);
  padding: 8px 16px; border-radius: var(--r-md); border: none;
  font-family: var(--font-body); font-size:var(--fs-sm); font-weight: 600;
  cursor: pointer; transition: all var(--t-fast); white-space: nowrap;
  text-decoration: none;
}
/* MEJORAS-50 #35/#36 (2026-09-02, Bloque D): texto #fff sobre --accent
   (#F59800) daba 2.24:1 — por debajo de AA (4.5:1) incluso para texto
   grande, y --accent no tiene override en [data-theme="dark"] así que
   fallaba igual en ambos temas. var(--mrj-brown) es alias de --accent-800
   (no de --brown, que SÍ cambia en oscuro a --mrj-cream) — mismo valor en
   claro y oscuro, igual que el fondo del botón, así que el arreglo vale
   para los dos temas sin condicional. Verificado numéricamente: 5.54:1
   normal, 6.37:1 en hover (el filter:brightness aclara el fondo, así que
   el contraste MEJORA con el hover en vez de empeorar). No se toca
   --accent: lo usan otras 54 reglas de este fichero como color de texto/
   borde/icono sobre --bg/--surface, donde sigue siendo AA. */
.mrj-btn-primary { background: var(--accent); color: var(--mrj-brown); }
.mrj-btn-primary:hover { filter: brightness(1.1); }
.mrj-btn-secondary { background: var(--surface); color: var(--brown); border: 1px solid var(--border); }
.mrj-btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
.mrj-btn-ghost { background: none; color: var(--muted); }
.mrj-btn-ghost:hover { color: var(--brown); background: var(--bg); }
.mrj-btn-danger { background: var(--bad); color: #fff; }
.mrj-btn-danger:hover { filter: brightness(1.1); }
.mrj-btn:disabled { opacity: .5; cursor: not-allowed; transform: none; }
.mrj-btn-sm { padding: 5px 10px; font-size:var(--fs-xs); }
.mrj-btn-lg { padding: 12px 24px; font-size: 15px; }
.mrj-btn-icon { width: 36px; height: 36px; padding: 0; border-radius: 50%; justify-content: center; }

/* ── Form elements ─── */
.mrj-input, .mrj-select, .mrj-textarea {
  width: 100%; padding: 9px 12px; border: 1px solid var(--border);
  border-radius: var(--r-md); font-family: var(--font-body); font-size:var(--fs-base);
  background: var(--surface); color: var(--text);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.mrj-input:focus, .mrj-select:focus, .mrj-textarea:focus {
  /* D5: "outline:none" retirado — el anillo global (regla en "Reset & base")
     ya cubre "input"/"select"/"textarea"; el halo de abajo es lo que esta
     regla aporta de EXTRA (no se toca) y ahora convive con el anillo en vez
     de anularlo. */
  border-color: var(--accent); box-shadow: 0 0 0 3px rgba(245,152,0,.15);
}
.mrj-input::placeholder { color: var(--muted); }
/* font-size:16px en móvil (auditoría móvil 2026-08-05): mismo motivo que
   .shell .input más abajo — por debajo de 16px iOS hace zoom automático al
   enfocar el campo. .mrj-input es la clase que sigue usando Mi Espacio (el
   móvil real de las empleadas), así que este arreglo es más urgente aquí
   que en el panel admin. Solo dentro de @media(max-width:768px). */
@media (max-width: 768px) {
  .mrj-input, .mrj-select, .mrj-textarea { font-size: 16px; }
}
.mrj-label { display: block; font-size:var(--fs-xs); font-weight: 700; color: var(--muted); margin-bottom: 4px; text-transform: uppercase; letter-spacing: .05em; }
.mrj-form-group { display: flex; flex-direction: column; gap: var(--sp-xs); }
.mrj-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-md); }

/* ── Chips/filters ─── */
.mrj-chips { display: flex; gap: var(--sp-xs); flex-wrap: wrap; }
.mrj-chip {
  padding: 4px 12px; border-radius: 20px; font-size:var(--fs-xs); font-weight: 600;
  border: 1px solid var(--border); background: var(--surface); color: var(--muted);
  cursor: pointer; transition: all var(--t-fast);
}
/* MEJORAS-50 #44 — mismo criterio que #35/#36: #fff sobre --accent da
   2.24:1 (AA roto); var(--mrj-brown) da 5.54:1. --mrj-brown es alias de
   --accent-800, que no cambia entre tema claro y oscuro → un solo valor
   sirve para los dos temas. No se toca --accent ni ningún fondo. */
.mrj-chip.active, .mrj-chip:hover { background: var(--accent); color: var(--mrj-brown); border-color: var(--accent); }

/* ── Badges ─── */
.mrj-badge { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 20px; font-size: 11px; font-weight: 700; }
/* C4 (purga hex fuera de paleta, 2026-07-07): los 4 badges de abajo usaban
   hexes Tailwind (verde/rojo/naranja/AZUL — el azul no existe en la paleta
   oficial de marca bajo ningún concepto). Sustituidos por tintes rgba() de
   los tokens oficiales (--ok/--bad/--accent/--mrj-mocha), mismo patrón ya
   usado en .pill-*/.cal-evt/.cat-* de este fichero. Texto en var(--brown)
   en vez de --ok-text/--bad-text: verificado con fórmula WCAG que esos dos
   tokens, pensados para uso en superficies sólidas o como acento, no llegan
   a 4.5:1 sobre un badge pastel (--ok-text da 3.5-4.2:1 según fondo real);
   --brown sobre estos mismos tintes da 9-11:1 en todos los casos. */
.mrj-badge-green { background: rgba(94,122,58,.16); color: var(--brown); }
.mrj-badge-red { background: rgba(192,57,43,.14); color: var(--brown); }
.mrj-badge-orange { background: rgba(245,152,0,.18); color: var(--brown); }
.mrj-badge-blue { background: rgba(140,101,79,.20); color: var(--brown); }
.mrj-badge-gray { background: var(--bg); color: var(--muted); border: 1px solid var(--border); }
.mrj-badge-brown { background: var(--mrj-brown); color: var(--mrj-cream); }

/* ── Toast notifications ─── */
#mrj-toasts { position: fixed; bottom: 24px; right: 24px; z-index: 9999; display: flex; flex-direction: column; gap: 8px; pointer-events: none; }
.mrj-toast {
  background: var(--brown); color: var(--mrj-cream); padding: 12px 18px;
  border-radius: var(--r-lg); font-size:var(--fs-sm); max-width: 320px;
  box-shadow: var(--shadow-lg); animation: mrj-toast-in 200ms ease;
  display: flex; align-items: center; gap: 10px; pointer-events: auto;
}
.mrj-toast.success { background: var(--ok); }
.mrj-toast.error { background: var(--bad); }
.mrj-toast.warning { background: var(--accent); color: var(--mrj-brown); }
@keyframes mrj-toast-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }

/* ── Skeletons ─── */
/* FASE 1 (2026-07-07): eliminado el linear-gradient que llevaba desde v1 —
   viola la regla de marca "sin degradados, nunca" (DISENO.md §1/§7). Sin
   consumidores reales (verificado con grep: solo se referenciaba a sí mismo
   en este fichero), así que se sustituye por el MISMO pulso de opacidad ya
   usado por el skeleton del shell nuevo (.shell .skeleton / shellSkelPulse,
   ver más abajo) en vez de dejar una animación rota o un segundo sistema
   paralelo. */
.mrj-skel {
  background: rgba(139,101,79,.16); animation: mrj-shimmer 1.4s ease-in-out infinite;
  border-radius: var(--r-sm);
}
[data-theme="dark"] .mrj-skel { background: rgba(234,205,168,.14); }
@keyframes mrj-shimmer { 0%,100% { opacity: .55; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .mrj-skel { animation: none; opacity: .75; } }
.mrj-skel-text { height: 14px; margin: 4px 0; border-radius: 4px; }
.mrj-skel-card { height: 80px; border-radius: var(--r-lg); }
.mrj-skel-circle { border-radius: 50%; }

/* ── Skeleton loaders del shell (Mejoras.md Lista 3) ───
   Sustituyen el texto plano "Cargando…"/"Consultando…" en las vistas de
   Inicio/Dashboard más visibles. A diferencia de .mrj-skel (legacy, no usado
   hoy en el shell) que anima con linear-gradient, esta versión usa un pulso
   de OPACIDAD puro sobre un bloque sólido en tono mocha/sand a baja opacidad
   — cero gradientes (regla de marca inquebrantable, DISENO.md §1/§7), y
   coincide literalmente con el vocabulario ya fijado en DISENO.md §4:
   "Skeleton | Bloques sand pulsantes mientras carga." */
.shell .skeleton{
  display:block;background:rgba(139,106,79,.16);border-radius:var(--r-sm);
  animation:shellSkelPulse 1.4s ease-in-out infinite;
  position:relative;overflow:hidden;
}
[data-theme="dark"] .shell .skeleton{background:rgba(234,205,168,.14)}
@keyframes shellSkelPulse{0%,100%{opacity:.55}50%{opacity:1}}
/* D14 (mejoras.md, 2026-07-09): shimmer direccional ADITIVO sobre el pulso de
   opacidad de arriba — barrido de luz izquierda→derecha. Pseudo-elemento de
   color SÓLIDO vía color-mix() (sand/transparente, sin degradado — la regla
   de marca "sin gradientes" se cumple literalmente: ningún linear-gradient/
   radial-gradient en esta declaración), difuminado con filter:blur para que
   se lea como una franja de luz suave en vez de un bloque de color duro. La
   única propiedad animada es transform:translateX (D9 motion budget: solo
   transform/opacity), en vez de animar background-position de un gradiente
   como haría la técnica "clásica" de shimmer. */
.shell .skeleton::after{
  content:'';position:absolute;top:0;bottom:0;left:0;width:40%;
  background:color-mix(in srgb, var(--mrj-sand) 55%, transparent);
  filter:blur(8px);pointer-events:none;
  animation:shellSkelShimmer 1.4s ease-in-out infinite;
}
[data-theme="dark"] .shell .skeleton::after{background:color-mix(in srgb, var(--mrj-cream) 30%, transparent)}
@keyframes shellSkelShimmer{
  0%{transform:translateX(-150%)}
  100%{transform:translateX(350%)}
}
@media (prefers-reduced-motion:reduce){
  .shell .skeleton{animation:none;opacity:.75}
  .shell .skeleton::after{display:none}
}
.shell .skeleton-line{height:12px;margin:6px 0}
.shell .skeleton-line.sm{height:9px}
.shell .skeleton-line.lg{height:20px}
.shell .skeleton-line.w-30{width:30%}.shell .skeleton-line.w-40{width:40%}
.shell .skeleton-line.w-50{width:50%}.shell .skeleton-line.w-60{width:60%}
.shell .skeleton-line.w-70{width:70%}.shell .skeleton-line.w-80{width:80%}
.shell .skeleton-line.w-100{width:100%}
.shell .skeleton-card{height:84px;border-radius:var(--r-lg)}
/* Skeleton de una tarjeta .kpi completa: mismo tamaño/marco que un .kpi real
   (sin cursor pointer ni hover) para que el layout no salte al llegar el dato. */
.shell .kpi.skeleton-kpi{cursor:default}
.shell .kpi.skeleton-kpi:hover{transform:none;box-shadow:none;border-color:var(--border)}

/* ── Empty states ─── */
.mrj-empty { text-align: center; padding: var(--sp-2xl); color: var(--muted); }
.mrj-empty-icon { font-size: 48px; margin-bottom: var(--sp-md); opacity: .5; }
.mrj-empty-title { font-family: var(--font-heading); font-size:var(--fs-lg); color: var(--brown); margin-bottom: var(--sp-sm); }
.mrj-empty-desc { font-size:var(--fs-sm); line-height: 1.6; }
.mrj-empty-cta { margin-top: var(--sp-md); display: inline-flex; }
/* FASE 3 (2026-07-07, migrador-os): slot de accion real dentro del empty-state. Uso: <div class="mrj-empty"><div class="mrj-empty-desc">texto</div><button class="btn btn-primary btn-sm mrj-empty-cta">Accion</button></div>. Aditivo, no toca el unico uso previo (Facturas/Anual), que no usaba mrj-empty-cta. */

/* ── Estado de error (MEJORAS-50 #41) ───
   Región que NO ha podido pintarse: motivo REAL del backend + una acción que
   repite la MISMA carga. Sin prefijo .shell a propósito → vale igual en el
   shell SPA, /mi-espacio, /b2b/admin y /b2b/portal. Mismos valores exactos
   que .mrj-alert-error (cero color nuevo, cero degradado). Componente y regla
   de uso: public/lmos-error-state.js + DISENO.md §5bis. El toast de
   cargarConReintento() (app.js) NO se sustituye por esto: toast = una acción
   puntual falla; esto = una región se queda sin poder pintarse. */
.mrj-error-state { padding: var(--sp-lg); border-radius: var(--r-md); background: rgba(192,57,43,.12); border: 1px solid rgba(192,57,43,.35); color: var(--brown); font-size: var(--fs-sm); line-height: 1.5; }
.mrj-error-state-msg { color: var(--brown); }
.mrj-error-state-cta { margin-top: var(--sp-md); display: inline-flex; }

/* ── Alerts ─── */
.mrj-alert { padding: var(--sp-md); border-radius: var(--r-md); font-size:var(--fs-sm); display: flex; align-items: flex-start; gap: 10px; line-height: 1.5; }
/* C4: mismo criterio que los badges de arriba — tintes rgba() de los
   tokens oficiales + texto var(--brown) (9-11:1, ver comentario de badges). */
.mrj-alert-warning { background: rgba(245,152,0,.12); border: 1px solid rgba(245,152,0,.35); color: var(--brown); }
.mrj-alert-error { background: rgba(192,57,43,.12); border: 1px solid rgba(192,57,43,.35); color: var(--brown); }
.mrj-alert-success { background: rgba(94,122,58,.14); border: 1px solid rgba(94,122,58,.35); color: var(--brown); }
.mrj-alert-info { background: var(--bg); border: 1px solid var(--border); color: var(--brown); }

/* ── Divider ─── */
.mrj-divider { border: none; border-top: 1px solid var(--border); margin: var(--sp-lg) 0; }

/* ── Modal ─── */
.mrj-modal-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-lg);
}
.mrj-modal {
  background: var(--surface); border-radius: var(--r-xl); padding: var(--sp-xl);
  width: 100%; max-width: 520px; box-shadow: var(--shadow-lg);
  animation: mrj-modal-in 200ms ease;
}
@keyframes mrj-modal-in { from { opacity:0; transform: scale(.95); } to { opacity:1; transform: scale(1); } }
.mrj-modal-title { font-family: var(--font-heading); font-size: clamp(17px, .58vw + 14.7px, 23px); color: var(--brown); margin-bottom: var(--sp-lg); text-wrap: balance; }

/* ── Diálogos unificados (<dialog> nativo) ───
   3 variantes lmos-dialog-{centrado,paleta,panel}, reutilizando los tokens
   de los patrones que sustituyen (.mrj-modal, .hist-modal/.cmdk, .assist-panel).
   Requiere un <div class="lmos-dialog-box"> interno porque <dialog> fusiona
   overlay+caja en un único elemento; ese div es contra el que se compara
   e.target para seguir detectando "clic fuera cierra". Piloto en
   #lmosShortcutsOverlay (centrado); el resto de sitios legacy sigue sin
   migrar (ver tracker). */
.lmos-dialog {
  border: none; padding: 0; margin: 0; background: transparent; color: inherit;
  max-width: none; max-height: none;
}
.lmos-dialog::backdrop { background: rgba(19,7,2,.5); }

/* Variante centrado — reemplazo de .mrj-modal-overlay/.mrj-modal */
.lmos-dialog-centrado[open] {
  position: fixed; inset: 0; width: 100vw; height: 100vh;
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-lg);
}
.lmos-dialog-centrado .lmos-dialog-box {
  background: var(--surface); border-radius: var(--r-xl); padding: var(--sp-xl);
  width: 100%; max-width: 520px; box-shadow: var(--shadow-lg);
  animation: mrj-modal-in 200ms ease;
}

/* Variante paleta — reemplazo de .hist-modal/.cmdk (⌘K, historial…) */
.lmos-dialog-paleta[open] {
  position: fixed; top: 14vh; left: 50%; transform: translateX(-50%);
  width: 560px; max-width: 92vw; max-height: 74vh;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-3); overflow: hidden;
  display: flex; flex-direction: column;
  animation: lmos-dialog-paleta-in var(--t-shell-base, 200ms ease);
}
@keyframes lmos-dialog-paleta-in {
  from { opacity: 0; transform: translateX(-50%) scale(.97); }
  to { opacity: 1; transform: translateX(-50%) scale(1); }
}

/* Variante panel lateral — reemplazo de .assist-panel */
.lmos-dialog-panel[open] {
  position: fixed; inset: 0 0 0 auto; right: 0;
  width: 420px; max-width: 92vw; height: 100vh;
  background: var(--surface); border-left: 1px solid var(--border);
  box-shadow: var(--shadow-3);
  display: flex; flex-direction: column;
  animation: lmos-dialog-panel-in var(--t-slow, 250ms ease);
}
@keyframes lmos-dialog-panel-in {
  from { transform: translateX(100%); }
  to { transform: translateX(0); }
}
/* MEJORAS-50 #11 (2026-07-08): contenido interno del panel — mismo padding
   que .lmos-dialog-box, con scroll propio (el <dialog> no scrollea, su
   contenido sí) para fichas largas sin perder la cabecera fija. */
.lmos-dialog-panel-body { padding: var(--sp-xl); overflow-y: auto; flex: 1; }
.lmos-dialog-panel-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: var(--sp-lg) var(--sp-xl); border-bottom: 1px solid var(--border); flex-shrink: 0;
}

/* Variante ficha — VENTANA GRANDE centrada (pedido de Joni, 2026-07-08: el
   detalle de producto deja de ser panel lateral). Reutiliza panel-head/body
   para cabecera fija + cuerpo con scroll. En móvil ocupa la pantalla entera
   (hoja completa). Fondo interior var(--bg) para que las tarjetas de la
   ficha respiren como en una vista normal. */
.lmos-dialog-ficha[open] {
  position: fixed; inset: 0; width: 100vw; height: 100dvh;
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-lg);
}
.lmos-dialog-ficha .lmos-dialog-box {
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--r-xl);
  width: min(1080px, 96vw); height: min(88dvh, 920px);
  box-shadow: var(--shadow-3); display: flex; flex-direction: column; overflow: hidden;
  padding: 0; max-width: none;
  animation: lmos-ficha-in 220ms var(--ease);
}
.lmos-dialog-ficha .lmos-dialog-panel-head { background: var(--surface); }
@keyframes lmos-ficha-in {
  from { opacity: 0; transform: translateY(10px) scale(.98); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .lmos-dialog-ficha .lmos-dialog-box { animation: none; } }

/* Variante documento — VENTANA GRANDE Y CENTRADA para formularios largos de
   documento (albarán, factura B2B, presupuesto). Pedido de Joni (2026-08-04):
   "las ventanas para editar/crear los documentos que salga más grande en la
   parte central, no en la columna derecha" — sustituye a .lmos-dialog-panel
   para estos casos concretos, sin tocar el panel lateral que sigue vivo para
   el Asistente. Cabecera fija + cuerpo con scroll propio en clases dedicadas
   (-documento-head/-documento-body, no las compartidas -panel-head/-panel-body)
   para que el HTML que la consuma no dependa por accidente de nombres
   pensados para el panel lateral. Mismos tokens que el resto de variantes
   (var(--surface), var(--r-xl), var(--sp-xl/--sp-lg), var(--shadow-lg), la
   keyframe mrj-modal-in) — ningún color/sombra nuevo. */
.lmos-dialog-documento[open] {
  position: fixed; inset: 0; width: 100vw; height: 100dvh;
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-lg);
}
.lmos-dialog-documento .lmos-dialog-box {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-xl);
  width: min(1100px, 94vw); max-height: 90vh;
  box-shadow: var(--shadow-lg); display: flex; flex-direction: column; overflow: hidden;
  padding: 0; max-width: none;
  animation: mrj-modal-in 200ms ease;
}
.lmos-dialog-documento-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: var(--sp-lg) var(--sp-xl); border-bottom: 1px solid var(--border); flex-shrink: 0;
  background: var(--surface);
}
.lmos-dialog-documento-body { padding: var(--sp-xl); overflow-y: auto; flex: 1; }
@media (prefers-reduced-motion: reduce) { .lmos-dialog-documento .lmos-dialog-box { animation: none; } }

/* Variante documento en móvil — mismo patrón de hoja inferior que D39 (abajo),
   pero en bloque APARTE para no modificar el selector agrupado ya existente
   de .lmos-dialog-centrado/.lmos-dialog-ficha. Reutiliza la keyframe
   lmos-sheet-in declarada más abajo (nombre global, no hace falta
   redeclararla). */
@media (max-width: 768px) {
  .lmos-dialog-documento[open] { align-items: flex-end; padding: 0; }
  .lmos-dialog-documento .lmos-dialog-box {
    width: 100vw; max-width: 100vw; height: auto; max-height: 92dvh;
    border-radius: 20px 20px 0 0; border: none; position: relative;
    padding-bottom: env(safe-area-inset-bottom);
    animation: lmos-sheet-in 220ms var(--ease);
  }
}
@media (max-width: 768px) and (prefers-reduced-motion: reduce) {
  .lmos-dialog-documento .lmos-dialog-box { animation: none; }
}

/* D39 (mejoras.md, 2026-07-09): en móvil, los diálogos centrados
   (.lmos-dialog-centrado) y la ficha (.lmos-dialog-ficha) dejan de aparecer
   en el centro/pantalla completa y pasan a HOJA INFERIOR (bottom sheet,
   patrón nativo de app): caja anclada abajo, ancho completo, solo redondeada
   arriba, deslizando desde abajo al abrir. SOLO CSS (media query) — no toca
   el JS de los diálogos, sigue siendo el mismo <dialog> con el mismo
   open/close y el mismo "clic fuera cierra" contra .lmos-dialog-box. La
   paleta ⌘K (.lmos-dialog-paleta) NO entra aquí — se queda arriba, sin
   cambios, tal como pide el pedido. La curva --spring-1 (D10, arriba) ya
   cubre estos dos selectores sin declararla otra vez aquí — su regla
   @supports es incondicional (no depende de viewport) y va DESPUÉS de este
   bloque en el fichero, así que su animation-timing-function gana también
   en móvil sobre el "animation" shorthand de aquí abajo. */
@media (max-width: 768px) {
  .lmos-dialog-centrado[open],
  .lmos-dialog-ficha[open] {
    align-items: flex-end; padding: 0;
  }
  .lmos-dialog-centrado .lmos-dialog-box,
  .lmos-dialog-ficha .lmos-dialog-box {
    width: 100vw; max-width: 100vw; height: auto; max-height: 92dvh;
    border-radius: 20px 20px 0 0; border: none; position: relative;
    padding-bottom: env(safe-area-inset-bottom);
    animation: lmos-sheet-in 220ms var(--ease);
  }
  /* "Asa" visual de la hoja — puramente decorativa, no añade gesto de
     arrastre nuevo (el cierre sigue siendo el botón/clic-fuera de siempre). */
  .lmos-dialog-centrado .lmos-dialog-box::before,
  .lmos-dialog-ficha .lmos-dialog-box::before {
    content: ''; position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
    width: 36px; height: 4px; border-radius: var(--r-full); background: var(--sand);
  }
  @keyframes lmos-sheet-in {
    from { transform: translateY(100%); }
    to { transform: translateY(0); }
  }
}
/* Redeclarado con el mismo alcance móvil para no depender del orden con las
   reglas de reduced-motion de más arriba/abajo (D10) — "prefers-reduced-motion"
   se respeta siempre, sin excepción de breakpoint. */
@media (max-width: 768px) and (prefers-reduced-motion: reduce) {
  .lmos-dialog-centrado .lmos-dialog-box,
  .lmos-dialog-ficha .lmos-dialog-box {
    animation: none;
  }
}

/* D10 (mejoras.md, 2026-07-09): física de muelle en la entrada de los 3
   overlays con caja propia (centrado/paleta/ficha) — SOLO cambia
   animation-timing-function (longhand, se declara aparte de la duración que
   ya fijaba el "animation" shorthand de cada regla, sin tocar duración ni
   keyframes). Fallback real: navegadores sin soporte de linear() ignoran
   toda la declaración de @supports y se quedan con el timing-function
   original (ease / var(--t-shell-base)) que ya tenían — mismo patrón de
   @supports ya usado en este fichero para "animation-timeline: scroll()".
   No se toca .lmos-dialog-panel (el panel lateral del Asistente) ni
   .mrj-modal (comparten o no la keyframe pero D10 solo pide estos 3). */
@supports (animation-timing-function: linear(0, 1)) {
  .lmos-dialog-centrado .lmos-dialog-box,
  .lmos-dialog-paleta[open],
  .lmos-dialog-ficha .lmos-dialog-box {
    animation-timing-function: var(--spring-1);
  }
}
@media (prefers-reduced-motion: reduce) {
  .lmos-dialog-centrado .lmos-dialog-box,
  .lmos-dialog-paleta[open] {
    animation: none;
  }
}

/* Física de muelle también para la variante documento — mismo criterio que
   D10 arriba, en bloque propio para no ampliar el selector agrupado
   original (que solo cubre las 3 variantes que ya existían cuando se
   escribió D10). Va DESPUÉS del bloque base de la variante (arriba) para
   ganar sobre el "animation" shorthand, igual que exige la nota de D10. */
@supports (animation-timing-function: linear(0, 1)) {
  .lmos-dialog-documento .lmos-dialog-box {
    animation-timing-function: var(--spring-1);
  }
}

/* Selector de variantes dentro de la ficha: cuando un producto tiene varias
   variantes, primero se elige cuál abrir (tarjetas clicables, mismo lenguaje
   de hover que .recipe). */
.shell .ficha-variantes { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--sp-4); }
.shell .ficha-variante {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-4); cursor: pointer; text-align: left;
  font-family: var(--font-body); color: var(--text); font-size: var(--fs-sm);
  display: flex; flex-direction: column; gap: 8px;
  transition: transform var(--t-base), box-shadow var(--t-base), border-color var(--t-base);
}
.shell .ficha-variante:hover { transform: translateY(-3px); box-shadow: var(--shadow-2); border-color: rgba(245,152,0,.5); }
/* D5: regla propia de focus-visible retirada — .ficha-variante es un
   <button> real (modules/costes/view.js), ya cubierto por el anillo global
   de "Reset & base" sin aportar nada distinto. */
.shell .ficha-variante .fv-nombre { font-weight: 800; font-size: var(--fs-md); display: flex; align-items: center; gap: 8px; justify-content: space-between; }
.shell .ficha-variante .fv-datos { display: flex; justify-content: space-between; gap: 8px; font-size: 12.5px; color: var(--muted); }

/* ── Tarjetas de Ingredientes (rediseño 2026-07-30, Joni: "más recogidas,
   solo formato/unidad/proveedor/precio kg-L, el resto en la ventana de
   detalle") — mismo lenguaje que .ficha-variante de arriba. */
.shell .ing-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--sp-4); }
/* Grids largos (Ingredientes/Packaging/Limpieza en Costes): alto acotado con scroll propio,
   mismo patrón que .tablecard.tc-scroll pero sin thead sticky (no es una tabla). */
.shell .ing-grid.tc-scroll{max-height:65vh;overflow-y:auto}
.shell .ing-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp-4); cursor: pointer; text-align: left; width: 100%;
  font-family: var(--font-body); color: var(--text);
  display: flex; flex-direction: column; gap: 8px;
  transition: transform var(--t-base), box-shadow var(--t-base), border-color var(--t-base);
}
.shell .ing-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-2); border-color: rgba(245,152,0,.5); }
.shell .ing-card-nombre { font-weight: 800; font-size: var(--fs-md); line-height: 1.25; }
.shell .ing-card-proveedor { font-size: 12.5px; color: var(--muted); margin-top: -4px; }
.shell .ing-card-precios { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 10px; margin-top: 4px; }
.shell .ing-card-precio-label { font-size: 10.5px; letter-spacing: .04em; color: var(--muted); text-transform: uppercase; }
.shell .ing-card-precio-valor { font-weight: 800; font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.shell .ing-card-kgl { grid-column: 1 / -1; border-top: 1px solid var(--border); padding-top: 6px; margin-top: 2px; display: flex; justify-content: space-between; align-items: baseline; }
.shell .ing-card-kgl .ing-card-precio-valor { color: var(--brown); font-size: var(--fs-md); }

/* ── Mobile hamburger / overlay ─── */
.mrj-hamburger {
  display: none; align-items: center; justify-content: center;
  position: fixed; top: 12px; left: 14px; z-index: 200;
  width: 40px; height: 40px; background: var(--brown); border-radius: 50%;
  border: none; cursor: pointer; color: var(--mrj-cream); font-size:var(--fs-lg);
  box-shadow: var(--shadow-md);
}
.mrj-overlay {
  display: none; position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 99;
}
.mrj-overlay.active { display: block; }

/* ── Bottom nav (mobile) ─── */
.mrj-bottom-nav {
  display: none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 100;
  background: var(--sidebar-bg); border-top: 1px solid rgba(255,255,255,.08);
  height: 58px; padding-bottom: env(safe-area-inset-bottom);
}
.mrj-bottom-nav-item {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  color: rgba(252,236,215,.6); font-size: 10px; gap: 2px; cursor: pointer;
  border: none; background: none; font-family: var(--font-body);
  transition: color var(--t-fast);
}
.mrj-bottom-nav-item.active { color: var(--mrj-orange); }
.mrj-bottom-nav-item span:first-child { font-size: 20px; }

/* ── Mobile ─── */
@media (max-width: 768px) {
  .mrj-sidebar {
    transform: translateX(-100%); transition: transform var(--t-normal);
    width: 260px !important;
  }
  .mrj-sidebar.open { transform: translateX(0); box-shadow: var(--shadow-lg); }
  .mrj-content { margin-left: 0 !important; padding: var(--sp-md); padding-bottom: 70px; }
  .mrj-hamburger { display: flex !important; }
  .mrj-bottom-nav { display: flex; }
  .mrj-form-row { grid-template-columns: 1fr; }
}
@media (min-width: 769px) {
  .mrj-hamburger, .mrj-bottom-nav { display: none !important; }
}

/* ── Print ─── */
@media print {
  .mrj-sidebar, .mrj-topbar, .mrj-bottom-nav, .mrj-hamburger, #mrj-toasts, .mrj-modal-overlay { display: none !important; }
  .mrj-content { margin-left: 0 !important; padding: 0 !important; }
  body { background: white; color: black; }
  .mrj-card { box-shadow: none; border: 1px solid #ccc; }
}

/* ── Utility classes ─── */
.mrj-flex { display: flex; }
.mrj-flex-col { display: flex; flex-direction: column; }
.mrj-grid { display: grid; }
.mrj-items-center { align-items: center; }
.mrj-justify-between { justify-content: space-between; }
.mrj-gap-xs { gap: var(--sp-xs); }
.mrj-gap-sm { gap: var(--sp-sm); }
.mrj-gap-md { gap: var(--sp-md); }
.mrj-gap-lg { gap: var(--sp-lg); }
.mrj-text-muted { color: var(--muted); }
.mrj-text-accent { color: var(--accent); }
.mrj-text-brown { color: var(--brown); }
.mrj-text-sm { font-size:var(--fs-xs); }
.mrj-text-base { font-size:var(--fs-base); }
.mrj-text-lg { font-size:var(--fs-md); }
.mrj-font-heading { font-family: var(--font-heading); }
.mrj-font-bold { font-weight: 700; }
.mrj-mt-xs { margin-top: var(--sp-xs); }
.mrj-mt-sm { margin-top: var(--sp-sm); }
.mrj-mt-md { margin-top: var(--sp-md); }
.mrj-mt-lg { margin-top: var(--sp-lg); }
.mrj-mb-xs { margin-bottom: var(--sp-xs); }
.mrj-mb-sm { margin-bottom: var(--sp-sm); }
.mrj-mb-md { margin-bottom: var(--sp-md); }
.mrj-mb-lg { margin-bottom: var(--sp-lg); }
.mrj-p-md { padding: var(--sp-md); }
.mrj-p-lg { padding: var(--sp-lg); }
.mrj-w-full { width: 100%; }
.mrj-section-title { font-family: var(--font-heading); font-size:var(--fs-xl); color: var(--brown); margin-bottom: var(--sp-lg); }
.mrj-section-subtitle { font-size:var(--fs-sm); color: var(--muted); margin-top: -12px; margin-bottom: var(--sp-lg); }
.mrj-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mrj-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; }
.mrj-nowrap { white-space: nowrap; }
.mrj-text-center { text-align: center; }
.mrj-text-right { text-align: right; }
.mrj-rounded { border-radius: var(--r-md); }
.mrj-rounded-lg { border-radius: var(--r-lg); }
.mrj-shadow { box-shadow: var(--shadow-md); }
.mrj-border { border: 1px solid var(--border); }
.mrj-surface { background: var(--surface); }

/* ═══
   LaMaruja OS · Design System v3 — shell SPA (Fase 0)
   ───
   Añadido de forma NO destructiva sobre el v1/v2 de arriba: las páginas de
   módulo que aún se sirven con lib/render.js (Costes/Sanidad/Facturas/Marujeo)
   siguen usando exclusivamente las clases .mrj-* y los tokens :root originales
   de más arriba — no se han tocado.
   Los tokens del mockup v3 que colisionaban de NOMBRE con los v1 pero con
   VALOR distinto (--mrj-brown, --mrj-ink, --surface, --muted, --border,
   --t-fast, --t-slow) se han aislado bajo ".shell" para que solo afecten al
   nuevo shell SPA. El resto de tokens nuevos (sin colisión) se declaran en
   :root global sin riesgo. Fuente de diseño: mockup verificado en
   /Users/jgar/Claude/LaMaruja OS/mockup/index.html (guardián de diseño).
   ═══ */
:root{
  /* D3 (mejoras.md, 2026-07-09): jerarquía explícita de superficies, en
     puros ALIAS de tokens ya existentes — cero cambio visual, "queda
     DISPONIBLE" para quien anide tarjeta-dentro-de-tarjeta sin resolver el
     contraste caso a caso. --surface-0 = fondo de página, --surface-1 =
     tarjeta, --surface-2 = elemento sobre una tarjeta (ya existía, se deja
     su valor intacto). Al ser alias de --bg/--surface (declarados arriba,
     con su propio override en [data-theme="dark"]), --surface-0/1 heredan el
     modo oscuro automáticamente sin necesitar una segunda declaración en ese
     bloque — por eso no se repiten ahí, solo se documenta la equivalencia. */
  --surface-0: var(--bg);
  --surface-1: var(--surface);
  /* tokens nuevos sin colisión con v1 — seguros globalmente */
  /* D1: NO derivables de la escala --accent-* — croma propio irreproducible
     con el multiplicador común de su parada (ver nota "NO migrados" en el
     bloque D1 del primer :root). Se quedan como hex. */
  --ok:#5E7A3A; --bad:#C0392B;
  /* --ok-text/--bad-text: su valor por defecto (claro) se declara arriba, en
     el primer bloque :root del fichero — NO aquí. Motivo (bug encontrado en
     QA visual, 2026-07-07): con la misma especificidad que "[data-theme=dark]",
     una redeclaración aquí (que aparece DESPUÉS del override oscuro más abajo
     en el fichero) ganaría por orden de cascada y anularía el override oscuro
     en cada carga. Ver comentario junto a su declaración real más arriba.
     --surface-2 (D47, 2026-07-09) tenía ESTE MISMO bug de verdad — vivía
     aquí sin scope y anulaba el --surface-2 de [data-theme="dark"] en cada
     carga oscura (4.03:1 real en vez de 7.13:1 en .pill-mut, confirmado con
     Puppeteer). Movido junto a --warn-text en el primer :root, mismo valor
     .28, ver el comentario allí. */
  --ease:cubic-bezier(.22,.61,.36,1);
  --t-shell-base:200ms var(--ease);
  /* D10 (mejoras.md, 2026-07-09): curva "muelle" para la entrada de overlays
     (ver bloque @supports junto a los 3 <dialog> de la Variante centrado/
     paleta/ficha, más abajo). linear() (CSS 2025, sin JS) describe un
     rebote muy sutil: sube hasta 1.015 (overshoot del 1.5%, el máximo
     pedido) y se asienta en 1 — perceptible como "muelle" sin llegar a
     bambolearse, coherente con el tono "serio" de DISENO.md §5. */
  --spring-1: linear(0, 0.32 9.1%, 0.65 20.9%, 0.89 34.3%, 1.015 51.8%, 0.998 66.9%, 1.004 83%, 1 100%);
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px;
  --sp-5:24px; --sp-6:32px; --sp-7:48px; --sp-8:64px;
  /* (2026-08-29): escala de espaciado ya existente y en uso extendido —
     no se reescala a 20/24/32/40 para --sp-5..8 porque cientos de reglas
     ya dependen de estos valores (24/32/48/64, vía los alias --sp-lg/
     --sp-xl/--sp-2xl de arriba); cambiar los números aquí sería un
     rediseño visual de toda la app, no una migración de utilidades. Las
     clases u-mt-*/u-mb-*/u-p-* de lamaruja-util.css quedan migradas a
     estos mismos tokens (ver ese fichero). */
  /* --tap-min (2026-07-10): área mínima de toque (WCAG 2.5.5 / Apple HIG),
     NO el tamaño visual de un control — usar como min-width/min-height del
     hitbox real, no como width/height del elemento pintado. */
  --tap-min: 44px;
  --r-full:999px;
  /* --shadow-1/2/3 (D47, 2026-07-09): MISMO bug de --surface-2 (ver nota
     junto a --warn-text, primer :root) — vivían aquí, después de
     [data-theme="dark"], y anulaban el shadow-1/2/3 oscuro (rgba negro) en
     cada carga con tema oscuro, sustituyéndolo por este mismo shadow claro
     (rgba marrón .10/.14/.20). No es un fallo de contraste (una sombra no
     tiene criterio WCAG propio) pero sí deshacía en silencio el trabajo de
     D2 "un solo sistema de sombras" en oscuro — se corrige con el mismo
     movimiento: valor por defecto en el primer :root (ver junto a
     --warn-text), sin tocar el valor de [data-theme="dark"]. */
  /* D4 (purga de doble alias, 2026-07-09): --font-brand/--font-ui eran una
     SEGUNDA declaración literal de las mismas dos familias que ya define
     --font-heading/--font-body arriba (mismo valor, nombre distinto, sin
     ninguna diferencia real entre "marca" y "heading" ni entre "ui" y
     "body"). Se convierten en alias puros — DEPRECATED: no usar en código
     nuevo, usar --font-heading/--font-body directamente. Se conservan como
     alias (no se borran) porque el shell los consume en ~8 sitios
     (topbar-brand-name, view-title, kpi-value, ring-num, search-pill kbd…)
     y no es el alcance de esta pasada migrar esos call-sites. */
  --font-brand: var(--font-heading); /* deprecated: alias de --font-heading */
  --font-ui: var(--font-body); /* deprecated: alias de --font-body */
  --z-drop:100; --z-sticky:200; --z-overlay:300; --z-modal:400; --z-toast:500;
}
/* NOTA (2026-07-05): el antiguo bloque de overrides duplicados bajo ".shell" y
   "[data-theme='dark'] .shell" se ELIMINÓ. Sus valores oficiales (--mrj-brown
   #4C2C1C, --mrj-ink #130702, --surface, --muted, --border, --t-fast, --t-slow,
   --t-base y el set de modo oscuro) viven ahora en el :root/[data-theme=dark]
   base de más arriba, unificando legacy .mrj-* y shell en una sola escala. */
/* D5: ".shell :focus-visible" (blanket rule) retirada — el anillo global de
   "Reset & base" ya cubre todo lo interactivo del shell (y de paso las
   páginas legacy .mrj-*, que antes se quedaban fuera de este scope). */
.shell button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}

/* ── Shell ─── */
.shell{display:flex;min-height:100vh}
.shell .main{flex:1;margin-left:252px;transition:margin var(--t-base);min-width:0}
.shell.collapsed .main{margin-left:64px}
/* Retirada del sidebar de 17 módulos (2026-07-11) — ver comentario sobre
   #lmosSidebar en index.html. !important a propósito: gana a TODOS los
   breakpoints (1024/768px) sin tener que tocarlos uno a uno — el sidebar
   nunca vuelve a robar ancho ni a reservar margen, en cualquier tamaño de
   pantalla. El HTML se conserva íntegro (nunca se borra), solo queda
   inerte/invisible. Auditoría 2026-08-01: al ir SIEMPRE junto con la clase
   sidebar-retired, se retiraron también sus reglas base de posición/ancho
   (position:fixed, width, etc.) — display:none ya las hacía irrelevantes. */
.shell .sidebar.sidebar-retired{display:none!important}
.shell .main,.shell.collapsed .main{margin-left:0!important}

/* Auditoría 2026-08-01: retirado el bloque de estilo exclusivo del sidebar
   (.logo/.nav-group/.nav-sec/.nav-mono/.nav-count/.nav-ico/.nav-ext/
   .collapse-btn y sus variantes hover/collapsed/dark) — verificado por grep
   en todo el repo, ninguna de esas clases vive ya fuera del único
   <nav class="sidebar sidebar-retired"> (display:none!important desde
   2026-07-11, ver arriba). .nav-item SÍ se queda: lo comparte la barra
   inferior móvil (.bn-item, ver public/index.html #lmosBottomNav). */
.shell .nav-item{
  display:flex;align-items:center;gap:var(--sp-3);width:calc(100% - 16px);
  margin:1px 8px;padding:8px 12px;border-radius:var(--r-sm);
  color:var(--sidebar-text);font-size:13.5px;font-weight:700;text-align:left;
  transition:background var(--t-fast),color var(--t-fast),transform var(--t-fast);
  white-space:nowrap;position:relative;text-decoration:none;
}
.shell .nav-item:hover{background:rgba(252,236,215,.10);transform:translateX(2px)}
.shell .nav-item.active{background:rgba(245,152,0,.22);color:var(--mrj-orange)}
.shell .nav-item.top{font-size:var(--fs-base);margin:2px 8px 6px}

.shell .topbar{
  position:sticky;top:0;z-index:calc(var(--z-sticky) - 10);
  background:var(--bg);border-bottom:1px solid var(--border);
  display:flex;align-items:center;gap:var(--sp-3);
  padding:var(--sp-3) var(--sp-6);transition:background var(--t-slow);
}
/* Blur del topbar al hacer scroll (portado de .mrj-topbar legacy, línea 328).
   Degradación segura vía @supports, mismo criterio que la condensación de
   abajo: si el navegador no soporta backdrop-filter, se queda con el fondo
   sólido var(--bg) tal cual, sin JS de respaldo. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .shell .topbar{
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
  }
}
/* Ítem 21 (MEJORAS 2.0): condensación del topbar al hacer scroll — scroll-
   driven animation nativa, sin JS. Padding original var(--sp-3)=12px arriba/
   abajo → 8px condensado (~33%, dentro del 30-40% pedido). .main no tiene
   overflow propio: el documento raíz es quien scrollea, de ahí scroll(root).
   Degradación segura vía @supports: si el navegador no soporta
   animation-timeline, el topbar se queda como está arriba, sin JS de
   respaldo (técnica nativa pedida explícitamente). */
@supports (animation-timeline: scroll()) {
  .shell .topbar{
    animation:mrjTopbarCondensar linear both;
    animation-timeline:scroll(root);
    animation-range:0 200px;
  }
  @keyframes mrjTopbarCondensar{
    to{padding-top:8px;padding-bottom:8px}
  }
}
.shell .topbar-brand{display:flex;align-items:center;gap:8px;cursor:pointer;padding:4px 12px;border-radius:var(--r-sm);transition:background var(--t-fast);flex-shrink:0;border:none;background:none}
.shell .topbar-brand:hover{background:var(--surface-2)}
.shell .topbar-brand-logo{height:24px;width:auto;display:block}
.shell .topbar-brand-logo-dark{display:none}
[data-theme="dark"] .shell .topbar-brand-logo-light{display:none}
[data-theme="dark"] .shell .topbar-brand-logo-dark{display:block}
.shell .topbar-brand-sub{font-size:8.5px;font-weight:900;letter-spacing:.1em;color:var(--accent);text-transform:uppercase}
.shell .crumb-divider{width:1px;height:22px;background:var(--border);flex-shrink:0}
.shell .crumb{display:flex;align-items:center;gap:var(--sp-2);font-size:var(--fs-sm);color:var(--muted);min-width:0}
.shell .crumb b{color:var(--text);font-weight:800;font-size:15px}
.shell .crumb .sep{opacity:.4}
.shell .topbar-spacer{flex:1}
/* Indicador de conexión (MEJORAS 2.0 ítem 23): reutiliza .status-dot, junto
   al buscador ⌘K. "En línea" discreto (sin pulso, opacidad baja) — "Sin
   conexión" visible con el pulso heredado de .status-dot.bad. */
.shell .topbar .conn-status{width:7px;height:7px;margin-right:2px}
.shell .topbar .conn-status.ok{opacity:.4}
.shell .topbar .conn-status.ok::before{display:none}
/* C19 (calmar micro-movimiento, 2026-07-07): search-pill/icon-btn/btn-primary/
   btn-ghost/pill/chip/row-actions NO son navegables (no llevan a una vista
   distinta: son un buscador, un botón de acción, una etiqueta de estado, una
   sugerencia de chat, una acción de fila) — se les retira el translateY/scale
   del hover, dejando solo cambio de fondo/borde/filter. La elevación
   (translateY/scale + box-shadow) se reserva para tarjetas que SÍ navegan a
   otra vista: .kpi, .card.hoverable, .modcard, .section-card, .recipe,
   .cal-day, .kanban-card (sin tocar, más abajo). */
.shell .search-pill{
  display:flex;align-items:center;gap:var(--sp-2);
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-full);
  padding:7px 14px;font-size:var(--fs-sm);color:var(--muted);min-width:200px;
  transition:border-color var(--t-fast),background var(--t-fast);
}
.shell .search-pill:hover{border-color:var(--accent);background:var(--surface-2)}
.shell .search-pill kbd{
  margin-left:auto;font-family:var(--font-ui);font-size:10.5px;font-weight:800;
  background:var(--surface-2);border:1px solid var(--border);border-radius:5px;padding:1px 6px;
}
.shell .icon-btn{
  width:36px;height:36px;border-radius:var(--r-sm);display:grid;place-items:center;
  font-size:var(--fs-md);border:1px solid transparent;position:relative;
  transition:background var(--t-fast),border-color var(--t-fast);
}
.shell .icon-btn:hover{background:var(--surface-2);border-color:var(--border)}

/* ── Sidebar móvil: backdrop + botón ☰ (ver toggle en lamaruja-os.js) ───
   Inertes/ocultos en escritorio; se activan en @media(max-width:768px)
   más abajo, junto con el resto de la sección Responsive. */
.shell .sidebar-backdrop{
  display:none;position:fixed;inset:0;background:rgba(19,7,2,.45);
  z-index:calc(var(--z-overlay) - 1);opacity:0;pointer-events:none;
  transition:opacity var(--t-base);
}
.shell .sidebar-backdrop.open{opacity:1;pointer-events:auto}
/* 3 clases a propósito: ".shell .btn{display:inline-flex}" viene DESPUÉS en
   el fichero y con 2 clases empataba y ganaba por orden — el ☰ se veía
   también en escritorio (bug cazado en la pasada móvil 2026-07-08). */
.shell .topbar .mobile-menu-btn{display:none;font-size:var(--fs-md)}
.shell .icon-btn.on{background:rgba(245,152,0,.16);border-color:var(--accent)}
.shell .icon-btn .dot{position:absolute;top:5px;right:6px;width:7px;height:7px;border-radius:50%;background:var(--ok);box-shadow:0 0 0 2px var(--bg)}
/* Historial "volver a donde estaba" + pins (MEJORAS 2.0 Productividad #3):
   botón ◀ inyectado junto a ⌘K y estrella ☆/★ junto al breadcrumb — ambos
   reutilizan .icon-btn, solo hacen falta estos ajustes mínimos. */
.shell .nav-back-btn{font-size:12px}
.shell .nav-back-btn:disabled{opacity:.3;cursor:default;pointer-events:none}
.shell .pin-toggle{font-size:14px;color:var(--muted)}
.shell .pin-toggle.pinned{color:var(--accent)}
.shell .btn{
  display:inline-flex;align-items:center;gap:var(--sp-2);
  border-radius:var(--r-sm);padding:8px 16px;font-size:var(--fs-sm);font-weight:800;
  transition:filter var(--t-fast),background var(--t-fast),border-color var(--t-fast),
             transform var(--t-fast),box-shadow var(--t-fast);
}
/* MEJORAS-50 #35/#36 — mismo arreglo de contraste que .mrj-btn-primary
   arriba (ver su comentario): #fff daba 2.24:1, var(--mrj-brown) da 5.54:1
   en ambos temas por igual. */
.shell .btn-primary{background:var(--accent);color:var(--mrj-brown)}
.shell .btn-primary:hover{filter:brightness(1.08);transform:translateY(-1px);box-shadow:var(--shadow-1)}
.shell .btn-primary:active{filter:brightness(.96);transform:translateY(0);box-shadow:none}
.shell .btn-ghost{border:1px solid var(--border);background:var(--surface)}
.shell .btn-ghost:hover{border-color:var(--accent);background:var(--surface-2);transform:translateY(-1px);box-shadow:var(--shadow-1)}
.shell .btn-ghost:active{background:var(--surface-2);border-color:var(--accent);transform:translateY(0);box-shadow:none}
.shell .btn-sm{padding:5px 10px;font-size:var(--fs-xs)}

/* Pulido de Costes 2/... (2026-07-10): look "píldora" tipo dock aplicado a
   TODOS los botones de la topbar (☰, +Crear, Herramientas, Avisos, Más ▾) —
   mismo --r-full que .search-pill/.btn-asistente/.lmos-dock-btn ya usan.
   Escopado a .topbar para no afectar el resto de la app (tablas, modales,
   formularios de cada módulo), que sigue con el --r-sm de .shell .btn de
   arriba. Cero función tocada: mismos id/clases/wiring de siempre, solo
   forma. Los menús desplegables que cuelgan de estos botones (Crear/
   Herramientas/Avisos) se redondean a juego con --r-lg (antes --r-md). */
.shell .topbar .btn{border-radius:var(--r-full)}
.shell .topbar .dropdown .menu{border-radius:var(--r-lg)}

.shell .view{display:none;padding:var(--sp-5) var(--sp-6) var(--sp-7);animation:shellFadeIn .18s var(--ease)}
.shell .view.active{display:block}
@keyframes shellFadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
.shell .view-head{display:flex;align-items:end;justify-content:space-between;gap:var(--sp-4);margin-bottom:var(--sp-5);flex-wrap:wrap}
/* C12 (2026-07-08): compromiso tipográfico con la marca — Paytone One en los
   títulos de vista (antes Lato 800, genérico). Paytone solo existe en peso 400;
   el tracking negativo de Lato se retira porque a Paytone lo aprieta. */
.shell .view-title{font-family:var(--font-brand);font-size:clamp(22px, .8vw + 19px, 30px);font-weight:400;letter-spacing:0;text-wrap:balance}
.shell .view-sub{color:var(--muted);font-size:13.5px;margin-top:2px}

/* Entrada escalonada (stagger). FIX 2026-07-07 (pantallas en blanco): el estado
   en reposo debe ser SIEMPRE visible (opacity:1) y la animación solo desplaza
   (transform), nunca la opacidad. Antes: opacity:0 + fade con "forwards". Bug:
   los hijos renderizados mientras su vista/panel estaba en display:none (carga
   inicial de módulos, cambio de sub-pestaña, o API lenta que reemplaza contenido
   con la vista oculta) se quedaban CLAVADOS al 0% de la animación (opacity:0) al
   mostrarse, porque el navegador no re-arranca la animación de forma fiable al
   pasar de display:none a block → módulo en blanco. Animando solo transform, si
   la animación se congela el contenido sigue visible (a lo sumo 7px desplazado). */
.shell .stagger-grid > *{opacity:1;animation:shellStaggerIn .4s var(--ease)}
@keyframes shellStaggerIn{from{transform:translateY(7px)}to{transform:none}}

.shell .kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(148px,1fr));gap:var(--sp-3);margin-bottom:var(--sp-4)}
.shell .kpi{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-md);
  padding:var(--sp-3) var(--sp-4);cursor:pointer;position:relative;overflow:hidden;
  transition:transform var(--t-base),box-shadow var(--t-base),border-color var(--t-base);
}
.shell .kpi:hover{transform:translateY(-3px);box-shadow:var(--shadow-2);border-color:rgba(245,152,0,.5)}
.shell .kpi-label{font-size:10.5px;font-weight:700;color:var(--muted);display:flex;align-items:center;gap:var(--sp-2);text-transform:uppercase;letter-spacing:.05em}
/* Reducido 2026-07-11 (Joni: "los dashboards deben verse más pequeños, una
   sola página, sin gráficas ni datos que ocupen toda la pantalla") —
   revierte el aumento del 2026-07-08 (30px→34px) que hacía que las tarjetas
   KPI ocuparan demasiado espacio en TODOS los dashboards de la app (componente
   único y compartido, ver .kpis arriba). .kpi-value.pos/.neg: color semántico
   verde/rojo reutilizando --ok-text/--bad-text (los mismos tokens de
   .trend-up/.trend-down, cero color nuevo) — cada módulo lo añade a los
   valores donde el signo tiene un significado real (margen, EBITDA…), nunca
   por defecto en magnitudes neutras (ingresos/gastos en bruto). */
.shell .kpi-value{font-family:var(--font-brand);font-size:clamp(20px, .5vw + 17px, 24px);margin:4px 0 1px;letter-spacing:.01em;font-variant-numeric:tabular-nums}
.shell .kpi-value.pos{color:var(--ok-text)}
.shell .kpi-value.neg{color:var(--bad-text)}
.shell .kpi-trend{font-size:var(--fs-xs);font-weight:800;display:inline-flex;align-items:center;gap:4px;border-radius:var(--r-full);padding:2px 9px}
.shell .trend-up{color:var(--ok-text);background:rgba(94,122,58,.13)}
.shell .trend-down{color:var(--bad-text);background:rgba(192,57,43,.11)}
.shell .trend-flat{color:var(--muted);background:var(--surface-2)}
/* Sparkline de KPI (tendencia 30 días, DISENO.md §4 "mini-sparkline"). SVG a
   mano, mismo idioma visual que el gráfico de temperaturas de Sanidad —
   stroke="currentColor" hereda el color puesto aquí (--accent, tema-aware). */
.shell .kpi-spark{display:block;margin-top:var(--sp-2);color:var(--accent);opacity:.75}
.shell .kpi:hover .kpi-spark{opacity:1}
.shell .kpi .go{
  position:absolute;top:12px;right:12px;font-size:var(--fs-base);color:var(--accent);
  opacity:0;transform:translateX(-4px);transition:opacity var(--t-fast),transform var(--t-fast);
}
.shell .kpi:hover .go{opacity:1;transform:none}
/* Tarjetas semánticas (Joni 2026-07-08: "si están en positivo que estén en
   verde, negativo en rojo, a medio camino en amarillo"). Tinte suave y borde
   del token semántico oficial (--ok #5E7A3A / --bad #C0392B / naranja oficial
   #F59800 como "a medias") + la cifra en el token de texto AA correspondiente.
   Sin degradados, cero color nuevo. REGLA DE USO: solo donde el dato tiene
   significado real (resultado, margen, metas, incidencias, vencidos…), nunca
   en magnitudes neutras (conteos, ingresos brutos). Válidas en .kpi y .card. */
.shell .kpi.kpi-ok,.shell .card.card-ok{background:rgba(94,122,58,.07);border-color:rgba(94,122,58,.4)}
.shell .kpi.kpi-ok .kpi-value{color:var(--ok-text)}
.shell .kpi.kpi-warn,.shell .card.card-warn{background:rgba(245,152,0,.08);border-color:rgba(245,152,0,.45)}
.shell .kpi.kpi-warn .kpi-value{color:var(--warn-text)}
.shell .kpi.kpi-bad,.shell .card.card-bad{background:rgba(192,57,43,.06);border-color:rgba(192,57,43,.4)}
.shell .kpi.kpi-bad .kpi-value{color:var(--bad-text)}

/* ── Alérgenos en tarjetas (pulido de Costes, 2026-07-10 — ficha de producto
   a pantalla completa, mockup aprobado por Joni). Los 14 alérgenos UE
   siempre visibles, no solo los declarados — mismos tokens de color que ya
   usan .pill-bad/.pill-warn/.pill-mut, cero color nuevo:
   - .contiene: mismo rojo de .pill-bad (rgba(192,57,43,.12) + --bad-text).
   - .traza: mismo naranja de .pill-warn (rgba(245,152,0,.16) + --warn-text),
     borde discontinuo para diferenciarlo de "contiene" sin depender solo del
     color (accesibilidad).
   - .libre (incluye "sin datos" cuando la variante no se pudo emparejar con
     ninguna ficha): atenuado con opacity + var(--muted), sin fondo de color. */
.shell .alergeno-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(112px,1fr));gap:var(--sp-3)}
.shell .alergeno-card{background:var(--surface);border:1.5px solid var(--border);border-radius:var(--r-md);padding:var(--sp-3) var(--sp-2);text-align:center}
.shell .alergeno-card .al-nombre{font-size:12.5px;font-weight:800;color:var(--text)}
.shell .alergeno-card .al-tag{display:block;margin-top:3px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
.shell .alergeno-card.contiene{background:rgba(192,57,43,.12);border-color:rgba(192,57,43,.4)}
.shell .alergeno-card.contiene .al-tag{color:var(--bad-text)}
.shell .alergeno-card.traza{background:rgba(245,152,0,.16);border-color:rgba(245,152,0,.5);border-style:dashed}
.shell .alergeno-card.traza .al-tag{color:var(--warn-text)}
.shell .alergeno-card.libre{opacity:.55}

/* ── Bento de KPIs (Facturas → Inicio), Joni 2026-07-10, viendo la app en
   vivo: "8 tarjetas todas iguales, muy grandes, no se lee así". Modificador
   ADITIVO sobre el componente COMPARTIDO .kpis/.kpi (usado también por
   Costes, Producción, Sanidad y los dashboards de Inicio) — nada de lo de
   abajo toca .shell .kpi/.kpi-value/.kpi-label base, todo va detrás de
   .kpis.kpi-grid-bento, y esa clase solo la llevan contenedores del módulo
   Facturas (#facKpisInicio y, desde 2026-07-13, #facAsesorKpis en
   modules/facturas/view.js). El resto de módulos siguen viendo la fila de
   tarjetas grandes de siempre, sin ningún cambio. */
/* 2026-07-13 (Joni, viendo la app en vivo: "todas las tarjetas deben tener
   el mismo tamaño"): se retira el modificador .kpi-hero (Margen neto a 2×2
   con número gigante) que dejaba la última tarjeta (antes IVA neto, ahora
   también Ingresos con IVA) colgando sola en una fila incompleta con
   grid-auto-flow:dense. Todas las tarjetas comparten ya la MISMA regla de
   tamaño compacto (antes solo aplicada a ":not(.kpi-hero)") — con 9 tarjetas
   uniformes el grid siempre cierra en filas completas, sin huecos. */
.shell .kpis.kpi-grid-bento{
  grid-template-columns:repeat(auto-fit,minmax(130px,1fr));
  gap:var(--sp-3);
}
.shell .kpis.kpi-grid-bento .kpi{padding:var(--sp-3) var(--sp-4)}
.shell .kpis.kpi-grid-bento .kpi .kpi-label{font-size:10.5px}
.shell .kpis.kpi-grid-bento .kpi .kpi-value{font-size:clamp(18px, .5vw + 15px, 22px);margin:4px 0 1px}
.shell .kpis.kpi-grid-bento .kpi .kpi-trend{font-size:10px;padding:1px 7px}
@media(max-width:768px){
  /* mismo criterio 2-columnas del resto de .kpis en móvil (regla de abajo,
     línea ~2159). */
  .shell .kpis.kpi-grid-bento{grid-template-columns:repeat(2,1fr)}
}
/* Gráfico "Evolución mensual" de Facturas → Inicio (2026-07-13, Joni: "el
   gráfico de abajo ser aún más pequeño"). dualBarSvg() (lmos-core.js) es
   compartida por Costes/Producción/dashboards — NO se toca su tamaño base
   (560×130, ya reducido una vez el 2026-07-11), solo se acota aquí, scoped
   a este contenedor, el alto máximo renderizado. El <svg> no lleva atributo
   height (solo viewBox + width:100%), así que max-height sí lo comprime;
   preserveAspectRatio por defecto evita que se deforme.  */
#facInicioEvolChart svg{max-height:90px}

.shell .grid{display:grid;gap:var(--sp-4)}
.shell .grid-dash{grid-template-columns:repeat(12,1fr)}
.shell .card{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);
  padding:var(--sp-4) var(--sp-5);
  transition:box-shadow var(--t-base),transform var(--t-base),border-color var(--t-base);
}
.shell .card.hoverable{cursor:pointer}
/* Fila de 2 tarjetas lado a lado en vez de apiladas — dashboards de una sola
   página (Joni, 2026-07-11). Cae a 1 columna sola en <900px. */
.shell .dash-row-2{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-4);align-items:start}
@media(max-width:900px){.shell .dash-row-2{grid-template-columns:1fr}}
.shell .card.hoverable:hover{transform:translateY(-2px);box-shadow:var(--shadow-2);border-color:rgba(245,152,0,.45)}

/* ── Dropzone (subir archivo) — accesible por teclado, hover real en CSS ── */
.shell .dropzone{
  border:2px dashed var(--border);border-radius:var(--r-lg);padding:28px;
  text-align:center;cursor:pointer;transition:border-color var(--t-fast),background var(--t-fast);
}
.shell .dropzone:hover,.shell .dropzone.dragover{border-color:var(--accent);background:rgba(245,152,0,.05)}
/* D5: outline/outline-offset retirados de aquí — [tabindex] (siempre
   presente en .dropzone, ver markup real) ya los cubre en el anillo global;
   lo que esta regla aporta de EXTRA es el cambio de color del borde
   punteado, que se conserva. */
.shell .dropzone:focus-visible{border-color:var(--accent)}
/* Ítem 59 (MEJORAS 2.0, regla Stripe — contraste densidad-respiro): margin-bottom
   subido de --sp-3(12px) a --sp-4(16px). Medido antes de tocar: .card-head es el
   contenedor real de título + filtro .search-pill justo encima de .tablecard en
   Facturas/Costes/Sanidad (grep verificado), y sus 12px quedaban casi idénticos
   al padding vertical de fila de tabla (11px) — cero contraste perceptible entre
   la franja "respiro" (cabecera+filtro) y la franja "densa" (tabla). Con el
   padding de fila bajado a --sp-2(8px) más abajo, el ratio pasa de 12/11≈1.1x a
   16/8=2x — contraste deliberado, sin tocar el gap horizontal interno (título↔acciones). */
.shell .card-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--sp-4);gap:var(--sp-3)}
.shell .card-title{font-size:var(--fs-base);font-weight:800;display:flex;align-items:center;gap:var(--sp-2)}
.shell .card-link{font-size:var(--fs-xs);font-weight:800;color:var(--accent);border-radius:6px;padding:3px 8px;transition:background var(--t-fast)}
.shell .card-link:hover{background:rgba(245,152,0,.13)}
.shell .span-3{grid-column:span 3}.shell .span-4{grid-column:span 4}.shell .span-5{grid-column:span 5}.shell .span-6{grid-column:span 6}
.shell .span-7{grid-column:span 7}.shell .span-8{grid-column:span 8}.shell .span-9{grid-column:span 9}.shell .span-12{grid-column:span 12}
@media(max-width:1180px){.shell .span-3,.shell .span-4,.shell .span-5{grid-column:span 6}.shell .span-7,.shell .span-8,.shell .span-9{grid-column:span 12}}
@media(max-width:820px){.shell .grid-dash>*{grid-column:span 12}}

.shell .pill{
  display:inline-flex;align-items:center;gap:5px;border-radius:var(--r-full);
  font-size:11.5px;font-weight:800;padding:3px 10px;cursor:pointer;
  transition:filter var(--t-fast);white-space:nowrap;
}
.shell .pill:hover{filter:brightness(.95)}
.shell .pill-ok{background:rgba(94,122,58,.14);color:var(--ok-text)}
.shell .pill-warn{background:rgba(245,152,0,.16);color:var(--warn-text)}
.shell .pill-bad{background:rgba(192,57,43,.12);color:var(--bad-text)}
.shell .pill-mut{background:var(--surface-2);color:var(--muted)}

.shell .feed{display:flex;flex-direction:column}
.shell .feed-item{
  display:flex;gap:var(--sp-3);padding:9px 10px;border-radius:var(--r-sm);font-size:12.5px;
  transition:background var(--t-fast);cursor:pointer;align-items:center;
}
.shell .feed-item:hover{background:var(--surface-2)}
.shell .feed-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);flex-shrink:0}
.shell .feed-time{margin-left:auto;color:var(--muted);font-size:11px;white-space:nowrap}
.shell .avatar{
  width:28px;height:28px;border-radius:var(--r-sm);background:var(--mrj-brown);color:var(--mrj-cream);
  display:grid;place-items:center;font-size:11px;font-weight:800;flex-shrink:0;
}

.shell .bar{height:8px;border-radius:var(--r-full);background:var(--surface-2);overflow:hidden}
.shell .bar i{display:block;height:100%;border-radius:var(--r-full);background:var(--accent);transition:width .6s var(--ease)}
.shell .bar i.ok{background:var(--ok)}
.shell .bar i.bad{background:var(--bad-text)}

.shell .modcards{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:var(--sp-3)}
.shell .modcard{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-md);
  padding:var(--sp-4);cursor:pointer;display:flex;flex-direction:column;gap:6px;
  transition:transform var(--t-base),box-shadow var(--t-base),border-color var(--t-base);
}
.shell .modcard:hover{transform:translateY(-2px);box-shadow:var(--shadow-2);border-color:rgba(245,152,0,.45)}
.shell .modcard .ico{font-size:var(--fs-xl)}
.shell .modcard b{font-size:13.5px}
.shell .modcard span{font-size:11.5px;color:var(--muted)}

/* ── C7 (Mejoras-Diseno-Estructura.md) — Jerarquía real del Dashboard ───
   Capa aditiva sobre .card/.grid-dash existentes (NO se modifica su
   definición base). Nivel 1 = lo que decide el día (bandeja "Hoy"), más
   grande y protagonista. Nivel 2 = seguimiento (Objetivo, Feed), tamaño
   estándar del sistema (sin regla propia, el .card base ya es "medio").
   Nivel 3 = periférico (Resumen, Health Score, Producción, Notas, Sistema,
   Marujeo), compacto: menos padding y título más pequeño que el estándar. */
.shell .dash-tier-1.card{padding:var(--sp-5) var(--sp-6)}
.shell .dash-tier-1 .card-title{font-size:var(--fs-lg)}

.shell .dash-tier-3 .card{padding:var(--sp-3) var(--sp-4)}
.shell .dash-tier-3 .card-head{margin-bottom:var(--sp-2)}
.shell .dash-tier-3 .card-title{font-size:var(--fs-sm)}

/* ── Pantalla honesta "en migración / próximamente" ─── */
.shell .stub{max-width:520px;margin:60px auto;text-align:center}
.shell .stub .ico{font-size:44px;margin-bottom:14px}
.shell .stub h2{font-size:20px;font-weight:800;margin-bottom:8px}
.shell .stub p{color:var(--muted);font-size:13.5px;line-height:1.6;margin-bottom:20px}

/* ── Asistente (vista de chat) ─── */
.shell .assist-view{max-width:760px;margin:0 auto;display:flex;flex-direction:column;height:calc(100vh - 140px)}
.shell .assist-notice{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--r-md);padding:8px 14px;font-size:var(--fs-xs);color:var(--muted);text-align:center;margin-bottom:var(--sp-3)}
.shell .assist-msgs{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:var(--sp-3);padding:var(--sp-2) 2px}
.shell .msg{max-width:88%;border-radius:var(--r-md);padding:10px 14px;font-size:var(--fs-sm);animation:shellFadeIn .2s var(--ease);line-height:1.55}
.shell .msg-ai{background:var(--surface-2);border:1px solid var(--border);align-self:flex-start}
.shell .msg-me{background:var(--mrj-brown);color:var(--mrj-cream);align-self:flex-end}
.shell .msg table{border-collapse:collapse;margin:6px 0;font-size:12.5px;width:100%}
.shell .msg th,.shell .msg td{border:1px solid var(--border);padding:5px 9px;text-align:left}
.shell .msg th{background:var(--surface-2)}
/* Indicador "escribiendo…" del Asistente — paridad con el original :3010
   (3 puntos rebotando con delay escalonado), var(--accent) en vez del
   naranja hardcodeado original; visible en modo claro y oscuro. */
.shell .think{display:flex;gap:6px;align-items:center;color:var(--muted);font-size:var(--fs-sm)}
.shell .dot{width:7px;height:7px;border-radius:50%;background:var(--accent);animation:shellThinkBounce 1.2s infinite}
.shell .dot:nth-child(2){animation-delay:.2s}
.shell .dot:nth-child(3){animation-delay:.4s}
@keyframes shellThinkBounce{0%,80%,100%{opacity:.3;transform:scale(.8)}40%{opacity:1;transform:scale(1)}}
.shell .assist-input{display:flex;gap:var(--sp-2);padding:var(--sp-3) 2px;border-top:1px solid var(--border)}
.shell .assist-input input, .shell .assist-input textarea{
  /* D5: "outline:none" retirado — deja pasar el anillo global; el
     border-color de la regla :focus de abajo sigue siendo lo EXTRA propio
     de este input y convive bien con el anillo. */
  flex:1;border:1px solid var(--border);border-radius:var(--r-full);padding:9px 16px;
  font-family:inherit;font-size:var(--fs-sm);background:var(--surface);color:var(--text);
  transition:border-color var(--t-fast);resize:none;max-height:120px;
}
.shell .assist-input input:focus, .shell .assist-input textarea:focus{border-color:var(--accent)}
.shell .suggest{display:flex;flex-wrap:wrap;gap:6px;padding:0 2px var(--sp-3)}
.shell .tool-chip{font-size:10.5px;font-weight:800;background:var(--surface-2);border:1px solid var(--border);border-radius:var(--r-full);padding:2px 9px;color:var(--muted);margin-right:5px;display:inline-block;margin-top:4px}
/* .chip: sugerencias de pregunta como <button> reales (foco + :focus-visible) */
.shell .chip{
  border:1px solid var(--border);background:var(--surface);border-radius:var(--r-full);
  font-size:var(--fs-xs);font-weight:700;padding:5px 13px;color:var(--muted);
  transition:border-color var(--t-fast),color var(--t-fast);
}
.shell .chip:hover{border-color:var(--accent);color:var(--text)}
.shell .chip.on{background:rgba(245,152,0,.15);border-color:var(--accent);color:var(--warn-text)}

/* ── Asistente · panel lateral deslizante (420px) ─── */
.shell .assist-panel{
  position:fixed;top:0;right:-440px;bottom:0;width:420px;max-width:92vw;
  background:var(--surface);border-left:1px solid var(--border);z-index:var(--z-modal);
  display:flex;flex-direction:column;box-shadow:var(--shadow-3);
  transition:right var(--t-slow);
}
.shell .assist-panel.open{right:0}
.shell .assist-head{display:flex;align-items:center;gap:var(--sp-3);padding:var(--sp-4) var(--sp-5);border-bottom:1px solid var(--border)}
.shell .assist-panel .assist-msgs{padding:var(--sp-4) var(--sp-5)}
.shell .assist-panel .suggest{padding:0 var(--sp-5) var(--sp-3)}
.shell .assist-panel .assist-input{padding:var(--sp-3) var(--sp-4)}

/* ── ⌘K palette ─── */
.shell .overlay{
  position:fixed;inset:0;background:rgba(19,7,2,.45);z-index:var(--z-overlay);
  opacity:0;pointer-events:none;transition:opacity var(--t-base);
}
.shell .overlay.open{opacity:1;pointer-events:auto}
.shell .cmdk{
  position:fixed;top:14vh;left:50%;transform:translateX(-50%) scale(.97);width:560px;max-width:92vw;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);
  box-shadow:var(--shadow-3);z-index:var(--z-modal);overflow:hidden;
  opacity:0;pointer-events:none;transition:opacity var(--t-base),transform var(--t-base);
}
.shell .cmdk.open{opacity:1;pointer-events:auto;transform:translateX(-50%) scale(1)}
.shell .cmdk input{
  /* D5: "outline:none" retirado — este input no tenía NINGÚN indicador de
     foco propio (a diferencia de .assist-input, sin :focus companion), así
     que dejar pasar el anillo global es una mejora real de accesibilidad
     de teclado, no solo una limpieza. */
  width:100%;border:none;padding:16px 20px;font-size:15px;font-family:inherit;
  background:transparent;color:var(--text);border-bottom:1px solid var(--border);
}
.shell .cmdk-list{max-height:360px;overflow-y:auto;padding:8px}
.shell .cmdk-section-label{
  padding:8px 12px 4px;font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);
}
.shell .cmdk-item{
  display:flex;align-items:center;gap:var(--sp-3);padding:10px 12px;border-radius:var(--r-sm);
  font-size:13.5px;cursor:pointer;transition:background var(--t-fast);width:100%;text-align:left;
}
.shell .cmdk-item:hover,.shell .cmdk-item.sel{background:rgba(245,152,0,.13)}
.shell .cmdk-item .k-ico{width:22px;text-align:center}
.shell .cmdk-item .k-type{margin-left:auto;font-size:10.5px;color:var(--muted);letter-spacing:.05em}
.shell .cmdk-empty{padding:24px 20px;text-align:center;color:var(--muted);font-size:var(--fs-sm)}

/* ── Historial de conversaciones (Asistente) ─── */
.shell .hist-modal{
  position:fixed;top:10vh;left:50%;transform:translateX(-50%) scale(.97);width:420px;max-width:92vw;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);
  box-shadow:var(--shadow-3);z-index:var(--z-modal);overflow:hidden;
  display:flex;flex-direction:column;max-height:74vh;
  opacity:0;pointer-events:none;transition:opacity var(--t-base),transform var(--t-base);
}
.shell .hist-modal.open{opacity:1;pointer-events:auto;transform:translateX(-50%) scale(1)}
.shell .hist-modal-head{display:flex;align-items:center;justify-content:space-between;padding:var(--sp-4) var(--sp-5);border-bottom:1px solid var(--border)}
.shell .hist-modal-head b{font-size:var(--fs-base)}
.shell .hist-modal-actions{padding:var(--sp-3) var(--sp-4) 0}
.shell .hist-list{overflow-y:auto;padding:var(--sp-3) var(--sp-4) var(--sp-4);display:flex;flex-direction:column;gap:6px}
.shell .hist-item{display:flex;align-items:center;gap:var(--sp-2);padding:9px 10px;border-radius:var(--r-sm);border:1px solid transparent;transition:background var(--t-fast),border-color var(--t-fast)}
.shell .hist-item:hover{background:var(--surface-2)}
.shell .hist-item.active{background:rgba(245,152,0,.10);border-color:var(--accent)}
.shell .hist-item-info{flex:1;min-width:0}
.shell .hist-item-title{font-size:var(--fs-sm);font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.shell .hist-item-meta{font-size:11px;color:var(--muted);margin-top:2px}
.shell .hist-empty{padding:24px 20px;text-align:center;color:var(--muted);font-size:var(--fs-sm)}

/* ── Dropdowns ─── */
.shell .dropdown{position:relative}
.shell .menu{
  position:absolute;top:calc(100% + 6px);right:0;min-width:210px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-md);
  box-shadow:var(--shadow-3);padding:6px;z-index:var(--z-drop);
  opacity:0;pointer-events:none;transform:translateY(-4px);
  transition:opacity var(--t-fast),transform var(--t-fast);
  max-height:70vh;overflow-y:auto;
}
.shell .menu.open{opacity:1;pointer-events:auto;transform:none}
/* Fix 2026-07-09 (re-aplicado tras reversión accidental — ver
   MEJORAS-EJECUCION.md): menú de una fila cerca del final de una tabla
   (p.ej. "Marcar pago" en Facturas → Pagos) — si no cabe hacia abajo, se
   abre hacia arriba (calculado en JS al abrir, ver modules/facturas/view.js). */
.shell .menu.open-up{top:auto;bottom:calc(100% + 6px)}
.shell .menu button{
  display:flex;align-items:center;gap:var(--sp-3);width:100%;text-align:left;
  padding:9px 12px;border-radius:var(--r-sm);font-size:var(--fs-sm);font-weight:700;
  transition:background var(--t-fast);
}
.shell .menu button:hover{background:rgba(245,152,0,.13)}
.shell .menu-wide{min-width:290px}
.shell .menu-group-label{font-size:10px;font-weight:800;letter-spacing:.08em;color:var(--muted);padding:8px 12px 4px;text-transform:uppercase}
.shell .menu-divider{height:1px;background:var(--border);margin:6px 4px}
.shell .menu-row-toggle{
  display:flex;align-items:center;justify-content:space-between;gap:var(--sp-3);
  width:100%;padding:9px 12px;border-radius:var(--r-sm);font-size:var(--fs-sm);font-weight:700;
  transition:background var(--t-fast);
}
.shell .menu-row-toggle:hover{background:rgba(245,152,0,.13)}
.shell .switch{width:34px;height:20px;border-radius:var(--r-full);background:var(--surface-2);border:1px solid var(--border);position:relative;transition:background var(--t-fast),border-color var(--t-fast);flex-shrink:0}
.shell .switch i{position:absolute;top:1px;left:1px;width:16px;height:16px;border-radius:50%;background:var(--muted);transition:transform var(--t-fast),background var(--t-fast)}
.shell .switch.on{background:rgba(245,152,0,.25);border-color:var(--accent)}
.shell .switch.on i{transform:translateX(14px);background:var(--accent)}
.shell .menu a{display:flex;align-items:center;gap:var(--sp-3);width:100%;text-align:left;padding:9px 12px;border-radius:var(--r-sm);font-size:var(--fs-sm);font-weight:700;text-decoration:none;color:inherit;transition:background var(--t-fast)}
.shell .menu a:hover{background:rgba(245,152,0,.13)}
.shell .doc-row{display:flex;align-items:center;gap:10px;width:100%;text-align:left;padding:8px 12px;border-radius:var(--r-sm);font-size:12.5px;font-weight:700;transition:background var(--t-fast)}
.shell .doc-row:hover{background:rgba(245,152,0,.13)}
.shell .doc-row .doc-meta{margin-left:auto;font-size:10px;color:var(--muted);font-weight:800}

/* ── Toast propio del shell (distinto id del v1 #mrj-toasts) ─── */
.shell .toasts{position:fixed;right:var(--sp-5);bottom:var(--sp-5);z-index:var(--z-toast);display:flex;flex-direction:column;gap:8px}
.shell .toast{
  background:var(--mrj-brown);color:var(--mrj-cream);border-radius:var(--r-md);
  padding:12px 18px;font-size:var(--fs-sm);font-weight:700;box-shadow:var(--shadow-3);
  display:flex;align-items:center;gap:10px;animation:shellToastIn .25s var(--ease);
}
@keyframes shellToastIn{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
/* FASE 1 (2026-07-07): BUG real — .shell .toast no tenía variantes de color;
   todo toast (éxito, error, aviso) se pintaba igual (marrón neutro) sin
   importar el tipo. Vocabulario cubierto = TODOS los nombres que ya pasan
   los módulos a su función toast (grep verificado: cstToast/facToast/
   vpToast/sanToast/shellToast/sistema): "ok"/"success" (Costes, Facturas,
   Producción, Sanidad, Sistema), "error"/"danger" (todos los módulos,
   Sanidad usa "danger"), "warning" (Sanidad). Colores = --ok/--bad ya
   establecidos en este fichero para superficies SÓLIDAS (.status-dot.ok/.bad,
   .bar i.ok/.bad, .ring .val.ok/.bad) — no --ok-text/--bad-text, que están
   pensados para texto sobre fondos tintados (.pill-ok/.trend-up), no como
   fondo sólido. Contraste verificado (fórmula WCAG): #fff sobre --ok
   (#5E7A3A) = 4.86:1, sobre --bad (#C0392B) = 5.44:1 — AA. "warning" usa
   --accent (naranja oficial) de fondo; #fff falla ahí (2.24:1), así que el
   texto es --mrj-brown (5.57:1) — paleta 100% oficial, cero hex nuevo. */
.shell .toast.ok, .shell .toast.success{background:var(--ok);color:#fff}
.shell .toast.error, .shell .toast.danger{background:var(--bad);color:#fff}
.shell .toast.warning{background:var(--accent);color:var(--mrj-brown)}
.shell .toast.out{opacity:0;transform:translateY(8px);transition:all var(--t-slow)}
/* Toast con botón "Reintentar" (fetch fallido) — mismo fondo/texto que .toast,
   sin iconos, solo texto + botón (app.js: mostrarToastReintentar/cargarConReintento) */
.shell .toast-retry{justify-content:space-between;gap:14px}
.shell .toast-retry-btn{
  flex-shrink:0;background:transparent;border:1px solid var(--mrj-cream);color:var(--mrj-cream);
  border-radius:var(--r-sm);padding:4px 10px;font-size:var(--fs-xs);font-weight:800;cursor:pointer;
  transition:background var(--t-fast);
}
.shell .toast-retry-btn:hover{background:rgba(252,236,215,.18)}
.shell .toast-retry-btn:disabled{opacity:.6;cursor:default}

/* ── Modo privado ─── (2026-07-11: 6px fijo no ocultaba nada en cifras
   grandes tipo KPI de Margen Neto — a 40px+ de fuente el blur era menor que
   el grosor del trazo, así que el dígito y su color semántico (rojo
   pérdidas, verde IVA a compensar...) seguían distinguiéndose. blur() en em
   escala con el font-size real de cada .money (números pequeños de tabla y
   KPIs grandes usan el mismo valor relativo); max(8px,…) pone un suelo para
   que el texto más pequeño no se quede por debajo del umbral legible.
   grayscale(1) es la pieza que faltaba: un blur, por fuerte que sea, sigue
   promediando al mismo tono (rojo blureado sigue leyéndose "rojizo") — solo
   quitar el color soluciona que se intuya el signo/estado por detrás. */
body.private .money{filter:blur(max(8px,0.42em)) grayscale(1);transition:filter var(--t-base)}
.shell .money{transition:filter var(--t-base);font-variant-numeric:tabular-nums}

/* ── Responsive ─── */
@media(max-width:720px){
  .shell .view{padding:var(--sp-4)}
  .shell .search-pill{min-width:0}
  .shell .search-pill span{display:none}
}

/* ── Móvil real (≤768px) — pasada de diseño 2026-07-06. Auditoría
   2026-08-01: quitado el drawer off-canvas del sidebar retirado (width/
   transform/mobile-open/nav-label/nav-count/nav-sec/collapse-btn — todo
   inerte desde 2026-07-11, ver .sidebar.sidebar-retired arriba); se queda
   el resto: el topbar deja que sus botones (+Crear/Documentos/Herramientas/
   Asistente) quiebren en una segunda fila en vez de desbordar o cortarse.
   Los .kpi (grid auto-fit) y .grid-dash (columna única ≤820px, regla ya
   existente más abajo) no necesitan nada más: ya colapsan solos. */
@media(max-width:768px){
  .shell .main,.shell.collapsed .main{margin-left:0}
  .shell .sidebar-backdrop{display:block}
  /* 2026-09-01 (Joni: "hay una pestaña que abre el dock de nuevo, es el
     mismo que está abajo, quítala"): el ☰ solo abría el drawer #lmosDock,
     que muestra las MISMAS 5 zonas que #lmosBottomNav ya fijo abajo — acceso
     duplicado. Se apaga con display:none (mismo patrón que el sidebar
     retirado: nunca se borra HTML/JS, solo se oculta) — el botón, su
     listener y openMobileSidebar()/closeMobileSidebar() en lamaruja-os.js
     quedan intactos e inertes por si hace falta revertir. */
  .shell .topbar .mobile-menu-btn{display:none}

  .shell .topbar{flex-wrap:wrap;row-gap:var(--sp-2)}
  .shell .topbar-spacer{flex-basis:100%;height:0}

  /* Captura universal (MEJORAS-MAESTRO.md Parte 3 A3): en móvil "+ Crear" es
     la acción de captura más usada — target táctil más grande que el resto
     de botones del topbar, sin cambiar su posición ni el orden fijo del
     patrón de 7 elementos (DISENO.md §2). */
  .shell #lmosCreateBtn{padding:12px 22px;font-size:var(--fs-md);min-height:44px}
}

/* LaMaruja OS · Design System v3 — integración final (2026-07-05). Clases de
   los 12 módulos migrados, copiadas literalmente de la fuente canónica
   (mockup/index.html), prefijo ".shell " y @keyframes con nombres únicos
   (shellFadeIn/shellStaggerIn/shellToastIn) para no colisionar. Tokens ya
   unificados, sin cambios de valor; ninguna clase existente tocada. */

/* ── Barras apiladas (mermas / comparativas) ─── */
.shell .stackbar{display:flex;height:14px;border-radius:var(--r-full);overflow:hidden;gap:2px}
.shell .stackbar i{transition:flex-grow .5s var(--ease);position:relative}
.shell .stackbar i:hover{opacity:.8}

/* ── Tabla viva (.tablecard) ─── */
/* Sin scroll lateral NUNCA (Joni, 2026-07-18: "las tablas no deben de poder
   escrolear hacia los lados, solo arriba y abajo... en todas las pantallas
   con tablas"). El scroll vertical acotado + cabecera fija (.tc-scroll, más
   abajo) sigue igual — ese es el único eje de scroll permitido. */
.shell .tablecard{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);overflow:hidden;overflow-x:hidden}
/* Joni, 2026-07-28 (2ª pasada — la 1ª con table-layout:fixed quedó verificada
   como regresión real: con 12 columnas en un ancho realista, cada columna
   recibía un reparto igual demasiado estrecho y CADA palabra (no solo las
   cortas) acababa partida en 2-3 líneas — mismo síntoma que el bug original,
   solo que repartido. Vuelta a table-layout:auto + th en nowrap (esto SÍ es
   correcto y no rompe palabras: ver comentario del th más abajo). El caso
   que ninguna combinación de table-layout puede resolver por sí sola —
   tabla de muchas columnas que sencillamente no cabe en el ancho disponible,
   sin scroll permitido (regla de 2026-07-18) y sin poder partir palabras
   (regla de hoy) — se resuelve fuera del CSS de columnas: JS mide si la
   tabla desborda de verdad (scrollWidth > clientWidth) y en ese caso
   conmuta esa .tablecard concreta al patrón de tarjetas apiladas que ya
   existe para móvil (ver ajustarTablecardsResponsive en lmos-core.js y el
   bloque ".tablecard.tc-cards" más abajo, mismas reglas que el @media
   docenas de líneas más abajo, mantener ambos sincronizados). */
.shell .tablecard table{width:100%;table-layout:auto;border-collapse:collapse;font-size:var(--fs-sm)}
.shell .tablecard thead th{
  text-align:left;font-size:11px;letter-spacing:.06em;color:var(--muted);
  /* Ítem 59: padding vertical bajado de 11px (literal mágico) a var(--sp-2)=8px —
     tabla más compacta/densa (texto denso, DISENO.md §"Tabla viva"), tokenizado
     en vez de mantener el número suelto. Horizontal (var(--sp-4)=16px, antes
     literal "16px") sin cambio de valor, solo tokenizado. Ver .card-head arriba
     para el razonamiento completo del contraste densidad/respiro. */
  padding:var(--sp-2) var(--sp-4);border-bottom:1px solid var(--border);cursor:pointer;user-select:none;
  transition:color var(--t-fast);white-space:nowrap;font-weight:800;
  /* Joni, 2026-07-28: bug real de palabras partidas letra a letra en las
     tablas. word-break:break-word (= overflow-wrap:anywhere) hacía que
     table-layout:auto calculase el ancho mínimo de cada columna como si
     cualquier palabra pudiera partirse en cualquier punto, así que ese
     mínimo caía casi a cero y en tablas con muchas columnas (Ingredientes,
     12; Lotes, 9) cabeceras cortas como "FORMATO" se veían forzadas a
     romperse letra a letra. Las cabeceras son cortas por diseño y no deben
     partirse nunca: con nowrap su ancho natural pasa a ser el mínimo real
     de la columna. Si la suma de esos mínimos no cabe en el contenedor, no
     se deja que la columna se recorte/desaparezca (ver nota de arriba): el
     JS de ajustarTablecardsResponsive detecta ese desborde y pasa la tabla
     a tarjetas apiladas antes de que eso ocurra. */
}
.shell .tablecard thead th:hover{color:var(--accent)}
/* FASE 1 (2026-07-07): en CRM y Fidelización el cursor:pointer + hover de
   arriba prometía una ordenación que no existe (verificado con grep: no hay
   ningún listener de clic ni lógica de sort detrás de sus <th>). Se retira
   SOLO en estos dos módulos para no generar una expectativa falsa; el resto
   de módulos no se toca en esta pasada. Implementar sort real es Fase 4. */
/* (2026-07-08, MEJORAS-50 #35) Excepción cursor:default de CRM/Fidelización
   retirada: sus tablas YA ordenan de verdad (wireSortableTable en sus
   view.js), así que el affordance de clic vuelve a ser honesto. */
.shell .tablecard tbody td{padding:var(--sp-2) var(--sp-4);border-bottom:1px solid var(--border);white-space:normal;overflow-wrap:break-word}
/* Joni, 2026-07-28: bug real de palabras partidas letra a letra en las
   tablas. Las celdas de datos SÍ necesitan envolver texto largo (nombres de
   ingrediente, notas…), así que aquí no vale nowrap como en el th — pero el
   mismo word-break:break-word forzaba roturas "en cualquier punto" y
   colapsaba el ancho mínimo de la columna. overflow-wrap:break-word solo
   rompe una palabra si de verdad no cabe entera en todo el ancho
   disponible y, a diferencia de word-break:break-word, no participa en el
   cálculo de min-content de table-layout:auto: la columna recibe al menos
   el ancho de sus palabras normales antes de partir nada. */
.shell .tablecard tbody tr{transition:background var(--t-fast);cursor:pointer;position:relative}
.shell .tablecard tbody tr:hover{background:var(--surface-2)}
.shell .tablecard tbody tr:last-child td{border-bottom:none}
.shell .row-actions{opacity:0;display:flex;gap:6px;flex-wrap:nowrap;transition:opacity var(--t-fast);justify-content:flex-end}
.shell .tablecard tbody tr:hover .row-actions{opacity:1}
/* Arreglado 2026-07-08 (Joni: "los botones se ven mal, no modernos") — esta
   regla se escribió pensando en botones-icono de 1 carácter (width/height
   28px fijos) pero TODA la app la usa con texto real ("Editar", "Borrar",
   "Ver ficha"…: Personal/Facturas/Sanidad/Costes/Producción/CRM). Por
   especificidad CSS (.shell .row-actions button > .btn-sm) el 28px fijo
   siempre ganaba y apretaba/recortaba el texto. Ahora se dimensiona al
   contenido (como ya hacía .fac-row-actions) manteniendo la altura y el
   hover-reveal — sin tocar ningún call-site de los módulos. */
.shell .row-actions button,.shell .row-actions a{
  height:28px;padding:0 12px;border-radius:6px;display:inline-flex;align-items:center;justify-content:center;
  font-size:var(--fs-xs);font-weight:700;white-space:nowrap;
  transition:background var(--t-fast);
}
.shell .row-actions button:hover,.shell .row-actions a:hover{background:rgba(245,152,0,.16)}
.shell .prov{display:flex;align-items:center;gap:10px;font-weight:700}

/* ── Acciones de fila con botones de texto (Facturas: Marcar pago / Ver
   documento) — a diferencia de .row-actions (botones-icono 28×28px), estos
   llevan texto real, así que se dimensionan al contenido y quedan siempre
   visibles (son la acción principal de la fila, no una acción secundaria
   que deba ocultarse hasta el hover). ─── */
.shell .fac-row-actions{display:flex;gap:6px;flex-wrap:nowrap;justify-content:flex-end;white-space:nowrap}

/* Cabecera de grupo dentro de una tabla (Facturas → Resumen: Ingresos/Gastos
   fijos/Gastos variables, 2026-07-08) — no es una fila de dato, así que
   pierde el cursor:pointer/hover que .tablecard tbody tr pone por defecto. */
.shell .tablecard tbody tr.fac-grupo-header{cursor:default;background:var(--surface-2)}
.shell .tablecard tbody tr.fac-grupo-header:hover{background:var(--surface-2)}
.shell .tablecard tbody tr.fac-grupo-header td{padding-top:10px;padding-bottom:10px}

/* ── Tabla viva (.tablecard) responsive — CAMPANA-MOVIL-TABLAS.md,
   2026-07-18. Joni: "no tener que desplazar en las tablas" — hasta ahora
   .tablecard resolvía el móvil con overflow-x:auto (scroll lateral
   funcional, pero justo la mala UX que Joni pide quitar). Patrón CSS
   estándar de tabla responsive: cada <tr> pasa a ser una tarjeta apilada,
   cada <td> muestra su etiqueta (atributo data-label, uno por columna)
   delante del valor; thead se oculta porque la etiqueta ya viaja con la
   celda. La primera celda de cada fila actúa de título de tarjeta (suele
   ser el nombre/identificador de la fila) — sin etiqueta redundante,
   tipografía más fuerte, separador debajo.
   Requiere que cada módulo añada data-label="Columna" a sus <td> — ver
   cobertura por módulo en CAMPANA-MOVIL-TABLAS.md. Cualquier <td> SIN
   data-label se oculta en vez de mostrarse sin contexto (celda "huérfana"
   ilegible es peor que no mostrarla) — esto hace el rollout seguro de
   desplegar módulo a módulo sin esperar a que los 12 estén terminados.
   IMPORTANTE (bug real detectado y corregido 2026-07-18, verificación de
   diseño de la campaña): este bloque tiene que declararse DESPUÉS de las
   reglas .tablecard incondicionales de arriba, no dentro del bloque "Móvil
   real" de más arriba en el archivo — con especificidad CSS empatada, en un
   empate gana la que aparece más tarde en el archivo, esté o no dentro de un
   @media. Declarado antes (como en el primer intento), las reglas de arriba
   ganaban la cascada y el móvil seguía con scroll lateral pese al @media.
   No muevas este bloque a otro sitio del fichero sin volver a comprobar el
   orden de cascada. */
@media(max-width:768px){
  .shell .tablecard{overflow-x:hidden}
  .shell .tablecard table{width:100%;table-layout:fixed;border-collapse:separate;border-spacing:0}
  .shell .tablecard thead{display:none}
  /* tfoot incluido junto a tbody (Personal → Control Horario, fila "Total"
     en <tfoot>, hallazgo real de la verificación de diseño 2026-07-18): sin
     esto la fila de totales quedaba fuera del patrón de tarjeta, sin estilo
     ni etiquetas, rota visualmente en móvil. */
  .shell .tablecard tbody,.shell .tablecard tfoot,.shell .tablecard tr,.shell .tablecard td{display:block;width:100%}
  .shell .tablecard tbody tr,.shell .tablecard tfoot tr{
    border:1px solid var(--border);border-radius:var(--r-md);
    margin-bottom:var(--sp-3);padding:var(--sp-2) 0;background:var(--surface);
  }
  .shell .tablecard tbody tr:last-child,.shell .tablecard tfoot tr:last-child{margin-bottom:0}
  .shell .tablecard tbody td,.shell .tablecard tfoot td{
    display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-3);
    padding:6px var(--sp-4);border-bottom:none;white-space:normal;text-align:right;
  }
  .shell .tablecard tbody td[data-label]::before,.shell .tablecard tfoot td[data-label]::before{
    content:attr(data-label);font-weight:700;font-size:11px;letter-spacing:.04em;
    color:var(--muted);text-align:left;flex-shrink:0;text-transform:uppercase;
  }
  .shell .tablecard tbody td:not([data-label]),.shell .tablecard tfoot td:not([data-label]){display:none}
  .shell .tablecard tbody td:first-child,.shell .tablecard tfoot td:first-child{
    font-weight:800;font-size:var(--fs-sm);color:var(--text);
    border-bottom:1px solid var(--border);padding-bottom:8px;margin-bottom:4px;display:block;
  }
  .shell .tablecard tbody td:first-child::before,.shell .tablecard tfoot td:first-child::before{content:none}
  /* Filas de cabecera de grupo (Facturas → Resumen) no son una tarjeta de
     dato — título de sección, sin etiquetas. */
  .shell .tablecard tbody tr.fac-grupo-header{background:var(--surface-2);border-radius:var(--r-md)}
  .shell .tablecard tbody tr.fac-grupo-header td{display:block;text-align:left}
  .shell .tablecard tbody tr.fac-grupo-header td::before{content:none}
  /* Los botones de acción por fila dependían de :hover para aparecer — en
     móvil no hay hover, así que quedaban invisibles/inalcanzables. Visibles
     siempre por debajo del breakpoint. */
  .shell .row-actions,.shell .fac-row-actions{opacity:1;justify-content:flex-start}

  /* Escape hatch (verificación de diseño, 2026-07-18): tablas tipo
     calendario/matriz (Personal → Cuadrante de turnos, .cuadrante-scroll)
     no encajan en el patrón etiqueta/valor 1:1 — son una rejilla Rol×Días,
     no una lista de registros. Forzarlas al patrón de tarjetas ocultaba la
     cabecera de días y las dejaba igual de anchas pero apiladas: peor que
     el scroll horizontal original. Se excluyen a propósito y conservan el
     scroll lateral de siempre (con cabecera visible, que es legible tal
     cual) hasta que tengan un diseño de calendario móvil propio. */
  /* Markup real: <div class="tablecard cuadrante-scroll"> — mismo elemento,
     no anidado (modules/personal/view.js:1191) — el selector es la
     combinación de ambas clases, no un descendiente. */
  .shell .tablecard.cuadrante-scroll{overflow-x:auto}
  .shell .tablecard.cuadrante-scroll thead{display:table-header-group}
  .shell .tablecard.cuadrante-scroll tbody,.shell .tablecard.cuadrante-scroll tr,.shell .tablecard.cuadrante-scroll td{
    display:revert;width:auto;
  }
  .shell .tablecard.cuadrante-scroll tbody tr{border:none;border-radius:0;margin-bottom:0;padding:0;background:transparent}
  .shell .tablecard.cuadrante-scroll tbody td{padding:var(--sp-2) var(--sp-4);white-space:nowrap;text-align:left}
  .shell .tablecard.cuadrante-scroll tbody td::before{content:none}
  /* Columna de rol fija al desplazar en horizontal (auditoría móvil
     2026-08-05): sin esto, al deslizar hacia la derecha se pierde de vista
     A QUIÉN corresponde cada fila — el problema real del scroll lateral,
     no el scroll en sí (que el comentario de arriba ya justifica conservar
     hasta un rediseño propio). Con la columna fija, cada fila se sigue
     leyendo sola sin tener que volver atrás. */
  .shell .tablecard.cuadrante-scroll{scroll-snap-type:x proximity}
  .shell .tablecard.cuadrante-scroll thead th:first-child,
  .shell .tablecard.cuadrante-scroll td.cuadrante-rol{
    position:sticky;left:0;z-index:1;background:var(--surface);
    box-shadow:2px 0 4px -2px rgba(0,0,0,.15);
  }
  .shell .tablecard.cuadrante-scroll thead th:first-child{background:var(--bg)}
}

/* ── .tablecard.tc-cards (Joni, 2026-07-28) — MISMO patrón de tarjetas de
   arriba, pero disparado por JS (ajustarTablecardsResponsive en
   lmos-core.js) en vez de por @media(max-width:768px). Cubre el caso real
   que detonó el bug de letras partidas: escritorio con ventana NO
   maximizada + tabla de muchas columnas (Ingredientes 12, Lotes 9) — ni es
   "móvil" para el media query ni cabe una tabla normal, así que ni el
   @media de arriba ni el table-layout:auto+nowrap por sí solos la
   resuelven sin partir palabras, recortar columnas o reintroducir scroll
   (las 3 cosas prohibidas). El JS mide table.scrollWidth vs el ancho real
   de .tablecard; si de verdad no cabe, añade esta clase y usa el mismo
   patrón etiqueta/valor que ya funciona en móvil (mismo data-label por
   columna, ya presente en los 12 módulos con listados). REGLA: si tocas el
   bloque @media de arriba, replica el cambio aquí — mismas propiedades,
   mantenidas a mano en dos sitios porque el proyecto no tiene preprocesador
   CSS. Excluye a propósito .cuadrante-scroll (Personal → Cuadrante de
   turnos): esa tabla-matriz ya tiene su propio layout de escritorio
   (table-layout:fixed + min-width:860px, ver más abajo) y el JS nunca le
   añade tc-cards (ver comprobación en lmos-core.js). */
.shell .tablecard.tc-cards{overflow-x:hidden}
.shell .tablecard.tc-cards table{width:100%;table-layout:fixed;border-collapse:separate;border-spacing:0}
.shell .tablecard.tc-cards thead{display:none}
.shell .tablecard.tc-cards tbody,.shell .tablecard.tc-cards tfoot,.shell .tablecard.tc-cards tr,.shell .tablecard.tc-cards td{display:block;width:100%}
.shell .tablecard.tc-cards tbody tr,.shell .tablecard.tc-cards tfoot tr{
  border:1px solid var(--border);border-radius:var(--r-md);
  margin-bottom:var(--sp-3);padding:var(--sp-2) 0;background:var(--surface);
}
.shell .tablecard.tc-cards tbody tr:last-child,.shell .tablecard.tc-cards tfoot tr:last-child{margin-bottom:0}
.shell .tablecard.tc-cards tbody td,.shell .tablecard.tc-cards tfoot td{
  display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-3);
  padding:6px var(--sp-4);border-bottom:none;white-space:normal;text-align:right;
}
.shell .tablecard.tc-cards tbody td[data-label]::before,.shell .tablecard.tc-cards tfoot td[data-label]::before{
  content:attr(data-label);font-weight:700;font-size:11px;letter-spacing:.04em;
  color:var(--muted);text-align:left;flex-shrink:0;text-transform:uppercase;
}
.shell .tablecard.tc-cards tbody td:not([data-label]),.shell .tablecard.tc-cards tfoot td:not([data-label]){display:none}
.shell .tablecard.tc-cards tbody td:first-child,.shell .tablecard.tc-cards tfoot td:first-child{
  font-weight:800;font-size:var(--fs-sm);color:var(--text);
  border-bottom:1px solid var(--border);padding-bottom:8px;margin-bottom:4px;display:block;
}
.shell .tablecard.tc-cards tbody td:first-child::before,.shell .tablecard.tc-cards tfoot td:first-child::before{content:none}
.shell .tablecard.tc-cards tbody tr.fac-grupo-header{background:var(--surface-2);border-radius:var(--r-md)}
.shell .tablecard.tc-cards tbody tr.fac-grupo-header td{display:block;text-align:left}
.shell .tablecard.tc-cards tbody tr.fac-grupo-header td::before{content:none}
.shell .tablecard.tc-cards .row-actions,.shell .tablecard.tc-cards .fac-row-actions{opacity:1;justify-content:flex-start}

/* ── Tabs (vista y módulo) ─── */
.shell .tabs{display:flex;gap:2px;border-bottom:2px solid var(--border);margin-bottom:var(--sp-4)}
.shell .tab{
  padding:8px 16px;font-size:var(--fs-sm);font-weight:800;color:var(--muted);
  border-bottom:2px solid transparent;margin-bottom:-2px;white-space:nowrap;
  transition:color var(--t-fast),border-color var(--t-fast);
}
.shell .tab:hover{color:var(--text)}
.shell .tab.on{color:var(--accent);border-bottom-color:var(--accent)}
.shell .module-tabs{display:flex;gap:2px;border-bottom:2px solid var(--border);margin-bottom:var(--sp-5);overflow-x:auto}

/* ── Navegación de secciones dentro de un módulo (tarjetas, sustituye a .module-tabs) ── */
.shell .section-cards{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(148px,1fr));
  gap:var(--sp-2);margin-bottom:var(--sp-5);
}
.shell .section-card{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-md);
  min-height:40px;padding:9px 14px;display:flex;align-items:center;justify-content:center;
  text-align:center;font-size:var(--fs-sm);font-weight:800;color:var(--muted);
  transition:transform var(--t-fast),box-shadow var(--t-fast),border-color var(--t-fast),
             background var(--t-fast),color var(--t-fast);
}
.shell .section-card:hover{
  transform:translateY(-2px);box-shadow:var(--shadow-2);
  border-color:rgba(245,152,0,.45);color:var(--text);
}
.shell .section-card.active{background:rgba(245,152,0,.14);border-color:var(--accent);color:var(--warn-text)}

.shell .mpanel{display:none}
.shell .mpanel.active{display:block;animation:shellFadeIn .18s var(--ease)}

/* ── Medidor circular (rings) ─── */
.shell .rings{display:flex;gap:var(--sp-6);flex-wrap:wrap}
.shell .ring-wrap{display:flex;flex-direction:column;align-items:center;gap:8px}
.shell .ring{position:relative;width:88px;height:88px}
.shell .ring svg{transform:rotate(-90deg)}
.shell .ring circle{fill:none;stroke-width:8}
.shell .ring .track{stroke:var(--surface-2)}
.shell .ring .val{stroke:var(--accent);stroke-linecap:round;transition:stroke-dashoffset 1s var(--ease)}
.shell .ring .val.ok{stroke:var(--ok)}
.shell .ring .val.bad{stroke:var(--bad)}
.shell .ring-num{position:absolute;inset:0;display:grid;place-items:center;font-family:var(--font-brand);font-size:var(--fs-lg)}
.shell .ring-label{font-size:11.5px;font-weight:700;color:var(--muted)}

/* ── Checklist ─── */
.shell .check{
  display:flex;align-items:center;gap:var(--sp-3);padding:9px 10px;border-radius:var(--r-sm);
  transition:background var(--t-fast);cursor:pointer;
  /* D-tap44 (2026-07-10): la fila entera es el hitbox real del toggle (JS,
     ver pintarChecklistEscritura) — min-height fuerza el mínimo táctil de
     44px aunque el padding real venga sobreescrito por las utilidades
     u-p-*-0 generadas (specificity x7, ver lamaruja-util.css). El cuadrado
     visual de .checkbox se queda en 20px, sin romper la densidad. */
  min-height:var(--tap-min);
}
.shell .check:hover{background:var(--surface-2)}
.shell .checkbox{
  width:20px;height:20px;border-radius:6px;border:2px solid var(--mrj-mocha);flex-shrink:0;
  display:grid;place-items:center;font-size:var(--fs-xs);color:#fff;
  transition:background var(--t-fast),border-color var(--t-fast),transform var(--t-fast);
}
.shell .check.done .checkbox{background:var(--ok);border-color:var(--ok)}
.shell .check:active .checkbox{transform:scale(.85)}
.shell .check.done .check-label{text-decoration:line-through;color:var(--muted)}
.shell .check-time{margin-left:auto;font-size:11px;color:var(--muted)}

/* ── Recetas / tarjetas de entidad ─── */
.shell .recipes{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:var(--sp-4)}
.shell .recipe{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);
  overflow:hidden;cursor:pointer;
  transition:transform var(--t-base),box-shadow var(--t-base),border-color var(--t-base);
}
.shell .recipe:hover{transform:translateY(-4px);box-shadow:var(--shadow-3);border-color:rgba(245,152,0,.5)}
.shell .recipe-img{
  height:96px;background:var(--mrj-sand);display:grid;place-items:center;font-size:38px;
  transition:transform var(--t-slow);position:relative;
}
[data-theme="dark"] .shell .recipe-img{background:rgba(234,205,168,.12)}
.shell .recipe:hover .recipe-img{transform:scale(1.04)}
.shell .recipe-body{padding:var(--sp-3) var(--sp-4) var(--sp-4)}
.shell .recipe-name{font-weight:800;font-size:var(--fs-base);margin-bottom:2px}
.shell .recipe-meta{display:flex;justify-content:space-between;font-size:var(--fs-xs);color:var(--muted);margin-bottom:8px}
.shell .recipe-margin{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:800}

/* ── Calendario contenido ─── */
.shell .cal{display:grid;grid-template-columns:repeat(7,1fr);gap:6px}
.shell .cal-head{font-size:10.5px;font-weight:800;letter-spacing:.08em;color:var(--muted);text-align:center;padding:4px}
.shell .cal-day{
  min-height:74px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r-sm);
  padding:6px;font-size:11px;color:var(--muted);cursor:pointer;
  transition:transform var(--t-fast),box-shadow var(--t-fast),border-color var(--t-fast);
}
.shell .cal-day:hover{transform:translateY(-2px);box-shadow:var(--shadow-1);border-color:var(--accent)}
.shell .cal-day.today{border-color:var(--accent);border-width:2px}
.shell .cal-evt{
  margin-top:4px;border-radius:5px;padding:2px 6px;font-size:10px;font-weight:800;
  background:rgba(245,152,0,.16);color:var(--warn-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.shell .cal-evt.brand{background:rgba(76,44,28,.12);color:var(--mrj-brown)}
[data-theme="dark"] .shell .cal-evt.brand{background:rgba(234,205,168,.12);color:var(--mrj-sand)}
/* Variantes por tipo de contenido (reel/post/carrusel/historia) — mismo
   criterio de alpha sobre la paleta oficial que .stackbar (modules/costes/
   view.js): sin hex nuevos, solo transparencias de brown/orange/mocha/sand. */
.shell .cal-evt.tipo-reel{background:rgba(245,152,0,.16);color:var(--mrj-brown)}
[data-theme="dark"] .shell .cal-evt.tipo-reel{background:rgba(245,152,0,.24);color:var(--mrj-orange)}
.shell .cal-evt.tipo-post{background:rgba(76,44,28,.12);color:var(--mrj-brown)}
[data-theme="dark"] .shell .cal-evt.tipo-post{background:rgba(234,205,168,.12);color:var(--mrj-sand)}
.shell .cal-evt.tipo-carrusel{background:rgba(139,106,79,.22);color:var(--mrj-brown)}
[data-theme="dark"] .shell .cal-evt.tipo-carrusel{background:rgba(139,106,79,.35);color:var(--mrj-sand)}
.shell .cal-evt.tipo-historia{background:rgba(232,213,184,.45);color:var(--mrj-brown)}
[data-theme="dark"] .shell .cal-evt.tipo-historia{background:rgba(232,213,184,.22);color:var(--mrj-sand)}

/* ── Kanban (CRM · Pipeline Leads) ─── */
.shell .kanban{display:flex;gap:var(--sp-4);overflow-x:auto;padding-bottom:6px}
.shell .kanban-col{flex:0 0 250px;background:var(--surface-2);border-radius:var(--r-lg);padding:var(--sp-3);display:flex;flex-direction:column;gap:8px;transition:background var(--t-fast)}
.shell .kanban-col.dragover{background:rgba(245,152,0,.16)}
.shell .kanban-col-head{display:flex;align-items:center;gap:8px;padding:4px 6px 8px;font-size:12.5px;font-weight:800}
.shell .kanban-col-head .cnt{margin-left:auto;background:var(--surface);border:1px solid var(--border);border-radius:var(--r-full);padding:1px 8px;font-size:11px;color:var(--muted)}
/* Color por etapa del pipeline (CRM): reutiliza los tokens pill-* ya
   definidos arriba, sin hex nuevos. Selector reforzado porque .kanban-col-head
   .cnt tiene más especificidad que .pill-* solo y si no, no se vería el color. */
.shell .kanban-col-head .cnt.pill-ok{background:rgba(94,122,58,.14);color:var(--ok-text);border-color:transparent}
.shell .kanban-col-head .cnt.pill-warn{background:rgba(245,152,0,.16);color:var(--warn-text);border-color:transparent}
.shell .kanban-col-head .cnt.pill-bad{background:rgba(192,57,43,.12);color:var(--bad-text);border-color:transparent}
.shell .kanban-col-head .cnt.pill-mut{background:var(--surface-2);color:var(--muted)}
.shell .kanban-card{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-md);
  padding:10px 12px;cursor:grab;transition:transform var(--t-fast),box-shadow var(--t-fast),border-color var(--t-fast);
}
.shell .kanban-card:hover{transform:translateY(-2px);box-shadow:var(--shadow-2);border-color:rgba(245,152,0,.45)}
.shell .kanban-card.dragging{opacity:.35}
.shell .kanban-card b{font-size:var(--fs-sm);display:block;margin-bottom:3px}
.shell .kanban-card .meta{font-size:11.5px;color:var(--muted);display:flex;justify-content:space-between;align-items:center;margin-top:6px}

/* ── Estado / monitor (Sistema) ─── */
.shell .status-row{display:flex;align-items:center;gap:12px;padding:10px 4px;border-bottom:1px solid var(--border)}
.shell .status-row:last-child{border-bottom:none}
.shell .status-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0;position:relative}
.shell .status-dot::before{content:'';position:absolute;inset:-4px;border-radius:50%;opacity:.35;animation:shellPulseRing 2.2s var(--ease) infinite}
.shell .status-dot.ok{background:var(--ok)}.shell .status-dot.ok::before{background:var(--ok)}
.shell .status-dot.warn{background:var(--accent)}.shell .status-dot.warn::before{background:var(--accent)}
.shell .status-dot.bad{background:var(--bad)}.shell .status-dot.bad::before{background:var(--bad);animation-duration:1.1s}
@keyframes shellPulseRing{0%{transform:scale(.7);opacity:.5}70%{transform:scale(1.6);opacity:0}100%{opacity:0}}
.shell .status-name{font-size:var(--fs-sm);font-weight:700;flex:1}
.shell .status-meta{font-size:11.5px;color:var(--muted)}
.shell .meter-row{display:flex;flex-direction:column;gap:5px;margin-bottom:14px}
.shell .meter-row:last-child{margin-bottom:0}
.shell .meter-label{display:flex;justify-content:space-between;font-size:12.5px;font-weight:700}
.shell .meter-label span{color:var(--muted);font-weight:400}

/* ── 8. Identidad de color por bloques temáticos (categorías) ───
   Modificador aditivo sobre .kpi / .card / .modcard: NO cambia su fondo/
   borde base salvo que lleve también una clase .cat-*. Cero hex nuevo:
   mismos tokens ya usados en .cal-evt.tipo-*/.pill-* del propio fichero, más
   --mrj-ink que se estrena aquí para "Finanzas y sistema". Spec: DISENO.md §8.

   Rediseño C6 (Mejoras-Diseno-Estructura.md, 2026-07-08): el lavado de
   fondo tintado — probado en dos rondas de opacidad, 5-6% y luego subido a
   8-14% — hacía "patchwork" entre tarjetas vecinas de categorías distintas
   y presionaba el contraste de --muted (texto gris sobre fondo ya tintado).
   Se retira el fondo tintado por completo: la tarjeta vuelve a ser
   var(--surface) puro, IDÉNTICO a .card/.kpi sin categoría — cero
   diferencia de contraste con el resto de la app. La identidad de
   categoría queda en dos señales finas y no-fondo: el borde superior (ya
   existía, ahora en color sólido del token en vez de rgba difuminado) y un
   punto de color de 7px junto al título (nuevo). El color es acento puro,
   nunca fondo. */
.shell .kpi[class*="cat-"],
.shell .card[class*="cat-"],
.shell .modcard[class*="cat-"]{
  border-top-width:3px;border-top-style:solid;
  padding-top:calc(var(--sp-4) - 2px); /* compensa el 2px extra del borde vs el 1px base; omitible, diferencia imperceptible */
}

/* Punto de color junto al título — única señal adicional a la franja
   superior. .card-title y .kpi-label ya son flex con gap:var(--sp-2): el
   punto entra como primer item de ese flex y el gap existente le da su
   espaciado, sin declarar margen propio. .modcard no es flex (título =
   <b> suelto) así que ahí se declara inline-block con margen explícito.
   Tamaño/forma en una regla común; el color, por categoría, más abajo. */
.shell .card[class*="cat-"] .card-title::before,
.shell .kpi[class*="cat-"] .kpi-label::before{
  content:'';width:7px;height:7px;border-radius:50%;flex-shrink:0;
}
.shell .modcard[class*="cat-"] b::before{
  content:'';display:inline-block;width:7px;height:7px;border-radius:50%;
  margin-right:6px;vertical-align:middle;
}

/* D1 (2026-07-09): las 5 tintas de categoría pasan de referenciar su hex
   --mrj-* a los tokens de fórmula --cat-* (bloque D1 del primer :root) —
   misma L, mismo croma y mismo hue que el token de origen, ΔE < 0.1
   verificado con scripts/verificar-paleta-lch.mjs: cero cambio visual.
   Las INVERSIONES de modo oscuro (control→sand, finanzas→cream) se quedan
   en los tokens oficiales: son un fallback de legibilidad sobre --surface
   oscuro, no la tinta identitaria de la categoría. */

/* Operación — Facturas · Costes · Producción · Operaciones */
.shell .cat-op{border-top-color:var(--cat-op)}
.shell .cat-op .card-title::before,.shell .cat-op .kpi-label::before,.shell .cat-op b::before{background:var(--cat-op)}

/* Control y calidad — Sanidad (APPCC). Invierte a sand en oscuro: el marrón
   --mrj-brown es casi invisible sobre --surface oscuro (#241207), mismo
   criterio ya usado en el fichero para .cal-evt.brand. */
.shell .cat-control{border-top-color:var(--cat-control)}
.shell .cat-control .card-title::before,.shell .cat-control .kpi-label::before,.shell .cat-control b::before{background:var(--cat-control)}
[data-theme="dark"] .shell .cat-control{border-top-color:var(--mrj-sand)}
[data-theme="dark"] .shell .cat-control .card-title::before,[data-theme="dark"] .shell .cat-control .kpi-label::before,[data-theme="dark"] .shell .cat-control b::before{background:var(--mrj-sand)}

/* Marca y clientas — Contenido · Asistente · Fidelización */
.shell .cat-marca{border-top-color:var(--cat-marca)}
.shell .cat-marca .card-title::before,.shell .cat-marca .kpi-label::before,.shell .cat-marca b::before{background:var(--cat-marca)}

/* Negocio B2B — CRM · Ventas B2B · Legal.
   D1 (2026-07-09): primera tinta migrada a la fórmula OKLCH (--cat-b2b,
   :root arriba) — mismo L/C/H que --mrj-mocha, ΔE < 0.1, cero cambio visual. */
.shell .cat-b2b{border-top-color:var(--cat-b2b)}
.shell .cat-b2b .card-title::before,.shell .cat-b2b .kpi-label::before,.shell .cat-b2b b::before{background:var(--cat-b2b)}

/* Finanzas y sistema — Finanzas · Analytics · Sistema. Invierte a cream en
   oscuro por el mismo motivo que .cat-control (--mrj-ink ilegible sobre
   --surface oscuro). */
.shell .cat-finanzas{border-top-color:var(--cat-finanzas)}
.shell .cat-finanzas .card-title::before,.shell .cat-finanzas .kpi-label::before,.shell .cat-finanzas b::before{background:var(--cat-finanzas)}
[data-theme="dark"] .shell .cat-finanzas{border-top-color:var(--mrj-cream)}
[data-theme="dark"] .shell .cat-finanzas .card-title::before,[data-theme="dark"] .shell .cat-finanzas .kpi-label::before,[data-theme="dark"] .shell .cat-finanzas b::before{background:var(--mrj-cream)}

/* 10. Acordeón de tarjetas (.acc) — genérico reutilizable, origen Facturas→
   Anual→"Mes a mes". Mismo truco de .nav-group (grid-template-rows 1fr→0fr,
   sin JS de altura), generalizado sobre `.card`. Items independientes, no
   acordeón estricto (varios pueden estar abiertos a la vez). */
.shell .acc{display:flex;flex-direction:column;gap:var(--sp-3)}

.shell .acc-item{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg);
  overflow:hidden;transition:border-color var(--t-fast),box-shadow var(--t-fast);
}
.shell .acc-item:hover{box-shadow:var(--shadow-1)}
.shell .acc-item.open{border-color:rgba(245,152,0,.45)}

.shell .acc-head{
  display:flex;align-items:center;gap:var(--sp-4);width:100%;text-align:left;
  padding:var(--sp-4) var(--sp-5);min-height:40px;
  transition:background var(--t-fast);
}
.shell .acc-head:hover{background:var(--surface-2)}
.shell .acc-item.open .acc-head{background:var(--surface-2)}

.shell .acc-summary{display:flex;align-items:center;gap:var(--sp-5);flex:1;flex-wrap:wrap;min-width:0}
.shell .acc-summary-title{font-size:var(--fs-base);font-weight:800;min-width:96px}
.shell .acc-summary-item{display:flex;flex-direction:column;gap:1px;min-width:64px}
.shell .acc-summary-item span{font-size:10.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;font-weight:700}
.shell .acc-summary-item b{font-size:13.5px;font-weight:800}

.shell .acc-chev{font-size:11px;color:var(--muted);transition:transform var(--t-base);flex-shrink:0}
.shell .acc-item.open .acc-chev{transform:rotate(180deg);color:var(--accent)}

.shell .acc-body{display:grid;grid-template-rows:0fr;transition:grid-template-rows var(--t-slow)}
.shell .acc-item.open .acc-body{grid-template-rows:1fr}
.shell .acc-body > div{overflow:hidden;min-height:0}
.shell .acc-body-inner{
  padding:var(--sp-4) var(--sp-5) var(--sp-5);border-top:1px solid var(--border);
  display:flex;flex-direction:column;gap:var(--sp-4);
}

.shell .acc-section{display:flex;flex-direction:column;gap:var(--sp-2)}
.shell .acc-subtitle{font-size:11.5px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--muted)}

.shell .acc-ratios{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:var(--sp-3)}
.shell .acc-ratio{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--r-md);padding:var(--sp-3)}
.shell .acc-ratio-label{font-size:10.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em}
.shell .acc-ratio-value{font-family:var(--font-brand);font-size:var(--fs-lg);margin-top:3px}

.shell .acc-cats{display:flex;flex-direction:column;gap:var(--sp-2)}
.shell .acc-cat-row{display:grid;grid-template-columns:140px 1fr 90px;align-items:center;gap:var(--sp-3);font-size:12.5px}
.shell .acc-cat-row span:first-child{font-weight:700;color:var(--text)}
.shell .acc-cat-row span:last-child{text-align:right;font-weight:800}

@media(max-width:720px){
  .shell .acc-summary{gap:var(--sp-3)}
  .shell .acc-cat-row{grid-template-columns:96px 1fr 74px;font-size:var(--fs-xs)}
}
@media (prefers-reduced-motion:reduce){
  .shell .acc-body,.shell .acc-chev{transition:none}
}

/* ═══ M16 + M15 (Mejoras-Diseno-Estructura §3C, 2026-07-08) — scroll pulido y
   entradas suaves nativas. scrollbar-gutter reserva el hueco de la barra de
   scroll (el layout no salta al pasar de una vista corta a una larga);
   overscroll-behavior evita que el scroll dentro de paneles, diálogos y el
   sidebar "arrastre" la página de fondo al llegar al final. @starting-style
   funde el backdrop de los diálogos al abrirse (progressive enhancement:
   navegadores sin soporte lo ignoran y el diálogo abre como hasta ahora). ═══ */
html{scrollbar-gutter:stable}
/* Sin rebote elástico al llegar al final de una pantalla sin más contenido
   (Joni, 2026-07-18: "no tiene sentido que la pantalla no se quede fija, es
   una muy mala sensación"). El scroll real de la app es html/body (.shell
   .main no tiene overflow propio, ver public/lmos-pulltorefresh.js:9-11) —
   overscroll-behavior-y:contain para el rebote/scroll-chaining, sin tocar
   scrollY (pull-to-refresh móvil sigue detectando scrollY===0 igual). */
html,body{overscroll-behavior-y:contain}
.lmos-dialog,.assist-panel,.cmdk,.hist-modal{overscroll-behavior:contain}
@media (prefers-reduced-motion: no-preference){
  .lmos-dialog[open]::backdrop{transition:opacity .25s var(--ease)}
  @starting-style{.lmos-dialog[open]::backdrop{opacity:0}}
}

/* ═══ MEJORAS-50 lote 2 (2026-07-08) ═══ */
/* #23 — separador de día del feed (Hoy / Ayer / fecha) */
.shell .feed-day{
  font-size:10px;font-weight:800;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted);padding:8px 0 3px;
}
/* #18 — draw-in: barras crecen desde la izquierda y los anillos se dibujan al
   pintarse (animación solo-from: el navegador anima hasta el valor inline
   real, así que sirve para cualquier % sin JS). Nada con reduce-motion. */
@media (prefers-reduced-motion: no-preference){
  .shell .bar i{animation:lmosBarGrow .5s var(--ease);transform-origin:left}
  .shell .ring .val{animation:lmosRingIn .8s var(--ease)}
}
@keyframes lmosBarGrow{from{transform:scaleX(0)}}
@keyframes lmosRingIn{from{stroke-dashoffset:226}}
/* #47 — las tarjetas del grid del dashboard bajo el pliegue no se pintan
   hasta acercarse al viewport (alto estimado para que el scroll no salte). */
.shell .grid-dash > .card{content-visibility:auto;contain-intrinsic-size:auto 240px}

/* ── Móvil app-like (≤768px) ───
   Capa ADITIVA: no toca ninguna regla de arriba, solo la refina. Piezas:
   1) ancho fijo (overflow-x:clip en página; scroll horizontal solo
   INTERNO en .tablecard/.kanban); 2) .section-cards plegables (wiring en
   lamaruja-os.js con listener capture, solo e.isTrusted — los .click()
   programáticos de activarPestana/runCapture no abren el selector);
   3) .bottom-nav + FAB "+ Crear" (los ítems llevan también .nav-item para
   heredar el wiring del shell, reseteando aquí el estilo de sidebar que
   esa clase arrastra); 4) ergonomía táctil (safe-areas, requiere
   viewport-fit=cover en index.html; targets ≥44px). */

/* 4b (todas las anchuras): en pantallas táctiles no existe el hover — las
   acciones de fila ocultas hasta hover eran INACCESIBLES en móvil/tablet. */
@media (hover:none){
  .shell .row-actions{opacity:1}
}

/* La barra inferior solo existe en móvil */
.shell .bottom-nav{display:none}

/* El wrapper de herramientas del topbar no existe visualmente en escritorio:
   display:contents = sus hijos participan del flex del topbar EXACTAMENTE
   igual que antes de envolverlos (cero cambio). El botón "Más" solo en móvil. */
.shell .topbar-tools{display:contents}
.shell .topbar-more-btn{display:none}

@media(max-width:768px){
  /* ── 1. Ancho fijo, cero scroll horizontal de página ── */
  html,body{overflow-x:clip;max-width:100vw}
  .shell{overflow-x:clip}
  /* Rejillas multicolumna declaradas inline (bandeja "Hoy" 3 col, ratios del
     dashboard 5 col, pares 1fr 1fr…) → apiladas; el !important es necesario
     porque compiten contra un style="" inline. Los calendarios (.cal) no
     usan .grid y conservan sus 7 columnas. */
  .shell .card .grid[style*="repeat("],
  .shell .card .grid[style*="1fr 1fr"]{grid-template-columns:1fr!important}
  /* Producción → Calendario (#vpCalGrid): rejilla de 7 columnas fijas vía
     clase (no style inline, por eso no la cazaba la regla de arriba) —
     verificación móvil 2026-08-01, tarjetas de día desbordaban recortadas
     e invisibles salvo la primera. */
  .shell #vpCalGrid{grid-template-columns:1fr!important}
  /* Formularios con rejilla de columnas fijas por clase de utilidad
     (Facturas → Subir factura: proveedor/categoría/Fijo-Variable) — mismo
     motivo que #vpCalGrid, apilar en vez de comprimir cada campo a ~180px. */
  .shell .u-gtc-1fr-1fr{grid-template-columns:1fr!important}
  /* Cabecera de tarjeta (título + botón) y estado vacío (icono+texto+CTA):
     sin flex-wrap, un título largo + botón desbordaba la tarjeta. */
  .shell .card-head{flex-wrap:wrap}
  .shell .empty-desc.u-d-inline-flex{flex-wrap:wrap;max-width:100%}
  /* Feed de actividad (Dashboard, Costes → Negociación): la hora/importe a
     la derecha se recortaba con textos largos. */
  .shell .feed-item{flex-wrap:wrap}
  .shell .feed-time{margin-left:0;white-space:normal;flex-basis:100%}
  /* Objetivo táctil mínimo (verificación móvil 2026-08-01): botones
     secundarios, chips de filtro y campos de formulario medían entre
     ~22px y ~38px de alto en toda la app — nada se rompe ni desborda,
     por debajo del mínimo recomendado de var(--tap-min) (WCAG 2.5.5).
     Solo dentro de este @media(max-width:768px); la versión de
     escritorio no se toca. */
  .shell .btn-sm{min-height:var(--tap-min);display:inline-flex;align-items:center}
  .shell .chip{min-height:var(--tap-min);display:inline-flex;align-items:center}
  /* font-size:16px (auditoría móvil 2026-08-05): por debajo de 16px, iOS
     Safari hace zoom automático al enfocar el campo — pasaba en TODOS los
     formularios de la app (--fs-base ronda 13-14px). Solo aquí, dentro de
     este @media; la versión de escritorio sigue con --fs-base. */
  .shell .input,.shell .select,.shell .textarea{min-height:var(--tap-min);font-size:16px}
  /* .tab/.icon-btn (auditoría móvil 2026-08-05): quedaron fuera de la
     pasada de tap-targets del 01/08 — mismo criterio de var(--tap-min). */
  .shell .tab{min-height:var(--tap-min);display:inline-flex;align-items:center}
  .shell .icon-btn{width:var(--tap-min);height:var(--tap-min)}

  /* ── 2. Secciones del módulo como desplegable ── */
  .shell .section-cards{display:flex;flex-direction:column;gap:var(--sp-2);margin-bottom:var(--sp-4)}
  .shell .section-cards .section-card{display:none;min-height:48px;padding:12px 16px;text-align:left;justify-content:flex-start}
  .shell .section-cards .section-card.active{display:flex;justify-content:space-between;gap:var(--sp-3)}
  /* chevron del selector: mismo idioma que .nav-sec/.acc-chev */
  .shell .section-cards .section-card.active::after{
    content:'▾';font-size:11px;color:var(--accent);flex-shrink:0;
    transition:transform var(--t-base);
  }
  .shell .section-cards.open{display:grid;grid-template-columns:1fr 1fr;gap:var(--sp-2)}
  .shell .section-cards.open .section-card{display:flex;animation:shellFadeIn .18s var(--ease)}
  .shell .section-cards.open .section-card.active::after{transform:rotate(180deg)}
  /* en móvil la elevación al hover no aplica: feedback por pulsación */
  .shell .section-cards .section-card:hover{transform:none;box-shadow:none}
  .shell .section-cards .section-card:active{background:var(--surface-2)}

  /* ── 3a. Barra de navegación inferior ── */
  .shell .bottom-nav{
    display:flex;position:fixed;left:0;right:0;bottom:0;z-index:var(--z-sticky);
    background:var(--surface);border-top:1px solid var(--border);
    padding:6px var(--sp-2) calc(6px + env(safe-area-inset-bottom));
    box-shadow:0 -4px 16px rgba(76,44,28,.10);
  }
  /* reset del estilo de sidebar que arrastra .nav-item + estilo propio */
  .shell .bottom-nav .bn-item{
    flex:1;width:auto;margin:0;display:flex;flex-direction:column;align-items:center;
    justify-content:center;gap:3px;padding:5px 2px;min-height:52px;border-radius:var(--r-sm);
    background:none;color:var(--muted);font-size:10px;font-weight:800;letter-spacing:.03em;
    white-space:nowrap;text-transform:uppercase;
    transition:color var(--t-fast),background var(--t-fast);
  }
  .shell .bottom-nav .bn-item:hover{background:none;transform:none}
  .shell .bottom-nav .bn-item:active{background:var(--surface-2)}
  .shell .bottom-nav .bn-mono{
    width:24px;height:24px;border-radius:var(--r-sm);display:grid;place-items:center;
    font-size:10px;font-weight:800;background:var(--surface-2);color:var(--muted);
    transition:background var(--t-fast),color var(--t-fast);
  }
  .shell .bottom-nav .bn-item.active{background:none;color:var(--warn-text)}
    /* MEJORAS-50 #44: color:#fff daba 2.24:1 sobre --accent (ver el comentario
     completo junto a .mrj-chip). */
  .shell .bottom-nav .bn-item.active .bn-mono{background:var(--accent);color:var(--mrj-brown)}

  /* ── 3b. "+ Crear" flotante (FAB) sobre la barra inferior ──
     Se reubica el MISMO botón del topbar (con su menú desplegable intacto):
     el .dropdown contenedor pasa a fixed y el menú abre hacia ARRIBA.
     visibility:visible lo saca del panel .topbar-tools aunque esté plegado
     (el panel oculta con visibility, no con display, justo para esto).
     Fallback sin :has() → el botón se queda dentro del panel, todo funciona. */
  .shell .topbar .dropdown:has(> #lmosCreateBtn){
    position:fixed;right:var(--sp-4);z-index:calc(var(--z-overlay) - 2);
    bottom:calc(72px + env(safe-area-inset-bottom));visibility:visible;
  }
  /* el id gana a las reglas de fila del panel (.topbar-tools .dropdown > .btn)
     que si no lo dejarían sin fondo naranja y a ancho completo */
  .shell #lmosCreateBtn{
    padding:13px 20px;font-size:var(--fs-base);min-height:50px;
    border-radius:var(--r-full);box-shadow:var(--shadow-3);
    /* MEJORAS-50 #44: #fff → var(--mrj-brown) (2.24:1 → 5.54:1). Es el botón
       de accion principal de toda la version movil. */
    background:var(--accent);color:var(--mrj-brown);width:auto;justify-content:center;
  }
  /* left/right:auto + min-width propios: si no, heredaría el left/right:6px
     de las filas del panel y saldría un menú de ~110px pegado al FAB */
  .shell .topbar .dropdown:has(> #lmosCreateBtn) .menu{top:auto;bottom:calc(100% + 8px);left:auto;right:0;min-width:230px}

  /* D41 (mejoras.md, 2026-07-09): FAB contextual por módulo — reutiliza este
     MISMO botón (ver 3b arriba), no se apila un segundo flotante. La clase
     .fab-contextual la pone lamaruja-os.js (actualizarFabContextual) solo en
     las 4 vistas de ACCION_FAB: encoge la píldora "+ Crear" a círculo 56px
     con el "+" como único símbolo visible (se oculta el span " Crear", que
     sigue en el DOM para cuando la vista no está mapeada y el botón vuelve a
     su aspecto/menú normal de siempre). */
  .shell #lmosCreateBtn.fab-contextual{
    width:56px;height:56px;padding:0;border-radius:50%;
    font-size:28px;line-height:1;box-shadow:var(--shadow-2);
  }
  .shell #lmosCreateBtn.fab-contextual .lmos-fab-crear-texto{display:none}

  /* ── 4. Topbar de UNA sola fila + panel "Más" (Joni 2026-07-08) ──
     Anula el flex-wrap/2ª fila del breakpoint 768 de más arriba: todas las
     herramientas viven ahora en .topbar-tools, que aquí deja de ser
     display:contents y pasa a ser un panel desplegable anclado al topbar
     (sticky ⇒ ancestro posicionado), abierto por el botón "Más". Se oculta
     con visibility (no display) para que el FAB +Crear pueda escapar. */
  .shell .topbar{flex-wrap:nowrap;padding:calc(var(--sp-2) + env(safe-area-inset-top)) var(--sp-3) var(--sp-2);gap:6px}
  .shell .topbar-spacer{flex:1;flex-basis:auto;height:auto}
  .shell .crumb-divider{display:none}
  .shell .crumb{overflow:hidden}
  .shell .crumb b{font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .shell .topbar-more-btn{display:inline-flex;flex-shrink:0;min-height:38px;white-space:nowrap}
  /* Fila única del topbar móvil no cabía con marca+asistente+Más a tamaño
     completo (medido en vivo, verificación de diseño móvil 2026-08-01):
     el botón "Más ▾" (única vía a Documentos/Herramientas/Avisos en móvil)
     quedaba empujado fuera del viewport, totalmente inalcanzable. Se
     recorta solo el peso de la marca (logo más pequeño, sin el texto "OS"
     redundante en la propia app) para liberar sitio sin tocar Asistente
     ni Más. */
  .shell .topbar-brand{padding:4px 6px;gap:4px}
  .shell .topbar-brand-logo{height:18px}
  .shell .topbar-brand-sub{display:none}
  /* Asistente siempre visible (piloto Obrador/Costes, 2026-07-10): más
     compacto en la fila única del topbar móvil — mismo botón/texto, solo
     menos padding para que quepa junto a "Más ▾" sin forzar 2ª fila. */
  .shell .btn-asistente{flex-shrink:0;padding:8px 12px;font-size:12.5px;gap:5px}
  .shell .topbar-tools{
    display:flex;flex-direction:column;align-items:stretch;gap:2px;
    position:absolute;top:calc(100% + 6px);right:var(--sp-3);
    width:min(320px,calc(100vw - 24px));
    background:var(--surface);border:1px solid var(--border);border-radius:var(--r-md);
    box-shadow:var(--shadow-3);padding:6px;z-index:var(--z-drop);
    /* OJO (2 bugs cazados en QA): el panel NO puede llevar ni transform (se
       volvería containing block del +Crear position:fixed) ni opacity (la
       opacidad del padre aplasta a TODO el subárbol y el FAB no podría
       escapar). Se oculta SOLO con visibility, que sí es anulable por hijo;
       la sensación de despliegue la ponen las filas animándose abajo. */
    visibility:hidden;
  }
  .shell .topbar-tools.open{visibility:visible}
  /* la fila del FAB +Crear queda FUERA de la animación (anima opacity y
     transform: le haría parpadear y saltar en cada apertura del panel) */
  @media (prefers-reduced-motion:no-preference){
    .shell .topbar-tools.open > *:not(:has(> #lmosCreateBtn)){animation:shellFadeIn .16s var(--ease) backwards}
    .shell .topbar-tools.open > *:nth-child(3){animation-delay:.02s}
    .shell .topbar-tools.open > *:nth-child(4){animation-delay:.04s}
    .shell .topbar-tools.open > *:nth-child(5){animation-delay:.06s}
    .shell .topbar-tools.open > *:nth-child(6){animation-delay:.08s}
    .shell .topbar-tools.open > *:nth-child(n+7){animation-delay:.1s}
  }
  /* dentro del panel, cada herramienta es una fila a lo ancho, estilo .menu */
  .shell .topbar-tools > .btn,
  .shell .topbar-tools .dropdown > .btn{
    width:100%;justify-content:flex-start;border:none;background:none;
    padding:11px 12px;font-size:var(--fs-sm);min-height:44px;border-radius:var(--r-sm);
  }
  .shell .topbar-tools > .btn:active,
  .shell .topbar-tools .dropdown > .btn:active{background:rgba(245,152,0,.13)}
  /* los submenús (Documentos/Herramientas/Avisos/Fijados) se anclan al panel
     entero, a lo ancho, justo donde está su fila */
  .shell .topbar-tools .dropdown{position:static}
  .shell .topbar-tools .dropdown .menu{left:6px;right:6px;min-width:0}
  /* buscador como primera fila del panel, con su texto y sin el ⌘K */
  .shell .search-pill{min-height:44px;min-width:0}
  .shell .search-pill span{display:inline}
  .shell .search-pill kbd{display:none}
  /* ◀ volver y el punto de conexión no aportan en móvil (gesto atrás del
     sistema + cola offline con aviso propio): fuera del panel */
  .shell .topbar-tools .nav-back-btn,.shell .topbar-tools .conn-status{display:none}

  /* las vistas respetan el notch y la barra inferior */
  .shell .view{padding:var(--sp-4) var(--sp-4) calc(88px + env(safe-area-inset-bottom))}

  /* KPIs: 2 columnas compactas (patrón app) en vez de una torre a 1 col */
  .shell .kpis{grid-template-columns:repeat(2,1fr);gap:var(--sp-3)}
  .shell .kpi{padding:var(--sp-3) var(--sp-4)}
  .shell .kpi-value{font-size:24px}
  .shell .dash-tier-1.card{padding:var(--sp-4)}

  /* pestañas .tabs legacy: scroll interno propio en vez de romper el ancho */
  .shell .tabs{overflow-x:auto;scrollbar-width:none}
  .shell .tabs::-webkit-scrollbar{display:none}

  /* toasts a lo ancho, por encima de la barra inferior */
  .shell .toasts{left:var(--sp-4);right:var(--sp-4);bottom:calc(76px + env(safe-area-inset-bottom))}

  /* panel del Asistente: hoja a pantalla completa, altura estable con
     teclado en pantalla (dvh donde existe) */
  .shell .assist-panel{width:100vw;max-width:100vw}
  .shell .assist-view{height:calc(100vh - 140px);height:calc(100dvh - 140px)}
}
@media (prefers-reduced-motion:reduce){
  .shell .section-cards.open .section-card{animation:none}
}

/* MEJORAS-50 #8 (2026-07-08) — pasada dark de las tarjetas semánticas: sobre
   fondo oscuro los tintes al 6-8% casi desaparecen; se suben a ~14% (mismo
   nivel que ya usa [data-theme=dark] .shell .skeleton) y el borde un punto
   más. Los tokens de texto (--ok-text/--bad-text/--warn-text) ya tienen
   variante oscura propia, no se tocan. */
[data-theme="dark"] .shell .kpi.kpi-ok,[data-theme="dark"] .shell .card.card-ok{background:rgba(94,122,58,.16);border-color:rgba(94,122,58,.55)}
[data-theme="dark"] .shell .kpi.kpi-warn,[data-theme="dark"] .shell .card.card-warn{background:rgba(245,152,0,.13);border-color:rgba(245,152,0,.5)}
[data-theme="dark"] .shell .kpi.kpi-bad,[data-theme="dark"] .shell .card.card-bad{background:rgba(192,57,43,.16);border-color:rgba(192,57,43,.55)}

/* ═══ MEJORAS-50 #37 + #38 + #39 (2026-07-08) — pasada de tablas ═══ */
/* Columnas numéricas detectadas por lmos-core (mejorarTabla): derecha + dígitos
   tabulares para que las cifras queden alineadas en vertical. */
.shell .tablecard th.num,.shell .tablecard td.num{text-align:right;font-variant-numeric:tabular-nums}
/* Tablas largas (>25 filas): alto acotado con scroll propio y cabecera fija.
   La clase la pone/quita lmos-core; no aplica si virtualTable ya gestiona. */
.shell .tablecard.tc-scroll{max-height:65vh;overflow-y:auto}
.shell .tablecard.tc-scroll thead th{position:sticky;top:0;z-index:2;background:var(--surface)}
/* Filtro instantáneo inyectado por lmos-core en tablas >10 filas */
.shell .tc-filter{display:flex;align-items:center;gap:10px;padding:8px 12px;border-bottom:1px solid var(--border)}
.shell .tc-filter input{
  flex:1;max-width:280px;border:1px solid var(--border);border-radius:var(--r-sm);
  padding:6px 10px;font-size:var(--fs-sm);background:var(--surface);color:inherit;
}
/* D5: regla propia de :focus retirada (duplicaba el mismo concepto con
   alpha/offset distintos sin motivo real) — "input" ya está en el anillo
   global de "Reset & base", ahora consistente con el resto de inputs. */
.shell .tc-filter .tc-count{font-size:11px;color:var(--muted);white-space:nowrap}
/* #39 — densidad compacta (toggle en Herramientas, persistida en localStorage) */
body.compact .shell .tablecard tbody td{padding:4px var(--sp-3)}
body.compact .shell .tablecard thead th{padding:5px var(--sp-3)}
body.compact .shell .row-actions button,body.compact .shell .row-actions a{height:24px;padding:0 9px}

/* MEJORAS-50 #20 (2026-07-08) — pulso de "este dato cambió" al refrescar un
   dashboard: borde naranja que se enciende y apaga UNA vez (900ms). */
@media (prefers-reduced-motion: no-preference){
  .shell .kpi.kpi-pulse{animation:lmosKpiPulse .9s var(--ease)}
}
@keyframes lmosKpiPulse{
  0%{border-color:var(--border)}
  30%{border-color:var(--mrj-orange);box-shadow:0 0 0 3px rgba(245,152,0,.18)}
  100%{border-color:var(--border);box-shadow:none}
}

/* ═══ MEJORAS-50 #14 (2026-07-08) — menú contextual compartido ═══ */
.shell .cmenu{
  position:fixed;z-index:var(--z-toast);background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-md);box-shadow:var(--shadow-3);padding:6px;min-width:170px;
  animation:cmenuIn .12s var(--ease);
}
@keyframes cmenuIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}
.shell .cmenu-item{
  display:block;width:100%;text-align:left;padding:7px 10px;border-radius:var(--r-sm);
  font-size:12.5px;font-weight:700;background:none;border:none;cursor:pointer;color:inherit;
}
.shell .cmenu-item:hover{background:rgba(245,152,0,.13)}
.shell .cmenu-item-bad{color:var(--bad-text)}
.shell .cmenu-item-bad:hover{background:rgba(192,57,43,.1)}
.shell .cmenu-divider{height:1px;background:var(--border);margin:4px 2px}

/* ═══ MEJORAS-50 #15 (2026-07-08) — drag&drop global de facturas ═══ */
.lmos-dnd-overlay{
  position:fixed;inset:0;z-index:var(--z-toast);display:none;
  align-items:center;justify-content:center;
  background:rgba(19,7,2,.55);backdrop-filter:blur(2px);
}
.lmos-dnd-overlay.open{display:flex}
.lmos-dnd-box{
  border:3px dashed var(--mrj-orange);border-radius:var(--r-xl);
  padding:48px 64px;background:var(--surface);color:var(--mrj-brown);
  font-family:var(--font-brand);font-size:22px;text-align:center;
  pointer-events:none;
}

/* ── <lmos-modal> · regla base (antes inyectada por lmos-components.js;
      movida aquí en la migración CSP 2026-07-08 — los <style> creados por JS
      quedan bloqueados sin 'unsafe-inline') ─── */
lmos-modal:not([open]) { display: none; }

/* ── Cuadrante semanal de turnos (Personal · Joni 2026-07-08) ───
   Réplica del planificador de Qamarero con el vocabulario de la casa:
   filas = roles, columnas = días; chips de turno con acento de color por rol
   (solo tintas de la paleta, borde izquierdo 3px + lavado de fondo, mismo
   mecanismo aditivo que las categorías .cat-*). */
.shell .cuadrante-scroll { overflow-x: auto; }
.shell .cuadrante { width: 100%; border-collapse: collapse; min-width: 860px; table-layout: fixed; }
.shell .cuadrante th, .shell .cuadrante td { border: 1px solid var(--border); padding: 6px; vertical-align: top; }
.shell .cuadrante thead th { text-align: center; font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); background: var(--bg); }
.shell .cuadrante thead th b { display: block; font-size: var(--fs-md); color: var(--text); }
.shell .cuadrante thead th.dia-cerrado { opacity: .55; }
.shell .cuadrante thead th.dia-cerrado i { display: block; font-style: normal; font-size: 9.5px; color: var(--muted); }
.shell .cuadrante thead th.dia-festivo { background: color-mix(in srgb, var(--mrj-orange) 10%, var(--bg)); }
.shell .cuadrante thead th.dia-festivo i { display: block; font-style: normal; font-size: 9.5px; color: var(--mrj-orange); font-weight: 800; }
.shell .cuadrante thead th:first-child, .shell .cuadrante td.cuadrante-rol { width: 92px; }
.shell .cuadrante td.cuadrante-rol { font-weight: 800; font-size: 12.5px; color: var(--mrj-brown); border-left: 3px solid var(--border); }
.shell .cuadrante td.cuadrante-rol.rol-cocinero { border-left-color: var(--mrj-orange); }
.shell .cuadrante td.cuadrante-rol.rol-camarero { border-left-color: var(--mrj-sand); }
.shell .cuadrante td.cuadrante-rol.rol-sala { border-left-color: var(--mrj-mocha); }
.shell .cuadrante td.cuadrante-rol.rol-repartidor { border-left-color: var(--mrj-ink); }
.shell .turno-chip {
  background: var(--surface); border: 1px solid var(--border); border-left-width: 3px;
  border-radius: var(--r-sm); padding: 4px 7px; margin-bottom: 4px;
  font-size: 11.5px; line-height: 1.35;
  overflow: hidden; overflow-wrap: anywhere;
  transition: transform var(--t-fast), box-shadow var(--t-fast);
}
.shell .turno-chip:hover { transform: translateY(-1px); box-shadow: var(--shadow-1); }
.shell .turno-chip b { color: var(--mrj-brown); }
.shell .turno-chip .tc-horas { color: var(--muted); font-size: 10.5px; white-space: nowrap; }
.shell .turno-chip .tc-tramos { display: block; color: var(--muted); font-variant-numeric: tabular-nums; font-size: 10.5px; white-space: normal; }
.shell .turno-chip.rol-cocinero { border-left-color: var(--mrj-orange); background: color-mix(in srgb, var(--mrj-orange) 7%, var(--surface)); }
.shell .turno-chip.rol-camarero { border-left-color: var(--mrj-sand); background: color-mix(in srgb, var(--mrj-sand) 14%, var(--surface)); }
.shell .turno-chip.rol-sala { border-left-color: var(--mrj-mocha); background: color-mix(in srgb, var(--mrj-mocha) 8%, var(--surface)); }
.shell .turno-chip.rol-repartidor { border-left-color: var(--mrj-ink); background: color-mix(in srgb, var(--mrj-ink) 6%, var(--surface)); }

/* ── Familia .shell de formularios/alertas/vacíos (D45) ───
   Copia EXACTA (mismo valor, propiedad por propiedad) de cada regla
   .mrj-* correspondiente de arriba, cambiando solo el selector a
   `.shell .X`. CERO rediseño — verificado numéricamente por
   scripts/verificar-familia-shell.mjs. Excepción legítima sin scope:
   public/mi-espacio.html/mi-espacio.js sigue usando .mrj-input/.mrj-alert.
   Migración módulo a módulo en curso (ver tracker). */

/* ── Form elements ─── */
.shell .input, .shell .select, .shell .textarea {
  width: 100%; padding: 9px 12px; border: 1px solid var(--border);
  border-radius: var(--r-md); font-family: var(--font-body); font-size:var(--fs-base);
  background: var(--surface); color: var(--text);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.shell .input:focus, .shell .select:focus, .shell .textarea:focus {
  border-color: var(--accent); box-shadow: 0 0 0 3px rgba(245,152,0,.15);
}
.shell .input::placeholder { color: var(--muted); }
.shell .label { display: block; font-size:var(--fs-xs); font-weight: 700; color: var(--muted); margin-bottom: 4px; text-transform: uppercase; letter-spacing: .05em; }
.shell .form-group { display: flex; flex-direction: column; gap: var(--sp-xs); }
.shell .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-md); }
@media (max-width: 768px) {
  .shell .form-row { grid-template-columns: 1fr; }
}

/* ── Empty states ─── */
.shell .empty { text-align: center; padding: var(--sp-2xl); color: var(--muted); }
.shell .empty-icon { font-size: 48px; margin-bottom: var(--sp-md); opacity: .5; }
.shell .empty-title { font-family: var(--font-heading); font-size:var(--fs-lg); color: var(--brown); margin-bottom: var(--sp-sm); }
.shell .empty-desc { font-size:var(--fs-sm); line-height: 1.6; }
.shell .empty-cta { margin-top: var(--sp-md); display: inline-flex; }

/* ── Alerts ─── */
.shell .alert { padding: var(--sp-md); border-radius: var(--r-md); font-size:var(--fs-sm); display: flex; align-items: flex-start; gap: 10px; line-height: 1.5; }
.shell .alert-warning { background: rgba(245,152,0,.12); border: 1px solid rgba(245,152,0,.35); color: var(--brown); }
.shell .alert-error { background: rgba(192,57,43,.12); border: 1px solid rgba(192,57,43,.35); color: var(--brown); }
.shell .alert-success { background: rgba(94,122,58,.14); border: 1px solid rgba(94,122,58,.35); color: var(--brown); }

/* ── Dock inferior (piloto reconstrucción navegación, 2026-07-10) ───
   Barra flotante centrada abajo con las 5 zonas de LaMaruja OS (Inicio,
   Obrador, Operativa, Mostrador, Empresa). Valores calcados 1:1 del mockup
   aprobado por Joni (artefacto 0dcd3084) — blur/sombra/radio/tipografía
   literales, no reinterpretados; solo el naranja de acento usa var(--accent)
   (=--mrj-orange) porque ya es el mismo valor que el mockup pintaba a mano.
   Nuevo <nav> hermano de .bottom-nav (ver index.html). Retirada del sidebar
   de 17 módulos (2026-07-11): este dock + los sidebars internos de cada zona
   son ahora la navegación real de escritorio; en móvil convive con
   .bottom-nav como drawer horizontal — ver @media(max-width:768px) más abajo
   y openMobileSidebar()/closeMobileSidebar() en lamaruja-os.js. */
.shell .lmos-dock{
  position:fixed;left:50%;bottom:18px;transform:translateX(-50%);z-index:var(--z-sticky);
  display:flex;gap:6px;background:rgba(255,253,249,.72);
  backdrop-filter:blur(18px) saturate(1.4);-webkit-backdrop-filter:blur(18px) saturate(1.4);
  border:1px solid rgba(255,255,255,.6);border-radius:22px;padding:8px;
  box-shadow:0 18px 40px rgba(50,30,10,.22), 0 2px 6px rgba(50,30,10,.10);
}
[data-theme="dark"] .shell .lmos-dock{
  background:rgba(36,18,7,.72);border-color:rgba(234,205,168,.16);
  box-shadow:0 18px 40px rgba(0,0,0,.45), 0 2px 6px rgba(0,0,0,.3);
}
/* Rediseño (Fase 1, 2026-08-02, rediseno-shell.html §"El dock se queda, y
   ahora se entiende para qué sirve"): de tira de texto con la zona activa en
   pastilla naranja rellena, a botón icono+nombre apilado con el MISMO
   tratamiento suave (fondo --bg, icono en --accent-600) que usa la fila
   activa de la columna lateral (.si-item.active más abajo) — así se ve de un
   vistazo que el dock y la columna hablan de la misma zona. El icono vive en
   el <svg> literal que cada <button> ya trae en index.html (cero JS nuevo). */
.shell .lmos-dock-btn{
  border:none;background:transparent;padding:8px 15px;border-radius:13px;white-space:nowrap;
  font-family:var(--font-heading);font-size:11px;color:var(--muted);cursor:pointer;
  display:flex;flex-direction:column;align-items:center;gap:3px;min-width:62px;
  transition:transform var(--t-fast),background var(--t-fast),color var(--t-fast);
}
.shell .lmos-dock-btn svg{opacity:.6}
.shell .lmos-dock-btn:hover{background:rgba(245,152,0,.12);color:var(--brown);transform:translateY(-2px)}
.shell .lmos-dock-btn.active{background:var(--bg);color:var(--brown);transform:translateY(-1px)}
.shell .lmos-dock-btn.active svg{opacity:1;color:var(--accent-600)}
.shell .lmos-dock-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* ── Botón Asistente en píldora (piloto Obrador/Costes, 2026-07-10) ───
   Ahora vive SIEMPRE visible en la topbar (fuera de #lmosTopbarTools, ver
   index.html) — mismo estilo `.btn-asistente` del mockup aprobado (artefacto
   0dcd3084): fondo --mrj-brown, texto --mrj-cream, "✨" en --mrj-sand. Mismo
   <button id="lmosAssistBtn"> de siempre, cero cambio de wiring/lógica. */
.shell .btn-asistente{
  display:inline-flex;align-items:center;gap:8px;white-space:nowrap;
  background:var(--brown);color:var(--mrj-cream);border:none;border-radius:var(--r-full);
  padding:9px 18px;font-family:var(--font-body);font-size:13.5px;font-weight:700;cursor:pointer;
  box-shadow:0 2px 8px rgba(50,30,10,.18);transition:transform var(--t-fast),box-shadow var(--t-fast);
}
.shell .btn-asistente:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(50,30,10,.24)}
.shell .btn-asistente:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.shell .btn-asistente-spark{color:var(--mrj-sand)}
[data-theme="dark"] .shell .btn-asistente{background:var(--mrj-orange);color:var(--mrj-ink)}
[data-theme="dark"] .shell .btn-asistente-spark{color:var(--mrj-ink)}
/* Móvil (≤768px): sigue siempre visible junto al resto de la topbar — no
   necesita override propio, hereda el flex-wrap ya existente del .topbar. */

/* ── Sidebar interno de Obrador + ficha a pantalla completa (piloto
   Obrador/Costes, 2026-07-10) — SOLO dentro de #view-costes. Valores
   calcados del mockup aprobado (artefacto 0dcd3084: .sidebar-interno/
   .si-zone/.si-item), traducidos a los tokens ya existentes de LaMaruja
   (var(--surface)/var(--border)/var(--accent)/var(--brown) en vez de los
   valores a mano del mockup — mismo resultado visual, cero color nuevo). */
/* Columna FIJA (Fase 1, 2026-08-02, Joni: "la columna de la izquierda debe
   ser FIJA en toda la app, no se puede hacer scroll en la pantalla si debajo
   no hay nada"). Antes align-items:stretch hacía que .lmos-sidebar-interno
   creciera hasta la altura de .lmos-obrador-content (su hermano, a veces
   mucho más alto) — el scroll de la PÁGINA dependía entonces de una columna
   que solo tenía 5 botones reales, dejando hueco vacío debajo de ella. Con
   align-items:flex-start el sidebar mide su altura natural, sticky (clavado
   bajo el topbar) funciona de forma directa sobre él —el bug de Chromium
   documentado el 2026-07-31 (sticky no calcula bien el rango de "pegado" en
   un flex item cuya altura viene de align-items/self:stretch) solo aparecía
   PORQUE el sidebar se estiraba; al dejar de estirarlo, deja de aplicar y ya
   no hace falta el truco de separarlo en dos cajas (EXTERIOR con stretch +
   INTERIOR .si-inner con el sticky real). max-height+overflow-y:auto acota
   la columna a la ventana: si sus items no caben, scrollea ELLA sola, nunca
   la página. El scroll de la página pasa a depender solo de la derecha
   (.lmos-obrador-content, sin tope de altura). */
.shell .lmos-obrador-body{display:flex;gap:var(--sp-5);align-items:flex-start}
.shell .lmos-obrador-content{flex:1;min-width:0}
.shell .lmos-sidebar-interno{
  width:220px;flex-shrink:0;background:var(--surface);border:1px solid var(--border);
  border-radius:var(--r-lg);
  position:sticky;top:var(--sp-3);max-height:calc(100vh - var(--sp-3)*2);overflow-y:auto;
}
.shell .lmos-sidebar-interno .si-inner{padding:18px 12px}
/* Rediseño (Fase 1, 2026-08-02, rediseno-shell.html): la "etiqueta de zona
   en naranja arriba" del prototipo — mismo look de antes (mayúsculas,
   pequeño, acento) + el puntito naranja delante (::before) que trae el
   mockup, único cambio puramente decorativo. */
.shell .si-zone{
  display:flex;align-items:center;gap:5px;width:100%;font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  color:var(--accent);padding:0 10px;margin-bottom:12px;background:transparent;border:none;text-align:left;
  font-family:var(--font-body);cursor:pointer;
}
.shell .si-zone::before{content:"";width:5px;height:5px;border-radius:50%;background:var(--accent);flex:none}
.shell .si-zone:hover{color:var(--brown)}
.shell .si-zone:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* .si-item: de pastilla naranja rellena en el activo a fondo suave + barrita
   naranja al canto (rediseno-shell.html, nota "La columna lateral pesaba
   demasiado") — y hueco para el icono opcional que ahora puede traer cada
   fila (lmos-sidebar-interno.js, campo `icon` de cada item). */
.shell .si-item{
  display:flex;align-items:center;width:100%;gap:10px;padding:10px 12px;border:none;position:relative;
  border-radius:10px;background:transparent;color:var(--brown);font-size:14px;font-weight:600;
  cursor:pointer;margin-bottom:2px;text-align:left;font-family:var(--font-body);
  transition:background var(--t-fast),color var(--t-fast);
}
.shell .si-item svg{flex:none;opacity:.6}
.shell .si-item:hover{background:var(--bg)}
.shell .si-item.active{background:var(--bg);color:var(--brown);font-weight:700}
.shell .si-item.active svg{opacity:1;color:var(--accent-600)}
.shell .si-item.active::before{content:"";position:absolute;left:0;top:6px;bottom:6px;width:3px;border-radius:0 3px 3px 0;background:var(--accent)}
.shell .si-item:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Separador + entrada discreta "Parámetros" (fuera de las 5 zonas reales del
   mockup, ver comentario de view.html) — mismo .si-item, tono apagado y
   tamaño algo menor para que no compita visualmente con las 5 de arriba. */
.shell .lmos-si-sep{height:1px;background:var(--border);margin:10px 10px 8px}
.shell .si-item-muted{color:var(--muted);font-size:12.5px;font-weight:600}
.shell .si-item-muted:hover{background:var(--bg);color:var(--brown)}
/* Cabecera de grupo con texto — "cabeceras de grupo en gris pequeño" del
   rediseño (Fase 1, 2026-08-02): mayúsculas y tamaño intactos, pero
   --muted en vez de --accent (antes competía en naranja con el contenido,
   nota "La columna lateral pesaba demasiado" de rediseno-shell.html). Sirve
   para dos cosas con el MISMO look: la etiqueta estática de un módulo activo
   (Sistema: "Salud del sistema"/"KPIs de negocio"; lmos-sidebar-interno.js:
   el nombre del módulo activo, p.ej. "Costes y recetas") y la cabecera
   "Más en {zona}" que agrupa los módulos plegados de abajo. Nunca es un
   botón — etiqueta pura, no navega. */
.shell .lmos-si-heading{
  display:block;width:100%;font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);padding:0 10px;margin:0 0 8px;
}
.shell .lmos-si-heading:not(:first-child){margin-top:14px}
.shell .si-group-items{margin-bottom:4px;animation:si-group-in .2s ease}
@keyframes si-group-in{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
/* Módulos plegados de la zona bajo "Más en {zona}" (rediseno-shell.html
   §".sect"): fila icono+nombre+flecha, peso normal y tono apagado — a
   diferencia de la vieja .si-group-header no compite visualmente con una
   cabecera de sección, es una fila de navegación más, con el mismo hover que
   .si-item. */
.shell .si-collapsed{
  display:flex;align-items:center;gap:9px;width:100%;padding:8px 10px;border:none;
  border-radius:9px;background:transparent;color:var(--muted);font-size:12.5px;font-weight:600;
  cursor:pointer;text-align:left;font-family:var(--font-body);margin-bottom:1px;
  transition:background var(--t-fast),color var(--t-fast);
}
.shell .si-collapsed svg{flex:none;opacity:.55}
.shell .si-collapsed-arrow{margin-left:auto;opacity:.45;font-size:12px;font-weight:700}
.shell .si-collapsed:hover{background:var(--bg);color:var(--brown)}
.shell .si-collapsed:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Pantalla de inicio de zona (Fase 1, 2026-08-02): una tarjeta por módulo de
   la zona (Obrador/Operativa/Mostrador/Empresa — modules/<zona>/view.html +
   view.js), mismo criterio visual que .modcard (accesos rápidos dentro de un
   módulo) pero con el icono de la zona arriba y un dato real opcional debajo
   del nombre — nunca un guion inventado, ver comentario de cada view.js. */
.shell .lmos-zona-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:var(--sp-3)}
.shell .lmos-zona-card{
  background:var(--surface);border:1px solid var(--border);border-radius:var(--r-md);
  padding:var(--sp-4);cursor:pointer;display:flex;flex-direction:column;align-items:flex-start;gap:8px;
  text-align:left;font-family:var(--font-body);width:100%;
  transition:transform var(--t-base),box-shadow var(--t-base),border-color var(--t-base);
}
.shell .lmos-zona-card:hover{transform:translateY(-2px);box-shadow:var(--shadow-2);border-color:rgba(245,152,0,.45)}
.shell .lmos-zona-card:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.shell .lmos-zona-card svg{color:var(--accent-600)}
.shell .lmos-zona-card-nombre{font-size:14.5px;font-weight:700;color:var(--brown)}
.shell .lmos-zona-card-dato{font-size:12.5px;color:var(--muted)}
/* Cabecera plegable de la columna interna en MÓVIL (mejora #12, 2026-09-03).
   Solo la pinta quien pasa `plegableMovil:true` a LmosSidebarInterno.render()
   — hoy únicamente Producción, con sus 21 items. En escritorio no existe. */
.shell .si-mob{display:none}
@media(max-width:900px){
  .shell .lmos-obrador-body{flex-direction:column;align-items:stretch}
  .shell .si-mob{
    display:flex;align-items:center;gap:9px;width:100%;padding:8px 10px;border:none;
    border-radius:9px;background:var(--bg);color:var(--brown);font-size:13px;font-weight:700;
    font-family:var(--font-body);min-height:var(--tap-min);cursor:pointer;text-align:left;
  }
  .shell .si-mob:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
  .shell .si-inner:not(.open) .si-zone,
  .shell .si-inner:not(.open) .si-group-items,
  .shell .si-inner:not(.open) .lmos-si-heading,
  .shell .si-inner:not(.open) .si-collapsed{display:none}
  .shell .lmos-sidebar-interno{width:100%;position:static;max-height:none;overflow-y:visible;background:none;border:none;border-radius:0}
  .shell .lmos-sidebar-interno .si-inner{display:flex;flex-wrap:wrap;gap:4px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--r-lg)}
  .shell .si-zone{width:auto;margin-bottom:0}
  .shell .si-item{width:auto}
  .shell .si-group-items{display:flex;flex-wrap:wrap;gap:4px;width:100%}
  .shell .lmos-si-sep{width:100%;margin:2px 10px}
  .shell .lmos-si-heading{width:100%;margin:6px 0 0}
  .shell .si-collapsed{width:auto}
  /* Objetivo táctil mínimo (verificación móvil 2026-08-01): .si-zone/
     .si-item/.si-collapsed medían entre 12px y 37px de alto en el
     drawer/sidebar de este breakpoint — nada se rompe ni desborda, pero
     por debajo del mínimo recomendado de var(--tap-min) (WCAG 2.5.5).
     Solo aquí (≤900px), la versión de escritorio (sidebar en columna,
     fuera de este @media) no se toca. */
  .shell .si-zone{min-height:var(--tap-min);display:flex;align-items:center}
  .shell .si-item{min-height:var(--tap-min)}
  .shell .si-collapsed{min-height:var(--tap-min)}
}
/* Ficha a pantalla completa: mismas tarjetas KPI/tablecard del design system,
   solo composición nueva (cabecera + KPIs + escandallo + historiales). */
.shell .lmos-ficha-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-3);margin-bottom:var(--sp-4)}
.shell .lmos-ficha-eyebrow{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);margin-bottom:4px}
.shell .lmos-ficha-title{font-family:var(--font-heading);font-size:clamp(22px,2vw,30px);color:var(--brown);margin:0}
.shell .lmos-ficha-back{flex-shrink:0}
/* Hueco para que el dock no tape el final de la vista en escritorio — misma
   especificidad que ".shell .view" (línea ~1171): gana por ir después en la
   cascada. Escritorio únicamente: en móvil el dock se oculta (regla de
   abajo) y el bottom-nav existente ya reserva su propio espacio. */
@media(min-width:769px){ .shell .view{padding-bottom:calc(var(--sp-7) + 64px)} }
/* Móvil (≤768px) — convivencia con .bottom-nav (2026-07-11, retirada del
   sidebar viejo) ───
   Ya tiene su propio patrón app-like con .bottom-nav fijo abajo (§Móvil,
   línea ~2077) — las 5 zonas son sus accesos directos (2026-09-01: antes
   eran 4 módulos sueltos + "Menú", ver comentario en index.html). Dos
   barras fixed-bottom a la vez se apilarían, así que el dock permanece
   oculto por defecto; hasta el 2026-08-31 se podía desplegar como drawer
   horizontal flotante (mismo backdrop/Escape/clic-fuera que usaba el
   sidebar viejo) tocando el ☰ del topbar — wiring en lamaruja-os.js
   (openMobileSidebar/closeMobileSidebar). El 2026-09-01 (Joni: acceso
   duplicado del .bottom-nav de aquí arriba) el ☰ se apagó en móvil
   (.mobile-menu-btn en §Responsive), así que en este breakpoint el dock
   queda inerte/inalcanzable; el "bottom" seguía calculado para flotar
   justo encima de .bottom-nav sin taparla — mismo valor que ya usan los
   toasts en este breakpoint (línea ~2150) para el mismo propósito, se deja
   igual por si hay que revertir. */
@media(max-width:768px){
  .shell .lmos-dock{
    display:none;position:fixed;left:12px;right:12px;bottom:calc(76px + env(safe-area-inset-bottom));
    transform:none;width:auto;flex-wrap:wrap;justify-content:center;z-index:var(--z-overlay);
  }
  .shell .lmos-dock.mobile-open{display:flex}
}

/* ── Barra contextual (Fase 1, 2026-08-02, rediseno-shell.html) ───────────
   Sustituye las 3-5 filas de controles de cabecera que tenía cada módulo
   (título repetido + Analizar/Excel + chips de navegación + fila de
   filtros/acciones) por UNA fila: nombre de la pantalla + contador,
   pestañas, buscador/filtros, la única acción principal en naranja, el
   resto secundario, y lo que sobra al menú "⋯". La pinta window.LmosCore.
   barraContextual() — ver lmos-core.js para la API y el ejemplo. Piloto:
   modules/costes/ (pantalla Ingredientes).
   Reutiliza a propósito clases que YA existen (.btn/.btn-primary/.btn-ghost/
   .chip/.dropdown/.menu) en vez de inventar un vocabulario paralelo — regla
   de "una sola acción principal por pantalla" ya la impone la propia API. */
.shell .lmos-ctx{
  display:flex;align-items:center;gap:var(--sp-3);padding:10px var(--sp-4);
  border:1px solid var(--border);border-radius:var(--r-lg);background:var(--surface);
  margin-bottom:var(--sp-4);flex-wrap:wrap;
}
.shell .lmos-ctx-title{font-family:var(--font-heading);font-size:var(--fs-lg);color:var(--brown);white-space:nowrap;display:flex;align-items:center}
.shell .lmos-ctx-count{background:var(--bg);color:var(--muted);border-radius:var(--r-full);padding:2px 9px;font-size:11px;font-weight:700;margin-left:8px}
/* Pestañas: si el módulo reparenta su propio contenedor de .chip ya cableado
   (opts.tabsEl, ver lmos-core.js), esta clase solo lo reviste — cero JS
   duplicado, el mecanismo de clic sigue siendo el que ya tenía el módulo. */
.shell .lmos-ctx-tabs{display:flex;gap:2px;background:var(--bg);border:1px solid var(--border);border-radius:10px;padding:3px;flex-wrap:wrap}
.shell .lmos-ctx-tabs .chip{border:0;border-radius:7px;padding:5px 12px;font-size:12px;background:transparent;color:var(--muted)}
.shell .lmos-ctx-tabs .chip:hover{border-color:transparent;color:var(--brown);background:rgba(140,101,79,.08)}
.shell .lmos-ctx-tabs .chip.on{background:var(--surface);color:var(--brown);font-weight:700;box-shadow:var(--shadow-1);border-color:transparent}
.shell .lmos-ctx-spacer{flex:1}
.shell .lmos-ctx .lmos-ctx-mas>.menu{min-width:190px}
@media(max-width:900px){
  .shell .lmos-ctx{padding:9px var(--sp-3);gap:8px}
  .shell .lmos-ctx-title{width:100%}
  .shell .lmos-ctx-spacer{flex-basis:0;flex-grow:0}
}

.shell .alert-info { background: var(--bg); border: 1px solid var(--border); color: var(--brown); }

/* ── Tablas .mrj-* en móvil: tarjetas apiladas (.mrj-table-cards) ─────────
   MEJORA #31 (2026-09-03) · El panel /b2b/admin no se adaptaba a móvil: sus
   dos tablas viven en .mrj-table-wrap, que es `overflow-x:auto` (línea ~346),
   así que en el teléfono se navegaban con scroll lateral — justo lo que Joni
   prohibió en 2026-07-18 ("las tablas no deben de poder escrolear hacia los
   lados, solo arriba y abajo... en todas las pantallas con tablas").

   POR QUÉ UN BLOQUE NUEVO Y NO .tablecard: las 25 reglas de .tablecard,
   incluido TODO su bloque responsive, están scopeadas bajo `.shell`. Las
   páginas standalone (/b2b, /b2b/admin, /mi-espacio) no tienen ancestro
   .shell — poner class="tablecard" ahí dejaría la tabla sin ningún estilo.
   Esto es el equivalente para la familia .mrj-*, con las MISMAS reglas.

   OPT-IN a propósito: .mrj-table se usa también en produccion/view.html,
   marujeo, costes/inicio, facturas/legacy-page y mi-espacio.html. Ninguna de
   esas cambia si no lleva además .mrj-table-cards.

   ⚠ VA AL FINAL DEL FICHERO, DESPUÉS de las reglas .mrj-table/.mrj-table-wrap
   incondicionales. Con especificidad empatada gana la última del archivo,
   esté o no dentro de un @media — el mismo error (declararlo antes y ver que
   el móvil seguía con scroll lateral) ya está documentado en el bloque de
   .tablecard más arriba. No lo muevas sin recomprobar la cascada.

   Requiere data-label="Columna" en cada <td> (los pone modules/b2b/admin.js).
   Un <td> SIN data-label y SIN colspan se OCULTA en vez de mostrarse sin
   contexto — mismo criterio que .tablecard: una celda huérfana ilegible es
   peor que no enseñarla. Los <td colspan> ("Cargando…", estado vacío) sí se
   muestran, a ancho completo y sin etiqueta. */
@media(max-width:768px){
  .mrj-table-cards{overflow-x:hidden;border:none;border-radius:0}
  .mrj-table-cards .mrj-table{width:100%;table-layout:fixed;border-collapse:separate;border-spacing:0}
  .mrj-table-cards .mrj-table thead{display:none}
  .mrj-table-cards .mrj-table tbody,
  .mrj-table-cards .mrj-table tr,
  .mrj-table-cards .mrj-table td{display:block;width:100%}
  .mrj-table-cards .mrj-table tbody tr{
    border:1px solid var(--border);border-radius:var(--r-md);
    margin-bottom:var(--sp-md);padding:var(--sp-sm) 0;background:var(--surface);
  }
  .mrj-table-cards .mrj-table tbody tr:last-child{margin-bottom:0}
  .mrj-table-cards .mrj-table tr:hover td{background:transparent}
  .mrj-table-cards .mrj-table tbody td{
    display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-sm);
    padding:6px 14px;border-bottom:none;white-space:normal;text-align:right;
  }
  .mrj-table-cards .mrj-table tbody td[data-label]::before{
    content:attr(data-label);font-weight:700;font-size:11px;letter-spacing:.04em;
    color:var(--muted);text-align:left;flex-shrink:0;text-transform:uppercase;
  }
  .mrj-table-cards .mrj-table tbody td:not([data-label]):not([colspan]){display:none}
  /* Filas de una sola celda (cargando / vacío): bloque a ancho completo. */
  .mrj-table-cards .mrj-table tbody td[colspan]{display:block;text-align:left;padding:10px 14px}
  .mrj-table-cards .mrj-table tbody td[colspan]::before{content:none}
  /* Primera celda = título de la tarjeta (el email del acceso, el ámbito de
     la tarifa), sin etiqueta redundante y con separador debajo. */
  .mrj-table-cards .mrj-table tbody td:first-child:not([colspan]){
    font-weight:800;font-size:var(--fs-sm);color:var(--text);
    border-bottom:1px solid var(--border);padding-bottom:8px;margin-bottom:4px;
    display:block;text-align:left;
  }
  .mrj-table-cards .mrj-table tbody td:first-child:not([colspan])::before{content:none}
}
