/* =====================================================================
   zv-components.css  --  Kit UI PaskayMind, componentes propios (prefijo zv-)
   Migracion a Dinamica, fase F1. Guia: 04-guia-design-system-zolvia §4, §5, §6.2, §6.3.
   ---------------------------------------------------------------------
   QUE VIVE ACA: los componentes que PaskayMind tiene y Bootstrap no. El markup
   que SI tiene equivalente Bootstrap no se reimplementa: se re-tematiza en
   paskaymind-bootstrap-theme.css (guia §6.1). Un componente zv- que envuelva
   markup BS gana por SELECTOR COMPUESTO (.zv-x .btn), nunca por !important.

   UN SOLO PREFIJO: el panel admin del origen usaba cc-*; aca todo es zv-*.

   ASCII ONLY (K5). Los glifos van por escape CSS ("\2713"), nunca literales.
   Los iconos NO van aca: viven en zv-icons.css (decision F1 de la bitacora).

   K9 -- POR QUE LOS EDITORES NO USAN @media: dentro de Dinamica cada action
   puede vivir en un iframe con ancho propio (openDialog de 380px, homeContent
   de ancho variable). Un @media de 860px mide el ancho del IFRAME, no el del
   dispositivo: un editor abierto en un dialogo angosto dispararia el layout
   movil con el usuario en desktop. Por eso .zv-editor-narrow/-wide/-grid son
   CLASES EXPLICITAS. Los @media de este archivo se reservan al shell y al
   portal, que si ocupan el viewport completo.
   ===================================================================== */

