/* ==============================================================================
   CRM-GREU — Components
   Estructuras reutilizables (cards, tables, badges, alerts, buttons, pagination,
   modal, toast, drawer, tabs) viven ahora en components/*.css — este archivo solo
   conserva lo verdaderamente común/transversal y estilos específicos de
   features/páginas que aún no tienen hueco propio.
   Los 10 archivos de components/ NO se importan aquí (un @import no lleva
   query de versión, así que un cambio en, p.ej., cards.css no invalidaría su
   caché): se cargan como <link> independientes con asset() justo antes de
   este archivo — ver layouts/app.php y las páginas de auth/públicas.
   ============================================================================== */

.text-danger {
    color: var(--color-danger, #e53e3e);
}

.text-danger:hover {
    color: var(--color-danger-hover, #c53030);
}

/* --- Empty State ----------------------------------------------------------- */
.empty-state {
    text-align: center;
    padding: 48px 24px;
    color: var(--color-muted);
}

.empty-state i {
    font-size: 48px;
    margin-bottom: 12px;
    opacity: 0.4;
}

.empty-state h4 {
    font-size: 15px;
    font-weight: 500;
    margin-bottom: 6px;
    color: var(--color-text);
}

.empty-state p {
    font-size: 13px;
    max-width: 320px;
    margin: 0 auto;
}

.form-section {
    margin-bottom: 24px;
}

.form-section:last-child {
    margin-bottom: 0;
}

.form-section-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-bottom: 12px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--color-border-soft);
}

/* --- Search Bar (card header) ---------------------------------------------- */
.search-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--color-bg);
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-sm);
    padding: 7px 14px;
    width: 240px;
    transition: border-color 0.2s;
}

.search-bar:focus-within {
    border-color: var(--color-accent);
}

.search-bar i {
    color: var(--color-muted);
    font-size: 15px;
    flex-shrink: 0;
}

.search-bar input {
    border: none;
    background: transparent;
    outline: none;
    font-family: var(--font-family);
    font-size: 13px;
    color: var(--color-text);
    width: 100%;
}

.search-bar input::placeholder {
    color: var(--color-muted);
}

/* --- Bottom Sheet Modal ---------------------------------------------------- */
.sheet-overlay {
    position: fixed;
    inset: 0;
    background: rgba(7, 20, 38, 0.4);
    backdrop-filter: blur(4px);
    z-index: 500;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    opacity: 1;
    transition: opacity 0.2s ease;
}

.sheet-overlay.hidden {
    display: none;
}

.sheet-container {
    background: var(--color-surface-strong);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    box-shadow: var(--shadow-sheet);
    width: 100%;
    max-width: 720px;
    height: 85vh;
    display: flex;
    flex-direction: column;
    animation: sheetScaleIn 0.38s cubic-bezier(.16, 1, .3, 1);
    overflow: hidden;
}

/* Trayectoria "scale" (default) — mismo criterio que modalScaleIn en
   modal.css: crece mientras asciende, sin overshoot. */
@keyframes sheetScaleIn {
    from { transform: translateY(100%) scale(.85); opacity: 0; }
    to   { transform: translateY(0) scale(1); opacity: 1; }
}

/* Trayectoria "genie" — mismo criterio que modalGenieIn en modal.css:
   silueta irregular (asimetría deliberada, nunca un trapecio perfecto), no
   scaleX/scaleY ni mask-image/humo (descartado, ver arbitraje en modal.css). */
html[data-motion="genie"] .sheet-container {
    animation: sheetGenieIn 0.42s cubic-bezier(.22, 1, .36, 1);
}
@keyframes sheetGenieIn {
    0%   { clip-path: polygon(44% 100%, 49% 99%, 53% 100%, 50% 98%); transform: translateY(40%); opacity: 0; }
    30%  { clip-path: polygon(18% 62%, 36% 46%, 60% 50%, 78% 42%, 88% 68%, 72% 100%, 38% 96%, 14% 100%); transform: translateY(20%); opacity: .8; }
    60%  { clip-path: polygon(2% 18%, 42% 8%, 98% 16%, 100% 55%, 100% 100%, 0% 100%, 0% 58%); transform: translateY(6%); opacity: 1; }
    100% { clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%); transform: translateY(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .sheet-overlay {
        transition: none;
    }
    .sheet-container {
        animation: none;
        transform: none;
    }
}

.sheet-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 18px 24px;
    border-bottom: 1px solid var(--color-border-soft);
    flex-shrink: 0;
}

.sheet-header h3 {
    font-size: 16px;
    font-weight: 600;
}

.sheet-close {
    background: none;
    border: none;
    font-size: 20px;
    color: var(--color-muted);
    cursor: pointer;
    padding: 4px;
    border-radius: var(--radius-xs);
    transition: background 0.15s;
}

.sheet-close:hover {
    background: var(--color-bg);
    color: var(--color-text);
}

.sheet-body {
    padding: 24px;
    overflow-y: auto;
    flex: 1;
}

.sheet-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    padding: 16px 24px;
    border-top: 1px solid var(--color-border-soft);
    background: var(--color-bg);
    flex-shrink: 0;
}

/* --- Detail Grid (view mode) ----------------------------------------------- */
.detail-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
}

.detail-row {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.detail-row:has(.detail-actions) {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 4px 12px;
    align-items: center;
    padding: 6px 0;
}

.detail-label {
    font-size: 11px;
    font-weight: 600;
    color: var(--color-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.detail-value {
    font-size: 13px;
    color: var(--color-text);
}

.detail-actions {
    margin-left: auto;
    display: flex;
    gap: 4px;
}

/* Widget "Contactos" de Worker 360 (Ficha) — tarjeta por contacto:
   etiqueta (nombre) + una línea por dato de contacto con su icono, en vez
   de una sola línea "cargo · teléfono · email" separada por puntos. */
.worker-contacts-list {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.worker-contact-card {
    padding-bottom: 12px;
    border-bottom: 1px solid var(--color-border-soft);
}

.worker-contact-card:last-child {
    padding-bottom: 0;
    border-bottom: none;
}

.worker-contact-name {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text);
    margin-bottom: 4px;
}

.worker-contact-line {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    color: var(--color-text-muted);
    line-height: 1.6;
}

.worker-contact-line i {
    color: var(--color-muted);
    font-size: 14px;
    flex-shrink: 0;
}

/* --- Autocomplete / Suggestions List --------------------------------------- */
.autocomplete-wrapper {
    position: relative;
}

.suggestions-list {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 50;
    list-style: none;
    margin: 2px 0 0 0;
    padding: 4px 0;
    background: var(--color-surface-strong);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-panel);
    max-height: 200px;
    overflow-y: auto;
}

.suggestions-list li {
    padding: 8px 14px;
    font-size: 13px;
    cursor: pointer;
    transition: background 0.1s;
}

.suggestions-list li:hover,
.suggestions-list li.active {
    background: var(--color-bg);
    color: var(--color-primary);
}

.suggestions-list li small {
    display: block;
    font-size: 11px;
    color: var(--color-muted);
    margin-top: 1px;
}

/* Validation feedback */
.validation-feedback {
    font-size: 11px;
    margin-top: 4px;
    display: block;
}

.validation-feedback.text-success {
    color: var(--color-success);
}

.validation-feedback.text-danger {
    color: var(--color-danger);
}

.is-valid {
    border-color: var(--color-success) !important;
}

.is-invalid {
    border-color: var(--color-danger) !important;
}

/* Tax ID (CIF/NIF/NIE/DNI) auto-detected type badge — see
   CRM.validators.initTaxIdRequiredField() in apps/shared/js/validators.js,
   used by the parties "Datos fiscales" form section. */
.tax-id-badge {
    display: inline-block;
    font-size: 11px;
    font-weight: 600;
    margin-top: 6px;
    padding: 2px 8px;
    border-radius: var(--radius-sm, 4px);
}

.tax-id-badge:empty {
    display: none;
}

.tax-id-badge-valid {
    background: var(--color-success-bg);
    color: var(--color-success);
    border: 1px solid var(--color-success-border);
}

.tax-id-badge-invalid {
    background: var(--color-danger-bg);
    color: var(--color-danger);
    border: 1px solid var(--color-danger-border);
}

/* --- Hidden ---------------------------------------------------------------- */
.hidden { display: none !important; }

/* --- Utility --------------------------------------------------------------- */
.text-left { text-align: left !important; }
.text-right { text-align: right !important; }
.text-center { text-align: center !important; }

.text-muted { color: var(--color-muted); }
.text-sm { font-size: 12px; }

.icon-warning { color: var(--color-warning); }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); }
.flex {
    display: flex;
}

.gap-2 {
    gap: 8px;
}

.search-box {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    border: 1px solid var(--color-border);
    border-radius: 6px;
    background: var(--color-surface);
    min-width: 240px;
}

.search-box i {
    font-size: 16px;
    color: var(--color-muted);
}

.search-box input {
    border: none;
    background: transparent;
    outline: none;
    width: 100%;
    font-size: 14px;
}
.flex-col { display: flex; flex-direction: column; }
.flex-start { display: flex; justify-content: flex-start; align-items: center; }
.flex-between { display: flex; justify-content: space-between; align-items: center; }
.flex-center { display: flex; justify-content: center; }

.gap-2 { gap: 8px; }
.gap-3 { gap: 12px; }
.gap-4 { gap: 16px; }

.mr-1 { margin-right: 6px; }
.mb-2 { margin-bottom: 8px; }
.mb-3 { margin-bottom: 12px; }
.mb-5 { margin-bottom: 20px; }
.mt-2 { margin-top: 8px; }
.mt-3 { margin-top: 12px; }
.mt-5 { margin-top: 20px; }

.p-2 { padding: 8px; }
.py-2 { padding: 8px 0; }
.px-2 { padding: 0 8px; }
.p-3 { padding: 12px; }
.py-3 { padding: 12px 0; }
.px-3 { padding: 0 12px; }
.p-4 { padding: 20px; }
.py-4 { padding: 20px 0; }
.px-4 { padding: 0 20px; }

.form-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 20px;
    padding-top: 16px;
    border-top: 1px solid var(--color-border-soft);
}

/* --- Keypad (chip-based multi-select) -------------------------------------- */
.keypad {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.keypad-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.keypad-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 16px;
    border: 1.5px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface-strong);
    color: var(--color-text);
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s;
    user-select: none;
}

.keypad-chip:hover {
    border-color: var(--color-accent);
    background: var(--color-accent-soft);
}

.keypad-chip.selected {
    border-color: var(--color-accent);
    background: var(--color-accent);
    color: #fff;
}

.keypad-chip.selected:hover {
    background: var(--color-accent-hover, var(--color-accent));
    opacity: 0.9;
}

/* data-readonly (ver asset-edit-form.php, keypad de Estado operativo sin
   permiso assets.update_status): mismo marcado que un keypad normal, pero
   sin afordancia de clic — el guard real está en el JS (Keypad chip
   toggle, app.js) y en el servidor; esto es solo la señal visual. */
.keypad[data-readonly] .keypad-chip {
    cursor: default;
    opacity: 0.75;
}
.keypad[data-readonly] .keypad-chip:hover {
    border-color: var(--color-border);
    background: var(--color-surface-strong);
}
.keypad[data-readonly] .keypad-chip.selected:hover {
    background: var(--color-accent);
    opacity: 1;
}

/* Fila de radio simple (ej. elegir email en el modal "Enviar contraseña
   por email", ver workers.js openPasswordResetModal()) — una opción por
   línea, sin componente FormUI dedicado para esto todavía. */
.radio-option {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 0;
    font-size: 14px;
    cursor: pointer;
}
.radio-option input[type="radio"] {
    width: 16px;
    height: 16px;
    cursor: pointer;
}

/* Asistente "Enviar documentación" (send-documentation-modal.js) — vive en
   la hoja compartida porque su markup (#send-documentation-container) está
   en el layout global, pensado para usarse desde cualquier página *-360,
   no solo Worker 360. */
.dw-step-indicator {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--color-muted);
    margin-bottom: 14px;
}

.dw-template-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 8px;
}

.dw-template-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 10px 14px;
    border: 1px solid var(--color-border-soft);
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.15s;
}
.dw-template-card:hover {
    background: var(--color-bg);
}
.dw-template-card.selected {
    border-color: var(--color-primary);
    background: var(--color-bg);
}
.dw-template-card-name {
    font-size: 14px;
    font-weight: 500;
}

