/* ============================================================================
   components.css — Primitivas de UI reutilizables del CRM Saphir Parfums
   ----------------------------------------------------------------------------
   Capa intermedia entre `app.css` (variables + base) y los `.razor.css` scoped
   por componente. Todo lo que viva aquí debe cumplir DOS condiciones:

   1. Ser un patrón compartido por 2+ componentes (modal, botón, campo de
      formulario, alert, spinner). Si solo lo usa un componente, vive en su
      `.razor.css`.

   2. No depender del scope de un componente concreto. Las reglas son globales
      a propósito — los `.razor.css` siguen pudiendo sobreescribir puntualmente
      gracias a la mayor especificidad que da el atributo `[b-xxxxxx]` que
      Blazor inyecta al compilar.

   Convenciones de naming
   ----------------------------------------------------------------------------
   Las clases conservan los nombres ya en uso por los componentes (`btn-primary`,
   `btn-secondary`, `btn-icon`, `field`, `form-alert`, `spin`) para que la
   migración sea sustracción, no renombrado masivo. Los prefijos `crm-modal-*`
   son nuevos y solo los usa `Shared/ModalShell.razor`.
   ========================================================================== */


/* ── Cabecera de página ─────────────────────────────────────────────────────
   Patrón consistente para todos los módulos: título + subtítulo agrupados a la
   izquierda y una fila de acciones opcional alineada a la derecha. Se apilan en
   móvil. */
.page-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    margin-bottom: 1.25rem;
}
.page-header__titles { min-width: 0; }
.page-subtitle {
    color: var(--color-text-secondary);
    font-size: 0.9rem;
    margin: 0.15rem 0 0;
    line-height: 1.4;
}
.page-header__actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-shrink: 0;
    flex-wrap: wrap;
}


/* ── Botones ──────────────────────────────────────────────────────────────── */
.btn-primary,
.btn-secondary {
    padding: 0.5rem 1rem;
    border-radius: 8px;
    font-size: 0.88rem;
    font-weight: 600;
    border: 1px solid transparent;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-family: inherit;
}

.btn-primary {
    background: var(--color-primary);
    color: var(--color-on-primary);
    border-color: var(--color-primary);
}
.btn-primary:hover:not(:disabled) {
    background: var(--color-primary-dark);
    border-color: var(--color-primary-dark);
}

.btn-secondary {
    background: var(--color-surface);
    color: var(--color-text-secondary);
    border-color: var(--color-border);
}
.btn-secondary:hover:not(:disabled) {
    background: var(--color-bg);
}

.btn-primary:disabled,
.btn-secondary:disabled,
.btn-danger:disabled {
    opacity: 0.6;
    cursor: default;
}

/* Botón destructivo — confirmación de borrado y similares. Usa los mismos
   patrones de tamaño que .btn-primary/.btn-secondary para encajar en el
   footer del ModalShell sin estilos extra. */
.btn-danger {
    padding: 0.5rem 1rem;
    border-radius: 8px;
    font-size: 0.88rem;
    font-weight: 600;
    border: 1px solid var(--color-danger);
    background: var(--color-danger);
    color: #fff;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-family: inherit;
}
.btn-danger:hover:not(:disabled) {
    background: #B62929;
    border-color: #B62929;
}

/* Botón icono — cierres de modal, acciones compactas en cabeceras */
.btn-icon {
    background: transparent;
    border: none;
    width: 34px;
    height: 34px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 6px;
    cursor: pointer;
    color: var(--color-text-secondary);
    transition: background 0.12s;
}
.btn-icon:hover {
    background: var(--color-bg);
}
.btn-icon .material-symbols-outlined {
    font-size: 22px;
}


/* ── Formularios ──────────────────────────────────────────────────────────── */
.field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 0;
}
.field--wide {
    grid-column: 1 / -1;
}
.field label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-text-secondary);
    font-weight: 600;
}
.field input,
.field select,
.field textarea {
    border: 1px solid var(--color-border);
    border-radius: 6px;
    padding: 0.5rem 0.6rem;
    font-family: inherit;
    /* 16px mínimo: con valores menores Safari iOS dispara zoom forzado al focus
       en cada input. La regla aplica a TODOS los formularios que usan .field. */
    font-size: 1rem;
    color: var(--color-text);
    background: var(--color-surface);
    outline: none;
    transition: border-color 0.12s;
}
.field textarea {
    resize: vertical;
    min-height: 72px;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
    border-color: var(--color-primary);
}

.input-error {
    border-color: var(--color-danger) !important;
}
.field-error {
    color: var(--color-danger);
    font-size: 0.78rem;
}
.field-help {
    color: var(--color-text-secondary);
    font-size: 0.78rem;
}
.req {
    color: var(--color-danger);
}