@layer zv-components {

  /* ===================================================================
     1. LAYOUT SHELL  (guia §4.7 -- .shell/.side/.topbar/.content)
     =================================================================== */
  .zv-shell {
    display: grid;
    grid-template-columns: 240px 1fr;
    min-height: 100%;
  }

  .zv-sidebar {
    background: var(--zv-sidebar-bg);
    color: var(--zv-sidebar-text);
    padding: var(--sp-5) var(--sp-3);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
  }
  .zv-sidebar nav { display: flex; flex-direction: column; gap: 2px; }
  .zv-sidebar nav a {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--r-input);
    color: var(--zv-sidebar-text);
    text-decoration: none;
    font-weight: 500;
    font-size: 14.5px;
    transition: background var(--t), color var(--t);
  }
  .zv-sidebar nav a:hover { background: rgba(255,255,255,.08); }
  .zv-sidebar nav a.active { background: rgba(255,255,255,.14); font-weight: 600; }
  .zv-sidebar nav a svg { width: 18px; height: 18px; flex: 0 0 18px; }
  .zv-sidebar .zv-sidebar-muted { color: var(--zv-sidebar-muted); font-size: 12px; }
  /* Anillo propio: sobre fondo oscuro el --ring ciruela no se distingue. */
  .zv-sidebar nav a:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(233,229,242,.5); }

  .zv-topbar {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    height: 50px;
    padding: 0 var(--sp-4);
    background: var(--surface);
    border-bottom: 1px solid var(--border);
  }
  .zv-topbar .zv-who { margin-left: auto; display: flex; align-items: center; gap: var(--sp-2); }
  .zv-topbar .zv-who-name { font-weight: 600; font-size: 14px; color: var(--text); }
  .zv-topbar .zv-who-role { font-size: 12px; color: var(--text-muted); }

  /* Variante admin del topbar (era .cc-topbar en el origen). */
  .zv-topbar--admin { background: var(--zv-sidebar-bg); border-bottom-color: transparent; }
  .zv-topbar--admin .zv-who-name { color: var(--zv-sidebar-text); }
  .zv-topbar--admin .zv-who-role { color: var(--zv-sidebar-muted); }

  /* Densidad calcada de `.content` de Psico (public/styles.css:209): sp-6 de
     padding. PaskayMind venia con sp-5, o sea 20% mas apretado.

     🚩 EL TOPE DE 1180px SE FUE (2026-08-01). Se habia copiado junto con la
     densidad, y no era densidad: era el defecto. A 2554px de viewport el
     contenido se cortaba a 1180 y sobraba media pantalla -- exactamente lo que
     muestra "disponibilidad-05-vista-ancha.png" del repo de Psico.
     La respuesta NO es otro tope global. El ancho sobrante se reparte con tres
     medidas quirurgicas, y las tres viven abajo:
       1) los paneles reflowean a 2 columnas (.zv-paneles),
       2) la columna de acciones de las tablas se queda de su ancho
          (.zv-col-acciones) para que el sobrante vaya al texto,
       3) SOLO la prosa lleva tope, y en ch y no en px, para que acompanie al
          tamanio de fuente y no deje margen muerto dentro del panel. */
  .zv-content {
    padding: var(--sp-6);
    background: var(--bg);
    min-width: 0;
    width: 100%;
  }

  /* 3) Tope de LECTURA, no de layout: se le pone al texto corrido y a nada
     mas. 78ch son unas 78 letras por linea -- por encima de 90 la vista pierde
     el renglon al volver. Va en ch a proposito: en px habria que recalcularlo
     cada vez que cambia la tipografia. */
  .zv-hint,
  .zv-panel-body > p,
  .form-text { max-width: 78ch; }
  .zv-page-title {
    font-family: var(--font-display);
    font-size: 22px;
    font-weight: 700;
    color: var(--primary-dark);
    margin: 0 0 var(--sp-4);
  }

  .zv-avatar {
    width: 32px; height: 32px;
    border-radius: var(--r-full);
    background: var(--primary-soft);
    color: var(--primary-on-soft);
    display: inline-flex; align-items: center; justify-content: center;
    font-weight: 700; font-size: 13px; line-height: 1.2;
    flex: 0 0 32px;
  }
  .zv-avatar--lg { width: 44px; height: 44px; flex-basis: 44px; font-size: 16px; }

  .zv-nav-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px; padding: 0 5px;
    border-radius: var(--r-full);
    background: var(--accent); color: var(--text);
    font-size: 11px; font-weight: 700;
  }

  /* ===================================================================
     2. MARCA  (guia §4.1 -- .brand/.mark/.dot, unificados)
     =================================================================== */
  .zv-brand { display: inline-flex; align-items: center; gap: var(--sp-2); }
  .zv-brand-mark {
    width: 28px; height: 28px;
    border-radius: 9px;
    background: var(--primary);
    color: var(--zv-on-primary);
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 28px;
  }
  .zv-brand-mark svg { width: 21px; height: 21px; }
  .zv-brand-name {
    font-family: var(--font-display);
    font-weight: 700; font-size: 17px; letter-spacing: .01em;
    color: var(--primary);
  }
  .zv-sidebar .zv-brand-name { color: var(--zv-sidebar-text); }
  .zv-brand-dot { width: 6px; height: 6px; border-radius: var(--r-full); background: var(--accent); }

  /* ===================================================================
     3. PANELES Y TARJETAS  (guia §4.5 -- .panel/.cards/.stat)
     =================================================================== */
  .zv-panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-panel);
    box-shadow: var(--shadow-xs);
    overflow: hidden;
  }
  .zv-panel-head {
    display: flex; align-items: center; gap: var(--sp-3);
    padding: var(--sp-4) var(--sp-5);
    background: var(--surface-alt);
    border-bottom: 1px solid var(--border);
    font-weight: 600; color: var(--primary-dark);
  }
  .zv-panel-body { padding: var(--sp-5); }   /* `.panel` de Psico usa sp-5 */
  /* Selector compuesto: gana por especificidad sobre el .btn de Bootstrap
     sin recurrir a !important (guia §7.4). */
  .zv-panel-head .btn { margin-left: auto; }
  /* Mismo empuje para la accion en icono (volver al menu), que no es un .btn.
     Va aparte y no junto al selector de arriba para no arrastrar el resto de
     los estilos de boton al icono. */
  .zv-panel-head .zv-ico-btn { margin-left: auto; }

  .zv-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--sp-4);
  }
  .zv-stat {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-card);
    padding: var(--sp-4);
    box-shadow: var(--shadow-xs);
    transition: box-shadow var(--t), transform var(--t);
  }
  .zv-stat:hover { box-shadow: var(--shadow-md); }
  .zv-stat .zv-stat-k {
    font-size: 12px; font-weight: 600;
    color: var(--text-muted);
    text-transform: uppercase; letter-spacing: .04em;
  }
  .zv-stat .zv-stat-v {
    font-family: var(--font-display);
    font-weight: 700; line-height: 1.12;
    font-size: clamp(20px, 4.2vw, 28px);
    color: var(--primary-dark);
    margin-top: var(--sp-1);
  }
  .zv-stat .zv-stat-v.zv-money { color: var(--success-on-soft); }
  .zv-stat.zv-warn { border-color: var(--warning); background: #F6EAD4; }
  .zv-stat.zv-warn .zv-stat-v { color: var(--warning-on-soft); }

  /* KPI del panel admin (era .kpi.k-*). */
  .zv-kpi { border-left: 4px solid var(--primary); }
  .zv-kpi--ok   { border-left-color: var(--success); }
  .zv-kpi--warn { border-left-color: var(--warning); }
  .zv-kpi--bad  { border-left-color: var(--danger); }

  /* ===================================================================
     4. TAGS DE ESTADO  (guia §4.4 -- .tag.a/.c/.e/.n/.p/.r)
     Nombres COMPLETOS, no de una letra: `.tag.e` no decia nada, y el
     `.tag` sin modificador de app.js:1427 era un bug latente (fondo
     transparente). Aca el modificador es obligatorio y hay default.
     =================================================================== */
  .zv-tag {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 3px 10px;
    border-radius: var(--r-full);
    font-size: 11.5px; font-weight: 700;
    line-height: 1.3;
    background: var(--surface-alt);
    color: var(--text-muted);
    white-space: nowrap;
  }
  .zv-tag--activo    { background: var(--primary-soft); color: var(--primary-on-soft); }
  .zv-tag--realizada { background: var(--success-soft); color: var(--success-on-soft); }
  .zv-tag--pagado    { background: var(--success-soft); color: var(--success-on-soft); }
  .zv-tag--pendiente { background: #F6EAD4;             color: var(--warning-on-soft); }
  .zv-tag--cancelada { background: var(--danger-soft);  color: var(--danger-on-soft); }
  .zv-tag--noshow    { background: var(--danger-soft);  color: var(--danger-on-soft); }

  /* ===================================================================
     5. ALERTAS PROPIAS  (guia §4.6 -- .err/.ok/.sunat-box)
     =================================================================== */
  .zv-alert {
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-card);
    border: 1px solid var(--border);
    font-size: 14px;
  }
  .zv-alert--error { background: var(--danger-soft);  border-color: var(--danger);  color: var(--danger-on-soft); }
  .zv-alert--ok    { background: var(--success-soft); border-color: var(--success); color: var(--success-on-soft); }
  .zv-alert--sunat { background: var(--info-soft);    border-color: var(--primary-line); color: var(--primary-on-soft); }
  /* Ambar: algo que TODAVIA no rompio nada pero va a cambiar solo si nadie
     interviene -- el caso del periodo de disponibilidad que esta por vencer.
     No es --error: no hay nada mal, hay algo que avisar. */
  .zv-alert--warning { background: #FBF0DC; border-color: var(--warning); color: var(--warning-on-soft); }
  .zv-alert-retry { margin-left: var(--sp-2); text-decoration: underline; cursor: pointer; }

  /* ===================================================================
     6. FORMULARIOS PROPIOS  (guia §4.3 -- .field/.row + §6.3 editores)
     =================================================================== */
  .zv-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
  .zv-row--3 { grid-template-columns: 1fr 1fr 1fr; }
  .zv-field { display: flex; flex-direction: column; gap: var(--sp-1); }
  .zv-field > label { font-size: 13px; font-weight: 600; color: var(--text-muted); }

  /* Layout de editor a pagina completa (§6.3, DISENO NUEVO: PaskayMind solo
     tenia formularios dentro de dialogos). Clases explicitas por K9. */
  .zv-editor-narrow { max-width: 760px; margin: 0 auto; }
  .zv-editor-wide   { max-width: 980px; margin: 0 auto; }
  .zv-editor-grid   { max-width: 100%; }

  .zv-field-row {
    display: grid;
    grid-template-columns: 190px minmax(0, 1fr);
    gap: var(--sp-3);
    align-items: start;
    padding: var(--sp-2) 0;
  }
  .zv-field-row > label {
    font-size: 13.5px; font-weight: 600;
    color: var(--text-muted);
    padding-top: 9px;
  }
  .zv-editor-grid .zv-field-row {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-1);
  }
  .zv-editor-grid .zv-field-row > label { padding-top: 0; }
  /* El editor-grid apila los campos en 2 columnas de campos completos. */
  .zv-editor-grid .zv-editor-cols {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 0 var(--sp-5);
  }
  .zv-editor-actions {
    display: flex; flex-wrap: wrap; gap: var(--sp-2);
    justify-content: flex-end;
    padding-top: var(--sp-4);
    margin-top: var(--sp-4);
    border-top: 1px solid var(--border);
  }
  .zv-required { color: var(--danger); font-weight: 700; }

  /* ===================================================================
     7. DIALOGOS  (guia §5.1 -- .modal/.modal-bg de ui.js)
     Se llaman zv-dialog y NO .modal a proposito: .modal es contrato de
     Bootstrap y el picklist del framework NO usa bootstrap.Modal. Dos
     mecanismos con el mismo nombre de clase es una fuga garantizada.
     =================================================================== */
  .zv-dialog-bg {
    position: fixed; inset: 0;
    background: rgba(27,29,54,.45);
    display: flex; align-items: center; justify-content: center;
    padding: var(--sp-4);
    z-index: 1000;
    animation: zv-fade-in 160ms cubic-bezier(.2,.6,.2,1);
  }
  /* El display:flex de arriba le GANA al atributo hidden, que solo trae el
     display:none de la hoja del navegador. Sin esta regla, un dialogo escrito
     en la plantilla (y no creado por JS, como los de zv-overlay.js) nace
     abierto y tapando la pantalla. */
  .zv-dialog-bg[hidden] { display: none; }
  .zv-dialog {
    background: var(--surface);
    border-radius: var(--r-modal);
    box-shadow: var(--shadow-lg);
    width: min(560px, 100%);
    max-height: calc(100vh - 2 * var(--sp-4));
    display: flex; flex-direction: column;
    overflow: hidden;
    animation: zv-rise 180ms cubic-bezier(.2,.6,.2,1);
  }
  .zv-dialog-head {
    padding: var(--sp-4);
    border-bottom: 1px solid var(--border);
    font-family: var(--font-display);
    font-weight: 700; font-size: 17px;
    color: var(--primary-dark);
    display: flex; align-items: center; gap: var(--sp-3);
  }
  .zv-dialog-body { padding: var(--sp-4); overflow: auto; line-height: 1.55; }
  .zv-dialog-msg { color: var(--text); font-size: 14.5px; }
  /* Segundo parrafo del dialogo: la consecuencia de aceptar, en tono menor.
     Lo mandan las plantillas como `texto` y hasta 2026-08-01 se descartaba. */
  .zv-dialog-detalle { color: var(--text-muted); font-size: 13px; margin-top: var(--sp-2); }
  .zv-dialog-actions {
    display: flex; gap: var(--sp-2); justify-content: flex-end;
    padding: var(--sp-3) var(--sp-4);
    border-top: 1px solid var(--border);
    background: var(--surface-alt);
  }
  .zv-dialog-err { color: var(--danger-on-soft); font-size: 13px; margin-top: var(--sp-2); }
  .zv-dialog-close {
    margin-left: auto;
    background: none; border: 0; padding: 4px;
    border-radius: var(--r-input);
    color: var(--text-muted);
    display: inline-flex;
  }
  .zv-dialog-close:hover { background: var(--primary-soft); color: var(--primary-on-soft); }
  .zv-dialog-close svg { width: 16px; height: 16px; }

  @keyframes zv-fade-in { from { opacity: 0 } to { opacity: 1 } }
  @keyframes zv-rise { from { opacity: 0; transform: translateY(8px) } to { opacity: 1; transform: none } }

  .zv-fading { opacity: 0; transition: opacity var(--t); }
  .zv-fading.zv-in { opacity: 1; }

  /* ===================================================================
     8. TOASTS  (guia §5.1)
     =================================================================== */
  .zv-toast-stack {
    position: fixed;
    right: var(--sp-4); bottom: var(--sp-4);
    display: flex; flex-direction: column-reverse; gap: var(--sp-2);
    z-index: 1100;   /* por encima de .zv-dialog-bg (1000): regla §5.7 */
    max-width: min(360px, calc(100vw - 2 * var(--sp-4)));
  }
  .zv-toast {
    background: var(--zv-sidebar-bg);
    color: var(--zv-sidebar-text);
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-card);
    box-shadow: var(--shadow-lg);
    font-size: 14px;
    display: flex; align-items: center; gap: var(--sp-3);
    animation: zv-rise 180ms cubic-bezier(.2,.6,.2,1);
  }
  .zv-toast--ok    { border-left: 4px solid var(--success); }
  .zv-toast--error { border-left: 4px solid var(--danger); }
  .zv-toast--accion { cursor: pointer; }
  .zv-toast-accion {
    margin-left: auto;
    background: none; border: 0;
    color: var(--accent);
    font-weight: 700; font-size: 13px;
    text-decoration: underline;
  }

  /* ===================================================================
     9. SPINNERS Y ESTADO DE CARGA  (guia §6.2 -- .spin/.btn-spin/.loading)
     =================================================================== */
  .zv-spin {
    width: 18px; height: 18px;
    border: 2px solid var(--primary-line);
    border-top-color: var(--primary);
    border-radius: var(--r-full);
    animation: zv-rotate .9s linear infinite;
    display: inline-block;
  }
  .zv-btn-loading { position: relative; pointer-events: none; opacity: .75; }
  .zv-loading { opacity: .6; pointer-events: none; }
  @keyframes zv-rotate { to { transform: rotate(360deg) } }

  /* Skeletons del panel admin (eran .skel*). */
  .zv-skeleton {
    background: linear-gradient(90deg, var(--surface-alt) 25%, var(--primary-soft) 37%, var(--surface-alt) 63%);
    background-size: 400% 100%;
    animation: zv-shimmer 1.4s ease infinite;
    border-radius: var(--r-input);
    min-height: 14px;
  }
  .zv-skeleton-row { height: 38px; margin-bottom: var(--sp-2); }
  @keyframes zv-shimmer { from { background-position: 100% 50% } to { background-position: 0 50% } }

  /* ===================================================================
     10. GRIDS: ORDEN Y PAGINACION  (guia §6.3, base .th-sort del admin)
     =================================================================== */
  .zv-th-sort { cursor: pointer; user-select: none; white-space: nowrap; }
  .zv-th-sort[aria-sort] { color: var(--primary-dark); }
  .zv-grid-wrap { overflow-x: auto; }
  .zv-grid-foot {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-top: 1px solid var(--border);
    background: var(--surface-alt);
    font-size: 13px; color: var(--text-muted);
  }
  .zv-grid-foot .zv-grid-progress { margin-left: auto; display: none; }
  .zv-grid-foot .zv-grid-progress.zv-on { display: inline-flex; }

  /* Mensaje "sin registros" con CTA integrado (§6.3, DISENO NUEVO: el
     origen solo tenia un .empty generico, sin variante con boton). */
  .zv-notfound {
    text-align: center;
    padding: var(--sp-10) var(--sp-5);
    color: var(--text-muted);
  }
  .zv-notfound-icon { color: var(--primary-line); margin-bottom: var(--sp-3); }
  /* El trazo va ACA y no via class="zv-ico" en el markup. MEDIDO el 2026-08-04:
     el <svg> de los 18 message.htm no llevaba ninguna clase, asi que heredaba
     el `fill: black` inicial de SVG y la lupa se pintaba como un PUNTO NEGRO en
     todos los estados vacios del sistema -- Notas, Recibos, Movimientos y el
     resto. Ningun test lo detecta: el elemento existe y el texto es correcto.
     Por que no se agrego class="zv-ico" a los 18: .zv-ico vive en zv-icons.css
     FUERA de todo @layer, y una regla sin capa le gana a cualquiera dentro de
     una capa por mas especifica que sea. Su `width:16px` habria pisado a este
     selector y el icono habria pasado de 40px a 16px -- cambiando un defecto
     por otro. Una regla aca arregla los 18 archivos sin tocar el markup. */
  .zv-notfound-icon svg {
    width: 40px; height: 40px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .zv-notfound-title { font-weight: 600; color: var(--text); margin-bottom: var(--sp-1); }
  .zv-notfound-actions { margin-top: var(--sp-4); }

  /* ===================================================================
     11. PICKLIST (LOOKUP)  (guia §6.3 -- shell propio, NO bootstrap.Modal)
     =================================================================== */
  .zv-pick-modal {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-modal);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    height: 100%;
    display: flex; flex-direction: column;
  }
  .zv-pick-header {
    display: flex; align-items: center; gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    background: var(--surface-alt);
    border-bottom: 1px solid var(--border);
    font-weight: 600; color: var(--primary-dark);
  }
  .zv-pick-header .zv-pick-tools { margin-left: auto; display: flex; gap: var(--sp-1); }
  .zv-pick-body { padding: var(--sp-3); overflow: auto; flex: 1 1 auto; }
  .zv-pick-trigger { display: flex; gap: var(--sp-1); align-items: stretch; }
  .zv-pick-trigger .form-control { border-top-right-radius: 0; border-bottom-right-radius: 0; }

  /* Visor de documento / imagen (§6.3). */
  .zv-viewer { background: var(--zv-sidebar-bg); border-radius: var(--r-card); padding: var(--sp-3); text-align: center; }
  .zv-viewer img { max-width: 100%; height: auto; border-radius: var(--r-input); }
  .zv-viewer-empty { color: var(--zv-sidebar-muted); font-size: 13px; padding: var(--sp-6) 0; }

  /* ===================================================================
     12. AUDITORIA DE CAMPO  (guia §6.3 -- bloque sin fuente PaskayMind)
     =================================================================== */
  .zv-audit-field { display: flex; align-items: center; gap: var(--sp-1); }
  /* min-width/height 24px: WCAG 2.2 AA 2.5.8 (Target Size Minimum). Medido
     antes, era 27x21 -- pasaba de ancho y fallaba de alto por 3px. */
  .zv-audit-btn {
    background: none; border: 1px solid var(--zv-control-line);
    border-radius: var(--r-input);
    padding: 3px 6px;
    color: var(--text-muted);
    display: inline-flex;
    align-items: center; justify-content: center;
    min-width: 24px; min-height: 24px;
  }
  .zv-audit-btn:hover { background: var(--primary-soft); color: var(--primary-on-soft); border-color: var(--primary-line); }
  .zv-audit-btn svg { width: 14px; height: 14px; }
  .zv-audit-hist { list-style: none; margin: 0; padding: 0; }
  .zv-audit-hist li {
    padding: var(--sp-2) 0;
    border-bottom: 1px solid var(--border);
    font-size: 13.5px;
  }
  .zv-audit-hist li:last-child { border-bottom: 0; }
  .zv-audit-when { color: var(--text-muted); font-size: 12px; }

  /* ===================================================================
     13. HUERFANOS RESCATADOS  (los 7 del critico, guia §6.2)
     =================================================================== */
  /* min-height 24px por WCAG 2.2 AA 2.5.8: es un enlace SUELTO, no uno dentro
     de una frase, asi que no le aplica la excepcion de enlaces en linea. Medido
     antes: 22px de alto. */
  .zv-back-link {
    display: inline-flex; align-items: center; gap: 6px;
    min-height: 24px;
    color: var(--primary-dark); text-decoration: none;
    font-size: 14px; font-weight: 600;
  }
  .zv-back-link:hover { text-decoration: underline; }
  .zv-back-link svg { width: 15px; height: 15px; }

  /* BLOQUE con sangria colgante -- ni inline-flex ni flex (decision F11).
     - `inline-flex` (lo que habia) se dimensiona a max-content: no envuelve,
       crece hasta caber el texto en UNA linea. Como .zv-panel lleva
       overflow:hidden, el sobrante no se scrollea: SE PIERDE. Medido a 320px,
       el hint de demo-picklist-trigger ocupaba 392px en un panel de 294px.
     - `flex` arregla el ancho pero rompe otra cosa: los hijos directos se
       vuelven flex items y se BLOQUIFICAN, asi que cada <code> inline pasa a
       display:block y se acomoda en su propia columna. Medido: el <code> de
       `bootstrap.Modal` seguia saliendose, ahora por otro motivo.
     El par padding-left / text-indent negativo deja al icono en su propio
     canalon, alinea todas las lineas y NO impone contrato de markup: el hint
     puede llevar <code>, <strong> o <a> inline sin que nada se blockifique.
     20px = 14px de icono + 6px de separacion. */
  .zv-hint {
    display: block;
    padding-left: 20px; text-indent: -20px;
    color: var(--text-muted); font-size: 12.5px;
  }
  .zv-hint svg {
    width: 14px; height: 14px; margin-right: 6px;
    display: inline-block; vertical-align: -2px;
  }

  .zv-section-label {
    font-size: 11.5px; font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-muted);
    margin: var(--sp-5) 0 var(--sp-2);
  }

  /* ===================================================================
     14. AGENDA: LEYENDA Y TEXTURAS  (guia §5.3; los .fc-* se dejan como
     selectores de la libreria, que es su contrato)
     =================================================================== */
  .zv-leyenda { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); font-size: 12.5px; }
  .zv-leyenda-item { display: inline-flex; align-items: center; gap: 6px; color: var(--text-muted); }
  .zv-leyenda-swatch { width: 12px; height: 12px; border-radius: 4px; border: 1px solid var(--border); }
  .zv-leyenda-sep { width: 1px; height: 14px; background: var(--border); }
  .zv-leyenda-nota { color: var(--text-muted); font-size: 12px; font-style: italic; }

  /* Texturas de estado de cita: rayado, SIN glifo. La guia §8.1 lo dice
     explicito -- el "\2713" tenia alternativa mejor y esta es. */
  .zv-cita-realizada {
    background-image: repeating-linear-gradient(45deg,
      transparent, transparent 5px,
      rgba(35,119,74,.18) 5px, rgba(35,119,74,.18) 10px);
  }
  .zv-cita-hold {
    background-image: repeating-linear-gradient(45deg,
      transparent, transparent 5px,
      rgba(168,107,18,.20) 5px, rgba(168,107,18,.20) 10px);
  }

  /* Chip de la leyenda: marcador semantico, sin pintura propia. Existe para
     que la leyenda se pueda contar por DOM (criterio C6) sin depender de la
     estructura interna de .zv-leyenda-item. */
  .zv-leyenda-chip { }

  /* El bloqueo sobre la reticula (.zv-blo) NO puede vivir en este archivo:
     todo zv-components.css esta dentro de @layer zv-components (linea 24) y
     FullCalendar inyecta SU css en runtime SIN capa, asi que le gana a
     cualquier especificidad de aca. Medido: el evento de fondo salia verde
     (#8fdf82, el default de la libreria) con la regla puesta aqui.
     Vive en paskaymind-bootstrap-theme.css, en el bloque N de FullCalendar, que
     es el unico sin capa. */

  /* Panel que ocupa la fila entera cuando el contenedor pasa a 2 columnas en
     pantallas anchas (tanda D). Fuera de una grilla no hace nada. */
  .zv-panel--ancho { grid-column: 1 / -1; }

  /* ===================================================================
     19. SIDEBAR PLANO  (2026-08-01)
     El dueño pidio la navegacion de Psico: TODOS los destinos visibles y a UN
     clic, sin acordeon. Psico lo hace con un solo <nav> de <a> (app.js:433).

     POR QUE SE RESUELVE DESDE ACA Y NO EN EL TEMPLATE DEL MENU. El colapso lo
     declara un <style> DENTRO de security/menu/template.htm (`.menuItems{
     max-height:0; overflow:hidden }`), y ese action es de los que el frameset
     carga por ruta fija: tocarlo obliga a reiniciar Tomcat. Este archivo, en
     cambio, lo sirve el DefaultServlet y sale fresco siempre. El menu ya carga
     zv-components.css (su template.htm linea 11), asi que la regla llega.

     POR QUE `!important` FUNCIONA CONTRA UN <style> QUE NO LO USA: la cascada de
     capas se INVIERTE para las declaraciones !important -- una !important dentro
     de @layer le gana a una normal sin capa. Es el unico caso en que algo de
     este archivo le gana a lo no apilado.
     =================================================================== */
  .menuBody .menuItems,
  .zv-sidebar .menuItems {
    max-height: none !important;
    overflow: visible !important;
    transition: none !important;
  }
  /* El titulo de seccion deja de ser un boton de acordeon y pasa a ser un
     rotulo: agrupa sin costar un clic. Sin cursor de mano y sin el chevron,
     que ahora mentiria. */
  .menuBody .menuTitle,
  .zv-sidebar .menuTitle {
    cursor: default;
    pointer-events: none;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    opacity: .55;
    padding-top: var(--sp-4);
  }
  .menuBody .menuTitle::after,
  .zv-sidebar .menuTitle::after { content: none !important; }
  /* El icono del rotulo tambien sobra: era la flecha del acordeon. Un rotulo
     que ya no despliega nada no lleva affordance de clic. */
  .menuBody .menuTitle .menuIco,
  .zv-sidebar .menuTitle .menuIco { display: none !important; }
  /* Una seccion sin titulo no deja hueco: asi un menu de un solo grupo se ve
     exactamente como el <nav> plano de Psico. */
  .menuBody .menuTitle:empty,
  .zv-sidebar .menuTitle:empty { display: none !important; }

  /* ===================================================================
     18. MODAL "CONFIGURA TU SEMANA"  (alta masiva de franjas)
     Reusa .zv-dialog* -- aca solo lo que ese kit no tenia: los toggles y la
     banda del contador. Vive DENTRO del iframe del action, asi que el
     position:fixed del fondo cubre el area de contenido, no el frameset.
     =================================================================== */
  .zv-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-4); }
  /* Un toggle no es un boton de accion: apagado se lee como contorno y
     encendido como relleno. Sin esa diferencia el usuario no sabe que eligio
     -- y el contador de abajo le mentiria sin que pueda notarlo. */
  .zv-chip {
    border: 1px solid var(--zv-control-line);
    background: var(--surface);
    color: var(--text);
    border-radius: var(--r-input);
    padding: 7px 13px;
    font-size: 13px; font-weight: 600;
    cursor: pointer;
    min-height: 36px;
    transition: background var(--t), border-color var(--t), color var(--t);
  }
  .zv-chip:hover { background: var(--primary-soft); border-color: var(--primary-line); }
  .zv-chip--on {
    background: var(--primary); border-color: var(--primary); color: var(--zv-on-primary);
  }
  .zv-chip--dia { min-width: 42px; text-align: center; }
  .zv-modal-label {
    display: block;
    font-size: 12.5px; font-weight: 700;
    color: var(--text-muted);
    margin-bottom: var(--sp-2);
  }
  .zv-rango { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
  .zv-rango input { width: 110px; }
  .zv-rango-sep { color: var(--text-muted); }
  .zv-resumen {
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: var(--r-input);
    padding: var(--sp-3);
    font-size: 13.5px;
    color: var(--text);
    margin-top: var(--sp-4);
  }

  /* ===================================================================
     17. PAGINA COMPUESTA DE PANELES  (Disponibilidad y reservas)
     =================================================================== */
  /* 1) REFLOW DE PANELES. Cuando sobra ancho, los paneles dejan de apilarse y
     pasan a dos columnas: el espacio se llena con CONTENIDO y no con aire.

     🚩 Sin @media a proposito. La nota K9 del kit avisa que un @media escrito
     en este archivo mide el ancho del IFRAME del action, no el del
     dispositivo: con --side en 230px, un viewport de 1090 produce un iframe de
     860 y disparar el layout movil con el usuario en desktop. `auto-fit` +
     `minmax` reflowea segun el ancho REAL del contenedor, que es justo lo que
     se quiere aca, y no puede equivocarse de medida porque no mide viewports.
     El min(520px, 100%) evita que la columna minima desborde un contenedor
     mas angosto que 520px -- sin el, en movil aparece scroll horizontal. */
  .zv-paneles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(520px, 100%), 1fr));
    gap: var(--sp-5);
    align-items: start;
  }

  /* 2) REPARTO DEL SOBRANTE DENTRO DE LA TABLA. Con table-layout auto, la
     celda que declara width:100% absorbe todo el espacio libre y las demas se
     quedan del ancho de su contenido. Asi el monitor ancho engorda la columna
     de texto (paciente, motivo, nombre del periodo) y NO la de botones, que
     era lo que quedaba de 400px por nada.

     OJO: la version anterior de esta regla apuntaba a :first-child, o sea POR
     POSICION. Funcionaba cuando la primera columna era un nombre y desfiguraba
     la tabla cuando era "Lunes" o un codigo corto: medido a 2560px daba
     [1719, 246, 71, 190]. Ahora el sobrante va a la columna que se DECLARA
     principal con .zv-col-principal, y si ninguna la lleva, reparto natural
     del navegador -- que nunca produce una columna de 1719px. */
  .zv-grid-wrap table th.zv-col-principal,
  .zv-grid-wrap table td.zv-col-principal { width: 100%; }
  /* El titulo del panel, separado del boton de accion que vive en el mismo
     head (.zv-panel-head .btn lo empuja a la derecha con margin-left:auto).
     Ahora es un <h2> (jerarquia semantica, antes <span>): font:inherit ya
     neutraliza el tamano/peso/line-height que trae un h2 de fabrica, pero
     NO cubre margin -- un h2 sin resetear trae ~0.83em de margen vertical
     que un span nunca tuvo, y lo separaria del icono/boton del mismo head
     flex. margin:0 deja el render identico al span de antes. */
  .zv-panel-title { font: inherit; margin: 0; }
  /* La frase que explica PARA QUE sirve el bloque, debajo del titulo. En Psico
     cada panel la lleva y es la mitad de lo que hace entendible la pantalla:
     sin ella el usuario ve una tabla y tiene que adivinar la regla de negocio.
     Va como primer hijo de .zv-panel-body y hereda el tope de 78ch de la prosa. */
  .zv-panel-desc {
    color: var(--text-muted);
    font-size: 13px;
    line-height: 1.5;
    max-width: 78ch;
    margin: 0 0 var(--sp-4);
  }
  /* Columna de acciones: se queda del ancho de sus botones y NO se lleva el
     sobrante cuando la tabla se estira en un monitor ancho.
     `text-align: right` agregado el 2026-08-05 (fase B2): la clase existia desde
     F1 y 23 grids seguian poniendo `text-end` crudo al lado, o en su lugar. Para
     que la clase REEMPLACE a la utilidad y no conviva con ella, tiene que
     llevarse tambien la alineacion. Con `width:1%` la celda ya mide lo que su
     contenido, asi que en los 7 grids que hoy usan la clase sin `text-end` el
     efecto visible es nulo -- verificado por huella. */
  .zv-col-acciones { width: 1%; white-space: nowrap; text-align: right; }
  /* Lista de destinos dentro de un panel: cada entrada es un boton y debajo la
     linea que explica a donde lleva. Se usa en "Seguridad de la cuenta", donde
     las tres opciones son pantallas propias y no acciones en linea. Apilada, y
     no en fila, porque el texto de apoyo es lo que evita que el psicologo entre
     a tantear. */
  .zv-linklist { display: flex; flex-direction: column; gap: var(--sp-4); }
  .zv-linklist > div { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
  .zv-linklist .form-text { margin: 0; }

  /* El codigo de reserva es una cadena que el psicologo copia y compara
     caracter por caracter: monoespaciada o no sirve. */
  .zv-cod {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    background: var(--surface-alt);
    border: 1px solid var(--border);
    border-radius: var(--r-input);
    padding: 2px 6px;
    color: var(--text);
  }

  /* ===================================================================
     15. CALENDARIO PROPIO DEL PORTAL  (guia §5.4)
     =================================================================== */
  .zv-cal { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
  .zv-cal-head { font-size: 11px; font-weight: 700; color: var(--text-muted); text-align: center; padding: 4px 0; }
  .zv-cal-day {
    aspect-ratio: 1 / 1;
    display: flex; align-items: center; justify-content: center;
    /* Cada dia es un control seleccionable: su borde lo identifica. */
    border: 1px solid var(--zv-control-line);
    border-radius: var(--r-input);
    background: var(--surface);
    font-size: clamp(13px, 3.6vw, 15px);
    color: var(--text);
    cursor: pointer;
    transition: background var(--t), border-color var(--t);
  }
  .zv-cal-day:hover { background: var(--primary-soft); border-color: var(--primary-line); }
  .zv-cal-day--sel { background: var(--primary); border-color: var(--primary); color: var(--zv-on-primary); font-weight: 700; }
  .zv-cal-day--libre { border-color: var(--success); }
  .zv-cal-day--off { color: var(--text-muted); background: var(--surface-alt); cursor: not-allowed; }
  /* Renombrado desde .cal-day.empty: colisionaba con el .empty generico. */
  .zv-cal-day--blank { border: 0; background: none; cursor: default; }
  .zv-cal-title { font-family: var(--font-display); font-weight: 700; color: var(--primary-dark); }

  /* ===================================================================
     16. PORTAL PUBLICO  (guia §5.4 -- se prepara en F1, se pone en pie en F6)
     =================================================================== */
  .zv-portal {
    min-height: 100%;
    background: var(--bg);
    display: flex; align-items: flex-start; justify-content: center;
    padding: var(--sp-6) var(--sp-4);
  }
  .zv-portal-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-hero);
    box-shadow: var(--shadow-lg);
    width: min(560px, 100%);
    padding: var(--sp-6);
    line-height: 1.55;
  }
  .zv-portal-head { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-5); }

  .zv-day-strip { display: flex; gap: var(--sp-2); overflow-x: auto; padding-bottom: var(--sp-2); }
  /* A11y (H-02): dia, horario y medio de pago pasaron de <div onclick> a <button>,
     operables por teclado. Reset de apariencia nativa acotado a `button.` (la clase
     .zv-slot la comparte el feature de retencion, que sigue siendo <div>) y sin pisar
     color/tipografia que las reglas .zv-* ya fijan. */
  button.zv-day-pill, button.zv-slot, button.zv-pay-method {
    font-family: inherit; margin: 0;
    box-sizing: border-box; appearance: none; -webkit-appearance: none;
  }
  /* CORREGIDO 2026-09-02. El `width: 100%` estaba en la regla de arriba, para
     los TRES. Para .zv-slot y .zv-pay-method es correcto -- viven en un grid y
     el 100% los estira dentro de su celda -- pero a .zv-day-pill lo destruia:
     esa vive en .zv-day-strip, que es un FLEX con overflow-x, y su regla propia
     dice `flex: 0 0 auto; min-width: 64px` para que entren varios dias y el
     resto se desplace. Como `button.zv-day-pill` (0,1,1) le gana en
     especificidad a `.zv-day-pill` (0,1,0), cada pildora tomaba el 100% del
     contenedor y SOLO SE VEIA UN DIA, con los otros 13 escondidos en el scroll.
     Medido en el navegador: 14 pildoras generadas, contenedor 510px, pildora
     510px, caben 1.00. Fue una regresion del pase de <div onclick> a <button>
     del punto H-02: el reset de apariencia nativa arrastro el ancho. */
  button.zv-slot, button.zv-pay-method { width: 100%; }
  button.zv-day-pill, button.zv-pay-method { color: inherit; }
  /* I-15: objetivo tactil >=44px (WCAG 2.5.5). Acotado a los <button> del
     portal (dia y horario); el <div>.zv-slot del feature de retencion no se toca. */
  button.zv-day-pill, button.zv-slot { min-height: 44px; }
  button.zv-day-pill:focus-visible, button.zv-slot:focus-visible, button.zv-pay-method:focus-visible {
    outline: 2px solid var(--ring); outline-offset: 2px;
  }
  /* Texto solo para lector de pantalla; el estado ocupado/sin-atencion se marca
     ademas de forma NO cromatica (borde punteado + opacidad en --off/--taken). */
  .zv-sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
  }
  /* H-06: aviso de politica de cancelacion/penalidad, notorio sin ser alarmista. */
  .zv-portal-politica { border-left: 3px solid var(--warning); padding-left: var(--sp-3); }
  .zv-day-pill {
    flex: 0 0 auto;
    min-width: 64px;
    padding: var(--sp-2);
    border: 1px solid var(--zv-control-line);
    border-radius: var(--r-card);
    background: var(--surface);
    text-align: center;
    line-height: 1.2;
    cursor: pointer;
    transition: background var(--t), border-color var(--t);
  }
  .zv-day-pill--sel { background: var(--primary); border-color: var(--primary); color: var(--zv-on-primary); }
  .zv-day-pill--off { background: var(--surface-alt); color: var(--text-muted); cursor: not-allowed; border-style: dashed; opacity: .6; }

  /* VISTA SEMANAL del portal (2026-09-02). Reemplaza a .zv-day-strip, que era un
     flex con overflow-x: el scroll horizontal no tiene affordance en escritorio
     y el paciente creia que no habia mas dias que los primeros. Ahora la semana
     entra COMPLETA en 7 columnas iguales, sin desplazamiento, y se navega con
     las flechas de .zv-week-nav.
     minmax(0,1fr) y no 1fr: sin el minmax, el contenido minimo de la pastilla
     empuja la columna y la grilla se desborda igual que antes. */
  .zv-week-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: var(--sp-2); }
  .zv-week-grid .zv-day-pill { min-width: 0; }

  .zv-week-nav { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); margin-bottom: var(--sp-2); }
  .zv-week-rot { font-size: 0.9rem; font-weight: 600; color: var(--text); text-align: center; flex: 1 1 auto; }
  .zv-week-arrow {
    flex: 0 0 auto; min-width: 44px; min-height: 44px;
    border: 1px solid var(--zv-control-line); border-radius: var(--r-input);
    background: var(--surface); color: inherit; font-size: 1.25rem; line-height: 1;
    cursor: pointer; font-family: inherit;
    appearance: none; -webkit-appearance: none;
  }
  .zv-week-arrow:hover:not(:disabled) { background: var(--surface-alt); }
  .zv-week-arrow:disabled { opacity: .35; cursor: not-allowed; }
  .zv-week-arrow:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

  /* En movil, 7 columnas dejan las pastillas muy angostas: se pasa a 4+3. */
  @media (max-width: 480px) {
    .zv-week-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  }

  .zv-slot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: var(--sp-2); }
  .zv-slot {
    padding: var(--sp-2);
    border: 1px solid var(--primary-line);
    border-radius: var(--r-input);
    background: var(--surface);
    color: var(--primary-dark);
    font-weight: 600; font-size: 14px;
    text-align: center;
    cursor: pointer;
    transition: background var(--t), color var(--t);
  }
  .zv-slot:hover:not(:disabled) { background: var(--primary-soft); }
  .zv-slot--sel { background: var(--primary); border-color: var(--primary); color: var(--zv-on-primary); }
  .zv-slot--taken { background: var(--surface-alt); color: var(--text-muted); border-color: var(--border); cursor: not-allowed; border-style: dashed; opacity: .6; }
  .zv-slot-list { display: flex; flex-direction: column; gap: var(--sp-2); }

  .zv-pay-method {
    display: flex; align-items: center; gap: var(--sp-3);
    padding: var(--sp-3);
    border: 1px solid var(--zv-control-line);
    border-radius: var(--r-card);
    background: var(--surface);
    cursor: pointer;
    transition: border-color var(--t), background var(--t);
  }
  .zv-pay-method:hover:not(:disabled) { border-color: var(--primary-line); background: var(--primary-soft); }
  .zv-pay-ico { width: 34px; height: 34px; flex: 0 0 34px; display: inline-flex; align-items: center; justify-content: center; color: var(--primary); }
  .zv-pay-lbl { font-weight: 600; }
  .zv-pay-amount { margin-left: auto; font-weight: 700; color: var(--success-on-soft); }
  .zv-pay-when { font-size: 12.5px; color: var(--text-muted); }
  .zv-pay-cci { font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: 13px; }
  .zv-pay-chev { color: var(--text-muted); display: inline-flex; }
  .zv-pay-qr { max-width: 190px; border-radius: var(--r-input); }

  /* `flex` y no `inline-flex` (decision F11): se usa sobre un <p>, y una caja
     inline-* se dimensiona a max-content -- no envuelve. Aca `flex` SI es
     seguro, al reves que en .zv-hint: el contenido es icono + texto + un
     <select>, sin markup inline de texto que la blockificacion pudiera romper.
     El flex-wrap deja que la etiqueta y el control se apilen a 320px. */
  .zv-tz { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); font-size: 12.5px; color: var(--text-muted); }
  .zv-tz-select { border: 1px solid var(--zv-control-line); border-radius: var(--r-input); padding: 3px 6px; background: var(--surface); }

  .zv-result { text-align: center; padding: var(--sp-5) 0; }
  .zv-result-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 12px; border-radius: var(--r-full);
    font-weight: 700; font-size: 13px;
  }
  .zv-result-badge--ok   { background: var(--success-soft); color: var(--success-on-soft); }
  .zv-result-badge--wait { background: var(--accent-soft);  color: var(--accent-dark); }
  .zv-result-badge--err  { background: var(--danger-soft);  color: var(--danger-on-soft); }
  .zv-result-badge--calm { background: var(--primary-soft); color: var(--primary-on-soft); }

  /* Medios de cobro (eran .mc-*) y escalas del portal (eran .scale-*). */
  .zv-mc-list { display: flex; flex-direction: column; gap: var(--sp-2); }
  .zv-mc-note { font-size: 12.5px; color: var(--text-muted); }
  .zv-scale-sm { font-size: 13px; }
  .zv-scale-lg { font-size: 17px; }

  /* ===================================================================
     17. AUTH  (guia §4.8 -- .auth-wrap/.auth-card/.tabs)
     =================================================================== */
  .zv-auth-wrap {
    min-height: 100%;
    display: flex; align-items: center; justify-content: center;
    padding: var(--sp-5);
    background: var(--bg);
  }
  .zv-auth-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--r-hero);
    box-shadow: var(--shadow-lg);
    width: min(420px, 100%);
    padding: var(--sp-6);
  }
  .zv-auth-card .tabs { display: flex; gap: var(--sp-2); margin-bottom: var(--sp-4); }
  .zv-auth-card .tabs button {
    flex: 1 1 auto;
    background: none;
    border: 0; border-bottom: 2px solid transparent;
    padding: var(--sp-2);
    color: var(--text-muted); font-weight: 600;
  }
  .zv-auth-card .tabs button.active { color: var(--primary-dark); border-bottom-color: var(--primary); }

  /* ===================================================================
     18. TOUR DE ONBOARDING  (guia §5.2 -- 11 clases .tour-*)
     =================================================================== */
  .zv-tour-root { position: fixed; inset: 0; z-index: 1200; pointer-events: none; }
  .zv-tour-band { position: absolute; background: rgba(27,29,54,.55); pointer-events: auto; }
  .zv-tour-ring {
    position: absolute;
    border-radius: var(--r-card);
    box-shadow: 0 0 0 3px var(--accent), 0 0 0 9999px rgba(27,29,54,.0);
    transition: all 220ms cubic-bezier(.2,.6,.2,1);
    pointer-events: none;
  }
  .zv-tour-ring-interactive { pointer-events: auto; }
  .zv-tour-tip {
    position: absolute;
    background: var(--surface);
    border-radius: var(--r-modal);
    box-shadow: var(--shadow-lg);
    padding: var(--sp-4);
    width: min(320px, calc(100vw - 2 * var(--sp-4)));
    pointer-events: auto;
  }
  .zv-tour-tip-title { font-family: var(--font-display); font-weight: 700; color: var(--primary-dark); margin-bottom: var(--sp-1); }
  .zv-tour-tip-body { font-size: 14px; color: var(--text); }
  .zv-tour-foot { display: flex; align-items: center; gap: var(--sp-2); margin-top: var(--sp-4); }
  .zv-tour-count { font-size: 12px; color: var(--text-muted); }
  .zv-tour-actions { margin-left: auto; display: flex; gap: var(--sp-2); }

  /* ===================================================================
     19. RESPONSIVE DEL SHELL Y DEL PORTAL
     Solo acá hay @media de ancho: son los dos documentos que ocupan el
     viewport completo. Los editores usan clases (K9).
     =================================================================== */
  @media (max-width: 860px) {
    .zv-shell { grid-template-columns: 1fr; }
    .zv-sidebar { flex-direction: row; align-items: center; overflow-x: auto; padding: var(--sp-2); }
    .zv-sidebar nav { flex-direction: row; }
    .zv-content { padding: var(--sp-4); }
    .zv-content table { display: block; overflow-x: auto; }
    /* 16px evita el zoom-jump de iOS al enfocar un input. */
    .zv-field input, .zv-field select, .zv-field textarea,
    .zv-field-row input, .zv-field-row select, .zv-field-row textarea { font-size: 16px; }
    .zv-field-row { grid-template-columns: minmax(0, 1fr); }
    .zv-field-row > label { padding-top: 0; }
  }

  @media (max-width: 560px) {
    .zv-row, .zv-row--3 { grid-template-columns: minmax(0, 1fr); }
    .zv-portal { padding: 0; }
    .zv-portal-card { border: 0; border-radius: 0; box-shadow: none; min-height: 100vh; padding: var(--sp-4); }
    .zv-topbar { padding: 0 var(--sp-3); }
  }

  @media (max-width: 480px) {
    .zv-dialog-actions { flex-direction: column-reverse; }
    .zv-dialog-actions .btn { width: 100%; }
    .zv-editor-actions { flex-direction: column-reverse; }
    .zv-editor-actions .btn { width: 100%; }
    /* Touch target minimo 44px (§3.3, bloque 5 del origen). */
    .zv-app .btn { min-height: 44px; }
    .zv-tour-tip-sheet {
      left: 0; right: 0; bottom: 0; top: auto;
      width: 100%;
      border-radius: var(--r-modal) var(--r-modal) 0 0;
    }
  }


  /* ===================================================================
     FUNDACIONES (fase B, 2026-08-05)

     Los componentes que el catalogo (docs/catalogo-componentes-zolvia.html)
     declaro "por crear": patrones que ya se repiten en el sistema y no
     tenian identidad propia. El CSS sale de ahi, no se improvisa aca.

     Ninguna pantalla los usa TODAVIA -- salvo los dos del topbar, que se
     mudan. Agregarlos no cambia el aspecto de nada, y eso es a proposito:
     es lo que hace verificable el criterio de la fase ("ninguna pantalla
     cambia salvo donde el cambio ES el arreglo").
     =================================================================== */

  /* --- Cabecera de pagina: el h1 y su nota, agrupados ---------------- */
  /* Hoy el h1 cuelga suelto de .zv-content y la nota es un <p class="zv-hint">
     hermano: dos elementos sin nada que los una, con el espacio ajustado a
     ojo en cada pantalla. */
  .zv-page-head { display: flex; flex-direction: column; gap: var(--sp-1);
                  margin-bottom: var(--sp-6); }
  .zv-page-nota { display: flex; align-items: flex-start; gap: var(--sp-2);
                  margin: 0; max-width: 78ch;
                  font-size: 13.5px; color: var(--text-muted); }
  .zv-page-nota .zv-ico { margin-top: .18em; color: var(--primary-line); }

  /* --- Buscador: el bloque mas repetido del sistema ------------------ */
  /* auto-fit y NO 1fr 1fr: .zv-row es dos columnas FIJAS (linea 263), asi que
     con tres filtros el tercero salta de fila y deja un hueco. Ese es todo el
     motivo de que el buscador no reuse .zv-row. Y el pie va aca y no en
     .zv-editor-actions, que es el pie de un EDITOR: reusar la clase equivocada
     significa que el dia que cambie el editor cambian los 12 buscadores. */
  /* Las dos van ACOTADAS a .zv-buscador y no sueltas. Dos motivos: el pie de
     un buscador no tiene por que aparecer fuera de un buscador --si aparece,
     es que alguien copio la clase equivocada--, y sobre todo que asi la clase
     raiz del componente EXISTE en la hoja. Una clase que el markup usa y que
     ninguna hoja menciona es exactamente lo que el mapa de pantallas denuncia
     como "usada sin CSS": queda viva en el HTML, no hace nada, y el dia que
     alguien le quiera dar estilo descubre que nunca lo tuvo. */
  .zv-buscador .zv-buscador-campos { display: grid; gap: var(--sp-4);
                        grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
  .zv-buscador .zv-buscador-acciones { display: flex; justify-content: flex-end; gap: var(--sp-2);
                          margin-top: var(--sp-5); padding-top: var(--sp-4);
                          border-top: 1px solid var(--border); }

  /* --- Microcopy de campo: la regla de negocio, no un texto cualquiera */
  /* Hoy es form-text, utilidad cruda de Bootstrap sin equivalente en el kit.
     Es lo que distingue "autoriza WhatsApp" de un checkbox mas. */
  .zv-field-nota { margin: 0; font-size: 12.5px; line-height: 1.45;
                   color: var(--text-muted); max-width: 62ch; }

  /* --- Destino de ajaxCall: hoy identificado SOLO por su id ---------- */
  /* <div id="status"> y <div id="resultado"> sin ninguna clase: no se les puede
     dar estilo ni buscarlos, y un id repetido rompe el destino sin avisar. */
  .zv-ajax-slot:empty { display: none; }
  .zv-ajax-slot--estado { margin-bottom: var(--sp-3); }

  /* --- Tabla del grid: el trio table table-hover table-striped x25 --- */
  /* Hereda de .table de Bootstrap; solo fija lo que el kit decide, para poder
     cambiar la densidad de TODOS los listados a la vez. */
  .zv-grid-tabla { width: 100%; margin: 0; }
  .zv-grid-tabla > thead > tr > th {
    font-size: 11px; letter-spacing: .07em; text-transform: uppercase;
    font-weight: 700; color: var(--text-muted);
    background: var(--surface-alt); border-bottom: 1px solid var(--border);
    white-space: nowrap;
  }
  .zv-grid-tabla > tbody > tr > td { vertical-align: middle; }
  .zv-grid-tabla td:has(.zv-money), .zv-grid-tabla .zv-money {
    font-variant-numeric: tabular-nums; text-align: right;
  }

  /* --- Vacio LIGERO, el de dentro de un panel ------------------------ */
  /* Distinto del .zv-notfound, que es el bloque grande con icono y boton.
     Hoy se resuelve con <p class="zv-hint">, que es la clase de las notas de
     ayuda: dos cosas distintas con la misma pinta. */
  .zv-vacio { margin: 0; padding: var(--sp-6) var(--sp-4);
              text-align: center; color: var(--text-muted); font-size: 14px; }

  /* --- El estado que faltaba: el formulario rechazado ---------------- */
  /* Hoy, cuando el validador rechaza, el framework devuelve su propia pagina
     de error y el usuario pierde lo que habia escrito. */
  .zv-form-error { margin-bottom: var(--sp-5); }
  .zv-field-nota--mal { color: var(--danger-on-soft); font-weight: 600; }

  /* ===================================================================
     MUESTRA DE COLOR  (fase C-8, 2026-08-05)
     Una celda de grid cuyo dato ES un color. Antes esa columna imprimia el
     literal "#7A5C99" debajo de un encabezado que decia "Color".
     Se aplica a un <span> propio, asi que no compite contra ningun reset de
     elemento ni contra una clase del kit (puente §7.1).
     El fondo de esta regla es el que se ve cuando la fila NO tiene color: el
     `style` en linea trae el valor y, si viene vacio, el parser descarta esa
     declaracion y queda este.
     =================================================================== */
  .zv-muestra-color {
    display: inline-block;
    width: 16px; height: 16px;
    vertical-align: -3px;
    border-radius: var(--r-input);
    border: 1px solid var(--zv-control-line);
    background: var(--surface-alt);
  }
  .zv-muestra-color-txt {
    margin-left: 8px;
    color: var(--text-muted);
    font-size: 13px;
    font-variant-numeric: tabular-nums;
  }

  /* --- Responsive de las fundaciones -------------------------------- */
  @media (max-width: 768px) {
    /* En pantalla angosta la barra se queda con lo esencial: hamburguesa,
       marca y las dos acciones. Venia del <style> de security/topbar. */
    .zv-topbar .zv-who-name { display: none; }
    .zv-topbar .zv-avatar { display: none; }
  }
  @media (max-width: 560px) {
    .zv-buscador .zv-buscador-acciones { flex-direction: column-reverse; }
    .zv-buscador .zv-buscador-acciones .btn { width: 100%; }
    /* :has() y NO .zv-panel-head a secas. MEDIDO el 2026-08-05: el
       `flex-wrap: wrap` suelto que proponia el catalogo cambiaba el layout a
       375px de TRES pantallas que nadie pidio tocar -- los 5 panel-head de
       Cobros, los 3 del Calendario y los 6 de Disponibilidad -- porque el wrap
       aplica al panel-head tenga o no una accion adentro. Acotado al que si la
       tiene, las tres vuelven a su huella exacta. */
    .zv-panel-head:has(.zv-panel-accion) { flex-wrap: wrap; }
  }

}