.dw-editor-mount {
    min-height: 260px;
}
/* El botón "Insertar variable" de VariableRichEditor se registra siempre en
   la barra de herramientas, con o sin variables — aquí no hay ninguna
   (generated_documents.html_content ya se guarda con todas resueltas), así
   que sin handler no haría nada; se oculta en vez de dejarlo muerto. */
.dw-editor-mount .ql-insertVariable {
    display: none;
}

/* Chips con color propio (ej. estado laboral del trabajador, ver
   FormUI::keypad() y StatusConstants::workerStatuses()) — mismo vocabulario
   de color que badge-success/warning/danger/info/secondary. El color solo
   se pinta al seleccionarse; no seleccionado queda neutro como cualquier
   otro chip, para no convertir el keypad entero en un semáforo. */
.keypad-chip-success.selected { border-color: var(--color-success); background: var(--color-success); color: #fff; }
.keypad-chip-warning.selected { border-color: var(--color-warning); background: var(--color-warning); color: #fff; }
.keypad-chip-danger.selected { border-color: var(--color-danger); background: var(--color-danger); color: #fff; }
.keypad-chip-info.selected { border-color: var(--color-primary); background: var(--color-primary); color: #fff; }
.keypad-chip-secondary.selected { border-color: var(--color-muted); background: var(--color-muted); color: #fff; }

/* --- Toggle/Checkbox Label ------------------------------------------------- */
.toggle-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-text);
    user-select: none;
}

.toggle-label input[type="checkbox"] {
    width: 16px;
    height: 16px;
    accent-color: var(--color-accent);
    cursor: pointer;
}

/* --- Obligatoriedad documental: matriz inline (dentro de la tabla de
   #doc-types-list, doc-types.js) y lista de "Reglas de documentación"
   (doc-rules.js) — ambas pantallas comparten las clases .doc-req-* porque
   leen/escriben la misma document_requirements, solo cambia el punto de
   entrada (ADR-0021). .doc-type-requirements vive dentro de una <td> (tabla
   plana, sin la card/acordeón de rondas anteriores) — sin borde/margen
   superior propio, y con un ancho máximo para no estirar la columna. */
.doc-type-requirements {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-width: 260px;
}

.doc-types-cell-name {
    font-weight: 600;
}

.doc-req-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
}

.doc-req-entity {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-text-secondary);
    min-width: 90px;
}

/* Resumen de "Obligatorio para" en Tipos documentales (doc-types.js,
   renderRequirementsBlock()) — de SOLO LECTURA, a diferencia de los chips
   .doc-req-chip de más abajo (esos SÍ siguen siendo togglables, los usa
   "Reglas de documentación"). Son <button> (para el data-action de
   navegación), reseteados a la pinta de .badge/.badge-soft/.badge-success
   normales en vez de la de un <button> nativo. */
.doc-req-summary-badge {
    border: none;
    font-family: inherit;
    cursor: pointer;
    transition: filter 0.15s;
}

.doc-req-summary-badge:hover {
    filter: brightness(0.94);
}

.doc-req-other-link {
    background: none;
    border: none;
    padding: 0;
    font-family: inherit;
    font-size: 11.5px;
    color: var(--color-muted);
    text-decoration: underline;
    text-decoration-style: dotted;
    cursor: pointer;
}

.doc-req-other-link:hover {
    color: var(--color-primary);
}

.doc-req-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.doc-req-chip {
    padding: 3px 10px;
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-full);
    background: var(--color-surface);
    color: var(--color-text-secondary);
    font-size: 11.5px;
    cursor: pointer;
    transition: all 0.15s;
}

.doc-req-chip:hover {
    border-color: var(--color-primary);
    color: var(--color-primary);
}

.doc-req-chip.active {
    background: var(--color-primary);
    border-color: var(--color-primary);
    color: #fff;
}

/* Grupos de alternativas (V166, Reglas de documentación → "Agrupar como
   alternativos") — el chip normal no cambia de tamaño/posición al ganar
   estos estados, solo se le añade un icono al lado (.doc-req-chip-wrap). */
.doc-req-chip-wrap {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}

.doc-req-chip.is-grouped {
    border-color: var(--color-accent);
}

.doc-req-chip.is-selected-for-group {
    outline: 2px solid var(--color-accent);
    outline-offset: 1px;
}

.doc-req-chip-select {
    font-size: 13px;
    color: var(--color-border);
    cursor: pointer;
    transition: color 0.15s;
}

.doc-req-chip-select:hover {
    color: var(--color-accent);
}

.doc-req-chip-select.selected {
    color: var(--color-accent);
}

.doc-req-chip-group {
    font-size: 13px;
    color: var(--color-accent);
    cursor: pointer;
}

.doc-req-chip-group:hover {
    color: var(--color-danger);
}

/* Modal "Obligatorio para" (doc-types.js, openRequirementsTreeModal()) —
   árbol de categorías con checkbox tri-estado (checked/unchecked/
   indeterminate) y cascada padre→descendientes al togglear. Reutiliza
   .catalog-tree-guide (config-catalog.css) para las marcas "│" de
   profundidad, mismo criterio visual que la vista árbol de Catálogos. */
.doc-req-tree-all {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 10px;
    margin-bottom: 10px;
    border-bottom: 1px solid var(--color-border-soft);
    cursor: pointer;
    font-size: 13.5px;
}

.doc-req-tree {
    max-height: 50vh;
    overflow-y: auto;
}

/* "Todos" activo: el árbol entero es redundante (ya cubierto por la regla
   comodín) — atenuado y con los checkboxes disabled (ver renderTreeModalBody()),
   en vez de ocultarlo del todo, para que se siga viendo QUÉ categorías existen. */
.doc-req-tree-disabled {
    opacity: 0.55;
}

.doc-req-tree-node {
    padding: 3px 0;
}

.doc-req-tree-item {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    cursor: pointer;
}

/* --- Document Summary Bar -------------------------------------------------- */
.doc-summary-bar {
    display: flex;
    gap: 12px;
    margin-bottom: 16px;
    flex-wrap: wrap;
}

.doc-summary-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    border-radius: var(--radius-md);
    border: 1px solid var(--color-border-soft);
    background: var(--color-surface-strong);
    min-width: 120px;
}

.doc-summary-item i { font-size: 18px; }

.doc-summary-count {
    font-size: 20px;
    font-weight: 700;
    line-height: 1;
}

.doc-summary-label {
    font-size: 12px;
    color: var(--color-text-secondary);
}

.doc-summary-item.badge-success { border-left: 3px solid var(--color-success); }
.doc-summary-item.badge-warning { border-left: 3px solid var(--color-warning); }
.doc-summary-item.badge-danger { border-left: 3px solid var(--color-danger); }
.doc-summary-item.badge-soft { border-left: 3px solid var(--color-border); }

.doc-summary-item.badge-success i { color: var(--color-success); }
.doc-summary-item.badge-warning i { color: var(--color-warning); }
.doc-summary-item.badge-danger i { color: var(--color-danger); }
.doc-summary-item.badge-soft i { color: var(--color-text-secondary); }

/* --- Missing Documents Grid ------------------------------------------------ */
.doc-missing-section {
    margin-bottom: 20px;
}

.doc-missing-section h4 {
    font-size: 14px;
    margin-bottom: 12px;
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--color-text-secondary);
}

.doc-missing-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 10px;
}

/* Título de cada sub-sección cuando "Documentos faltantes" se separa en
   Obligatorios/Opcionales (Worker 360 usa dos .tab-card, Machine 360 usa
   esto dentro de una sola — ver AssetTabService::documentacionTab()). */
.doc-missing-section-title {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    margin: 14px 0 8px;
}

.doc-missing-section-title:first-child {
    margin-top: 0;
}

.doc-missing-card {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px;
    background: var(--color-surface);
    border: 1px dashed var(--color-border);
    border-radius: var(--radius-md);
    transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}

/* Destino de drop del explorador (docs-explorer-dnd.js): soltar un
   documento sin clasificar aquí lo asocia directamente a ESTE tipo
   (data-type-id) — mismo verde que las carpetas, distinto del borde
   discontinuo en reposo para que se note que ahora sí es un destino claro. */
.doc-missing-card.drag-over {
    background: var(--color-success-bg, #e6f6ec);
    border-style: solid;
    border-color: var(--color-success);
    transform: translateY(-2px) scale(1.03);
    box-shadow: var(--shadow-soft);
}

/* "Típico prohibido": un documento YA clasificado solo puede soltarse en
   el Baúl — si se arrastra sobre esta ficha igualmente, se marca como NO
   válido para él en vez de ignorarse sin más (ver isValidDropTarget() en
   docs-explorer-dnd.js). */
.doc-missing-card.drag-forbidden {
    cursor: not-allowed;
    background: var(--color-danger-bg);
    border-style: solid;
    border-color: var(--color-danger);
    opacity: 0.4;
}

.doc-missing-icon i {
    font-size: 24px;
    color: var(--color-text-secondary);
}

.doc-missing-info { flex: 1; min-width: 0; }
.doc-missing-info strong { font-size: 13px; display: block; }
.doc-missing-info small { color: var(--color-text-secondary); }
/* Especificidad: sin este selector más específico, la regla genérica de
   arriba (.doc-missing-info small) ganaba siempre a .text-danger — el
   "Obligatorio" en rojo no se veía nunca, daba igual el orden en el fichero. */
.doc-missing-info small.text-danger { color: var(--color-danger); }
/* Icono de "Obligatorio" (ri-shield-star-fill) en los faltantes — hasta
   ahora solo existía por página (worker-360.css/asset-360.css), así que en
   cualquier otro sitio que renderizara faltantes (p.ej. el panel
   Documentación del sheet de edición de Empresa/Tercero/Artículo, ver
   refreshDocsList() en app.js) el icono nunca se veía en rojo. */
.icon-danger { color: var(--color-danger); }

/* --- Documentación compacta (pestaña "Documentación" del sheet de edición
   Trabajador/Empresa/Cliente-proveedor/Artículo — refreshDocsList(), app.js).
   Componente propio, NO reutiliza .doc-card/.doc-missing-*/.doc-summary-*
   (esas siguen en uso por docs-explorer.js/CompanyController.php/Worker 360)
   — diseñado con ui-product-designer + motion-designer + design-critic
   para caber en un modal estrecho: píldoras de filtro sin etiqueta,
   buscador, lista única (subidos+faltantes mezclados por severidad) con
   filas de una línea que se abren al clic. ------------------------------- */

.doc-filter-pills {
    display: flex;
    gap: 6px;
    margin-bottom: 10px;
    flex-wrap: wrap;
}

.doc-filter-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 10px;
    border-radius: var(--radius-full);
    border: 1px solid var(--color-border-soft);
    background: var(--color-surface);
    color: var(--color-text-secondary);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}

.doc-filter-pill i { font-size: 14px; }

.doc-filter-pill-danger i { color: var(--color-danger); }
.doc-filter-pill-warning i { color: var(--color-warning); }
.doc-filter-pill-success i { color: var(--color-success); }
.doc-filter-pill-soft i { color: var(--color-text-secondary); }

.doc-filter-pill.is-active {
    border-color: currentColor;
    background: var(--color-bg-subtle);
    color: var(--color-text);
}
.doc-filter-pill-danger.is-active { border-color: var(--color-danger); background: var(--color-danger-bg); }
.doc-filter-pill-warning.is-active { border-color: var(--color-warning); background: var(--color-warning-bg); }
.doc-filter-pill-success.is-active { border-color: var(--color-success); background: var(--color-success-bg); }

.doc-search-box {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 12px;
    margin-bottom: 12px;
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-md);
    background: var(--color-surface);
}
.doc-search-box i { color: var(--color-text-muted); }
.doc-search-input {
    flex: 1;
    border: none;
    outline: none;
    background: none;
    font-size: 13px;
    color: var(--color-text);
}

.doc-row-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.doc-row {
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--color-surface);
}

.doc-row-summary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    cursor: pointer;
}
.doc-row-summary:focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: -2px;
}

.doc-row-icon {
    flex-shrink: 0;
    display: flex;
    font-size: 16px;
}
.doc-row-icon-danger i { color: var(--color-danger); }
.doc-row-icon-warning i { color: var(--color-warning); }
.doc-row-icon-success i { color: var(--color-success); }
.doc-row-icon-soft i { color: var(--color-text-muted); }

