/* ─────────────────────────────────────────────────────────────────────
 * Responsive overlay
 * Loaded after global.css and custom.css so its rules win the cascade
 * (matching specificity). Targets the most common overflow patterns
 * across the app (Bootstrap + custom Metronic-style markup) and adds
 * four breakpoints: ≤1100px (small desktop / tablet), ≤768px (tablet
 * / large phone), ≤480px (phone) and ≤380px (tiny phone).
 * ─────────────────────────────────────────────────────────────────── */

/* === Design tokens =================================================
 * Single source of truth for breakpoints and gutters so component
 * authors can reference them via CSS custom properties instead of
 * hard-coding magic numbers. Used by global utilities below and
 * available for any Svelte <style> block via var(--bp-mobile) etc.
 * ----------------------------------------------------------------- */
:root {
	--bp-desktop-sm: 1100px;
	--bp-mobile:     768px;
	--bp-phone:      480px;
	--bp-tiny:       380px;

	--gutter-mobile: 12px;
	--gutter-phone:  10px;

	/* Touch-friendly minimum tap target (WCAG 2.2 AA). */
	--tap-target: 40px;

	/* Altura de la barra de pestañas inferior del teléfono
	   (`Navigation.svelte`, ≤768px). La leen el padding inferior de la
	   página y todo lo anclado al borde de abajo (snackbar) para no
	   quedar debajo de ella. */
	--mobile-tabbar-h: 58px;
}

/* === 0. Modal stacking ============================================== *
 * The OR drawer sits at z-index 1060; Bootstrap's default modal stack
 * is 1055 (modal) / 1050 (backdrop) so anything opened FROM the drawer
 * (Contactar, Crear factura, presupuesto, mechanic picker, motivo de
 * pausa…) ends up rendered behind the drawer panel. The backdrop
 * still catches clicks, which is why the app appears frozen. Push
 * every modal stack above the drawer so they overlay correctly while
 * staying below the floating SidePanel (1090) and snackbar (2000).
 * ────────────────────────────────────────────────────────────────── */
.modal,
.modal.show {
	z-index: 1080 !important;
}
.modal-backdrop,
.modal-backdrop.show {
	z-index: 1075 !important;
	/* Blur sutil del fondo cuando hay un modal abierto. Da el mismo
	   efecto "popup over blur" que aplican los modales del módulo de
	   fichaje (ModalShell), pero cubre también todos los modales
	   Bootstrap-style del CRM (CreateUser, AddCustomerModal,
	   AddVehicleModal, EmailInvoiceModal…) sin tener que tocarlos
	   uno a uno. */
	-webkit-backdrop-filter: blur(6px);
	backdrop-filter: blur(6px);
	background-color: rgba(15, 23, 42, 0.45) !important;
}

/* Texto auxiliar legible por defecto. Bootstrap define `text-muted`
   como #6c757d (gris claro borderline ilegible sobre cards blancas).
   Lo subimos a #475569 que sigue siendo "secundario" pero se lee.
   `.text-help` es la clase canónica para textos de ayuda en los
   modales del módulo de fichaje. */
.text-help {
	color: #475569 !important;
}
.modal-body .text-muted,
.modal-content .text-muted,
.popup-mode .text-muted {
	color: #475569 !important;
}

/* === UX VIS-06: se ve dónde está el foco del teclado =====================
   Ningún botón ni casilla del tema enseñaba el foco (global.css anula el
   contorno y la sombra en `.btn:focus`, `.form-control:focus`, `.form-select`
   y `.form-check-input`) y los componentes llevan 41 `outline: none`. Un solo
   anillo para toda la app, con `:focus-visible` y no `:focus`: al pinchar con
   el ratón un botón no se pinta (un campo de texto sí, porque se va a
   escribir en él). Azul #1565c0 (5,75:1 sobre blanco) con un filo blanco por
   dentro que lo separa de un botón oscuro, de la barra lateral y del naranja
   Andel. `!important` porque los `outline: none` de los componentes van en su
   <style> con más especificidad que este selector. Lo vigila
   src/tests/ui/focoVisible.test.js. */
:where(a[href], button, summary, [role="button"], [role="tab"], [role="link"],
	[role="menuitem"], [role="option"], [role="switch"], [role="checkbox"],
	[role="radio"], [tabindex]:not([tabindex="-1"])):focus-visible {
	outline: 3px solid #1565c0 !important;
	outline-offset: 2px !important;
	box-shadow: 0 0 0 2px #fff !important;
}
:where(input:not([type="hidden"]), select, textarea):focus-visible {
	outline: 2px solid #1565c0 !important;
	outline-offset: 1px !important;
	box-shadow: 0 0 0 1px #fff !important;
}
/* Campos de fecha y hora: el foco entra en el día, el mes, el año y el botón
   del calendario (por dentro del campo) y en ese botón el campo no casa ni con
   `:focus-visible` ni con `:focus`; con `:focus-within`, sí en los cuatro. No
   se pierde nada: un campo para escribir enseña el anillo también al pinchar
   con el ratón. */
:where(input[type="date"], input[type="time"], input[type="datetime-local"],
	input[type="month"], input[type="week"]):focus-within {
	outline: 2px solid #1565c0 !important;
	outline-offset: 1px !important;
	box-shadow: 0 0 0 1px #fff !important;
}
/* Interruptores hechos con una casilla oculta (`opacity: 0`) y una pista
   dibujada al lado, y colores dentro de una muestra redonda: la casilla no se
   ve, así que el anillo va en lo que se ve. */
:where(input[type="checkbox"], input[type="radio"]):focus-visible + :where([class*="-track"], .slider),
label:has(> input[type="color"]:focus-visible) {
	outline: 3px solid #1565c0 !important;
	outline-offset: 2px !important;
	box-shadow: 0 0 0 2px #fff !important;
}
/* UX VIS-17: un botón de opción hecho con una casilla oculta solo a la vista
   (`visually-hidden`) dentro de su etiqueta («Particular / Empresa»): el foco
   está en la casilla, que no se ve, así que el anillo va en la etiqueta. Regla
   aparte: si un navegador no entiende `:has()`, no se lleva las demás. */
label:has(> :where(input[type="radio"], input[type="checkbox"]).visually-hidden:focus-visible) {
	outline: 3px solid #1565c0 !important;
	outline-offset: 2px !important;
	box-shadow: 0 0 0 2px #fff !important;
}
/* Dentro de una tira de pestañas o de chips que se desliza, del menú lateral,
   de la barra inferior del teléfono, de una tabla o de una columna del
   tablero, el contenedor lleva `overflow` y recortaba el anillo de fuera (de
   una pestaña solo quedaba un palito a un lado). Ahí va POR DENTRO: el mismo
   azul en el borde y el filo blanco hacia dentro. Una tira nueva con
   `role="tablist"` lo hereda. */