/* =====================================================================
   FUNDACIONES QUE NO PUEDEN VIVIR EN @layer  (fase B, 2026-08-05)

   OJO: estas cinco NO son un capricho de orden. MEDIDAS dentro de
   @layer zv-components, las cinco perdian contra una regla sin capa de
   bootstrap.min.css o del reset, y el componente salia con el aspecto
   por defecto del navegador SIN DAR NINGUN ERROR:

     .zv-grid-accion            border-radius: 10px -> 0px    (boton cuadrado)
     .zv-btn-off[disabled]      opacity .55 -> .65, cursor not-allowed -> pointer
     .zv-field--mal             borde rojo -> lavanda  (el campo con error no se veia)
     .zv-panel-accion           margin-left auto -> 0  (la accion no se iba a la derecha)
     .zv-topbar-burger/-salir   inline-flex -> block, radio 0, fondo gris de <button>
     .zv-num-libre              width 110px -> 100%     (el campo de duracion)

   La causa esta escrita en zv-tokens.css:149 y en el puente S7: una regla
   de importancia normal SIN capa le gana a CUALQUIER regla dentro de un
   @layer, por mas especifica que sea. Bootstrap y el CSS del framework
   cargan sin capa. Es el mismo motivo por el que .zv-ico vive en
   zv-icons.css (linea 98) y no aca dentro.

   Van despues del cierre de la capa y compiten por especificidad contra
   Bootstrap: clase (0,1,0) contra elemento (0,0,1), y ademas cargan
   despues. No hace falta ni un !important.
   ===================================================================== */