.doc-row-name {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.doc-row-required {
    flex-shrink: 0;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    padding: 1px 6px;
    border-radius: var(--radius-full);
    text-transform: uppercase;
    letter-spacing: .02em;
}
.doc-row-required-mandatory { color: var(--color-danger); background: color-mix(in srgb, var(--color-danger) 12%, transparent); }
.doc-row-required-optional { color: var(--color-text-muted); background: var(--color-bg-subtle); }

.doc-row-meta {
    flex-shrink: 0;
    display: flex;
    align-items: center;
}
.doc-row-days { font-size: 11px; color: var(--color-text-secondary); }

.doc-row-files {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 2px;
    font-size: 11px;
    color: var(--color-text-muted);
}

.doc-row-chevron {
    flex-shrink: 0;
    display: flex;
    color: var(--color-text-muted);
    transition: transform .16s ease-out;
}
.doc-row-summary[aria-expanded="true"] .doc-row-chevron {
    transform: rotate(180deg);
}

.doc-row-body {
    /* height/opacity inline los controla toggleDocRow() (app.js) —
       measure-then-animate, sin max-height fijo (ver comentario ahí:
       notas/adjuntos de longitud variable no deben recortarse). */
    border-top: 1px solid var(--color-border-soft);
}
.doc-row-body-inner {
    padding: 10px 12px;
}

.doc-row-fields {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 8px;
    font-size: 12px;
}
.doc-row-field .doc-field-label {
    color: var(--color-text-muted);
    font-weight: 600;
}

.doc-row-actions {
    display: flex;
    gap: 6px;
    margin-top: 8px;
}

.doc-row-missing-detail {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}
.doc-row-missing-info { min-width: 0; }
.doc-row-missing-info p { margin: 2px 0 0; }

.doc-archived-disclosure {
    margin-top: 14px;
}
.doc-archived-disclosure summary {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-text-secondary);
    cursor: pointer;
    margin-bottom: 8px;
}

.doc-row-skeleton {
    height: 38px;
    border-radius: var(--radius-md);
    margin-bottom: 6px;
    background: linear-gradient(90deg, var(--color-bg-subtle) 25%, var(--color-border-soft) 37%, var(--color-bg-subtle) 63%);
    background-size: 400% 100%;
    animation: docRowSkeletonShimmer 1.4s ease infinite;
}
@keyframes docRowSkeletonShimmer {
    0% { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}
@media (prefers-reduced-motion: reduce) {
    .doc-row-skeleton { animation: none; }
}

/* --- Document Cards -------------------------------------------------------- */
.doc-list h4 {
    font-size: 14px;
    margin-bottom: 12px;
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--color-text-secondary);
}

/* Aspecto de "archivo" (ficha/carpeta con lomo de color): sombra suave que
   la despega del fondo (como una hoja física) + borde izquierdo grueso
   coloreado según el estado, mismo criterio que el badge de la cabecera
   pero legible de un vistazo sin leer el texto pequeño. */
.doc-card {
    border: 1px solid var(--color-border-soft);
    border-left: 4px solid var(--color-border);
    border-radius: var(--radius-md);
    margin-bottom: 10px;
    background: var(--color-surface-strong);
    overflow: hidden;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 3px 8px rgba(0, 0, 0, 0.06);
    transition: box-shadow 0.15s ease, transform 0.15s ease;
}

.doc-card:hover {
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.07), 0 6px 16px rgba(0, 0, 0, 0.09);
    transform: translateY(-1px);
}

.doc-card-status-active { border-left-color: var(--color-success); }
.doc-card-status-expiring_soon { border-left-color: var(--color-warning); }
.doc-card-status-expired { border-left-color: var(--color-danger); }
.doc-card-status-pending { border-left-color: var(--color-info, #31688b); }
.doc-card-status-rejected { border-left-color: var(--color-danger); }
.doc-card-status-renewed { border-left-color: var(--color-muted); }

.doc-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border-soft);
}

.doc-card-type {
    font-weight: 600;
    font-size: 14px;
}

/* Resumen (siempre visible) — sustituye a .doc-card-header dentro del
   explorador (DocsExplorerRenderer::docCard()): SOLO el nombre (tipo ·
   título). Estado, adjuntos y las 3 acciones (añadir archivo/editar/
   borrar) viven en .doc-card-detail y solo se ven con hover/foco — antes
   se veían 8-10 elementos a la vez en el peor caso (documento sin tipo +
   varios archivos + caducado + con firma). */
.doc-card-summary {
    display: flex;
    /* flex-start, no center: el nombre puede partirse en varias líneas
       (ver .doc-card-type más abajo) — con center, el tirador de arrastre
       quedaría flotando en medio de un bloque de texto de 2-3 líneas. */
    align-items: flex-start;
    gap: 8px;
    padding: 10px 14px;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border-soft);
}

.doc-card-summary .doc-card-type {
    flex: 1;
    /* min-width:0 sigue haciendo falta para que el flex-item pueda encoger
       por debajo del ancho de su contenido. Ahora el resumen solo lleva el
       nombre (nada que empujar fuera), así que en vez de truncar a una
       línea con "..." se deja partir en varias líneas — la tarjeta es
       angosta (grid-column: span 2, más abajo) y un nombre largo tipo
       "Tipo · Título" no cabía entero de una línea. */
    min-width: 0;
    white-space: normal;
    word-break: break-word;
    line-height: 1.3;
}

.btn-icon-only {
    padding: 4px 6px;
    line-height: 1;
}

/* Fila 1 del detalle: estado + documentos adjuntos. */
.doc-card-badge-files-row {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 10px 14px 0;
}

.doc-card-badge-files-row .badge {
    flex-shrink: 0;
    padding: 1px 7px;
    font-size: 10px;
}

.doc-card-badge-files-row .doc-files-list {
    flex: 1;
    padding: 0;
}

.doc-files-empty {
    font-size: 12px;
}

/* Fila 2 del detalle: añadir archivo / editar / borrar. */
.doc-card-actions-row {
    display: flex;
    gap: 2px;
    padding: 6px 14px 0;
}

/* Documento sin document_type_id -> sin carpeta propia (cae en "Sin
   categoría" del explorador, ver DocsExplorerRenderer::render()) — se
   distingue visualmente y es el único caso arrastrable (punto 1: mover
   documentos sin clasificar a su carpeta), ver docs-explorer-dnd.js. */
.doc-card-unfiled {
    font-style: italic;
    color: var(--color-text-secondary);
}

.doc-card-drag-handle {
    cursor: grab;
    opacity: .5;
    flex-shrink: 0;
    touch-action: none;
}

.doc-card:hover .doc-card-drag-handle {
    opacity: 1;
}

/* Detalle (ref, fechas, notas, chips de archivo, firma, renovar) — oculto
   por defecto, revelado con :hover Y :focus-within (el foco es necesario
   para que DocsExplorer.locate(), que hace scroll+resalta la tarjeta desde
   las alertas de caducidad, y la navegación por teclado, puedan seguir
   mostrando "Renovar"/"Ver firma" sin depender del ratón). */
.doc-card-detail {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    transition: max-height .18s ease, opacity .12s ease;
}

.doc-card:hover .doc-card-detail,
.doc-card:focus-within .doc-card-detail {
    max-height: 480px;
    opacity: 1;
}

.doc-card-body {
    padding: 10px 14px;
}

.doc-card-field {
    font-size: 13px;
    margin-bottom: 4px;
    color: var(--color-text-secondary);
}

.doc-field-label {
    color: var(--color-text-secondary);
    font-weight: 500;
}

.doc-days {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-warning);
    margin-left: 6px;
}

.doc-days.text-danger { color: var(--color-danger); }

.doc-card-actions {
    display: flex;
    gap: 4px;
    padding: 6px 14px;
    border-top: 1px solid var(--color-border-soft);
    background: var(--color-surface);
}

/* Evidencia de firma inline en la tarjeta (solo si el documento viene de un
   generated_document firmado, ver DocsExplorerRenderer::docCard()) — mismo
   verde que el resto de indicadores de "firmado" (ri-quill-pen-line en
   Firmas), separado del resto de campos con su propia franja. */
.doc-card-signature {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin: 0 14px 10px;
    padding: 6px 10px;
    border-radius: var(--radius-sm);
    background: var(--color-success-bg, #e6f6ec);
    font-size: 12px;
    color: var(--color-text-secondary);
}

.doc-card-signature .btn { margin-left: auto; font-size: 12px; }

/* Pestaña "Historial" del modal de editar documento (ver
   ApiController::documentHistory() / loadDocumentEditHistory() en
   documents.js) — mismo patrón visual de línea de tiempo ya usado en
   Pedidos de compra (.po-history-*, apps/internal/public/assets/css/pages/purchases.css),
   aquí en components.css (no en una hoja de página) porque este modal se
   abre desde cualquier ficha 360 (Trabajador, Máquina, Tercero...), no solo
   desde /documents. */
.doc-history-timeline {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.doc-history-item {
    position: relative;
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border-soft);
    border-radius: 10px;
    padding: 10px 14px;
}

.doc-history-item-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 4px;
}

.doc-history-item-date {
    font-size: 11px;
    color: var(--color-text-muted);
    white-space: nowrap;
}

.doc-history-item-user {
    font-size: 12px;
    color: var(--color-text-secondary);
    margin-bottom: 4px;
}

.doc-history-item-details {
    color: var(--color-text-secondary);
    line-height: 1.5;
}

.doc-card-actions .btn { font-size: 12px; }

/* Resalte al llegar desde la alerta de "documento caducado" de la cabecera
   (DocsExplorer.locate(), apps/shared/js/docs-explorer.js) — mismo criterio
   que .stock-highlight (apps/internal/public/assets/css/pages/stock/stock.css):
   un pulso de box-shadow que se desvanece solo, sin necesidad de setTimeout. */
.doc-card-highlight {
    animation: docCardHighlightPulse 1.5s ease;
}

@keyframes docCardHighlightPulse {
    0% { box-shadow: 0 0 0 3px var(--color-accent-soft); }
    100% { box-shadow: 0 0 0 0 transparent; }
}

.doc-files-list {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 0 14px 10px;
}

.doc-file-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-sm, 6px);
    background: var(--color-surface);
    font-size: 12px;
}

.doc-file-chip a {
    color: inherit;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.doc-file-chip .doc-file-delete {
    color: var(--color-text-secondary);
    cursor: pointer;
    line-height: 1;
    display: inline-flex;
}

.doc-file-chip .doc-file-delete:hover { color: var(--color-danger); }

.doc-file-chip-name {
    color: var(--color-text-muted);
    font-weight: 400;
}

.doc-file-chip .doc-file-rename,
.doc-file-chip .doc-file-replace {
    color: var(--color-text-secondary);
    cursor: pointer;
    line-height: 1;
    display: inline-flex;
}

.doc-file-chip .doc-file-rename:hover,
.doc-file-chip .doc-file-replace:hover { color: var(--color-primary); }

.doc-file-rename-input {
    font-size: 12px;
    padding: 2px 4px;
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-sm, 4px);
    min-width: 120px;
}

/* Botón "Vista previa" de la fila de tabla + contador de archivos extra
   (ver docRowActionsMenu() en WorkerTabService/AssetTabService — ese icono
   solo previsualiza el archivo principal). */
.w360-docs-preview-wrap {
    position: relative;
    display: inline-flex;
}

.w360-docs-preview-count {
    position: absolute;
    top: -4px;
    right: -4px;
    background: var(--color-primary);
    color: #fff;
    font-size: 9px;
    line-height: 1;
    padding: 2px 3px;
    border-radius: 999px;
    pointer-events: none;
}

/* =============================================================================
   Explorador de documentación (docs-explorer) — carpetas = categorías del
   catálogo document_type_category, ver DocsExplorerRenderer. <details>
   nativos anidados; el chevron rota con el estado open del propio <details>.
   Compartido por la ficha 360 del trabajador y el panel global de /workers.
   ============================================================================= */

.docs-explorer {
    margin-top: 12px;
}

/* Barra de navegación (breadcrumb) del explorador — ver docs-explorer.js. */
.docs-explorer-nav {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    padding: 6px 8px;
    margin-bottom: 10px;
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-sm, 6px);
    background: var(--color-bg-subtle);
    font-size: 13px;
    min-height: 34px;
}

