/* ============================================================================
   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);
}


/* ── Error de carga (Shared/LoadError.razor) ─────────────────────────────────
   Distinto del estado vacío: un fallo de lectura no es «no hay datos». */
.load-error {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem;
    padding: 0.9rem 1rem;
    margin: 0 0 1rem;
    border-radius: 12px;
    background: rgba(204, 51, 51, 0.06);
    border: 1px solid rgba(204, 51, 51, 0.2);
    color: var(--color-text);
}
.load-error__icon { color: var(--color-danger); font-size: 1.6rem; }
.load-error__text { display: flex; flex-direction: column; gap: 0.15rem; flex: 1 1 200px; min-width: 0; font-size: 0.9rem; }
.load-error__text span { color: var(--color-text-secondary); }
.load-error__retry { margin-left: auto; }


/* ── 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: backdrop 1050 / panel 1060. Si algún día hace falta un modal sobre
   otro, usar 1100/1110 (las variantes «stacked» del CRM se retiraron sin uso).
   ───────────────────────────────────────────────────────────────────────── */

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

.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;
}

/* 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); }


/* ── 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;
}

/* 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));
}


/* ── Píldoras de estado de una solicitud (heredadas del soporte del CRM) ─────
   Las usa la lista de ausencias. Color 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); }

/* ── Etiquetas de estado (.badge) ──────────────────────────────────────────
   Una sola definición para todo el portal. Antes la copiaban 27 páginas en su
   <style> (que en un .razor NO es aislado: se aplicaba a toda la app mientras la
   página estaba pintada), cada una con sus medidas, y la misma «Pendiente» salía
   distinta según la pantalla. Pisa la .badge de Bootstrap (texto blanco).
   Variantes: --ok, --warn (alias --pending), --bad, --muted, --info (alias --ett),
   --new (lo recién llegado) y --count (contador redondo en la cabecera de un grupo). */
.badge {
    display: inline-block;
    padding: 0.15rem 0.55rem;
    border-radius: 20px;
    font-size: 0.72rem;
    font-weight: 700;
    background: rgba(107, 113, 134, 0.15);
    background: color-mix(in srgb, var(--color-text-secondary) 15%, transparent);
    color: var(--color-text-secondary);
}
.badge--ok {
    background: rgba(46, 158, 90, 0.15);
    background: color-mix(in srgb, var(--color-success) 15%, transparent);
    color: var(--color-success);
}
/* El naranja de la paleta sobre su propio tinte no llega al contraste mínimo: el
   texto va oscurecido a partir del mismo color. */
.badge--warn, .badge--pending {
    background: rgba(224, 155, 48, 0.18);
    background: color-mix(in srgb, var(--color-warning) 18%, transparent);
    color: #9a6412;
    color: color-mix(in srgb, var(--color-warning) 62%, #000);
}
.badge--bad {
    background: rgba(204, 51, 51, 0.15);
    background: color-mix(in srgb, var(--color-danger) 15%, transparent);
    color: var(--color-danger);
}
.badge--muted {
    background: var(--color-border);
    color: var(--color-text-secondary);
}
.badge--info, .badge--ett {
    background: rgba(45, 90, 142, 0.12);
    background: color-mix(in srgb, var(--color-primary) 12%, transparent);
    color: var(--color-primary);
}
.badge--new {
    background: var(--color-accent);
    color: #fff;
    letter-spacing: 0.02em;
}
.badge--count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 7px;
    background: var(--color-primary);
    color: var(--color-on-primary);
    font-size: 0.75rem;
}