/* Duracion y colchon a medida en la franja: el numero que el psicologo
   escribe cuando ninguno de los chips le sirve. Medido: dentro de la capa
   quedaba en 347px (ancho completo) porque ganaba .form-control. */
.zv-num-libre { width: 110px; }

/* Boton-icono de fila: 47 repeticiones de 4 clases en cadena. */
.zv-grid-accion {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  border: 1px solid var(--border); border-radius: var(--r-input);
  background: var(--surface); color: var(--text-muted);
  transition: border-color var(--t), color var(--t), background-color var(--t);
}
.zv-grid-accion + .zv-grid-accion { margin-left: var(--sp-1); }
.zv-grid-accion:hover { border-color: var(--primary-line); color: var(--primary-dark);
                        background: var(--primary-soft); }
.zv-grid-accion--peligro:hover { border-color: var(--danger); color: var(--danger);
                                 background: var(--danger-soft); }

/* Boton presente pero apagado, con el motivo en el title. Pelea contra
   --bs-btn-disabled-opacity: .65 de Bootstrap. */
.zv-btn-off[disabled] { opacity: .55; cursor: not-allowed; }

/* =====================================================================
   ERROR DE VALIDACION  (2026-08-07)

   Era el unico sitio donde el sistema de diseno se caia entero, y se caia
   justo cuando el usuario se equivoca. Tres cosas medidas el 06-ago:

     1. `.errormsg` -- la clase que setFormErrorMsg (dinamica.js:1252) le
        pone a CADA mensaje de error -- no tenia UNA SOLA regla en las 8
        hojas de CSS. Heredaba el tamano del cuerpo y, peor, dentro de un
        .zv-field-row (grid de 190px + 1fr) el <div> apendido a
        elem.parentNode caia en la COLUMNA DEL LABEL de la fila siguiente.
     2. `.zv-field--mal` existia desde F1 con CERO consumidores, y ademas
        solo cubria .form-control: los <select> del kit son .form-select.
     3. `.alertbox` venia con otro azul (#1e92d2), otra tipografia
        ("Segoe UI") y alto FIJO de 180px. Y no es solo del validador:
        dinamica.js:85-90 lo usa para 500, 403, 404 y "su sesion ha
        caducado", o sea que es el dialogo que el usuario ve en el peor
        momento posible.

   Todo va FUERA de la capa y sin !important: pelea por especificidad
   contra bootstrap.min.css y contra bootstrap.helper.css, que cargan sin
   capa (mismo motivo que el bloque de arriba).
   ===================================================================== */

/* El mensaje por campo. `:empty` importa: setFormErrorMsg no borra el div
   cuando el campo se corrige, lo deja con innerHTML vacio -- sin esta regla
   seguiria ocupando su renglon. */
.errormsg {
  margin-top: 4px;
  font-size: 12.5px; font-weight: 600; line-height: 1.35;
  color: var(--danger);
}
.errormsg:empty { display: none; }
/* Devuelve el mensaje debajo del control en vez de la columna del label. */
.zv-field-row > .errormsg { grid-column: 2; }
.zv-editor-grid .zv-field-row > .errormsg { grid-column: 1; }

/* Campo rechazado por el validador. Pelea contra .form-control. */
.zv-field--mal > .form-control,
.zv-field--mal > .form-select,
.zv-field--mal > textarea { border-color: var(--danger); }
.zv-field--mal > .form-control:focus,
.zv-field--mal > .form-select:focus,
.zv-field--mal > textarea:focus {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-soft);
}
.zv-field--mal > label { color: var(--danger-on-soft); }

/* El dialogo del framework, con los tokens del kit. Se conserva el nombre de
   las clases porque las escribe dinamica.js:1617-1627 y no se toca ese
   archivo: es del framework y forkearlo obliga a re-aplicar el parche en
   cada actualizacion. Solo se re-pinta. */
.alertbox-overlay {
  background: rgba(27, 29, 54, .45);
  backdrop-filter: blur(2px);
}
.alertbox {
  min-width: 300px; max-width: 420px;
  height: auto;                      /* el 180px fijo cortaba los mensajes largos */
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-lg);
  font-family: var(--font);
  font-size: 14px;
  text-align: left;
}
#alertbox-msg {
  margin: 0 0 var(--sp-4);
  color: var(--text);
  line-height: 1.5;
}
.alertbox-buttons { justify-content: flex-end; gap: var(--sp-2); margin-top: 0; }
.alertbox-buttons button:first-child { margin-right: 0; }
#alertbox-ok {
  padding: 8px 18px;
  border: 1px solid var(--primary);
  border-radius: var(--r-input);
  background: var(--primary);
  color: #fff; font-weight: 600; font-family: inherit; font-size: 13.5px;
}
#alertbox-ok:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
.alertbox .cancel-btn {
  padding: 8px 18px;
  border: 1px solid var(--border);
  border-radius: var(--r-input);
  background: var(--surface);
  color: var(--text-muted); font-weight: 600; font-family: inherit; font-size: 13.5px;
}
.alertbox .cancel-btn:hover { background: var(--surface-alt); color: var(--text); }

