/* ══════════════════════════════════════════════════════════════
   BeautySoft — app.css
   Shared component styles. Load after app-tokens.css (§5 CLAUDE.md).
   Source Serif 4 (--font-display) is restricted to hero/login/dashboard
   VIP h1/h2 only — never in content-page headings (§5.5 CLAUDE.md).
   ══════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════
   Login — warm single-canvas shell, "Buenos días" (V3)
   docs/superpowers/specs/2026-08-14-login-revamp-design.md
   Scoped ENTIRELY under body.login-page: app.css loads on every screen
   (partials/head.php) via partials/head.php — nothing here may leak
   into the ERP shell (spec §5).
   ══════════════════════════════════════════════════════════════ */
body.login-page {
    margin: 0;
    min-height: 100vh;
    font-family: 'Inter', system-ui, sans-serif;
    color: #1C3650;
    /* La textura de ruido NO se declara acá: app-tokens.css:107-114 ya la
       aplica a `body` en toda la app. Repetirla era duplicar bytes por una
       premisa falsa (creímos que el login no la tenía; sí la tiene). */
    background-color: #FBF7EE;
    position: relative;
    overflow-x: hidden;
}
/* Firma de la pantalla: el sello de la marca al 5%, sangrando por el borde
   derecho. Solo index.php la lleva — forgot/reset no (spec §2). */