:where([role="tablist"] > *, [role="tab"], .tab-group > *, .or-tabs > *,
	.right-panel-tabs > *, .op2-tabs > *, .acct-tabs > *, .acc-tabs > *,
	.cam-tabs > *, .wd-tabs > *, .campaign-subtabs > *, .filter-tabs > *,
	.mech-filter > *, .aside a[href], .aside button, .mobile-tabbar > *,
	tr, .or-card, .cf-cabecera):focus-visible {
	outline-offset: -3px !important;
	box-shadow: inset 0 0 0 5px #fff !important;
}

/* === 1. Universal overflow safety — y el scroll vive en el VIEWPORT ==
 * `overflow-x: hidden` va SOLO en <html>. Por la regla de propagación de
 * CSS, el overflow de <html> se aplica al viewport; y cuando <html> ya
 * no es `visible`, el overflow que lleve <body> se aplica al PROPIO body.
 * Con `overflow-x: hidden` en los dos (como estaba) y el `height: 100%`
 * que global.css pone al body, el body se convertía en un contenedor de
 * scroll INTERNO: `window.scrollY` siempre 0, nada de restauración de
 * scroll al volver atrás, la barra de Safari iOS nunca se retraía y —lo
 * peor— el desplazamiento se ARRASTRABA entre pantallas (navegar desde
 * el final de un listado abría la siguiente ya desplazada, porque el
 * <body> es el mismo elemento en toda la sesión y SvelteKit solo
 * recoloca la ventana). Ahora la página la desplaza el viewport, que es
 * lo que esperan el navegador, SvelteKit y `position: sticky`.
 * ----------------------------------------------------------------- */
html {
	overflow-x: hidden;
	overflow-y: auto;
}
body {
	overflow: visible;
	height: auto;
	min-height: 100%;
}

/* === El scroll no se escapa del modal ===============================
 * Con una hoja a pantalla completa abierta, la página de DETRÁS seguía
 * desplazándose: arrastrabas dentro del filtro, el listado de debajo se
 * movía, y al cerrar aparecías en otro sitio. Medido en «Filtrar
 * clientes», «Campaña Flash» y el alta de cliente: los tres.
 *
 * Se arregla haciendo que el VELO sea el contenedor de scroll y cortando
 * ahí el encadenamiento (`overscroll-behavior: contain`). Un gesto que
 * empieza sobre el modal se queda en el modal, tenga o no contenido que
 * desplazar.
 *
 * NO se hace con `overflow: hidden` en <html>, que es la receta habitual:
 * al dejar de desbordar, el viewport PIERDE su posición de scroll y la
 * página salta arriba del todo (medido: de 500px a 0 al abrir el modal),
 * así que al cerrar aparecías al principio del listado. Eso exigiría
 * guardar y restaurar la posición con JS en cada uno de los ~50
 * componentes que pintan un modal; contener el scroll no necesita nada.
 * ----------------------------------------------------------------- */
.modal-overlay,
.modal.show,
.modal-backdrop.show,
.tc-overlay,
.sp-backdrop,
.sp-panel {
	overflow-y: auto;
	overscroll-behavior: contain;
	-webkit-overflow-scrolling: touch;
}
/* Y el CUERPO del modal, que es quien suele tener el scroll real: al
   llegar a su final, el gesto se encadenaba al viewport y arrastraba la
   página de detrás. */
.modal-body,
.modal-content,
.sp-body,
.tc-body {
	overscroll-behavior: contain;
}

/* === Fundamentos táctiles =========================================
 * Reglas de comportamiento (no de aspecto) que hacen que la app se
 * sienta nativa en un teléfono. Se aplican en todas las anchuras
 * porque son inocuas con ratón.
 * ----------------------------------------------------------------- */

/* Sin ajuste automático de tamaño de texto al girar el teléfono: iOS
   agranda el texto de bloques anchos en horizontal y descuadra las
   tarjetas. */
html {
	-webkit-text-size-adjust: 100%;
	text-size-adjust: 100%;
}

/* Sin destello gris al tocar: usamos estados :active propios. */
* {
	-webkit-tap-highlight-color: transparent;
}

@media (max-width: 768px) {
	/* Sin «tirar para refrescar» ni rebote que se encadene: un gesto de
	   scroll al final de un formulario largo no debe recargar la página.
	   Va en <html> porque es el viewport quien desplaza (ver sección 1). */
	html,
	body {
		overscroll-behavior-y: contain;
	}

	/* Áreas seguras del dispositivo (muesca, barra de gestos) para los
	   elementos anclados al borde de la pantalla, MÁS la barra de
	   pestañas inferior (`--mobile-tabbar-h`): el final de cualquier
	   listado o formulario tiene que poder desplazarse por encima de
	   ella. La app se instala como PWA (`display: standalone`), donde el
	   contenido llega hasta el borde físico. */
	.container-fluid.padded {
		padding-bottom: calc(24px + var(--mobile-tabbar-h, 0px) + env(safe-area-inset-bottom, 0px));
	}

	/* Scroll con inercia en cualquier contenedor deslizable. */
	.h-scroll,
	.table-responsive,
	[data-scroll-x] {
		-webkit-overflow-scrolling: touch;
		overscroll-behavior-x: contain;
	}
}

img, svg, video, canvas {
	max-width: 100%;
	height: auto;
}

/* Allow flex/grid children to shrink below their content size — this
 * is the single most common cause of "text spills out of card" bugs.
 * Bootstrap's .col already sets min-width:0, but lots of code uses
 * raw .d-flex which doesn't.
 */
.row > * { min-width: 0; }
.d-flex > *, .d-inline-flex > * { min-width: 0; }
.card, .card-body, .card-header, .card-footer { min-width: 0; }

/* Long words (URLs, license plates joined to text) shouldn't push the
 * card wider than its container. We use `break-word` (NOT `anywhere`)
 * because `anywhere` will break short tokens like a 4-digit number
 * character-by-character into a vertical column when the flex parent
 * is competing for room. `break-word` only breaks at word boundaries
 * unless a single word is wider than the line — which is what we
 * actually want. */
.card .card-body p,
.card .card-body span,
.card .card-body small,
.card .card-body div {
	word-wrap: break-word;
	overflow-wrap: break-word;
}

/* Buttons should never grow past their container, and pill buttons
 * with long labels should be allowed to wrap on small screens. */
.btn { max-width: 100%; }

/* Tables: any .table-responsive wrapper gets touch scroll. */
.table-responsive {
	overflow-x: auto;
	-webkit-overflow-scrolling: touch;
}

/* === Modal sizing standards =======================================
 * The app has dozens of modals built with different patterns
 * (Bootstrap `.modal-dialog`, custom `.modal-panel`, custom
 * `.confirm-panel`, etc.). Standardise the sane upper bounds here so
 * no modal stretches edge-to-edge on a wide monitor or pushes off
 * screen on a narrow one.
 * ----------------------------------------------------------------- */