/* ── Alerts en formularios ───────────────────────────────────────────────── */
.form-alert {
    padding: 0.6rem 0.9rem;
    border-radius: 6px;
    font-size: 0.88rem;
}
.form-alert--error {
    background: rgba(204, 51, 51, 0.08);
    color: var(--color-danger);
    border: 1px solid rgba(204, 51, 51, 0.2);
}
.form-alert--success {
    background: rgba(46, 158, 90, 0.08);
    color: var(--color-success);
    border: 1px solid rgba(46, 158, 90, 0.2);
}
/* Aviso que pide una decisión (no es un error): p. ej. enlazar una ficha que ya
   tiene cuenta, que puede ser la segunda cuenta de la misma persona. */
.form-alert--warn {
    background: rgba(224, 155, 48, 0.10);
    color: var(--color-text);
    border: 1px solid rgba(224, 155, 48, 0.35);
}


/* ── Spinner de carga inline (botones, indicadores) ──────────────────────── */
.spin {
    animation: spin 1s linear infinite;
    font-size: 18px;
}
@keyframes spin {
    100% { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .spin { animation-duration: 0.001ms; }
}


/* ── Banner de contexto en modales (cliente fijado, plantilla previa, etc.)  */
.customer-hint {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.7rem 0.85rem;
    background: rgba(45, 90, 142, 0.06);
    border: 1px solid rgba(45, 90, 142, 0.18);
    border-radius: 8px;
    color: var(--color-text);
}
.customer-hint .material-symbols-outlined {
    color: var(--color-primary);
    font-size: 22px;
}
.customer-hint__text {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
    flex: 1;
}
.customer-hint__label {
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-text-secondary);
    font-weight: 600;
}
.customer-hint__value {
    font-size: 0.9rem;
    color: var(--color-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}


/* ── Modal shell ──────────────────────────────────────────────────────────
   Las clases `.crm-modal-*` las consume `Shared/ModalShell.razor`. Son
   globales (no scoped) porque ModalShell expone su markup vía atributos
   estándar y los hijos render-fragment no pueden recibir scope ajeno.

   z-index strategy
   ----------------
   - Default (modal raíz, abierto desde una página):  backdrop 1050 / panel 1060
   - Stacked  (modal abierto sobre otro modal):       backdrop 1100 / panel 1110
   - Si en el futuro hay un tercer nivel (popover dentro de modal stacked),
     usar 1130/1140 — coherente con el rango de los pickers ya existentes.
   ───────────────────────────────────────────────────────────────────────── */

.crm-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    z-index: 1050;
}
.crm-modal-backdrop--stacked {
    z-index: 1100;
}

.crm-modal-panel {
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    /* Fallback `vh` para navegadores antiguos; el `dvh` se aplica si el browser
       lo soporta gracias al @supports de app.css que sobreescribe la variable. */
    max-height: 90vh;
    max-height: min(90dvh, calc(var(--app-100vh) - 24px));
    background: var(--color-surface);
    border-radius: 12px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.22);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    z-index: 1060;
}
.crm-modal-panel--stacked {
    z-index: 1110;
}

/* Tamaños — alineados con los anchos previos para no romper layouts existentes.
   Anchos crecen ligeramente en pantallas grandes mediante clamp() para que
   pantallas 1440+ no malgasten espacio. */
.crm-modal-panel--compact { width: min(95vw, clamp(360px, 38vw, 480px)); }
.crm-modal-panel--small   { width: min(95vw, clamp(420px, 42vw, 520px)); }
.crm-modal-panel--medium  { width: min(95vw, clamp(460px, 48vw, 560px)); }
.crm-modal-panel--large   { width: min(95vw, clamp(520px, 60vw, 640px)); }

.crm-modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--color-border);
}
.crm-modal-header h3 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--color-text);
}

.crm-modal-body {
    padding: 1.25rem;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    /* Reserva espacio para el teclado virtual: cuando se enfoca un input al
       fondo del modal en móvil, el navegador puede hacer scroll y dejar el
       campo justo bajo el teclado. 56px aproxima la altura típica. */
    scroll-padding-bottom: 56px;
}

.crm-modal-footer {
    padding: 0.85rem 1.25rem;
    border-top: 1px solid var(--color-border);
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    background: #F9FAFD;
}

/* En móvil el modal pasa a full-screen — patrón ya consolidado en v0.18.1.
   En v0.46.0+ usamos `dvh` para que el panel se ajuste al alto REAL del
   viewport (sin barra URL), evitando el bug clásico iOS donde el footer
   queda oculto bajo la barra cuando ésta vuelve a aparecer. */
@media (max-width: 767.98px) {
    .crm-modal-panel {
        width: 100vw;
        height: 100vh;
        max-height: 100vh;
        height: var(--app-100vh);
        max-height: var(--app-100vh);
        top: 0;
        left: 0;
        transform: none;
        border-radius: 0;
    }
    .crm-modal-header { padding: 0.85rem 1rem; }
    .crm-modal-body { padding: 1rem; }
    .crm-modal-footer {
        padding: 0.75rem 1rem;
        flex-direction: column;
        align-items: stretch;
        /* Honor safe-area en iOS (notch / home indicator). */
        padding-bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
    }
    .crm-modal-footer .btn-primary,
    .crm-modal-footer .btn-secondary,
    .crm-modal-footer .btn-danger {
        justify-content: center;
        /* En móvil, garantizar el touch-target Apple HIG (44 px) */
        min-height: 44px;
    }
}