/* La accion que cuelga del zv-panel-head, a la derecha del titulo. */
.zv-panel-accion { margin-left: auto; flex: 0 0 auto; }

/* La ALINEACION de la columna de acciones. El `width:1%` de .zv-col-acciones
   funciona dentro de la capa, pero `text-align` no: los `th`/`td` de .table de
   Bootstrap cargan sin capa y ganan, asi que los botones quedaban a la
   izquierda de la celda. Sin esta regla la clase no puede REEMPLAZAR al
   `text-end` que se le quito a los 16 grids -- solo lo aproximaria. */
.zv-grid-wrap table th.zv-col-acciones,
.zv-grid-wrap table td.zv-col-acciones { text-align: right; }

/* Los dos botones del topbar. MUDADOS desde el <style> de
   security/topbar/template.htm (lineas 21-34): no se reescriben, el aspecto
   ya estaba aprobado; esto solo lo reubica donde el resto pueda usarlo. */
.zv-topbar-burger, .zv-topbar-salir {
  display: inline-flex; padding: var(--sp-1);
  border: 0; background: none;
  border-radius: var(--r-input); color: var(--text-muted);
}
.zv-topbar-burger:hover { background: var(--primary-soft); color: var(--primary-on-soft); }
.zv-topbar-salir:hover  { background: var(--danger-soft);  color: var(--danger-on-soft); }

@media (max-width: 560px) {
  .zv-panel-accion { margin-left: 0; width: 100%; }
}

/* =====================================================================
   PORTAL PUBLICO: encabezados reales  (fase C-12, 2026-08-05)

   La pantalla de reserva no tenia NI UN encabezado: 0 h1 y 0 h2, con los
   rotulos de seccion puestos como <div>. Para un lector de pantalla eso es
   una pagina sin estructura, y es la unica pantalla del sistema que ve
   alguien que NO es usuario de la app.

   Las dos van FUERA de la capa por la regla del puente §7.1: se aplican a
   <h1> y <h2>, que traen su propio reset de elemento de bootstrap.min.css
   -- sin capa, y por lo tanto ganador sobre cualquier regla dentro de
   @layer, por especifica que sea. Dentro de la capa, promover el <div> a
   <h2> habria dejado los rotulos con el tamanio y el margen de un h2 de
   Bootstrap, sin error y sin que ningun test lo viera.
   ===================================================================== */
.zv-portal-title {
  font-family: var(--font-headline, inherit);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--text);
  margin: 0 0 var(--sp-2);
}
h2.zv-section-label {
  font-size: 11.5px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-muted);
  margin: var(--sp-5) 0 var(--sp-2);
}

/* =====================================================================
   FLUJO EN PASOS  (2026-08-06, mockup 05-recibos-por-honorarios)

   Para tramites que NO terminan dentro de PaskayMind. El caso que lo origina
   es el RHE: SUNAT no deja emitir por API, asi que en el medio hay un
   paso que ocurre FUERA de la app y la tabla sola no lo contaba.

   Es un <ol>: el orden es informacion, no decoracion. El numero sale de
   un <span> propio y no de un ::before con counter() porque el paso 2
   lleva un enlace y necesita ser un elemento con su propio flujo.

   La linea que une los pasos es un ::after del <li> y NO un absolute
   sobre el contenedor: asi desaparece sola en una columna (movil) sin
   una segunda media query, y no queda colgada del ultimo paso.
   ===================================================================== */
.zv-flujo {
  list-style: none;
  margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-4);
}
.zv-flujo-paso {
  position: relative;
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 6px;
  padding-right: var(--sp-3);
}
.zv-flujo-paso::after {
  content: "";
  position: absolute; z-index: 0;
  top: 15px; left: calc(100% - var(--sp-3) + 4px); right: calc(var(--sp-4) * -1 + 4px);
  height: 2px; background: var(--border);
}
.zv-flujo-paso:last-child::after { display: none; }
.zv-flujo-num {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  border-radius: var(--r-full);
  background: var(--surface);
  border: 2px solid var(--primary);
  color: var(--primary);
  font-weight: 700; font-size: 14px;
  box-shadow: var(--shadow-xs);
}
.zv-flujo-titulo {
  margin: 0;
  font-size: 14px; font-weight: 600;
  color: var(--text);
}
.zv-flujo-desc {
  margin: 0;
  font-size: 12.5px; line-height: 1.45;
  color: var(--text-muted);
}
/* El enlace externo va con aspecto de boton chico y no de link suelto: es
   la unica accion del bloque y compite con tres parrafos de texto. */
.zv-flujo-link {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-input);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
  font-size: 12.5px; font-weight: 600;
  color: var(--primary);
  text-decoration: none;
}
.zv-flujo-link:hover { background: var(--primary-soft); color: var(--primary-on-soft); }

/* Celda de grid cuyo valor todavia NO existe (no es un cero, es un "aun
   no"). Sin esto, una columna vacia se lee como dato faltante. */
.zv-dato-vacio { color: var(--text-muted); font-style: italic; font-size: 12.5px; }

/* =====================================================================
   SEMANA DE UN VISTAZO  (2026-08-06, mockup 02-disponibilidad-y-reservas)

   La pantalla tenia seis bloques del MISMO peso y ninguno mostraba el
   resultado: el psicologo cargaba franjas en una tabla y se iba sin ver la
   semana que acababa de armar. Esto la dibuja al lado de la tabla que la
   produce, con los mismos datos (no hay SQL nuevo).

   La ventana horaria NO es fija de 0 a 24: se calcula del minimo y el
   maximo de las franjas. Con 0-24 fijo, una jornada de 9 a 18 ocupaba el
   37% de la pista y las diferencias entre dias no se distinguian.

   Los bloques van en POSICION ABSOLUTA con left/width en %, no en un flex:
   dos franjas del mismo dia pueden tener un hueco entre ellas (9-13 y
   15-19) y ese hueco es informacion -- un flex lo cerraria.
   ===================================================================== */
.zv-semana { display: flex; flex-direction: column; gap: 5px; }
.zv-semana-fila { display: flex; align-items: center; gap: var(--sp-3); }
.zv-semana-dia {
  flex: 0 0 76px; width: 76px;
  font-size: 12px; font-weight: 600;
  color: var(--text);
}
.zv-semana-fila--off .zv-semana-dia { color: var(--text-muted); font-weight: 500; }
.zv-semana-pista {
  position: relative; flex: 1 1 auto;
  height: 16px;
  border-radius: var(--r-input);
  background: var(--surface-alt);
  overflow: hidden;
}
.zv-semana-bloque {
  position: absolute; top: 0; bottom: 0;
  background: var(--success-soft);
  border-left: 1px solid var(--surface);
  border-right: 1px solid var(--surface);
}
/* El dia sin franjas se raya en vez de quedar liso: liso se confunde con
   "todavia no cargue nada" y rayado dice "aca no atiendo". */
.zv-semana-fila--off .zv-semana-pista {
  background-image: repeating-linear-gradient(45deg,
    transparent, transparent 4px,
    rgba(84,88,116,.10) 4px, rgba(84,88,116,.10) 8px);
}
.zv-semana-eje {
  display: flex; justify-content: space-between;
  flex: 1 1 auto;
  font-size: 11px; color: var(--text-muted);
}
.zv-semana-horas { font-size: 11.5px; color: var(--text-muted); white-space: nowrap; }
.zv-semana-leyenda { margin-top: var(--sp-4); }
/* Las muestras de la leyenda repiten la pintura de la pista, no la aproximan:
   si un dia cambia el verde, cambia en los dos lados a la vez. */
.zv-leyenda-swatch--libre { background: var(--success-soft); }
.zv-leyenda-swatch--off {
  background-color: var(--surface-alt);
  background-image: repeating-linear-gradient(45deg,
    transparent, transparent 2px,
    rgba(84,88,116,.28) 2px, rgba(84,88,116,.28) 4px);
}
/* En angosto la pista se queda con 121px y las proporciones dejan de leerse:
   el nombre del dia y las horas se van a su propia linea y la barra toma el
   ancho entero. Medido a 375px: 121px -> 343px de pista. */
@media (max-width: 560px) {
  .zv-semana-fila { flex-wrap: wrap; row-gap: 3px; }
  .zv-semana-dia  { flex: 0 0 auto; width: auto; }
  .zv-semana-horas { margin-left: auto; }
  .zv-semana-pista, .zv-semana-eje { order: 3; flex: 1 0 100%; }
}

/* Rotulo que agrupa paneles dentro de la grilla de .zv-paneles. Sin el
   grid-column ocuparia una celda y empujaria los paneles de su grupo a la
   columna de al lado. */
.zv-paneles > .zv-section-label { grid-column: 1 / -1; margin: var(--sp-3) 0 0; }

/* El bloque que MANDA en una pagina de varios: el resto son excepciones o
   consecuencias suyas. Un filete a la izquierda, no un color de fondo: la
   pagina ya usa el fondo tramado para "esto explica". */
.zv-panel--principal { border-left: 3px solid var(--primary); }

/* Dos o tres cifras NO deben estirarse a todo el ancho: con auto-fit y 1fr,
   .zv-cards reparte los 2554px de un monitor entre dos tarjetas y cada numero
   queda solo en medio metro de crema. Se les pone tope y se alinean a la
   izquierda, donde empieza la lectura. */
.zv-cards--pocas {
  grid-template-columns: repeat(auto-fit, minmax(200px, 280px));
  justify-content: start;
}

/* =====================================================================
   CIFRA ACCIONABLE Y BARRA DE REPARTO  (2026-08-06, mockup 09-dashboard)

   El dashboard listaba sus pendientes como texto y no llevaba a ningun
   lado: el psicologo leia "3 reservas por aprobar" y tenia que ir a
   buscarlas al menu. Cada pendiente es ahora una tarjeta que ES el enlace
   a la pantalla que lo resuelve.

   Va como <a> y no como <div onclick>: asi entra en el orden de tabulado,
   se abre en pestania nueva con el clic del medio y el lector de pantalla
   lo anuncia como enlace. Fuera de @layer porque tiene que ganarle al
   reset de <a> de bootstrap.min.css, que no esta en ninguna capa.
   ===================================================================== */
a.zv-stat--accion {
  display: block;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}
a.zv-stat--accion:hover { border-color: var(--primary); box-shadow: var(--shadow-md); }
a.zv-stat--accion:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.zv-stat-ir {
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 6px;
  font-size: 12px; font-weight: 600;
  color: var(--primary);
}

/* Barra de reparto de un total en dos partes. Las anchuras las pone el JS
   en % sobre el mismo total, asi que la barra no puede contradecir a las
   cifras de arriba: sale de ellas. */
.zv-barra {
  display: flex; overflow: hidden;
  height: 8px;
  margin-top: var(--sp-4);
  border-radius: var(--r-full);
  background: var(--border);
}
.zv-barra-parte--ok   { background: var(--success); }
.zv-barra-parte--pend { background: var(--warning); }
.zv-barra-pie {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--sp-2);
  margin: 6px 0 0;
  font-size: 12px; color: var(--text-muted);
}

/* Panel de fondo tramado: para bloques que EXPLICAN en vez de listar. Se
   distingue del panel normal sin cambiarle el borde ni el radio. */
.zv-panel--explica { background: var(--surface-alt); }
.zv-panel--explica .zv-panel-head { border-bottom-color: var(--border); }

/* Pie de una cifra: dice QUE HACER con el numero de arriba. Va debajo del
   .zv-stat-v y hereda su caja, por eso no lleva margen superior propio. */
.zv-stat-pie {
  margin: 6px 0 0;
  font-size: 12px; line-height: 1.35;
  color: var(--warning-on-soft);
  font-weight: 600;
}

@media (max-width: 700px) {
  .zv-flujo { grid-template-columns: 1fr; }
  .zv-flujo-paso { flex-direction: row; align-items: flex-start; flex-wrap: wrap; }
  .zv-flujo-paso .zv-flujo-titulo { flex: 1 1 auto; align-self: center; }
  .zv-flujo-paso .zv-flujo-desc,
  .zv-flujo-paso .zv-flujo-link { flex: 1 0 100%; }
}

/* === PANTALLAS DE ACCION (2026-08-08) ===
   Piezas compartidas por las 8 pantallas de accion. Viven aca y no en cada
   template porque el generador no emite este archivo: es lo que hace seguro
   que los editor/form/template.htm esten en noPisar. */

/* Dos columnas: el formulario a la izquierda, la consecuencia a la derecha. */
.zv-blo-cols {
  display: grid;
  grid-template-columns: minmax(0, 45fr) minmax(0, 55fr);
  gap: var(--sp-4);
  align-items: start;
}
@media (max-width: 900px) {
  .zv-blo-cols { grid-template-columns: 1fr; }
}

/* Fila de atajos de rango. */
.zv-blo-atajos { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-4); }

/* Un renglon de la lista de sesiones afectadas. */
.zv-blo-item {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border);
}
.zv-blo-item:last-child { border-bottom: 0; }
.zv-blo-item-txt { flex: 1 1 auto; min-width: 0; }
.zv-blo-item-nom { margin: 0; font-weight: 600; }
.zv-blo-item-det { margin: 0; font-size: 13px; color: var(--text-muted); }
.zv-blo-item-acc { flex: 0 0 auto; display: flex; gap: var(--sp-2); }

/* Las cifras del pie del panel derecho. */
.zv-blo-cifras {
  display: flex; flex-wrap: wrap; gap: var(--sp-4);
  margin-top: var(--sp-4); padding-top: var(--sp-3);
  border-top: 1px solid var(--border);
}
.zv-blo-cifras > div { display: flex; flex-direction: column; gap: 2px; }
.zv-blo-cifras span   { font-size: 13px; color: var(--text-muted); }
.zv-blo-cifras strong { font-size: 18px; font-variant-numeric: tabular-nums; }

/* Aviso de consecuencia: dice lo que va a pasar, no es un error. */
.zv-aviso {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-3); margin-bottom: var(--sp-4);
  border-radius: var(--r-input); background: var(--primary-soft);
}
.zv-aviso p { margin: 0; font-size: 14px; }
.zv-aviso-titulo { font-weight: 600; margin-bottom: 2px !important; }
.zv-aviso--ojo { background: var(--info-soft); }

/* Titulo del estado vacio, que hoy solo tenia parrafo. */
.zv-vacio-titulo { font-weight: 600; margin: 0 0 4px; }

/* Interruptor de una fila de formulario. */
.zv-field-row--switch {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
}
.zv-field-row--switch > label { margin: 0; display: flex; flex-direction: column; }
/* INTERRUPTOR DE VERDAD, no un checkbox estirado.
   Antes esto era solo un tamano: el navegador seguia pintando su casilla
   nativa a 42x24, que es lo que la auditoria leyo como "el switch no existe".
   Va sobre el mismo <input type="checkbox">, asi que ningun template cambia. */
.zv-switch {
  appearance: none; -webkit-appearance: none;
  width: 42px; height: 24px; flex: 0 0 auto; cursor: pointer;
  margin: 0; padding: 0; border: 1px solid var(--zv-control-line);
  border-radius: var(--r-full); background: var(--surface-alt);
  position: relative; transition: background var(--t), border-color var(--t);
}
/* La perilla. Un pseudo-elemento y no un <span>: el control sigue siendo un
   solo nodo, con lo cual el label, el foco y el teclado funcionan sin ayuda. */
.zv-switch::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: var(--r-full);
  background: var(--surface); box-shadow: var(--shadow-xs);
  transition: transform var(--t);
}
.zv-switch:checked { background: var(--primary); border-color: var(--primary); }
.zv-switch:checked::after { transform: translateX(18px); }
.zv-switch:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.zv-switch:disabled { opacity: .55; cursor: not-allowed; }