/* Universal: stay inside the viewport with ≥16px breathing room. */
.modal-dialog,
.modal-dialog.modal-lg,
.modal-dialog.modal-xl,
.modal-panel {
	max-width: calc(100vw - 16px);
}

/* Default cap when no size class is set. Tightens forms and dialogs
 * that previously flowed to ~full width. */
.modal-dialog:not(.modal-sm):not(.modal-lg):not(.modal-xl) {
	max-width: min(560px, calc(100vw - 16px));
}

/* `modal-lg` → 720px. Bootstrap's default 800px renders too wide on
 * 1280px laptops once you add the dialog padding. */
.modal-dialog.modal-lg {
	max-width: min(720px, calc(100vw - 16px));
}

/* `modal-xl` → 980px. Reasonable for complex multi-card forms
 * (BudgetCreateModal, InvoiceCreateModal) without monopolising 4K
 * displays. */
.modal-dialog.modal-xl {
	max-width: min(980px, calc(100vw - 16px));
}

/* Custom invoicing overlays (`.modal-panel`) used by BudgetForm,
 * BudgetDetail, InvoiceForm, InvoiceDetail had no width cap at all
 * and stretched to the viewport. Cap them like a comfortable form. */
.modal-panel {
	width: 100%;
	max-width: min(820px, calc(100vw - 32px));
	margin: 24px auto;
}

/* Body padding standard for any modal-body + scroll container. */
.modal-body,
.modal-panel .modal-body {
	padding: 18px 22px;
}

/* Form fields stretch to their container — never wider. */
input, select, textarea, .form-control, .form-select {
	max-width: 100%;
}

/* === Reusable responsive utilities ================================
 * Lightweight helpers that any component can opt into. They combine
 * a sensible "comfortable" max-width with a 100vw guard so nothing
 * ever overflows the viewport — a recurring bug pattern in the app.
 * ----------------------------------------------------------------- */

/* Form/page wrappers: caps to a comfortable line length, never wider
 * than the viewport (minus 32px gutters). Drop-in for `max-width: 900px`
 * etc. */
.responsive-form-wrap {
	width: 100%;
	max-width: min(900px, calc(100vw - 32px));
	margin-left: auto;
	margin-right: auto;
}
.responsive-form-wrap--narrow { max-width: min(560px, calc(100vw - 32px)); }
.responsive-form-wrap--wide   { max-width: min(1100px, calc(100vw - 32px)); }

/* Generic modal/panel wrapper used by custom overlays that aren't
 * Bootstrap modals. Same viewport guard. */
.responsive-modal {
	width: 100%;
	max-width: min(820px, calc(100vw - 32px));
	margin: 24px auto;
}

/* Horizontal-scroll wrapper for content that genuinely cannot reflow
 * (kanban columns, wide datasets). Use instead of letting the content
 * blow out the viewport. */
.h-scroll {
	width: 100%;
	max-width: 100%;
	overflow-x: auto;
	overflow-y: hidden;
	-webkit-overflow-scrolling: touch;
	scrollbar-width: thin;
}
.h-scroll > * { min-width: 0; }

/* Touch-friendly hit areas on mobile. Apply to icon buttons that are
 * smaller than 40px. */
@media (max-width: 768px) {
	.tap-target,
	.btn-icon.tap-target {
		min-width: var(--tap-target);
		min-height: var(--tap-target);
	}
}

/* Long URLs / code shouldn't push the layout. */
pre, code {
	white-space: pre-wrap;
	word-break: break-word;
	overflow-wrap: anywhere;
}
a { word-break: break-word; overflow-wrap: anywhere; }

/* Inline-flex wrappers used as button rows wrap when too narrow. */
.d-inline-flex.flex-wrap,
.d-flex.flex-wrap {
	min-width: 0;
}

/* === 2. Fluid typography =========================================== */
/* clamp() pattern: min, preferred (vw-based), max — so text scales
 * gradually with viewport instead of jumping at breakpoints. */

/* El título de la cabecera de página (`.std-page-header__title`) queda
   FUERA de estas reglas genéricas: custom.css le da su propio tamaño por
   corte (1,35rem / 1,2rem / 1,05rem) pensado para compartir línea con los
   accesos rápidos, y los `!important` de abajo se lo pisaban — a 360px el
   título salía a 1,25rem y «Presupuestos» ya no cabía en su columna. */
h1:not(.std-page-header__title), .h1 {
	font-size: clamp(1.4rem, 1.15rem + 1.1vw, 2.2rem);
	line-height: 1.2;
}
h2, .h2 {
	font-size: clamp(1.2rem, 1.02rem + 0.85vw, 1.7rem);
	line-height: 1.25;
}
h3, .h3 {
	font-size: clamp(1.05rem, 0.95rem + 0.55vw, 1.35rem);
	line-height: 1.3;
}
h4, .h4 {
	font-size: clamp(0.98rem, 0.92rem + 0.35vw, 1.18rem);
	line-height: 1.3;
}

/* === 3. Tablet & small desktop (≤1100px) =========================== */
@media (max-width: 1100px) {
	.container-fluid.padded {
		padding-left: 86px;
		/* El padding derecho debe cubrir el margen negativo de la fila de
		   la cáscara (`--bs-gutter-x: 2.5rem` → −20px). Con 16px se salía
		   4px por la derecha en cualquier tablet, portátil pequeño o
		   teléfono EN HORIZONTAL. */
		padding-right: max(20px, env(safe-area-inset-right, 0px));
	}

	/* Cabecera: con poco ancho, el título y la fila de acciones no caben
	   en la misma línea (en un teléfono apaisado las acciones llegaban a
	   x=993 con el viewport en 844). Se permite que las acciones bajen a
	   su propia línea y envuelvan.
	   `min-width: 769px` NO es decorativo: por debajo manda la tira
	   deslizable de `custom.css`, y como este fichero se carga DESPUÉS,
	   sin el límite inferior este `flex-wrap: wrap` ganaba por orden de
	   aparición y devolvía los botones del teléfono a una fila cada uno. */
	.std-page-header__inner {
		flex-wrap: wrap;
	}
	.std-page-header__actions {
		max-width: 100%;
		min-width: 0;
	}
	.std-page-header__actions > * {
		max-width: 100%;
	}
}
@media (max-width: 1100px) and (min-width: 769px) {
	.std-page-header__actions {
		flex-wrap: wrap;
	}

	/* Cualquier tabla ancha se desliza dentro de su envoltorio en vez de
	   estirar la página. */
	.table-responsive,
	.card-body > .table-responsive {
		overflow-x: auto;
		max-width: 100%;
	}
}