/* ── 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; overflow: visible; }
.viz__ring-track { fill: none; stroke: var(--viz-s0); }
/* Porción: color + luz común encima (degradado translúcido) + brillo en el borde. */
.viz__arc { cursor: pointer; transition: opacity .15s, transform .15s var(--viz-ease); transform-origin: 110px 110px; }
.viz__arc > path:not(.viz__gloss) { stroke: none; }
.viz__arc:hover { transform: scale(1.035); }
.viz--ring:hover .viz__arc:not(:hover) { opacity: .5; }
.viz__gloss { fill: none; stroke: #fff; stroke-opacity: .32; stroke-linecap: round; pointer-events: none; }
.viz__ring-hole { stroke: var(--viz-grid); stroke-width: 1; }
/* El del periodo comparado: más pequeño y aclarado (contexto, no protagonista). */
.viz--ring-prev { opacity: .62; }
.viz-ring__chart--prev { width: 138px; }
.viz-ring__chart--prev .viz-ring__value { font-size: 1.1rem; color: var(--color-text-secondary); }
.viz-ring__chart--prev .viz-ring__label { font-size: .64rem; max-width: 80px; }
.viz-ring__caption { display: block; text-align: center; font-size: .72rem; color: var(--color-text-secondary); margin-top: .35rem; line-height: 1.2; }
.viz-ring__caption--now { color: var(--color-text); font-weight: 700; }
.viz-ring__pair { display: flex; align-items: flex-end; gap: 1rem; flex: 0 0 auto; flex-wrap: wrap; }
.viz-ring__fig { margin: 0; display: flex; flex-direction: column; align-items: center; }
.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; }
/* Leyenda común de los dos anillos: ahora, peso, comparado y variación por porción. */
.viz-legend--cmp { flex-basis: 300px; }
.viz-legend--cmp li { grid-template-columns: 10px minmax(0, 1fr) auto auto auto auto; }
.viz-legend--cmp .viz-legend__head { font-size: .66rem; font-weight: 700; color: var(--color-text-secondary); text-transform: uppercase; letter-spacing: .03em; }
.viz-legend--cmp .viz-legend__head span { text-align: right; }
.viz-legend__prev { color: var(--color-text-secondary); font-variant-numeric: tabular-nums; min-width: 2.5em; text-align: right; }
.viz-legend__delta { color: var(--color-text); font-variant-numeric: tabular-nums; min-width: 5.5em; text-align: right; font-size: .76rem; }

/* 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; }

/* Comparativa de pocos tramos: barras horizontales pareadas desde cero. */
.viz__hb { transform-box: fill-box; transform-origin: left center; animation: viz-grow-x .8s var(--viz-ease) both; animation-delay: calc(var(--i, 0) * 80ms); }
.viz__pair-label { font-size: 15px; fill: var(--color-text-secondary); }
.viz__pair-label.is-now { fill: var(--color-text); font-weight: 700; }
.viz__pair-value { font-size: 15px; fill: var(--color-text-secondary); font-variant-numeric: tabular-nums; }
.viz__pair-value.is-now { fill: var(--color-text); font-weight: 700; font-size: 16px; }
.viz__delta { font-size: 14px; font-weight: 600; fill: var(--color-text); font-variant-numeric: tabular-nums; }
.viz:hover .viz__band:not(:hover) .viz__hb { opacity: .45; }

/* Serie a cero en los dos periodos: una línea, no unos ejes vacíos. */
.viz-quiet {
    display: flex; align-items: center; justify-content: center; gap: .4rem;
    min-height: 2.4rem; padding: .35rem .8rem; border-radius: 999px; background: var(--color-bg);
    color: var(--color-text-secondary); font-size: .8rem;
}
.viz-quiet .material-symbols-outlined { font-size: 1.05rem; }

/* 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-spark--cmp { overflow: visible; }

.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; }
    .viz__arc:hover { transform: none; }
}

@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; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Tarjetas KPI (/indicadores, modo «KPIs»): semáforo y mini gráficos. El marco de la
   tarjeta está en Shared/KpiWidget.razor.css; el marcado, en Shared/KpiViz.cs.

   Colores de ESTADO, reservados: no son ninguna serie ni se usan para nada más (paleta
   de estado de la skill dataviz, distinta de la categórica). Siempre acompañados de
   icono y texto («En objetivo», «Cerca del límite», «Fuera de objetivo»): el ámbar no
   llega a 3:1 sobre blanco y el verde y el rojo se confunden con daltonismo.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
    --kpi-ok: #0ca30c;   --kpi-ok-soft: #e7f5e7;
    --kpi-warn: #fab219; --kpi-warn-soft: #fff4dc; --kpi-warn-icon: #c98500;
    --kpi-bad: #d03b3b;  --kpi-bad-soft: #fbe9e9;
}

/* Control segmentado (dos o tres opciones excluyentes a la vista): «Informe | KPIs» en
   /indicadores y el formato del PDF en el panel de IA. Botones con role="radio". */