/* En móviles muy estrechos los labels en uppercase de 0.75rem quedan al
   límite de legibilidad — los subimos un punto. */
@media (max-width: 479.98px) {
    .field label { font-size: 0.8rem; }
}

/* En tablet vertical / móvil grande (576-991 px) el header del modal en dos
   líneas (título largo + botón cierre) puede pisarse. Permite que el título
   wrappee si es necesario sin romper la cabecera. */
@media (min-width: 576px) and (max-width: 991.98px) {
    .crm-modal-header h3 {
        font-size: clamp(0.95rem, 1.4vw, 1.05rem);
        line-height: 1.3;
    }
}

/* Headers fluidos: el modal sigue legible en pantallas amplias y compacto en
   móvil. */
.crm-modal-header h3 {
    font-size: clamp(0.95rem, 1.1vw + 0.6rem, 1.1rem);
}

/* En pantallas táctiles (sin hover) los botones-icono ganan padding visual
   para mejorar la tasa de acierto del dedo. */
@media (hover: none) and (pointer: coarse) {
    .btn-icon {
        width: 40px;
        height: 40px;
    }
    .btn-icon .material-symbols-outlined {
        font-size: 24px;
    }
    .btn-primary,
    .btn-secondary,
    .btn-danger {
        min-height: 40px;
        padding: 0.55rem 1rem;
    }
}


/* ── Tablas con scroll horizontal ─────────────────────────────────────────
   Patrón "scroll shadow" (Roman Komarov): gradientes que aparecen en los
   bordes solo cuando hay contenido oculto a izquierda/derecha. Pista visual
   de que la tabla se puede deslizar — crítico en móvil donde no hay
   scrollbar nativa. Aplica globalmente a cualquier .table-scroll usado por
   las listas (Orders, DeliveryNotes, Invoices, ShoppingCarts, etc.). */
.table-scroll {
    /* Scroll horizontal en móvil (imprescindible para que el "scroll shadow"
       funcione y las tablas anchas se puedan deslizar; el body tiene overflow-x
       hidden). */
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    background:
        linear-gradient(to right, var(--color-surface) 30%, rgba(255, 255, 255, 0)) left center,
        linear-gradient(to right, rgba(255, 255, 255, 0), var(--color-surface) 70%) right center,
        radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0)) left center,
        radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0)) right center;
    background-repeat: no-repeat;
    background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
    background-attachment: local, local, scroll, scroll;
}

/* ── Tabla base (aspecto Saphir, no el Bootstrap pelado) ──────────────────── */
.table-scroll > .table { margin-bottom: 0; }
.table thead th {
    background: var(--color-bg);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    font-weight: 700;
    color: var(--color-text-secondary);
    border-bottom: 1px solid var(--color-border);
    white-space: nowrap;
    padding: 0.6rem 0.75rem;
}
.table tbody td {
    font-size: 0.9rem;
    vertical-align: middle;
    padding: 0.6rem 0.75rem;
    border-bottom: 1px solid var(--color-border);
}
.table tbody tr:last-child td { border-bottom: none; }
.table tbody tr:hover { background: var(--color-bg-hover); }


/* ── Listados que ocupan el alto del viewport (v0.38.0+) ──────────────────
   Patrón opt-in: añade `table-card--fill` a un `.table-card` (o
   `catalog-card--fill` a un `.catalog-card`) para que ocupe todo el alto
   disponible del content area. La cabecera (caption + tabs + toolbar +
   chips) y la paginación quedan fijas, el `.table-scroll` (tbody) o el
   `.items-grid` (vista tarjetas del catálogo) es el único que scrollea
   internamente, y la cabecera de tabla queda sticky.

   Estructura esperada:
     .table-card / .catalog-card (display: flex column, height 100%)
       ├── .table-caption / .catalog-caption (flex-shrink: 0)
       ├── .tab-bar / .toolbar / .active-chips / .cart-lock-banner (flex-shrink: 0)
       ├── .table-scroll / .items-grid (flex: 1, overflow auto)
       └── .pagination-bar (flex-shrink: 0)

   Funciona porque `.layout__content` (MainLayout) tiene altura definida vía
   flex, así que el `height: 100%` del card propaga correctamente. La regla
   gana en especificidad (0,3,0) frente a las reglas scoped de cada página
   (0,2,0), por lo que anula los `max-height: 7Xvh` legacy sin necesidad de
   tocar cada `.razor.css` ni recurrir a `!important`. */
.table-card.table-card--fill,
.catalog-card.catalog-card--fill {
    display: flex;
    flex-direction: column;
    height: 100%;
    max-height: none;
    /* Permite que la card se contraiga si su contenido natural fuera mayor
       (no debería pasar gracias al scroll interno, pero es defensivo). */
    min-height: 0;
}