/* === 3-bis. Teléfono EN HORIZONTAL ================================
 * Un teléfono apaisado mide ~844×390: ancho de tablet pero ALTO de
 * nada. Los diálogos y paneles calculados como porcentaje de la altura
 * (`max-height: 88vh`, cabeceras generosas) dejan el contenido sin
 * sitio. Aquí solo se aprieta lo VERTICAL — el ancho ya lo resuelven
 * los cortes por anchura.
 * ----------------------------------------------------------------- */
@media (max-height: 500px) and (orientation: landscape) {
	.std-page-header {
		padding-top: 8px;
		padding-bottom: 8px;
	}
	.std-page-header__title {
		font-size: 1.2rem;
	}
	.std-page-header__subtitle {
		display: none;
	}
	.modal-content,
	.modal-panel,
	.tc-shell {
		max-height: 96dvh;
	}
	.modal-header,
	.tc-head {
		padding-top: 8px;
		padding-bottom: 8px;
	}
	.modal-body,
	.tc-body {
		padding-top: 10px;
		padding-bottom: 10px;
	}
}

/* === 4. Tablet / large phone (≤768px) ============================== */
@media (max-width: 768px) {
	/* Bajo 768px el carril de navegación de escritorio desaparece: la
	 * navegación es la barra de pestañas INFERIOR + el cajón
	 * (Navigation.svelte). Arriba no hay ningún botón flotante, así que
	 * ninguna pantalla tiene que reservar hueco en su primera línea y
	 * la página usa casi todo el ancho del viewport. */
	.container-fluid.padded {
		/* `max()` con el inset del dispositivo: en vertical los insets
		   laterales son 0 y manda el gutter; en HORIZONTAL sobre un
		   iPhone con muesca el inset vale ~44px y evita que el contenido
		   quede debajo de la muesca (con `viewport-fit=cover` la página
		   llega al borde físico). */
		padding-left: max(var(--gutter-mobile), env(safe-area-inset-left, 0px));
		padding-right: max(var(--gutter-mobile), env(safe-area-inset-right, 0px));
		padding-top: 0;
	}

	/* === Page shell gutter — el bug estructural del móvil ===========
	 * TODAS las páginas usan el mismo envoltorio:
	 *
	 *   .container-fluid.padded > .row > .col > .px-12.py-12
	 *
	 * En escritorio cuadra, pero en móvil NO: `.row` arrastra el
	 * `--bs-gutter-x: 2.5rem` por defecto de Bootstrap, así que sus
	 * márgenes negativos son de 20px por lado mientras el contenedor
	 * solo aporta 12px de padding. Resultado: la fila se sale 8px por
	 * cada lado (scrollWidth 398 en un viewport de 390) y el
	 * `overflow-x: hidden` de <body> lo RECORTA en silencio — se
	 * perdían 8px de contenido por la derecha en cada pantalla.
	 *
	 * Encima, el `.px-12` interno añadía otros 16px por lado: entre
	 * contenedor + col + px-12 se comían ~43px de los 390 (11% del
	 * ancho) ANTES de que empezara el contenido, y las tarjetas
	 * añadían el suyo encima.
	 *
	 * Arreglo: anular el gutter SOLO en la fila de la cáscara de
	 * página (hijo directo del contenedor) — las filas anidadas
	 * (`.g-3`, `.g-4`, rejillas de KPIs…) conservan el suyo — y
	 * neutralizar el `.px-12` del envoltorio. El gutter lateral pasa
	 * a venir de un único sitio: el contenedor.
	 * ------------------------------------------------------------- */
	/* Las filas ANIDADAS (rejillas de KPIs, formularios) conservan un
	 * gutter, pero uno pequeño y ÚNICO para todo el móvil. Los suyos de
	 * escritorio (2,5rem por defecto de Bootstrap, 1,5rem con `.g-4`)
	 * son márgenes negativos de 20px/12px por lado que, sin padding en
	 * el envoltorio que los absorba, se salen del viewport (el
	 * `overflow-x: hidden` del <body> lo tapa, pero el contenido se
	 * recorta igual). Con el selector de contenedor (0,3,0) ganamos a
	 * las utilidades `.g-*` (0,1,0) sin `!important`.
	 *
	 * Va ANTES que la regla del hijo directo: ambas tienen la misma
	 * especificidad, así que la de la cáscara debe ir después para
	 * ganar por orden. */
	.container-fluid.padded .row {
		--bs-gutter-x: 12px;
	}
	.container-fluid.padded > .row {
		--bs-gutter-x: 0;
	}
	.container-fluid.padded > .row > .col,
	.container-fluid.padded > .row > [class^="col-"],
	.container-fluid.padded > .row > [class*=" col-"] {
		padding-left: 0;
		padding-right: 0;
	}
	.container-fluid.padded > .row > * > .px-12 {
		padding-left: 0 !important;
		padding-right: 0 !important;
	}

	/* `.std-page-fallback-pad` reservaba 60px para la hamburguesa
	 * flotante; sin ella, solo un respiro normal. Se mantiene la clase
	 * para no tocar las páginas que la usan. */
	.std-page-fallback-pad {
		padding-top: 12px !important;
	}

	.px-12 {
		padding-left: 1rem !important;
		padding-right: 1rem !important;
	}
	.py-12 {
		padding-top: 1rem !important;
		padding-bottom: 1.5rem !important;
	}

	/* Reusable header pattern: row with title in .col and actions in
	 * .col-auto. On narrow screens the action group wraps to a new
	 * line under the title with a small gap. */
	.row.align-items-center > .col,
	.row.align-items-center > .col-auto {
		flex: 0 0 auto;
		width: 100%;
		max-width: 100%;
	}
	.row.align-items-center > .col-auto {
		margin-top: 10px;
	}

	/* Action button cluster (the Sidebar-trigger row, "Filtrar
	 * clientes", "Configurar puntos"…) wraps cleanly. */
	.col-auto.d-inline-flex,
	.col-auto > .d-inline-flex,
	.col-auto > .d-flex {
		flex-wrap: wrap;
		gap: 8px !important;
	}

	/* Compact CTA buttons. */
	.btn-lg {
		font-size: 0.95rem;
		padding: 0.55rem 0.95rem;
	}
	.btn {
		font-size: 0.88rem;
	}
	.btn.rounded-pill {
		padding-left: 0.95rem;
		padding-right: 0.95rem;
	}

	/* === Pestañas = tira deslizable ===================================
	 * Cada módulo trae su propia implementación de pestañas (.or-tabs,
	 * .right-panel-tabs, .op2-tabs, .acct-tabs, .tab-group…). Varias
	 * eran `display: flex` SIN wrap ni scroll, así que en un teléfono
	 * cada pestaña caía en su propia línea y una barra de 3 pestañas se
	 * comía 150px de alto (además de dejar de parecer una barra de
	 * pestañas). Aquí se unifican todas en el patrón móvil de siempre:
	 * una sola fila que se desliza con el dedo.
	 *
	 * `[role="tablist"]` cubre las bien construidas; el resto van por
	 * nombre de clase. Al añadir una pestaña nueva, ponle
	 * `role="tablist"` y hereda esto gratis.
	 * ----------------------------------------------------------------- */
	[role="tablist"]:not([aria-orientation="vertical"]),
	.tab-group,
	.or-tabs,
	.right-panel-tabs,
	.op2-tabs,
	.acct-tabs,
	.cam-tabs,
	.wd-tabs,
	.campaign-subtabs,
	.filter-tabs {
		/* `!important` a propósito: los estilos de estas pestañas viven
		   en el <style> SCOPED de su componente Svelte, que compila a
		   `.or-tabs.s-HASH` — más específico que cualquier selector de
		   esta hoja. Sin `!important` la capa móvil no puede ganar. */
		display: flex !important;
		flex-wrap: nowrap !important;
		overflow-x: auto !important;
		overflow-y: hidden;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
		gap: 6px;
		/* El contenedor recorta el scroll; sin esto un `min-width` de
		   pestaña larga volvería a desbordar la tarjeta. */
		max-width: 100%;
	}
	[role="tablist"]:not([aria-orientation="vertical"])::-webkit-scrollbar,
	.tab-group::-webkit-scrollbar,
	.or-tabs::-webkit-scrollbar,
	.right-panel-tabs::-webkit-scrollbar,
	.op2-tabs::-webkit-scrollbar,
	.acct-tabs::-webkit-scrollbar,
	.cam-tabs::-webkit-scrollbar,
	.wd-tabs::-webkit-scrollbar,
	.campaign-subtabs::-webkit-scrollbar,
	.filter-tabs::-webkit-scrollbar {
		display: none;
	}
	[role="tablist"]:not([aria-orientation="vertical"]) > *,
	.tab-group > *,
	.or-tabs > *,
	.right-panel-tabs > *,
	.op2-tabs > *,
	.acct-tabs > *,
	.cam-tabs > *,
	.wd-tabs > *,
	.campaign-subtabs > *,
	.filter-tabs > * {
		flex: 0 0 auto !important;
		white-space: nowrap;
		min-height: var(--tap-target);
	}
	.tab-group .tab-item,
	.tab-group .tab-btn,
	.or-tabs .or-tab,
	.right-panel-tabs .rp-tab,
	.op2-tabs .op2-tab {
		padding: 0.45rem 0.9rem;
		font-size: 0.85rem;
	}

	/* btn-action (the small black/white action chips reused across
	 * Customers and Diagnóstico) shrinks slightly. */
	.btn-action {
		padding: 4px 8px !important;
		font-size: 0.72rem !important;
	}

	/* KPI grids: tighter gutters. */
	.row.g-3 { --bs-gutter-x: 0.75rem; --bs-gutter-y: 0.75rem; }
	.row.g-4 { --bs-gutter-x: 1rem; --bs-gutter-y: 1rem; }

	/* Tables: smaller font, tighter padding. */
	table { font-size: 0.88rem; }
	table th, table td { padding: 8px 10px !important; }

	/* Wrap any bare table in a horizontal scroller. The Bootstrap
	 * `.table-responsive` wrapper already handles its own — this
	 * targets ad-hoc tables that don't have the wrapper. */
	.card-body > table,
	.card > table,
	.col > table {
		display: block;
		max-width: 100%;
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
	}

	/* Los 5 botones redondos del Sidebar (chat, llamadas, incidencias,
	 * citas y novedades) van en la fila del TÍTULO, alineados a la derecha:
	 * son accesos GLOBALES, iguales en todas las pantallas, así que es
	 * donde los espera cualquiera y donde no le roban una línea entera
	 * a la cabecera (medido: con fila propia, Fichaje llegaba a 251px).
	 * El `order` y el `margin-left` los pone custom.css; esta hoja se
	 * carga DESPUÉS, así que un `order` aquí lo pisaría en silencio por
	 * orden de fuente (misma especificidad). No devolver el `order`. */
	.std-page-header__actions .spt-group {
		flex: 0 0 auto !important;
		width: auto !important;
		flex-wrap: nowrap !important;
		justify-content: flex-end !important;
	}
	/* Algo más pequeños DENTRO de la cabecera para dejarle sitio al
	 * título; 38px sigue siendo un objetivo táctil cómodo. Fuera de la
	 * cabecera conservan sus 44px. */
	.std-page-header__actions .spt-group .spt-btn,
	.std-page-header__actions .spt-group button {
		width: 36px !important;
		height: 36px !important;
	}
	.std-page-header__actions .spt-group {
		gap: 4px !important;
	}
	/* Fuera de la cabecera (filas de botones sueltas) envuelve con
	 * normalidad, que es donde tiene sentido. */
	.spt-group {
		flex-wrap: wrap;
		justify-content: flex-start;
	}

	/* === Modales = hoja a pantalla completa ===========================
	 * En un teléfono un diálogo centrado con el contenido dentro de una
	 * caja pequeña obliga a hacer scroll dentro del scroll y esconde los
	 * botones de Guardar/Cancelar bajo el pliegue. El patrón correcto es
	 * una HOJA: cabecera fija arriba, cuerpo con scroll propio e inercia,
	 * y pie de acciones fijo abajo (respetando la barra de gestos de
	 * iOS). Cubre las dos familias de modal de la app: los de Bootstrap
	 * (.modal-dialog) y los propios (.modal-panel, .tc-shell).
	 * ----------------------------------------------------------------- */
	.modal-dialog,
	.modal-dialog.modal-lg,
	.modal-dialog.modal-xl {
		max-width: calc(100vw - 16px);
		margin: 8px auto;
	}

	.modal-content,
	.modal-panel,
	.tc-shell {
		/* `dvh` sigue a la barra de direcciones cuando se retrae; con
		   `vh` el pie se quedaba por debajo del borde visible. */
		max-height: 92dvh;
		display: flex;
		flex-direction: column;
	}
	.modal-body,
	.modal-panel .modal-body,
	.tc-body {
		flex: 1 1 auto;
		overflow-y: auto;
		-webkit-overflow-scrolling: touch;
		/* Que el scroll del modal no arrastre el de la página detrás. */
		overscroll-behavior: contain;
		padding: 14px 16px;
	}
	.modal-header,
	.tc-head {
		position: sticky;
		top: 0;
		z-index: 2;
		background: #fff;
		flex: 0 0 auto;
		padding: 12px 14px;
	}
	.modal-footer,
	.tc-foot {
		position: sticky;
		bottom: 0;
		z-index: 2;
		background: #fff;
		flex: 0 0 auto;
		gap: 8px;
		padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
		box-shadow: 0 -4px 12px rgba(15, 23, 42, 0.06);
	}
	/* Acciones del pie: a lo ancho y en orden de importancia. Un botón
	   de 90px en la esquina es difícil de acertar con el pulgar. */
	.modal-footer > .btn,
	.tc-foot > .btn {
		flex: 1 1 auto;
		min-width: 44%;
		min-height: 44px;
	}
	/* El botón de cerrar de la cabecera, grande. */
	.modal-header .btn-close,
	.tc-close {
		min-width: 40px;
		min-height: 40px;
	}

	/* === Formularios táctiles =========================================
	 * 1. `font-size: 16px` NO es estética: por debajo de 16px, Safari en
	 *    iOS hace ZOOM automático al enfocar el campo y deja la página
	 *    desencuadrada (el usuario tiene que pellizcar para volver). Es
	 *    el fallo de usabilidad móvil más común en un panel de gestión
	 *    con formularios largos como éste.
	 * 2. Altura mínima de 44px = objetivo táctil cómodo (WCAG 2.2 AA).
	 * ----------------------------------------------------------------- */
	input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
	select,
	textarea,
	.form-control,
	.form-select {
		font-size: 16px !important;
		max-width: 100%;
	}
	.form-control,
	.form-select,
	input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="hidden"]),
	select {
		min-height: 44px;
	}
	textarea.form-control { min-height: 88px; }
	/* Checkbox y radio: diana grande de verdad. En escritorio son 13px. */
	input[type="checkbox"],
	input[type="radio"] {
		width: 20px;
		height: 20px;
		min-width: 20px;
	}
	/* Etiqueta de campo un poco más aireada para el pulgar. */
	.form-label { margin-bottom: 0.3rem; }

	/* Botones e items pulsables: diana mínima. */
	.btn:not(.btn-link):not(.btn-sm),
	button.btn:not(.btn-link):not(.btn-sm) {
		min-height: var(--tap-target);
	}

	/* Cards used as KPI tiles: a touch less padding. */
	.card .card-body { padding: 14px; }

	/* Common header pattern: a flex row aligned justify-between with
	 * a title block and an actions block. Stack the actions below the
	 * title with a small gap. Used by the customers, marketing,
	 * invoicing and OR pages. */
	.d-flex.justify-content-between.align-items-center {
		flex-wrap: wrap;
		gap: 12px;
	}
	.d-flex.justify-content-between.align-items-center > * {
		min-width: 0;
	}

	/* Dashboard layout: the two top groups (facturación + clients)
	 * stack and the inner KPI strips collapse to a single column. */
	.dash-top-row {
		display: flex;
		flex-direction: column;
		gap: 12px;
	}
	.dash-group { width: 100%; }
	.dash-group-row {
		display: grid !important;
		grid-template-columns: 1fr 1fr !important;
		gap: 8px !important;
	}

	/* Chart wrappers: give them a sane max width so they shrink with
	 * the card. Chart.js will rescale on resize. */
	canvas, .chart-container, .sparkline {
		max-width: 100% !important;
		height: auto !important;
	}

	/* Tag-input chips and other long-pill rows: wrap. */
	.svelte-tags-input-tag,
	.tag-input-tag {
		max-width: 100%;
	}

	/* Dropdown menus inside cards: cap width. */
	.dropdown-menu {
		max-width: calc(100vw - 24px);
	}
}