.docs-explorer-back {
    border: none;
    background: var(--color-surface);
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-sm, 6px);
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: var(--color-text);
    margin-right: 4px;
}

.docs-explorer-crumb {
    border: none;
    background: none;
    padding: 2px 6px;
    font-size: 13px;
    color: var(--color-primary);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    border-radius: var(--radius-sm, 6px);
}

.docs-explorer-crumb:hover {
    background: var(--color-surface);
}

.docs-explorer-crumb.is-current {
    color: var(--color-text);
    font-weight: 600;
    cursor: default;
}

.docs-explorer-sep {
    color: var(--color-muted);
    font-size: 14px;
}

/* Cuerpo de un nivel (o de los resultados de búsqueda): carpetas y
   documentos en rejilla de columnas, tantas filas como hagan falta. */
.docs-explorer-body {
    display: grid;
    /* grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); */
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    gap: 8px;
    align-items: start;
}

/* BUG corregido: el atributo hidden de los niveles no visibles perdía
   contra el display:grid de la clase (especificidad autor > UA) — TODOS los
   niveles se pintaban a la vez y los archivos "se salían" de sus carpetas. */
.docs-explorer-body[hidden] {
    display: none !important;
}

/* Tarjeta de documento COMPACTA dentro del explorador. Las columnas del
   grid (.docs-explorer-body, más arriba) miden mínimo 100px — pensado para
   las fichas de carpeta, pequeñas de por sí. Un doc-card es hermano en ese
   mismo grid (los documentos sin clasificar se pintan sueltos junto a las
   carpetas). Ahora que el resumen solo lleva el nombre (y parte en varias
   líneas si hace falta, ver .doc-card-type), 2 columnas de ancho bastan —
   se queda casi tan estrecha como una carpeta, en vez de las 3 columnas
   que hacían falta cuando el resumen llevaba también badge y 3 botones. */
.docs-explorer .doc-card {
    font-size: 12px;
    grid-column: span 2;
}

/* Mismo motivo que el doc-card de arriba: las fichas de "falta subir X"
   pintadas DENTRO de una carpeta (missingCards[categoryId] en
   DocsExplorerRenderer::render()) son hermanas de las carpetas en el mismo
   grid de columnas de 100px — sin esto, su contenido (icono + texto +
   botón "Subir") se solapaba entre fichas contiguas. Las fichas de la
   sección aparte "Documentos faltantes" (fuera de .docs-explorer, su
   propio grid) no llevan este selector y no se ven afectadas. */
.docs-explorer .doc-missing-card {
    grid-column: span 3;
}

.docs-explorer .doc-card .doc-card-summary {
    padding: 6px 10px;
}

.docs-explorer .doc-card .doc-card-badge-files-row {
    padding: 6px 10px 0;
}

.docs-explorer .doc-card .doc-card-actions-row {
    padding: 4px 10px 0;
}

.docs-explorer .doc-card .doc-card-body {
    padding: 6px 10px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.docs-explorer .doc-card .doc-card-field {
    font-size: 12px;
}

.docs-explorer .doc-card .doc-files-list {
    padding: 0 10px 6px;
}

.docs-explorer .doc-card .doc-card-actions {
    padding: 4px 10px;
}

.docs-explorer .doc-card .doc-card-actions .btn,
.docs-explorer .doc-card .doc-files-list .btn {
    font-size: 11.5px;
    padding: 2px 8px;
}

.docs-explorer .doc-card .doc-card-actions-row .btn {
    font-size: 11.5px;
}

/* Ficha de carpeta (clic = entrar, ver docs-explorer.js) — forma de carpeta
   real (pestaña arriba-izquierda vía ::before + cuerpo cálido), en vez de
   una fila de lista genérica, para que se lea como un explorador de
   archivos de verdad (Windows/Finder/Drive). */
.docs-folder-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-height: 70px;
    margin-top: 8px; /* hueco para que la pestaña asome por encima */
    padding: 16px 12px 10px;
    border: 1px solid var(--color-warning-border);
    border-radius: 2px var(--radius-md) var(--radius-md) var(--radius-md);
    background: var(--color-warning-bg);
    cursor: pointer;
    font-size: 13.5px;
    color: var(--color-text);
    text-align: center;
    width: 100%;
    transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}

/* Pestaña de la carpeta: mismo tono que el cuerpo, solapada 3px para que no
   se note la costura entre ambas piezas. */
.docs-folder-tile::before {
    content: '';
    position: absolute;
    top: -9px;
    left: 14px;
    width: 46%;
    max-width: 92px;
    height: 12px;
    background: var(--color-warning-bg);
    border: 1px solid var(--color-warning-border);
    border-bottom: none;
    border-radius: var(--radius-xs) var(--radius-md) 0 0;
}

.docs-folder-tile:hover {
    background: var(--color-warning-border);
    border-color: var(--color-warning);
    transform: translateY(-2px);
    box-shadow: var(--shadow-soft);
}

.docs-folder-tile:hover::before {
    background: var(--color-warning-border);
    border-color: var(--color-warning);
}

.docs-folder-tile-name {
    font-weight: 600;
    max-width: 100%;
    line-height: 1.25;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    white-space: normal;
}

/* "Baúl · renovados": archivador cerrado, tono neutro en vez de cálido. */
.docs-folder-tile-vault {
    background: var(--color-bg-subtle);
    border-color: var(--color-border-soft);
}

.docs-folder-tile-vault::before {
    background: var(--color-bg-subtle);
    border-color: var(--color-border-soft);
}

.docs-folder-tile-vault:hover {
    background: var(--color-border-soft);
    border-color: var(--color-border);
}

.docs-folder-tile-vault:hover::before {
    background: var(--color-border-soft);
    border-color: var(--color-border);
}

.docs-folder-tile-vault .docs-folder-icon {
    color: var(--color-muted);
}

.docs-folder-tile-vault .docs-folder-count {
    border-color: var(--color-border-soft);
}

/* Drag & drop de documentos sin clasificar (docs-explorer-dnd.js) — carpeta
   resaltada como destino válido mientras se arrastra por encima (solo
   carpetas con algún tipo de documento propio, ver
   DocsExplorerRenderer::tile()/data-doc-types; sin tipos propios no se
   añade esta clase, nunca se resalta). */
.docs-folder-tile.drag-over {
    background: var(--color-success-bg, #e6f6ec);
    border-color: var(--color-success);
    transform: translateY(-2px) scale(1.03);
    box-shadow: var(--shadow-soft);
}

.docs-folder-tile.drag-over::before {
    background: var(--color-success-bg, #e6f6ec);
    border-color: var(--color-success);
}

/* "Típico prohibido": un documento YA clasificado solo puede soltarse en
   el Baúl — arrastrarlo sobre una carpeta se marca como NO válido en vez
   de ignorarse sin más (ver isValidDropTarget() en docs-explorer-dnd.js). */
.docs-folder-tile.drag-forbidden {
    cursor: not-allowed;
    background: var(--color-danger-bg);
    border-color: var(--color-danger);
    opacity: 0.4;
}

.docs-folder-tile.drag-forbidden::before {
    background: var(--color-danger-bg);
    border-color: var(--color-danger);
}

.doc-card-dragging {
    opacity: .45;
}

/* Papelera flotante (docs-trash-fab) — creada dinámicamente por JS al
   iniciar un arrastre (el explorador se inyecta por AJAX en varios sitios,
   así que no tiene sentido declararla en cada plantilla PHP). "Pop" al
   aparecer/desaparecer con cada drag; refuerzo visual (rojo + escala mayor)
   mientras el cursor está encima, para que soltar ahí sea inequívoco. */
.docs-trash-fab {
    position: fixed;
    right: 28px;
    bottom: 28px;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    background: var(--color-surface-strong);
    border: 2px solid var(--color-border);
    color: var(--color-text-secondary);
    box-shadow: var(--shadow-panel, 0 4px 16px rgba(0, 0, 0, .12));
    transform: scale(0);
    opacity: 0;
    transition: transform .2s cubic-bezier(.34, 1.56, .64, 1), opacity .15s ease, background-color .12s, border-color .12s, color .12s;
    z-index: 2000;
    cursor: pointer;
}

.docs-trash-fab.visible {
    transform: scale(1);
    opacity: 1;
}

.docs-trash-fab.drag-over {
    transform: scale(1.15);
    background: var(--color-danger);
    border-color: var(--color-danger);
    color: #fff;
}

/* Popover ligero para elegir tipo cuando la carpeta destino tiene varios
   tipos propios — deliberadamente NO el modal completo de editar documento
   (ese sigue siendo el sitio para título/fechas/notas/etc.), solo el campo
   que hace falta para esta acción. */
.doc-type-popover {
    position: fixed;
    z-index: 2001;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px;
    min-width: 200px;
    background: var(--color-surface-strong);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-panel, 0 4px 16px rgba(0, 0, 0, .12));
}

.doc-type-popover-label {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-text-secondary);
}

/* Tarjeta de documento renovado (histórico, "baúl"). */
.doc-card-renewed {
    opacity: 0.75;
}

/* /documents → Vencimientos: scroll independiente dentro de cada tarjeta
   (Vencidos / Próximos / Faltantes van en grid-3, una fila visible) en vez
   de hacer scroll sobre el body de la página. */
.tab-panel[data-panel="vencimientos"] .card-body {
    max-height: 420px;
    overflow-y: auto;
}

/* Faltante OPCIONAL (tipo aplicable no subido y no marcado obligatorio):
   mismo card que los obligatorios pero atenuado. */
.doc-missing-card-optional {
    opacity: 0.75;
}

.doc-missing-card-optional .doc-missing-icon i {
    color: var(--color-muted);
}

/* Lista de documentos faltantes por entidad (paneles Documentación de
   /workers y /assets): chips clicables que abren el modal de subida con el
   tipo preseleccionado. Obligatorios en rojo, opcionales en gris. */
.workers-missing-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.workers-missing-row {
    display: flex;
    align-items: baseline;
    gap: 12px;
    flex-wrap: wrap;
    padding: 6px 0;
    border-bottom: 1px solid var(--color-border-soft);
}

.workers-missing-row:last-child {
    border-bottom: none;
}

.workers-missing-worker {
    font-weight: 600;
    font-size: 13px;
    color: inherit;
    min-width: 160px;
}

/* Variante colapsable: usada solo en el panel lateral (.docs-cal-side) de
   /workers?tab=documentacion — cada trabajador arranca colapsado mostrando
   solo el nombre y el nº de faltantes; el desplegable revela los chips.
   Misma técnica grid-template-rows 0fr↔1fr que .docs-cal-side-section
   (ver assets-documents-calendar.css), pero independiente por fila: no es
   exclusivo como el acordeón de mini-secciones que la contiene. */
.workers-missing-row-collapsible {
    border-bottom: 1px solid var(--color-border-soft);
}

.workers-missing-row-collapsible:last-child {
    border-bottom: none;
}

.workers-missing-row-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 6px 0;
    cursor: pointer;
}

.workers-missing-row-header-right {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.workers-missing-row-collapsible.is-open .docs-cal-side-chevron {
    transform: rotate(180deg);
}

.workers-missing-row-body {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.2s ease;
}

.workers-missing-row-collapsible.is-open .workers-missing-row-body {
    grid-template-rows: 1fr;
}

.workers-missing-row-body-inner {
    overflow: hidden;
    min-height: 0;
}

.workers-missing-row-collapsible.is-open .workers-missing-row-body-inner {
    padding-bottom: 8px;
}

.workers-missing-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.workers-missing-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    border-radius: 12px;
    font-size: 12px;
    cursor: pointer;
    border: 1px solid transparent;
    background: var(--color-bg-subtle);
    color: var(--color-text);
}

.workers-missing-chip.is-mandatory {
    color: var(--color-danger);
    border-color: var(--color-danger);
    background: transparent;
}

.workers-missing-chip.is-optional {
    color: var(--color-muted);
    border-color: var(--color-border-soft);
}

.workers-missing-chip:hover {
    background: var(--color-bg-subtle);
    border-color: var(--color-primary);
    color: var(--color-primary);
}