/* --- Preview de la recurrencia --- */
/* Un renglon del preview: fecha, horario y veredicto. */
.zv-prev-item {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) 0; border-bottom: 1px solid var(--border);
}
.zv-prev-item:last-child { border-bottom: 0; }
.zv-prev-item--salta { opacity: .72; }
.zv-prev-fecha {
  flex: 0 0 auto; min-width: 58px; text-align: center;
  font-weight: 600; font-variant-numeric: tabular-nums;
  padding: var(--sp-2); border-radius: var(--r-input); background: var(--surface-alt);
}
.zv-prev-txt   { flex: 1 1 auto; min-width: 0; }
.zv-prev-hora  { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }
.zv-prev-mes   { margin: 0; font-size: 13px; color: var(--text-muted); }
.zv-prev-motivo{ margin: 2px 0 0; font-size: 13px; color: var(--text-muted); font-style: italic; }

/* El select que los chips gobiernan: sigue viajando en el POST. */
.zv-oculto-visual { display: none; }

/* --- Grilla de huecos y contexto del paciente --- */
/* Tarjeta de contexto del paciente elegido. */
.zv-pacctx {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3); margin-bottom: var(--sp-4);
  border: 1px solid var(--border); border-radius: var(--r-input); background: var(--surface-alt);
}
.zv-pacctx-txt { min-width: 0; }
.zv-pacctx-nom { margin: 0; font-weight: 600; }
.zv-pacctx-det { margin: 0; font-size: 13px; color: var(--text-muted); }

/* Navegacion de semana en la cabecera del panel. */
.zv-sem-nav { display: flex; align-items: center; gap: var(--sp-2); font-size: 13px; }

/* La grilla de huecos la define ahora `.zv-ret` (tanda 5), al final de este
   archivo. Aca vivian `#zv-grilla { display: flex }`, `.zv-dia` y
   `.zv-dia-tit`, que pintaban una columna suelta por dia. Se retiraron: el
   selector de ID (1,0,0) le ganaba a `.zv-ret` (0,1,0) y la reticula no
   llegaba a ser grid. Ningun otro documento las usaba. */
.zv-slot {
  display: block; width: 100%; margin-bottom: 4px;
  padding: 6px 8px; font-size: 13px; text-align: left;
  border: 1px solid var(--primary-line); border-radius: var(--r-input);
  background: var(--surface); color: var(--text); cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.zv-slot:hover:not(:disabled) { background: var(--primary-soft); }
.zv-slot--sel { background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600; }
.zv-slot--ocu,
.zv-slot--blo { cursor: not-allowed; opacity: .6; border-style: dashed; }
.zv-slot--blo { background: var(--surface-alt); }

/* Leyenda de la grilla. */
.zv-leyenda {
  display: flex; flex-wrap: wrap; gap: var(--sp-3);
  margin-top: var(--sp-3); font-size: 12px; color: var(--text-muted);
}
/* `.zv-marca` = el CUADRADITO de color de la leyenda, no el logotipo de PaskayMind.
   El logotipo del login es `.zv-logotipo` (index.htm) justamente porque esta
   regla se lo comia: 10x10 px con el nombre desbordando una letra por linea. */
.zv-marca {
  display: inline-block; width: 10px; height: 10px; margin-right: 4px;
  border-radius: 2px; border: 1px solid var(--primary-line); vertical-align: middle;
}
.zv-marca--libre { background: var(--surface); }
.zv-marca--sel   { background: var(--primary); border-color: var(--primary); }
.zv-marca--ocu   { background: var(--surface-alt); border-style: dashed; }
.zv-marca--blo   { background: var(--surface-alt); border-style: dotted; }

/* --- Desglose de cobro y medio de pago --- */
/* Las tres filas del desglose: tarifa, adelanto y saldo. */
.zv-desglose {
  margin-bottom: var(--sp-4); padding: var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r-input); background: var(--surface-alt);
}
.zv-desglose > div {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: var(--sp-3); padding: 3px 0; font-size: 14px;
}
.zv-desglose > div > span:last-child { font-variant-numeric: tabular-nums; }
.zv-desglose-total {
  margin-top: var(--sp-2); padding-top: var(--sp-2) !important;
  border-top: 1px solid var(--border); font-weight: 700; font-size: 16px !important;
}

/* El medio de cobro que el paciente va a usar. */
.zv-medio-rot  { margin: 0 0 var(--sp-2); font-weight: 600; }
.zv-medio-qr   { display: block; max-width: 200px; width: 100%; height: auto;
                 margin: 0 0 var(--sp-2); border: 1px solid var(--border); border-radius: var(--r-input); }
.zv-medio-dato { margin: 0 0 var(--sp-2); font-variant-numeric: tabular-nums; }

/* --- Cotejo del pago --- */
/* Dos columnas enfrentadas, con la divisoria que separa lo que dice el
   paciente de lo que se esta reservando. */
.zv-cotejo-cols {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4);
  position: relative;
}
.zv-cotejo-cols > .zv-cotejo-col:first-child { border-right: 1px solid var(--border); padding-right: var(--sp-4); }
@media (max-width: 760px) {
  .zv-cotejo-cols { grid-template-columns: 1fr; }
  .zv-cotejo-cols > .zv-cotejo-col:first-child {
    border-right: 0; padding-right: 0;
    border-bottom: 1px solid var(--border); padding-bottom: var(--sp-4);
  }
}
.zv-cotejo-tit {
  margin: 0 0 var(--sp-3); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em; color: var(--text-muted);
}
/* La cifra que se compara: se lee de un vistazo. */
.zv-cifra { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
/* El codigo de operacion: monoespaciado, que es como se coteja contra la app
   del banco sin equivocarse de digito. */
.zv-mono { font-family: Consolas, "Courier New", monospace; font-size: 13px; word-break: break-all; }

/* --- Cifras operativas de la ficha --- */
/* Las tres cifras: cuanto debe, cuando vuelve, cuantas lleva. */
.zv-cifras {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3); margin-bottom: var(--sp-4);
}
@media (max-width: 700px) { .zv-cifras { grid-template-columns: 1fr; } }
.zv-cifra-caja {
  padding: var(--sp-3); border: 1px solid var(--border);
  border-radius: var(--r-input); background: var(--surface);
}
.zv-cifra-caja h3 {
  margin: 0 0 var(--sp-2); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted);
}
.zv-cifra-val { margin: 0; font-size: 24px; font-weight: 700; font-variant-numeric: tabular-nums; }
.zv-cifra-pie { margin: 2px 0 0; font-size: 13px; color: var(--text-muted); }

/* Acciones rapidas del encabezado de la ficha. */
.zv-acciones-rapidas { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-4); }

/* El pie que declara que la ficha es administrativa. No es decoracion: es lo
   que mantiene al MVP fuera del regimen de datos sensibles. */
.zv-aviso--legal { margin-top: var(--sp-4); background: var(--surface-alt); }

/* --- Troceo de la franja --- */
/* Cada sesion que entra en el tramo. */
.zv-slot-fila {
  display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); margin-bottom: 4px;
  border: 1px solid var(--primary-line); border-radius: var(--r-input); background: var(--surface);
}
.zv-slot-hora { font-weight: 600; font-variant-numeric: tabular-nums; }
.zv-slot-dur  { font-size: 13px; color: var(--text-muted); }
/* El remanente que no completa una sesion: se ve distinto porque no se ofrece. */
/* La fila que no completa una sesion. Con opacidad sola se leia como
   "deshabilitada" -- justo lo contrario de lo que hay que mirar. */
.zv-slot-fila--inc {
  border-style: dashed; border-color: var(--warning);
  background: var(--surface-alt); color: var(--warning-on-soft);
}
.zv-slot-fila--inc .zv-ico { color: var(--warning); }

/* La banda de colchon entre dos sesiones. */
.zv-banda-buf {
  margin: 0 0 4px; padding: 3px var(--sp-3);
  font-size: 12px; color: var(--text-muted); text-align: center;
  background: var(--surface-alt); border-radius: var(--r-input);
}

/* --- Recibo por honorarios: stepper y copiado --- */
/* El stepper deja explicito que el paso 2 ocurre FUERA de PaskayMind. */
.zv-stepper {
  display: flex; flex-wrap: wrap; gap: var(--sp-3);
  list-style: none; margin: 0 0 var(--sp-4); padding: 0;
}
.zv-paso {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: 13px; color: var(--text-muted);
}
.zv-paso-n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%;
  border: 1px solid var(--primary-line); font-size: 12px; font-weight: 700;
}
.zv-paso--on { color: var(--text); font-weight: 600; }
.zv-paso--on .zv-paso-n { background: var(--primary); border-color: var(--primary); color: #fff; }

/* Los cinco campos listos para copiar al SEE-SOL. */
.zv-copiar { list-style: none; margin: 0 0 var(--sp-3); padding: 0; }
.zv-copiar-item {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-2) 0; border-bottom: 1px solid var(--border);
}
.zv-copiar-item:last-child { border-bottom: 0; }
.zv-copiar-txt { min-width: 0; }
.zv-copiar-rot { margin: 0; font-size: 12px; color: var(--text-muted); }
.zv-copiar-val { margin: 0; font-weight: 600; word-break: break-word; }


/* =====================================================================
   PIEZAS COMPARTIDAS POR LAS 8 PANTALLAS DE ACCION (2026-08-08)
   ===================================================================== */

/* --- Miga de pan --------------------------------------------------- */
/* Dice DE DONDE vienes cuando la pantalla se abre desde tres sitios
   distintos. No reemplaza al .zv-back-link: aquel es el boton de volver,
   esta es la ubicacion. */
.zv-miga {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
  margin: 0 0 var(--sp-3); padding: 0; list-style: none;
  font-size: 13px; color: var(--text-muted);
}
.zv-miga a { color: var(--text-muted); text-decoration: none; }
.zv-miga a:hover { color: var(--primary); text-decoration: underline; }
/* El separador se dibuja, no se teclea: asi no viaja un caracter fuera de
   ASCII a un archivo ISO-8859-1 ni se lee dos veces en un lector de pantalla. */
.zv-miga li + li::before { content: "/"; margin-right: var(--sp-2); color: var(--border); }
.zv-miga li[aria-current] { color: var(--text); font-weight: 600; }

/* --- Afijo monetario ----------------------------------------------- */
/* El S/ pegado al campo. Es la pieza que faltaba para que un importe se lea
   como plata y no como un numero cualquiera. */
.zv-afijo { display: flex; align-items: stretch; }
.zv-afijo-rot {
  display: flex; align-items: center; flex: 0 0 auto;
  padding: 0 var(--sp-3);
  background: var(--surface-alt); color: var(--text-muted);
  border: 1px solid var(--zv-control-line); border-right: 0;
  border-radius: var(--r-input) 0 0 var(--r-input);
  font-weight: 600; font-variant-numeric: tabular-nums;
}
.zv-afijo > .form-control {
  border-top-left-radius: 0; border-bottom-left-radius: 0;
  text-align: right; font-variant-numeric: tabular-nums;
}
/* El foco tiene que rodear al conjunto, no solo al input, o el anillo queda
   cortado por la mitad justo donde se pegan las dos piezas. */
.zv-afijo:focus-within .zv-afijo-rot { border-color: var(--primary-line); }

/* --- Calendario: el estado que faltaba ------------------------------ */
/* Los dias INTERMEDIOS de un rango. El kit ya tenia --sel (un dia elegido),
   --libre, --off y --blank, pero no sabia decir "del 12 al 15": los tres del
   medio se veian igual que un dia sin tocar. */
.zv-cal-day--rango {
  background: var(--primary-soft); border-color: var(--primary-line);
  color: var(--primary-on-soft); border-radius: 0;
}
/* Las puntas del rango cierran la forma. */
.zv-cal-day--rango-ini { border-radius: var(--r-input) 0 0 var(--r-input); }
.zv-cal-day--rango-fin { border-radius: 0 var(--r-input) var(--r-input) 0; }

/* Encabezado de identidad de la ficha: avatar, nombre y chip de estado en una
   sola linea. Es lo que hace que la pantalla diga DE QUIEN es antes de mostrar
   un solo campo. */
.zv-ficha-id { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.zv-ficha-id .zv-page-title { margin: 0; }
.zv-ficha-id .zv-tag { margin-left: auto; }

/* Grupo de radios en linea. Se usa donde las opciones son dos o tres y
   excluyentes: un radio postea solo, sin hidden de apoyo, y el lector de
   pantalla ya sabe que son alternativas. */
.zv-radios { display: flex; flex-wrap: wrap; gap: var(--sp-4); }
.zv-radio { display: flex; align-items: center; gap: var(--sp-2); cursor: pointer; margin: 0; }
.zv-radio input { margin: 0; cursor: pointer; }

/* Envoltura del calendario mensual dentro de un editor. El widget de dias es
   .zv-cal*, que ya estaba en el kit: esto solo le pone marco y navegacion. */
.zv-calendario { margin-bottom: var(--sp-4); }
.zv-cal-nav {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); margin-bottom: var(--sp-3);
}


/* =====================================================================
   ARMAZON DE LAS 8 PANTALLAS DE ACCION  (2026-08-09)
   ---------------------------------------------------------------------
   Todo cuelga de .zv-accion, que va en el <body> de esas ocho y de ninguna
   otra. El kit lo comparten las 85 pantallas de PaskayMind: una regla suelta
   aca redisena la app entera sin que nadie lo haya pedido.
   Origen: comparacion contra los mockups de Stitch, 148 brechas confirmadas.
   ===================================================================== */

/* --- 1. La barra de acciones es el PIE de la pantalla ----------------- */
/* Estaba dentro de un .zv-panel-body y, peor, dentro de la columna angosta:
   el boton principal quedaba a mitad de camino y la pantalla no cerraba.
   Ahora manda sobre las dos columnas, con el secundario a la izquierda y el
   primario a la derecha, que es como lo lee el diseno. */
.zv-accion .zv-editor-actions {
  justify-content: space-between;
  align-items: center;
  margin-top: var(--sp-5);
  padding: var(--sp-4) 0 0;
  border-top: 1px solid var(--border);
}
/* Con un solo boton, que no se pegue a la izquierda. */
.zv-accion .zv-editor-actions > :only-child { margin-left: auto; }
/* Los primarios de la derecha se agrupan y el resto empuja. */
.zv-accion .zv-editor-actions .btn-primary { margin-left: auto; }
.zv-accion .zv-editor-actions .btn-primary ~ .btn { margin-left: 0; }

/* --- 2. El rotulo va ENCIMA del campo --------------------------------- */
/* El grid de 190px + 1fr es la silueta del formulario generado, y es lo que
   hace que las ocho se vean "hechas por el framework" antes de leer una
   palabra. El kit ya apilaba asi en movil (:980): esto lo vuelve el default
   de las pantallas de accion en todos los anchos. */
.zv-accion .zv-field-row {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-1);
}
.zv-accion .zv-field-row > label { padding-top: 0; }
/* La fila de switch sigue siendo rotulo-a-la-izquierda: ahi el control es
   chico y el par se lee en linea. Es la unica excepcion, y es la del diseno. */
.zv-accion .zv-field-row--switch { grid-template-columns: minmax(0, 1fr) auto; }

/* --- 3. Los pares que van lado a lado --------------------------------- */
/* hora/duracion, desde/hasta, los tres del receptor. Apilados de a uno
   estiran la pantalla y rompen la relacion entre campos que se leen juntos. */