/* === 5. Phone (≤480px) ============================================ */
@media (max-width: 480px) {
	/* En teléfono el gutter de página baja a 10px. Se redefine el TOKEN
	 * (no solo el padding del contenedor) para que cualquier componente
	 * que se sangre hasta los bordes con un margen negativo —la barra
	 * pegajosa de guardar de Configuración de facturación, por ejemplo—
	 * siga automáticamente al contenedor. Cablear un −12px a mano hacía
	 * que esas barras se salieran 2px por lado por debajo de 480px. */
	:root {
		--gutter-mobile: var(--gutter-phone);
	}

	.container-fluid.padded {
		padding-left: max(var(--gutter-phone), env(safe-area-inset-left, 0px));
		padding-right: max(var(--gutter-phone), env(safe-area-inset-right, 0px));
		/* Sin padding-top: la cabecera de página lleva el suyo (con la
		   muesca). Sumarlo aquí dejaba una franja muerta en la parte
		   alta de TODAS las pantallas de teléfono. */
		padding-top: 0;
	}

	/* === Tables → stacked card rows ===================================
	 * On phones we drop horizontal scroll and reflow each row as a
	 * vertical block. The thead is hidden and each <td> becomes a
	 * "Etiqueta … valor" line — readable without ever scrolling
	 * horizontally. Pages that absolutely need the tabular layout can
	 * opt out by adding `.no-stack` to the table.
	 *
	 * La ETIQUETA sale de `data-label`, que estampa `stackTables.js`
	 * copiando el `<th>` de la columna (ver
	 * `src/lib/mobile/stackTables.js`, montado en el layout). Sin
	 * etiqueta la fila apilada era una lista de valores sueltos sin
	 * decir de QUÉ eran: «4521 KLM / 128.400 / 12/03/2026 / 234,50 €».
	 * ----------------------------------------------------------------- */
	.table-responsive,
	.card-body > table,
	.card > table,
	.col > table,
	.card .table-responsive,
	.col .table-responsive {
		overflow-x: visible;
	}
	table:not(.no-stack),
	.table-responsive table:not(.no-stack) {
		display: block;
		width: 100%;
		border: 0;
		/* Las anchuras mínimas de escritorio (min-width: 1080px…) no
		   aplican cuando la tabla se apila. */
		min-width: 0 !important;
	}
	table:not(.no-stack) thead {
		display: none;
	}
	table:not(.no-stack) tbody,
	table:not(.no-stack) tr,
	table:not(.no-stack) td {
		display: block;
		width: 100%;
	}
	table:not(.no-stack) tr {
		background: #ffffff;
		border: 1px solid #e5e7eb;
		border-radius: 12px;
		padding: 10px 12px;
		margin-bottom: 8px;
		box-shadow: 0 1px 2px rgba(15, 23, 42, 0.03);
	}
	table:not(.no-stack) td {
		padding: 5px 0 !important;
		border: 0 !important;
		font-size: 0.85rem;
		line-height: 1.4;
		text-align: left !important;
		min-height: 0;
		white-space: normal;
		/* Nada dentro de una celda puede desbordar la tarjeta. */
		max-width: 100%;
		overflow-wrap: break-word;
	}
	/* Celda con etiqueta: nombre de columna a la izquierda (gris,
	   pequeño) y valor a la derecha, alineados como una ficha. */
	table:not(.no-stack) td[data-label] {
		display: flex;
		align-items: baseline;
		justify-content: space-between;
		gap: 10px;
	}
	table:not(.no-stack) td[data-label]::before {
		content: attr(data-label);
		flex: 0 0 auto;
		max-width: 45%;
		font-size: 0.72rem;
		font-weight: 700;
		text-transform: uppercase;
		letter-spacing: 0.02em;
		color: #64748b;
		line-height: 1.5;
	}
	/* La primera celda es el identificador de la fila (matrícula, nº de
	   documento, nombre): va a cuerpo completo, sin etiqueta, como
	   título de la tarjeta. */
	table:not(.no-stack) td[data-label]:first-child {
		display: block;
		font-size: 0.95rem;
		font-weight: 700;
	}
	table:not(.no-stack) td[data-label]:first-child::before {
		display: none;
	}
	/* Dentro de una fila apilada no hay columna que respetar, así que un
	   `text-truncate` (pensado para que la columna no se ensanche en
	   escritorio) solo sirve para esconder el nombre del cliente detrás
	   de unos puntos suspensivos. Aquí el texto envuelve. El `max-width`
	   suele venir en `style=""` inline, de ahí el `!important`. */
	table:not(.no-stack) td .text-truncate {
		overflow: visible;
		text-overflow: clip;
		white-space: normal;
		max-width: none !important;
	}

	/* Celdas de acciones (botones/enlaces): a lo ancho y envolviendo,
	   nunca en una línea que se corte por la derecha. */
	table:not(.no-stack) td[data-cell="actions"] {
		display: flex;
		flex-wrap: wrap;
		gap: 6px;
		justify-content: flex-start;
	}
	table:not(.no-stack) td[data-cell="actions"]::before {
		display: none;
	}
	/* Slight visual separation between fields in a stacked row. */
	table:not(.no-stack) td + td {
		border-top: 1px dashed #f1f5f9 !important;
		padding-top: 6px !important;
		margin-top: 2px;
	}
	/* Empty/zero-content cells just collapse out of view to avoid
	 * lingering empty divider lines. */
	table:not(.no-stack) td:empty {
		display: none;
	}
	/* Una celda cuyo único contenido es un contenedor vacío también
	   sobra (columnas de icono sin icono para esa fila). */
	table:not(.no-stack) td[data-empty="1"] {
		display: none;
	}

	.px-12 {
		padding-left: 0.6rem !important;
		padding-right: 0.6rem !important;
	}
	.py-12 {
		padding-top: 0.5rem !important;
		padding-bottom: 1rem !important;
	}

	h1:not(.std-page-header__title), .h1 { font-size: 1.4rem !important; line-height: 1.2; }
	h2, .h2 { font-size: 1.15rem !important; line-height: 1.25; }
	h3, .h3 { font-size: 1.02rem !important; line-height: 1.3; }

	/* Single-column for any 2/3/4-up grid that uses utility
	 * classes — overrides only when the page hasn't supplied its
	 * own .col-12 fallback. */
	.row > .col-md-3,
	.row > .col-md-4,
	.row > .col-md-6,
	.row > .col-lg-3,
	.row > .col-lg-4,
	.row > .col-lg-6,
	.row > .col-xl,
	.row > .col-xl-3,
	.row > .col-xl-4,
	.row > .col-xl-6 {
		flex: 0 0 100%;
		max-width: 100%;
	}

	/* …salvo los CAMPOS DE FILTRO cortos. Una columna que en escritorio
	 * es `col-md-3` (4 por fila) y contiene solo su etiqueta y UN
	 * control cabe de sobra a media pantalla. Estirarla al 100% convertía
	 * los paneles de filtros —que son rejillas de 4— en columnas de 7
	 * campos que empujaban el listado fuera de la primera pantalla.
	 * Dos por fila mantiene el control cómodo (≈175px) y parte la altura
	 * del panel por la mitad. Los campos anchos (`col-md-6`, texto libre)
	 * siguen a línea completa. */
	.row > .col-md-3:has(> .form-control),
	.row > .col-md-3:has(> .form-select),
	.row > .col-md-3:has(> label + .form-control),
	.row > .col-md-3:has(> label + .form-select),
	.row > .col-lg-3:has(> .form-control),
	.row > .col-lg-3:has(> .form-select) {
		flex: 0 0 50%;
		max-width: 50%;
	}

	/* Tabs become a horizontal scroller on phone. */
	.tab-group {
		display: flex;
		flex-wrap: nowrap;
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
	}
	.tab-group::-webkit-scrollbar { display: none; }

	/* Tables shrink even more. */
	table { font-size: 0.82rem; }
	table th, table td { padding: 6px 8px !important; }

	/* Modales a pantalla completa en teléfono: la hoja ES la pantalla. */
	.modal-dialog,
	.modal-dialog.modal-lg,
	.modal-dialog.modal-xl {
		max-width: 100vw;
		margin: 0;
		min-height: 100dvh;
	}
	.modal-content {
		border-radius: 0;
		min-height: 100dvh;
		max-height: 100dvh;
	}
	/* ── La TERCERA familia de modales: `.modal-overlay` ───────────────
	 * Once componentes (alta de cliente y de vehículo, editar vehículo,
	 * conectar OBD, los cuatro de facturación, admin…) pintan el velo con
	 * `.modal-overlay` y la tarjeta dentro (`.modal-container`,
	 * `.modal-box`). No estaban en ninguna regla de esta capa, así que en
	 * el teléfono salían como tarjeta flotante con márgenes muertos
	 * arriba y abajo mientras el resto de la app abría hojas. Aquí se
	 * unifican.
	 *
	 * Los diálogos de CONFIRMACIÓN cortos (`.guard-container` del aviso de
	 * duplicado, `.modal-box-narrow` de admin) se quedan como tarjeta
	 * centrada a propósito: una pregunta de dos líneas a pantalla completa
	 * se lee como un error de la app, no como una pregunta. Solo se les
	 * garantiza que quepan. */
	/* El `!important` es OBLIGATORIO, no pereza: estas tarjetas declaran
	 * su `max-width`/`border-radius` en el <style> SCOPED de su
	 * componente, que compila a `.modal-container.s-HASH` — la MISMA
	 * especificidad (0,2,0) que un selector de esta hoja — y Svelte
	 * inyecta los estilos de componente DESPUÉS de las hojas estáticas,
	 * así que gana por orden de aparición. Sin `!important` la regla no
	 * hacía nada (medido: la tarjeta seguía en 358px con esquinas
	 * redondeadas). Misma trampa que `.content` de Facturación y las
	 * pestañas. */
	.modal-overlay {
		padding: 0 !important;
		align-items: stretch !important;
	}
	.modal-overlay > .modal-container,
	.modal-overlay > .modal-box:not(.modal-box-narrow) {
		max-width: 100% !important;
		width: 100% !important;
		margin: 0 !important;
		border-radius: 0 !important;
		min-height: 100dvh;
		max-height: 100dvh;
		display: flex;
		flex-direction: column;
	}
	/* Confirmaciones cortas: centradas, pero dentro del viewport. Una
	 * pregunta de dos líneas a pantalla completa se lee como un error de
	 * la app, no como una pregunta. */
	.modal-overlay:has(> .guard-container),
	.modal-overlay:has(> .modal-box-narrow) {
		padding: 16px !important;
		align-items: center !important;
	}
	.modal-overlay > .guard-container,
	.modal-overlay > .modal-box-narrow {
		max-width: calc(100vw - 32px) !important;
		max-height: calc(100dvh - 32px);
		overflow-y: auto;
	}

	/* Overlays propios (invoicing, fichaje): mismo comportamiento. */
	.modal-panel {
		max-width: 100vw;
		width: 100vw;
		margin: 0;
		border-radius: 0;
		min-height: 100dvh;
		max-height: 100dvh;
	}
	.tc-overlay {
		padding: 0;
	}
	.tc-shell {
		max-height: 100dvh;
		min-height: 100dvh;
		border-radius: 0;
	}
	/* Cabecera de la hoja: deja hueco a la muesca cuando la PWA corre
	   a pantalla completa. */
	.modal-header,
	.tc-head {
		padding-top: calc(12px + env(safe-area-inset-top, 0px));
	}

	/* Dashboard inner KPI rows go single-column on phones. */
	.dash-group-row {
		grid-template-columns: 1fr !important;
	}

	/* Buttons inside d-flex.gap-2 rows shrink. */
	.d-flex.gap-2 > .btn,
	.d-flex.gap-3 > .btn {
		font-size: 0.8rem;
		padding: 0.4rem 0.8rem;
	}

	/* Cards: tighten body padding even more. */
	.card .card-body { padding: 12px; }

	/* Plate chips and other inline pills: ensure they don't push
	 * neighbours. */
	.symbol, .symbol-circle {
		flex-shrink: 0;
	}
}