/* Diagrama de configuración de documentación (Config → Documentación →
   Tipos, botón "Ver diagrama") — árbol de solo lectura entidad > categoría
   > tipos aplicables, con barra lateral de navegación, buscador y
   contadores (ver docs/ui/mockups/configuracion/diagrama-relacion-
   documentos.png, el diseño de referencia). Todo dentro de #modal-body
   (modal-xl, ver doc-types.js openConfigurationTreeDiagram()). */
.doc-config-diagram-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 12px;
}

.doc-config-diagram-legend {
    display: flex;
    align-items: center;
    gap: 10px;
}

.doc-config-diagram-actions {
    display: flex;
    align-items: center;
    gap: 10px;
}

.doc-config-search-bar {
    width: 220px;
}

.doc-config-status-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 9px;
    border-radius: 12px;
    font-size: 12px;
    border: 1px solid transparent;
    background: var(--color-bg-subtle);
}

.doc-config-status-badge.is-mandatory {
    color: var(--color-danger);
    border-color: var(--color-danger);
    background: transparent;
}

.doc-config-status-badge.is-optional {
    color: var(--color-muted);
    border-color: var(--color-border-soft);
}

/* Sidebar + panel principal con scroll propio (no toda la altura del
   modal): igual que .modal-body, con overflow-y:auto — así la barra de
   herramientas y la cabecera de columnas de arriba quedan siempre visibles
   sin necesidad de position:sticky (mismo razonamiento que el editor de
   plantillas, ver el comentario largo junto a #version-editor-mount). */
.doc-config-diagram-layout {
    display: grid;
    grid-template-columns: 220px 1fr;
    gap: 16px;
    align-items: start;
}

.doc-config-diagram-sidebar h5 {
    margin: 0 0 8px;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--color-text-muted);
}

.doc-config-diagram-sidebar ul {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.doc-config-sidebar-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 7px 10px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text);
    font-size: 13px;
    cursor: pointer;
    text-align: left;
}

.doc-config-sidebar-item span:first-of-type {
    flex: 1;
}

.doc-config-sidebar-item:hover {
    background: var(--color-bg-subtle);
}

.doc-config-sidebar-item.active {
    background: var(--color-bg-subtle);
    color: var(--color-primary);
    font-weight: 600;
}

.doc-config-diagram-main {
    max-height: 60vh;
    overflow-y: auto;
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-sm);
}

.doc-config-col-head {
    display: flex;
    justify-content: space-between;
    padding: 8px 14px;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--color-text-muted);
    background: var(--color-bg);
    border-bottom: 1px solid var(--color-border-soft);
    position: sticky;
    top: 0;
    z-index: 1;
}

.doc-config-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 14px;
    font-size: 13px;
    border-bottom: 1px solid var(--color-border-soft);
}

.doc-config-row-icon {
    color: var(--color-text-muted);
    flex-shrink: 0;
}

.doc-config-row-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.doc-config-row-status {
    flex-shrink: 0;
}

.doc-config-row-entity {
    cursor: pointer;
    font-weight: 600;
    background: var(--color-bg);
}

.doc-config-row-category,
.doc-config-row-group {
    cursor: pointer;
}

/* Un peldaño más discreto que la categoría (carpeta): es una subdivisión
   fina (Obligatorios/Opcionales), no una carpeta real del catálogo. */
.doc-config-row-group .doc-config-row-label {
    font-size: 11.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.3px;
    color: var(--color-text-muted);
}

.doc-config-row-chevron {
    transition: transform 0.15s ease;
    flex-shrink: 0;
}

/* Colapsable genérico — mismo elemento (.doc-config-collapsible-section) para
   las 3 capas del diagrama (entidad/categoría/grupo Obligatorios-Opcionales,
   ver doc-types.js): abierto por defecto (sin clase — la idea del diagrama
   es verlo todo de un vistazo), .is-collapsed lo pliega. grid-template-rows
   0fr↔1fr anima una altura "auto" real (mismo criterio que
   .docs-cal-side-section-body en assets-documents-calendar.css — un
   max-height fijo o recorta listas largas o deja salto en las cortas). */
.doc-config-collapsible-body {
    display: grid;
    grid-template-rows: 1fr;
    transition: grid-template-rows 0.2s ease;
}

.doc-config-collapsible-section.is-collapsed .doc-config-collapsible-body {
    grid-template-rows: 0fr;
}

.doc-config-collapsible-body-inner {
    overflow: hidden;
    min-height: 0;
}

.doc-config-collapsible-section.is-collapsed .doc-config-row-chevron {
    transform: rotate(-90deg);
}

.doc-config-count {
    flex-shrink: 0;
}

.doc-config-empty {
    padding: 4px 14px 10px;
}

.doc-config-row-doc.is-search-hidden,
.doc-config-collapsible-section.is-search-hidden {
    display: none;
}

.doc-config-diagram-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--color-border-soft);
}

/* Documentos archivados (ya sustituidos por una renovación posterior) de
   las fichas de Empresa/Tercero/Artículo: sin árbol de carpetas propio como
   Worker/Asset 360, así que un <details> nativo plegado basta para no
   ensuciar la tabla principal por defecto. */
.doc-archived-details {
    margin-top: 12px;
}

.doc-archived-details summary {
    cursor: pointer;
    font-size: 0.8rem;
    color: var(--color-text-muted);
}

.doc-archived-details[open] summary {
    margin-bottom: 8px;
}

.docs-folder-icon {
    font-size: 30px;
    color: var(--color-warning);
    flex-shrink: 0;
}

.docs-folder-count {
    position: absolute;
    top: 6px;
    right: 8px;
    font-size: 10.5px;
    font-weight: 700;
    color: var(--color-muted);
    background: var(--color-surface);
    border: 1px solid var(--color-warning-border);
    border-radius: var(--radius-full, 999px);
    padding: 1px 7px;
    flex-shrink: 0;
}

/* Punto de estado sobre el icono de carpeta — marca en cascada (carpeta
   padre y subcarpetas) si hay algún documento caducado/próximo a caducar en
   su interior, en cualquier nivel (ver DocsExplorerRenderer::folderStatus()).
   'expired' pesa más que 'expiring_soon': con que haya un solo caducado en
   cualquier subcarpeta, toda la cadena de carpetas padre se marca en rojo. */
.docs-folder-icon-wrap {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
}

.docs-folder-status {
    position: absolute;
    top: -1px;
    right: -1px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    border: 1.5px solid var(--color-surface);
}

.docs-folder-status-expired { background: var(--color-danger); }
.docs-folder-status-expiring_soon { background: var(--color-warning); }

/* Badge de nº de documentos obligatorios sin subir dentro de la carpeta
   (tab Explorador de /documents, ver DocsExplorerRenderer::tile()) — para
   verlo de un vistazo sin entrar. Esquina opuesta al punto de estado
   (.docs-folder-status, caducados/próx.) para que puedan convivir sin
   solaparse. */
.docs-folder-missing-badge {
    position: absolute;
    top: -6px;
    left: -6px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-full);
    background: var(--color-danger);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    border: 1.5px solid var(--color-surface);
}

/* Tab "Explorador" de /documents (Nivel 1: tipos de entidad, Nivel 2:
   registro de entidades) — mismo lenguaje visual que .docs-folder-tile
   (carpeta con pestaña) para que se lea como el mismo componente, sin
   acoplarse a su JS (docs-explorer.js solo gestiona .docs-explorer, ver
   documents-explorer.js aparte). */
.explorer-entity-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 14px;
}

.explorer-entity-tile {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 90px;
    margin-top: 8px;
    padding: 18px 12px 12px;
    border: 1px solid var(--color-warning-border);
    border-radius: 2px var(--radius-md) var(--radius-md) var(--radius-md);
    background: var(--color-warning-bg);
    cursor: pointer;
    font: inherit;
    color: var(--color-text);
    text-align: center;
    transition: transform 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}

.explorer-entity-tile::before {
    content: '';
    position: absolute;
    top: -9px;
    left: 14px;
    width: 46%;
    max-width: 92px;
    height: 12px;
    background: var(--color-warning-bg);
    border: 1px solid var(--color-warning-border);
    border-bottom: none;
    border-radius: var(--radius-xs) var(--radius-md) 0 0;
}

.explorer-entity-tile:hover {
    background: var(--color-warning-border);
    border-color: var(--color-warning);
    transform: translateY(-2px);
    box-shadow: var(--shadow-soft);
}

.explorer-entity-tile:hover::before {
    background: var(--color-warning-border);
    border-color: var(--color-warning);
}

.explorer-entity-tile.is-active {
    background: var(--color-primary-bg, var(--color-info-bg));
    border-color: var(--color-primary, var(--color-info));
    box-shadow: var(--shadow-soft);
}

.explorer-entity-tile.is-active::before {
    background: var(--color-primary-bg, var(--color-info-bg));
    border-color: var(--color-primary, var(--color-info));
}

.explorer-entity-tile.is-active .explorer-entity-icon,
.explorer-entity-tile.is-active .explorer-entity-name {
    color: var(--color-primary, var(--color-info));
}

.explorer-entity-icon {
    font-size: 26px;
    color: var(--color-warning);
}

.explorer-entity-name {
    font-weight: 600;
    font-size: 13px;
    max-width: 100%;
    line-height: 1.25;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    white-space: normal;
}

.explorer-entities-toolbar,
.explorer-detail-toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 14px;
}

.explorer-entities-toolbar .search-bar {
    flex: 1;
    max-width: 320px;
}

.explorer-detail-title {
    margin: 0;
}

.docs-explorer-body > .docs-folder-empty {
    grid-column: 1 / -1;
}

/* Buscador de la pestaña Documentación (ficha 360). */
.docs-search-box {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 10px;
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-sm, 6px);
    background: var(--color-surface);
    margin-left: auto;
    margin-right: 10px;
}

.docs-search-box i {
    color: var(--color-muted);
}

.docs-search-box input {
    border: none;
    outline: none;
    background: transparent;
    font-size: 13px;
    color: var(--color-text);
    width: 200px;
}

.docs-folder-empty {
    font-size: 12.5px;
    font-style: italic;
    margin: 0;
}

/* Botones "Previsualizar archivo"/"Leer con OCR" del modal de documento —
   alineados a la altura del input de archivo contiguo (misma fila del
   form-grid); en columna porque ahora son dos, no uno. */
.doc-form-preview {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 8px;
}

.doc-form-preview .btn {
    width: 100%;
    justify-content: center;
}

.doc-preview-btn-filename {
    opacity: 0.8;
    font-weight: 400;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 220px;
}

/* KPIs de documentación por entidad (/documents): una tarjeta compacta por
   tipo de entidad con su desglose de estados. */
.doc-entity-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 10px;
}

.doc-entity-kpi-card {
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-sm, 6px);
    background: var(--color-surface);
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.doc-entity-kpi-header {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13.5px;
}

.doc-entity-kpi-header i {
    color: var(--color-primary);
    font-size: 16px;
}

.doc-entity-kpi-total {
    margin-left: auto;
    font-size: 12px;
    font-weight: 600;
    color: var(--color-muted);
}

.doc-entity-kpi-badges {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

/* =============================================================================
   360 Header photo/logo (shared by worker-360, company-360, asset-360) — el
   tamaño/forma de la propia foto (circular, cuadrada...) se define en el CSS
   de cada página; esto es solo el wrapper + botón + hint, idénticos en las 3.
   ============================================================================= */

.w360-header-photo-wrap,
.a360-header-photo-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    position: relative;
}

/* Trigger "ver ampliado" superpuesto a la foto/logo — independiente del click
   sobre la propia foto (data-action="upload-photo"/"upload-logo") que sirve
   para reemplazarla. Solo se renderiza cuando ya existe una foto. */
.w360-photo-view-btn,
.a360-photo-view-btn {
    position: absolute;
    top: 0;
    right: 0;
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--color-border-soft);
    border-radius: 50%;
    background: var(--color-surface-strong);
    color: var(--color-text-muted);
    cursor: pointer;
    font-size: 14px;
    box-shadow: var(--shadow-soft);
    z-index: 1;
}

.w360-photo-view-btn:hover,
.a360-photo-view-btn:hover {
    background: var(--color-bg-subtle);
    color: var(--color-text);
}

/* --- File Preview modal (imágenes, PDF, hojas de cálculo) ------------------ */
.file-preview-download {
    display: flex;
    justify-content: flex-end;
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px solid var(--color-border-soft);
}