/* Hijos directos que NO deben crecer ni encogerse — caben con su tamaño
   natural y la zona scrollable se queda con el resto. Si una página añade
   un nuevo bloque "header-like" (banner, chips, breadcrumb), añadir su
   selector aquí. */
.table-card.table-card--fill > .table-caption,
.table-card.table-card--fill > .tab-bar,
.table-card.table-card--fill > .pagination-bar,
.table-card.table-card--fill > .table-toolbar,
.catalog-card.catalog-card--fill > .catalog-caption,
.catalog-card.catalog-card--fill > .toolbar,
.catalog-card.catalog-card--fill > .active-chips,
.catalog-card.catalog-card--fill > .cart-lock-banner,
.catalog-card.catalog-card--fill > .pagination-bar {
    flex-shrink: 0;
}

/* Zona scrollable: tabla (.table-scroll) o rejilla de tarjetas
   (.items-grid en catálogo). `min-height: 0` es la clave que permite que
   un hijo flex pueda contraerse por debajo de su tamaño de contenido y
   activar el overflow interno. */
.table-card.table-card--fill > .table-scroll,
.catalog-card.catalog-card--fill > .table-scroll,
.catalog-card.catalog-card--fill > .items-grid {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
    overflow-y: auto;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* La rejilla de tarjetas no necesita scroll horizontal y suele llevar
   padding interior; sólo activamos el scroll vertical. */
.catalog-card.catalog-card--fill > .items-grid {
    overflow-x: hidden;
}

/* Cabecera de tabla sticky para que las columnas se lean siempre durante el
   scroll interno. Z-index 2 para quedar por encima de `.bc-doc-tag` u otros
   pills de la primera fila. */
.table-card.table-card--fill .crm-table thead th,
.catalog-card.catalog-card--fill .crm-table thead th,
.admin-page--fill .users-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    /* El thead lleva fondo gris en todas las páginas; lo declaro aquí para
       garantizar que al hacer sticky no se vea contenido de fondo a través. */
    background: var(--color-bg);
}


/* ── Páginas admin que ocupan el alto del viewport ────────────────────────
   Pendant del patrón table-card--fill para Users.razor y Configuration.razor,
   que tienen una estructura distinta (header + tabs sueltos + wrapper) en
   lugar de una sola card. El wrapper marcado con .admin-page--fill se
   convierte en flex column de 100% de alto, deja la cabecera y las tabs
   con su tamaño natural y reserva el resto para el .users-table-wrapper
   marcado con .users-table-wrapper--fill, que scrollea internamente. */
.admin-page.admin-page--fill {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.admin-page--fill > .admin-header,
.admin-page--fill > .admin-tabs {
    flex-shrink: 0;
}

.users-table-wrapper.users-table-wrapper--fill {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}


/* ── Container queries — reactividad por contenedor (v0.46.0+) ──────────────
   El sidebar puede estar expandido (256 px) o colapsado (70 px), lo que cambia
   ~186 px del ancho útil del contenido a viewport idéntico. Las media queries
   sobre el viewport no lo distinguen. Marcamos el área de contenido como
   container y exponemos breakpoints de TABLA/CARD que reaccionan al ancho real
   del contenedor, no al viewport. Soporte: Safari ≥16, Chrome ≥105, Firefox
   ≥110 (≈97 % usuarios). Navegadores antiguos ignoran las @container y caen al
   tamaño base — sin regresiones. */
.layout__content {
    container-type: inline-size;
    container-name: content;
}

/* Cuando el contenedor de contenido es muy estrecho (≤720 px de ancho real,
   con sidebar colapsado o sin él), las cards laterales pasan a una sola columna
   automáticamente sin tocar cada `.razor.css`. */
@container content (max-width: 720px) {
    .responsive-grid--auto {
        grid-template-columns: 1fr !important;
    }
}

/* Layout con grid responsive automático para fichas y dashboards.
   Las cards adoptan tantas columnas como permita el contenedor. */
.responsive-grid {
    display: grid;
    gap: var(--space-content);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
}
.responsive-grid--narrow {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
}
.responsive-grid--wide {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr));
}


/* ── Z-index de modales legacy (v0.46.0+) ──────────────────────────────────
   Patrón unificado para los modales que aún no han migrado a ModalShell
   (CustomerList, Users, OrderTemplateDetail, ApplyOrderTemplate,
   NewShoppingCart, ShoppingCartDetailModal, CatalogItemDetailModal,
   IncomingShareModal, OrderDetailModal, DeliveryNoteDetailModal,
   InvoiceDetailModal). Mantienen su CSS scoped propio pero heredan el
   comportamiento de panel responsive. La regla sólo aplica reset de
   max-height a dvh; cada componente sigue manejando width/posicionamiento. */
.modal-panel {
    max-height: 90vh;
    max-height: min(90dvh, calc(var(--app-100vh) - 24px));
}
@media (max-width: 767.98px) {
    .modal-panel {
        max-height: 100vh;
        max-height: var(--app-100vh);
    }
}