.seg {
    display: inline-flex; padding: 3px; gap: 2px; border-radius: 10px;
    background: var(--color-bg); border: 1px solid var(--color-border);
}
.seg__btn {
    display: inline-flex; align-items: center; gap: .3rem;
    padding: .32rem .8rem; border: 0; border-radius: 8px; background: transparent;
    font-size: .82rem; font-weight: 600; color: var(--color-text-secondary); cursor: pointer;
    transition: background .15s ease, color .15s ease, box-shadow .15s ease;
}
.seg__btn .material-symbols-outlined { font-size: 1.05rem; }
.seg__btn:hover:not(.is-active):not(:disabled) { color: var(--color-text); background: rgba(45, 90, 142, .06); }
.seg__btn.is-active { background: var(--color-surface); color: var(--color-primary); box-shadow: 0 1px 3px rgba(26, 29, 43, .14); }
.seg__btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.seg__btn:disabled { opacity: .55; cursor: default; }

/* Rejilla de tarjetas: 1 columna en el móvil estrecho, 2 en tableta, las que quepan. */
.kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr));
    gap: .85rem;
}
@media (min-width: 480px) and (max-width: 767.98px) {
    .kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* «Pendiente de leer de a3» / «Sin casos»: una línea, no unos ejes vacíos. */
.kpi-pill {
    display: flex; align-items: center; gap: .35rem;
    padding: .4rem .65rem; border-radius: 10px; background: var(--color-bg);
    color: var(--color-text-secondary); font-size: .74rem; line-height: 1.3;
}
.kpi-pill .material-symbols-outlined { font-size: 1rem; flex: 0 0 auto; }

/* Sparkline con el comparado discontinuo. */
.kpi-spark__wrap { position: relative; height: 52px; }
.kpi-spark { display: block; width: 100%; height: 52px; overflow: visible; }
.kpi-spark__line {
    fill: none; stroke: var(--viz-1); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round;
    vector-effect: non-scaling-stroke;
    /* Sin el trazado animado de .viz__line: con non-scaling-stroke el pathLength no
       escala los guiones y el final de la línea se quedaba sin pintar. */
    animation: viz-fade .6s ease .1s both;
}
.kpi-spark__area { animation: viz-fade .8s ease .4s both; }
.kpi-spark__prev {
    fill: none; stroke: var(--viz-axis); stroke-width: 1.5; stroke-dasharray: 4 3;
    vector-effect: non-scaling-stroke; animation: viz-fade .6s ease both;
}
.kpi-spark__hit { fill: transparent; }
.kpi-spark__hit:hover { fill: rgba(47, 111, 191, .07); }
.kpi-spark__dot {
    position: absolute; width: 8px; height: 8px; margin: -4px 0 0 -4px; border-radius: 50%;
    background: var(--viz-1); box-shadow: 0 0 0 2px var(--color-surface);
    animation: viz-pop .35s var(--viz-ease) .9s both; pointer-events: none;
}
.kpi-spark__axis {
    display: flex; justify-content: space-between; align-items: center; gap: .5rem;
    margin-top: .2rem; font-size: .66rem; color: var(--viz-axis); font-variant-numeric: tabular-nums;
}
.kpi-spark__axis > span:first-child, .kpi-spark__axis > span:last-child { flex: 0 0 auto; white-space: nowrap; }
.kpi-spark__legend { display: inline-flex; align-items: center; gap: .3rem; min-width: 0; overflow: hidden; white-space: nowrap; }
.kpi-key { display: inline-block; width: 14px; height: 0; border-top: 2px solid var(--viz-1); }
.kpi-key--prev { border-top: 2px dashed var(--viz-axis); margin-left: .35rem; }

/* Columnas de dos tramos y mini comparativa de un tramo. */
.kpi-cols { display: block; width: 100%; height: auto; max-height: 72px; overflow: visible; }
.kpi-col { fill: var(--viz-1); transform-box: fill-box; transform-origin: 50% 100%; animation: viz-grow-y .6s var(--viz-ease) both; animation-delay: calc(var(--i, 0) * 60ms); }
.kpi-col--prev { fill: var(--viz-prev); }
.kpi-col__value { font-size: 10.5px; font-weight: 700; fill: var(--color-text); font-variant-numeric: tabular-nums; }
.kpi-col__label { font-size: 10px; fill: var(--viz-axis); }
.kpi__mini--compare .viz-spark--cmp { height: 40px; max-width: 260px; }

/* Anillo pequeño con sus tres porciones mayores. */
.kpi-ring { display: flex; align-items: center; gap: .75rem; }
.kpi-ring__chart { width: 74px; flex: 0 0 auto; }
.kpi-ring__legend { list-style: none; margin: 0; padding: 0; display: grid; gap: .2rem; flex: 1 1 auto; min-width: 0; font-size: .72rem; }
.kpi-ring__legend li { display: grid; grid-template-columns: 10px minmax(0, 1fr) auto; align-items: center; gap: .4rem; }
.kpi-ring__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text); }
.kpi-ring__pct { color: var(--color-text-secondary); font-variant-numeric: tabular-nums; }
.kpi-more { display: block; font-size: .68rem; color: var(--color-text-secondary); }