/* Barra ‹ N/M › del previsualizador multi-archivo (openFilePreviewSet(),
   app.js) — documentos con varios archivos, p.ej. DNI anverso/reverso. */
.file-preview-nav {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    margin-bottom: 10px;
}

.file-preview-nav-count {
    font-size: 13px;
    color: var(--color-text-muted);
    min-width: 44px;
    text-align: center;
}

.file-preview-download a {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-primary);
    text-decoration: none;
}

.file-preview-download a:hover {
    text-decoration: underline;
}

/* Imagen ampliable dentro del preview de un documento (openFilePreview()/
   openFilePreviewLayer2(), app.js) — el <img> ahí ya limita a max-height:75vh
   por vivir dentro de un modal acotado; clic para verla más grande sin tener
   que maximizar la ventana del navegador. Solo aplica a imágenes: un PDF
   usa el <iframe> del visor nativo del navegador, que ya trae su propio zoom. */
.file-preview-zoomable {
    cursor: zoom-in;
}

.image-lightbox-overlay {
    position: fixed;
    inset: 0;
    z-index: 2000;
    background: rgba(7, 20, 38, 0.88);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 32px;
    cursor: zoom-out;
    animation: imageLightboxFadeIn 0.15s ease;
}

@keyframes imageLightboxFadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
}

.image-lightbox-overlay img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    box-shadow: var(--shadow-panel);
    cursor: default;
    transition: transform 0.1s ease-out;
}

.image-lightbox-overlay img.is-zoomed {
    cursor: grab;
}

.image-lightbox-overlay img.is-dragging {
    cursor: grabbing;
    transition: none;
}

.image-lightbox-toolbar {
    position: absolute;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 10px;
    background: rgba(255, 255, 255, 0.12);
    border-radius: var(--radius-full);
    padding: 6px 10px;
    cursor: default;
}

.image-lightbox-toolbar button {
    width: 32px;
    height: 32px;
    border: none;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.14);
    color: #fff;
    font-size: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

.image-lightbox-toolbar button:hover {
    background: rgba(255, 255, 255, 0.26);
}

.image-lightbox-zoom-level {
    color: #fff;
    font-size: 12.5px;
    font-weight: 600;
    min-width: 42px;
    text-align: center;
}

.image-lightbox-close {
    position: absolute;
    top: 20px;
    right: 24px;
    width: 40px;
    height: 40px;
    border: none;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    font-size: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

.image-lightbox-close:hover {
    background: rgba(255, 255, 255, 0.22);
}

.w360-photo-change-btn,
.a360-photo-change-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    font-weight: 500;
    color: var(--color-text-muted);
    background: var(--color-bg-subtle);
    border: 1px solid var(--color-border-soft);
    border-radius: 6px;
    padding: 4px 10px;
    cursor: pointer;
    white-space: nowrap;
}

.w360-photo-change-btn:hover,
.a360-photo-change-btn:hover {
    background: var(--color-border-soft);
    color: var(--color-text);
}

.w360-photo-hint,
.a360-photo-hint {
    font-size: 11px;
    color: var(--color-text-muted);
    text-align: center;
    max-width: 110px;
}

/* --- Mi Perfil: foto de usuario --------------------------------------------- */
.profile-photo-row {
    display: flex;
    align-items: center;
    gap: 20px;
}

.profile-avatar {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background: var(--color-bg-subtle);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 32px;
    color: var(--color-text-muted);
    flex-shrink: 0;
    overflow: hidden;
    cursor: pointer;
}

.profile-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 50%;
}

.profile-photo-actions {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
}

.profile-photo-actions .w360-photo-hint {
    text-align: left;
    max-width: none;
}

/* =============================================================================
   Ficha widget (grid de datos generales, usado por worker-360 y asset-360)
   ============================================================================= */

.widget-ficha-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
}

.widget-ficha-item {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.ficha-label {
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

.ficha-value {
    font-size: 13px;
    color: var(--color-text);
}

.widget-ficha .widget-footer {
    display: flex;
    justify-content: flex-end;
}

/* =============================================================================
   360 Widget System (shared by worker-360, company-360, asset-360)
   ============================================================================= */

.widget {
    background: var(--color-surface);
    border: 1px solid var(--color-border-soft);
    border-radius: 10px;
    overflow: hidden;
}

.widget-header {
    padding: 14px 16px 0;
}

.widget-header h4 {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 8px;
}

.widget-body {
    padding: 14px 16px;
}

.widget-footer {
    padding: 0 16px 14px;
    font-size: 12px;
    color: var(--color-text-muted);
}

.widget-empty {
    padding: 40px;
    text-align: center;
    color: var(--color-text-muted);
}

.widget-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 60px;
    color: var(--color-text-muted);
    background: var(--color-surface);
    border: 1px solid var(--color-border-soft);
    border-radius: 10px;
}

.widget-stat-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
    margin-bottom: 12px;
}

.widget-stat {
    text-align: center;
    padding: 8px;
    border-radius: 6px;
    background: var(--color-bg);
}

.widget-stat .stat-value {
    display: block;
    font-size: 24px;
    font-weight: 700;
    color: var(--color-text);
}

.widget-stat .stat-label {
    display: block;
    font-size: 11px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

.widget-stat.stat-danger .stat-value { color: var(--color-danger); }
.widget-stat.stat-warning .stat-value { color: var(--color-warning); }

/* Vacation balance — anillo de progreso CSS puro (conic-gradient), usado en
   la pestaña "Vacaciones" de la ficha de trabajador (ver
   WorkerTabService::vacacionesTab()). --pct (0-100) y --ring-color se fijan
   inline por PHP según el % de días restantes. */
.vacation-balance {
    display: flex;
    align-items: center;
    gap: 20px;
    flex-wrap: wrap;
}

.vacation-balance-ring {
    --size: 100px;
    width: var(--size);
    height: var(--size);
    border-radius: 50%;
    flex-shrink: 0;
    background: conic-gradient(var(--ring-color, var(--color-primary)) calc(var(--pct, 0) * 1%), var(--color-border-soft) 0);
    display: flex;
    align-items: center;
    justify-content: center;
}

.vacation-balance-ring-inner {
    width: calc(var(--size) - 18px);
    height: calc(var(--size) - 18px);
    border-radius: 50%;
    background: var(--color-surface);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
}

.vacation-balance-value {
    font-size: 24px;
    font-weight: 700;
    line-height: 1.1;
    color: var(--color-text);
}

.vacation-balance-total {
    font-size: 11px;
    color: var(--color-text-muted);
}

.vacation-balance-details {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 180px;
}

.vacation-balance-row {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    font-size: 13px;
    color: var(--color-text-muted);
}

.vacation-balance-row strong {
    color: var(--color-text);
}

.vacation-balance-prorated {
    font-size: 12px;
    color: var(--color-warning);
    display: flex;
    align-items: center;
    gap: 4px;
}

.widget-recent-list {
    margin-top: 8px;
}

.widget-recent-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 6px 0;
    border-bottom: 1px solid var(--color-border-soft);
    font-size: 13px;
}

.widget-recent-item:last-child {
    border-bottom: none;
}

.recent-item-title {
    color: var(--color-text);
}

.widget-footer .btn {
    font-size: 12px;
}

.w360-dashboard {
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.w360-widgets {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 16px;
}

.w360-alerts {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.w360-alert {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    border-radius: 8px;
    font-size: 13px;
    flex: 1;
    min-width: 200px;
}

.w360-alert.alert-danger {
    background: var(--color-danger-bg);
    color: var(--color-danger);
    border-left: 4px solid var(--color-danger);
}

.w360-alert.alert-warning {
    background: var(--color-warning-bg);
    color: var(--color-warning);
    border-left: 4px solid var(--color-warning);
}

.w360-alert.alert-info {
    background: rgba(7, 27, 53, 0.06);
    color: var(--color-primary);
    border-left: 4px solid var(--color-primary);
}

/* =============================================================================
   Shared 360 Header system
   ============================================================================= */

.w360-header {
    background: var(--color-surface);
    border: 1px solid var(--color-border-soft);
    border-radius: 12px;
    padding: 24px;
    margin-bottom: 20px;
    display: flex;
    gap: 24px;
    align-items: flex-start;
}

.w360-header-loading {
    display: flex;
    align-items: center;
    gap: 8px;
    color: var(--color-text-muted);
    padding: 20px;
}

.w360-header-info {
    flex: 1;
    min-width: 0;
}

.w360-header-name {
    font-size: 20px;
    font-weight: 600;
    margin: 0 0 4px;
}

.w360-header-code {
    font-size: 12px;
    color: var(--color-text-muted);
    margin-bottom: 8px;
}

.w360-header-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 8px;
}

.w360-header-meta-item {
    font-size: 13px;
    color: var(--color-text-secondary);
}

.w360-header-meta-item strong {
    color: var(--color-text);
}

.w360-header-indicators {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 8px 0;
}

.w360-header-actions {
    display: flex;
    gap: 8px;
    flex-shrink: 0;
}

/* --- Responsive: ver apps/shared/css/responsive.css ------------------------- */

/* =============================================================================
   Loading animation (smooth pulse)
   ============================================================================= */

@keyframes loading-pulse {
    0%, 100% { opacity: 0.4; }
    50% { opacity: 1; }
}

.w360-header-loading,
.w360-tab-loading,
.widget-loading,
.widget-placeholder,
.schedule-loading,
.modal-loading-overlay {
    animation: loading-pulse 1.4s ease-in-out infinite;
}

/* --- Skeleton shimmer for widget loading --- */
@keyframes shimmer {
    0% { background-position: -200px 0; }
    100% { background-position: calc(200px + 100%) 0; }
}

.widget-loading-skeleton {
    background: linear-gradient(90deg, var(--color-bg) 25%, var(--color-border-soft) 50%, var(--color-bg) 75%);
    background-size: 200px 100%;
    animation: shimmer 1.5s ease-in-out infinite;
    border-radius: 6px;
    height: 80px;
    margin-bottom: 8px;
}

/* =============================================================================
   Loading animation: animated ellipsis
   ============================================================================= */

.loading-ellipsis::after {
    display: inline-block;
    animation: loading-ellipsis 1.5s steps(4) infinite;
    content: '';
    vertical-align: bottom;
}

/* =============================================================================
   Dashboard Inicio — .dashboard-header/.dashboard-base-filter/
   .dashboard-timeseries-* y el grid de 3 columnas viven ahora en
   apps/shared/css/pages/dashboard/dashboard-home.css (exclusivos de esta
   pantalla). Lo que queda aquí (.dashboard-donut-wrap, .donut-chart,
   .donut-chart-hole) es compartido con workers-resumen.php/assets-resumen.php
   — no mover sin comprobar antes esas dos pantallas.
   ============================================================================= */

.dashboard-donut-wrap {
    display: flex;
    align-items: center;
    gap: 24px;
    flex-wrap: wrap;
}

.donut-chart {
    position: relative;
    width: 140px;
    height: 140px;
    border-radius: 50%;
    flex-shrink: 0;
}

.donut-chart-hole {
    position: absolute;
    inset: 18px;
    border-radius: 50%;
    background: var(--color-surface-strong);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
}

.donut-chart-hole strong {
    font-size: 1.3rem;
    color: var(--color-text);
}

.donut-chart-hole span {
    font-size: 0.7rem;
    color: var(--color-muted);
}

/* Variación vs periodo anterior en un stat-card (p.ej. "+12,4% vs mes
   anterior") — reutiliza .text-success/.text-danger (components.css) para
   el color, esto solo alinea icono+texto. */
.stat-delta {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 0.72rem;
    margin-top: 2px;
}

/* Editor de arrastre de bloques de texto sobre la portada (Diseño
   corporativo > Sistema documental) — ver document-designs.js. Proporción
   210/297 (A4) para que la posición arrastrada en pantalla se corresponda
   con el PDF real sea cual sea el tamaño del lienzo en pantalla. */
.cover-editor-canvas {
    position: relative;
    width: 100%;
    max-width: 320px;
    aspect-ratio: 210 / 297;
    background-color: var(--color-bg-subtle);
    background-size: cover;
    background-position: center;
    border: 1px solid var(--color-border-soft);
    border-radius: 6px;
    overflow: hidden;
    user-select: none;
}

.cover-block-item {
    position: absolute;
    transform: translate(-50%, -50%);
    cursor: move;
    white-space: nowrap;
    padding: 2px 4px;
    border: 1px dashed transparent;
    border-radius: 3px;
}

.cover-block-item:hover,
.cover-block-item.is-dragging {
    border-color: #0d6efd;
    background: rgba(13, 110, 253, 0.08);
}

.cover-block-actions {
    position: absolute;
    top: -22px;
    right: 0;
    display: none;
    gap: 2px;
}

.cover-block-item:hover .cover-block-actions {
    display: flex;
}

.cover-block-actions button {
    width: 18px;
    height: 18px;
    padding: 0;
    line-height: 1;
    font-size: 11px;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

.donut-legend {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
    font-size: 0.85rem;
    color: var(--color-text);
}

.donut-legend li {
    display: flex;
    align-items: center;
    gap: 8px;
}

.donut-legend-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
}

/* --- Timeline ---------------------------------------------------------------
   Movida aquí desde worker-360.css: la usan también Machine 360 (historial de
   lecturas) y otras vistas 360 — es un componente compartido, no de una sola
   página. Ver docs/history sobre este mismo patrón de bug con .widget-ficha-item. */

.timeline {
    display: flex;
    flex-direction: column;
    gap: 0;
}

.timeline-item {
    display: flex;
    gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--color-border-soft);
    position: relative;
}