.zv-field-grid--2, .zv-field-grid--3 {
  display: grid;
  gap: 0 var(--sp-4);
  align-items: start;
}
.zv-field-grid--2 { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.zv-field-grid--3 { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }

/* --- 4. Una sola jerarquia de titulo ---------------------------------- */
/* La barra tintada de .zv-panel-head hace que cada tarjeta pese lo mismo que
   la pantalla. El diseno pone versalitas chicas DENTRO de la tarjeta, con el
   mismo tratamiento que .zv-section-label, que el kit ya tenia. */
.zv-accion .zv-panel-head {
  background: transparent;
  border-bottom: 0;
  padding: var(--sp-5) var(--sp-5) 0;
}
/* El TITULO DE LA TARJETA es grande y en negrita, como en el mockup
   (text-lg font-bold). La versalita chica es para los ROTULOS DE SECCION que
   van DENTRO de la tarjeta, y para eso el kit ya tiene .zv-section-label.
   Meter los dos en el mismo molde dejo las tarjetas sin encabezado y la
   pantalla plana: medido como empeoramiento en ficha y cobrar. */
.zv-accion .zv-panel-title {
  font-size: 18px; font-weight: 700;
  letter-spacing: 0; text-transform: none;
  color: var(--text);
  margin: 0;
}
/* El cuerpo ya no necesita separarse de una barra que no existe. */
.zv-accion .zv-panel-head + .zv-panel-body { padding-top: var(--sp-3); }

/* --- 5. Cabecera de TAREA, no de entidad ------------------------------ */
/* El diseno abre con la flecha de volver pegada al titulo, en la misma linea,
   y el titulo nombra la ACCION. Lo servido ponia un enlace de texto en un
   renglon propio arriba y el h1 con el nombre de la tabla. */
.zv-task-head {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}
.zv-task-head .zv-page-title { margin: 0; }
.zv-task-head-volver {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  border: 0; border-radius: var(--r-input);
  background: transparent; color: var(--text-muted);
  cursor: pointer;
  transition: background var(--t), color var(--t);
}
.zv-task-head-volver:hover { background: var(--surface-alt); color: var(--primary); }
.zv-task-head-volver:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
/* El chip de estado, si lo hay, se va al extremo opuesto. */
.zv-task-head .zv-tag { margin-left: auto; }

/* --- 6. Dos columnas donde el formulario manda ------------------------ */
/* .zv-blo-cols es 45/55, pensada para bloquear (formulario chico, panel de
   consecuencias grande). En recibo y cobrar es al reves. */
.zv-blo-cols--form { grid-template-columns: minmax(0, 60fr) minmax(0, 40fr); }

/* =====================================================================
   TANDA 4 -- SENIAL. Severidad, cifra ancla y estados de color.
   ---------------------------------------------------------------------
   Va FUERA de @layer a proposito, igual que el resto de esta cola: son
   modificadores que tienen que ganarle al componente base declarado
   dentro de la capa.
   ===================================================================== */

/* --- 1. Tercer tono de aviso: ADVERTENCIA -------------------------- */
/* .zv-aviso informa (primary-soft) y --ojo baja a azul. Faltaba el tono de
   lo que puede salir mal. Lleva borde porque el ambar solo, sin contorno,
   se confunde con el fondo alterno de las tarjetas. */
.zv-aviso--alerta {
  background: #FBF0DC;
  border: 1px solid var(--warning);
  color: var(--warning-on-soft);
}
.zv-aviso--alerta .zv-aviso-titulo { color: var(--warning-on-soft); }
.zv-aviso--alerta > .zv-ico { color: var(--warning); }

/* --- 2. Cifra ancla: el numero que decide la accion ---------------- */
/* Selector con DOS clases para ganarle a `.zv-desglose > div` (0,1,1), que
   impone fila con space-between. Ver cabecera del parche. */
.zv-desglose > .zv-desglose-total--ancla {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 2px;
}
.zv-desglose > .zv-desglose-total--ancla > span:first-child {
  font-size: 11px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-muted);
}
.zv-desglose > .zv-desglose-total--ancla > span:last-child {
  font-size: 32px; line-height: 1.12; font-weight: 700;
  color: var(--primary);
  font-variant-numeric: tabular-nums;
}
/* Recibo: misma forma, un escalon menos. El neto es la cifra protagonista
   de su pantalla, pero comparte columna con el desglose de retencion. */
.zv-desglose > .zv-desglose-total--ancla-md > span:last-child { font-size: 24px; }

/* --- 3. Estados de color ------------------------------------------- */
/* El saldo en contra: en ficha y en agendar es lo unico que el diseno pinta
   en color de peligro, y hoy sale en tinta normal. */
.zv-monto--deuda { color: var(--danger-on-soft); font-weight: 700; }

/* Los dos contadores de la vista previa de recurrente. "Se saltaran" NO es
   una cancelacion -- usar --cancelada (rojo) exageraba, y --activo (morado)
   para "se crearan" no decia exito. */
.zv-tag--crea  { background: var(--success-soft); color: var(--success-on-soft); }
.zv-tag--salta { background: #F6EAD4;             color: var(--warning-on-soft); }

/* La fila que se salta: hoy solo baja a opacity .72, asi que en una tarjeta
   cuyo unico trabajo es distinguir lo que se crea de lo que se salta la
   distincion queda casi invisible. Fondo ambar, hora tachada, motivo en
   ambar. La opacidad se sube a 1: el color ya hace el trabajo. */
.zv-prev-item--salta {
  opacity: 1;
  background: #FBF0DC;
  box-shadow: inset 3px 0 0 var(--warning);
}
.zv-prev-item--salta .zv-prev-hora { text-decoration: line-through; }
.zv-prev-item--salta .zv-prev-motivo {
  color: var(--warning-on-soft);
  font-style: normal;
}

/* Aviso de campo: el mismo color y la misma familia, con el peso de una
   nota. `.zv-aviso` esta dimensionado para un aviso de panel (padding sp-3,
   margin-bottom sp-4) y dentro de un `.zv-field` empuja el formulario. */
.zv-aviso--compacto {
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  margin-bottom: 0;
}
.zv-aviso--compacto p { font-size: 12.5px; line-height: 1.45; }

/* =====================================================================
   TANDA 5 -- RETICULA dia x hora de agendar.
   ---------------------------------------------------------------------
   Reemplaza al flex-wrap de columnas independientes: sin eje de horas, las
   filas no se alineaban entre dias y la agenda se leia como cuatro listas
   de botones sueltas.
   ===================================================================== */

.zv-ret {
  display: grid;
  /* grid-template-columns lo pone el JS: el numero de dias es un dato. */
  gap: 4px;
  grid-auto-rows: minmax(64px, auto);
  overflow-x: auto;
  padding-bottom: var(--sp-2);
}

/* Cabecera de dia y eje de horas. */
.zv-ret-dia {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  min-height: 32px; padding: 4px 6px;
  font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-muted); background: var(--surface);
}
.zv-ret-hora {
  position: sticky; left: 0; z-index: 1;
  display: flex; align-items: flex-start; justify-content: flex-end;
  padding: 6px var(--sp-2) 0 0;
  font-size: 12px; color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  background: var(--surface);
}
/* La esquina: comparte los dos sticky, o al scrollear se le monta la hora. */
.zv-ret-esq { position: sticky; left: 0; top: 0; z-index: 3; background: var(--surface); }

/* Celda sin slot: ese dia no atiende a esa hora. Se dibuja, no se omite --
   un hueco en la grilla es informacion, y ademas mantiene la alineacion. */
.zv-ret-celda { border-radius: var(--r-input); background: var(--surface-alt); opacity: .45; }

/* El slot dentro de la reticula. La clase y el contrato son los de siempre;
   aca solo se le quita lo que era propio de la lista vertical. */
.zv-ret .zv-slot {
  margin: 0; height: 100%;
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  gap: 2px; text-align: left;
}
/* `.zv-slot-hora` y `.zv-slot-dur` YA existen en el kit (`:1790-1791`, de la
   fila de troceo de franja) y significan exactamente esto. Se reusan; aca
   solo se ajusta la segunda al espacio de una celda, y con el `.zv-ret`
   delante para no tocar como se ve en franja. */
.zv-ret .zv-slot-dur { font-size: 11.5px; line-height: 1.25; color: inherit; opacity: .85; overflow-wrap: anywhere; }

/* Ocupado: bloque crema con el nombre, como en el diseno. Deja de ser un
   boton atenuado y pasa a ser un bloque que dice quien esta. */
.zv-ret .zv-slot--ocu {
  opacity: 1; border-style: solid;
  background: #FBF0DC; border-color: var(--warning);
  color: var(--warning-on-soft);
}
/* Bloqueado: tramado, para que no compita con el ocupado ni parezca libre. */
.zv-ret .zv-slot--blo {
  opacity: 1; border-style: dashed;
  color: var(--text-muted);
  background: repeating-linear-gradient(45deg,
    var(--surface-alt) 0 6px, var(--surface) 6px 12px);
}
/* El elegido manda sobre los dos anteriores: va despues a proposito. */
.zv-ret .zv-slot--sel {
  background: var(--primary); border-color: var(--primary);
  color: var(--zv-on-primary); font-weight: 600;
}

/* En pantalla angosta la reticula scrollea; las columnas dejan de encogerse
   para que un dia no quede en 20px de ancho. */
@media (max-width: 700px) {
  .zv-ret { grid-auto-rows: minmax(56px, auto); }
  .zv-ret .zv-slot { padding: 4px 6px; font-size: 12px; }
}

/* =====================================================================
   TANDA 6 -- TARJETAS DE ACCION. Pie pegajoso, tiles de metodo, tarjeta
   de contexto y la tira del medio de cobro.
   ---------------------------------------------------------------------
   Fuera de @layer, igual que las colas t4/t5: son modificadores que
   tienen que ganarle al componente base declarado dentro de la capa.
   ===================================================================== */

/* --- 1. Pie de accion: pegajoso y agrupado a la derecha ------------- */
/* El armazon del 2026-08-09 separaba con space-between y empujaba el
   primario con margin-left:auto; el diseno agrupa TODO a la derecha y la
   barra acompana el scroll. Misma especificidad que el armazon (0,3,0):
   gana por orden, esta cola va despues. */
.zv-accion .zv-editor-actions--fija {
  position: sticky; bottom: 0; z-index: 30;
  background: var(--surface);
  padding: var(--sp-3) var(--sp-4);
  margin-top: var(--sp-5);
  border-top: 1px solid var(--border);
  border-radius: var(--r-input) var(--r-input) 0 0;
  box-shadow: 0 -4px 6px -1px rgba(27,29,54,.06);
}
.zv-accion .zv-editor-actions--der { justify-content: flex-end; }
.zv-accion .zv-editor-actions--der .btn-primary { margin-left: 0; }
/* La nota que vive DENTRO de la barra, a la izquierda de los botones
   (la linea de recordatorios de agendar). El margin-right:auto es el que
   agrupa los botones a la derecha cuando la nota esta presente. */
.zv-pie-nota {
  margin-right: auto; font-size: 13px; color: var(--text-muted);
  display: flex; align-items: center; gap: var(--sp-2);
}

/* --- 2. Tiles de opcion: icono arriba, rotulo debajo ---------------- */
.zv-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: var(--sp-2);
}
.zv-tile {
  position: relative;
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 6px;
  padding: 12px 8px; min-height: 64px;
  border: 1px solid var(--zv-control-line); border-radius: var(--r-card);
  background: var(--surface); color: var(--text-muted);
  font-size: 13px; font-weight: 600; cursor: pointer;
  transition: background var(--t), border-color var(--t), color var(--t);
}
.zv-tile:hover { background: var(--primary-soft); color: var(--primary); }
.zv-tile .zv-ico { width: 22px; height: 22px; }
/* El elegido: borde grueso sin mover el layout (el padding compensa el
   pixel extra). El :has cubre el tile-radio, que se marca solo. */
.zv-tile--on, .zv-tile:has(> input:checked) {
  border: 2px solid var(--primary); padding: 11px 7px;
  background: var(--primary-soft); color: var(--primary);
}
/* El radio dentro de un tile viaja en el POST pero no se pinta: el tile
   entero es el control. Sigue enfocable para el teclado. */
.zv-tile > input[type="radio"] {
  position: absolute; opacity: 0; pointer-events: none;
}

/* --- 3. Tarjeta de contexto DENTRO de un panel ----------------------- */
/* El panel ya es la tarjeta: el marco propio de .zv-pacctx duplicaba el
   borde y el mockup tiene UNO solo. */
.zv-panel .zv-pacctx--tarjeta {
  border: 0; background: transparent; padding: 0; margin: 0;
}
.zv-pacctx--tarjeta .zv-tag { margin-left: auto; }
/* Accion secundaria pintada como enlace, como el "Cambiar" del diseno. */
.zv-btn-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--primary); font-size: 13px; font-weight: 600;
  text-decoration: underline;
}
.zv-btn-link:hover { color: var(--primary-dark); }

/* --- 4. El medio de cobro como tira, en la tarjeta del metodo -------- */
.zv-medio-tira {
  display: flex; align-items: center; gap: var(--sp-4);
  margin-top: var(--sp-4); padding: var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r-input);
  background: var(--surface-alt);
}
.zv-medio-tira .zv-medio-qr {
  width: 128px; max-width: 128px; margin: 0; flex: 0 0 auto;
  background: var(--surface);
}
.zv-medio-tira .zv-medio-rot { margin: 0; font-size: 13px; font-weight: 400; color: var(--text-muted); }
.zv-medio-tira .zv-medio-dato { margin: 2px 0 0; font-size: 19px; font-weight: 700; letter-spacing: .02em; }
.zv-medio-tira .zv-field-nota { margin: var(--sp-2) 0 0; }
@media (max-width: 560px) {
  .zv-medio-tira { flex-direction: column; align-items: flex-start; }
}

/* --- 5. Aviso crema de la barra lateral ------------------------------ */
.zv-aviso--crema { background: #F3ECE1; }

/* =====================================================================
   TANDA 6 -- KIT FINO. Piezas por pantalla: cotejo de pago, fila del
   cobro en recibo, tarjeta de paciente en agendar, tiles-radio de
   recurrente, tira de cifras y divisor de franja, calendario compacto
   de bloquear. Fuera de @layer, como toda esta cola.
   ===================================================================== */

/* --- pago: el codigo de operacion se coteja contra la app del banco --- */
.zv-mono--lg { font-size: 18px; font-weight: 700; letter-spacing: .03em; }
/* La divisoria antes del pendiente y el enfasis del adelanto. Dos clases
   para ganarle a .zv-desglose > div (0,1,1). */
.zv-desglose > .zv-fila-corte {
  border-top: 1px solid var(--border);
  margin-top: var(--sp-2); padding-top: var(--sp-2);
}
.zv-desglose > .zv-fila-fuerte { font-weight: 600; color: var(--text); }
/* El grupo derecho de la barra: "Ahora no" + primario juntos, el
   destructivo queda solo a la izquierda con el space-between del armazon. */
.zv-acciones-grupo { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* --- recibo: el cobro elegido como fila destacada ---------------------- */
.zv-pacctx-imp {
  margin-left: auto; font-size: 20px; font-weight: 700;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* La columna lateral acompana el scroll. Va en la COLUMNA y no en cada
   panel: dos paneles pegajosos por separado se pisan al desplazarse. */
.zv-lado-pegado { position: sticky; top: var(--sp-4); }
/* El enlace de accion dentro de una tarjeta de contexto se va al borde;
   cuando comparte fila con el importe, vuelve a pegarse a el. */
.zv-pacctx > .zv-btn-link { margin-left: auto; }
.zv-pacctx-imp + .zv-btn-link { margin-left: var(--sp-2); }

/* --- agendar: la tarjeta del paciente en renglones --------------------- */
.zv-pacctx--renglones { flex-wrap: wrap; align-items: flex-start; }
.zv-pac-renglones { display: flex; flex-direction: column; gap: 3px; margin-top: 4px; }
.zv-pac-renglon {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--text-muted);
}
.zv-pac-renglon .zv-ico { color: var(--primary); flex: 0 0 auto; }
/* La franja tintada del pie: sangra hasta los bordes de la tarjeta. */
.zv-pacpie {
  flex-basis: 100%; display: flex; justify-content: space-between;
  align-items: center; gap: var(--sp-3);
  margin: var(--sp-3) calc(-1 * var(--sp-3)) calc(-1 * var(--sp-3));
  padding: var(--sp-2) var(--sp-3);
  border-top: 1px solid var(--border); background: #F3ECE1;
  border-radius: 0 0 var(--r-input) var(--r-input);
  font-size: 13px;
}
.zv-pacpie > span + span { border-left: 1px solid var(--border); padding-left: var(--sp-3); }

/* --- recurrente: tiles a lo ancho, caja del switch, icono del motivo -- */
.zv-tiles--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.zv-caja-switch {
  background: var(--surface-alt); border: 1px solid var(--border);
  border-radius: var(--r-input); padding: var(--sp-3);
}
.zv-caja-switch .zv-field-nota { margin: 2px 0 0; display: block; }
.zv-prev-motivo > .zv-ico { width: 14px; height: 14px; margin-right: 4px; vertical-align: -2px; }

/* --- franja: subtitulo, divisor de versalitas y tira de cifras --------- */
.zv-page-sub { margin: calc(-1 * var(--sp-2)) 0 var(--sp-4); font-size: 13.5px; color: var(--text-muted); }
.zv-divisor {
  display: flex; align-items: center; gap: var(--sp-3);
  margin: var(--sp-5) 0 var(--sp-4);
  font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-muted);
}
.zv-divisor::before, .zv-divisor::after {
  content: ""; flex: 1; border-top: 1px solid var(--border);
}
.zv-cifras-tira {
  display: flex; margin-bottom: var(--sp-4);
  border: 1px solid var(--border); border-radius: var(--r-input);
  background: var(--surface-alt);
}
.zv-cifras-tira > div {
  flex: 1 1 0; display: flex; flex-direction: column; gap: 2px;
  align-items: center; padding: var(--sp-3); text-align: center;
}
.zv-cifras-tira > div + div { border-left: 1px solid var(--border); }
.zv-cifras-tira span {
  font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-muted);
}
.zv-cifras-tira strong { font-size: 18px; font-variant-numeric: tabular-nums; }