/* ── Support ticket status pills (módulo de soporte v0.95.0) ────────────────
   Globales porque las usan 3 componentes: SupportPanel, SupportTicketDetailModal
   y la página Admin/Support. Color-coded por estado (paleta --color-*). */
.support-pill {
    display: inline-block;
    padding: 2px 12px;
    border-radius: 20px;
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
}
.support-pill--pending  { background: rgba(224, 155, 48, 0.16); color: #9A6712; }
.support-pill--inreview { background: rgba(45, 90, 142, 0.14); color: var(--color-primary); }
.support-pill--resolved { background: rgba(46, 158, 90, 0.16); color: #1E7A42; }
.support-pill--closed   { background: rgba(107, 113, 134, 0.16); color: var(--color-text-secondary); }

/* ── Dato sensible enmascarado ──────────────────────────────────────────────
   El backend redacta el valor (nunca envía el dato real); aquí se muestra
   difuminado para reforzar visualmente que está oculto para el rol actual. */
.redacted {
    filter: blur(3px);
    user-select: none;
    letter-spacing: 3px;
    color: var(--color-text-secondary);
    cursor: not-allowed;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Visualización de datos (/indicadores y sus tarjetas). Dibujo en Shared/VizSvg.cs.

   Paleta categórica VALIDADA (skill dataviz, validate_palette.js sobre #FFFFFF:
   banda de luminosidad, croma, separación para daltonismo >= 8 y visión normal >= 15).
   El orden es fijo y no se cicla: la 9.ª categoría ya es «Otros», en gris.
   Tres tonos quedan por debajo de 3:1 sobre blanco (aqua, amarillo, rosa): por eso
   hay leyenda, tooltip y tabla siempre — el color nunca va solo.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
    --viz-1: #2f6fbf;  --viz-1-soft: #9cbde6;
    --viz-2: #d9783a;
    --viz-3: #1baf7a;
    --viz-4: #eda100;
    --viz-5: #e87ba4;
    --viz-6: #008300;
    --viz-7: #6250d6;
    --viz-8: #e34948;
    --viz-rest: #b9bfcc;     /* «Otros», «Sin dato»: resto, no identidad */
    --viz-prev: #c3c8d4;     /* periodo comparado: contexto */
    --viz-grid: #eceef4;
    --viz-axis: #8a90a2;
    /* Rampa secuencial de un solo tono (mapa de calor), de claro a oscuro. */
    --viz-s0: #f2f4f8; --viz-s1: #dbe8f8; --viz-s2: #b7d3f6; --viz-s3: #86b6ef;
    --viz-s4: #5598e7; --viz-s5: #2a78d6; --viz-s6: #1c5cab;
    --viz-ease: cubic-bezier(.2, .8, .2, 1);
}

.viz-c1 { fill: var(--viz-1); stroke: var(--viz-1); background: var(--viz-1); }
.viz-c2 { fill: var(--viz-2); stroke: var(--viz-2); background: var(--viz-2); }
.viz-c3 { fill: var(--viz-3); stroke: var(--viz-3); background: var(--viz-3); }
.viz-c4 { fill: var(--viz-4); stroke: var(--viz-4); background: var(--viz-4); }
.viz-c5 { fill: var(--viz-5); stroke: var(--viz-5); background: var(--viz-5); }
.viz-c6 { fill: var(--viz-6); stroke: var(--viz-6); background: var(--viz-6); }
.viz-c7 { fill: var(--viz-7); stroke: var(--viz-7); background: var(--viz-7); }
.viz-c8 { fill: var(--viz-8); stroke: var(--viz-8); background: var(--viz-8); }
.viz-cx { fill: var(--viz-rest); stroke: var(--viz-rest); background: var(--viz-rest); }
.viz-cprev { background: var(--viz-prev); }

.viz { display: block; width: 100%; height: auto; overflow: visible; }
.viz-host { position: relative; }

.viz__grid { stroke: var(--viz-grid); stroke-width: 1; }
.viz__axis-line { stroke: #d5d9e3; stroke-width: 1; }
.viz__axis { font-size: 11px; fill: var(--viz-axis); font-variant-numeric: tabular-nums; }
.viz__axis--strong { fill: var(--color-text-secondary); font-weight: 600; }
.viz__value { font-size: 12.5px; font-weight: 700; fill: var(--color-text); }
.viz__muted { font-size: 11px; fill: var(--viz-axis); font-style: italic; }
.viz__legend-text { font-size: 12px; fill: var(--color-text-secondary); font-weight: 600; }

/* Columnas: crecen desde la base, una tras otra. */
.viz__bar, .viz__seg {
    transform-box: fill-box;
    transform-origin: 50% 100%;
    animation: viz-grow-y .7s var(--viz-ease) both;
    animation-delay: calc(var(--i, 0) * 35ms);
    transition: opacity .15s ease;
    stroke: none;
}
.viz__bar--prev { fill: var(--viz-prev); }

.viz__hit { fill: transparent; cursor: crosshair; }
.viz__band:hover .viz__hit { fill: rgba(47, 111, 191, .06); }
/* Foco: al pasar por un tramo, el resto se apaga. */
.viz:hover .viz__band:not(:hover) .viz__bar,
.viz:hover .viz__band:not(:hover) .viz__seg { opacity: .45; }

/* Área: la línea se traza y el degradado aparece después. */
.viz__line {
    fill: none; stroke: var(--viz-1); stroke-width: 2.25;
    stroke-linejoin: round; stroke-linecap: round;
    stroke-dasharray: 1; stroke-dashoffset: 0;
    animation: viz-draw 1.1s var(--viz-ease) both;
}
.viz__line--prev { stroke: var(--viz-prev); stroke-width: 1.75; stroke-dasharray: none; animation: viz-fade .6s ease both; }
.viz__area { animation: viz-fade 1s ease .35s both; }
.viz__guide { stroke: #aab3c5; stroke-width: 1; opacity: 0; transition: opacity .12s; }
.viz__band:hover .viz__guide { opacity: 1; }
.viz__dot { fill: var(--viz-1); stroke: var(--color-surface); stroke-width: 2.5; }
.viz__dot--hover { opacity: 0; transition: opacity .12s; }
.viz__band:hover .viz__dot--hover { opacity: 1; }
.viz__dot--end { animation: viz-pop .4s var(--viz-ease) 1s both; transform-box: fill-box; transform-origin: center; }

/* Anillo */
.viz-ring { display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap; }
.viz-ring__chart { position: relative; width: 190px; max-width: 100%; flex: 0 0 auto; }
.viz--ring { animation: viz-spin .9s var(--viz-ease) both; }
.viz__ring-track { fill: none; stroke: var(--viz-s0); }
.viz__arc { fill: none; cursor: pointer; transition: opacity .15s; }
.viz--ring:hover .viz__arc:not(:hover) { opacity: .5; }
.viz-ring__center {
    position: absolute; inset: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; text-align: center; pointer-events: none;
}
.viz-ring__value { font-size: 1.55rem; font-weight: 700; line-height: 1.05; color: var(--color-text); }
.viz-ring__label { font-size: .72rem; color: var(--color-text-secondary); max-width: 110px; line-height: 1.2; margin-top: .15rem; }

/* Leyenda: muestra de color + texto en tinta (el texto nunca va teñido). */
.viz-legend { display: flex; flex-wrap: wrap; gap: .3rem 1rem; margin-bottom: .5rem; font-size: .76rem; color: var(--color-text-secondary); }
.viz-legend__item { display: inline-flex; align-items: center; gap: .4rem; }
.viz-swatch { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; display: inline-block; }
.viz-legend--list { list-style: none; margin: 0; padding: 0; flex: 1 1 180px; display: grid; gap: .45rem; }
.viz-legend--list li { display: grid; grid-template-columns: 10px 1fr auto auto; align-items: center; gap: .55rem; font-size: .8rem; }
.viz-legend__label { min-width: 0; color: var(--color-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.viz-legend__value { font-weight: 700; color: var(--color-text); font-variant-numeric: tabular-nums; }
.viz-legend__pct { color: var(--color-text-secondary); font-variant-numeric: tabular-nums; min-width: 3.5em; text-align: right; }

/* Barras horizontales y medidores */
.viz-hbar { display: grid; gap: .55rem; }
.viz-hbar__row {
    display: grid; grid-template-columns: minmax(90px, 34%) 1fr auto;
    align-items: center; gap: .65rem; font-size: .8rem; border-radius: 8px; padding: .1rem .2rem;
    transition: background .15s;
}
.viz-hbar__row:hover { background: var(--color-primary-light); }
.viz-hbar__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text); }
.viz-hbar__track { height: 12px; border-radius: 6px; background: var(--viz-s0); overflow: hidden; }
.viz-hbar__fill {
    display: block; height: 100%; border-radius: 6px;
    background: linear-gradient(90deg, var(--viz-1-soft), var(--viz-1));
    transform-origin: left center;
    animation: viz-grow-x .8s var(--viz-ease) both;
    animation-delay: calc(var(--i, 0) * 50ms);
}
.viz-hbar__fill.is-rest { background: var(--viz-rest); }
.viz-hbar__track { position: relative; }
/* Periodo comparado: una muesca gris donde llegaba la barra entonces (contexto, no protagonista). */
.viz-hbar__mark {
    position: absolute; top: 0; bottom: 0; width: 3px; margin-left: -1.5px;
    border-radius: 2px; background: var(--color-text-secondary); opacity: .55;
}
.viz-hbar__value { display: inline-flex; align-items: baseline; gap: .4rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
.viz-hbar__pct { color: var(--color-text-secondary); font-size: .74rem; }
.viz-hbar--meter .viz-hbar__track { height: 14px; border-radius: 7px; }
.viz-hbar--meter .viz-hbar__fill { background: linear-gradient(90deg, var(--viz-s3), var(--viz-s6)); }

/* Mapa de calor: rejilla HTML (se adapta al ancho sin deformar el texto). */
.viz-heat {
    display: grid; grid-template-columns: minmax(56px, 30%) repeat(var(--cols), minmax(0, 1fr));
    gap: 3px; font-size: .72rem; align-items: center;
}
.viz-heat__col { text-align: center; color: var(--viz-axis); font-size: .68rem; font-variant-numeric: tabular-nums; }
.viz-heat__row { color: var(--color-text-secondary); padding-right: .4rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.viz-heat__cell {
    display: flex; align-items: center; justify-content: center;
    min-height: 30px; border-radius: 6px; font-weight: 600; font-variant-numeric: tabular-nums;
    color: var(--color-text); cursor: default;
    animation: viz-fade .5s ease both; animation-delay: calc(var(--i, 0) * 12ms);
    transition: transform .12s ease, box-shadow .12s ease;
}
.viz-heat--dense .viz-heat__cell { min-height: 22px; border-radius: 4px; }
.viz-heat__cell:hover { transform: scale(1.08); box-shadow: 0 2px 8px rgba(26, 29, 43, .18); position: relative; z-index: 1; }
.viz-heat__cell.s0 { background: var(--viz-s0); color: var(--viz-axis); }
.viz-heat__cell.s1 { background: var(--viz-s1); }
.viz-heat__cell.s2 { background: var(--viz-s2); }
.viz-heat__cell.s3 { background: var(--viz-s3); }
.viz-heat__cell.s4 { background: var(--viz-s4); color: #fff; }
.viz-heat__cell.s5 { background: var(--viz-s5); color: #fff; }
.viz-heat__cell.s6 { background: var(--viz-s6); color: #fff; }
/* Suprimida (< 5): trama, no color — no es «poco», es «no se publica». */
.viz-heat__cell.is-hidden {
    background: repeating-linear-gradient(135deg, #f2f4f8 0 4px, #e3e6ee 4px 6px);
}
.viz-scale { display: flex; align-items: center; gap: 3px; justify-content: flex-end; margin-top: .55rem; font-size: .68rem; color: var(--viz-axis); }
.viz-scale .viz-heat__cell { width: 18px; min-height: 10px; border-radius: 3px; animation: none; }
.viz-scale > span:first-child { margin-right: .3rem; }
.viz-scale > span:last-child { margin-left: .3rem; }

/* Pirámide */
.viz__pbar { animation: viz-grow-x .8s var(--viz-ease) both; animation-delay: calc(var(--i, 0) * 60ms); transition: opacity .15s; stroke: none; }
.viz:hover .viz__pbar:not(:hover) { opacity: .55; }

/* Sparkline de las cifras clave */
.viz-spark { display: block; width: 100%; height: 40px; }
.viz-spark__line { fill: none; stroke: var(--viz-1); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.viz-spark__dot { fill: var(--viz-1); stroke: var(--color-surface); stroke-width: 2; vector-effect: non-scaling-stroke; }

.viz-empty {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .35rem;
    min-height: 140px; border-radius: 12px; background: var(--color-bg);
    color: var(--color-text-secondary); font-size: .82rem;
}

/* Tooltip (lo crea js/charts.js en <body>). */
.viz-tip {
    position: fixed; top: 0; left: 0; z-index: 3000; pointer-events: none;
    display: flex; flex-direction: column; gap: .1rem;
    max-width: 280px; padding: .5rem .7rem; border-radius: 10px;
    background: rgba(26, 29, 43, .92); color: #fff; font-size: .76rem; line-height: 1.35;
    box-shadow: 0 8px 24px rgba(26, 29, 43, .25);
    backdrop-filter: blur(6px);
    opacity: 0; transition: opacity .12s ease;
}
.viz-tip.is-visible { opacity: 1; }
.viz-tip strong { font-size: .8rem; margin-bottom: .1rem; }
.viz-tip span { color: rgba(255, 255, 255, .85); font-variant-numeric: tabular-nums; }

@keyframes viz-grow-y { from { transform: scaleY(0); } }
@keyframes viz-grow-x { from { transform: scaleX(0); } }
@keyframes viz-draw { from { stroke-dashoffset: 1; } }
@keyframes viz-fade { from { opacity: 0; } }
@keyframes viz-pop { from { transform: scale(0); } }
@keyframes viz-spin { from { transform: rotate(-60deg) scale(.9); opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
    .viz *, .viz, .viz-hbar__fill, .viz-heat__cell { animation: none !important; transition: none !important; }
}

@media print {
    .viz *, .viz, .viz-hbar__fill, .viz-heat__cell { animation: none !important; }
    .viz-tip { display: none; }
    .viz-heat__cell, .viz-hbar__fill, .viz-hbar__track, .viz-hbar__mark, .viz-swatch { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}

/* ── Columnas prescindibles en pantallas pequeñas (v0.63.2) ─────────────────
   Una tabla de 7 columnas no cabe en un móvil (iPhone 13 Pro: 390 px) y, como el
   <body> tiene overflow-x: hidden, lo que no cabe se desliza dentro de la tabla o
   se corta. En vez de convertir cada tabla en tarjetas, las columnas secundarias se
   marcan con estas clases (en el <th> Y en sus <td>) y desaparecen donde no caben:
   lo que identifica la fila, su estado y su acción se quedan. Solo para datos que
   también se ven al abrir la fila o que no hacen falta para decidir. */
@media (max-width: 575.98px) {
    .hide-sm { display: none !important; }
}
@media (max-width: 767.98px) {
    .hide-md { display: none !important; }
}

/* ── Red de seguridad para móvil (v0.63.2) ──────────────────────────────────
   El <body> corta lo que desborda (overflow-x: hidden), así que en un móvil lo que
   no cabe NO se ve. Estas reglas evitan los desbordes que comparten todas las
   pantallas; lo propio de cada una vive en su componente. */
@media (max-width: 767.98px) {
    /* Campos: 16 px como mínimo. La raíz baja a 15 px en móvil y 1rem se quedaba en
       15 px: Safari iOS hace zoom al enfocar cualquier campo de menos de 16 px. */
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
    select, textarea {
        font-size: 16px;
        max-width: 100%;
    }
}
@media (max-width: 575.98px) {
    /* Cabecera de página: las acciones pueden encoger y repartirse en varias filas.
       Con flex-shrink: 0 la caja de botones medía lo que su contenido en una línea
       y se salía por la derecha. */
    .page-header__titles { flex: 1 1 100%; }
    .page-header__actions { flex: 1 1 100%; min-width: 0; max-width: 100%; }
    .page-header__actions > * { max-width: 100%; }
    .page-title { overflow-wrap: anywhere; }

    /* Tablas: la cabecera puede partirse en dos líneas (en escritorio va en una) y
       las celdas aprietan el relleno; las palabras sin espacios (correos, IBAN,
       códigos) se parten en vez de ensanchar la tabla. */
    .table thead th { white-space: normal; padding: 0.5rem 0.55rem; }
    .table tbody td { padding: 0.5rem 0.55rem; overflow-wrap: anywhere; }
    /* Una etiqueta con texto largo (el nombre de una ficha) no se partía y ensanchaba
       la tabla: en móvil se parte como el resto del texto. */
    .table tbody td .badge { white-space: normal; overflow-wrap: anywhere; text-align: left; }

    /* Botones con etiqueta larga: que el texto se parta antes que salirse. */
    .btn-primary, .btn-secondary, .btn-danger { max-width: 100%; white-space: normal; text-align: left; }

    /* Pie de los modales: botones a todo el ancho y sin desbordar. */
    .crm-modal-footer { flex-wrap: wrap; }
    /* Cabecera de los modales: un título largo (el nombre de una persona) se parte
       en vez de empujar el botón de cerrar fuera de la pantalla. */
    .crm-modal-header { gap: .5rem; }
    .crm-modal-header h3 { min-width: 0; overflow-wrap: anywhere; }

    /* Contenido enriquecido (comunicados, ayuda): imágenes y tablas pegadas desde
       fuera no pueden ensanchar la página. */
    .post__body--rich img, .post__body--rich iframe, .post__body--rich video { max-width: 100%; height: auto; }
    .post__body--rich table { display: block; max-width: 100%; overflow-x: auto; }
    .post__body--rich pre { white-space: pre-wrap; overflow-wrap: anywhere; }
}

/* ── Tabla que pasa a fichas en el móvil (v0.63.2) ─────────────────────────
   Para tablas cuyas columnas hacen falta TODAS para decidir (no se pueden ocultar con
   hide-sm) y que no caben en 358 px: cada fila se convierte en una ficha y cada
   celda en «ETIQUETA  valor». La etiqueta sale del atributo data-label de la celda;
   una celda sin data-label (las acciones) ocupa la línea entera. */
@media (max-width: 575.98px) {
    .table--stack thead { display: none; }
    .table--stack, .table--stack tbody, .table--stack tr, .table--stack td { display: block; width: 100%; }
    .table--stack tbody tr { padding: .55rem 0; border-bottom: 1px solid var(--color-border); }
    .table--stack tbody tr:last-child { border-bottom: none; }
    .table--stack tbody td { border: none; padding: .2rem .55rem; text-align: left !important; white-space: normal !important; }
    .table--stack tbody td[data-label] { display: flex; gap: .6rem; align-items: baseline; }
    .table--stack tbody td[data-label]::before {
        content: attr(data-label); flex: 0 0 5.5rem;
        font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
        color: var(--color-text-secondary);
    }
}

/* ── Propiedades animables del inicio (v0.65.2) ─────────────────────────────
   Registradas aquí y no en Pages/Home.razor porque Razor toma «<integer>» por una
   etiqueta. Registrarlas es lo que permite ANIMARLAS: la cifra que cuenta (--n), el
   anillo del organigrama que se dibuja (--sweep) y el anillo que gira (--spin). */
@property --n { syntax: '<integer>'; inherits: false; initial-value: 0; }
@property --sweep { syntax: '<percentage>'; inherits: false; initial-value: 100%; }
@property --spin { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