.timeline-item:last-child {
    border-bottom: none;
}

.timeline-icon {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--color-bg-subtle);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    color: var(--color-text-secondary);
    flex-shrink: 0;
}

.timeline-content {
    flex: 1;
    min-width: 0;
}

.timeline-date {
    font-size: 11px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

.timeline-text {
    font-size: 13px;
    color: var(--color-text);
}

.timeline-author {
    font-size: 11px;
    color: var(--color-text-muted);
    margin-top: 2px;
}

.timeline-actions {
    display: flex;
    align-items: flex-start;
    gap: 4px;
    flex-shrink: 0;
}

@keyframes loading-ellipsis {
    0% { content: ''; }
    25% { content: '.'; }
    50% { content: '..'; }
    75% { content: '...'; }
}

/* =============================================================================
   Full-screen page loader (prevents F5 flash / jumping)
   ============================================================================= */

.page-loader {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(245, 246, 250, 0.85);
    backdrop-filter: blur(6px);
    transition: opacity 0.35s ease;
}

.page-loader.hidden {
    opacity: 0;
    pointer-events: none;
}

.page-loader-card {
    background: var(--color-surface-strong, #fff);
    border: 1px solid var(--color-border-soft, #e2e8f0);
    border-radius: var(--radius-lg, 12px);
    box-shadow: var(--shadow-sheet, 0 4px 24px rgba(0,0,0,0.12));
    padding: 48px 56px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
    min-width: 200px;
}

.page-loader .ri-loader-4-line {
    font-size: 44px;
    color: var(--color-primary, #071b35);
    animation: loader-spin 0.8s linear infinite;
}

@keyframes loader-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

/* Spinner reutilizable para cualquier icono de "Cargando…" (cards, tabs, widgets, botones) */
.ri-spin {
    display: inline-block;
    animation: loader-spin 0.8s linear infinite;
}

.page-loader-text {
    font-size: 15px;
    color: var(--color-text-muted, #718096);
    font-weight: 500;
}

@media (prefers-color-scheme: dark) {
    .page-loader {
        background: rgba(26, 32, 44, 0.85);
    }
}

/* --- Entity listing toolbar (Visual/Tabla toggle + filter dropdowns) -------
   Patrón genérico para listados con vista dual (tarjetas/tabla) + filtros
   servidos por querystring + recarga completa (ver Trabajadores, y
   convergerá con Máquinas/otros listados que adopten el mismo patrón). */
.entity-toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 16px;
}

.view-toggle {
    display: inline-flex;
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-md);
    overflow: hidden;
    flex-shrink: 0;
}

.view-toggle a,
.view-toggle button {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: none;
    background: var(--color-surface-strong);
    color: var(--color-muted);
    padding: 7px 16px;
    font-size: 13px;
    font-family: inherit;
    font-weight: 500;
    cursor: pointer;
    text-decoration: none;
}

.view-toggle a.active,
.view-toggle button.active {
    background: var(--color-primary);
    color: #fff;
}

/* Grid de filtros basados en FormUI::select()/input() — mismo patrón que
   .assets-filter-fields (apps/internal/public/assets/css/pages/assets.css),
   generalizado aquí para que cualquier listado nuevo lo reutilice sin
   reimplementar el grid. */
.entity-filter-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px 16px;
    flex: 1 1 420px;
}

.entity-filter-fields .form-group {
    margin-bottom: 0;
}

.entity-per-page .form-group {
    margin-bottom: 0;
}

.entity-toolbar .search-bar {
    margin-left: auto;
}

/* --- Entity card grid (Trabajadores "Visual", y patrón a reutilizar por
   otros listados con vista de tarjetas) --------------------------------- */
.entity-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 16px;
    margin-bottom: 16px;
}

.entity-card {
    background: var(--color-surface-strong);
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-soft);
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.entity-card-header {
    display: flex;
    align-items: center;
    gap: 10px;
}

.entity-card-avatar {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    overflow: hidden;
    background: var(--color-bg-subtle);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: var(--color-muted);
    font-size: 20px;
}

.entity-card-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.entity-card-title {
    min-width: 0;
}

.entity-card-title strong {
    display: block;
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.entity-card-title span {
    display: block;
    font-size: 12px;
    color: var(--color-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.entity-card-meta {
    font-size: 12px;
    color: var(--color-muted);
}

.entity-card-metrics {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
}

.entity-metric-label {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    font-size: 11px;
    color: var(--color-muted);
    margin-bottom: 4px;
}

.entity-metric-label strong {
    font-size: 12.5px;
    color: var(--color-text);
    font-weight: 600;
}

.entity-metric-bar {
    height: 5px;
    border-radius: var(--radius-full);
    background: var(--color-bg-subtle);
    overflow: hidden;
}

.entity-metric-fill {
    height: 100%;
    border-radius: var(--radius-full);
}

.entity-metric-fill[data-level="good"] { background: var(--color-success); }
.entity-metric-fill[data-level="warn"] { background: var(--color-warning); }
.entity-metric-fill[data-level="bad"] { background: var(--color-danger); }

.entity-card-rows {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 12.5px;
    border-top: 1px solid var(--color-border-soft);
    padding-top: 10px;
}

.entity-card-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: var(--color-muted);
}

.entity-card-row i {
    margin-right: 5px;
}

.entity-card-row span:last-child {
    color: var(--color-text);
    font-weight: 500;
}

.entity-card-footer .btn {
    width: 100%;
    justify-content: center;
}

/* Pestaña Nóminas (Worker 360 / Mi Jornada) — un <details> nativo por mes,
   sin JS: real (nómina subida, docCard reutilizado) o estimación bruta
   (PayrollEstimateService). */
.payroll-months {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.payroll-month-item {
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-md);
    background: var(--color-surface-strong);
    overflow: hidden;
}

.payroll-month-item summary {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    cursor: pointer;
    list-style: none;
}

.payroll-month-item summary::-webkit-details-marker {
    display: none;
}

.payroll-month-label {
    font-weight: 600;
    flex: 1;
}

.payroll-month-amount {
    font-weight: 600;
    color: var(--color-text-secondary);
}

.payroll-month-detail {
    padding: 0 16px 16px;
    border-top: 1px solid var(--color-border-soft);
}

.payroll-total-row td {
    border-top: 2px solid var(--color-border-soft);
}

/* Contenedor de la simulación neta en Mi Jornada — se rellena al abrir el
   <details> del mes (evento 'toggle', ver my-day.js), sin botón aparte. */
.payroll-net-container:empty {
    display: none;
}

/* Simulación completa de nómina en formato "recibo de salarios" — Devengos
   / Deducciones en columnas, líquido destacado, bases de cotización debajo. */
.payslip-disclaimer {
    color: var(--color-warning);
    font-size: 12px;
    margin-bottom: 12px;
}

.payslip-columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
}

@media (max-width: 640px) {
    .payslip-columns {
        grid-template-columns: 1fr;
    }
}

.payslip-col h5 {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-text-secondary);
    margin-bottom: 6px;
    padding-bottom: 4px;
    border-bottom: 2px solid var(--color-border-soft);
}

.payslip-subtotal-row td {
    border-top: 1px dashed var(--color-border-soft);
    font-style: italic;
    color: var(--color-text-secondary);
}

.payslip-total-row td {
    border-top: 2px solid var(--color-border-soft);
}

.payslip-liquido {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin: 16px 0;
    padding: 14px 18px;
    background: var(--color-success-bg, rgba(16, 185, 129, .08));
    border: 1px solid var(--color-success-border, var(--color-success));
    border-radius: var(--radius-md);
    font-size: 14px;
    font-weight: 600;
}

.payslip-liquido strong {
    font-size: 19px;
    color: var(--color-success);
}

/* ============================================================================
   Worker 360 — Documentación: árbol lateral siempre visible + tabla, ver
   docs/ui/mockups/trabajadores/trabajadores-documentacion.png y
   WorkerTabService::documentacionTab() (único emisor de este marcado —
   Machine 360/Companies/Explorador de /documents siguen usando el grid de
   carpetas de DocsExplorerRenderer, sin cambios).
   ============================================================================ */

/* Variante azul de .doc-summary-item para "Pendientes" (antes gris, igual
   que "Faltantes oblig." — ahora se distinguen). */
.doc-summary-item.badge-info { border-left: 3px solid var(--color-info); }
.doc-summary-item.badge-info i { color: var(--color-info); }

.w360-docs-layout {
    display: grid;
    grid-template-columns: 230px 1fr;
    gap: 18px;
    align-items: start;
}

@media (max-width: 900px) {
    .w360-docs-layout { grid-template-columns: 1fr; }
}

.w360-docs-tree {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 8px;
    background: var(--color-surface-strong);
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-md);
}

.w360-docs-tree-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 10px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text);
    font-size: 13px;
    text-align: left;
    cursor: pointer;
    width: 100%;
}

.w360-docs-tree-item i { font-size: 15px; color: var(--color-text-secondary); flex-shrink: 0; }
.w360-docs-tree-item span:not(.w360-docs-tree-count) { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.w360-docs-tree-item:hover { background: var(--color-border-soft); }

.w360-docs-tree-item.is-active {
    background: var(--color-primary-bg, var(--color-info-bg));
    color: var(--color-primary, var(--color-info));
    font-weight: 600;
}
.w360-docs-tree-item.is-active i { color: inherit; }

.w360-docs-tree-count {
    font-size: 10.5px;
    font-weight: 700;
    color: var(--color-text-secondary);
    background: var(--color-surface);
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-full, 999px);
    padding: 1px 7px;
    flex-shrink: 0;
}

.w360-docs-tree-item.is-active .w360-docs-tree-count {
    background: var(--color-surface);
    border-color: transparent;
}

/* La fila del árbol lleva la clase docs-folder-tile SOLO por el contrato
   con docs-explorer-dnd.js (destino de drop + limpieza de .drag-over, ver
   WorkerTabService::docsTreeNode()) — se anula aquí toda su apariencia de
   "tarjeta de carpeta con pestaña" (fondo, borde, pseudo-elemento de
   pestaña, margen), que no pinta nada en una fila de árbol. */
.w360-docs-tree-item.docs-folder-tile,
.w360-docs-tree-item.docs-folder-tile:hover {
    margin-top: 0;
    padding: 7px 10px;
    min-height: 0;
    flex-direction: row;
    justify-content: flex-start;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    box-shadow: none;
    transform: none;
    font-size: 13px;
    text-align: left;
}
.w360-docs-tree-item.docs-folder-tile::before,
.w360-docs-tree-item.docs-folder-tile:hover::before {
    display: none;
}
.w360-docs-tree-item.docs-folder-tile:hover {
    background: var(--color-border-soft);
}

/* Destino de drop (docs-explorer-dnd.js, sin cambios en ese fichero) — el
   árbol reutiliza .docs-folder-tile.drag-over tal cual, este bloque solo
   redondea las esquinas al estilo de fila de árbol en vez de tarjeta. */