/* === 6. Tiny phone (≤380px) ======================================= */
@media (max-width: 380px) {
	/* Los 5 accesos rápidos de la cabecera, algo más juntos y de 34px: a
	 * 360px dejan al título 150px y «Facturación» cabe sin partirse (con
	 * 36px le quedaban 132px). La reserva del título (100% − 210px) está
	 * en custom.css. */
	.std-page-header__actions .spt-group .spt-btn,
	.std-page-header__actions .spt-group button {
		width: 34px !important;
		height: 34px !important;
	}
	.std-page-header__actions .spt-group {
		gap: 3px !important;
	}

	.container-fluid.padded {
		padding-left: 10px;
		padding-right: 10px;
	}

	.px-12 {
		padding-left: 0.4rem !important;
		padding-right: 0.4rem !important;
	}
	.py-12 {
		padding-top: 0.75rem !important;
		padding-bottom: 0.75rem !important;
	}

	h1:not(.std-page-header__title), .h1 { font-size: 1.25rem !important; }
	h2, .h2 { font-size: 1.05rem !important; }
	h3, .h3 { font-size: 0.95rem !important; }

	/* Card body even tighter. */
	.card .card-body { padding: 10px; }

	/* Table cells tighter. */
	table { font-size: 0.78rem; }
	table th, table td { padding: 5px 6px !important; }

	/* Buttons inside common spacing wrappers. */
	.btn { font-size: 0.82rem; padding: 0.4rem 0.75rem; }
	.btn-sm { font-size: 0.72rem; padding: 0.3rem 0.6rem; }
	.btn.rounded-pill {
		padding-left: 0.85rem;
		padding-right: 0.85rem;
	}

	/* Tab group items tighter. */
	.tab-group .tab-item,
	.tab-group .tab-btn {
		padding: 0.38rem 0.7rem;
		font-size: 0.78rem;
	}

	.btn-action {
		padding: 4px 7px !important;
		font-size: 0.68rem !important;
	}
}