/* --- bloquear: calendario compacto y piezas del panel derecho ---------- */
/* El picker del mockup: sin marco por dia y sin aspect-ratio 1/1, que
   duplicaba el alto y desbalanceaba la columna. Los estados se
   re-declaran DENTRO de la variante porque (0,2,0) del compacto le
   ganaria a los modificadores (0,1,0) del kit. */
.zv-cal--compacto .zv-cal-day {
  aspect-ratio: auto; min-height: 34px;
  border-color: transparent; background: none; border-radius: 6px;
}
.zv-cal--compacto .zv-cal-day:hover { background: var(--primary-soft); border-color: transparent; }
.zv-cal--compacto .zv-cal-day--sel { background: var(--primary); border-color: var(--primary); }
.zv-cal--compacto .zv-cal-day--rango { background: var(--primary-soft); }
.zv-cal-flecha {
  border: 0; background: none; cursor: pointer;
  font-size: 18px; line-height: 1; padding: 2px 8px;
  color: var(--text-muted); border-radius: 6px;
}
.zv-cal-flecha:hover { background: var(--primary-soft); color: var(--primary); }
.zv-hr { border: 0; border-top: 1px solid var(--border); margin: var(--sp-4) 0; }
/* "Todo el dia" apaga las horas sin esconderlas. */
.zv-fila-apagada { opacity: .5; }
/* Las sesiones afectadas como tarjetas, no renglones de hairline. */
.zv-blo-item {
  display: flex; align-items: center; gap: var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r-card);
  padding: var(--sp-3);
}
.zv-blo-item + .zv-blo-item { margin-top: var(--sp-2); }
/* El pie de cifras como franja tintada pegada al fondo del panel, con
   divisor vertical y rotulos en versalitas de 10px. Sangra el padding
   sp-5 del panel-body. */
.zv-blo-cifras {
  display: flex; gap: 0;
  margin: var(--sp-4) calc(-1 * var(--sp-5)) calc(-1 * var(--sp-5));
  padding: 0;
  border-top: 1px solid var(--border); background: var(--surface-alt);
  border-radius: 0 0 var(--r-card) var(--r-card);
}
.zv-blo-cifras > div { flex: 1 1 0; padding: var(--sp-3) var(--sp-5); }
.zv-blo-cifras > div + div { border-left: 1px solid var(--border); }
.zv-blo-cifras span {
  font-size: 10px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase;
}
/* El chip de conteo del panel se empuja al borde, no pegado al titulo. */
.zv-accion .zv-panel-head > .zv-tag { margin-left: auto; }

/* A8 y A10 del plan de privacidad (2026-08-23).
   El aviso legal va PLEGADO al pie de la tarjeta: tiene que estar en la misma
   pantalla donde el paciente entrega sus datos, pero no puede interrumpir la
   reserva. El boton de salida se pinta como enlace discreto por lo mismo. */
.zv-portal-legal { margin-top: var(--sp-5); border-top: 1px solid var(--zv-borde, #e3e6ea); padding-top: var(--sp-3); font-size: 0.82rem; color: var(--zv-texto-suave, #5b6470); }
.zv-portal-legal > summary { cursor: pointer; font-weight: 600; list-style: revert; }
.zv-portal-legal ol { margin: var(--sp-3) 0 0; padding-left: 1.2em; }
.zv-portal-legal li { margin-bottom: var(--sp-2); line-height: 1.45; }

/* 2026-09-02: variante de PIE del aviso de privacidad. El bloque completo
   distraia de la reserva -- que es a lo que el paciente viene -- asi que el
   resumen pasa a ser un enlace de una linea, chico y en color suave, sin el
   separador que lo hacia leer como una seccion mas de la pantalla. El texto
   sigue ahi, a un clic: la Ley 29733 pide informar en el MISMO lugar donde se
   entregan los datos, asi que se pliega, no se mueve ni se borra. */
.zv-portal-legal--pie { margin-top: var(--sp-3); border-top: 0; padding-top: 0; font-size: 0.76rem; text-align: center; }
.zv-portal-legal--pie > summary { font-weight: 400; text-decoration: underline; opacity: .75; }
.zv-portal-legal--pie > summary:hover { opacity: 1; }
.zv-portal-legal--pie ol { text-align: left; }
.zv-portal-exit { margin-top: var(--sp-4); text-align: center; }
.zv-portal-exit .btn { font-size: 0.85rem; }

/* ===== PORTAL: eleccion visible antes de confirmar (2026-09-03) =====
   El portal partio "elegir" de "reservar": el clic en un horario o en una forma
   de pago ya no dispara nada, solo marca. Eso EXIGE que lo elegido se vea, y
   hasta hoy nada en el portal marcaba una eleccion.

   Por que `button.zv-pay-method--sel` y no `.zv-pay-method--sel`:
   la regla `button.zv-day-pill, button.zv-pay-method { color: inherit }` de mas
   arriba en este mismo archivo tiene especificidad (0,1,1); un modificador
   pelado (0,1,0) PIERDE contra ella y el texto se quedaria en --text sobre un
   fondo --primary. Ese es exactamente el defecto que ya arrastra
   `.zv-day-pill--sel` (0,1,0, sin usar en el portal vivo): sobre <button> da
   2.06:1 y falla WCAG AA, y el gate no lo ve porque calcula el contraste de la
   dupla DECLARADA (--zv-on-primary sobre --primary = 7.4:1), no del par que de
   verdad computa el navegador. Aca se declara con el `button` delante para
   ganar esa carrera; el color de texto se pide explicito por lo mismo.

   `.zv-slot--sel` NO necesita este tratamiento: `.zv-slot` no entra en la regla
   de `color: inherit`, y su definicion vive fuera de @layer mas abajo. Se usa
   tal cual. */
button.zv-pay-method--sel {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--zv-on-primary);
}
button.zv-pay-method--sel .zv-pay-lbl,
button.zv-pay-method--sel .zv-pay-when,
button.zv-pay-method--sel .zv-pay-amount,
button.zv-pay-method--sel .zv-pay-ico { color: var(--zv-on-primary); }
button.zv-pay-method--sel:hover:not(:disabled) { background: var(--primary); border-color: var(--primary); }

/* La barra de accion del portal. `.zv-editor-actions` ya existe (y en <=480px
   apila en column-reverse con los botones al 100%), pero el portal nunca la
   habia usado: sus botones eran sueltos. Se reusa tal cual y solo se le quita
   el borde superior, que aca duplicaria el del resumen que va justo encima. */
.zv-portal-acciones { border-top: 0; padding-top: var(--sp-2); }

/* ===== PORTAL: el calendario mensual del paciente (2026-09-03) =====
   El widget .zv-cal* ya existia en el kit y lo usaba el editor de bloqueos del
   psicologo, donde las celdas son <div> con onclick. En el portal tienen que ser
   <button> -- es el mismo fix H-02 que ya se aplico a dias, horarios y medios de
   pago -- y eso obliga a dos cosas que el kit no traia:

   (1) EL RESET DE APARIENCIA NATIVA. La regla de mas arriba enumera
       `button.zv-day-pill, button.zv-slot, button.zv-pay-method`; .zv-cal-day no
       estaba en esa lista porque nunca habia sido un boton. Sin esto hereda el
       fondo y el borde del sistema operativo y el calendario se ve como una
       botonera de Windows 95.

   (2) OBJETIVO TACTIL DE 44px (I-15 / WCAG 2.5.5). .zv-cal-day solo declara
       aspect-ratio: 1/1: a 320px de ancho, siete columnas con gap de 4px dan
       celdas de ~40px, por debajo del minimo que este mismo proyecto ya exigio
       para .zv-day-pill y .zv-slot. Ningun gate lo mide (banco-kit busca anchos
       DECLARADOS, no computados), asi que se pone a mano.

   NO se toca el @media de la vista semanal que pasaba de 7 a 4 columnas: un
   calendario no puede reflowear a 4 columnas sin romper la correspondencia entre
   celda y dia de la semana. En movil las celdas se achican, la grilla no. */
button.zv-cal-day {
  -webkit-appearance: none; appearance: none;
  font: inherit; color: inherit;
  min-height: 44px;
}
.zv-cal--portal { gap: var(--sp-2); }
.zv-cal--portal .zv-cal-day { min-height: 44px; }
/* El dia CON cupo es lo unico que el paciente busca: se marca con el borde de
   exito que el kit ya define en .zv-cal-day--libre, y se refuerza el peso para
   que no dependa solo del color (el borde verde sobre blanco no llega a 3:1
   contra el borde neutro, asi que por si solo no seria una senal accesible). */
.zv-cal--portal .zv-cal-day--libre { font-weight: 700; border-width: 2px; }
.zv-cal--portal .zv-cal-day--off { opacity: .55; }

/* =====================================================================
   PARTE B RESPONSIVE: "Tus sesiones" del portal en tarjetas (2026-09-17).
   A <=767px la tabla de 4 columnas (fecha/duracion/estado/acciones)
   desbordaba su tarjeta (383>311px medido) y CORTABA los botones
   Cancelar/Reprogramar/No mover del ultimo <td>. El portal es standalone
   (no iframe), asi que el @media mide el dispositivo (no aplica K9).
   Scope: #zvp-citas -> no toca ninguna otra tabla del sistema. */
@media (max-width: 767px) {
  #zvp-citas .table,
  #zvp-citas .table tbody,
  #zvp-citas .table tr,
  #zvp-citas .table td { display: block; width: 100%; }
  #zvp-citas .table { overflow: visible; margin-bottom: 0; }
  #zvp-citas .table tr {
    border: 1px solid var(--border);
    border-radius: var(--r-card);
    background: var(--surface);
    padding: var(--sp-3);
    margin-bottom: var(--sp-3);
  }
  #zvp-citas .table td { border: 0; padding: 2px 0; }
  #zvp-citas .table td:first-child { font-weight: 600; }
  #zvp-citas .table td:last-child {
    display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-2);
  }
  #zvp-citas .table td:last-child .btn {
    flex: 1 1 auto; min-width: 128px; min-height: 44px;
  }
  /* El caso "sin accion posible" (span.zv-hint con el motivo) ocupa la fila. */
  #zvp-citas .table td:last-child .zv-hint { flex: 1 1 100%; }
}

/* >>> PARTE-B-SHELL-PORTAL INICIO (2026-09-17) */
/* =====================================================================
   PARTE B RESPONSIVE (2026-09-17): shell burger + calendario portal.
   Verificado por el mapeo mapa-responsive-zolvia. Todo CSS append-only.
   1) Burger/salir del topbar a piso tactil 44px (unico acceso en movil).
   2) Calendario del portal deja de desbordar a <=560px: gap 4px y las celdas
      encogen (aspect-ratio auto + min-width 0) para caber en la tarjeta sin
      recortar la ultima columna; mantienen 44px de alto tactil. Acotado a
      .zv-cal--portal: el calendario del psicologo (iframe) no se toca.
   3) Flechas de mes del portal a 44px (unico control de meses siguientes).
   4) Boton-icono de fila .zv-grid-accion a 40px SOLO en movil. */
.zv-topbar-burger, .zv-topbar-salir {
  min-width: 44px; min-height: 44px;
  align-items: center; justify-content: center;
}
.zv-portal .zv-cal-flecha {
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
}
@media (max-width: 560px) {
  .zv-cal--portal { gap: var(--sp-1); }
  .zv-cal--portal .zv-cal-day,
  .zv-cal--portal .zv-cal-head { aspect-ratio: auto; min-width: 0; }
}
@media (max-width: 480px) {
  .zv-grid-accion { width: 40px; height: 40px; }
}
/* <<< PARTE-B-SHELL-PORTAL FIN */

/* >>> FC-ICONOS-SIN-FUENTE
   FullCalendar 6 trae sus iconos (flechas, cerrar) como una fuente incrustada
   data:, y la CSP del webapp (font-src 'self', PermissionsPolicyFilter) la
   bloquea: cada flecha salia como un cuadrado. Se dibujan con bordes, sin
   fuente, para no aflojar la CSP. La especificidad alta es a proposito: la
   regla de FullCalendar lleva font-family !important y se inyecta en tiempo
   de ejecucion. */
.zv-app .fc .fc-icon { font-family: inherit !important; position: relative; }
.zv-app .fc .fc-icon-chevron-left::before,
.zv-app .fc .fc-icon-chevron-right::before,
.zv-app .fc .fc-icon-chevrons-left::before,
.zv-app .fc .fc-icon-chevrons-right::before {
  content: "" !important;
  position: absolute; top: 50%; left: 50%;
  width: .42em; height: .42em;
  border: solid currentColor; border-width: 0 0 .12em .12em;
}
.zv-app .fc .fc-icon-chevron-left::before,
.zv-app .fc .fc-icon-chevrons-left::before { transform: translate(-30%, -50%) rotate(45deg); }
.zv-app .fc .fc-icon-chevron-right::before,
.zv-app .fc .fc-icon-chevrons-right::before { transform: translate(-70%, -50%) rotate(-135deg); }
.zv-app .fc .fc-icon-x::before { content: "\00d7" !important; }
/* <<< FC-ICONOS-SIN-FUENTE FIN */
/* >>> MARCA-PASKAYMIND INICIO
   Wordmark de la marca como SVG inline (geometria propia, no Inter): "paskay" usa
   currentColor (el color del contenedor) y "mind" va en achiote. En la barra lateral
   oscura "mind" usa el achiote claro del logo negativo (#F08A69, 6.70:1 sobre la tinta). */
.zv-app .zv-wm { display: block; height: 1.2em; width: auto; overflow: visible; }
.zv-app .zv-wm .zv-wm-b { stroke: var(--accent-dark); }
.zv-app .zv-wm .zv-wm-dot { fill: var(--accent-dark); }
.zv-app .zv-nav-marca .zv-wm .zv-wm-b { stroke: #F08A69; }
.zv-app .zv-nav-marca .zv-wm .zv-wm-dot { fill: #F08A69; }
/* <<< MARCA-PASKAYMIND FIN */