.w360-docs-tree-item.docs-folder-tile.drag-over {
    background: var(--color-success-bg, #e6f6ec);
    color: var(--color-text);
}

/* Mismo criterio que arriba, pero para el "típico prohibido" (documento ya
   clasificado, arrastrado sobre una carpeta del árbol en vez del Baúl). */
.w360-docs-tree-item.docs-folder-tile.drag-forbidden {
    cursor: not-allowed;
    background: var(--color-danger-bg);
    color: var(--color-text);
    opacity: 0.4;
}

.w360-docs-tree-vault { border-top: 1px solid var(--color-border-soft); margin-top: 4px; padding-top: 9px; }

/* Destino de drop del Baúl (data-vault-drop, docs-explorer-dnd.js): mismo
   tratamiento visual que una carpeta destino válida (.docs-folder-tile.drag-
   over de arriba), pero el botón del Baúl no lleva esa clase (no es una
   carpeta real). */
.w360-docs-tree-vault.drag-over {
    background: var(--color-success-bg, #e6f6ec);
    color: var(--color-text);
}

/* Grupos "por entidad" del árbol del Explorador (documents.php): un
   <details> por tipo de entidad, envolviendo su propio subárbol de
   categorías (mismos .w360-docs-tree-item, con conteos SCOPED a esa
   entidad) — ver PanelController::countDocumentsByCategoryAndEntityTypeTenantWide().
   Colapsados por defecto (5 grupos abiertos a la vez sería demasiado
   largo). El marcador nativo de <summary> se sustituye por un chevron
   propio para que se vea como una fila más del árbol, no como un
   <details> genérico. */
.w360-docs-tree-group > summary.w360-docs-tree-item {
    list-style: none;
}
.w360-docs-tree-group > summary.w360-docs-tree-item::-webkit-details-marker {
    display: none;
}
.w360-docs-tree-chevron {
    transition: transform 0.15s ease;
}
.w360-docs-tree-group[open] > summary .w360-docs-tree-chevron {
    transform: rotate(90deg);
}

.w360-docs-main { min-width: 0; }

.w360-docs-table-toolbar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 10px;
}

.w360-docs-sort { font-size: 12.5px; color: var(--color-text-secondary); display: flex; align-items: center; gap: 6px; }
.w360-docs-sort select {
    font-size: 12.5px;
    padding: 4px 8px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border-soft);
    background: var(--color-surface);
    color: var(--color-text);
}

.w360-docs-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.w360-docs-table th {
    text-align: left;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .02em;
    color: var(--color-text-secondary);
    padding: 8px 10px;
    border-bottom: 1px solid var(--color-border-soft);
}

/* Cabeceras de columna ordenables (Nombre/Estado/Tipo en Machine 360, ver
   AssetTabService::documentacionTab()) — sustituyen al <select> "Ordenar"
   de antes (.w360-docs-sort, que sigue existiendo para Worker 360, que no
   ha migrado a este patrón). data-action="sort-doc-column" (ver
   worker-documents-table.js) alterna asc/desc al pulsar; el icono neutro
   (ambas flechas) pasa a asc/desc solo en la columna activa. */
.w360-docs-th-sortable {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}
.w360-docs-th-sortable:hover { color: var(--color-text); }
.w360-docs-sort-icon {
    font-size: 12px;
    opacity: .45;
    margin-left: 2px;
    vertical-align: -1px;
}
.w360-docs-th-sortable[data-sort-dir] { color: var(--color-primary); }
.w360-docs-th-sortable[data-sort-dir] .w360-docs-sort-icon { opacity: 1; }

.w360-docs-row { cursor: pointer; transition: background-color .1s ease; }
.w360-docs-row:hover { background: var(--color-surface-strong); }
.w360-docs-row.is-selected { background: var(--color-primary-bg, var(--color-info-bg)); }
.w360-docs-row.is-selected:hover { background: var(--color-primary-bg, var(--color-info-bg)); }
.w360-docs-row[hidden], .w360-docs-detail-row[hidden] { display: none; }

/* Llegada por deep-link (?doc=<id>, ver highlightDeepLinkedDoc() en
   asset-360.js): flash breve para que el ojo encuentre la fila tras el
   scroll automático, sin dejar un estado "seleccionado" permanente como
   .is-selected. */
.w360-docs-row.is-doc-highlighted { animation: w360-docs-row-flash 2.5s ease-out; }
@keyframes w360-docs-row-flash {
    0%, 15% { background: var(--color-warning-bg, #fff3cd); }
    100% { background: transparent; }
}
.w360-docs-row td { padding: 9px 10px; border-bottom: 1px solid var(--color-border-soft); vertical-align: middle; }

.w360-docs-col-drag { width: 22px; }
.w360-docs-col-drag .doc-card-drag-handle { cursor: grab; color: var(--color-text-secondary); opacity: .6; }
.w360-docs-col-drag .doc-card-drag-handle:hover { opacity: 1; }

.w360-docs-col-name { display: flex; align-items: center; gap: 8px; font-weight: 500; }
.w360-docs-col-name i { font-size: 16px; color: var(--color-text-secondary); flex-shrink: 0; }
.w360-docs-name-stack { display: flex; flex-direction: column; line-height: 1.35; min-width: 0; }
.w360-docs-filename { font-weight: 400; font-size: 11.5px; }

.w360-docs-col-actions { text-align: right; }
.w360-docs-actions { display: flex; align-items: center; justify-content: flex-end; gap: 2px; flex-wrap: nowrap; }
/* .w360-docs-move-select ahora es tag-select-init (ver docMoveSelect() en
   WorkerTabService.php) — TagSelect construye su propio .tag-select-display
   con la altura/padding de un campo de formulario completo (con label
   flotante), demasiado alto para una celda de tabla compacta; se
   sobreescribe aquí, scoped a esta tabla, sin tocar el aspecto del
   componente en el resto de la app (filtros, modales...). */
.w360-docs-table .tag-select { max-width: 150px; }
.w360-docs-table .tag-select-display {
    height: auto;
    min-height: 0;
    padding: 4px 6px;
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    /* docMoveSelect() (WorkerTabService/AssetTabService) es SIEMPRE selección
       única — nunca necesita el flex-wrap:wrap de la regla base (pensado
       para que los chips de un multiSelect salten de línea). Sin este
       override, flexbox decide qué cabe en la línea usando el ancho NATURAL
       (sin encoger) de cada hijo: un valor largo (p.ej. "Seguro de
       Circulación") + el botón de flecha no caben juntos, así que el
       algoritmo manda el botón entero a una segunda línea — la fila dobla
       su alto — en vez de encoger el texto (aunque tenga min-width:0 y
       ellipsis, nunca llega a aplicarse porque nunca comparte línea con el
       botón). nowrap fuerza a que compartan línea, y AHÍ SÍ se encoge el
       texto y el ellipsis entra en juego. */
    flex-wrap: nowrap;
}
.w360-docs-table .tag-select-display:hover { background: var(--color-border-soft); }
.w360-docs-table .tag-select-display.focused { border-color: var(--color-border); background: var(--color-surface); }
.w360-docs-table .tag-select-placeholder,
.w360-docs-table .tag-select-single-value { font-size: 12px; }
.w360-docs-table .tag-select-toggle { width: 18px; height: 18px; font-size: 14px; }

/* Columna "Estado": <select> nativo con pinta de badge coloreado (ver
   docStatusControl() en WorkerTabService.php) — se quita el chrome nativo
   del desplegable y se sustituye por una flechita propia en semitransparencia,
   que se apoya en cualquier fondo pastel de .badge-success/warning/danger/info. */
.w360-docs-status-select {
    border: none;
    cursor: pointer;
    font: inherit;
    -webkit-appearance: none;
    appearance: none;
    padding-right: 22px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='%23000' fill-opacity='.45' d='M2.5 4.5l3.5 3.5 3.5-3.5z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 7px center;
    background-size: 9px;
}

.w360-docs-status-select:hover { filter: brightness(0.96); }
.w360-docs-status-select:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }

/* Fila de detalle plegada: preview + campos, se despliega al hacer click en
   la fila (ver worker-documents-table.js), colspan completo. */
.w360-docs-detail-row td { padding: 0; border-bottom: 1px solid var(--color-border-soft); }
.w360-docs-detail {
    display: flex;
    gap: 20px;
    padding: 16px;
    background: var(--color-surface-strong);
}
.w360-docs-detail-preview {
    flex-shrink: 0;
    width: 160px;
    height: 110px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-surface);
    border: 1px solid var(--color-border-soft);
    border-radius: var(--radius-md);
    overflow: hidden;
}
.w360-docs-detail-preview-img { width: 100%; height: 100%; object-fit: cover; }
.w360-docs-detail-nopreview { display: flex; flex-direction: column; align-items: center; gap: 6px; color: var(--color-text-secondary); }
.w360-docs-detail-nopreview i { font-size: 32px; }
.w360-docs-detail-fields { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.w360-docs-uploader-avatar { width: 20px; height: 20px; border-radius: 50%; object-fit: cover; vertical-align: middle; margin-right: 4px; }

.w360-docs-empty-filter { padding: 20px 0; text-align: center; }

/* Tarjetas de "Documentos faltantes obligatorios/opcionales" en Worker
   360 — mismo botón "Subir" compacto que ya usa Machine 360 (antes una
   zona de soltar/subir a pantalla completa que ocupaba mucho más alto).
   Columnas algo más anchas que el .doc-missing-grid genérico para que
   nombre + botón quepan cómodos sin envolver a 3 líneas; .doc-missing-info
   en fila (en vez del <br> del HTML, que sin esto dejaba "Obligatorio"/
   "Opcional" en su propia línea, huérfano bajo el nombre). */
.w360-docs-missing-grid {
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
.w360-docs-missing-grid .doc-missing-card {
    padding: 10px 12px;
    gap: 8px;
}
.w360-docs-missing-grid .doc-missing-icon i { font-size: 18px; }
.w360-docs-missing-grid .doc-missing-info {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px;
}
.w360-docs-missing-grid .doc-missing-info strong { font-size: 12.5px; }
.w360-docs-missing-grid .doc-missing-info small { font-size: 10.5px; white-space: nowrap; }

.w360-docs-missing-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--color-border-soft);
    font-size: 12.5px;
    flex-wrap: wrap;
}

/* Editor de "Contenido" del generador de plantillas (document-templates.js,
   VariableRichEditor.mount() → Quill vía CDN, ver #version-editor-mount en
   plantillas.php). Estructura real: VariableRichEditor crea un <div> HIJO
   dentro de #version-editor-mount y monta Quill sobre ÉL — Quill inserta la
   barra (.ql-toolbar.ql-snow) como HERMANA de ese div (que pasa a ser
   .ql-container.ql-snow), pero ambas quedan DENTRO de #version-editor-mount,
   no fuera. Quill trae de fábrica (quill.snow.css) ".ql-container{height:100%}"
   y ".ql-editor{height:100%; overflow-y:auto}" — NO basta con quitar un
   override propio si lo hubiera: hay que neutralizar explícitamente los de
   Quill, o el editor sigue creando su propio scroll interno además del de
   ".modal-body" (único scroll real del modal, ver modal.css), dando DOS
   barras simultáneas. position:sticky en la barra la fija al desplazarse
   ".modal-body" sin más contenedores con overflow de por medio entre ambas. */
#version-editor-mount .ql-container.ql-snow,
#version-editor-mount .ql-editor {
    height: auto;
    max-height: none;
    overflow-y: visible;
}

#version-editor-mount .ql-toolbar.ql-snow {
    position: sticky;
    top: 0;
    z-index: 3;
    background: var(--color-surface-strong, var(--color-bg));
}

/* Los pickers nativos de Quill (header/align/"Estilo del diseño") abren su
   menú (.ql-picker-options) en position:absolute sin z-index propio — entre
   dos con z-index:auto gana el orden del DOM, así que un picker posterior
   puede pintarse por encima del menú ABIERTO de uno anterior si sus cajas se
   solapan (más probable con .ql-docStyle, más ancho que el resto). Quill
   marca el picker abierto con la clase .ql-expanded — usarla para que ese
   siempre gane, sin depender del orden. */
#version-editor-mount .ql-toolbar.ql-snow .ql-picker {
    position: relative;
    z-index: 1;
}

#version-editor-mount .ql-toolbar.ql-snow .ql-picker.ql-expanded {
    z-index: 5;
}