/* === 12. Barras propias de Facturación (`.top-bar`) ===================
 * Las seis pantallas de documento de Facturación (nueva factura, nuevo
 * presupuesto, sus tres variantes de edición/rectificación y la ficha de
 * factura) NO usan `<PageHeader>`: traen su propia barra pegajosa con el
 * título a la izquierda y los botones a la derecha, con el padding
 * lateral de escritorio (3,5rem = 56px por lado).
 *
 * En un teléfono eso son 112px de los 390 gastados en aire, y como la
 * barra es `justify-content: space-between` SIN envolver, los botones
 * («Guardar borrador», «Emitir factura»…) se salían de la pantalla: 90px
 * fuera en nuevo-presupuesto y 11px en nueva-factura, medidos a 390px con
 * el `overflow-x` del body en `visible` (con la mordaza puesta se veían
 * «bien» y el botón simplemente no existía para el usuario).
 *
 * Estas dos pantallas nunca habían pasado la revisión de móvil por un
 * motivo tonto: hasta el arreglo de SSR de esta misma rama devolvían un
 * 500, así que no se podían ni abrir para medirlas.
 * ------------------------------------------------------------------- */
@media (max-width: 768px) {
	.top-bar {
		flex-wrap: wrap;
		gap: 8px;
		/* `!important`: el `padding: … 3.5rem` de la barra vive en el
		   <style> SCOPED de cada página (`.top-bar.s-HASH`, (0,2,0)) y
		   ganaba a esta regla — medido a 360px, el título seguía a 56px del
		   borde (3,5rem) mientras las tarjetas arrancaban a 17px. */
		padding-left: var(--gutter-mobile, 12px) !important;
		padding-right: var(--gutter-mobile, 12px) !important;
		padding-top: 12px !important;
		padding-bottom: 12px !important;
	}
	/* El grupo de botones se lleva su propia línea y envuelve dentro. */
	.top-bar > .d-flex {
		flex-wrap: wrap;
		min-width: 0;
	}
	.top-bar > .d-flex:last-child {
		flex: 1 1 100%;
		justify-content: flex-start;
	}
	/* El contenido de estas páginas reserva el carril del menú de
	 * escritorio (`.content { margin-left: 90px }` en el <style> SCOPED
	 * de cada una, que compila a `.content.s-HASH`, (0,2,0)); en móvil la
	 * navegación es la barra inferior y ese carril era una franja muerta
	 * de 90px que además empujaba el formulario fuera del viewport
	 * (medido a 390px: contenido arrancando en x=107). El `!important`
	 * es obligatorio para ganar al scoped. */
	.content {
		margin-left: 0 !important;
	}
	.form-column,
	.preview-column {
		padding-left: var(--gutter-mobile, 12px);
		padding-right: var(--gutter-mobile, 12px);
	}
	/* La hoja de vista previa imita un A4: su cabecera interna (tipo de
	 * documento + número, alineados a los extremos) no baja de ~410px y
	 * empujaba la PÁGINA 20px a 390 y 50px a 360. Que se desplace dentro
	 * de su propia caja —la regla de la casa para contenido ancho— en vez
	 * de arrastrar el documento entero. */
	.preview-container {
		max-width: 100%;
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
	}
	.preview-paper {
		max-width: 100%;
	}
}