.login-page .login-mark {
    position: fixed;
    right: -3vw;
    top: 50%;
    transform: translateY(-50%);
    width: 50vw;
    max-width: 720px;
    opacity: .05;
    pointer-events: none;
    user-select: none;
}
.login-page .login-shell {
    position: relative;
    z-index: 1;
    min-height: 100vh;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-content: center;
    padding: 56px clamp(20px, 9vw, 140px);
}
.login-page .login-main {
    display: block;
}
.login-page .login-main-inner {
    width: 100%;
    max-width: 432px;
}
.login-page .login-brand-logo {
    height: 76px;
    width: auto;
    display: block;
    margin-bottom: 36px;
}
.login-page .login-brand-logo-propio { max-width: 280px; object-fit: contain; }
.login-page .login-brand-logo:has(+ .login-eslogan) { margin-bottom: 10px; }
.login-page .login-eslogan { margin: 0 0 30px; font-family: 'Source Serif 4', Georgia, serif; font-style: italic; color: var(--brand-600, #2F6E9E); font-size: 1rem; }
.login-page .login-greeting {
    font-family: 'Source Serif 4', Georgia, serif;
    font-weight: 600;
    font-size: clamp(2.4rem, 4.4vw, 3.5rem);
    line-height: 1.02;
    letter-spacing: -.025em;
    margin: 0 0 14px;
    color: #1C3650;
}
.login-page .login-rule {
    height: 2px;
    width: 64px;
    background: #3F86BD;
    border-radius: 2px;
    margin: 0 0 16px;
    transform-origin: left center;
    animation: loginRule .7s cubic-bezier(.2,.7,.3,1) both;
}
@keyframes loginRule {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}
.login-page .login-sub {
    margin: 0 0 34px;
    color: #5B6B7A;
    font-size: 1rem;
    line-height: 1.5;
    max-width: 40ch;
}
/* Línea intermedia del subtítulo de dos renglones (reset.php?bienvenida=1:
   "Cuenta: <email>" + la frase de invitación, spec §2). */
.login-page .login-sub-line {
    margin: 0 0 6px;
    color: #5B6B7A;
    font-size: 1rem;
    line-height: 1.5;
    max-width: 40ch;
}
.login-page .login-card {
    background: #fff;
    border: 1px solid #F2E8D3;
    border-radius: 12px;
    padding: 28px 28px 26px;
    box-shadow: 0 20px 44px -32px rgba(28,54,80,.34);
}
.login-page .login-card-body {
    padding: 0;
}
.login-page .login-card .form-label {
    display: block;
    font-size: .78rem;
    font-weight: 600;
    color: #2A4A6B;
    margin-bottom: 7px;
    letter-spacing: 0;
}
.login-page .login-card .form-control {
    width: 100%;
    min-height: 44px; /* piso táctil — tap-targets.spec.js (spec §8) */
    min-width: 0;
    border: 1px solid #F2E8D3;
    border-radius: 8px;
    background: #FDFBF6;
    padding: 12px 14px;
    font-size: .98rem;
    font-family: inherit;
    color: #1C3650;
    transition: border-color .15s, background .15s;
}
/* #8A8880 medía 3.40:1 sobre #FDFBF6 — bajo AA, y era un RETROCESO: antes
   el placeholder heredaba el gris de Bootstrap (~8:1). #5B6B7A mide 5.27:1 y
   sigue siendo claramente más claro que el texto escrito (#1C3650). */
.login-page .login-card .form-control::placeholder { color: #5B6B7A; }
/* outline real, nunca box-shadow: en Modo de Alto Contraste de Windows la
   sombra no se pinta y el foco desaparece (spec §8). */
.login-page .login-card .form-control:focus-visible {
    outline: 2px solid #3F86BD;
    outline-offset: 2px;
}
.login-page .login-card .form-control:focus {
    border-color: #3F86BD;
    background: #fff;
    box-shadow: none;
}
/* Chrome pinta amarillo sobre el lienzo cálido si no se fuerza (spec §7). */
.login-page .login-card .form-control:-webkit-autofill,
.login-page .login-card .form-control:-webkit-autofill:hover,
.login-page .login-card .form-control:-webkit-autofill:focus {
    -webkit-box-shadow: inset 0 0 0 100px #FDFBF6;
    -webkit-text-fill-color: #1C3650;
    caret-color: #1C3650;
}
.login-page .login-pwd { position: relative; }
.login-page .login-pwd .form-control { padding-right: 44px; }
.login-page .login-pwd button {
    position: absolute;
    right: 0;
    top: 0;
    height: 44px; /* piso táctil — spec §8 */
    width: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: 0;
    color: #5B6B7A;
    border-radius: 7px;
    cursor: pointer;
    font-size: 1.02rem;
}
.login-page .login-pwd button:hover {
    color: #1C3650;
    background: #FBF7EE;
}
.login-page .login-pwd button:focus-visible {
    outline: 2px solid #3F86BD;
    outline-offset: 1px;
}
.login-page .login-row {
    margin: -4px 0 22px;
}
/* Mismo motivo que .login-submit: la regla de tema para <a> tiene (0,5,1) y
   pinta estos enlaces de #3F86BD, que sobre blanco mide 3.39:1 y no pasa AA.
   #2F6E9E mide 6.20:1. */
.login-page .login-link {
    color: #2F6E9E !important;
    font-size: .83rem;
    text-decoration: none;
    font-weight: 500;
}
.login-page .login-link:hover { text-decoration: underline; }
/* Foco visible propio: sin esto el enlace cae al anillo por defecto del
   navegador, que en Alto Contraste de Windows puede no verse (spec §8). */
.login-page .login-link:focus-visible {
    outline: 2px solid #3F86BD;
    outline-offset: 2px;
    border-radius: 3px;
}
/* Fondo teal-600, no teal-500: blanco sobre #3F86BD mide 3.39:1 y no pasa
   AA (4.5) para texto normal; sobre #2F6E9E mide 6.20:1 (spec §3).

   El `color` lleva !important a propósito. En reset.php este control es un
   <a>, y bs-theme-beautysoft.css trae:
     [data-color-theme="BeautySoft_Theme"] a:not(.btn):not(.nav-link):not(.sidebar-link):not(.dropdown-item)
   Los cuatro :not() suben esa regla a especificidad (0,5,1) y le ganan a
   `.login-page .login-submit` (0,2,0): el texto salía teal sobre teal,
   medido en 1.83:1 — ilegible. Escalar especificidad acá sería una escalera
   frágil que el próximo cambio de tema vuelve a romper. */
.login-page .login-submit {
    width: 100%;
    min-height: 44px;
    border: 0;
    border-radius: 8px;
    background: #2F6E9E;
    color: #fff !important;
    font-family: inherit;
    font-weight: 600;
    font-size: .98rem;
    padding: 13px 20px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    text-decoration: none;
    transition: background .15s;
}
.login-page .login-submit:hover { background: #245A84; color: #fff !important; }
.login-page .login-submit:focus-visible {
    outline: 2px solid #2F6E9E;
    outline-offset: 2px;
}
.login-page .login-back {
    margin: 18px 0 0;
    text-align: center;
    font-size: .88rem;
}
.login-page .login-footer {
    margin-top: 26px;
    display: flex;
    align-items: center;
    gap: 16px;
    font-size: .76rem;
    color: #5B6B7A;
    flex-wrap: wrap;
}
.login-page .login-secure {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
/* Instalar app (PWA, issue #834): discreto, no compite con el botón de
   "Iniciar sesión" — arranca oculto, app-install.js lo muestra solo si el
   navegador realmente puede instalar. Reestilizado, NO borrado — borrarla
   deja el botón PWA sin estilos y regresa #834 (spec §5). */
.login-page .login-install {
    text-align: center;
    margin: 18px 0 0;
}
.login-page .btn-instalar-login {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: transparent;
    border: 1px solid #E3D8C2;
    color: #5B6B7A;
    font-family: inherit;
    font-size: .78rem;
    font-weight: 600;
    padding: 6px 14px;
    border-radius: 999px;
    cursor: pointer;
    transition: border-color .15s, color .15s;
}
.login-page .btn-instalar-login:hover {
    border-color: #3F86BD;
    color: #2F6E9E;
}
@media (max-width: 640px) {
    .login-page .login-shell { padding: 40px 20px; }
    .login-page .login-mark { right: -38vw; width: 118vw; opacity: .045; }
    .login-page .login-brand-logo { margin-bottom: 32px; }
    .login-page .login-greeting { font-size: 2.15rem; }
    .login-page .login-card { padding: 24px 20px 22px; }
}
@media (prefers-reduced-motion: reduce) {
    .login-page .login-rule { animation: none; }
}

/* ══════════════════════════════════════════════════════════════
   Dashboard welcome panel (replaces flat welcome bar)
   ══════════════════════════════════════════════════════════════ */
.dash-welcome {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    border-radius: 18px;
    background: linear-gradient(135deg, #3F86BD 0%, #2F6E9E 100%);
    color: #fff;
    padding: 28px 32px;
    margin-bottom: 24px;
    box-shadow: 0 18px 40px -22px rgba(47,110,158,0.45);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
}
.dash-welcome::before {
    content: "";
    position: absolute;
    width: 380px; height: 380px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(169,214,245,0.55) 0%, rgba(169,214,245,0) 70%);
    top: -180px; right: -120px;
    z-index: 0;
    animation: dhBlob1 22s ease-in-out infinite alternate;
}
.dash-welcome::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(rgba(255,255,255,0.05) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,0.05) 1px, transparent 1px);
    background-size: 44px 44px;
    mask-image: radial-gradient(ellipse 90% 80% at 50% 50%, black 30%, transparent 85%);
    -webkit-mask-image: radial-gradient(ellipse 90% 80% at 50% 50%, black 30%, transparent 85%);
    z-index: 0;
    pointer-events: none;
}
.dash-welcome-content { position: relative; z-index: 1; max-width: 640px; }
.dash-welcome-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: rgba(255,255,255,0.92);
    background: rgba(255,255,255,0.12);
    border: 1px solid rgba(255,255,255,0.22);
    padding: 4px 11px;
    border-radius: 999px;
    margin-bottom: 14px;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.dash-welcome-eyebrow .dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: #F2E8D3;
    box-shadow: 0 0 0 3px rgba(242,232,211,0.35);
}
.dash-welcome h1 {
    font-family: 'Source Serif 4', Georgia, serif;
    font-weight: 600;
    font-size: clamp(1.55rem, 2.4vw, 2rem);
    line-height: 1.14;
    color: #fff;
    margin: 0 0 6px;
    letter-spacing: -0.01em;
}
.dash-welcome p {
    color: rgba(255,255,255,0.82);
    font-size: 0.95rem;
    margin: 0;
}
.dash-welcome-actions {
    position: relative;
    z-index: 1;
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}
.dash-welcome-actions .btn-quick {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 9px 16px;
    border-radius: 10px;
    font-size: 0.86rem;
    font-weight: 600;
    background: rgba(255,255,255,0.13);
    color: #fff !important;
    border: 1px solid rgba(255,255,255,0.24);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    transition: background .15s, transform .12s;
    text-decoration: none;
}
.dash-welcome-actions .btn-quick:hover {
    background: rgba(255,255,255,0.22);
    color: #fff !important;
    transform: translateY(-1px);
    text-decoration: none;
}
/* Primary CTA on hero — solid white pill, teal text. Use for the one most-important action. */
.dash-welcome-actions .btn-quick.btn-quick-primary {
    background: #fff;
    border-color: rgba(255,255,255,0.9);
    color: #2F6E9E !important;
    font-weight: 700;
}
.dash-welcome-actions .btn-quick.btn-quick-primary:hover {
    background: rgba(255,255,255,0.92);
    color: #2F6E9E !important;
}

/* KPI cards (dashboard mini-grid) */
.kpi-mini {
    background: #fff;
    border: 1px solid rgba(28,54,80,0.08);
    border-radius: 14px;
    padding: 20px 22px;
    height: 100%;
    position: relative;
    overflow: hidden;
    transition: transform .15s, box-shadow .15s;
}
.kpi-mini:hover {
    transform: translateY(-2px);
    box-shadow: 0 14px 28px -18px rgba(47,110,158,0.25);
}
.kpi-mini .kpi-mini-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}
.kpi-mini .kpi-mini-label {
    font-size: 0.74rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: #6b7280;
    margin: 0;
}
.kpi-mini .kpi-mini-icon {
    width: 40px;
    height: 40px;
    border-radius: 12px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    flex-shrink: 0;
}
.kpi-mini .kpi-mini-icon.bg-teal   { background: rgba(63,134,189,0.12); color: #2F6E9E; }
.kpi-mini .kpi-mini-icon.bg-amber  { background: rgba(245,158,11,0.14); color: #b45309; }
.kpi-mini .kpi-mini-icon.bg-charcoal { background: rgba(28,54,80,0.08); color: #1C3650; }
.kpi-mini .kpi-mini-icon.bg-indigo { background: rgba(129,140,248,0.14); color: #4f46e5; }
.kpi-mini .kpi-mini-value {
    font-size: 2rem;
    font-weight: 700;
    line-height: 1;
    color: #1C3650;
    letter-spacing: -0.01em;
    margin: 0 0 6px;
}
.kpi-mini .kpi-mini-sub {
    font-size: 0.82rem;
    color: #6b7280;
    margin: 0;
}
.kpi-mini.is-primary {
    background: linear-gradient(135deg, #3F86BD 0%, #2F6E9E 100%);
    border-color: transparent;
    color: #fff;
    box-shadow: 0 14px 32px -18px rgba(47,110,158,0.55);
}
.kpi-mini.is-primary .kpi-mini-label,
.kpi-mini.is-primary .kpi-mini-sub { color: rgba(255,255,255,0.78); }
.kpi-mini.is-primary .kpi-mini-value { color: #fff; }
.kpi-mini.is-primary .kpi-mini-icon {
    background: rgba(255,255,255,0.15);
    color: #fff;
    border: 1px solid rgba(255,255,255,0.22);
}

/* Dark mode for new dashboard cards */
:root[data-menu-color="dark"] .kpi-mini {
    background: var(--card-bg);
    border-color: var(--card-border);
}
:root[data-menu-color="dark"] .kpi-mini .kpi-mini-value { color: var(--surface-900); }
:root[data-menu-color="dark"] .kpi-mini .kpi-mini-sub,
:root[data-menu-color="dark"] .kpi-mini .kpi-mini-label { color: #9aa39d; }

/* ══════════════════════════════════════════════════════════════
   Premium page header (clientes, tipos_servicio, empleados, …)
   Replaces the flat "h4 + p" pattern.
   ══════════════════════════════════════════════════════════════ */
.page-hero {
    position: relative;
    background: #fff;
    border: 1px solid rgba(28,54,80,0.07);
    border-radius: 14px;
    padding: 22px 26px;
    margin-bottom: 24px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    flex-wrap: wrap;
    overflow: hidden;
    box-shadow: 0 6px 22px -16px rgba(28,54,80,0.18);
}
.page-hero::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 5px;
    background: linear-gradient(180deg, #3F86BD 0%, #2F6E9E 100%);
    border-radius: 14px 0 0 14px;
}
.page-hero::after {
    content: "";
    position: absolute;
    right: -120px; top: -80px;
    width: 280px; height: 280px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(63,134,189,0.10) 0%, rgba(63,134,189,0) 70%);
    pointer-events: none;
    z-index: 0;
}
.page-hero-main {
    display: flex;
    align-items: center;
    gap: 16px;
    min-width: 0;
    position: relative;
    z-index: 1;
}
.page-hero-icon {
    width: 52px;
    height: 52px;
    border-radius: 14px;
    background: linear-gradient(135deg, #3F86BD 0%, #2F6E9E 100%);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.4rem;
    flex-shrink: 0;
    box-shadow: 0 10px 22px -12px rgba(47,110,158,0.55);
}
.page-hero-text { min-width: 0; }
.page-hero-title {
    font-family: var(--font-ui);
    font-size: 1.5rem;
    font-weight: 700;
    color: #1C3650;
    margin: 0 0 2px;
    letter-spacing: -0.01em;
    line-height: 1.15;
}
.page-hero-subtitle {
    font-size: 0.92rem;
    color: #6b7280;
    margin: 0;
}
.page-hero-actions {
    position: relative;
    z-index: 1;
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}

:root[data-menu-color="dark"] .page-hero {
    background: var(--card-bg);
    border-color: var(--card-border);
}
:root[data-menu-color="dark"] .page-hero-title { color: var(--surface-900); }
:root[data-menu-color="dark"] .page-hero-subtitle { color: #9aa39d; }

@media (max-width: 575px) {
    .page-hero { padding: 18px 18px 18px 22px; }
    .page-hero-icon { width: 44px; height: 44px; font-size: 1.15rem; }
    .page-hero-title { font-size: 1.2rem; }
    .page-hero-actions { width: 100%; }
    .page-hero-actions .btn { width: 100%; }
}

/* ── Page header ──────────────────────────────────────────────── */
.page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 22px;
}
.page-header h1 {
    font-family: var(--font-ui); /* Inter — Source Serif 4 only in hero/login/VIP */
    font-weight: 700;
    font-size: 1.7rem;
    margin: 0;
    color: var(--brand-900);
}
.page-header .subtitle {
    color: var(--surface-700);
    font-size: 0.95rem;
    margin-top: 4px;
}

/* ── Sidebar section labels ────────────────────────────────────── */
.sidebar-menu-section {
    display: block;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.38);
    padding: 20px 20px 6px;
    pointer-events: none;
    user-select: none;
}

/* ── DataTables controls ──────────────────────────────────────── */
.dataTables_wrapper {
    font-family: var(--font-ui);
    font-size: 0.88rem;
    color: var(--brand-900);
}
.dataTables_wrapper .dataTables_length,
.dataTables_wrapper .dataTables_filter {
    margin-bottom: 12px;
    display: flex;
    align-items: center;
    gap: 8px;
}
.dataTables_wrapper .dataTables_filter {
    justify-content: flex-end;
}
.dataTables_wrapper .dataTables_length label,
.dataTables_wrapper .dataTables_filter label {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    font-weight: 500;
    color: var(--surface-700);
    white-space: nowrap;
}
.dataTables_wrapper .dataTables_length select {
    border: 1px solid var(--brand-100);
    border-radius: var(--radius-sm, 8px);
    padding: 4px 8px;
    font-family: var(--font-ui);
    font-size: 0.86rem;
    color: var(--brand-900);
    background: #fff;
    cursor: pointer;
    outline: none;
}
.dataTables_wrapper .dataTables_length select:focus {
    border-color: var(--brand-500);
    box-shadow: 0 0 0 3px rgba(63,134,189, 0.12);
}
.dataTables_wrapper .dataTables_filter input {
    border: 1px solid var(--brand-100);
    border-radius: var(--radius-sm, 8px);
    padding: 7px 12px 7px 36px; /* espacio izq para icono lupa */
    font-family: var(--font-ui);
    font-size: 0.92rem;
    color: var(--brand-900);
    background: #fff url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%233F86BD' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='11' cy='11' r='7'/><line x1='21' y1='21' x2='16.65' y2='16.65'/></svg>") no-repeat 10px center;
    outline: none;
    min-width: 320px;
}
.dataTables_wrapper .dataTables_filter input::placeholder {
    color: var(--surface-500, #9aa3a7);
    font-style: italic;
}
.dataTables_wrapper .dataTables_filter input:focus {
    border-color: var(--brand-500);
    box-shadow: 0 0 0 3px rgba(63,134,189, 0.12);
}
/* El tema Modernize (styles.css) trae
   `.table-responsive .dataTables_wrapper .dataTables_filter input { padding: .25rem .5rem }`
   con mayor especificidad → reinicia el padding-left a 8px y la lupa (que sí
   pinta nuestra regla de arriba) queda encima del texto. Como toda tabla va
   envuelta en .table-responsive, el solape se ve en todas las páginas. Re-
   afirmamos el hueco de la lupa con la MISMA especificidad: app.css carga
   después de styles.css, así que gana por orden de fuente. */
.table-responsive .dataTables_wrapper .dataTables_filter input {
    padding-left: 36px;
}

/* Scroll horizontal cuando la tabla excede el ancho del contenedor.
   scrollX: true en DataTables genera .dataTables_scroll → su body es
   el área que scrollea. Forzamos que se vea claro con un fondo sutil
   y un borde para indicar overflow al usuario. */
.dataTables_wrapper .dataTables_scrollBody {
    overflow-x: auto !important;
    border-bottom: 1px solid var(--brand-100);
}

/* ── DataTables toolbar: filtros izq · longitud der ───────────────
   La toolbar (dom "fl") agrupa búsqueda + dropdowns de filtro a la
   izquierda y el selector de longitud a la derecha. Empujamos la
   longitud con margin-left:auto para que respire en una sola fila. */
.dt-toolbar {
    gap: 12px;
}
.dt-toolbar .dataTables_length {
    margin-left: auto;
}
.dt-toolbar .dataTables_filter {
    margin-bottom: 0;
}
.dt-toolbar .dataTables_filter,
.dt-toolbar .dt-colfilter {
    align-self: flex-end;
}

/* Dropdown de filtro por columna (appColumnFilter) */
.dt-colfilter {
    min-width: 200px;
}
.dt-colfilter .form-label {
    line-height: 1.1;
}
.dt-colfilter select {
    border: 1px solid var(--brand-100);
    border-radius: var(--radius-sm, 8px);
    color: var(--brand-900);
}
.dt-colfilter select:focus {
    border-color: var(--brand-500);
    box-shadow: 0 0 0 3px rgba(63,134,189, 0.12);
}

/* ── Responsive: control de expandir (botón "+") y fila hija ──────
   Reemplaza el scroll horizontal: las columnas de menor prioridad se
   colapsan en una fila hija expandible. Marca teal de la marca. */
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control {
    position: relative;
    padding-left: 30px;
    cursor: pointer;
}
table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr > th.dtr-control:before {
    top: 50%;
    left: 6px;
    height: 18px;
    width: 18px;
    margin-top: -9px;
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 18px;
    border: none;
    border-radius: 50%;
    box-shadow: none;
    background-color: var(--brand-600);
    color: #fff;
    font-weight: 700;
    content: "+";
}
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td.dtr-control:before,
table.dataTable.dtr-inline.collapsed > tbody > tr.parent > th.dtr-control:before {
    background-color: var(--accent-500, #C2410C);
    content: "−";
}
/* Lista clave/valor dentro de la fila hija */
table.dataTable > tbody > tr.child ul.dtr-details {
    width: 100%;
}
table.dataTable > tbody > tr.child ul.dtr-details > li {
    border-bottom: 1px solid var(--brand-50, #EAF4FB);
    padding: 6px 0;
}
table.dataTable > tbody > tr.child span.dtr-title {
    min-width: 140px;
    font-weight: 600;
    color: var(--surface-700);
}

.dataTables_wrapper .dataTables_scrollBody::-webkit-scrollbar {
    height: 10px;
}
.dataTables_wrapper .dataTables_scrollBody::-webkit-scrollbar-thumb {
    background: var(--brand-300, #6ec3a8);
    border-radius: 6px;
}
.dataTables_wrapper .dataTables_scrollBody::-webkit-scrollbar-track {
    background: var(--surface-100, #F4EEE0);
}
.dataTables_wrapper .dataTables_info {
    color: var(--surface-700);
    font-size: 0.84rem;
    padding: 8px 0 0;
}
.dataTables_wrapper .dataTables_paginate {
    padding: 8px 0 0;
    display: flex;
    justify-content: flex-end;
    gap: 4px;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 32px;
    height: 32px;
    padding: 0 8px;
    border: 1px solid var(--brand-100);
    border-radius: 6px;
    font-size: 0.84rem;
    font-family: var(--font-ui);
    color: var(--brand-900) !important;
    background: #fff;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
    text-decoration: none !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
    background: var(--brand-50);
    border-color: var(--brand-200);
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current,
.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
    background: var(--brand-600) !important;
    border-color: var(--brand-600) !important;
    color: #fff !important;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,
.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover {
    opacity: 0.4;
    cursor: default;
    background: #fff;
}
.dataTables_wrapper .dataTables_processing {
    color: var(--brand-600); /* brand-500 sobre fondo claro mide 3.08:1, no pasa AA (#870) */
    font-weight: 600;
}

/* ── Card components ──────────────────────────────────────────── */
.card-app {
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: var(--card-radius);
    box-shadow: var(--card-shadow);
    overflow: hidden;
}
.card-app .card-header-gradient {
    background: linear-gradient(135deg, var(--brand-500) 0%, var(--brand-600) 100%);
    color: #fff;
    padding: 14px 20px;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 10px;
}
.card-app .card-body { padding: 20px; }

/* ── Table components ─────────────────────────────────────────── */
.table-app {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.93rem;
}
.table-app thead th {
    background: var(--brand-50);
    color: var(--brand-900);
    text-align: left;
    padding: 10px 12px;
    font-weight: 600;
    border-bottom: 1px solid var(--brand-100);
    white-space: nowrap;
}
.table-app tbody td {
    padding: 10px 12px;
    border-bottom: 1px solid var(--surface-200);
    vertical-align: middle;
}
.table-app tbody tr:hover td { background: var(--brand-50); }
.num-col { text-align: right; white-space: nowrap; }

/* ── Badge / pill ─────────────────────────────────────────────── */
.badge-pill {
    display: inline-block;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 0.78rem;
    font-weight: 600;
    line-height: 1.4;
}
.badge-activo   { background: var(--brand-200); color: var(--brand-900); }
.badge-inactivo { background: var(--surface-200); color: var(--surface-700); }
.badge-amber    { background: #FEF3C7; color: #92400E; }
.badge-cargo    { background: var(--brand-50); color: var(--brand-600); border: 1px solid var(--brand-200); }
.badge-esp      { background: #E0E7FF; color: var(--info-400); font-size: 0.72rem; padding: 2px 7px; }

/* ── Button components ────────────────────────────────────────── */
.btn-app {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 16px;
    border: none;
    border-radius: var(--radius-sm);
    font-family: var(--font-ui);
    font-size: 0.9rem;
    font-weight: 500;
    cursor: pointer;
    text-decoration: none;
    transition: background var(--transition-fast);
}
.btn-teal         { background: var(--brand-600); color: #fff; }
.btn-teal:hover   { background: #245A84; color: #fff; }
.btn-charcoal     { background: var(--brand-900); color: #fff; }
.btn-charcoal:hover { background: var(--brand-800); color: #fff; }
.btn-outline      { background: transparent; color: var(--brand-600); border: 1px solid var(--brand-200); }
.btn-outline:hover { background: var(--brand-50); color: var(--brand-600); }
.btn-icon         { padding: 6px 10px; font-size: 0.85rem; }
.btn-accent       { background: var(--accent-500); color: #fff; }
.btn-accent:hover { background: #d97706; color: #fff; }

/* ── Utility ──────────────────────────────────────────────────── */
.text-muted-sm { color: var(--surface-700); font-size: 0.83rem; }
.no-records { padding: 40px 20px; text-align: center; color: var(--surface-700); }

/* Modernize pone --bs-light-text-emphasis igual que el fondo de .alert-light
   (#f6f9fc sobre #f6f9fc): el texto no se veía (Cobros, vista previa). */
.alert-light {
  --bs-alert-color: var(--bs-body-color);
  --bs-alert-link-color: var(--bs-body-color);
}

/* ── Avatar — Clientes ────────────────────────────────────────── */
.cliente-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--brand-50);
    color: var(--brand-600);
    font-weight: 700;
    font-size: 0.85rem;
    margin-right: 8px;
    vertical-align: middle;
    border: 1px solid var(--brand-200);
}

/* ── Avatar + specialties — Empleados ────────────────────────── */
.emp-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--brand-50);
    color: var(--brand-600);
    font-weight: 700;
    font-size: 0.88rem;
    margin-right: 8px;
    vertical-align: middle;
    border: 1px solid var(--brand-200);
    flex-shrink: 0;
}
.emp-name-cell { display: flex; align-items: center; }
.esp-pills     { display: flex; flex-wrap: wrap; gap: 4px; }
.select-all-link {
    font-size: 0.82rem;
    color: var(--brand-600); /* brand-500 sobre fondo claro mide 3.08:1, no pasa AA (#870) */
    cursor: pointer;
    text-decoration: underline;
    margin-left: 4px;
}

/* ── Color dot — Tipos de servicio ───────────────────────────── */
.color-dot {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 1px solid var(--surface-300);
    vertical-align: middle;
    margin-right: 6px;
}

/* ══════════════════════════════════════════════════════════════
   Dashboard-specific
   ══════════════════════════════════════════════════════════════ */
.dash-section { margin-bottom: 28px; }

.btn-ghost-white {
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.24);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.btn-ghost-white:hover {
    background: rgba(255, 255, 255, 0.22);
    color: #fff;
}

/* VIP Hero */
.dash-hero {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    background: linear-gradient(135deg, #3F86BD 0%, #2F6E9E 100%);
    border-radius: var(--card-radius, 14px);
    color: #fff;
    padding: 40px 48px;
    margin-bottom: 28px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
    min-height: 170px;
}
.dash-hero::before,
.dash-hero::after {
    content: "";
    position: absolute;
    border-radius: 50%;
    filter: blur(70px);
    opacity: 0.45;
    pointer-events: none;
    z-index: 0;
}
.dash-hero::before {
    width: 420px; height: 420px;
    background: #6FB1DE;
    top: -150px; right: -80px;
    animation: dhBlob1 18s ease-in-out infinite alternate;
}
.dash-hero::after {
    width: 300px; height: 300px;
    background: #A9D6F5;
    bottom: -110px; left: -70px;
    animation: dhBlob2 22s ease-in-out infinite alternate;
}
@keyframes dhBlob1 {
    from { transform: translate(0, 0) scale(1); }
    to   { transform: translate(-40px, 28px) scale(1.14); }
}
@keyframes dhBlob2 {
    from { transform: translate(0, 0) scale(1); }
    to   { transform: translate(50px, -22px) scale(1.1); }
}
.dash-hero-grid {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(rgba(255,255,255,0.04) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px);
    background-size: 48px 48px;
    mask-image: radial-gradient(ellipse 90% 80% at 50% 50%, black 30%, transparent 85%);
    -webkit-mask-image: radial-gradient(ellipse 90% 80% at 50% 50%, black 30%, transparent 85%);
    z-index: 1;
    pointer-events: none;
}
.dash-hero-noise {
    position: absolute;
    inset: 0;
    background-image: var(--noise);
    opacity: 0.5;
    mix-blend-mode: overlay;
    pointer-events: none;
    z-index: 1;
}
.dash-hero-watermark {
    position: absolute;
    right: 200px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 8.5rem;
    opacity: 0.07;
    color: #fff;
    z-index: 1;
    pointer-events: none;
}
.sparkle {
    position: absolute;
    border-radius: 50%;
    background: #F2E8D3;
    pointer-events: none;
    z-index: 2;
    animation: sparklePulse 2.5s ease-in-out infinite alternate;
}
@keyframes sparklePulse {
    from { opacity: 0.55; transform: scale(1); }
    to   { opacity: 1;    transform: scale(1.5); }
}
.dash-hero-content {
    position: relative;
    z-index: 3;
}
.hero-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 0.76rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: rgba(255,255,255,0.85);
    background: rgba(255,255,255,0.1);
    border: 1px solid rgba(255,255,255,0.2);
    padding: 5px 12px;
    border-radius: 999px;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    margin-bottom: 16px;
}
.hero-eyebrow .dot {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: #F2E8D3;
    box-shadow: 0 0 0 3px rgba(242,232,211,0.35);
}
.dash-hero-content h1 {
    font-family: 'Source Serif 4', Georgia, serif; /* VIP hero — display font allowed here */
    font-weight: 600;
    font-size: clamp(1.65rem, 2.5vw, 2.1rem);
    line-height: 1.1;
    margin: 0 0 10px;
    color: #fff;
}
.hero-sub {
    font-size: 0.9rem;
    color: rgba(255,255,255,0.8);
    margin: 0;
}
.dash-hero-actions {
    position: relative;
    z-index: 3;
    flex-shrink: 0;
}

/* KPI Star card */
.kpi-star {
    background: linear-gradient(135deg, var(--brand-500) 0%, var(--brand-600) 100%);
    border-radius: var(--card-radius, 14px);
    box-shadow: 0 8px 32px rgba(47,110,158,0.25);
    color: #fff;
    padding: 28px 24px;
    position: relative;
    overflow: hidden;
    height: 100%;
    min-height: 200px;
    display: flex;
    flex-direction: column;
    justify-content: center;
}
.kpi-star .kpi-watermark {
    position: absolute;
    right: 16px;
    bottom: 12px;
    font-size: 4.5rem;
    opacity: 0.12;
    color: #fff;
    pointer-events: none;
}
.kpi-star .kpi-label {
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: rgba(255,255,255,0.75);
    margin-bottom: 10px;
}
.kpi-star .kpi-value {
    font-size: 3.4rem;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    font-family: var(--font-ui);
}
.kpi-star .kpi-sub {
    font-size: 0.85rem;
    color: rgba(255,255,255,0.68);
    margin-top: 8px;
}
.kpi-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 18px;
    font-size: 0.74rem;
    font-weight: 500;
    background: rgba(255,255,255,0.15);
    border: 1px solid rgba(255,255,255,0.22);
    color: #fff;
    padding: 4px 10px;
    border-radius: 999px;
}

/* ── btn-secondary neutral override (Modernize sets --bs-secondary to cyan) ── */
.btn-secondary {
    --bs-btn-color: #fff;
    --bs-btn-bg: #5B6B7A;
    --bs-btn-border-color: #5B6B7A;
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #2A4A6B;
    --bs-btn-hover-border-color: #2A4A6B;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #1C3650;
    --bs-btn-active-border-color: #1C3650;
    background-color: #5B6B7A !important;
    border-color: #5B6B7A !important;
    color: #fff !important;
}
.btn-secondary:hover, .btn-secondary:focus {
    background-color: #2A4A6B !important;
    border-color: #2A4A6B !important;
}
.btn-outline-secondary {
    --bs-btn-color: #5B6B7A;
    --bs-btn-border-color: #aaa89e;
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #5B6B7A;
    --bs-btn-hover-border-color: #5B6B7A;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #2A4A6B;
    --bs-btn-active-border-color: #2A4A6B;
    background-color: transparent !important;
    border-color: #aaa89e !important;
    color: #5B6B7A !important;
}
.btn-outline-secondary:hover, .btn-outline-secondary:focus {
    background-color: #5B6B7A !important;
    border-color: #5B6B7A !important;
    color: #fff !important;
}

/* ── DataTables Bootstrap5 pagination brand override ─────────── */
/* dataTables.net-bs5 v1: .dataTables_paginate > ul.pagination > li.page-item */
/* dataTables.net v2:     .dt-paging > ul > li.page-item */
.dataTables_paginate .page-item.active .page-link,
.dt-paging .page-item.active .page-link,
.dt-paging-button.active .page-link {
    background-color: var(--brand-600) !important;
    border-color: var(--brand-600) !important;
    color: #fff !important;
}
.dataTables_paginate .page-link,
.dt-paging .page-link { color: var(--brand-900); }
.dataTables_paginate .page-link:hover,
.dt-paging .page-link:hover { color: var(--brand-600); background: var(--brand-50); border-color: var(--brand-200); }
.dataTables_paginate .page-link:focus,
.dt-paging .page-link:focus { box-shadow: 0 0 0 3px rgba(63,134,189,.18); }

/* ── FullCalendar brand buttons ───────────────────────────────── */
.fc .fc-button-primary {
    background: var(--brand-900);
    border-color: var(--brand-900);
    color: #fff;
    font-size: .82rem;
    font-weight: 500;
    padding: 5px 14px;
}
.fc .fc-button-primary:hover {
    background: var(--brand-800);
    border-color: var(--brand-800);
}
.fc .fc-button-primary:not(:disabled).fc-button-active,
.fc .fc-button-primary:not(:disabled):active {
    background: var(--brand-600) !important;
    border-color: var(--brand-600) !important;
}
.fc .fc-today-button {
    background: var(--brand-600) !important;
    border-color: var(--brand-600) !important;
}
.fc .fc-today-button:disabled {
    background: var(--brand-600) !important;
    border-color: var(--brand-600) !important;
    opacity: .65;
}
.fc .fc-button:focus { box-shadow: 0 0 0 3px rgba(63,134,189,.25) !important; }
.fc .fc-day-today { background: rgba(63,134,189,.07) !important; }

/* ── Responsive ───────────────────────────────────────────────── */
@media (max-width: 768px) {
    .dash-hero { padding: 28px 24px; }
    .dash-hero-watermark { display: none; }
    .dash-hero-actions { width: 100%; }
    .dataTables_wrapper .dataTables_filter { justify-content: flex-start; }
    .dataTables_wrapper .dataTables_paginate { justify-content: center; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   Banners dentro del topbar (issue #844)
   Antes, los banners de facturación/impersonación vivían FUERA de <header
   class="topbar"> (que es position:fixed, o sea fuera del flujo normal) y
   quedaban superpuestos sobre el avatar, "Ir al POS", "Instalar app" y el
   toggle del sidebar. Ahora viven DENTRO del topbar como primer hijo, así el
   topbar crece hacia abajo y nunca tapa nada. --bs-banner-h se actualiza en
   runtime (partials/topbar.php) con el alto real del wrapper de banners, y
   el contenido de la página se desplaza esa misma cantidad.
   ══════════════════════════════════════════════════════════════════════════════ */
:root { --bs-banner-h: 0px; }

/* Alto TOTAL del topbar (navbar + banners), medido en runtime por el script
   de partials/topbar.php. El valor estático de 52px es el alto real del navbar
   SIN banner en ESTA app: `density-compact` no es opcional — está escrito a
   mano en el <body> de TODAS las vistas que cargan el topbar (sin condicional
   PHP ni toggle JS), y baja el navbar de los 70px de Modernize a 52px
   (body.density-compact .topbar .navbar{min-height:52px}, más abajo en este
   mismo archivo). Sirve de red para el caso en que el script no llegue a
   correr (JS apagado o error de script): las páginas que dependen de
   --bs-topbar-h (ej. .ws-header-card en conciliacion_bancaria.php) quedan
   pegadas al navbar en vez de 18px más abajo. */
:root { --bs-topbar-h: 52px; }

/* Full-bleed: el topbar tiene padding:0 25px (styles.css) parejo en todos los
   anchos (sin media query que lo cambie) — verificado a 1440px y 375px con el
   navegador. Los banners deben ocupar el ancho completo del topbar, así que
   se cancela ese padding con márgenes negativos iguales. */
.bs-top-banners { margin-left: -25px; margin-right: -25px; }

/* Desplaza el contenido de la página el alto real de los banners. Se suma a
   los mismos valores base que ya usaba .body-wrapper (styles.css: 70+30;
   modo compacto más abajo: 72). */
.body-wrapper { padding-top: calc(70px + 30px + var(--bs-banner-h)); }

/* Sidebar logo: full horizontal when expanded, favicon when mini */
body[data-sidebartype="mini-sidebar"] .logo-full { display: none !important; }
body[data-sidebartype="mini-sidebar"] .logo-mini { display: inline-block !important; }

/* ══════════════════════════════════════════════════════════════════════════════
   density-compact layout layer
   Scoped to body.density-compact — added permanently to every view <body> tag.
   Recovers ~250px vertical space per listing page vs Modernize defaults.
   ══════════════════════════════════════════════════════════════════════════════ */

/* Base font: 15px → 14px */
body.density-compact { font-size: 14px; }

/* Topbar: 70px → 52px. body-wrapper must match: 52 + 20 = 72px */
body.density-compact .topbar { min-height: 52px !important; }
body.density-compact .topbar .navbar { min-height: 52px !important; }
body.density-compact .body-wrapper { padding-top: calc(72px + var(--bs-banner-h)) !important; }

/* Migas de pan: viven en .page-wrapper ANTES de .body-wrapper y .topbar es
   position:fixed, así que sin este bloque quedan DEBAJO de la barra (así salió
   v0.1.118 a prod el 28-ago). El nav asume el desplazamiento del header y
   .body-wrapper cede el suyo cuando hay nav (selector de hermanos).

   Por qué var(--bs-topbar-h) y no un calc(52px/70px + banner) a mano: en
   pantallas angostas la navbar del topbar puede crecer MÁS ALTA que el
   min-height de density-compact (52px) porque su contenido se envuelve —
   medido en iphone-14-pro: navbar real 70px con banner, no 52px, así que un
   calc() fijo dejaba la miga 18px corta y otra vez tapada. --bs-topbar-h ya
   es el alto TOTAL real del topbar (navbar + banners), medido en runtime por
   el script de partials/topbar.php — ni hay que sumarle --bs-banner-h
   aparte, ya está incluido. El fallback estático de 52px en :root (arriba,
   #844) es el mismo que ya usan otras páginas (ej. conciliacion_bancaria.php)
   para el caso sin JS.
   Nota de especificidad en el override de .body-wrapper: "body.density-compact
   .body-wrapper" de arriba pesa (0,2,1) — el elemento "body" cuenta como tag
   además de las dos clases — y le gana a un ".app-breadcrumb ~ .body-wrapper"
   simple (0,2,0) aunque ambos lleven !important (a y b empatan, decide c).
   Por eso el override de abajo se repite calificado con "body.density-compact"
   para igualar esa cuenta. */
/* PADDING, no margin: el nav es el primer hijo en flujo de .page-wrapper (el
   sidebar y el topbar son position:fixed) y .page-wrapper/#main-wrapper no
   crean contexto de formato de bloque, así que un margin-top ahí se
   "escapa" hasta <body> y alarga el documento 52px — barra de scroll
   fantasma en toda página con miga (ronda 1 de review del hotfix). */
.page-wrapper > .app-breadcrumb { padding: calc(var(--bs-topbar-h) + 8px) 26px 4px; }
.app-breadcrumb ~ .body-wrapper { padding-top: 8px !important; }
body.density-compact .app-breadcrumb ~ .body-wrapper { padding-top: 8px !important; }
/* 7 vistas (cancelaciones, compras, cxc_cobros, cxp_suplidores,
   movimientos_consumo, orden_compra, reposicion) incluyen el topbar DENTRO de
   .body-wrapper: ahí el padding de .body-wrapper ya libra el header, así que
   el nav no necesita offset propio. */
.body-wrapper > .app-breadcrumb { padding: 0 26px 8px; }

/* Sidebar: 270px → 250px */
@media (min-width: 768px) {
    body.density-compact .left-sidebar { width: 250px !important; }
    body.density-compact .page-wrapper { margin-left: 250px !important; }
    body.density-compact .topbar { width: calc(100% - 250px) !important; }
    body.density-compact .left-sidebar:hover { width: 250px !important; }
    /* Mini-sidebar collapse: keep Modernize's 87px */
    body.density-compact[data-sidebartype=mini-sidebar] .left-sidebar { width: 87px !important; }
    body.density-compact[data-sidebartype=mini-sidebar] .page-wrapper { margin-left: 87px !important; }
    body.density-compact[data-sidebartype=mini-sidebar] .topbar { width: calc(100% - 87px) !important; }
}

/* Sidebar visible (sticky desktop) a partir de 992px — override del
   `left:-270px` de Modernize gateado a <1299.98px, que esconde el sidebar
   en laptops estándar (1280/1366/1440). Pareado con el threshold de JS
   que mantiene data-sidebartype=full desde 992 hacia arriba. */
@media (min-width: 992px) {
    body.density-compact .left-sidebar { left: 0 !important; }
}

/* Container horizontal padding: 24px → 18px */
body.density-compact .body-wrapper > .container-fluid,
body.density-compact .body-wrapper > .container,
body.density-compact .body-wrapper > .container-lg,
body.density-compact .body-wrapper > .container-xl,
body.density-compact .body-wrapper > .container-xxl { padding-left: 18px !important; padding-right: 18px !important; }

/* Page hero: 22px 26px → 10px 16px, margin 24px → 12px */
body.density-compact .page-hero { padding: 10px 16px !important; margin-bottom: 12px !important; }
body.density-compact .page-hero-icon { width: 36px !important; height: 36px !important; font-size: 1.05rem !important; border-radius: 10px !important; }
body.density-compact .page-hero-main { gap: 10px !important; }
body.density-compact .page-hero-title { font-size: 1.15rem !important; margin-bottom: 0 !important; }

/* Cards */
body.density-compact .card-app .card-body,
body.density-compact .card .card-body { padding: 14px !important; }
body.density-compact .card-app .card-header-gradient,
body.density-compact .card .card-header { padding: 10px 14px !important; }

/* Table cell padding */
body.density-compact .table > :not(caption) > * > * { padding: 8px 10px !important; }

/* Flechas de ordenar: dentro del padding que YA existe, no encima del título.
   DataTables las dibuja absolutas a `right:10px`, o sea justo donde TERMINA el
   texto — cuenta con reservarle 26px de padding a la celda. La regla de
   densidad de arriba le pisa ese padding con !important y lo deja en 10px, así
   que las flechas caían sobre las últimas letras. En TODA tabla de la app.

   Devolverle los 26px fue el primer intento y ROMPIÓ otra cosa: son ~26px más
   por columna ordenable, y en un iPad el plugin Responsive paga esa cuenta
   escondiendo una columna — en clientes.php desaparecían los botones de acción
   de la fila (lo cazó el carril de navegador de CI, no yo).

   Esto no pide ni un píxel extra: mueve la flecha DENTRO de los 10px de padding
   que la celda ya tiene (2px..10px desde el borde) y la achica un punto. El
   texto sigue terminando en 10px, así que deja de haber solape y el ancho de la
   tabla no cambia — Responsive se comporta igual que antes. */
body.density-compact table.dataTable > thead > tr > th.sorting:before,
body.density-compact table.dataTable > thead > tr > th.sorting:after,
body.density-compact table.dataTable > thead > tr > th.sorting_asc:before,
body.density-compact table.dataTable > thead > tr > th.sorting_asc:after,
body.density-compact table.dataTable > thead > tr > th.sorting_desc:before,
body.density-compact table.dataTable > thead > tr > th.sorting_desc:after,
body.density-compact table.dataTable > thead > tr > th.sorting_asc_disabled:before,
body.density-compact table.dataTable > thead > tr > th.sorting_desc_disabled:after {
    right: 2px;
    font-size: .7em;
}

/* Flechas de ordenar: solo donde dicen algo.
   DataTables las pinta en las NUEVE cabeceras a la vez, permanentes, justo
   sobre el borde de cada columna — donde además está la manija de
   redimensionar. El resultado es un borde lleno de ruido en el que las flechas
   parecen controles de ancho y ninguna dice nada, porque están todas.

   Regla: la columna ORDENADA enseña su dirección siempre (es información: por
   dónde está ordenada la tabla). Las demás solo al pasarles por encima (es una
   invitación, y solo hace falta cuando vas a usarla). */
body.density-compact table.dataTable > thead > tr > th.sorting:before,
body.density-compact table.dataTable > thead > tr > th.sorting:after {
    opacity: 0;
    transition: opacity .12s ease-in-out;
}
body.density-compact table.dataTable > thead > tr > th.sorting:hover:before,
body.density-compact table.dataTable > thead > tr > th.sorting:hover:after {
    opacity: .3;
}
/* La ordenada: su flecha activa bien visible, la contraria apagada del todo
   (DataTables deja la contraria en .125 y se lee como si hubiera dos). */
body.density-compact table.dataTable > thead > tr > th.sorting_asc:after,
body.density-compact table.dataTable > thead > tr > th.sorting_desc:before {
    opacity: 0;
}
body.density-compact table.dataTable > thead > tr > th.sorting_asc:before,
body.density-compact table.dataTable > thead > tr > th.sorting_desc:after {
    opacity: .75;
}

/* El "+" que abre la fila cuando Responsive escondió una columna.
   Venía como un círculo verde macizo en CADA fila: un acento de marca gritando
   en la columna del id, compitiendo con el botón que sí importa. Se baja a un
   signo discreto — sigue siendo tocable (el área no cambia), deja de ser lo
   primero que ve el ojo. */
body.density-compact table.dataTable.dtr-inline.collapsed > tbody > tr > td.dtr-control:before {
    background-color: transparent !important;
    border: 1px solid var(--bs-border-color, #DEE2E6) !important;
    color: #6B7280 !important;
    box-shadow: none !important;
}
body.density-compact table.dataTable.dtr-inline.collapsed > tbody > tr.parent > td.dtr-control:before {
    background-color: #E5E7EB !important;
    border-color: #D1D5DB !important;
    color: #374151 !important;
}

/* Manija para redimensionar columnas (app-datatables.js).
   Va pegada al borde derecho de la cabecera y se pinta solo al pasarle por
   encima: en reposo la tabla se ve exactamente igual que antes. Se monta
   DENTRO del hueco de 26px que la regla de arriba le devuelve a las flechas,
   así que no le roba espacio al título. */
th .bs-col-resizer,
td .bs-col-resizer {
    position: absolute;
    top: 0;
    right: -3px;
    width: 7px;
    height: 100%;
    cursor: col-resize;
    z-index: 2;
    /* touch-action evita que el navegador se quede con el gesto y lo convierta
       en scroll antes de que lleguen los pointermove. */
    touch-action: none;
    user-select: none;
}
th .bs-col-resizer::after {
    content: "";
    position: absolute;
    top: 20%;
    left: 3px;
    width: 1px;
    height: 60%;
    background: transparent;
    transition: background .12s ease-in-out;
}
th:hover .bs-col-resizer::after { background: var(--bs-border-color, #DEE2E6); }
th .bs-col-resizer:hover::after { background: #2F6E9E; width: 2px; }

/* Mientras se arrastra: el cursor no cambia según lo que haya debajo y no se
   selecciona texto de la tabla por accidente. */
body.bs-col-resizing { cursor: col-resize !important; user-select: none !important; }
body.bs-col-resizing * { cursor: col-resize !important; }

/* Form labels: tighter margin */
body.density-compact .form-label { margin-bottom: .2rem !important; }

/* Gap reduction */
body.density-compact .gap-4 { gap: 1rem !important; }

/* ── Empty state ──────────────────────────────────────────────────
   Componente reutilizable para tablas/listas sin registros: icono
   tenue + una línea de guía + un CTA real (no solo texto). Centrado.
   Uso: <div class="empty-state"><span class="empty-state-icon">…</span>
        <p class="empty-state-text">…</p><a class="btn …">…</a></div> */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 2.5rem 1rem;
    gap: .85rem;
}
.empty-state-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--brand-50, #EAF4FB);
    color: var(--brand-500, #3F86BD);
    font-size: 1.6rem;
}
/* Ilustración suelta de estado vacío: un <i class="ti …"> grande, sin la
   pastilla redonda de .empty-state-icon (esa espera un <span> envolvente
   dentro de .empty-state; con d-block encima se le rompe el inline-flex).
   NO usar .fs-1 para esto: en este build Modernize la redefine a .625rem
   (10px), así que la "ilustración" salía del tamaño de un punto (#898).

   El !important no es capricho: TODAS las .fs-* de Modernize lo llevan, así
   que sin él un `class="ti ti-inbox empty-state-glyph fs-4"` se pintaría al
   tamaño del texto y ninguna prueba se quejaría (el check 4 solo prohíbe
   fs-1/fs-2/fs-3, que son las que encogen). */
.empty-state-glyph {
    font-size: 2.5rem !important;
    line-height: 1;
}
.empty-state-text {
    margin: 0;
    max-width: 36rem;
    color: var(--bs-secondary-color);
}
.empty-state-hint {
    margin: 0;
    font-size: .85rem;
    color: var(--bs-warning-text-emphasis, #997404);
}
/* ══════════════════════════════════════════════════════════════
   Modal — scrollable body with scroll-shadow UX indicator
   ══════════════════════════════════════════════════════════════ */
.modal-body {
    max-height: calc(100dvh - 220px);
    overflow-y: auto;
    overscroll-behavior: contain;
    /* scroll shadow: appears only when content overflows top/bottom */
    background:
        linear-gradient(var(--bs-modal-bg, #fff) 30%, transparent)         top    / 100% 24px no-repeat,
        linear-gradient(transparent, var(--bs-modal-bg, #fff) 70%)         bottom / 100% 24px no-repeat,
        radial-gradient(farthest-side at 50% 0,   rgba(0,0,0,.10), transparent) top    / 100%  8px no-repeat,
        radial-gradient(farthest-side at 50% 100%, rgba(0,0,0,.10), transparent) bottom / 100%  8px no-repeat;
    background-attachment: local, local, scroll, scroll;
}

/* ══════════════════════════════════════════════════════════════
   Nav tabs — brand teal active state (overrides Bootstrap indigo).
   Texto en brand-600 (no brand-500) para contraste AA sobre fondo
   claro; tinte brand-50 + subrayado brand-500 para afianzar el
   estado activo. Variante propia para dark mode (teal claro).
   ══════════════════════════════════════════════════════════════ */
.nav-tabs .nav-link {
    color: var(--bs-secondary-color);
}
.nav-tabs .nav-link.active,
.nav-tabs .nav-item.show .nav-link {
    color: var(--brand-600, #2F6E9E);
    background-color: var(--brand-50, #E6F2FB);
    border-color: var(--bs-border-color) var(--bs-border-color) var(--brand-500, #3F86BD);
    border-bottom-width: 2px;
    font-weight: 600;
}
.nav-tabs .nav-link:hover,
.nav-tabs .nav-link:focus {
    color: var(--brand-600, #2F6E9E);
    border-bottom-color: var(--brand-200, #A9D6F5);
}

/* ══════════════════════════════════════════════════════════════
   KPI mini label — Title Case (no uppercase per brand rules)
   ══════════════════════════════════════════════════════════════ */
.kpi-mini .kpi-mini-label {
    text-transform: none;
}

/* ══════════════════════════════════════════════════════════════
   Modal confinado al área de contenido (derecha del sidebar).
   Bootstrap centra el modal sobre el viewport completo, lo que
   hace que el borde izquierdo del modal quede debajo del sidebar
   y la imagen se vea "cortada". Desplazamos backdrop y modal
   para que empiecen donde termina el sidebar (270 px).
   En mini-sidebar (87 px) y móvil, se usa el viewport completo.
   ══════════════════════════════════════════════════════════════ */
@media (min-width: 992px) {
    body:not([data-sidebartype="mini-sidebar"]) .modal-backdrop,
    body:not([data-sidebartype="mini-sidebar"]) .modal {
        left: 270px;
        width: calc(100% - 270px);
    }
}

/* ══════════════════════════════════════════════════════════════
   Segundo y tercer nivel del sidebar.
   sidebarmenu.js usa .in (Modernize) — Modernize styles.css ya
   tiene .collapse.in { display:block }. Solo necesitamos
   indentado y guía visual por nivel.
   ══════════════════════════════════════════════════════════════ */
.second-level,
.third-level {
    border-left: 2px solid rgba(63,134,189,.22);
    margin-left: 1.25rem;
    padding-left: .25rem;
}
.third-level {
    margin-left: 1rem;
}
.second-level .sidebar-link {
    font-size: .82rem !important;
    padding-top:    .35rem !important;
    padding-bottom: .35rem !important;
}
.third-level .sidebar-link {
    font-size: .78rem !important;
    padding-top:    .3rem !important;
    padding-bottom: .3rem !important;
    opacity: .92;
}
/* Un nombre largo ("Reporte de Recibos de Ingreso") baja a una segunda línea
   en vez de cortarse contra el borde del menú. */
.second-level .sidebar-link .hide-menu,
.third-level .sidebar-link .hide-menu {
    white-space: normal;
    line-height: 1.25;
}

/* ══════════════════════════════════════════════════════════════
   Paquete inicial de catálogo — tarjetas del modal (#864 / #886)
   Antes: <div class="form-check border rounded p-2 h-100"> ponía el
   borde en el MISMO elemento que .form-check (padding-left:1.5em),
   y .form-check-input trae margin-left:-1.5em — el checkbox quedaba
   mordiendo el borde de la tarjeta. Aquí la tarjeta es un <label>
   propio (toda el área es clicable) y el checkbox vive DENTRO del
   padding, sin depender de las reglas de .form-check.
   ══════════════════════════════════════════════════════════════ */
.starter-pack-card {
    display: flex;
    align-items: flex-start;
    gap: .65rem;
    height: 100%;
    margin: 0;
    padding: .65rem .85rem;
    border: 1px solid var(--card-border, var(--brand-100));
    border-radius: var(--radius-md);
    background: var(--card-bg);
    cursor: pointer;
    transition: border-color var(--transition-fast), background var(--transition-fast), box-shadow var(--transition-fast);
}
.starter-pack-card:hover {
    border-color: var(--brand-400);
}
.starter-pack-card.is-selected {
    border-color: var(--brand-500);
    background: var(--brand-50);
    box-shadow: 0 0 0 1px var(--brand-500) inset;
}
/* #886 round 2: el hit target es la tarjeta, no el checkbox — sin esto un
   usuario de teclado no ve dónde está el foco al tabular por la lista. */
.starter-pack-card:has(.starter-pack-card-check:focus-visible) {
    outline: 2px solid var(--brand-500);
    outline-offset: 2px;
}
.starter-pack-card-check {
    flex: 0 0 auto;
    margin: .2rem 0 0 !important;
}
.starter-pack-card-body {
    flex: 1 1 auto;
    min-width: 0;
}
.starter-pack-card-title {
    display: block;
    color: var(--surface-900);
    font-weight: 600;
}
.starter-pack-card-meta {
    color: var(--surface-900);
    opacity: .85;
}
/* #886 round 2 (WCAG AA): esta regla vive DENTRO de .starter-pack-card-meta
   (opacity:.85) — un opacity propio aquí MULTIPLICA (.85 × X), no reemplaza.
   Con opacity:.8 el alfa efectivo bajaba a .68 → 3.97:1 sobre la tarjeta y
   3.73:1 sobre --brand-50, ambos por debajo de 4.5:1 en texto de 12.25px.
   NO reintroducir un opacity aquí — usar solo el color token (--surface-800
   solo ya da 9.77:1, y sigue leyendo más claro que el título por el color).
*/
.starter-pack-card-label {
    color: var(--surface-800);
    font-weight: 600;
}

/* ══════════════════════════════════════════════════════════════
   Tap targets táctiles — 44×44 CSS px, Apple HIG (issue #906)
   ══════════════════════════════════════════════════════════════
   Todo el bloque vive detrás de @media (any-pointer: coarse):
   "coarse" = hay ALGÚN puntero grueso (un dedo), en vez de solo
   mouse o trackpad ("fine"). Confirmado en WebKit real
   (Playwright, iPhone 14 Pro + iPad Pro 11): any-pointer:coarse
   sí matchea ahí — no es una suposición.

   Se eligió any-pointer y NO pointer a propósito: en un iPad con
   teclado y trackpad el puntero PRIMARIO puede reportarse "fine",
   y ese es justo el equipo de trabajo que hay que cubrir. El precio
   de esa elección, decidido por el lead: una laptop con pantalla
   táctil (Windows, Chromebook) también entra al bloque y muestra
   los controles grandes aunque se maneje con mouse. Es más seguro
   equivocarse hacia el lado del dedo.

   OJO — un comentario anterior decía acá que el escritorio "no
   puede regresionar por este cambio". Era falso, y la segunda
   revisión lo marcó: un escritorio SIN pantalla táctil no entra
   (que es el caso común, y ahí sí queda pixel-idéntico), pero uno
   táctil sí. Si algún día molesta, el cambio es a pointer:coarse
   — y se pierde el iPad con trackpad, que es peor.

   --tap-min vive en un solo lugar; todo lo demás usa
   var(--tap-min) en vez de repetir "44px".

   min-height NO centra el texto de un .btn: Bootstrap lo deja
   display:inline-block, así que el label se queda pegado arriba
   con el padding viejo. Por eso, junto con min-height, agregamos
   display:inline-flex + align-items:center + justify-content:center
   en los controles que subimos de tamaño.

   min-width se agrega en botones y .page-link porque un botón de
   texto ya mide más de 44px de ancho — ahí no cambia nada. El
   único efecto real es en botones ícono-solo (lápiz, papelera...),
   que es justo el caso que hay que arreglar.
   ══════════════════════════════════════════════════════════════ */
@media (any-pointer: coarse) {
    :root { --tap-min: 44px; }

    /* ── Botones Bootstrap (.btn genérico, y .btn-sm que lo hereda) ──
       #906: .btn medía 37px de alto; .btn-sm, 28.4px. Esta regla
       sube ambos al piso de 44. Los dos casos que negocio pidió
       dejar chicos (selector "mostrar N" y CSV/Imprimir de
       DataTables) se restauran más abajo con carve-outs explícitos,
       para que la barra de herramientas no se coma la pantalla del
       celular. */
    .btn {
        min-height: var(--tap-min);
        min-width: var(--tap-min);
        display: inline-flex;
        align-items: center;
        justify-content: center;
        /* El gap repara un daño que causa el propio inline-flex, no es
           decorativo. 106 botones de la app se escriben así:
               <button class="btn"><i class="ti ti-x"></i> Rechazar</button>
           con un espacio normal entre el ícono y el texto. En flujo
           inline ese espacio se pinta; en un contenedor flex el texto
           suelto se envuelve en un item anónimo y el blanco ENTRE items
           se descarta, así que en táctil quedaba "✕Rechazar" pegado
           (admin_voucher_review.php:90-91, compras.php:71-72, y ~102
           más). Es el MISMO defecto que ya había aparecido en los labels
           de checkbox — se documentó allá y no se aplicó la lección acá;
           lo cazó la segunda revisión.
           0.35em imita el ancho de un espacio. Va en em y no en px ni rem
           a propósito: un espacio de verdad escala con el tamaño de letra,
           y acá conviven .btn-sm y los chips del POS con font-size .76rem.
           En un botón de puro texto (un solo item) no hace nada.

           Dos casos que NO se comportan igual, verificados en styles.css:
           · si el ícono trae me-1, el margen y el gap se suman (~8px en
             #btn-agregar-pago-mixto de pos.php): más aire, no roto;
           · si el botón trae la utilidad gap-1, esta regla no hace NADA
             — styles.css la define como "gap:.25rem!important", y el
             !important gana sin importar el orden de carga. Esos botones
             (partials/topbar.php:65 y :76, "Instalar app" e "Ir al POS")
             ya traían su espacio, así que no hay nada que reparar ahí.
             Una versión anterior de este comentario decía que también
             "sumaba ~4px" en ese caso; era falso y lo marcó la tercera
             revisión. */
        gap: 0.35em;
    }

    /* Inputs y selects: a diferencia de un .btn (inline-block), el
       navegador ya centra verticalmente el texto de un <input>/
       <select> aunque le subamos el alto — no hace falta flex acá. */
    .form-control,
    .form-select {
        min-height: var(--tap-min);
    }

    /* ── Select2 (ej. #sel-cliente en pos.php) ─────────────────────
       #906 ronda 3, defecto #3: Select2 reemplaza el <select> por un
       <span>, así que el selector genérico de botones/inputs de arriba
       nunca lo tocó. Medido en pos.php: 327×40 — bajo el piso, y en la
       pantalla de dinero.

       select2.min.css trae `.select2-selection--single{height:28px}`
       (una sola clase). select2-app.css ya lo sube con
       `.select2-container--default .select2-selection--single
       {min-height:38px}` (dos clases, y carga DESPUÉS de select2.min.css)
       — por eso hoy mide 38+2px de borde=40, no 28. height y min-height
       son propiedades distintas: no hace falta ganar la cascada, el
       navegador ya usa el mayor de los dos (por eso min-height:38 le
       gana a height:28 sin !important). Mismo truco acá: basta con
       min-height:44 para que vuelva a ganar, sea cual sea la
       especificidad del selector.

       .select2-selection__rendered trae su propio line-height (36px en
       select2-app.css, pensado para la caja de 38px) — si no lo subimos
       junto con la caja, el texto se queda pegado arriba en vez de
       centrado en los 44px nuevos. Lo mismo para el ícono de la flecha
       (.select2-selection__arrow), que select2-app.css ya calcula como
       "alto de caja − 2px de borde" (36 = 38−2); seguimos esa misma
       fórmula con --tap-min para que la flecha quede centrada. */
    .select2-container--default .select2-selection--single,
    .select2-container--default .select2-selection--multiple {
        min-height: var(--tap-min);
    }
    .select2-container--default .select2-selection--single .select2-selection__rendered {
        line-height: calc(var(--tap-min) - 2px);
    }
    .select2-container--default .select2-selection--single .select2-selection__arrow {
        height: calc(var(--tap-min) - 2px);
    }

    /* Carve-out #1 — selector "mostrar N" de DataTables
       (.dataTables_length select, ver dataTables.bootstrap5.mjs:
       className "form-select form-select-sm"). A propósito se queda
       chico (~28px): en el celular no vale la pena gastar ancho de
       toolbar en un control que casi nadie toca. Sigue pasando el
       piso de 24×24 de WCAG 2.2 AA. ".dataTables_length select" pesa
       más que ".form-select" (2 clases + tag contra 1 clase), así que
       gana sin necesitar !important. */
    .dataTables_length select {
        min-height: 0;
    }

    /* Carve-out #2 — botones CSV/Imprimir de DataTables (extensión
       Buttons; ver buildButton() en app-datatables.js — className
       "btn btn-sm btn-outline-secondary" + la clase que agrega el
       plugin, "buttons-csv"/"buttons-print", dentro del contenedor
       ".dt-buttons"). Mismo motivo que el carve-out de arriba: se
       quedan chicos por decisión de negocio, y siguen pasando 24×24.

       #906 ronda 3, defecto #4: antes el selector también incluía
       ".dt-buttons .btn" — eso agarra CUALQUIER botón que algún día
       caiga dentro de ".dt-buttons", no solo CSV/Imprimir, que es lo
       único que negocio decidió dejar chico. Se borra ese selector:
       solo quedan las dos clases específicas del plugin. Un botón de
       toolbar nuevo que se agregue a futuro no lleva "buttons-csv" ni
       "buttons-print", así que por defecto hereda el piso de 44px de
       ".btn" de arriba en vez de quedarse chico en silencio. */
    .buttons-csv,
    .buttons-print {
        min-height: 0;
        min-width: 0;
    }

    /* ── Paginación de DataTables ───────────────────────────────── */
    .page-link {
        min-height: var(--tap-min);
        min-width: var(--tap-min);
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    /* Con .page-link ahora en 44×44 la fila de páginas puede
       desbordar en pantallas angostas si no puede quebrar línea.
       tests/e2e-browser/no-overflow.spec.js vigila justo eso. */
    .pagination {
        flex-wrap: wrap;
    }
    /* #906 ronda 3, defecto #1 (el peor): el CHIP (el <li>, que en
       DataTables lleva las clases "paginate_button page-item" juntas —
       ver dataTables.bootstrap5.mjs) sigue fijado a height:32px por la
       regla de escritorio en ~línea 799 (NO se toca — ese CSS es fuera
       de este bloque y decide el look de escritorio). Mientras tanto
       .page-link (el <a> de adentro) ya sube a 44×44 arriba en este
       mismo bloque. Resultado medido en clientes.php: li 70×32 con el
       <a> de 44×44 adentro, sobresaliendo 6px arriba y abajo — y con
       .pagination:flex-wrap de arriba, las filas que envuelven se
       pisan porque el layout las separa a 32px pero cada <a> pinta 44.

       height y min-height son propiedades distintas — no hace falta
       pelear especificidad contra la regla de escritorio (que gana en
       especificidad, 3 clases) ni usar !important: el navegador ya usa
       el mayor entre los dos (max(32, 44) = 44), igual que el truco de
       Select2 más arriba. Con el <li> en 44 y ya display:inline-flex +
       align-items:center por la regla de escritorio, el borde/fondo/
       radius del chip envuelven al <a> en vez de quedarse cortos. */
    /* OJO con el selector de 4 clases: styles.css trae
           .table-responsive .dataTables_wrapper .dataTables_paginate .paginate_button
           { padding:6px 12px; border:...; margin:0 3px }
       y esas cuatro clases le ganan a las tres de la regla de escritorio
       de acá (línea ~799). Un primer intento de esta regla usaba solo las
       tres: el min-width SÍ entró (styles.css no lo declara) pero el
       padding NO, y el chip quedó de 57px con el <a> de 31 adentro — el
       spec lo agarró en rojo. Por eso se repite el selector con el
       prefijo .table-responsive: empata en especificidad y gana por orden
       (app.css carga después de styles.css, ver partials/head.php). */
    .table-responsive .dataTables_wrapper .dataTables_paginate .paginate_button,
    .dataTables_wrapper .dataTables_paginate .paginate_button {
        /* El padding del chip se va en táctil porque el <a> de adentro
           ahora lo ocupa completo (ver regla siguiente). Si se dejara,
           el fondo verde del enlace activo quedaría metido hacia
           adentro del borde blanco del chip.

           A propósito NO se le pone min-width/min-height de 44 al chip:
           el chip es border-box y lleva 1px de borde, así que un chip de
           44 deja al <a> de adentro en 42 — y el <a> es el control que
           el dedo realmente toca. Se probó y el spec lo cazó en rojo con
           42×44. El piso va en el <a> (regla general .page-link, más
           arriba) y el chip se estira solo hasta envolverlo: 44 + 2 de
           borde = 46. Así ambas capas pasan y el que manda el tamaño es
           el que de verdad es el objetivo táctil.

           El ALTO sí hay que fijarlo acá, porque la regla de escritorio
           clava height:32px y sin esto el <a> de 44 sobresale del chip
           (que es justo el defecto #1 de la ronda 3). Va +2px por los
           dos bordes de 1px: el chip es border-box, así que 46 de caja
           = 44 de contenido = exactamente el <a>. Con 44 pelado el <a>
           se desbordaba 2px. El ancho no necesita nada: el chip se
           estira solo hasta los 46 que pide el <a> más sus bordes. */
        min-height: calc(var(--tap-min) + 2px);
        padding: 0;
    }
    /* Subir solo el chip no alcanzaba: el <a> quedaba en 44px de ancho
       DENTRO de un chip de 70px, y como el fondo verde del "página
       actual" lo pinta Bootstrap sobre el <a> (no sobre el <li>), se veía
       un cuadrado verde de esquinas rectas flotando dentro de una cajita
       blanca redondeada. Se vio en una captura, no se dedujo del CSS.
       El <li> ya es display:inline-flex por la regla de escritorio, así
       que el <a> es un flex item: flex:1 lo estira a lo ancho y
       align-self:stretch a lo alto, y border-radius:inherit le copia el
       redondeo al chip.

       El <a> CONSERVA el min-width:44px de la regla general .page-link:
       es él quien manda el tamaño, y el chip se estira para envolverlo
       (ver el comentario del chip arriba). Una versión anterior de esta
       regla le ponía min-width:0 al <a> para que el chip mandara — se
       revirtió porque dejaba el <a> en 42, pero el comentario se quedó
       describiendo la versión vieja. Lo marcó la segunda revisión. */
    .table-responsive .dataTables_wrapper .dataTables_paginate .paginate_button .page-link,
    .dataTables_wrapper .dataTables_paginate .paginate_button .page-link {
        flex: 1;
        align-self: stretch;
        border-radius: inherit;
    }

    /* ── Checkbox / switch — EXCEPCIÓN DELIBERADA, no se tocan ──────
       #906 NO sube los checkboxes al piso de 44px. No es un olvido: se
       intentó tres veces y las tres rompieron algo visible en pantalla.
       Queda escrito acá para que el próximo no repita el recorrido.

       Intento 1 — ".form-check-input{width:24px;height:24px}" (subir el
       cuadrito al piso de WCAG AA). El ancho del switch sobrevivió
       (".form-switch .form-check-input{width:2em}" de Bootstrap pesa
       más), pero el alto no: el pill de 28×14 se deformó en un blob de
       28×24 con la perilla estirada.

       Intento 2 — ".form-check{min-height:44px;display:flex;
       align-items:center}" (agrandar la fila entera). .form-check se usa
       en 38 archivos y no siempre es "cuadrito + label":
         · catalogo_cuentas.php:292-299 — el switch trae un TERCER hijo,
           un <div class="form-text"> de ayuda, que como item flex se iba
           AL LADO del label en vez de abajo.
         · configuracion_negocio.php:336/350 y ~20 iguales — el switch
           trae SOLO el <input>; su texto vive en un <div class=
           "config-label"> HERMANO. Centrar en 44px hundía el switch
           ~13px por debajo del texto al que pertenece.
         · App-wide: Bootstrap coloca el cuadrito con float + margen
           negativo contra el padding-left del padre. float no aplica a
           hijos flex, así que el espacio cuadrito↔label pasó de ~8px a 0.

       Intento 3 — ".form-check-label{min-height:44px;display:inline-flex}"
       (agrandar solo el label, que es lo que el for= hace tocable). Rompe
       la alineación vertical: el cuadrito lo posiciona el float en la
       PRIMERA línea del bloque, mientras el texto del label se centra en
       sus 44px nuevos. Medido en configuracion_negocio.php: los 14
       checkboxes de "Bancos con que Trabaja" quedaban descuadrados 8.7px
       cada uno (centro del cuadrito en y=748.4, centro del label en
       y=757.1). Además el inline-flex se comía el espacio en blanco entre
       el <i> del ícono y el texto ("⚠Solo sin contacto" pegado), que hubo
       que reponer con un gap.

       El patrón común: el layout de Bootstrap para checkboxes ata la
       posición del cuadrito a la primera línea de texto. Cualquier cosa
       que cambie el alto del label o del contenedor los separa. Subirlos
       de verdad pide rediseñar el componente, no parchar CSS — y eso es
       un cambio de diseño que merece su propio issue, no un rincón de
       este PR. Se deja como está y se reporta.

       Qué pasa mientras tanto: el cuadrito mide 28×18.4 y el label su
       alto natural. El área tocable REAL sigue siendo el label completo
       gracias al for=, y tests/e2e-browser/tap-targets.spec.js lo prueba
       tocando el extremo de la fila y comprobando que el switch cambia.
       No llega a 44px, y este comentario existe para no fingir que sí. */

    /* ── "Volver al POS" (topbar) ────────────────────────────────
       #906: medía 24×10 en iPhone — no era un botón chico, era un hijo
       flex APLASTADO. El <ul class="navbar-nav"> del topbar es un
       contenedor flex sin wrap; con varios items compitiendo por el
       ancho, ese <li> se encogía por debajo de su propio contenido.

       No hace falta una regla propia. El primer arreglo de #906 le puso
       flex-shrink:0 al <li> (vía :has()) y al link — y el mutation-check
       demostró que era CÓDIGO MUERTO: borrando ambas reglas el botón
       sigue midiendo 44×44. El motivo es que #bs-volver-pos lleva la
       clase .btn, y el min-width/min-height de arriba ya lo protege: un
       flex item no puede encogerse por debajo de su propio min-width,
       sin importar el flex-shrink. Se borró en vez de dejarla "por si
       acaso" — una regla que no hace nada igual se lee como si hiciera
       algo, y encima :has() pedía WebKit 15.4+ para nada.
       La aserción de tap-targets.spec.js sobre #bs-volver-pos se queda:
       lo que vigila es que el control no vuelva a aplastarse, no el
       mecanismo con el que se arregló. */

    /* ── Flecha para cerrar el sidebar deslizante (NO el hamburger
       del topbar, #headerCollapse, que ya mide 50×70 y no necesita
       nada) ─────────────────────────────────────────────────────── */
    .sidebartoggler.ms-auto {
        min-height: var(--tap-min);
        min-width: var(--tap-min);
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }

    /* ── Dashboard: accesos rápidos del hero + CTA "Ver todo →" ───── */
    .dash-welcome-actions .btn-quick {
        min-height: var(--tap-min);
    }
    .dash-tareas-cta {
        display: inline-flex;
        align-items: center;
        min-height: var(--tap-min);
    }

    /* ── POS: botones de modo (Facturar/Turno/Cliente), "+Producto/
       Cert/Paquete/Servicio" y "Abrir Panel Completo" — todos ya
       traen display:inline-flex + align-items:center propio en
       pos.php, así que acá solo hace falta el alto mínimo. ────────── */
    .pos-mode-btn,
    .pos-add-btn,
    .pqi-open-panel {
        min-height: var(--tap-min);
    }
    /* Logo del topbar del POS — mismo trato; .pos-topbar .pos-brand
       ya centra con align-items:center. */
    a.pos-brand {
        min-height: var(--tap-min);
    }

    /* ── Login ──────────────────────────────────────────────────── */
    .btn-instalar-login {
        min-height: var(--tap-min);
    }
    /* El enlace "¿Olvidaste tu contraseña?" no tenía clase ni id en
       public/index.php — se le agregó .link-forgot-pwd solo para
       poder targetearlo desde CSS (el texto no cambió). */
    .link-forgot-pwd {
        display: inline-flex;
        align-items: center;
        min-height: var(--tap-min);
    }

    /* ── Clientes: link del nombre en la tabla — acepta filas más
       altas a propósito (así lo pide el #906). ─────────────────────
       Nota: el mismo nombre de clase se reutiliza en el botón-lápiz
       de la misma fila (ver cl_render_row() en
       controllers/clientes_controller.php) — ese botón ya sube a
       44×44 por la regla general de .btn de arriba, así que acá no
       hace falta nada extra para él. */
    .btn-ficha {
        display: inline-flex;
        align-items: center;
        min-height: var(--tap-min);
    }
}

/* ── Casillas visibles sobre cualquier fondo ───────────────────────────────
   El tema (styles.css) deja `--bs-form-check-bg: transparent` y el borde en
   var(--bs-border-color) = #dfe5ef. Sobre blanco eso ya es un contraste de
   ~1.2:1 — por debajo del 3:1 que WCAG 2.2 pide para un control —, y sobre
   cualquier panel de color la casilla desaparece del todo: en "Requiere ficha
   técnica" (servicios), que vive dentro de un recuadro verde claro, el usuario
   veía SOLO el letrero y daba por hecho que la opción no existía. La casilla
   estaba ahí y respondía al clic; simplemente no se veía.

   Fondo blanco propio (la separa del panel, sea del color que sea) y borde con
   contraste real. Se excluye :checked y :indeterminate para no pisar el relleno
   de marca que ya les pone Bootstrap. */
.form-check-input:not(:checked):not(:indeterminate) {
    background-color: #fff;
    border-color: #5a6a85;
}

/* ── Sidebar: el área desplazable mide lo que sobra ───────────────────────
   El tema fija la altura del menú con un calc() a mano
   (.left-sidebar .scroll-sidebar { height: calc(100vh - 175px) }) escrito
   ANTES de que el sidebar tuviera buscador. Con la barra de búsqueda (52px)
   y el logo (70px) arriba, ese calc() dejaba el menú más alto que el espacio
   real: la ÚLTIMA entrada quedaba debajo del borde de la pantalla y no había
   forma de llegar a ella ni desplazando hasta el final. Se ve en la consola
   de superadmin, que es el menú más largo, y empeora con cada entrada nueva.

   Arreglo: columna flex. El logo y el buscador toman lo suyo y el menú se
   queda con el resto, sea cual sea la altura de la ventana o lo que crezca
   la cabecera. `min-height: 0` es obligatorio — sin él un hijo flex no baja
   de su altura de contenido y no desplaza. */
.left-sidebar > .bs-sidebar-col {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.left-sidebar .scroll-sidebar {
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
}

/* ── Por qué el menú NO lleva overflow:hidden ─────────────────────────────
   Hubo aquí una regla
       .left-sidebar .scroll-sidebar[data-simplebar=INIT] { overflow: hidden }
   para quitar el segundo desplazador (el tema deja overflow-y:auto en el
   anfitrión y el .simplebar-placeholder lo volvía desplazable además del de
   SimpleBar). Salió en v0.1.124 y DEJÓ EL MENÚ SIN DESPLAZAMIENTO NINGUNO en
   producción.

   Por qué: SimpleBar decide si hay desbordamiento midiendo el anfitrión. Con
   overflow:hidden concluye que no hay nada que desplazar y SE APAGA SOLO:
   quita la clase `simplebar-scrollable-y` y pone `overflow:hidden` en su
   propio content-wrapper. Medido en el navegador sobre v0.1.124: clases del
   anfitrión `sidebar-nav scroll-sidebar` (sin `simplebar-scrollable-y`),
   estilo en línea del wrapper `overflow: hidden`, 70px de contenido
   inalcanzable y sin barra a la vista.

   O sea: el arreglo de un defecto cosmético creó uno que impedía USAR el
   menú. Se revierte. Vuelve el segundo desplazador (arrastrar la barra se
   queda corto; la rueda sí llega al final), que es muy preferible a un menú
   que no se mueve. El arreglo correcto no pasa por el overflow del anfitrión.

   Lo vigila tests/e2e-browser/sidebar-scroll.spec.js: SimpleBar encendido,
   barra con alto > 0, y la rueda moviendo el menú de verdad.
   ────────────────────────────────────────────────────────────────────────── */

/* ══════════════════════════════════════════════════════════════
   Miga de pan (breadcrumbs) — lib/breadcrumbs.php + partials/topbar.php.
   Vive justo debajo de la barra global (<header class="topbar">) y por
   encima del page-hero de cada vista. NN/g "Breadcrumbs: 11 Design
   Guidelines": jerarquía del menú, no historial; la pantalla actual no es
   un enlace.
   Nota: no existe un token --ink-soft en app-tokens.css — se usa
   --surface-800 (texto secundario) para los ítems intermedios y
   --surface-900 (texto principal) para el actual, que son los tokens
   equivalentes ya definidos ahí.
   ══════════════════════════════════════════════════════════════ */
.app-breadcrumb {
    background: transparent;
    padding: 6px 26px;
    overflow-x: auto;
    white-space: nowrap; /* nunca 2 líneas — regla NN/g de móvil */
    -ms-overflow-style: none;
    scrollbar-width: none;
}
.app-breadcrumb::-webkit-scrollbar { display: none; }

.app-breadcrumb .breadcrumb {
    --bs-breadcrumb-divider: '\203A'; /* › */
    margin: 0;
    font-size: 13px;
    display: inline-flex;
    align-items: center;
    flex-wrap: nowrap;
}

.app-breadcrumb .breadcrumb-item {
    color: var(--surface-800);
    display: inline-flex;
    align-items: center;
}

.app-breadcrumb .breadcrumb-item a {
    color: var(--surface-800);
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    min-height: 32px; /* en escritorio la miga es angosta (8px 26px 4px de padding) */
}
/* Tap target: 44px solo hace falta cuando el dedo es la entrada — móvil. */
@media (max-width: 767.98px) {
    .app-breadcrumb .breadcrumb-item a { min-height: 44px; }
}
.app-breadcrumb .breadcrumb-item a:hover { color: var(--primary); text-decoration: underline; }

.app-breadcrumb .breadcrumb-item.active {
    color: var(--surface-900);
    font-weight: 600;
}

.app-breadcrumb .app-bc-ellipsis { display: none; } /* solo aparece <576px */

@media (max-width: 575.98px) {
    /* Regla NN/g de móvil: nunca 2 líneas. En vez de envolver, se colapsan
       TODOS los grupos intermedios en un solo "…" — Inicio › … › Actual. */
    .app-breadcrumb .breadcrumb-item.app-bc-group { display: none; }
    .app-breadcrumb .app-bc-ellipsis { display: inline-flex; }
}

/* ── Atajos del topbar (spec 2026-09-15 "Volver al menú v1 para todos +
   atajos de uso frecuente en el topbar", Parte B) — grupo ligero de
   contorno junto a "Ir al POS" (que sigue siendo el único botón sólido).
   Tokens de app-tokens.css: borde y texto --brand-600, fondo hover/activo
   --brand-50, radio 8px, alto 32px — mismas medidas que "Instalar app".
   Clase .btn en el anchor: bs-theme-beautysoft.css trae
   "a:not(.btn):not(.nav-link):not(.sidebar-link):not(.dropdown-item)" con
   especificidad (0,5,1) que le gana a una clase sola — el escape es .btn,
   no una guerra de especificidad (ver reference_theme_anchor_specificity_trap). */
.bs-atajos {
    display: inline-flex;
    align-items: stretch;
    height: 32px;
    border: 1px solid var(--brand-600);
    border-radius: 8px;
    overflow: hidden;
}
.bs-atajos .bs-atajos__item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 100%;
    padding: 0 10px;
    margin: 0;
    border: 0;
    border-right: 1px solid var(--brand-600);
    border-radius: 0;
    background: transparent;
    color: var(--brand-600);
    font-size: .78rem;
    font-weight: 600;
    line-height: 1;
    text-decoration: none;
}
.bs-atajos .bs-atajos__item:last-child { border-right: 0; }
.bs-atajos .bs-atajos__item:hover,
.bs-atajos .bs-atajos__item:focus-visible {
    background: var(--brand-50);
    color: var(--brand-600);
    text-decoration: none;
}
.bs-atajos .bs-atajos__item.is-active {
    background: var(--brand-50);
    font-weight: 700;
}
.bs-atajos__toggle {
    background: transparent;
    color: var(--brand-600);
    border: 1px solid var(--brand-600);
    font-weight: 600;
    font-size: .78rem;
    padding: 5px 12px;
    border-radius: 8px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.bs-atajos__toggle:hover,
.bs-atajos__toggle:focus-visible {
    background: var(--brand-50);
    color: var(--brand-600);
}
/* Piso táctil del dropdown del teléfono (#906): filas ≥44px. */
.bs-atajos-menu .dropdown-item {
    min-height: 44px;
    display: flex;
    align-items: center;
    gap: 8px;
}
.bs-atajos-menu .dropdown-item.is-active {
    background: var(--brand-50);
    color: var(--brand-600);
    font-weight: 700;
}
/* #906: en táctil (any-pointer: coarse) el bloque de arriba (~L1778) sube
   .btn a 44px de piso, pero .bs-atajos fija height:32px + overflow:hidden
   y .bs-atajos__item hereda height:100% del padre — el hijo queda RECORTADO
   a 32px sin importar su propio min-height. Esta regla tiene que ir DESPUÉS
   del bloque de arriba en el archivo (misma especificidad 0,1,0: gana el
   que carga último) para que el auto/min-height de acá no lo pise el
   height:32px fijo de escritorio. tests/e2e-browser/tap-targets.spec.js
   (ipad: .bs-atajos__item; iphone: .bs-atajos__toggle) lo vigila. */
@media (any-pointer: coarse) {
    .bs-atajos {
        height: auto;
        min-height: var(--tap-min);
    }
}

/* ═══════════════════════════════════════════════════════════════════════
   Teléfono (#1205) — barrido de 135 pantallas a 390px en WebKit
   (tools/mobile_sweep.js). Todo lo de aquí es ≤575.98px: escritorio y
   tablet no cambian. La guarda es tests/e2e-browser/mobile-every-screen.spec.js.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 575.98px) {
    /* Topbar: con el piso táctil de 44px (#906) los seis controles de la
       derecha (instalar, Atajos, POS, ayuda, avatar) más sus márgenes me-2
       medían 405px en una pantalla de 390 — el avatar quedaba cortado en
       93 pantallas. Se aprietan los márgenes, no los controles. */
    .topbar .navbar-nav.ms-auto > .nav-item { margin-right: 2px !important; margin-left: 0 !important; }
    .topbar .navbar-nav.ms-auto { min-width: 0; }
    .bs-atajos__toggle { padding: 5px 8px; }

    /* Buscador de DataTables: el min-width:320px de arriba (~L674) más la
       etiqueta "Buscar:" que traen las tablas iniciadas con .DataTable()
       directo (r_clientes, r_suplidores, r_eficiencia_laboral…) medía ~380px
       en una tarjeta de 326 — la caja se salía por la derecha. En el
       teléfono el buscador ocupa el ancho de la fila y se encoge con ella. */
    .dataTables_wrapper .dataTables_filter,
    .dataTables_wrapper .dataTables_filter label { width: 100%; }
    .dataTables_wrapper .dataTables_filter input { min-width: 0; flex: 1 1 auto; width: auto; }

    /* Primera columna de una tabla DataTables (el nombre: "Apellidos, Nombres"
       + avisos): puede partirse en líneas. Muchas tablas son .text-nowrap
       (utilidad con !important) y en el teléfono el nombre en una sola línea
       más la columna de acciones (que ya no se pliega) no cabían: empleados.php
       medía 426px en 326 con todo lo demás plegado. */
    table.dataTable > tbody > tr > td:first-child,
    table.dataTable > tbody > tr > td:first-child .badge { white-space: normal !important; }
    table.dataTable > tbody > tr > td:first-child .badge { text-align: left; }

}

/* #1205: el min-width:44px de .btn (#906, arriba) tiene un efecto colateral
   en filas flex. Un item flex trae por defecto min-width:auto, que significa
   "nunca más angosto que mi propio texto"; al fijarle 44px, ese piso
   desaparece y una fila apretada encoge el botón hasta 44px con el texto
   colgando por fuera ("Salir de impersonación" en el banner rojo, "Ir a
   Cuentas Bancarias" en contabilidad_puesta_en_marcha.php). flex-shrink:0
   devuelve el piso: el botón conserva su ancho y lo que se acomoda es el
   texto vecino, que sí puede partirse en líneas. */
@media (any-pointer: coarse) {
    /* En toda pantalla táctil, teléfono y iPad (el iPad es el equipo de los
       salones, #906). Primero fue solo teléfono; el barrido del iPad Pro 11
       (834 y 1194px) encontró los mismos botones aplastados bajo su texto
       ("Ir a Cuentas Bancarias", r_movimientos_bancarios). La fila que ya no
       cabe la recoge la regla de abajo, que también vale en táctil. Un .w-100
       está hecho para ceder: si no encoge, empuja a su vecino fuera de la
       fila (el "×" de cotizaciones.php:115, revisión de #1205). */
    .btn:not(.w-100) { flex-shrink: 0; }
    .d-flex:has(> .btn) { flex-wrap: wrap; }
}

/* #1205 — .table-stack: tabla corta que en el teléfono se lee fila por fila.
   Cada <tr> pasa a ser un bloque: la primera celda (el nombre) ocupa la
   línea completa y el resto se acomoda debajo; una celda .text-end (las
   acciones) se va a la derecha. Se usa en tablas de POCAS columnas cuyo
   contenido se entiende sin encabezado (nombre · estado · fecha · botones),
   p.ej. los cuatro bloques de superadmin/overview.php, donde los botones de
   contacto quedaban fuera de la pantalla. Una tabla de reporte NO lleva esto:
   ésas se deslizan con la primera columna fija (report-shell.css). Si una
   celda necesita su etiqueta, ponle data-label="…" y se muestra delante. */
@media (max-width: 575.98px) {
    .table-stack > thead { display: none; }
    .table-stack,
    .table-stack > tbody { display: block; width: 100%; }
    .table-stack > tbody > tr {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: .35rem .75rem;
        padding: .65rem .25rem;
        border-bottom: 1px solid var(--bs-border-color);
    }
    .table-stack > tbody > tr > td {
        display: block;
        border: 0;
        padding: 0;
        background: transparent;
        box-shadow: none;
    }
    .table-stack > tbody > tr > td:first-child { flex-basis: 100%; }
    /* Un <select> mide de ancho mínimo su opción más larga ("01 — Gastos de
       personal…"): sin esto empujaba la tabla apilada a 441px en una tarjeta
       de 326 (contabilidad_puesta_en_marcha.php). La celda y sus controles
       ceden al ancho de la fila. */
    .table-stack > tbody > tr > td { min-width: 0; max-width: 100%; }
    /* Puesta en marcha: la celda del select de cuenta ocupa la fila entera.
       Acotado a esa celda: en reposicion.php los inputs chicos de cantidad y
       precio SÍ deben compartir línea (revisión de #1205). */
    .table-stack > tbody > tr > td.cpm-cuenta { flex-basis: 100%; }
    .table-stack > tbody > tr > td .form-select,
    .table-stack > tbody > tr > td .form-control,
    .table-stack > tbody > tr > td select { max-width: 100%; min-width: 0; }
    .table-stack > tbody > tr > td.text-end { margin-left: auto; }
    .table-stack > tbody > tr > td[data-label]::before {
        content: attr(data-label) ": ";
        color: var(--bs-secondary-color);
        font-size: .8em;
    }
}

/* #1205: la "×" de limpiar de Select2 heredaba line-height:40px de la
   selección y medía 15×26px: el glifo se salía por debajo de su propia caja
   y era un blanco imposible para un dedo (lo cazó mobile-every-screen.spec
   en el modal de gastos_menores.php). En táctil: glifo centrado y un área
   de toque de 32px sin mover el resto del control. */
@media (any-pointer: coarse) {
    .select2-container .select2-selection__clear {
        line-height: 1;
        min-width: 32px;
        min-height: 32px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        padding: 0;
    }
}

/* Teléfono (#1205): un reporte de 8-14 columnas se lee como hoja de
   cálculo, así que en el celular sigue deslizándose de lado dentro de su
   .table-responsive — pero la primera columna queda fija, para no perder de
   vista de qué fila es cada número. Las tablas DataTables quedan fuera: ésas
   pliegan columnas con Responsive y su primera columna es el botón "+".
   Solo pantalla: el PDF/impresión no cambia. Vivía en report-shell.css;
   aquí lo ven todas las pantallas, no solo 46 de los 56 reportes. Vale a
   cualquier ancho: en el iPad (834px) nueve reportes también se deslizan, y
   un sticky que no se desliza no cambia nada. Hasta 1199px: el escritorio
   normal queda como estaba. */
@media screen and (max-width: 1199.98px) {
    /* Nunca una celda con colspan ("Subtotal …" de r_servicios_empleado,
       "Total …" de r_gastos_menores): fija y opaca, tapaba el monto que viene
       después al deslizar. */
    /* Solo la PRIMERA fila del encabezado: en un encabezado de dos filas
       (r_clientes_fidelizacion: "Cliente" con rowspan=2) el primer <th> de
       la segunda fila es de la columna 2, y fijarlo lo montaba sobre
       "Cliente" al deslizar. */
    .table-responsive > table:not(.dataTable):not(.table-stack):not(.table-stack-lg) > thead > tr:first-child > :first-child,
    .table-responsive > table:not(.dataTable):not(.table-stack):not(.table-stack-lg) > tbody > tr > :first-child:not([colspan]),
    .table-responsive > table:not(.dataTable):not(.table-stack):not(.table-stack-lg) > tfoot > tr > :first-child:not([colspan]) {
        position: sticky;
        left: 0;
        z-index: 1;
        /* Bootstrap pinta la franja (table-striped) y el hover con un
           box-shadow inset en cada celda; reemplazarlo dejaba la celda fija
           sin franja ni hover. Se conserva y se le suma la línea del borde. */
        box-shadow: inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg, transparent))),
                    1px 0 0 rgba(0, 0, 0, .08);
    }
    /* Fondo opaco para que lo que pasa por debajo al deslizar no se vea; pero
       no en filas con color propio (tr.table-secondary de las reversiones en
       r_diario_general, etc.) ni en celdas con fondo en línea. */
    .table-responsive > table:not(.dataTable):not(.table-stack):not(.table-stack-lg) > tbody > tr:not([class*="table-"]) > :first-child:not([colspan]):not([style*="background"]) {
        background-color: #fff;
    }
}

/* ========================================================================
   #1205 fase 2 — bloque de teléfono/táctil añadido al final.
   ======================================================================== */

/* #1205: blancos de toque de 44px (mínimo de Apple) en controles que medían
   23-32px. Solo táctil (any-pointer: coarse): en escritorio con ratón nada
   cambia. Los tres tienen su tamaño en estilo en línea o en un <style> de la
   propia pantalla, que gana por orden — de ahí el !important. */
@media (any-pointer: coarse) {
    /* "×" del aviso de contabilidad (partials/contabilidad_banner.php, en
       125 pantallas): medía 26×24px con padding inline de 2px 4px. */
    #bsContabilidadBannerDismiss {
        min-width: 44px !important;
        min-height: 44px !important;
        padding: 0 !important;
        display: inline-flex !important;
        align-items: center;
        justify-content: center;
    }
    /* Barra de exportar de los reportes (report/_exportar.php): 29px de alto. */
    .exp-btn {
        min-height: 44px !important;
        display: inline-flex !important;
        align-items: center;
        justify-content: center;
    }
    /* Selector Día/Semana/Mes de agenda.php: 32px de alto. */
    .ag-vista-btn {
        min-height: 44px !important;
        min-width: 44px;
    }
}

@media (max-width: 575.98px) {
    /* #1205: el pie (Total) de una .table-stack: sin esto el <tfoot> seguía
       siendo una tabla dentro del bloque y su celda "Total" se desbordaba
       (reposicion.php, #tbl-orden). Fila flex: etiqueta a la izquierda, cifra
       a la derecha; las celdas vacías no pintan nada. */
    .table-stack > tfoot { display: block; width: 100%; }
    .table-stack > tfoot > tr {
        display: flex;
        justify-content: space-between;
        gap: .75rem;
        padding: .65rem .25rem;
    }
    .table-stack > tfoot > tr > td { display: block; border: 0; padding: 0; }
    .table-stack > tfoot > tr > td:empty { display: none; }
    /* Los campos de una celda de lista apilada no se salen de la línea. */
    .table-stack > tbody > tr > td .form-control,
    .table-stack > tbody > tr > td .form-select { max-width: 100%; }
}

/* #1205: la "×" de cerrar de Bootstrap (.btn-close) mide ~24px: en el iPad no
   se acierta con el dedo (modal de vender paquete, todos los modales). En
   táctil la CAJA sube a 44px; el ícono sigue de 1em, centrado (Bootstrap lo
   pinta como fondo "center/1em"), así que no cambia cómo se ve. */
@media (any-pointer: coarse) {
    .btn-close {
        box-sizing: border-box;
        width: 44px;
        height: 44px;
        padding: 0;
    }
    /* Dentro de una píldora (compras.php: "OC: … ×") la caja de 44px estiraba
       la píldora entera; ahí se queda del tamaño de Bootstrap. */
    .badge > .btn-close {
        box-sizing: content-box;
        width: 1em;
        height: 1em;
        padding: .25em;
    }
}

/* ========================================================================
   #1205 fase 3 — rejillas de líneas y listas de consola en teléfono/iPad.
   ======================================================================== */

/* #1205: .table-stack-lg = la misma tabla apilada de .table-stack pero hasta
   991.98px, no solo 575.98. Existe porque un modal-xl solo ensancha desde
   992px: en el iPad vertical (834px) el modal mide 500px y una rejilla de 7
   columnas (compras.php) seguía deslizándose de lado. Va junto a
   .table-stack; la regla de primera-columna-fija excluye las dos clases.
   Cada celda con data-label muestra su etiqueta ARRIBA del valor, como un
   campo de formulario. Escritorio (≥992px) no cambia. */
@media (max-width: 991.98px) {
    .table-stack-lg > thead { display: none; }
    .table-stack-lg,
    .table-stack-lg > tbody { display: block; width: 100%; }
    .table-stack-lg > tbody > tr {
        display: flex;
        flex-wrap: wrap;
        align-items: flex-start;
        gap: .5rem .75rem;
        padding: .65rem .25rem;
        border-bottom: 1px solid var(--bs-border-color);
    }
    .table-stack-lg > tbody > tr > td {
        display: block;
        border: 0;
        padding: 0;
        background: transparent;
        box-shadow: none;
        min-width: 0;
        max-width: 100%;
        text-align: left !important; /* gana a .text-center/.text-end de la celda */
    }
    .table-stack-lg > tbody > tr > td:first-child { flex-basis: 100%; }
    .table-stack-lg > tbody > tr > td.ts-wide { flex: 1 1 100%; }
    .table-stack-lg > tbody > tr > td[data-label] { display: flex; flex-direction: column; }
    .table-stack-lg > tbody > tr > td[data-label]::before {
        content: attr(data-label);
        color: var(--bs-secondary-color);
        font-size: .75rem;
        line-height: 1.2;
        margin-bottom: .15rem;
    }
    .table-stack-lg > tbody > tr > td .form-control,
    .table-stack-lg > tbody > tr > td .form-select,
    .table-stack-lg > tbody > tr > td select { width: 100%; max-width: 100%; min-width: 0; }
    .table-stack-lg > tfoot { display: block; width: 100%; }
    .table-stack-lg > tfoot > tr {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: .5rem .75rem;
        padding: .5rem .25rem;
    }
    .table-stack-lg > tfoot > tr > td { display: block; border: 0; padding: 0; width: auto; }
    .table-stack-lg > tfoot > tr > td:empty { display: none; }
    .table-stack-lg > tfoot > tr > td[data-label]::before { content: attr(data-label) ": "; color: var(--bs-secondary-color); font-size: .8em; font-weight: 400; }

    /* compras.php, #tbl-grid-compra — fila: [Producto ......][🗑] / Cód. /
       Cant. | Precio / Impuesto | Total. Los anchos fijos de los inputs
       (90/110px) ceden al ancho de la celda. */
    #tbl-grid-compra > tbody > tr > td:nth-child(1) { flex: 1 1 calc(100% - 5rem); order: 0; }
    #tbl-grid-compra > tbody > tr > td:nth-child(7) { flex: 0 0 auto; order: 1; }
    #tbl-grid-compra > tbody > tr > td:nth-child(n+2):nth-child(-n+6) { order: 2; }
    #tbl-grid-compra > tbody > tr > td:nth-child(2) { flex: 1 1 100%; }
    #tbl-grid-compra > tbody > tr > td:nth-child(3),
    #tbl-grid-compra > tbody > tr > td:nth-child(4) { flex: 1 1 calc(50% - .4rem); }
    #tbl-grid-compra > tbody > tr > td:nth-child(5),
    #tbl-grid-compra > tbody > tr > td:nth-child(6) { flex: 1 1 calc(50% - .4rem); }
    #tbl-grid-compra .inp-cant,
    #tbl-grid-compra .inp-precio { width: 100%; }

    /* asientos_contables.php, #tabla-detalles — fila: [#][Cuenta .....][🗑] /
       Descripción / Debe | Haber. */
    #tabla-detalles > tbody > tr > td.linea-num { flex: 0 0 auto; order: 0; padding-top: .45rem; }
    #tabla-detalles > tbody > tr > td:nth-child(2) { flex: 1 1 0; order: 1; }
    #tabla-detalles > tbody > tr > td:nth-child(6) { flex: 0 0 auto; order: 2; }
    #tabla-detalles > tbody > tr > td:nth-child(3) { flex: 1 1 100%; order: 3; }
    #tabla-detalles > tbody > tr > td:nth-child(4),
    #tabla-detalles > tbody > tr > td:nth-child(5) { flex: 1 1 calc(50% - .4rem); order: 4; }
}

/* Logo subido por la empresa (Configuración → Empresa): el menú es oscuro,
   así que va sobre una placa clara para que se lea cualquier logo. */
.sb-logo-propio { max-width: 160px; max-height: 56px; width: auto; height: auto; background: #FBF7EE; border-radius: 8px; padding: 4px 8px; object-fit: contain; }
.sb-logo-propio-mini { width: 34px; height: 34px; background: #FBF7EE; border-radius: 6px; padding: 2px; object-fit: contain; }