/* Reparto grande: cuatro filas. */
.kpi-bars { display: grid; gap: .3rem; }
.kpi-bars__row { display: grid; grid-template-columns: minmax(0, 38%) 1fr auto; align-items: center; gap: .45rem; font-size: .72rem; }
.kpi-bars__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-text); }
.kpi-bars__track { position: relative; height: 8px; border-radius: 4px; background: var(--viz-s0); }
.kpi-bars__fill {
    display: block; height: 100%; border-radius: 4px; background: var(--viz-1);
    transform-origin: left center; animation: viz-grow-x .7s var(--viz-ease) both; animation-delay: calc(var(--i, 0) * 50ms);
}
.kpi-bars__fill.is-rest { background: var(--viz-rest); }
.kpi-bars__mark { position: absolute; top: -2px; bottom: -2px; width: 2px; margin-left: -1px; border-radius: 1px; background: var(--color-text-secondary); opacity: .6; }
.kpi-bars__value { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--color-text); }

/* Mapa de calor sin rótulos. */
.kpi-heat { display: grid; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); gap: 2px; }
.kpi-heat__c { display: block; min-height: 9px; aspect-ratio: 1.6; max-height: 16px; border-radius: 2px; animation: viz-fade .5s ease both; }
.kpi-heat__c.s0 { background: var(--viz-s0); }
.kpi-heat__c.s1 { background: var(--viz-s1); }
.kpi-heat__c.s2 { background: var(--viz-s2); }
.kpi-heat__c.s3 { background: var(--viz-s3); }
.kpi-heat__c.s4 { background: var(--viz-s4); }
.kpi-heat__c.s5 { background: var(--viz-s5); }
.kpi-heat__c.s6 { background: var(--viz-s6); }
.kpi-heat__c.is-hidden { background: repeating-linear-gradient(135deg, #f2f4f8 0 3px, #e3e6ee 3px 5px); }

@media (prefers-reduced-motion: reduce) {
    .kpi-spark__line, .kpi-spark__area, .kpi-spark__prev, .kpi-spark__dot,
    .kpi-col, .kpi-bars__fill, .kpi-heat__c { animation: none !important; }
}

@media print {
    .kpi-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .5rem; }
    .kpi-spark__line, .kpi-spark__area, .kpi-spark__prev, .kpi-spark__dot,
    .kpi-col, .kpi-bars__fill, .kpi-heat__c { animation: none !important; }
    .kpi-spark__dot, .kpi-bars__fill, .kpi-bars__track, .kpi-heat__c, .kpi-key {
        -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.
       `!important` a propósito: es una red de seguridad y tiene que ganar a las clases
       compactas de cada pantalla (.form-control-sm, .form-control--inline…) y a un
       font-size en línea. Para un campo más pequeño en escritorio, se pone ahí; en el
       móvil no se negocia. */
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
    select, textarea {
        font-size: 16px !important;
        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; }
