/* Vive UI 3.1.0 — bundle.css. Loads after tokens.css. Las clases vv-* son API pública y estable (React, Svelte y Astro las comparten); ver CHANGELOG.md. */
/* Reset de caja global: sin esto, `height: var(--row-h)` + `border: 1px` en un control (Input,
   GroupedRow, DataTable td…) rendería 2px más alto que --row-h (border sumándose al content-box),
   rompiendo la rejilla de 4px en cualquier fila con hairline. Ver README "Rejilla y alineación". */
*, *::before, *::after { box-sizing: border-box; }
/* Contención global (2.4.1, README "Rejilla y alineación" regla 9): una imagen o SVG suelto (cover
   de MediaCard, ilustración, logo) nunca desborda su contenedor. Cualquier width/height explícito
   de un componente concreto (.vv-icon, .vv-mediacard-img…) sigue ganando por especificidad. */
img, svg { max-width: 100%; }
/* [hidden] global (2.8.0, feedback esaem PR #120 T04c): varios overlays sin React (.vv-confirm-scrim, .vv-drawer-scrim, .vv-menu, .vv-accountmenu, .vv-notiflist-popover, vv-authpage-*, .vv-tooltip, ya corregido .vv-sheet-scrim) fijan `display` incondicional, así que el atributo nativo `hidden` (la única forma de ocultar un nodo estatico sin React) no ganaba: `display:flex/inline-flex` del propio componente pesa lo mismo que `[hidden]{display:none}` y el último en el cascade era el del componente. `!important` aquí es la excepción documentada: es la
   unica forma de que un atributo nativo siga ganando siempre, sin tener que enumerar cada clase vv-* que fija su propio display. */
[hidden] { display: none !important; }

:root {
  /* M3 Expressive motion physics, compiled to CSS linear(): spatial springs move/resize/reshape (overshoot), effects springs recolor/fade (no overshoot). */
  --spring-spatial-fast: linear(0, 0.149, 0.446, 0.733, 0.94, 1.053, 1.093, 1.087, 1.061, 1.033, 1.011, 0.997, 0.992, 0.991, 0.993, 0.996, 0.998);
  --dur-spatial-fast: 353ms;
  --spring-spatial-default: linear(0, 0.098, 0.298, 0.508, 0.686, 0.819, 0.91, 0.965, 0.996, 1.01, 1.015, 1.014, 1.012, 1.008, 1.006, 1.003, 1.002);
  --dur-spatial-default: 417ms;
  --spring-spatial-slow: linear(0, 0.098, 0.296, 0.506, 0.684, 0.818, 0.908, 0.965, 0.996, 1.01, 1.015, 1.014, 1.012, 1.009, 1.006, 1.003, 1.002);
  --dur-spatial-slow: 573ms;
  --spring-effects-fast: linear(0, 0.106, 0.302, 0.492, 0.646, 0.761, 0.842, 0.897, 0.934, 0.958, 0.974, 0.984, 0.99, 0.994, 0.996, 0.998, 0.999);
  --dur-effects-fast: 143ms;
  --spring-effects-default: linear(0, 0.103, 0.295, 0.484, 0.638, 0.754, 0.836, 0.892, 0.93, 0.955, 0.972, 0.982, 0.989, 0.993, 0.996, 0.997, 0.998);
  --dur-effects-default: 217ms;
  --spring-effects-slow: linear(0, 0.102, 0.293, 0.481, 0.635, 0.751, 0.833, 0.89, 0.929, 0.954, 0.971, 0.982, 0.988, 0.993, 0.995, 0.997, 0.998);
  --dur-effects-slow: 305ms;
  /* the brand spring (stiffness 320, damping 22): the signature press/morph, between spatial-fast and spatial-default */
  --ease-spring: linear(0, 0.145, 0.433, 0.714, 0.919, 1.036, 1.082, 1.082, 1.061, 1.036, 1.014, 1.001, 0.994, 0.993, 0.994, 0.996, 0.998);
  --dur-spring: 550ms;
  --ease-out: var(--spring-effects-fast);
  --dur-fast: var(--dur-effects-fast);
  --dur-base: var(--dur-effects-default);
  /* density: comfortable by default */
  --text-base: var(--text-comfortable);
  --row-h: var(--row-comfortable);
  /* Escala secundaria (2.5.1): la altura del escalón inmediatamente más denso. Un solo tamaño «sm»
     por densidad — 28 / 32 / 40 — para pastillas de cabecera y acciones de fila; nunca tamaños libres. */
  --row-h-sm: var(--row-compact);
  --icon: var(--icon-size);
  /* Single page gutter: every shell/page header, its tabs and its content start at the same x.
     space-6 (24px) desktop/tablet; the width-compact class (<600px) redefines it to space-4 (16px)
     wherever a container query can key off it (see @container blocks below and README "Rejilla y
     alineación"). */
  --page-gutter: var(--space-6);
  /* Anchos de columna de tabla (2.11.0): escala corta en la rejilla de 4px —96/120/144/168/192— para
     que una columna se dimensione por su tipo/rol (`size`) y no por píxeles libres por producto. */
  --dt-col-xs: 96px; --dt-col-sm: 120px; --dt-col-md: 144px; --dt-col-lg: 168px; --dt-col-xl: 192px;
}
[data-density="compact"] { --text-base: var(--text-compact); --row-h: var(--row-compact); --row-h-sm: 28px; }
[data-density="touch"] { --text-base: var(--text-touch); --row-h: var(--row-touch); --row-h-sm: var(--row-comfortable); --icon: 20px; }
@media (max-width: 599.98px) { :root { --page-gutter: var(--space-4); } }

@media (prefers-reduced-motion: reduce) {
  :root { --ease-spring: var(--ease-out); --dur-spring: 1ms; --spring-spatial-fast: var(--ease-out); --spring-spatial-default: var(--ease-out); --spring-spatial-slow: var(--ease-out); --dur-spatial-fast: 1ms; --dur-spatial-default: 1ms; --dur-spatial-slow: 1ms; }
}

body { margin: 0; font-family: var(--font-sans); font-size: var(--text-base); line-height: 1.5; background: var(--canvas); color: var(--ink); -webkit-font-smoothing: antialiased; }
.vv-row { display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; padding: var(--space-4); }
.vv-stack { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4); }

/* .vv-grid — rejilla de tiles/tarjetas (StatTile, Card, MediaCard…): tantas columnas como quepan de
   --vv-grid-min (224px por defecto), una sola en contenedor estrecho. Base de StatGrid/CardGrid (2.12.0). */
.vv-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--vv-grid-min, 224px)), 1fr)); }
.vv-grid > * { min-width: 0; }

/* .vv-split — principal 2 : lateral 1 (primer hijo = principal). Responde al ancho de SU caja, no al
   viewport: pasa a una columna cuando no caben 512px + 256px + gap, también dentro de un tile de
   .vv-grid o con el sidebar abierto. Sin @container (no depende de un ancestro). Base de SplitLayout (2.12.0). */
.vv-split { display: flex; flex-wrap: wrap; gap: var(--space-6); }
.vv-split > * { flex: 1 1 256px; min-width: 0; }
.vv-split > :first-child { flex: 2 1 512px; }

/* ===== Utility: expanded touch hit-area =====
   Pattern used ~10 times across the kit (FilterChip, FilterChip's remove button, Button sm,
   Stepper circle, PanelToggle, Breadcrumb link, GroupedList's add button, Toast action/close,
   Gallery check, ResizeHandle): a control whose visual size is intentionally kept below the 48×48
   touch minimum (M3: contenedor pequeño / objetivo táctil 48) grows only its TAP TARGET via an
   invisible `::before`/`::after` with a negative `inset` — the visible box never changes size.
   `inset: Y X` (2-value shorthand) means Y = top & bottom, X = left & right; a negative value grows
   that axis outward, 0 leaves it untouched (e.g. a wide pill only needs to grow vertically).
   Each component below sets `--vv-hit-y`/`--vv-hit-x` (both default `0px` = no growth) with the
   exact inset value for its own geometry, right next to its own rule, same as it always has — only
   the `content: ''; position: absolute; inset: …` boilerplate (the part that was copy-pasted, and
   the part where a copy-pasted fixed inset silently goes stale if the box's own size changes, see
   FC-01b) is centralized here, once per trigger. Reuse this: set the two custom properties on the
   component's own selector, do not copy the `content`/`position`/`inset` triplet again.
   Three trigger groups below, not one — kept as three because the trigger CONDITION itself was
   never part of the duplicated pattern: `[data-density="touch"]` (an explicit app-level density
   setting) and `(pointer: coarse)` / `(any-pointer: coarse)` (hardware capability, primary vs. any
   attached pointer) are different conditions with different existing call sites; collapsing them
   into one would change which devices get the expanded tap target under which density/pointer
   combination — a behavior change this refactor does not make. A component still needs its own
   `position: relative` (or `position: absolute`, if it already is) for the pseudo-element to anchor
   to the right box; that already exists on every component below except where noted inline.
   ⚠️ Known pre-existing limitation, not introduced by this utility: a component (or an ancestor,
   e.g. `.vv-btn`'s own `overflow: hidden`, `.vv-gal-item`'s `overflow: hidden`,
   `.vv-shell-breadcrumbs-full`'s `overflow: hidden`) that clips overflow also clips this
   expansion — `.vv-btn-sm`/`.vv-btn-sm.vv-btn-icon` and `.vv-shell-breadcrumb-link` never actually
   grow past their own box in current markup, and `.vv-gal-check` only grows on the two edges with
   room before `.vv-gal-item`'s own edge. Verified by measuring real `elementFromPoint` hit
   rectangles, not just the computed `inset` — see `reports/feedback.md`. Fixing the ancestor clip is
   a separate change (touches ellipsis/thumbnail clipping behavior) and out of scope here.
   ⚠️ 4th known limitation (auditoría adaptativa 2.9.0, ya excepcionado en `scripts/overflow.js`
   `SELECTOR_EXCEPTIONS` para `.vv-shell-breadcrumb-link`): cuando el elemento que lleva el
   `::before` combina `white-space: nowrap` + `position: relative` + un ANCESTRO con
   `overflow: visible` (no `hidden`, el caso contrario al de arriba), el lado DERECHO de un `inset`
   negativo SÍ infla `scrollWidth` por encima de `clientWidth` — a diferencia del lado izquierdo, que
   en el modelo de caja estándar no lo afecta. `.vv-shell-breadcrumb-link` mide 4px de "exceso" en
   `scrollWidth` (su `--vv-hit-x` de -4px) aunque el TEXTO real («Clientes») nunca se recorta ni un
   píxel — falso positivo de la categoría 2 del script de overflow (mide de más, no un corte real),
   no una regresión de contención. No cambiar `--vv-hit-x`/`--vv-hit-y` para "arreglar" esto: el
   hueco es del cálculo de `scrollWidth` del navegador con `::before` absoluto, no del layout visible;
   cualquier control nuevo con este trío (nowrap + relative + overflow visible en el ancestro) y un
   hit-area que crezca por la derecha necesita su propia entrada en `SELECTOR_EXCEPTIONS`. */
[data-density="touch"] .vv-btn-sm::before,
[data-density="touch"] .vv-btn-sm.vv-btn-icon::before,
[data-density="touch"] button.vv-stepper-circle::before,
[data-density="touch"] .vv-paneltoggle::before,
[data-density="touch"] .vv-shell-breadcrumb-link::before,
[data-density="touch"] .vv-mg-cell::before,
[data-density="touch"] .vv-accent-swatch::before,
[data-density="touch"] .vv-dt-th-check input[type="checkbox"]::before,
[data-density="touch"] .vv-dt-td.vv-dt-th-check input[type="checkbox"]::before,
[data-density="touch"] .vv-dt-tree-toggle::before,
[data-density="touch"] .vv-ap-play-sm::before,
[data-density="touch"] .vv-onb-swatch::before,
[data-density="touch"] .vv-authpage-forgot::before,
[data-density="touch"] .vv-board-quickadd::before,
[data-density="touch"] .vv-shell-nav-section-toggle::before {
  content: ''; position: absolute; inset: var(--vv-hit-y, 0px) var(--vv-hit-x, 0px);
}
@media (pointer: coarse) {
  .vv-fchip::after, .vv-fchip-remove::after {
    content: ''; position: absolute; inset: var(--vv-hit-y, 0px) var(--vv-hit-x, 0px);
  }
}
@media (any-pointer: coarse) {
  .vv-group-header-add::before,
  .vv-toast-action::before, .vv-toast-close::before,
  .vv-gal-check::before,
  .vv-resizehandle-v::after, .vv-resizehandle-h::after {
    content: ''; position: absolute; inset: var(--vv-hit-y, 0px) var(--vv-hit-x, 0px);
  }
}

/* ===== Icon ===== */
/* V.Icon always puts this class on its <svg> (in addition to props.className). It carries the
   default size/behaviour so every per-component `:is(svg, .vv-icon) { width: … }` rule below also
   matches an icon marked up by hand (Astro/Svelte/HTML) instead of only a bare descendant <svg> —
   see assets/Icons/README.md for the exact markup. Component rules still override this default. */
.vv-icon { width: var(--icon); height: var(--icon); flex: none; stroke: currentColor; }

/* Anillo de foco global (2.8.0, feedback owner — WCAG 2.2 Focus Appearance): 2px solid + 2px de
   offset, igual que siempre, pero con --focus-ring-a (25% de transparencia sobre --focus-ring) en
   vez del color opaco — se ve más fino/sutil sin perder visibilidad. Verificado ≥3:1 en los 8
   temas/marca/tema combinados de brands.css y los 7 acentos de usuario (peor caso: emaus claro,
   ~3.35:1); no bajar de 70% de opacidad sin repetir esa comprobación. Todo :focus-visible de este
   archivo usa --focus-ring-a — no reintroducir var(--focus-ring) suelto en un outline nuevo. */
:where(.vv-btn, .vv-input, .vv-switch, .vv-datarow, .vv-dock-tab, .vv-palette-item, .vv-card[tabindex]):focus-visible {
  outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px;
}

/* Button */
.vv-btn {
  display: inline-flex; flex: none; align-items: center; justify-content: center; gap: var(--space-2);
  height: var(--row-h); min-width: 0; max-width: 100%; padding: 0 var(--space-4); overflow: hidden;
  font: inherit; font-weight: 500; font-size: var(--text-base); line-height: 1;
  border-radius: var(--radius-md); border: 1px solid var(--border);
  background: var(--surface); color: var(--ink); cursor: pointer; white-space: nowrap; text-overflow: ellipsis;
  transition: border-radius var(--dur-spring) var(--ease-spring), transform var(--dur-spring) var(--ease-spring),
    background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
/* Contención (2.4.1): cuando el propio `.vv-btn` es más estrecho que su contenido (max-width: 100%
   forzado por un contenedor angosto — un ítem de sidebar, un botón `width: 100%`), la etiqueta de
   texto es lo único que se recorta con elipsis; icono y kbd nunca se encogen (flex: none). Dentro de
   una barra de varios botones esto no entra en juego — ahí es la barra la que envuelve, ver abajo.
   `overflow: hidden` en `.vv-btn` es la red de seguridad final: si aun así (icono + kbd fijos +
   padding) no cupiera en un contenedor extremo, se recorta dentro de la píldora del botón en vez de
   salirse visualmente de sus bordes redondeados. */
.vv-btn-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* Contención (2.4.1) — regla 9 del README: una barra de acciones nunca desborda su tarjeta/diálogo.
   El botón NO se encoge dentro de la barra (sigue flex: none, su tamaño natural: icono/kbd fijos +
   texto sin recortar) — es la barra la que envuelve (flex-wrap: wrap, fijado en cada selector) y
   manda el botón que no cabe a una línea nueva entera. Encogerlo en vez de envolverlo (flex-shrink
   con min-width: 0) rompía peor de lo que arreglaba: con flex-wrap, el navegador solo pasa a una
   línea nueva cuando ya no puede seguir encogiendo, así que un botón con icono+kbd se aplastaba
   hasta que el kbd (flex: none) sobresalía de un botón más estrecho que su propio contenido mínimo.
   Bajo 360px de contenedor (MediaCard/ConfirmDialog/Drawer) los botones se apilan aparte a ancho
   completo vía @container, sin depender de este shrink. */
/* Contención: hijo directo de un contenedor flex/grid de layout no arrastra el ancho de su
   contenido — sin esto, un hijo con texto largo (o un componente con su propio ancho intrínseco)
   fuerza al padre a crecer y desborda la tarjeta/diálogo/panel que lo contiene. */
.vv-card > *, .vv-mediacard > *, .vv-confirm > *, .vv-sheet > *, .vv-drawer > *,
.vv-appearance > *, .vv-appearance-group > *, .vv-formactions > *, .vv-authpage-card > * {
  min-width: 0;
}
.vv-btn:hover { background: var(--surface-3); border-radius: var(--radius-xl); }
.vv-btn:active { transform: scale(0.97); border-radius: var(--radius-xl); }
.vv-btn-primary { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
.vv-btn-primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
.vv-btn-ghost { background: transparent; border-color: transparent; }
.vv-btn-ghost:hover { background: var(--surface-3); }
.vv-btn-danger { background: transparent; border-color: var(--border); color: var(--danger); }
.vv-btn-danger:hover { background: var(--danger-soft); border-color: var(--danger-soft); }
/* Destructiva confirmada (antes vivía junto a ConfirmDialog): fondo `danger`. Solo dentro de un
   ConfirmDialog — una página nunca muestra un «Eliminar» sólido sin confirmación delante. */
.vv-btn-danger-solid { background: var(--danger); border-color: var(--danger); color: var(--on-status); }
.vv-btn-danger-solid:hover { filter: brightness(.92); }
.vv-btn-danger-solid .vv-kbd { background: transparent; border-color: var(--on-status); color: var(--on-status); opacity: .85; }
.vv-btn-danger-solid[disabled] { filter: none; }
.vv-btn-danger-solid .vv-loading-shape { background: var(--on-status); }
.vv-btn-icon { padding: 0; width: var(--row-h); }
/* Ancho completo (2.5.1): hojas, formularios y acciones de página en móvil. Es del consumidor
   (prop `block`); las barras de acciones lo aplican solas bajo 360–480px vía @container. */
.vv-btn-block { display: flex; width: 100%; }
/* Escala secundaria «sm» (2.5.1): un solo tamaño por debajo de --row-h, ver --row-h-sm. En Touch mide
   40 visual y conserva el objetivo táctil de 48 con un área de pulsación invisible (M3: contenedor
   40 / target 48). Pastillas de cabecera, acciones de fila, icon-buttons de TopBar. */
.vv-btn-sm { height: var(--row-h-sm); padding: 0 var(--space-3); font-size: min(var(--text-base), 13px); gap: var(--space-1); position: relative; }
.vv-btn-sm.vv-btn-icon { width: var(--row-h-sm); padding: 0; }
.vv-btn-sm :is(svg, .vv-icon) { width: 16px; height: 16px; }
/* BTN-01 (auditoría móvil 2026-09-23): área táctil (utilidad arriba, "Expanded touch hit-area") —
   solo amplía el eje vertical (pensada para la píldora de texto, que ya es ancha de sobra) — un
   icon-button sm es cuadrado, así que se quedaba en 40px de ANCHO real de toque pese a medir 48 de
   alto; --vv-hit-x reutiliza el mismo cálculo para la variante cuadrada, simétrico en los dos ejes. */
[data-density="touch"] .vv-btn-sm { --vv-hit-y: calc((var(--row-h-sm) - var(--row-touch)) / 2); }
[data-density="touch"] .vv-btn-sm.vv-btn-icon { --vv-hit-x: var(--vv-hit-y); }
/* Botón como enlace (prop `href`): mismo aspecto, semántica <a>. */
a.vv-btn { text-decoration: none; }
a.vv-btn:visited { color: inherit; }
/* Cargando (prop `loading`): el icono se sustituye por el LoadingIndicator, el texto se queda,
   aria-busy y sin clics; nunca cambia de ancho (no aparece un spinner al lado del texto). */
.vv-btn-loading { pointer-events: none; }
.vv-spin { animation: vv-spin 0.9s linear infinite; }
@keyframes vv-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .vv-spin { animation: none; } }
.vv-btn-loading .vv-btn-label { opacity: .7; }
.vv-btn-primary .vv-loading-shape { background: var(--on-primary); }
.vv-btn[disabled]:not(.vv-btn-loading) { color: var(--ink-faint); background: var(--surface-2); border-color: var(--border-subtle); cursor: not-allowed; transform: none; }
.vv-btn :is(svg, .vv-icon) { width: var(--icon); height: var(--icon); }
.vv-kbd {
  flex: none;
  font-family: var(--font-mono); font-size: 11px; line-height: 16px; padding: 0 var(--space-1);
  border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface-2); color: var(--ink-muted);
}
.vv-btn-primary .vv-kbd { background: transparent; border-color: var(--on-primary); color: var(--on-primary); opacity: .8; }

/* Input */
/* 2.12.0: el defecto de .vv-field es «seguro para columna» (flex: 0 0 auto) — no reserva alto en
   ningún contenedor flex-column, sea cual sea su profundidad de anidamiento (grid de FormSection,
   .vv-form-span-2, un .vv-field anidado dentro de otro por el consumidor, etc.). Antes el defecto
   era `flex: 1 1 220px` (pensado para fila) y cada contenedor de columna nuevo (FormDialog,
   RecordPage con envoltorio de grid) necesitaba su propia excepción `> .vv-field`, que se rompía en
   cuanto había un div intermedio — la causa raíz de los defectos «hueco-formulario»/altura de 2.12.
   El único caso que necesita el reparto de 220px con crecimiento es la fila flex (`.vv-row`,
   `.vv-filterbar-scroll`): ahí se declara explícitamente como excepción, no como defecto. */
.vv-field { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; flex: 0 0 auto; }
.vv-row > .vv-field, .vv-filterbar-scroll > .vv-field { flex: 1 1 220px; }
.vv-field-inline { flex: 0 1 auto; width: auto; min-width: 160px; }
/* Escala secundaria (como Button size="sm"): filtros de barra de herramientas, cabeceras. */
.vv-field-sm .vv-input, .vv-field-sm .vv-select, .vv-field-sm .vv-select-rich-trigger { height: var(--row-h-sm); font-size: min(var(--text-base), 13px); }
.vv-field-sm .vv-select, .vv-field-sm .vv-select-rich-trigger { padding-right: var(--space-7); }
.vv-label { font-size: 12px; line-height: 16px; font-weight: 500; color: var(--ink-muted); }
.vv-input {
  width: 100%; min-width: 0; box-sizing: border-box;
  height: var(--row-h); padding: 0 var(--space-3); font: inherit; font-size: var(--text-base);
  border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--surface); color: var(--ink);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--dur-fast) var(--ease-out), border-radius var(--dur-spring) var(--ease-spring);
}
.vv-input::placeholder { color: var(--ink-faint); }
/* I-01 (2.7.0, Gastos): bajo 16px iOS hace zoom al enfocar. Con puntero grueso el campo nace a
   max(16px, --text-base) sea cual sea la densidad; la altura no cambia (--row-h), solo el cuerpo.
   M-01 (auditoría móvil 2026-09-23): `(pointer: coarse)` no dispara en un híbrido con ratón
   (Surface/Chromebook) que igual monta `data-density="touch"`, ni en un harness de auditoría sin
   emulación táctil — el selector de atributo es una red de seguridad explícita para que ≥16px no
   dependa solo de la capacidad real del puntero. Solo el tamaño de fuente del campo sube; el resto
   del texto en densidad touch (--text-touch, 15px) no cambia. */
@media (pointer: coarse) { .vv-input, .vv-select, .vv-select-rich-trigger, .vv-textarea { font-size: max(16px, var(--text-base)); } }
[data-density="touch"] .vv-input, [data-density="touch"] .vv-select, [data-density="touch"] .vv-select-rich-trigger, [data-density="touch"] .vv-textarea { font-size: max(16px, var(--text-base)); }
/* I-02 (2.7.0): campo de cifra — importe o total a escala title (20px) con cifras tabulares. Altura
   --row-h + space-2 (múltiplo de 4; excepción documentada en audit.js). Encaja con MoneyInput. */
.vv-input-lg { height: calc(var(--row-h) + var(--space-2)); font-size: 20px; font-weight: 600; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
/* I-03 (2.7.0): campo-botón (combobox / abre un Picker o Sheet): la misma piel que .vv-input sobre
   un <button class="vv-input vv-input-trigger">; el chevron va al final como <svg class="vv-icon">. */
.vv-input-trigger { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); text-align: left; cursor: pointer; }
.vv-input-trigger > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-input-trigger > :is(svg, .vv-icon) { flex: none; color: var(--ink-muted); }
.vv-input-trigger-placeholder { color: var(--ink-faint); }
.vv-input:hover { border-color: var(--border-strong); }
.vv-input:focus-visible { border-color: var(--border-strong); border-radius: var(--radius-lg); }
.vv-input[aria-invalid="true"] { border-color: var(--danger); }
/* Estados que faltaban (2.5.2): disabled igual que Select/Textarea; readonly se lee como texto, no como campo. */
.vv-input[disabled] { background: var(--surface-2); color: var(--ink-faint); border-color: var(--border-subtle); box-shadow: none; cursor: not-allowed; }
.vv-input[readonly]:not([disabled]) { background: var(--surface-2); border-color: var(--border-subtle); box-shadow: none; cursor: default; }
.vv-input[readonly]:not([disabled]):hover, .vv-input[readonly]:not([disabled]):focus-visible { border-color: var(--border-subtle); border-radius: var(--radius-md); }
/* Icono inicial (prop `icon`): búsqueda, importe, correo. El icono no roba el foco ni el clic. */
.vv-input-wrap { position: relative; display: flex; align-items: center; min-width: 0; }
.vv-input-wrap .vv-input-icon ~ .vv-input { padding-left: calc(var(--space-3) + 16px + var(--space-2)); }
.vv-input-wrap-end .vv-input { padding-right: calc(var(--space-2) + 32px); }
.vv-input-icon { position: absolute; left: var(--space-3); width: 16px !important; height: 16px !important; color: var(--ink-muted); pointer-events: none; }
.vv-input-suffix { position: absolute; right: 2px; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: 0; background: transparent; color: var(--ink-muted); cursor: pointer; border-radius: var(--radius-sm); }
.vv-input-suffix .vv-icon { width: 16px; height: 16px; }
.vv-help { font-size: 11px; line-height: 16px; color: var(--ink-muted); }
.vv-help-error { color: var(--danger); }

/* Badge */
.vv-badge {
  display: inline-flex; align-items: center; gap: var(--space-1); height: 20px; padding: 0 var(--space-2);
  font-size: 12px; line-height: 16px; font-weight: 500; border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--ink); border: 1px solid var(--border-subtle);
  white-space: nowrap; /* layout estable: un badge nunca se parte en dos líneas */
}
.vv-badge-primary { background: var(--primary-soft); color: var(--primary); border-color: transparent; }
.vv-badge-success { background: var(--success-soft); color: var(--success); border-color: transparent; }
.vv-badge-warning { background: var(--warning-soft); color: var(--warning); border-color: transparent; }
.vv-badge-danger { background: var(--danger-soft); color: var(--danger); border-color: transparent; }
.vv-badge-purple { background: var(--surface-2); color: var(--accent-purple); }
.vv-badge-pink { background: var(--surface-2); color: var(--accent-pink); }
.vv-badge-blue { background: var(--surface-2); color: var(--accent-blue); }
.vv-badge-orange { background: var(--surface-2); color: var(--accent-orange); }
.vv-badge-lime { background: var(--surface-2); color: var(--accent-lime); }
.vv-badge-dot { width: 6px; height: 6px; border-radius: var(--radius-full); background: currentColor; }
.vv-badge-pill { border-radius: var(--radius-full); }

/* Card */
.vv-card {
  display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4);
  border-radius: var(--radius-lg); border: 1px solid var(--border-subtle); background: var(--surface); color: var(--ink);
  box-shadow: var(--shadow-sm);
  transition: border-radius var(--dur-spring) var(--ease-spring), box-shadow var(--dur-base) var(--ease-out), transform var(--dur-spring) var(--ease-spring);
}
[data-density="compact"] .vv-card { padding: var(--space-3); }
[data-density="touch"] .vv-card { padding: var(--space-5); }
/* C-01 / C-02 (2.7.0, Gastos: 31 de 33 tarjetas alojan filas): `flush` quita padding y gap para que
   DataRow/GroupedList pongan los suyos (borde y superficie intactos, hijos recortados al radio);
   `flat` quita la sombra (listas largas, tarjetas dentro de una hoja). Combinables. */
.vv-card-flush { padding: 0; gap: 0; overflow: hidden; }
[data-density="compact"] .vv-card-flush, [data-density="touch"] .vv-card-flush { padding: 0; }
.vv-card-flat { box-shadow: none; }
.vv-card-interactive { cursor: pointer; }
.vv-card-interactive:hover { border-radius: var(--radius-2xl); box-shadow: var(--shadow-md); }
.vv-card-interactive:active { transform: scale(0.985); }
.vv-card-title { margin: 0; font-size: 15px; line-height: 22px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-card-meta { font-size: 11px; line-height: 16px; color: var(--ink-muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-card-body { margin: 0; color: var(--ink-muted); }
.vv-card-head { display: flex; justify-content: space-between; align-items: center; gap: var(--space-2); }
/* collapsible=true: the head becomes a real <button aria-expanded>, same visual rhythm as a plain head. */
.vv-card-head-toggle {
  all: unset; box-sizing: border-box; display: flex; width: 100%; min-height: 24px;
  justify-content: space-between; align-items: center; gap: var(--space-2); cursor: pointer;
}
/* Objetivo táctil (auditoría de contención 2026-09-23): es un `<button>` de ancho completo (el
   ancho nunca falla), pero la altura mínima se quedaba en 24px — mismo criterio que ya usa
   `.vv-group-header` (GroupedList) para su propia cabecera colapsable: crece la fila entera a
   `--row-h` (48 en touch), no un hit-area hack, porque toda la fila es el objetivo. */
[data-density="touch"] .vv-card-head-toggle { min-height: var(--row-h); }
.vv-card-head-toggle:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; border-radius: var(--radius-sm); }
.vv-card-head-right { display: flex; align-items: center; gap: var(--space-2); flex: none; }
.vv-card-chevron { color: var(--ink-muted); transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast); }
.vv-card-chevron-open { transform: rotate(180deg); }

/* ===== Collapse (grid-rows trick) =====
   Shared by Card (collapsible) and Disclosure/FormSection (collapsible): animates open/close without
   ever measuring the content's height in JS — 0fr → 1fr on a single-row grid track, the row's own
   intrinsic content driving 1fr, clipped by min-height:0/overflow:hidden on the one grid item inside. */
.vv-collapse {
  display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--dur-spatial-default) var(--spring-spatial-default);
}
.vv-collapse-open { grid-template-rows: 1fr; }
.vv-collapse-inner { display: flex; flex-direction: column; gap: var(--space-2); min-height: 0; overflow: hidden; }
@media (prefers-reduced-motion: reduce) {
  .vv-collapse { transition: none; }
  .vv-card-chevron { transition: none; }
}

/* Switch — input[type=checkbox][role=switch] real (form-safe, ver bundle.js); :checked/:disabled
   pintan pista y pulgar sobre el propio input, mismo patrón que .vv-checkbox-input. appearance:none
   quita el checkbox nativo del navegador para que solo se vea la pista/pulgar de vv-*. */
.vv-switch {
  --w: 36px; --h: 20px;
  appearance: none; -webkit-appearance: none; margin: 0;
  position: relative; width: var(--w); height: var(--h); flex: none; padding: 0; border: 1px solid var(--border-strong);
  border-radius: var(--radius-full); background: var(--surface-2); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
[data-density="compact"] .vv-switch { --w: 32px; --h: 18px; }
[data-density="touch"] .vv-switch { --w: 44px; --h: 24px; }
.vv-switch::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: calc(var(--h) - 6px); height: calc(var(--h) - 6px);
  border-radius: var(--radius-full); background: var(--ink-muted);
  transition: transform var(--dur-spring) var(--ease-spring), background-color var(--dur-fast) var(--ease-out);
}
.vv-switch:disabled { opacity: .45; cursor: not-allowed; }
.vv-switch:checked { background: var(--primary); border-color: var(--primary); }
.vv-switch:checked::after { transform: translateX(calc(var(--w) - var(--h))); background: var(--on-primary); }
.vv-switch-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); min-height: var(--row-h); }

/* DataRow */
.vv-list { display: flex; flex-direction: column; border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); background: var(--surface); overflow: hidden; }
.vv-list-head, .vv-datarow {
  display: grid; grid-template-columns: var(--cols, 1fr auto auto); align-items: center; gap: var(--space-4);
  min-height: var(--row-h); padding: 0 var(--space-4); font-size: var(--text-base);
}
.vv-list-head { min-height: var(--row-compact); font-size: 12px; font-weight: 500; color: var(--ink-muted); background: var(--surface-2); border-bottom: 1px solid var(--border-subtle); }
[data-density="compact"] .vv-list-head, [data-density="compact"] .vv-datarow { padding: 0 var(--space-3); }
[data-density="touch"] .vv-list-head, [data-density="touch"] .vv-datarow { padding: 0 var(--space-5); }
/* D-01 (2.7.0, Gastos: 41 filas, 2 familias de 4 celdas): presets de --cols. `cols-3` = icono/avatar · texto · valor;
   `cols-4` = fecha/icono · avatar · texto · valor. El texto siempre en minmax(0, 1fr) para poder elidir. */
.vv-datarow-cols-3 { --cols: auto minmax(0, 1fr) auto; }
.vv-datarow-cols-4 { --cols: auto auto minmax(0, 1fr) auto; }
.vv-datarow { border-bottom: 1px solid var(--border-subtle); cursor: default; transition: background-color var(--dur-fast) var(--ease-out); }
.vv-datarow:last-child { border-bottom: 0; }
.vv-datarow:hover { background: var(--surface-3); }
.vv-datarow[aria-selected="true"] { background: var(--primary-soft); }
.vv-datarow-text { min-width: 0; } /* grid children default to min-width:auto — without this the row grows instead of eliding */
.vv-datarow-title { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* Layout estable: el subtítulo va siempre en una línea, también en Touch (bug: "run_8f3a · hace 4 min" se partía en dos). */
.vv-datarow-sub { display: block; color: var(--ink-muted); font-size: 11px; line-height: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.vv-datarow-actions { display: none; gap: var(--space-1); }
.vv-datarow:hover .vv-datarow-actions, .vv-datarow:focus-within .vv-datarow-actions { display: inline-flex; }
[data-density="touch"] .vv-datarow-actions { display: inline-flex; gap: var(--space-2); }
/* DataRow · actionsVisibility="always" (2.12.0, §25 «bandeja accionable»): acciones visibles con
   icono + texto, alineadas a la derecha. >3 acciones: 2 visibles + «⋯» (V.Menu). Bajo 600px de la
   propia fila pasan a solo-icono (el aria-label del botón es el texto de la acción); en touch miden
   --row-h (48px) de zona de toque, y las demás densidades conservan su altura. `min-height:44px` no
   se fuerza para no romper el grid de altura de la fila en Compact. */
/* `border-bottom` fuera del modelo de caja y como sombra interior: con las acciones de altura
   completa (--row-h) el borde de 1px sumaba a la fila y la dejaba en 41px (auditoría). La sombra
   separa igual sin afectar al alto. */
.vv-datarow-always { container-type: inline-size; border-bottom: 0; }
.vv-datarow-always:not(:last-child) { box-shadow: inset 0 -1px 0 var(--border-subtle); }
.vv-datarow-actions-always { display: inline-flex; align-items: center; justify-content: flex-end; gap: var(--space-2); }
.vv-datarow-sub-link { color: var(--primary); text-decoration: none; }
.vv-datarow-sub-link:hover { text-decoration: underline; }
@container (max-width: 599.98px) {
  .vv-datarow-actions-always .vv-btn { padding: 0; width: var(--row-h); }
  .vv-datarow-actions-always .vv-btn-label { display: none; }
}
@supports not (container-type: inline-size) {
  @media (max-width: 599.98px) {
    .vv-datarow-actions-always .vv-btn { padding: 0; width: var(--row-h); }
    .vv-datarow-actions-always .vv-btn-label { display: none; }
  }
}
.vv-mono { font-family: var(--font-mono); font-size: 12px; color: var(--ink-muted); }
/* cifras de ancho fijo para HTML suelto (importes, contadores, tasas): composable con cualquier .vv-* */
.vv-tabular-nums { font-variant-numeric: tabular-nums; }

/* CommandPalette */
.vv-scrim { position: fixed; inset: 0; background: var(--scrim); display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; container-type: inline-size; /* full-viewport por `inset:0`: el ancho de este contenedor SIEMPRE es el del viewport real, sin depender de wiring del consumidor (evita el fallo D-01 de Drawer) */ }
.vv-palette {
  width: min(var(--palette-w), calc(100% - 2 * var(--space-6))); max-height: 60vh; display: flex; flex-direction: column;
  border-radius: var(--radius-lg); border: 1px solid var(--border-subtle); background: var(--surface); box-shadow: var(--shadow-lg); overflow: hidden;
  /* OV-01 (auditoría de espaciado 2026-09-28, queja owner): sin esto la fila del input queda a 1px
     (el borde del panel) del borde superior redondeado — su único "aire" era el centrado interno
     implícito del <input>, mismo bug que .vv-picker (búsqueda de Select) y .vv-sheet-head en
     escritorio (ver ambos más abajo). --space-2 iguala el padding vertical de .vv-palette-foot. */
  padding-top: var(--space-2);
  animation: vv-pop var(--dur-base) var(--ease-spring);
}
@keyframes vv-pop { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: none; } }
.vv-palette-input { height: 48px; padding: 0 var(--space-4); font: inherit; font-size: 15px; border: 0; border-bottom: 1px solid var(--border-subtle); background: transparent; color: var(--ink); outline: none; }
.vv-palette-input::placeholder { color: var(--ink-faint); }
.vv-palette-list { list-style: none; margin: 0; padding: var(--space-2); overflow: auto; }
.vv-palette-group { padding: var(--space-2) var(--space-2) var(--space-1); font-size: 11px; font-weight: 500; color: var(--ink-muted); }
.vv-palette-item {
  display: flex; align-items: center; gap: var(--space-3); min-height: var(--row-h); padding: 0 var(--space-2);
  border-radius: var(--radius-sm); color: var(--ink); cursor: pointer;
}
[data-density="touch"] .vv-palette-item { padding: 0 var(--space-3); }
.vv-palette-item :is(svg, .vv-icon) { width: var(--icon); height: var(--icon); color: var(--ink-muted); }
.vv-palette-item[aria-selected="true"] { background: var(--primary-soft); color: var(--primary); }
.vv-palette-item[aria-selected="true"] :is(svg, .vv-icon) { color: var(--primary); }
.vv-palette-item .vv-kbd { margin-left: auto; }
/* Item de dos líneas (2.8.0, finding esaem-46 PR #124): label + meta (segunda línea, p.ej. «DNI
   12345678A · alumno@vive.es» en un resultado de búsqueda de servidor) — mismo patrón MEN-01 de Menu. */
.vv-palette-item-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; gap: 1px; }
.vv-palette-item-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-palette-item-meta { font-size: 11px; line-height: 16px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-palette-foot { display: flex; gap: var(--space-4); padding: var(--space-2) var(--space-4); border-top: 1px solid var(--border-subtle); font-size: 11px; color: var(--ink-muted); }
/* CP-03 (3.0.0, "movil nativo" — owner 2026-09-28, sustituye a CP-01/2.9.0): compact (<600px, el
   ancho de `.vv-scrim` que por `inset:0` siempre coincide con el viewport real) → vista de búsqueda
   a pantalla completa con el campo fijo arriba, como Gmail/Drive (nunca la hoja inferior con asa que
   usaba CP-01 hasta 2.13.x — ese patrón queda reservado a `Sheet presentation="bottom"`). Referente:
   m3.material.io/components/search → Full-screen search. */
@container (max-width: 599.98px) {
  .vv-scrim { align-items: stretch; padding-top: 0; }
  .vv-palette {
    width: 100%; max-width: none; height: 100%; max-height: none; margin: 0;
    border-radius: 0; border: 0; box-shadow: none;
    padding-top: env(safe-area-inset-top, 0px);
    animation: vv-sheet-in var(--dur-spatial-default) var(--spring-spatial-default);
  }
}
@supports not (container-type: inline-size) {
  @media (max-width: 599.98px) {
    .vv-scrim { align-items: stretch; padding-top: 0; }
    .vv-palette {
      width: 100%; max-width: none; height: 100%; max-height: none; margin: 0;
      border-radius: 0; border: 0; box-shadow: none;
      padding-top: env(safe-area-inset-top, 0px);
      animation: vv-sheet-in var(--dur-spatial-default) var(--spring-spatial-default);
    }
  }
}
@media (prefers-reduced-motion: reduce) {
  .vv-palette { animation: none; }
}

/* Dock (mobile) */
.vv-dock {
  position: relative; display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--space-1);
  height: var(--dock-h); padding: var(--space-1); margin: 0 var(--space-4) calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
  border-radius: var(--radius-2xl); border: 1px solid var(--border-subtle); background: var(--surface); box-shadow: var(--shadow-lg);
}
/* --dock-h (tokens.css) es fijo en 64px (pensado como si el Dock viviera siempre en Touch); lo
   sobreescribimos aquí por densidad para que la barra (y las tabs, que se estiran a su alto por el
   grid) también se note más pequeña en Compact/Comfortable en vez de quedar siempre a tamaño Touch. */
[data-density="compact"] .vv-dock { height: 48px; }
[data-density="comfortable"] .vv-dock { height: 56px; }
[data-density="touch"] .vv-dock { height: var(--dock-h); }
/* D-02 (auditoría móvil 2026-09-23): 4px de separación entre tabs de navegación de uso frecuente
   es el de mayor riesgo real de mistap de todo el audit (barra inferior, dedo grande, uso repetido)
   — sube a 8px (M3-14) en touch. */
[data-density="touch"] .vv-dock { gap: var(--space-2); }
/* SH-01 (2.7.0, Gastos): PWA sin AppShell — el Dock anclado abajo. El consumidor reserva el scroll con
   `padding-bottom: var(--dock-clear)` en el contenedor de página (mismo valor que usa el FAB/Toaster). */
:root { --dock-clear: calc(var(--dock-h) + var(--space-6) + env(safe-area-inset-bottom, 0px)); }
.vv-dock-fixed { position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; margin-inline: var(--space-4); }
.vv-dock-clear { padding-bottom: var(--dock-clear); }
/* DK-03 (dock móvil, 2.12.0): con muchas entradas o etiquetas largas `grid-auto-columns: 1fr`
   (= minmax(auto, 1fr)) no encoge por debajo del min-content de cada tab, así que el Dock crecía más
   allá de su caja y las etiquetas se salían de la píldora. minmax(0, 1fr) deja encoger las columnas;
   el tab (min-width:0) y la etiqueta se recortan con elipsis. */
.vv-dock-tab {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  min-width: 0; min-height: var(--row-h); padding: 0; border: 0; border-radius: var(--radius-full); background: transparent;
  font: inherit; font-size: 11px; line-height: 14px; font-weight: 500; color: var(--ink-muted); cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out), transform var(--dur-spring) var(--ease-spring);
}
[data-density="touch"] .vv-dock-tab { font-size: 12px; }
.vv-dock-tab :is(svg, .vv-icon) { width: var(--icon); height: var(--icon); }
.vv-dock-tab > span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-dock-tab:active { transform: scale(0.94); }
.vv-dock-tab[aria-current="page"] { color: var(--primary); background: var(--primary-soft); }
.vv-dock-badge { position: absolute; top: 6px; right: calc(50% - 18px); min-width: 16px; height: 16px; padding: 0 4px; border-radius: var(--radius-full); background: var(--danger); color: var(--on-status); font-size: 10px; line-height: 16px; text-align: center; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ---- appearance ---- */
/* Appearance: user-selectable accent (data-accent), background (data-canvas), contrast (data-contrast) — theme and density come from tokens.css / :root. The boot script always sets data-theme explicitly (resolving "system" with matchMedia) so the dark accent block applies. data-accent and [data-brand] (brands.css) share the same specificity (one attribute); brands.css loads first, so a chosen accent here always wins over the brand's default color — see brands.css header and AppearanceSettings/README.md. */
[data-accent="indigo"] { --primary: #4f46e5; --primary-hover: #4338ca; --primary-soft: #eef2ff; --on-primary: #ffffff; --focus-ring: #4f46e5; }
[data-theme="dark"][data-accent="indigo"], [data-theme="dark"] [data-accent="indigo"] { --primary: #818cf8; --primary-hover: #a5b4fc; --primary-soft: #1e1b4b; --on-primary: #0b0b10; --focus-ring: #a5b4fc; }
[data-accent="purple"] { --primary: #7e22ce; --primary-hover: #6b21a8; --primary-soft: #faf5ff; --on-primary: #ffffff; --focus-ring: #7e22ce; }
[data-theme="dark"][data-accent="purple"], [data-theme="dark"] [data-accent="purple"] { --primary: #c084fc; --primary-hover: #d8b4fe; --primary-soft: #3b0764; --on-primary: #0b0b10; --focus-ring: #d8b4fe; }
[data-accent="pink"] { --primary: #be185d; --primary-hover: #9d174d; --primary-soft: #fdf2f8; --on-primary: #ffffff; --focus-ring: #be185d; }
[data-theme="dark"][data-accent="pink"], [data-theme="dark"] [data-accent="pink"] { --primary: #f472b6; --primary-hover: #f9a8d4; --primary-soft: #500724; --on-primary: #0b0b10; --focus-ring: #f9a8d4; }
[data-accent="emerald"] { --primary: #047857; --primary-hover: #065f46; --primary-soft: #ecfdf5; --on-primary: #ffffff; --focus-ring: #047857; }
[data-theme="dark"][data-accent="emerald"], [data-theme="dark"] [data-accent="emerald"] { --primary: #34d399; --primary-hover: #6ee7b7; --primary-soft: #022c22; --on-primary: #0b0b10; --focus-ring: #6ee7b7; }
[data-accent="amber"] { --primary: #92400e; --primary-hover: #78350f; --primary-soft: #fffbeb; --on-primary: #ffffff; --focus-ring: #92400e; }
[data-theme="dark"][data-accent="amber"], [data-theme="dark"] [data-accent="amber"] { --primary: #fbbf24; --primary-hover: #fcd34d; --primary-soft: #451a03; --on-primary: #0b0b10; --focus-ring: #fcd34d; }
[data-accent="rose"] { --primary: #be123c; --primary-hover: #9f1239; --primary-soft: #fff1f2; --on-primary: #ffffff; --focus-ring: #be123c; }
[data-theme="dark"][data-accent="rose"], [data-theme="dark"] [data-accent="rose"] { --primary: #fb7185; --primary-hover: #fda4af; --primary-soft: #4c0519; --on-primary: #0b0b10; --focus-ring: #fda4af; }
[data-accent="neutral"] { --primary: #334155; --primary-hover: #1e293b; --primary-soft: #f1f5f9; --on-primary: #ffffff; --focus-ring: #334155; }
[data-theme="dark"][data-accent="neutral"], [data-theme="dark"] [data-accent="neutral"] { --primary: #cbd5e1; --primary-hover: #e2e8f0; --primary-soft: #1e293b; --on-primary: #0b0b10; --focus-ring: #e2e8f0; }
[data-contrast="high"] { --ink-muted: #334155; --ink-faint: #64748b; --border-subtle: rgba(0, 0, 0, 0.18); --border: rgba(0, 0, 0, 0.32); --border-strong: #475569; }
[data-theme="dark"][data-contrast="high"], [data-theme="dark"] [data-contrast="high"] { --ink-muted: #cbd5e1; --ink-faint: #94a3b8; --border-subtle: rgba(255, 255, 255, 0.18); --border: rgba(255, 255, 255, 0.32); --border-strong: #cbd5e1; }
/* Fondo «Cálido» (2.9.0; TP-04): ajusta solo canvas/surface-2 a un tono crema sutil. Los valores
   toman el cálido de Anthropic — Velum White (#faf9f5) de fondo y Cream (#f0eee6) de superficie en
   claro; sus neutros cálidos de oscuro, página (#1f1e1d) y superficie elevada (#262624) — en lugar
   del crema naranja anterior (#fdf8f0 / #f6eddf) y del carbón previo, que derivaban de esaem/emaus
   (ver brands.css). Neutro = por defecto (sin atributo, sin cambio). */
[data-canvas="warm"] { --canvas: #faf9f5; --surface-2: #f0eee6; }
[data-theme="dark"][data-canvas="warm"], [data-theme="dark"] [data-canvas="warm"] { --canvas: #1f1e1d; --surface-2: #262624; }

/* ---- linear ---- */
/* Vive UI — Linear block: StatusIcon, PropertyChip, GroupedList. Loads after tokens.css + bundle.css. */

/* StatusIcon */
.vv-status-icon { display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; flex: none; }
.vv-status-icon :is(svg, .vv-icon) { display: block; width: 100%; height: 100%; }
[data-density="touch"] .vv-status-icon { width: 16px; height: 16px; }
.vv-status-icon-btn {
  all: unset; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
  width: max(24px, var(--row-h)); height: max(24px, var(--row-h)); flex: none; cursor: pointer;
  border-radius: var(--radius-md); color: inherit;
  transition: background-color var(--dur-fast) var(--ease-out), border-radius var(--dur-spring) var(--ease-spring);
}
.vv-status-icon-btn :is(svg, .vv-icon) { width: 14px; height: 14px; }
[data-density="touch"] .vv-status-icon-btn :is(svg, .vv-icon) { width: 16px; height: 16px; }
.vv-status-icon-btn:hover { background: var(--surface-3); border-radius: var(--radius-xl); }
.vv-status-icon-btn:active { transform: scale(0.94); }
.vv-status-icon-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }

/* PropertyChip */
.vv-chip {
  position: relative; display: inline-flex; align-items: center; gap: var(--space-1);
  max-width: 160px; height: 24px; padding: 0 var(--space-2); min-width: 0; /* layout estable: ancho fijo, el resto se corta con elipsis */
  border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); background: var(--surface);
  color: var(--ink-muted); font-size: 12px; font-weight: 500; line-height: 1; white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
[data-density="touch"] .vv-chip { height: 28px; }
.vv-chip:hover { background: var(--surface-3); }
.vv-chip[role="button"] { cursor: pointer; }
.vv-chip[role="button"]:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-chip-empty { color: var(--ink-faint); border-style: dashed; }
.vv-chip :is(svg, .vv-icon) { width: 14px; height: 14px; flex: none; color: currentColor; }
.vv-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-chip-avatar {
  width: 16px; height: 16px; border-radius: var(--radius-full); background: var(--primary-soft); color: var(--primary);
  display: flex; align-items: center; justify-content: center; font-size: 9px; font-weight: 600; flex: none;
}
.vv-chip-chevron { width: 12px !important; height: 12px !important; flex: none; color: var(--ink-faint); transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast); }
.vv-chip[aria-expanded="true"] .vv-chip-chevron { transform: rotate(180deg); }

/* PropertyChip popover */
.vv-chip-pop {
  position: absolute; top: calc(100% + var(--space-1)); left: 0; width: 220px; z-index: 20;
  display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-md); overflow: hidden;
  animation: vv-pop var(--dur-base) var(--ease-spring);
  color: var(--ink); font-weight: 400; white-space: normal;
}
.vv-chip-pop-input {
  box-sizing: border-box; width: 100%; height: 32px; padding: 0 var(--space-3); font: inherit; font-size: 13px;
  border: 0; border-bottom: 1px solid var(--border-subtle); background: transparent; color: var(--ink); outline: none;
}
.vv-chip-pop-input::placeholder { color: var(--ink-faint); }
.vv-chip-pop-list { list-style: none; margin: 0; padding: var(--space-1); max-height: 220px; overflow: auto; }
.vv-chip-pop-item {
  display: flex; align-items: center; gap: var(--space-2); min-height: 32px; padding: 0 var(--space-2);
  border-radius: var(--radius-sm); color: var(--ink); font-size: 13px; font-weight: 400; cursor: pointer;
}
.vv-chip-pop-item :is(svg, .vv-icon) { width: 14px; height: 14px; color: var(--ink-muted); }
.vv-chip-pop-item[aria-selected="true"] { background: var(--surface-3); }
.vv-chip-pop-check { margin-left: auto; width: 14px !important; height: 14px !important; color: var(--primary); }
.vv-chip-pop-empty { padding: var(--space-3) var(--space-2); color: var(--ink-muted); font-size: 12px; }

/* GroupedList */
/* Contención: sin un ancestro con container-type propio, la regla @container de más abajo (que
   oculta los chips extra bajo 600px) no se aplica en ningún navegador — ni siquiera el fallback
   @supports, que solo comprueba soporte de la propiedad, no si hay un contenedor real más arriba.
   .vv-grouped-wrap se declara a sí mismo como ese contenedor para que la regla siempre tenga uno,
   lo use quien lo use (ListPage, RecordPage o un GroupedList suelto). */
.vv-grouped-wrap { display: flex; flex-direction: column; min-height: 0; min-width: 0; container-type: inline-size; }
.vv-grouped {
  display: flex; flex-direction: column; overflow: auto; background: var(--surface);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
}
.vv-grouped:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
.vv-group:first-child .vv-group-header { border-top: 0; }
.vv-group-header {
  position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: var(--space-2);
  min-height: var(--row-compact); padding: 0 var(--space-3);
  background: var(--surface-2); border-top: 1px solid var(--border-subtle); border-bottom: 1px solid var(--border-subtle);
  cursor: pointer;
}
/* row-compact (32px) is below the touch minimum (48px) — the header is a full-width tap target
   (collapse/expand the group), so it's the row height that needs to grow, not a hit-area hack. */
[data-density="touch"] .vv-group-header { min-height: var(--row-h); }
.vv-group-header:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
.vv-group-header-chevron { width: 14px !important; height: 14px !important; color: var(--ink-muted); transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast); }
.vv-group-header[aria-expanded="false"] .vv-group-header-chevron { transform: rotate(-90deg); }
.vv-group-header-title { font-size: 12px; font-weight: 500; color: var(--ink); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-group-header-count { font-size: 12px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.vv-group-header-add {
  position: relative; margin-left: auto; width: 24px; height: 24px; min-width: 24px; padding: 0;
  opacity: 0; transition: opacity var(--dur-fast) var(--ease-out);
}
.vv-group-header-add :is(svg, .vv-icon) { width: 13px; height: 13px; }
.vv-group-header:hover .vv-group-header-add, .vv-group-header-add:focus-visible { opacity: 1; }
/* :hover never fires on touch — an opacity:0-until-hover action was permanently invisible and
   untappable on a phone/tablet with a coarse pointer. Same expanded hit-area pattern as .vv-fchip
   (FC-01, core.css ~1147): visual size stays 24px, the tap target grows to the touch minimum. */
@media (any-pointer: coarse) {
  .vv-group-header-add { opacity: 1; --vv-hit-x: -12px; --vv-hit-y: -12px; }
}

.vv-grouped-row {
  position: relative; display: flex; align-items: center; gap: var(--space-2);
  min-height: var(--row-h); padding: 0 var(--space-4); font-size: var(--text-base);
  border-bottom: 1px solid var(--border-subtle); cursor: default;
  transition: background-color var(--dur-fast) var(--ease-out);
}
[data-density="compact"] .vv-grouped-row { padding: 0 var(--space-3); }
[data-density="touch"] .vv-grouped-row { padding: 0 var(--space-5); }
.vv-grouped-row:hover { background: var(--surface-3); }
.vv-grouped-row[aria-selected="true"] { background: var(--primary-soft); }
.vv-grouped-row[aria-selected="true"]::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px; background: var(--primary); }
.vv-grouped-id { flex: none; }
.vv-grouped-row > code { flex: none; white-space: nowrap; }
.vv-grouped-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-grouped-chips { display: flex; align-items: center; gap: var(--space-1); flex: none; min-width: 0; }
.vv-grouped-date { flex: none; min-width: 52px; text-align: right; font-size: 11px; color: var(--ink-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Below 600px — M3 Expressive window size class "compact" (nearest ancestor with container-type,
   e.g. a page shell's own wrapper — unnamed so it works in any context, not just ListPage) — there
   isn't room for every PropertyChip: keep only the first one (assignee, by GroupedList's own
   defaultRenderItem convention) and the date, and let the title do the truncating it already does.
   Same rule under a plain @media as a fallback for engines without container-query support. */
@container (max-width: 599.98px) {
  .vv-grouped-chips > * ~ * { display: none; }
}
@supports not (container-type: inline-size) {
  @media (max-width: 599.98px) {
    .vv-grouped-chips > * ~ * { display: none; }
  }
}

.vv-grouped-shortcuts {
  position: sticky; bottom: 0; display: flex; flex-wrap: wrap; gap: var(--space-4);
  padding: var(--space-2) var(--space-4); border-top: 1px solid var(--border-subtle);
  background: var(--surface-2); font-size: 11px; color: var(--ink-muted);
}

/* ---- m3e ---- */
/* Vive UI — frag/m3e.css. Bloque "M3 Expressive": Shape, LoadingIndicator, ButtonGroup, Toolbar. Loads after tokens.css + bundle.css. */

:root {
  /* Biblioteca de formas M3 Expressive. Coordenadas 0-100, 36 curvas cúbicas cada una (mismo
     número y tipo de comando en todas) para que clip-path: path() interpole limpio entre ellas. */
  --shape-circle: path("M90,50 C90,52.32 89.79,54.67 89.39,56.95 C88.99,59.23 88.38,61.51 87.59,63.68 C86.8,65.86 85.8,67.99 84.64,70 C83.48,72.01 82.13,73.94 80.64,75.71 C79.15,77.49 77.49,79.15 75.71,80.64 C73.94,82.13 72.01,83.48 70,84.64 C67.99,85.8 65.86,86.8 63.68,87.59 C61.51,88.38 59.23,88.99 56.95,89.39 C54.67,89.79 52.32,90 50,90 C47.68,90 45.33,89.79 43.05,89.39 C40.77,88.99 38.49,88.38 36.32,87.59 C34.14,86.8 32.01,85.8 30,84.64 C27.99,83.48 26.06,82.13 24.29,80.64 C22.51,79.15 20.85,77.49 19.36,75.71 C17.87,73.94 16.52,72.01 15.36,70 C14.2,67.99 13.2,65.86 12.41,63.68 C11.62,61.51 11.01,59.23 10.61,56.95 C10.21,54.67 10,52.32 10,50 C10,47.68 10.21,45.33 10.61,43.05 C11.01,40.77 11.62,38.49 12.41,36.32 C13.2,34.14 14.2,32.01 15.36,30 C16.52,27.99 17.87,26.06 19.36,24.29 C20.85,22.51 22.51,20.85 24.29,19.36 C26.06,17.87 27.99,16.52 30,15.36 C32.01,14.2 34.14,13.2 36.32,12.41 C38.49,11.62 40.77,11.01 43.05,10.61 C45.33,10.21 47.68,10 50,10 C52.32,10 54.67,10.21 56.95,10.61 C59.23,11.01 61.51,11.62 63.68,12.41 C65.86,13.2 67.99,14.2 70,15.36 C72.01,16.52 73.94,17.87 75.71,19.36 C77.49,20.85 79.15,22.51 80.64,24.29 C82.13,26.06 83.48,27.99 84.64,30 C85.8,32.01 86.8,34.14 87.59,36.32 C88.38,38.49 88.99,40.77 89.39,43.05 C89.79,45.33 90,47.68 90,50 Z");
  --shape-pill: path("M94,50 C94,52.59 94.14,55.14 94,57.76 C93.86,60.38 94.81,63.72 93.15,65.71 C91.49,67.69 87.27,68.95 84.05,69.66 C80.83,70.37 76.69,69.92 73.81,69.98 C70.93,70.04 68.83,70 66.78,70 C64.74,70 63.13,70 61.55,70 C59.96,70 58.62,70 57.28,70 C55.94,70 54.74,70 53.53,70 C52.31,70 51.18,70 50,70 C48.82,70 47.69,70 46.47,70 C45.26,70 44.06,70 42.72,70 C41.38,70 40.04,70 38.45,70 C36.87,70 35.26,70 33.22,70 C31.17,70 29.07,70.04 26.19,69.98 C23.31,69.92 19.17,70.37 15.95,69.66 C12.73,68.95 8.51,67.69 6.85,65.71 C5.19,63.72 6.14,60.38 6,57.76 C5.86,55.14 6,52.59 6,50 C6,47.41 5.86,44.86 6,42.24 C6.14,39.62 5.19,36.28 6.85,34.29 C8.51,32.31 12.73,31.05 15.95,30.34 C19.17,29.63 23.31,30.08 26.19,30.02 C29.07,29.96 31.17,30 33.22,30 C35.26,30 36.87,30 38.45,30 C40.04,30 41.38,30 42.72,30 C44.06,30 45.26,30 46.47,30 C47.69,30 48.82,30 50,30 C51.18,30 52.31,30 53.53,30 C54.74,30 55.94,30 57.28,30 C58.62,30 59.96,30 61.55,30 C63.13,30 64.74,30 66.78,30 C68.83,30 70.93,29.96 73.81,30.02 C76.69,30.08 80.83,29.63 84.05,30.34 C87.27,31.05 91.49,32.31 93.15,34.29 C94.81,36.28 93.86,39.62 94,42.24 C94.14,44.86 94,47.41 94,50 Z");
  --shape-cookie-4: path("M92,50 C92,52.32 90.98,54.95 89.52,56.97 C88.06,58.99 85.51,60.77 83.25,62.1 C81,63.44 78.14,64.18 75.98,65 C73.82,65.82 71.78,66.14 70.29,67.02 C68.79,67.9 67.9,68.79 67.02,70.29 C66.14,71.78 65.82,73.82 65,75.98 C64.18,78.14 63.44,81 62.1,83.25 C60.77,85.51 58.99,88.06 56.97,89.52 C54.95,90.98 52.32,92 50,92 C47.68,92 45.05,90.98 43.03,89.52 C41.01,88.06 39.23,85.51 37.9,83.25 C36.56,81 35.82,78.14 35,75.98 C34.18,73.82 33.86,71.78 32.98,70.29 C32.1,68.79 31.21,67.9 29.71,67.02 C28.22,66.14 26.18,65.82 24.02,65 C21.86,64.18 19,63.44 16.75,62.1 C14.49,60.77 11.94,58.99 10.48,56.97 C9.02,54.95 8,52.32 8,50 C8,47.68 9.02,45.05 10.48,43.03 C11.94,41.01 14.49,39.23 16.75,37.9 C19,36.56 21.86,35.82 24.02,35 C26.18,34.18 28.22,33.86 29.71,32.98 C31.21,32.1 32.1,31.21 32.98,29.71 C33.86,28.22 34.18,26.18 35,24.02 C35.82,21.86 36.56,19 37.9,16.75 C39.23,14.49 41.01,11.94 43.03,10.48 C45.05,9.02 47.68,8 50,8 C52.32,8 54.95,9.02 56.97,10.48 C58.99,11.94 60.77,14.49 62.1,16.75 C63.44,19 64.18,21.86 65,24.02 C65.82,26.18 66.14,28.22 67.02,29.71 C67.9,31.21 68.79,32.1 70.29,32.98 C71.78,33.86 73.82,34.18 75.98,35 C78.14,35.82 81,36.56 83.25,37.9 C85.51,39.23 88.06,41.01 89.52,43.03 C90.98,45.05 92,47.68 92,50 Z");
  --shape-cookie-8: path("M92,50 C92,52.24 89.74,54.77 88.11,56.72 C86.47,58.67 83.33,59.83 82.18,61.71 C81.02,63.59 81.3,65.55 81.18,68 C81.06,70.45 82.25,74.15 81.46,76.4 C80.66,78.64 78.64,80.66 76.4,81.46 C74.15,82.25 70.45,81.06 68,81.18 C65.55,81.3 63.59,81.02 61.71,82.18 C59.83,83.33 58.67,86.47 56.72,88.11 C54.77,89.74 52.24,92 50,92 C47.76,92 45.23,89.74 43.28,88.11 C41.33,86.47 40.17,83.33 38.29,82.18 C36.41,81.02 34.45,81.3 32,81.18 C29.55,81.06 25.85,82.25 23.6,81.46 C21.36,80.66 19.34,78.64 18.54,76.4 C17.75,74.15 18.94,70.45 18.82,68 C18.7,65.55 18.98,63.59 17.82,61.71 C16.67,59.83 13.53,58.67 11.89,56.72 C10.26,54.77 8,52.24 8,50 C8,47.76 10.26,45.23 11.89,43.28 C13.53,41.33 16.67,40.17 17.82,38.29 C18.98,36.41 18.7,34.45 18.82,32 C18.94,29.55 17.75,25.85 18.54,23.6 C19.34,21.36 21.36,19.34 23.6,18.54 C25.85,17.75 29.55,18.94 32,18.82 C34.45,18.7 36.41,18.98 38.29,17.82 C40.17,16.67 41.33,13.53 43.28,11.89 C45.23,10.26 47.76,8 50,8 C52.24,8 54.77,10.26 56.72,11.89 C58.67,13.53 59.83,16.67 61.71,17.82 C63.59,18.98 65.55,18.7 68,18.82 C70.45,18.94 74.15,17.75 76.4,18.54 C78.64,19.34 80.66,21.36 81.46,23.6 C82.25,25.85 81.06,29.55 81.18,32 C81.3,34.45 81.02,36.41 82.18,38.29 C83.33,40.17 86.47,41.33 88.11,43.28 C89.74,45.23 92,47.76 92,50 Z");
  --shape-clover: path("M92,50 C92,52.34 91.19,54.97 89.82,57.02 C88.44,59.07 86.2,61.04 83.75,62.28 C81.3,63.53 78.12,64.35 75.11,64.5 C72.11,64.65 67.7,62.98 65.72,63.19 C63.73,63.39 63.39,63.73 63.19,65.72 C62.98,67.7 64.65,72.11 64.5,75.11 C64.35,78.12 63.53,81.3 62.28,83.75 C61.04,86.2 59.07,88.44 57.02,89.82 C54.97,91.19 52.34,92 50,92 C47.66,92 45.03,91.19 42.98,89.82 C40.93,88.44 38.96,86.2 37.72,83.75 C36.47,81.3 35.65,78.12 35.5,75.11 C35.35,72.11 37.02,67.7 36.81,65.72 C36.61,63.73 36.27,63.39 34.28,63.19 C32.3,62.98 27.89,64.65 24.89,64.5 C21.88,64.35 18.7,63.53 16.25,62.28 C13.8,61.04 11.56,59.07 10.18,57.02 C8.81,54.97 8,52.34 8,50 C8,47.66 8.81,45.03 10.18,42.98 C11.56,40.93 13.8,38.96 16.25,37.72 C18.7,36.47 21.88,35.65 24.89,35.5 C27.89,35.35 32.3,37.02 34.28,36.81 C36.27,36.61 36.61,36.27 36.81,34.28 C37.02,32.3 35.35,27.89 35.5,24.89 C35.65,21.88 36.47,18.7 37.72,16.25 C38.96,13.8 40.93,11.56 42.98,10.18 C45.03,8.81 47.66,8 50,8 C52.34,8 54.97,8.81 57.02,10.18 C59.07,11.56 61.04,13.8 62.28,16.25 C63.53,18.7 64.35,21.88 64.5,24.89 C64.65,27.89 62.98,32.3 63.19,34.28 C63.39,36.27 63.73,36.61 65.72,36.81 C67.7,37.02 72.11,35.35 75.11,35.5 C78.12,35.65 81.3,36.47 83.75,37.72 C86.2,38.96 88.44,40.93 89.82,42.98 C91.19,45.03 92,47.66 92,50 Z");
  --shape-scallop-12: path("M92,50 C92,52.21 88.68,54.46 87.67,56.64 C86.66,58.82 86.16,60.69 85.94,63.08 C85.73,65.48 87.48,69.08 86.37,71 C85.27,72.92 81.27,73.2 79.3,74.59 C77.34,75.97 75.97,77.34 74.59,79.3 C73.2,81.27 72.92,85.27 71,86.37 C69.08,87.48 65.48,85.73 63.08,85.94 C60.69,86.16 58.82,86.66 56.64,87.67 C54.46,88.68 52.21,92 50,92 C47.79,92 45.54,88.68 43.36,87.67 C41.18,86.66 39.31,86.16 36.92,85.94 C34.52,85.73 30.92,87.48 29,86.37 C27.08,85.27 26.8,81.27 25.41,79.3 C24.03,77.34 22.66,75.97 20.7,74.59 C18.73,73.2 14.73,72.92 13.63,71 C12.52,69.08 14.27,65.48 14.06,63.08 C13.84,60.69 13.34,58.82 12.33,56.64 C11.32,54.46 8,52.21 8,50 C8,47.79 11.32,45.54 12.33,43.36 C13.34,41.18 13.84,39.31 14.06,36.92 C14.27,34.52 12.52,30.92 13.63,29 C14.73,27.08 18.73,26.8 20.7,25.41 C22.66,24.03 24.03,22.66 25.41,20.7 C26.8,18.73 27.08,14.73 29,13.63 C30.92,12.52 34.52,14.27 36.92,14.06 C39.31,13.84 41.18,13.34 43.36,12.33 C45.54,11.32 47.79,8 50,8 C52.21,8 54.46,11.32 56.64,12.33 C58.82,13.34 60.69,13.84 63.08,14.06 C65.48,14.27 69.08,12.52 71,13.63 C72.92,14.73 73.2,18.73 74.59,20.7 C75.97,22.66 77.34,24.03 79.3,25.41 C81.27,26.8 85.27,27.08 86.37,29 C87.48,30.92 85.73,34.52 85.94,36.92 C86.16,39.31 86.66,41.18 87.67,43.36 C88.68,45.54 92,47.79 92,50 Z");
  --shape-arch: path("M90,50 C90.13,52.64 90,55.29 90,57.94 C90,60.58 90,63.23 90,65.87 C90,68.52 90,71.16 90,73.81 C90,76.45 90,79.1 90,81.74 C90,84.39 91.27,88.3 90,89.68 C88.73,91.05 84.98,89.95 82.39,90 C79.8,90.05 77.1,90 74.45,90 C71.81,90 69.16,90 66.52,90 C63.87,90 61.23,90 58.58,90 C55.94,90 53.29,90 50.65,90 C48,90 45.36,90 42.71,90 C40.07,90 37.42,90 34.78,90 C32.13,90 29.49,90 26.84,90 C24.2,90 21.55,90 18.91,90 C16.26,90 12.46,91.16 10.97,90 C9.49,88.84 10.16,85.52 10,83.04 C9.84,80.56 10,77.75 10,75.1 C10,72.46 10,69.81 10,67.17 C10,64.52 10,61.88 10,59.23 C10,56.59 9.91,53.94 10,51.3 C10.09,48.66 10.11,45.98 10.55,43.39 C10.99,40.8 11.69,38.2 12.63,35.75 C13.56,33.29 14.77,30.88 16.17,28.66 C17.57,26.44 19.22,24.32 21.04,22.41 C22.85,20.51 24.89,18.75 27.04,17.25 C29.19,15.74 31.54,14.42 33.95,13.36 C36.35,12.31 38.91,11.48 41.48,10.92 C44.05,10.36 46.72,10.05 49.35,10.01 C51.98,9.96 54.66,10.19 57.25,10.66 C59.83,11.14 62.42,11.89 64.86,12.86 C67.3,13.84 69.68,15.08 71.88,16.52 C74.08,17.96 76.18,19.64 78.05,21.49 C79.93,23.33 81.65,25.4 83.12,27.58 C84.6,29.75 85.88,32.12 86.89,34.54 C87.91,36.97 88.7,39.54 89.22,42.12 C89.73,44.69 89.87,47.36 90,50 Z");
}

/* ===== Shape ===== */
/* Las formas se diseñan en un lienzo fijo de 100x100 y se escalan a --size con transform,
   así el mismo path() sirve para un avatar de 24px o un FAB de 96px. */
.vv-shape {
  position: relative;
  display: inline-block;
  flex: none;
  overflow: hidden;
}
.vv-shape-inner {
  position: absolute;
  top: 0; left: 0;
  width: 100px; height: 100px;
  transform-origin: 0 0;
  background: var(--brand-indigo);
  display: flex; align-items: center; justify-content: center;
}
.vv-shape-inner > * { transform: scale(calc(100 / var(--vv-shape-size, 100))); }

/* ===== LoadingIndicator ===== */
.vv-loading {
  --vv-li-size: 40px;
  position: relative;
  display: inline-flex;
  width: var(--vv-li-size); height: var(--vv-li-size);
  flex: none;
}
.vv-loading-sm { --vv-li-size: 24px; }
/* Dentro de un botón mide como el icono; el scaler de 100px queda recortado a la caja (2.5.2). */
.vv-loading { overflow: hidden; }
.vv-btn .vv-loading { --vv-li-size: var(--icon); }
.vv-loading-scaler {
  position: absolute; top: 0; left: 0; width: 100px; height: 100px;
  transform-origin: 0 0;
  transform: scale(calc(var(--vv-li-size) / 100px));
}
.vv-loading-shape {
  position: absolute; top: 0; left: 0; width: 100px; height: 100px;
  transform-origin: 50px 50px;
  background: var(--primary);
  clip-path: var(--shape-cookie-4);
  animation: vv-m3e-morph 2.4s infinite, vv-m3e-spin 8s linear infinite;
}
@keyframes vv-m3e-morph {
  0%   { clip-path: var(--shape-cookie-4);   animation-timing-function: var(--spring-spatial-slow); }
  25%  { clip-path: var(--shape-clover);     animation-timing-function: var(--spring-spatial-slow); }
  50%  { clip-path: var(--shape-scallop-12); animation-timing-function: var(--spring-spatial-slow); }
  75%  { clip-path: var(--shape-cookie-8);   animation-timing-function: var(--spring-spatial-slow); }
  100% { clip-path: var(--shape-circle);     animation-timing-function: var(--spring-spatial-slow); }
}
@keyframes vv-m3e-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
@keyframes vv-m3e-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .45; }
}
@media (prefers-reduced-motion: reduce) {
  .vv-loading-shape {
    clip-path: var(--shape-cookie-8);
    animation: vv-m3e-pulse 1.6s ease-in-out infinite;
    transform: none;
  }
}

/* ===== ButtonGroup ===== */
.vv-btngroup {
  display: inline-flex;
  gap: var(--space-0-5, 2px);
  padding: var(--space-0-5, 2px);
  height: calc(var(--row-h) + 4px);
  background: var(--surface-2);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-full);
  /* Contención (regla global, guideline 20-dual-shell.md §Grupos de controles: nunca dos líneas):
     un segmented control no se envuelve (rompería la píldora) ni se encoge (los segmentos deben
     seguir siendo pulsables) — `flex-wrap: nowrap` explícito aquí porque este es EL primitivo de
     referencia del contrato, no solo un default heredado de `inline-flex`. En touch, dentro de un
     panel estrecho (<360px), a veces no cabe entero; se desplaza horizontalmente en vez de
     desbordar el panel — último recurso de la cascada (i. icono-only si hay icono inequívoco,
     ii. Select/menú «⋯», iii. scroll), misma excepción documentada que Toolbar/FilterBar/Tabs.
     El scroll solo funciona si cada segmento fija su propio ancho — ver `flex: none` en `.vv-bg-btn`
     (BG-02): sin eso el shrink de flexbox gana siempre al overflow. */
  flex-wrap: nowrap; max-width: 100%; overflow-x: auto; scrollbar-width: none;
}
.vv-btngroup::-webkit-scrollbar { display: none; }
.vv-bg-btn {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  height: var(--row-h); padding: 0 var(--space-4); flex: none;
  font: inherit; font-size: var(--text-base); font-weight: 500; line-height: 1;
  background: transparent; color: var(--ink-muted); border: 0; cursor: pointer;
  border-radius: var(--radius-sm);
  transform: scaleX(1);
  transform-origin: center;
  transition: border-radius var(--dur-spatial-default) var(--spring-spatial-default),
    background-color var(--dur-effects-fast) var(--ease-out), color var(--dur-effects-fast) var(--ease-out),
    transform var(--dur-spatial-fast) var(--spring-spatial-fast);
}
/* BG-02 (2.9.0, auditoria adaptativa, P1 — corrige BG-01 de 2026-09-23): BG-01 dejaba que el
   flex-item se encogiera (`min-width:0`) para que `.vv-bg-btn-label` recortara con «…» en vez de un
   corte duro contra el borde de la pildora — pero eso rompia la promesa del propio comentario de
   `.vv-btngroup` ("se desplaza horizontalmente en vez de desbordar"): en flexbox el shrink SIEMPRE
   gana al overflow cuando el hijo no fija su propio ancho, asi que `.vv-btngroup{overflow-x:auto}`
   nunca llegaba a activarse — confirmado en AppearanceSettings -> "Tema" leyendose "C.../O.../Sis..."
   a 360px, ilegible. Arreglo minimo de CONTENCION: `flex:none` en vez de `min-width:0` — cada
   segmento mantiene su ancho natural y el padre SI scrollea de verdad, igual que Tabs/FilterBar
   ya hacen. Sigue siendo el ultimo recurso de la cascada (guideline 20-dual-shell.md
   §Grupos de controles: nunca dos lineas) — un grupo de seleccion unica sin iconos que ya no cabe ni
   con scroll deberia migrar a `V.Select`, no quedarse aqui. */
.vv-bg-btn-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-bg-btn:first-child { border-top-left-radius: var(--radius-full); border-bottom-left-radius: var(--radius-full); }
.vv-bg-btn:last-child { border-top-right-radius: var(--radius-full); border-bottom-right-radius: var(--radius-full); }
.vv-bg-btn :is(svg, .vv-icon) { width: var(--icon); height: var(--icon); flex: none; }
.vv-bg-btn:hover { background: var(--surface-3); color: var(--ink); }
/* Segmento seleccionado (BG-03, 2026-09-28, owner sobre captura real de «Ajustes de apariencia»:
   "el seleccionado es solo una pildora blanca sobre gris; quiero color de fondo y destacado en el
   elemento activo"). Revierte la superficie neutra de 2.8.0 (nota historica abajo) por el mismo
   relleno tintado que ya usa el resto del sistema para "seleccionado/activo" — NavRail/Sidebar nav
   activo, FilterChip seleccionado, CommandPalette item activo, Toolbar sizer pulsado: todos
   `background: var(--primary-soft); color: var(--primary)`. El icono hereda el color por
   `currentColor`, asi que no hace falta tocar `.vv-bg-btn :is(svg, .vv-icon)`. Funciona en claro/
   oscuro y con cualquier marca porque `--primary`/`--primary-soft` ya resuelven brand/acento.
   Historico 2.8.0 ("como el dark mode de claude.ai"): superficie elevada neutra + sombra sutil, sin
   relleno saturado — coherente entonces con `.vv-tabs-pill`, que sigue asi (Tabs no esta en el
   alcance de este pedido; si se unifica, que sea su propio cambio deliberado). */
.vv-bg-btn[aria-checked="true"], .vv-bg-btn[aria-pressed="true"] {
  background: var(--primary-soft); color: var(--primary); border-radius: var(--radius-full);
}
.vv-bg-btn[aria-checked="true"]:hover, .vv-bg-btn[aria-pressed="true"]:hover { background: var(--primary-soft); }
.vv-bg-btn-pressed { transform: scaleX(1.06); z-index: 1; }
.vv-bg-btn-neighbor { transform: scaleX(0.94); }
/* BG-03: outline-offset negativo (dentro del boton) en vez de positivo — `.vv-btngroup` es
   `overflow-x: auto` (scroll horizontal de la pildora en paneles estrechos) y, por la propia spec de
   CSS Overflow, un eje no-"visible" fuerza el otro eje a comportarse como "auto" tambien: un anillo
   con offset positivo que sobresale de la caja del segmento se recorta en cualquier borde del grupo
   (owner, captura real: el segmento «Completo» perdia el anillo por el lado derecho). Con offset
   negativo el anillo se pinta dentro de la propia caja del boton, nunca fuera del area con scroll,
   asi que nunca hay nada que recortar — sin tocar layout/padding del grupo. */
.vv-bg-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; z-index: 2; }

/* Escalón 1 de la cascada de control-groups (guidelines/20-dual-shell.md §Grupos de controles):
   icono-only opt-in, para cuando cada opción ya trae un `icon` inequívoco y aun así el grupo no
   cabe entero (3.1.0, feedback esaem — AppearanceSettings "Tema" en el flyout de 300px de
   AccountMenu). Sin `.vv-bg-btn-label` en el DOM (`ButtonGroup` no lo renderiza con
   `collapseLabel`), el botón se estrecha a su contenido real (icono + padding) en vez de forzar el
   ancho de la etiqueta más larga ("Sistema"). El nombre accesible pasa a `aria-label`/`title` por
   botón — ver `bundle.js`. */
.vv-btngroup-collapse .vv-bg-btn { padding-inline: var(--space-3); }

/* ===== Toolbar ===== */
.vv-toolbar {
  display: inline-flex; align-items: center; gap: var(--space-1);
  height: calc(var(--row-h) + 8px);
  padding: var(--space-1);
  background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-full);
  box-shadow: var(--shadow-lg);
  opacity: 0; transform: translateY(12px) scale(.96);
  animation: vv-toolbar-in var(--dur-spatial-default) var(--spring-spatial-default) forwards;
}
.vv-toolbar-vertical {
  flex-direction: column;
  height: auto;
  /* Defecto real (auditoría de contención 2026-09-23, no solo del script): `calc(row-h + 8px)`
     solo sumaba el padding (`--space-1` × 2 = 8px) y se olvidaba del propio borde de 1px × 2 lados
     de `.vv-toolbar` — con box-sizing:border-box eso dejaba 46px de ancho de contenido para un
     botón `.vv-btn-icon` de `--row-h` (48px), que `.vv-btn{max-width:100%}` recortaba a 46px real
     (medido, no solo lo que reporta el script). +10px (8 padding + 2 borde) da el contenido exacto. */
  width: calc(var(--row-h) + 10px);
}
@keyframes vv-toolbar-in {
  to { opacity: 1; transform: none; }
}
.vv-toolbar-sep {
  flex: none;
  background: var(--border-subtle);
}
.vv-toolbar:not(.vv-toolbar-vertical) .vv-toolbar-sep { width: 1px; align-self: stretch; margin: 0 var(--space-1); }
.vv-toolbar-vertical .vv-toolbar-sep { height: 1px; width: 100%; margin: var(--space-1) 0; }
.vv-toolbar .vv-btn-primary { border-radius: var(--radius-full); }
.vv-toolbar .vv-btn-primary:hover { border-radius: var(--radius-full); }
@media (prefers-reduced-motion: reduce) {
  .vv-toolbar { animation: none; opacity: 1; transform: none; }
}

/* ---- app ---- */
/* Vive UI — frag/app.css. AppearanceSettings, Sheet, and shared type-scale utilities. Loads after tokens.css + bundle.css. */

/* Type-scale utilities referenced by project/README.md ("Tipografía") that bundle.css does not yet
   expose as classes: page titles, section/card titles, row/inspector headings and captions. Labels
   reuse .vv-label and monospace text reuses .vv-mono, both already defined in bundle.css. */
.vv-text-display-md { margin: 0; font-size: 28px; line-height: 34px; font-weight: 600; letter-spacing: -0.015em; color: var(--ink); }
.vv-text-title { margin: 0; font-size: 20px; line-height: 28px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink); }
.vv-text-heading { margin: 0; font-size: 15px; line-height: 22px; font-weight: 600; color: var(--ink); }
.vv-text-body { font-size: var(--text-base); line-height: 1.5; color: var(--ink); }
.vv-text-caption { font-size: 11px; line-height: 16px; color: var(--ink-muted); }

/* ===== AppearanceSettings ===== */
.vv-appearance { display: flex; flex-direction: column; gap: var(--space-6); max-width: 100%; min-width: 0; }
.vv-appearance .vv-btngroup { display: flex; width: 100%; }
.vv-appearance .vv-bg-btn { flex: 1 1 0; }
.vv-appearance-group { display: flex; flex-direction: column; gap: var(--space-2); }
/* compact (2.13.1, aditivo): versión del panel para un segundo popover (AccountMenu «Apariencia») — mismo
   markup, menos aire vertical, sin scroll en el flyout de 300px. */
.vv-appearance-compact { gap: var(--space-3); }

.vv-accent-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* El área táctil de abajo crece 10px por lado (28px visual → 48px real) — con el gap por defecto de
   8px, dos swatches contiguos tendrían objetivos táctiles que se solapan entre sí (10+10=20 > 8 de
   gap). En touch se amplía el gap a 20px (--space-5 = 10+10) para que los objetivos táctiles se
   toquen exactamente sin invadirse, mismo criterio que MonthGrid con su propio gap. */
[data-density="touch"] .vv-accent-row { gap: var(--space-5); }
.vv-accent-swatch {
  all: unset; position: relative; box-sizing: border-box; width: 28px; height: 28px; flex: none; border-radius: var(--radius-full);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.vv-accent-swatch:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
/* Objetivo táctil (auditoría de contención 2026-09-23): 28px visual, M3 lo permite para un swatch de
   color (igual criterio que el chip de 32dp), pero el ÁREA TÁCTIL en touch debe llegar a 48 — misma
   utilidad que el resto del kit (`--vv-hit-x/-y`, ver "Expanded touch hit-area"). `.vv-accent-row`
   no recorta (sin `overflow:hidden`), así que la expansión funciona sin la limitación conocida de
   `.vv-btn-sm`/breadcrumb/`.vv-gal-check`. */
[data-density="touch"] .vv-accent-swatch { --vv-hit-x: -10px; --vv-hit-y: -10px; }
.vv-accent-fill {
  width: 100%; height: 100%; border-radius: var(--radius-full); background: var(--primary);
  display: flex; align-items: center; justify-content: center;
  transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast), box-shadow var(--dur-effects-fast) var(--ease-out);
}
.vv-accent-swatch:hover .vv-accent-fill { transform: scale(1.08); }
.vv-accent-swatch:active .vv-accent-fill { transform: scale(0.94); }
.vv-accent-swatch[aria-checked="true"] .vv-accent-fill { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--focus-ring); }
.vv-accent-check { width: 14px !important; height: 14px !important; color: var(--on-primary); }

/* Density group reuses .vv-btngroup/.vv-bg-btn from m3e.css but needs a real disabled state that
   V.ButtonGroup's option shape does not support, so it is markup-only here. */
.vv-bg-btn[disabled] { color: var(--ink-faint); cursor: not-allowed; opacity: 0.5; }
.vv-bg-btn[disabled]:hover { background: transparent; border-radius: var(--radius-sm); }

/* Live sample wrapper in the preview: receives data-accent/data-canvas/data-density/data-contrast
   so a viewer sees the effect without touching <html>. */
.vv-appearance-sample-row { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; padding-top: var(--space-2); }

/* ===== Sheet ===== */
/* Presentation (3.0.0, "movil nativo" — owner 2026-09-28): compact (<600px de contenedor) elige
   el patron M3 por `presentation` (Sheet/README.md "Mapeo de patrones"); desde 600px SIEMPRE es el
   dialogo centrado de siempre (bloque `@container (min-width:600px)` mas abajo), sin importar la
   presentacion — ninguna regla de esta seccion "compact" debe filtrarse a ese ancho: todas viven
   dentro de `@container (max-width:599.98px)` para que la especificidad no compita con el bloque
   de escritorio (mismo idiom que CP-01 de CommandPalette, ver "CommandPalette" mas abajo). */
.vv-sheet-scrim {
  position: fixed; inset: 0; z-index: 50; background: var(--scrim);
  display: flex; align-items: flex-end; justify-content: center;
  container-type: inline-size; /* the sheet queries the scrim's width, not the window's: a 390px shell inside a wide page still gets its compact presentation */
}
.vv-sheet-scrim-inline { position: relative; inset: auto; height: 100%; background: transparent; align-items: flex-end; }
/* Base compartida entre las 4 presentaciones: solo lo que no varia. Radio, borde, sombra y
   animacion de entrada son propios de cada `.vv-sheet-<presentation>` (compact) o del bloque de
   escritorio (`@container (min-width:600px)`, sin cambios). */
.vv-sheet {
  width: 100%; max-width: 640px; display: flex; flex-direction: column; min-height: 0;
  background: var(--surface); box-shadow: var(--shadow-lg);
}
/* % instead of vh: Sheet is position:fixed, so its containing block is normally the OS viewport —
   but any ancestor with contain:layout/paint, a transform, or a filter becomes that containing block
   instead (e.g. a page shell's own frame). % resolves against that box's real height either way, so
   snap="half" is always "50% of the box the sheet is actually anchored to, measured from the
   bottom" — vh would instead measure the outer browser window, ignoring any such ancestor. Solo
   tiene efecto real con `presentation="bottom"`; en el resto la presentacion fija su propia altura. */
/* S-02 (2.7.0): --sheet-max permite otra altura sin abrir presets nuevos (p. ej. style="--sheet-max: 70%"). */
.vv-sheet-half { max-height: var(--sheet-max, 50%); }
.vv-sheet-full { max-height: var(--sheet-max, 90%); }
@keyframes vv-sheet-in { from { transform: translateY(100%); } to { transform: translateY(0); } }
@keyframes vv-sheet-in-page { from { transform: translateX(100%); } to { transform: translateX(0); } }
.vv-sheet-handle { width: 36px; height: 4px; flex: none; margin: var(--space-2) auto var(--space-1); border-radius: var(--radius-full); background: var(--border-strong); }
.vv-sheet-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); padding: 0 var(--space-4) var(--space-3); flex: none; }
.vv-sheet-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Top app bar (fullscreen/page): accion final (p. ej. «Guardar» de FormDialog) empujada al borde
   trailing, aunque el titulo no llene el espacio disponible. */
.vv-sheet-head-action { flex: none; margin-inline-start: auto; }
.vv-sheet-body { overflow: auto; padding: 0 var(--space-4) var(--space-4); padding-bottom: max(var(--space-4), env(safe-area-inset-bottom, 0px)); display: flex; flex-direction: column; gap: var(--space-4); }
[data-density="compact"] .vv-sheet-body { padding: 0 var(--space-3) var(--space-3); padding-bottom: max(var(--space-3), env(safe-area-inset-bottom, 0px)); gap: var(--space-3); }
[data-density="touch"] .vv-sheet-body { padding: 0 var(--space-5) var(--space-5); padding-bottom: max(var(--space-5), env(safe-area-inset-bottom, 0px)); gap: var(--space-5); }
[data-density="compact"] .vv-sheet-head { padding: 0 var(--space-3) var(--space-2); }
[data-density="touch"] .vv-sheet-head { padding: 0 var(--space-5) var(--space-4); }

@container (max-width: 599.98px) {
  /* fullscreen (default, M3 "Full-screen dialog"): formulario/alta — X a la izquierda, titulo,
     `headerAction` a la derecha. Cubre toda la pantalla; entra deslizando desde abajo + fade, como
     Gmail compose / Calendar nuevo evento. Referente: m3.material.io/components/dialogs. */
  .vv-sheet-fullscreen {
    height: 100%; max-height: none; max-width: none; margin: 0;
    border-radius: 0; border: 0; box-shadow: none;
    animation: vv-sheet-in var(--dur-spatial-default) var(--spring-spatial-default);
  }
  .vv-sheet-fullscreen .vv-sheet-head { padding-top: max(var(--space-3), env(safe-area-inset-top, 0px)); }

  /* page (M3 "list-detail", canonico en compact): ficha/inspector — flecha atras a la izquierda,
     sin accion por defecto. Entra empujada desde la derecha (navegacion), no desde abajo.
     Referente: m3.material.io/foundations/layout/canonical-layouts (Contacts detail). */
  .vv-sheet-page {
    height: 100%; max-height: none; max-width: none; margin: 0;
    border-radius: 0; border: 0; box-shadow: none;
    animation: vv-sheet-in-page var(--dur-spatial-default) var(--spring-spatial-default);
  }
  .vv-sheet-page .vv-sheet-head { padding-top: max(var(--space-3), env(safe-area-inset-top, 0px)); }

  /* dialog (M3 "Basic dialog" / menus): listas de eleccion cortas y confirmaciones simples —
     dialogo centrado incluso en compact, nunca hoja inferior. Referente:
     m3.material.io/components/dialogs y /components/menus. */
  .vv-sheet-dialog {
    margin: auto; width: min(92vw, var(--sheet-w, 400px)); max-width: none; max-height: var(--sheet-max, 80vh);
    border-radius: var(--radius-xl); border: 1px solid var(--border-subtle);
    animation: vv-sheet-in-desktop var(--dur-spatial-default) var(--spring-spatial-default);
  }

  /* bottom (opt-in explicito, ya NO es el default): el comportamiento heredado hasta 2.13.x —
     hoja que sube desde abajo con asa de arrastre — reservado a listas de acciones tipo
     "compartir". Referente: m3.material.io/components/bottom-sheets. */
  .vv-sheet-bottom {
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    border: 1px solid var(--border-subtle); border-bottom: 0;
    animation: vv-sheet-in var(--dur-spatial-default) var(--spring-spatial-default);
  }
}

/* 600px = M3 Expressive window size class "medium" and up: the sheet is ALWAYS the centered dialog,
   whatever `presentation` says on compact — this block does not change with 3.0.0. */
@container (min-width: 600px) {
  .vv-sheet { margin: auto; max-width: var(--sheet-w, 480px); border-radius: var(--radius-xl); border: 1px solid var(--border-subtle); border-bottom: 1px solid var(--border-subtle); box-shadow: var(--shadow-lg); animation: vv-sheet-in-desktop var(--dur-spatial-default) var(--spring-spatial-default); }
  .vv-sheet-half, .vv-sheet-full { max-height: var(--sheet-max, 80%); }
  .vv-sheet-handle { display: none; }
  /* OV-01 (auditoría de espaciado 2026-09-28, queja owner): .vv-sheet-head nace con padding-top:0
     porque en móvil el drag handle ya pone el aire de arriba — aquí ese handle se oculta y nadie lo
     compensaba, así que el título (y FormDialog, que es un .vv-sheet xl) quedaba a 1px del borde
     superior redondeado. Mismo valor que el padding-bottom ya existente de .vv-sheet-head. Sigue
     aplicando con movil-nativo: a >=600px CUALQUIER presentación cae en este mismo diálogo
     centrado (el handle se oculta igual) — 0 solo era el valor pre-OV-01 del que partía esa rama. */
  .vv-sheet-head { padding-top: var(--space-3); }
  .vv-sheet-xl .vv-sheet-head { padding-inline: var(--space-6); }
  .vv-sheet-xl .vv-sheet-body { padding-inline: var(--space-6); }
  .vv-sheet-xl .vv-sheet-foot { padding-inline: var(--space-6); }
}
/* Sheet size (2.12.0): ancho de escritorio por preset. 'md' = 480px (el de 2.11.0, por defecto). */
.vv-sheet-sm { --sheet-w: 400px; }
.vv-sheet-md { --sheet-w: 480px; }
.vv-sheet-lg { --sheet-w: 600px; }
.vv-sheet-xl { --sheet-w: 720px; }
/* Pie fijo, hermano de .vv-sheet-body (no hijo): el cuerpo hace scroll y el pie queda visible. */
.vv-sheet-foot {
  flex: none; display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-4); padding-bottom: max(var(--space-3), env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border-subtle);
}
.vv-sheet-foot > .vv-formactions { flex: 1 1 auto; border-top: 0; padding: 0; }
/* FormDialog (2.12.0): Sheet xl con cuerpo flex y pie fijo. */
.vv-formdialog { --sheet-max: 88%; }
.vv-formdialog .vv-sheet-body { flex: 1 1 auto; }
@keyframes vv-sheet-in-desktop { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .vv-sheet { animation: none; }
}

/* ---- data ---- */
/* Vive UI — frag/data.css. Data display + Forms block. Loads after tokens.css + bundle.css. */

/* ===== DataTable ===== */
.vv-dt-wrap {
  display: flex; flex-direction: column; min-width: 0; min-height: 0; width: 100%; flex: 1 1 auto;
  border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); background: var(--surface); overflow: hidden;
  container-type: inline-size;
}
.vv-dt-scroll { flex: 1 1 auto; min-height: 0; overflow: auto; }
.vv-dt { display: flex; flex-direction: column; min-width: max-content; }
.vv-dt-headrow, .vv-dt-row { display: grid; grid-template-columns: var(--vv-dt-cols, 1fr); align-items: stretch; }
.vv-dt-headrow {
  position: sticky; top: 0; z-index: 3; background: var(--surface-2); border-bottom: 1px solid var(--border-subtle);
  min-height: var(--row-compact);
}
.vv-dt-th {
  box-sizing: border-box; display: flex; align-items: center; gap: var(--space-2); padding: 0 var(--space-3);
  font-size: 12px; font-weight: 500; color: var(--ink-muted); min-width: 0; overflow: hidden;
}
.vv-dt-th-right { justify-content: flex-end; }
.vv-dt-th-check { justify-content: center; padding: 0; }
.vv-dt-sortbtn {
  all: unset; box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--space-1);
  min-height: var(--row-compact); cursor: pointer; color: inherit; max-width: 100%;
}
.vv-dt-sortbtn span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-dt-sortbtn:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; border-radius: var(--radius-sm); }
.vv-dt-sorticon { width: 13px !important; height: 13px !important; flex: none; color: var(--ink-faint); opacity: 0; transition: opacity var(--dur-fast) var(--ease-out); }
.vv-dt-sortbtn:hover .vv-dt-sorticon { opacity: 1; }
.vv-dt-sorticon-active { opacity: 1; color: var(--ink); }

.vv-dt-row {
  box-sizing: content-box; border-bottom: 1px solid var(--border-subtle); min-height: var(--row-h);
  transition: background-color var(--dur-fast) var(--ease-out); cursor: default;
}
.vv-dt-row:last-child { border-bottom: 0; }
.vv-dt-row:hover { background: var(--surface-3); }
.vv-dt-row-selected, .vv-dt-row-selected:hover { background: var(--primary-soft); }
.vv-dt-td {
  box-sizing: border-box; position: relative; display: flex; align-items: center; min-width: 0; padding: 0 var(--space-3);
  font-size: var(--text-base); color: var(--ink); overflow: hidden;
}
.vv-dt-td:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; z-index: 1; }
.vv-dt-td-right { justify-content: flex-end; }
.vv-dt-mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
/* overflow:visible (no el `hidden` heredado de `.vv-dt-th`/`.vv-dt-td`, ahí para la elipsis del
   resto de columnas): esta celda solo aloja un checkbox centrado, sin texto que recortar, así que
   puede dejar salir el `::before` de área táctil de abajo sin generar un recorte fantasma — real,
   no solo lo que mida el script (ver "Known pre-existing limitation" en la utilidad de arriba: aquí
   no aplica porque este ancestro no recorta). */
.vv-dt-th-check, .vv-dt-td.vv-dt-th-check { justify-content: center; padding: 0; overflow: visible; }
.vv-dt-celltext { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-dt-status { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; }
.vv-dt-status span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-dt-td .vv-badge, .vv-dt-td .vv-chip { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-dt-th-check input[type="checkbox"], .vv-dt-td.vv-dt-th-check input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--primary); cursor: pointer; }
/* Objetivo táctil 48×48 en touch (auditoría de contención 2026-09-23): el checkbox se queda en
   16px visual (M3 no exige agrandar el control en sí) pero su ÁREA TÁCTIL crece con la misma
   utilidad del kit — (48-16)/2 = 16px por lado. */
.vv-dt-th-check input[type="checkbox"], .vv-dt-td.vv-dt-th-check input[type="checkbox"] { position: relative; }
[data-density="touch"] .vv-dt-th-check input[type="checkbox"],
[data-density="touch"] .vv-dt-td.vv-dt-th-check input[type="checkbox"] { --vv-hit-x: -16px; --vv-hit-y: -16px; }
.vv-dt-td-editable { cursor: text; }
.vv-dt-editinput {
  position: absolute; inset: 0; box-sizing: border-box; width: 100%; height: 100%; padding: 0 var(--space-3);
  font: inherit; font-size: var(--text-base); border: 2px solid var(--primary); border-radius: 0;
  background: var(--surface); color: var(--ink); outline: none;
}
.vv-dt-actions { display: none; gap: var(--space-1); align-items: center; }
.vv-dt-row:hover .vv-dt-actions, .vv-dt-row:focus-within .vv-dt-actions { display: inline-flex; }
[data-density="touch"] .vv-dt-actions { display: inline-flex; }
.vv-dt-empty { padding: var(--space-8) var(--space-4); text-align: center; color: var(--ink-muted); font-size: var(--text-base); }

/* Tree rows (expandable / getSubRows) */
.vv-dt-tree-cell { display: flex; align-items: center; gap: var(--space-1); min-width: 0; }
.vv-dt-tree-toggle {
  all: unset; position: relative; box-sizing: border-box; flex: none; width: 24px; height: 24px; display: inline-flex;
  align-items: center; justify-content: center; border-radius: var(--radius-sm); color: var(--ink-muted); cursor: pointer;
  transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast), background-color var(--dur-effects-fast) var(--spring-effects-fast), color var(--dur-effects-fast) var(--spring-effects-fast);
}
/* Objetivo táctil 48×48 en touch: la celda del título (`.vv-dt-td-title`) mide 48px de alto en
   touch (row-h) y el botón está centrado con `align-items:center` en toda esa altura — el hueco
   vertical (48-24)/2=12px por lado cabe exacto dentro de la propia celda sin tocar el recorte de
   `.vv-dt-td{overflow:hidden}` heredado (a diferencia de `.vv-btn-sm`/breadcrumb: aquí el objetivo
   táctil no sale del propio cuadro de la celda, solo del cuadro visual del botón). El lado
   izquierdo tiene exactamente `--space-3` (12px) de padding de celda antes del borde — incluido en
   el nivel 0 de indentación; en niveles más profundos hay aún más margen. */
[data-density="touch"] .vv-dt-tree-toggle { --vv-hit-x: -12px; --vv-hit-y: -12px; }
.vv-dt-tree-toggle:hover { background: var(--surface-3); color: var(--ink); }
.vv-dt-tree-toggle:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-dt-tree-toggle :is(svg, .vv-icon) { width: 14px !important; height: 14px !important; }
.vv-dt-tree-toggle-expanded { transform: rotate(90deg); }
.vv-dt-tree-spacer { flex: none; width: 24px; height: 24px; }
.vv-dt-tree-content { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-dt-row-parent { font-weight: 500; }
.vv-dt-row-totals { background: var(--surface-2); border-top: 1px solid var(--border); font-weight: 500; position: sticky; bottom: 0; }

.vv-dt-foot {
  display: flex; align-items: center; gap: var(--space-4); flex: none; min-height: var(--row-h);
  padding: 0 var(--space-3); border-top: 1px solid var(--border-subtle); background: var(--surface-2);
  font-size: 12px; color: var(--ink-muted);
}
.vv-dt-foot-count { flex: 1 1 auto; font-variant-numeric: tabular-nums; }
.vv-dt-foot-size { display: flex; align-items: center; gap: var(--space-2); }
.vv-dt-foot-size select {
  font: inherit; font-size: 12px; padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface); color: var(--ink);
}
.vv-dt-foot-nav { display: flex; gap: var(--space-1); }

/* Compact row grouping wrappers (see bundle.js `groupedCols`/`cellFor`): `display: contents` above
   600px removes them from the box tree so their children (the real gridcells) flow as direct grid
   items of `.vv-dt-row`, exactly matching the flat pre-grouping DOM — desktop/medium are unaffected
   by this restructure. The compact @container below overrides these to real flex boxes. */
.vv-dt-row-content, .vv-dt-row-metaline, .vv-dt-row-trailing, .vv-dt-row-values { display: contents; }
.vv-dt-actions-compact { display: none; }
/* rowActionsVisibility="always" (§25): las acciones de fila se revelan siempre, no al hover/foco.
   En `layout:'list'` quien manda es el patrón lista de abajo (compact), no esta regla. */
.vv-dt-actions-always:not(.vv-dt-list) .vv-dt-row .vv-dt-actions { display: inline-flex; }

/* Below 600px — window size class "compact": hiding low-priority columns alone still doesn't fit
   a grid of columns (audit 2026-09-23: 1050px at 375px viewport even with `priority:"low"` hidden).
   MD3 list-item pattern instead: [checkbox 48][content 1fr][trailing auto] per row — content is
   title (line 1) + a joined meta line (line 2); trailing is value(s)/status stacked and right-
   aligned next to the row's ONE action (a lone action stays a 48px icon-button, 2+ collapse into a
   "⋯" menu — see `DataTableCell`'s `col.type==='actions'` branch in bundle.js). No horizontal
   scroll, no fixed row height (72-88px falls out of 2 lines of text + padding, never clipped — the
   `.vv-dt-scroll` ancestor scrolls the whole list, it never crops a row mid-height). Which column is
   title/meta/value/actions is assigned by DataTable itself (`dtCellRole`/`groupedCols` in bundle.js)
   — nothing for a consumer to opt into beyond the existing `columns` array (optionally
   `priority: "primary"` on the column that should be the title). */
@container (max-width: 599.98px) {
  .vv-dt-th-low, .vv-dt-td.vv-dt-td-low { display: none; }
  .vv-dt-scroll { overflow-x: hidden; }
  .vv-dt { min-width: 0; }
  .vv-dt-headrow { display: none; }

  /* M3 2-line list row, redesigned 2026-09-23 per coordinator review (the first pass used flex
     `order`/`flex-basis:100%` on flat siblings — meta wrapped to the flex container's own left edge,
     not the title's, because flex-basis:100% starts a new "line" from column 0, not from wherever
     the previous item left off. Real grouping needs real nesting: `.vv-dt-row-content`/`-trailing`
     (display:contents above 600px, see the base rule near .vv-dt-foot-nav) so desktop's flat grid is
     untouched. Target height 72-88px (M3 2-3 line list item): --space-4 (16px) padding + 2 text
     lines + 2px gap lands there without a fixed height. */
  .vv-dt-row:not(.vv-dt-row-totals) {
    display: grid; grid-template-columns: 1fr auto; column-gap: var(--space-3);
    align-items: start; min-height: 0; padding: var(--space-4);
  }
  .vv-dt-wrap-selectable .vv-dt-row:not(.vv-dt-row-totals) { grid-template-columns: 48px 1fr auto; }
  .vv-dt-row-totals { flex-wrap: wrap; padding: var(--space-3); }
  .vv-dt-td { padding: 0; }
  /* Every cell still carries its desktop-grid `style="width: <colWidth>"` inline (harmless there —
     it's the same value already baked into `--vv-dt-cols`) — but once title/meta/value/actions
     leave the grid for these flex wrappers, that inline pixel width actively fights the wrapper's
     own sizing (the actions column's 104px was eating the row, starving content's 1fr track to 0).
     `!important` because only `!important` external CSS out-specificities an inline `style`. */
  .vv-dt-row-content .vv-dt-td, .vv-dt-row-trailing .vv-dt-td { width: auto !important; }
  .vv-dt-td.vv-dt-th-check { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; }

  /* Content column: title (line 1) + meta line (line 2) share the same left edge — both are direct
     flex children of `.vv-dt-row-content`, never re-based to x:0 like the old sibling-flex attempt. */
  .vv-dt-row-content { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  .vv-dt-td-title { font-size: 15px; line-height: 20px; font-weight: 600; }
  .vv-dt-td-title .vv-dt-celltext, .vv-dt-td-title .vv-dt-tree-content { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .vv-dt-row-metaline { display: flex; min-width: 0; overflow: hidden; }
  .vv-dt-td-meta { font-size: 13px; line-height: 18px; color: var(--ink-muted); white-space: nowrap; flex: none; }
  .vv-dt-td-meta .vv-dt-celltext { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* Multiple meta columns (e.g. cliente + fecha) join on one line as "cliente · fecha", not as
     "Cliente: valor" label pairs — a11y still gets the column name via `aria-label` (DataTableCell),
     this is the sighted/visual rendering only. Only the LAST one shrinks/ellipses; the ones before
     it are short enough in practice (assignee names, dates) that they shouldn't be the one to give. */
  .vv-dt-td-meta:last-child { flex: 1 1 auto; min-width: 0; }
  .vv-dt-td-meta:not(:first-child)::before { content: "·"; margin: 0 4px; }

  /* Trailing: value(s)+status stacked and right-aligned, the row's one action alongside — never its
     own line, never 3 buttons in a row (M3: a list item has ONE trailing element, not an actions bar). */
  .vv-dt-row-trailing { display: flex; align-items: flex-start; gap: var(--space-2); }
  .vv-dt-row-values { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; padding-top: 2px; }
  .vv-dt-td-value { display: flex; align-items: center; justify-content: flex-end; gap: 4px; font-size: 13px; }
  .vv-dt-row-trailing .vv-dt-actions { display: none; }
  .vv-dt-row-trailing .vv-dt-actions-compact { display: inline-flex; }
  [data-density="touch"] .vv-dt-actions { gap: var(--space-2); }
}
@supports not (container-type: inline-size) {
  @media (max-width: 599.98px) {
    .vv-dt-th-low, .vv-dt-td.vv-dt-td-low { display: none; }
    .vv-dt-scroll { overflow-x: hidden; }
    .vv-dt { min-width: 0; }
    .vv-dt-headrow { display: none; }

    /* M3 2-line list row, redesigned 2026-09-23 per coordinator review (the first pass used flex
       `order`/`flex-basis:100%` on flat siblings — meta wrapped to the flex container's own left edge,
       not the title's, because flex-basis:100% starts a new "line" from column 0, not from wherever
       the previous item left off. Real grouping needs real nesting: `.vv-dt-row-content`/`-trailing`
       (display:contents above 600px, see the base rule near .vv-dt-foot-nav) so desktop's flat grid is
       untouched. Target height 72-88px (M3 2-3 line list item): --space-4 (16px) padding + 2 text
       lines + 2px gap lands there without a fixed height. */
    .vv-dt-row:not(.vv-dt-row-totals) {
      display: grid; grid-template-columns: 1fr auto; column-gap: var(--space-3);
      align-items: start; min-height: 0; padding: var(--space-4);
    }
    .vv-dt-wrap-selectable .vv-dt-row:not(.vv-dt-row-totals) { grid-template-columns: 48px 1fr auto; }
    .vv-dt-row-totals { flex-wrap: wrap; padding: var(--space-3); }
    .vv-dt-td { padding: 0; }
    .vv-dt-row-content .vv-dt-td, .vv-dt-row-trailing .vv-dt-td { width: auto !important; }
    .vv-dt-td.vv-dt-th-check { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; }

    /* Content column: title (line 1) + meta line (line 2) share the same left edge — both are direct
       flex children of `.vv-dt-row-content`, never re-based to x:0 like the old sibling-flex attempt. */
    .vv-dt-row-content { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
    .vv-dt-td-title { font-size: 15px; line-height: 20px; font-weight: 600; }
    .vv-dt-td-title .vv-dt-celltext, .vv-dt-td-title .vv-dt-tree-content { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .vv-dt-row-metaline { display: flex; min-width: 0; overflow: hidden; }
    .vv-dt-td-meta { font-size: 13px; line-height: 18px; color: var(--ink-muted); white-space: nowrap; flex: none; }
    .vv-dt-td-meta .vv-dt-celltext { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    /* Multiple meta columns (e.g. cliente + fecha) join on one line as "cliente · fecha", not as
       "Cliente: valor" label pairs — a11y still gets the column name via `aria-label` (DataTableCell),
       this is the sighted/visual rendering only. Only the LAST one shrinks/ellipses; the ones before
       it are short enough in practice (assignee names, dates) that they shouldn't be the one to give. */
    .vv-dt-td-meta:last-child { flex: 1 1 auto; min-width: 0; }
    .vv-dt-td-meta:not(:first-child)::before { content: "·"; margin: 0 4px; }

    /* Trailing: value(s)+status stacked and right-aligned, the row's one action alongside — never its
       own line, never 3 buttons in a row (M3: a list item has ONE trailing element, not an actions bar). */
    .vv-dt-row-trailing { display: flex; align-items: flex-start; gap: var(--space-2); }
    .vv-dt-row-values { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; padding-top: 2px; }
    .vv-dt-td-value { display: flex; align-items: center; justify-content: flex-end; gap: 4px; font-size: 13px; }
    .vv-dt-row-trailing .vv-dt-actions { display: none; }
    .vv-dt-row-trailing .vv-dt-actions-compact { display: inline-flex; }
    [data-density="touch"] .vv-dt-actions { gap: var(--space-2); }
  }
}

/* layout="list" (§25, 2.12.0): fuerza el patrón lista M3 a CUALQUIER ancho. No inventa markup:
   re-aplica el mismo bloque compacto de arriba (mismo renderer `.vv-dt-row-content`/`-metalione`/
   `-trailing`) bajo `.vv-dt-list` en `.vv-dt-wrap` en vez de por ancho de contenedor. La cabecera
   de columnas no se pinta (el runtime la omite con `layout:'list'`; aquí se oculta por si llega
   marcada a mano o por HTML/Astro). */
.vv-dt-list .vv-dt-th-low, .vv-dt-list .vv-dt-td.vv-dt-td-low { display: none; }
.vv-dt-list .vv-dt-scroll { overflow-x: hidden; }
.vv-dt-list .vv-dt { min-width: 0; }
.vv-dt-list .vv-dt-headrow { display: none; }
.vv-dt-list .vv-dt-row:not(.vv-dt-row-totals) {
  display: grid; grid-template-columns: 1fr auto; column-gap: var(--space-3);
  align-items: start; min-height: 0; padding: var(--space-4);
}
.vv-dt-list.vv-dt-wrap-selectable .vv-dt-row:not(.vv-dt-row-totals) { grid-template-columns: 48px 1fr auto; }
.vv-dt-list .vv-dt-row-totals { flex-wrap: wrap; padding: var(--space-3); }
.vv-dt-list .vv-dt-td { padding: 0; }
.vv-dt-list .vv-dt-row-content .vv-dt-td, .vv-dt-list .vv-dt-row-trailing .vv-dt-td { width: auto !important; }
.vv-dt-list .vv-dt-td.vv-dt-th-check { width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; }
.vv-dt-list .vv-dt-row-content { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.vv-dt-list .vv-dt-td-title { font-size: 15px; line-height: 20px; font-weight: 600; }
.vv-dt-list .vv-dt-td-title .vv-dt-celltext, .vv-dt-list .vv-dt-td-title .vv-dt-tree-content { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-dt-list .vv-dt-row-metaline { display: flex; min-width: 0; overflow: hidden; }
.vv-dt-list .vv-dt-td-meta { font-size: 13px; line-height: 20px; color: var(--ink-muted); white-space: nowrap; flex: none; }
.vv-dt-list .vv-dt-td-meta .vv-dt-celltext { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-dt-list .vv-dt-td-meta:last-child { flex: 1 1 auto; min-width: 0; }
.vv-dt-list .vv-dt-td-meta:not(:first-child)::before { content: "·"; margin: 0 4px; }
.vv-dt-list .vv-dt-row-trailing { display: flex; align-items: flex-start; gap: var(--space-2); }
.vv-dt-list .vv-dt-row-values { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; padding-top: 2px; }
.vv-dt-list .vv-dt-td-value { display: flex; align-items: center; justify-content: flex-end; gap: 4px; font-size: 13px; }
.vv-dt-list .vv-dt-row-trailing .vv-dt-actions { display: none; }
.vv-dt-list .vv-dt-row-trailing .vv-dt-actions-compact { display: inline-flex; }
.vv-dt-list [data-density="touch"] .vv-dt-actions { gap: var(--space-2); }

/* Footer (pagination + page size) can't fit "1–8 de 12" + "Filas por página" + select + two nav
   buttons on one line at 375px — wrap onto two: count + prev/next on line 1, page-size on line 2. */
@container (max-width: 599.98px) {
  .vv-dt-foot { flex-wrap: wrap; row-gap: var(--space-2); min-height: auto; padding: var(--space-2) var(--space-3); }
  .vv-dt-foot-count { order: 1; }
  .vv-dt-foot-nav { order: 2; margin-left: auto; }
  .vv-dt-foot-size { order: 3; flex: 1 1 100%; }
}
@supports not (container-type: inline-size) {
  @media (max-width: 599.98px) {
    .vv-dt-foot { flex-wrap: wrap; row-gap: var(--space-2); min-height: auto; padding: var(--space-2) var(--space-3); }
    .vv-dt-foot-count { order: 1; }
    .vv-dt-foot-nav { order: 2; margin-left: auto; }
    .vv-dt-foot-size { order: 3; flex: 1 1 100%; }
  }
}

/* 600–839.98px — window size class "medium": low-priority columns are hidden (same rule as
   compact) but a table can still need more room than it has. `.vv-dt-scroll` scrolls on both axes
   (vertical for rows under the sticky header, horizontal for wide columns), so the classic
   local/scroll-attachment "scroll shadow" trick isn't a clean fit (its gradients are pinned to one
   axis) — a static inset shadow at the trailing/leading edges is a deliberately simpler affordance
   that doesn't fight the vertical scroll. Sticky title column (plus sticky checkbox column,
   offsetting the title's `left` when selectable) keeps the row's subject in view either way. */
@container (min-width: 600px) and (max-width: 839.98px) {
  /* Only when there's really something to scroll to (bundle.js ResizeObserver/scroll listener sets
     these), and only on the side that has it — a shadow with nothing behind it is decoration that
     doesn't inform (Linear: no chrome without a reason). */
  .vv-dt-scroll[data-overflow-start] { box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, .12); }
  .vv-dt-scroll[data-overflow-end] { box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, .12); }
  .vv-dt-scroll[data-overflow-start][data-overflow-end] { box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, .12), inset 10px 0 8px -8px rgba(0, 0, 0, .12); }
  .vv-dt-th.vv-dt-th-title, .vv-dt-td-title { position: sticky; left: 0; z-index: 2; background: var(--surface); }
  .vv-dt-wrap-selectable .vv-dt-th.vv-dt-th-title, .vv-dt-wrap-selectable .vv-dt-td-title { left: var(--row-h); }
  .vv-dt-th-check, .vv-dt-td.vv-dt-th-check { position: sticky; left: 0; z-index: 3; background: var(--surface); }
  .vv-dt-headrow .vv-dt-th.vv-dt-th-title { background: var(--surface-2); z-index: 4; }
  .vv-dt-headrow .vv-dt-th-check { background: var(--surface-2); z-index: 5; }
  .vv-dt-row:hover .vv-dt-td-title, .vv-dt-row:hover .vv-dt-td.vv-dt-th-check { background: var(--surface-3); }
  .vv-dt-row-selected .vv-dt-td-title, .vv-dt-row-selected .vv-dt-td.vv-dt-th-check { background: var(--primary-soft); }
}
@supports not (container-type: inline-size) {
  @media (min-width: 600px) and (max-width: 839.98px) {
    .vv-dt-scroll[data-overflow-start] { box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, .12); }
    .vv-dt-scroll[data-overflow-end] { box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, .12); }
    .vv-dt-scroll[data-overflow-start][data-overflow-end] { box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, .12), inset 10px 0 8px -8px rgba(0, 0, 0, .12); }
    .vv-dt-th.vv-dt-th-title, .vv-dt-td-title { position: sticky; left: 0; z-index: 2; background: var(--surface); }
    .vv-dt-wrap-selectable .vv-dt-th.vv-dt-th-title, .vv-dt-wrap-selectable .vv-dt-td-title { left: var(--row-h); }
    .vv-dt-th-check, .vv-dt-td.vv-dt-th-check { position: sticky; left: 0; z-index: 3; background: var(--surface); }
    .vv-dt-headrow .vv-dt-th.vv-dt-th-title { background: var(--surface-2); z-index: 4; }
    .vv-dt-headrow .vv-dt-th-check { background: var(--surface-2); z-index: 5; }
    .vv-dt-row:hover .vv-dt-td-title, .vv-dt-row:hover .vv-dt-td.vv-dt-th-check { background: var(--surface-3); }
    .vv-dt-row-selected .vv-dt-td-title, .vv-dt-row-selected .vv-dt-td.vv-dt-th-check { background: var(--primary-soft); }
  }
}

/* ===== StatTile ===== */
.vv-stattile {
  container-type: inline-size; /* el valor KPI escala con la tarjeta: nunca se recorta un número */
  display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); background: var(--surface);
  min-width: 0;
}
/* Fila de KPIs: grid auto-fit, nunca `.vv-row` (flex) — ahí min-width:0 colapsa cada tarjeta a
   ~34px. 160px = suelo legible de un importe; 1 columna en móvil sin media query (esaem 2026-09-28). */
.vv-stattile-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-4); }
[data-density="compact"] .vv-stattile { padding: var(--space-3); }
[data-density="touch"] .vv-stattile { padding: var(--space-5); }
.vv-stattile-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-width: 0; }
.vv-stattile-head > * { min-width: 0; }
.vv-stattile-head .vv-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-stattile-icon { width: var(--icon); height: var(--icon); color: var(--ink-muted); flex: none; }
/* ST-01 (auditoría móvil 2026-09-23): `min(28px, 11cqi)` sin suelo colapsaba a ~3.5px en una
   rejilla de 4 columnas estrechas (CashflowTile a 375px) — un KPI ilegible es peor que uno que deja
   de escalar. `clamp()` conserva el mismo techo/comportamiento fluido y añade un suelo de 16px
   (mínimo legible de la escala tipográfica del kit); si el contenedor es tan estrecho que ni 16px
   cabe, el consumidor debe colapsar la rejilla a menos columnas (ver StatTile/README), no seguir
   encogiendo el número. */
/* ST-02 (2.12.0): line-height fijo (no proporcional) para que la altura total del tile
   (padding + borde + gap + head + value) caiga siempre en la rejilla de 4px cuando se envuelve
   en `.vv-statstrip-tile` (botón accionable auditado). 34px reutiliza el mismo par (28px/34px)
   que `.vv-text-display-md` — no es un valor suelto. La variante `-inline` (fuente más pequeña)
   recupera el ratio proporcional justo debajo. */
.vv-stattile-value { font-size: clamp(16px, 11cqi, 28px); line-height: 34px; font-weight: 600; letter-spacing: -0.015em; color: var(--ink); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-stattile-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-width: 0; }
.vv-stattile-delta { display: flex; flex-direction: column; gap: 0; font-size: 12px; font-weight: 500; color: var(--ink-muted); min-width: 0; flex: 1 1 auto; }
.vv-stattile-delta-lead { display: inline-flex; align-items: center; gap: var(--space-1); white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.vv-stattile-delta-success { color: var(--success); }
.vv-stattile-delta-danger { color: var(--danger); }
.vv-stattile-delta-neutral { color: var(--ink-muted); }
.vv-stattile-delta-icon { width: 13px !important; height: 13px !important; flex: none; }
.vv-stattile-delta-caption { color: var(--ink-muted); font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vv-stattile-spark { flex: none; color: var(--primary); }
/* ST-inline (2.12.0): tira de KPI de una franja. Sigue siendo `.vv-stattile` (hereda densidad,
   borde, superficie y `container-type`); solo cambia la dirección a fila. El valor usa un `clamp`
   propio más bajo (a todo el ancho, el `11cqi` en columna daría un número desproporcionado) y la
   sparkline se oculta: no cabe sin encoger el valor por debajo del suelo legible (16px). */
.vv-stattile-inline { flex-direction: row; align-items: center; gap: var(--space-4); }
.vv-stattile-inline .vv-stattile-head { flex-direction: column; align-items: flex-start; justify-content: center; gap: 1px; flex: 1 1 auto; }
.vv-stattile-inline .vv-stattile-value { flex: none; font-size: clamp(16px, 4cqi, 24px); line-height: 1.15; }
.vv-stattile-inline .vv-stattile-foot { flex: none; }
.vv-stattile-inline .vv-stattile-spark { display: none; }

/* ===== Select ===== */
.vv-select-wrap { position: relative; display: flex; align-items: center; }
.vv-select {
  width: 100%; height: var(--row-h); padding: 0 var(--space-8) 0 var(--space-3); font: inherit; font-size: var(--text-base);
  border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--surface); color: var(--ink);
  box-shadow: var(--shadow-sm); appearance: none; -webkit-appearance: none; cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out), border-radius var(--dur-spring) var(--ease-spring);
}
.vv-select:hover { border-color: var(--border-strong); }
.vv-select:focus-visible { border-color: var(--border-strong); border-radius: var(--radius-lg); }
.vv-select[aria-invalid="true"] { border-color: var(--danger); }
.vv-select-chevron { position: absolute; right: var(--space-3); width: 14px !important; height: 14px !important; color: var(--ink-muted); pointer-events: none; }
.vv-select[disabled], .vv-textarea[disabled], .vv-numberinput-wrap:has(input[disabled]), .vv-dateinput-wrap:has(input[disabled]) { background: var(--surface-2); color: var(--ink-faint); cursor: not-allowed; box-shadow: none; }
.vv-select[disabled] { color: var(--ink-faint); }

/* ===== Textarea ===== */
.vv-textarea {
  box-sizing: border-box; width: 100%; min-height: calc(var(--row-h) * 2); padding: var(--space-2) var(--space-3);
  font: inherit; font-size: var(--text-base); line-height: 1.5; resize: vertical;
  border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--surface); color: var(--ink);
  box-shadow: var(--shadow-sm); transition: border-color var(--dur-fast) var(--ease-out);
}
.vv-textarea::placeholder { color: var(--ink-faint); }
.vv-textarea:hover { border-color: var(--border-strong); }
.vv-textarea:focus-visible { border-color: var(--border-strong); border-radius: var(--radius-lg); }
.vv-textarea[aria-invalid="true"] { border-color: var(--danger); }
.vv-textarea-autogrow { resize: none; overflow: hidden; }
.vv-textarea-foot { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.vv-textarea-count { flex: none; }

/* ===== Checkbox ===== */
.vv-checkbox-row { display: flex; align-items: flex-start; gap: var(--space-2); min-height: var(--row-h); }
.vv-checkbox-box-wrap { position: relative; display: inline-flex; flex: none; width: 16px; height: 16px; margin-top: calc((var(--row-h) - 16px) / 2); }
.vv-checkbox-input { position: absolute; inset: 0; margin: 0; width: 16px; height: 16px; opacity: 0; cursor: pointer; }
.vv-checkbox-input:disabled { cursor: not-allowed; }
.vv-checkbox-box {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); border: 1px solid var(--border-strong); background: var(--surface);
  transition: background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
  /* Sits on top of .vv-checkbox-input (same absolute inset:0 rect, later in the DOM) with no wrapping
     <label> around this pair — unlike RadioGroup, whose <label> forwards clicks to its input regardless
     of what's on top. Without this, a real click on the visual square never reaches the input: the
     browser's hit-test resolves to this span, so the native checkbox toggle never fires (bug reported
     2026-09-28, AuthPage terms checkbox). pointer-events:none lets the click fall through to the input. */
  pointer-events: none;
}
.vv-checkbox-check { width: 11px !important; height: 11px !important; color: var(--on-primary); opacity: 0; transition: opacity var(--dur-fast) var(--ease-out); }
.vv-checkbox-input:checked + .vv-checkbox-box, .vv-checkbox-input:indeterminate + .vv-checkbox-box { background: var(--primary); border-color: var(--primary); }
.vv-checkbox-input:checked + .vv-checkbox-box .vv-checkbox-check { opacity: 1; }
.vv-checkbox-input:indeterminate + .vv-checkbox-box { position: relative; }
.vv-checkbox-input:indeterminate + .vv-checkbox-box::after { content: ""; position: absolute; width: 8px; height: 2px; border-radius: 1px; background: var(--on-primary); }
.vv-checkbox-input:indeterminate + .vv-checkbox-box .vv-checkbox-check { opacity: 0; }
.vv-checkbox-input:focus-visible + .vv-checkbox-box { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-checkbox-input:disabled + .vv-checkbox-box { background: var(--surface-2); border-color: var(--border-subtle); cursor: not-allowed; }
.vv-checkbox-input:disabled + .vv-checkbox-box .vv-checkbox-check { color: var(--ink-faint); }
.vv-checkbox-input:disabled ~ .vv-checkbox-text .vv-checkbox-label { color: var(--ink-faint); cursor: not-allowed; }
.vv-radio-input:disabled ~ .vv-radio-circle { background: var(--surface-2); border-color: var(--border-subtle); cursor: not-allowed; }
.vv-radio-input:disabled ~ .vv-radio-circle::after { background: var(--ink-faint); }
.vv-checkbox-text { display: flex; flex-direction: column; gap: 2px; padding-top: calc((var(--row-h) - 16px) / 2 - 2px); }
.vv-checkbox-label { font-size: var(--text-base); color: var(--ink); cursor: pointer; min-width: 0; }
.vv-checkbox-desc { font-size: 11px; line-height: 16px; color: var(--ink-muted); }

/* ===== RadioGroup ===== */
.vv-radiogroup { display: flex; flex-direction: column; gap: var(--space-3); }
.vv-radiogroup-inline { flex-direction: row; flex-wrap: wrap; gap: var(--space-4); }
.vv-radio-option { display: flex; align-items: center; gap: var(--space-2); cursor: pointer; min-height: var(--row-h); }
.vv-radio-circle-wrap { position: relative; display: inline-flex; flex: none; width: 16px; height: 16px; }
.vv-radio-input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.vv-radio-circle {
  position: absolute; inset: 0; border-radius: var(--radius-full); border: 1px solid var(--border-strong); background: var(--surface);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.vv-radio-circle::after {
  content: ""; position: absolute; inset: 3px; border-radius: var(--radius-full); background: var(--primary);
  opacity: 0; transform: scale(0.4); transition: opacity var(--dur-fast) var(--ease-out), transform var(--dur-spring) var(--ease-spring);
}
.vv-radio-input:checked ~ .vv-radio-circle { border-color: var(--primary); }
.vv-radio-input:checked ~ .vv-radio-circle::after { opacity: 1; transform: scale(1); }
.vv-radio-input:focus-visible ~ .vv-radio-circle { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-radio-label { font-size: var(--text-base); color: var(--ink); min-width: 0; }

/* ===== NumberInput ===== */
.vv-numberinput-wrap {
  display: flex; align-items: center; height: var(--row-h);
  border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--surface); box-shadow: var(--shadow-sm);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.vv-numberinput-wrap:hover { border-color: var(--border-strong); }
.vv-numberinput-wrap:focus-within { border-color: var(--border-strong); border-radius: var(--radius-lg); }
.vv-numberinput {
  flex: 1 1 auto; min-width: 0; width: 100%; height: 100%; padding: 0 var(--space-2) 0 var(--space-3); text-align: right;
  font: inherit; font-size: var(--text-base); font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  border: 0; background: transparent; color: var(--ink); outline: none;
}
.vv-numberinput[aria-invalid="true"] { color: var(--danger); }
.vv-numberinput-unit { flex: none; padding-right: var(--space-3); font-size: 12px; color: var(--ink-muted); }
.vv-numberinput-btn {
  all: unset; box-sizing: border-box; display: flex; align-items: center; justify-content: center; flex: none;
  width: var(--row-h); height: 100%; cursor: pointer; color: var(--ink-muted); font-size: 15px; line-height: 1;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.vv-numberinput-btn:hover { background: var(--surface-3); color: var(--ink); }
.vv-numberinput-btn:first-child { border-right: 1px solid var(--border-subtle); border-radius: var(--radius-md) 0 0 var(--radius-md); }
.vv-numberinput-btn:last-child { border-left: 1px solid var(--border-subtle); border-radius: 0 var(--radius-md) var(--radius-md) 0; }
.vv-numberinput-btn :is(svg, .vv-icon) { width: 13px; height: 13px; }
.vv-numberinput-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }

/* ===== DateInput ===== */
.vv-dateinput-wrap {
  position: relative; display: flex; align-items: center; height: var(--row-h);
  border-radius: var(--radius-md); border: 1px solid var(--border); background: var(--surface); box-shadow: var(--shadow-sm);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.vv-dateinput-wrap:hover { border-color: var(--border-strong); }
.vv-dateinput-wrap:focus-within { border-color: var(--border-strong); border-radius: var(--radius-lg); }
.vv-dateinput-icon { position: absolute; left: var(--space-3); width: 14px !important; height: 14px !important; color: var(--ink-muted); pointer-events: none; }
.vv-dateinput {
  width: 100%; height: 100%; padding: 0 var(--space-3) 0 calc(var(--space-3) + 14px + var(--space-2));
  font: inherit; font-size: var(--text-base); border: 0; background: transparent; color: var(--ink); outline: none;
}
.vv-dateinput[aria-invalid="true"] { color: var(--danger); }
.vv-dateinput::-webkit-calendar-picker-indicator { position: absolute; inset: 0; width: 100%; opacity: 0; cursor: pointer; }

/* ===== FormSection ===== */
.vv-formsection { display: flex; flex-direction: column; gap: var(--space-4); container-type: inline-size; }
.vv-formsection-head { display: flex; flex-direction: column; gap: 2px; }
/* collapsible=true (FormSection reuses Card's collapse mechanism, see .vv-collapse above). */
.vv-formsection-head-toggle {
  all: unset; box-sizing: border-box; display: flex; width: 100%; align-items: center; min-height: 24px;
  justify-content: space-between; gap: var(--space-2); cursor: pointer;
}
.vv-formsection-head-toggle:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; border-radius: var(--radius-sm); }
.vv-formsection-head-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.vv-formsection-desc { margin: 0; font-size: 12px; color: var(--ink-muted); }
.vv-formsection-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); align-items: start; }
.vv-formsection-grid-1 { grid-template-columns: 1fr; }
.vv-form-span-2 { grid-column: 1 / -1; }
/* Below 600px — window size class "compact" — a two-column form collapses to one. */
@container (max-width: 599.98px) {
  .vv-formsection-grid { grid-template-columns: 1fr; }
}
@supports not (container-type: inline-size) {
  @media (max-width: 599.98px) {
    .vv-formsection-grid { grid-template-columns: 1fr; }
  }
}

/* ===== Disclosure ===== */
/* Layout primitive: summary + children, no card chrome — FAQs, opcionales de un formulario largo sin
   FormSection alrededor. Mismo mecanismo `.vv-collapse` de Card/FormSection. */
.vv-disclosure { border-bottom: 1px solid var(--border-subtle); }
.vv-disclosure-summary {
  all: unset; box-sizing: border-box; display: flex; width: 100%; align-items: center; justify-content: space-between;
  gap: var(--space-2); padding: var(--space-3) 0; font-size: var(--text-base); font-weight: 500; color: var(--ink); cursor: pointer;
}
/* DC-01 (auditoría móvil 2026-09-23): `padding` fijo + line-height del texto da 47px en touch, 1px
   corto del mínimo M3 — `min-height` cierra el hueco sin afectar compact/comfortable (más
   compactos a propósito). */
[data-density="touch"] .vv-disclosure-summary { min-height: var(--row-h); }
.vv-disclosure-summary:hover { color: var(--ink); }
.vv-disclosure-summary:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; border-radius: var(--radius-sm); }
.vv-disclosure-summary-text { min-width: 0; }
.vv-disclosure-body { padding-bottom: var(--space-3); color: var(--ink-muted); font-size: var(--text-base); }

/* CSS-only version for Astro (no JS): <details class="vv-disclosure"><summary class="vv-disclosure-summary">…
   the chevron rotates from the native [open] state instead of a JS-toggled class; ::-webkit-details-marker
   is hidden because the chevron icon replaces it. */
details.vv-disclosure > summary.vv-disclosure-summary { list-style: none; }
details.vv-disclosure > summary.vv-disclosure-summary::-webkit-details-marker { display: none; }
details.vv-disclosure > summary.vv-disclosure-summary .vv-card-chevron { transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast); }
details.vv-disclosure[open] > summary.vv-disclosure-summary .vv-card-chevron { transform: rotate(180deg); }
details.vv-disclosure > .vv-disclosure-body { padding-top: 0; }

/* ===== FormActions ===== */
.vv-formactions {
  position: sticky; bottom: 0; z-index: 4; display: flex; flex-wrap: wrap; min-width: 0; align-items: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-4); padding-bottom: max(var(--space-3), env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--border-subtle); background: var(--surface);
}
.vv-formactions-spacer { flex: 1 1 auto; }

/* ===== ErrorSummary ===== */
.vv-errorsummary {
  display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4);
  border-radius: var(--radius-lg); border: 1px solid var(--danger-soft); background: var(--danger-soft);
}
.vv-errorsummary-head { display: flex; align-items: center; gap: var(--space-2); color: var(--danger); }
.vv-errorsummary-head .vv-text-heading { color: var(--danger); }
.vv-errorsummary-icon { width: var(--icon); height: var(--icon); flex: none; }
.vv-errorsummary-list { margin: 0; padding-left: calc(var(--icon) + var(--space-2)); display: flex; flex-direction: column; gap: var(--space-1); }
.vv-errorsummary-link { color: var(--danger); font-size: var(--text-base); text-decoration: underline; text-underline-offset: 2px; }

.vv-errorsummary-link:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }

/* ===== Board (2.12.0, §27, incubating; 2.13.0 D-Board «tablero profesional») =====
   El tablero por etapas del paso 2 del árbol de colecciones. La tarjeta reutiliza la superficie de
   `.vv-card`/`.vv-card-interactive` (borde, radio, sombra, padding por densidad, hover, active) —
   no se inventa una caja nueva; `.vv-datarow` dentro pierde su cromo de fila (borde inferior, alto
   mínimo, hover propio) porque el hover/foco los da ahora la tarjeta entera. Mobile por container
   query sobre `.vv-board` (nunca viewport): ≥600px scroll horizontal; <600px columnas apiladas;
   <480px selector de etapa y una columna a la vez. */
.vv-board { container-type: inline-size; container-name: board; min-width: 0; }
.vv-board-toolbar { display: flex; justify-content: flex-end; margin-bottom: var(--space-2); }
.vv-board-scroll { display: flex; align-items: flex-start; gap: var(--space-4); overflow-x: auto; min-width: 0; padding-bottom: var(--space-2); scroll-snap-type: x proximity; }
.vv-board-scroll > * { scroll-snap-align: start; }
.vv-board-col { width: var(--board-col-w, 300px); flex: none; display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.vv-board-col-body { display: flex; flex-direction: column; gap: var(--space-2); min-height: calc(var(--row-h) + var(--space-4)); }
.vv-board-head { display: flex; align-items: center; gap: var(--space-2); position: sticky; top: 0; z-index: 1; background: var(--surface); padding-block: var(--space-1); }
.vv-board-title { font-weight: 600; color: var(--ink); }
.vv-board-count { flex: none; }
.vv-board-add { margin-inline-start: auto; opacity: 0; transition: opacity var(--dur-fast) var(--ease-out); }
.vv-board-col:hover .vv-board-add, .vv-board-head:focus-within .vv-board-add { opacity: 1; }
[data-density="touch"] .vv-board-add { opacity: 1; }
.vv-board-collapse { appearance: none; background: transparent; border: 0; margin: 0; padding: 0; font: inherit; color: var(--ink-muted); cursor: pointer; display: inline-flex; border-radius: var(--radius-sm); }
.vv-board-collapse:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
/* Objetivo táctil 48×48 en touch: el control visual es de 16px, la zona de toque es --row-h (48). */
[data-density="touch"] .vv-board-collapse { width: 48px; height: 48px; align-items: center; justify-content: center; }
@media (any-pointer: coarse) { .vv-board-collapse { width: 48px; height: 48px; align-items: center; justify-content: center; } }
.vv-board-collapse-icon { width: 16px; height: 16px; transform: rotate(0deg); transition: transform var(--dur-fast) var(--ease-out); }
.vv-board-col--collapsed .vv-board-collapse-icon { transform: rotate(-90deg); }

/* Tarjeta real (D-Board-01): superficie de `.vv-card`; `.vv-datarow` anidado pierde su propio
   cromo de fila (antes: borde inferior + hover propio hacían que el título/subtítulo pareciera una
   fila de tabla con los chips/meta «colgando» fuera). El título envuelve hasta 3 líneas en vez de
   truncar en 1 — igual que Linear/Trello, un board no es una lista densa. */
.vv-board-card { position: relative; }
.vv-board-card:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.vv-board-card .vv-datarow { border-bottom: 0; min-height: 0; padding: 0; align-items: start; background: transparent; }
.vv-board-card .vv-datarow:hover { background: transparent; }
.vv-board-card .vv-datarow-title { white-space: normal; overflow: hidden; text-overflow: clip; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; }
.vv-board-card-body { display: flex; flex-direction: column; gap: var(--space-2); }
.vv-board-chips { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.vv-board-meta { display: flex; align-items: center; gap: var(--space-1); color: var(--ink-muted); font-size: var(--text-compact); }
.vv-board-meta-icon { width: 12px; height: 12px; flex: none; }
.vv-board-move { position: absolute; inset-block-start: var(--space-1); inset-inline-end: var(--space-1); opacity: 0; transition: opacity var(--dur-fast) var(--ease-out); }
.vv-board-card:hover .vv-board-move, .vv-board-card:focus-within .vv-board-move { opacity: 1; }
[data-density="touch"] .vv-board-move { opacity: 1; }
.vv-board-card--grabbed { outline: 2px solid var(--primary); }
/* La tarjeta origen se atenúa mientras el navegador dibuja su propio «fantasma» nativo de arrastre
   (no hay forma de suprimir ese ghost sin una imagen de arrastre custom): atenuar el original evita
   la sensación de que se solapan dos tarjetas sólidas. */
.vv-board-card--dragging { opacity: .45; }
.vv-board-card--locked { opacity: .72; }

/* Objetivo de suelta (D-Board-02): la columna se resalta al pasar por encima; si no puede recibir
   (wip lleno), el aro cambia a `--danger` y el cursor lo dice — nunca un soltado silencioso. La
   línea de inserción marca dónde caería la tarjeta dentro de la columna (entre las existentes). */
.vv-board-col--target { border-radius: var(--radius-lg); box-shadow: inset 0 0 0 2px var(--primary); }
.vv-board-col--target-blocked { border-radius: var(--radius-lg); box-shadow: inset 0 0 0 2px var(--danger); cursor: not-allowed; }
/* 2px = `border.width.selected` de Atlassian Design Tokens, el mismo grosor que usa
   `@atlaskit/pragmatic-drag-and-drop-react-drop-indicator` para su línea de inserción
   (packages/react-drop-indicator/src/internal/line.tsx, `--stroke-width`). */
.vv-board-drop-line { height: 2px; margin: 0 var(--space-1); border-radius: var(--radius-full); background: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }

/* Alto = --row-h-sm (misma escala secundaria que .vv-btn-sm: 32/28/40 según densidad, siempre
   múltiplo de 4 — con padding-driven auto-height salía a 34px, defecto de rejilla del auditor). En
   touch, el objetivo táctil se amplía a 48px por el mismo truco `--vv-hit-y` de .vv-btn-sm (caja
   visual queda en 40px, la zona de toque real crece con un ::before invisible). */
.vv-board-quickadd { display: flex; align-items: center; gap: var(--space-1); width: 100%; height: var(--row-h-sm); padding-inline: var(--space-2); border: 1px dashed transparent; border-radius: var(--radius-md); background: transparent; color: var(--ink-muted); font-size: var(--text-compact); cursor: pointer; position: relative; transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out); }
.vv-board-quickadd:hover { background: var(--surface-3); color: var(--ink); }
.vv-board-quickadd:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
[data-density="touch"] .vv-board-quickadd { --vv-hit-y: calc((var(--row-h-sm) - var(--row-touch)) / 2); }

.vv-board-col--collapsed { width: auto; }
.vv-board-col--collapsed .vv-board-col-body { display: none; }
.vv-board-col--collapsed .vv-board-head { writing-mode: vertical-rl; position: static; }
.vv-board-empty { display: flex; align-items: center; justify-content: center; min-height: calc(var(--row-h) + var(--space-4)); padding: var(--space-2); border: 1px dashed var(--border-subtle); border-radius: var(--radius-md); color: var(--ink-muted); font-size: var(--text-compact); }
.vv-board-stages { display: none; margin-bottom: var(--space-3); }
.vv-board-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
@container board (max-width: 599.98px) {
  .vv-board-scroll { flex-direction: column; overflow-x: visible; }
  .vv-board-col { width: 100%; }
  .vv-board-col--collapsed { width: 100%; }
  .vv-board-col--collapsed .vv-board-head { writing-mode: horizontal-tb; }
}
@container board (max-width: 479.98px) {
  .vv-board-stages { display: block; }
  .vv-board-col:not(.vv-board-col--selected) { display: none; }
}
@supports not (container-type: inline-size) {
  @media (max-width: 599.98px) {
    .vv-board-scroll { flex-direction: column; overflow-x: visible; }
    .vv-board-col { width: 100%; }
    .vv-board-col--collapsed { width: 100%; }
    .vv-board-col--collapsed .vv-board-head { writing-mode: horizontal-tb; }
  }
  @media (max-width: 479.98px) {
    .vv-board-stages { display: block; }
    .vv-board-col:not(.vv-board-col--selected) { display: none; }
  }
}

/* ---- ui ---- */
/* Vive UI — frag/ui.css. Bloque nuevo: Tabs, FilterChip/FilterBar, Progress, Stepper, Drawer,
   EmptyState, Tooltip. Loads after tokens.css + bundle.css (+ frag/*.css alfabético). */

/* Reglas transversales de este bloque:
   - Layout estable: ninguna transición aquí anima width/height/top/left; posición y tamaño usan
     transform (translate/scale) sobre --spring-spatial-*, color/opacidad usan --spring-effects-*.
   - Divulgación progresiva: menús, popovers y tooltips solo existen en el DOM cuando están abiertos.
   - Todo control interactivo respeta el foco visible de bundle.css y añade el suyo si su etiqueta
     no está ya cubierta por el selector :where(...) global. */

:where(.vv-tabs-tab, .vv-fchip, .vv-fchip-remove, .vv-fchip-menu-item, .vv-stepper-circle[type="button"], .vv-drawer, .vv-tooltip-wrap > *):focus-visible {
  outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px;
}

/* ===== Tabs ===== */
.vv-tabs { display: flex; flex-direction: column; min-width: 0; }
.vv-tabs-scroll {
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -ms-overflow-style: none;
  -webkit-mask-image: linear-gradient(to right, transparent 0, black 20px, black calc(100% - 20px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, black 20px, black calc(100% - 20px), transparent 100%);
}
.vv-tabs-scroll::-webkit-scrollbar { display: none; }
/* TB-02 (2.7.0, Gastos): un segmentado a fit-content no desplaza; la máscara lateral le desvanecía las puntas. */
.vv-tabs-segmented .vv-tabs-scroll { -webkit-mask-image: none; mask-image: none; }
.vv-tabs-list { position: relative; display: inline-flex; align-items: center; min-width: 100%; width: max-content; }

.vv-tabs-line .vv-tabs-list { border-bottom: 1px solid var(--border-subtle); }
.vv-tabs-line .vv-tabs-tab {
  position: relative; z-index: 1; display: inline-flex; align-items: center; gap: var(--space-2);
  height: var(--row-h); padding: 0 var(--space-4); border: 0; background: transparent; cursor: pointer;
  font: inherit; font-size: var(--text-base); font-weight: 500; line-height: 1; white-space: nowrap;
  color: var(--ink-muted); transition: color var(--dur-effects-fast) var(--ease-out);
}
.vv-tabs-line .vv-tabs-tab:hover { color: var(--ink); }
.vv-tabs-line .vv-tabs-tab-selected { color: var(--ink); }
.vv-tabs-line .vv-tabs-tab-icon { width: var(--icon); height: var(--icon); flex: none; }
.vv-tabs-tab-count {
  display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: var(--radius-full); background: var(--surface-2); color: var(--ink-muted); font-size: 10px; font-weight: 600;
}
.vv-tabs-line .vv-tabs-tab-selected .vv-tabs-tab-count { background: var(--primary-soft); color: var(--primary); }
.vv-tabs-indicator {
  position: absolute; left: 0; bottom: -1px; height: 2px; width: 1px; transform-origin: left center;
  background: var(--primary); border-radius: var(--radius-full);
  transition: transform var(--dur-spatial-default) var(--spring-spatial-default), width var(--dur-spatial-default) var(--spring-spatial-default);
}

/* TP-01: la .vv-tabs-pill se anclaba con `transform: translateX(L) scaleX(W)`. Ese scaleX no solo
   estiraba su box-shadow: al escalar una caja de `width: 1px`, el `border-radius` se aplana en el eje
   X y la píldora pierde las esquinas (se ve como un rectángulo de esquinas vivas), y con `top: 0` su
   realce blanco interior pisaba el borde del carril. Ahora se ancla con `translateX(L)` + `width: W`
   (sin escalar: mismo radio redondo de siempre, sombra entera) y nace alineada con la fila, con el
   mismo inset que el `padding` del carril. Misma corrección en el JS de `components/bundle.js`
   (React) y en la réplica sin React `components/vive-shell.js` (Astro). */
.vv-tabs-segmented .vv-tabs-list {
  --vv-tabs-inset: 2px;
  gap: var(--vv-tabs-inset); padding: var(--vv-tabs-inset); border-radius: var(--radius-full); background: var(--surface-2); border: 1px solid var(--border-subtle);
}
.vv-tabs-segmented .vv-tabs-tab {
  position: relative; z-index: 1; display: inline-flex; align-items: center; gap: var(--space-2);
  height: var(--row-h); padding: 0 var(--space-4); border: 0; background: transparent; cursor: pointer;
  font: inherit; font-size: var(--text-base); font-weight: 500; line-height: 1; white-space: nowrap; border-radius: var(--radius-full);
  color: var(--ink-muted); transition: color var(--dur-effects-fast) var(--ease-out);
}
.vv-tabs-segmented .vv-tabs-tab:hover { color: var(--ink); }
.vv-tabs-segmented .vv-tabs-tab-selected { color: var(--ink); }
.vv-tabs-segmented .vv-tabs-tab-icon { width: var(--icon); height: var(--icon); flex: none; }
.vv-tabs-pill {
  position: absolute; z-index: 0; top: var(--vv-tabs-inset, 2px); left: 0; width: 1px; height: var(--row-h); transform-origin: left center;
  background: var(--surface); border-radius: var(--radius-full); box-shadow: var(--shadow-sm);
  transition: transform var(--dur-spatial-default) var(--spring-spatial-default), width var(--dur-spatial-default) var(--spring-spatial-default);
}

/* ===== FilterChip / FilterBar ===== */
.vv-fchip-wrap { position: relative; display: inline-flex; align-items: center; flex: none; }
.vv-fchip {
  display: inline-flex; align-items: center; gap: var(--space-1); height: 28px; padding: 0 var(--space-3);
  border-radius: var(--radius-full); border: 1px solid var(--border); background: var(--surface); color: var(--ink-muted);
  font: inherit; font-size: 12px; font-weight: 500; white-space: nowrap; cursor: pointer; position: relative;
  transition: background-color var(--dur-effects-fast) var(--ease-out), border-color var(--dur-effects-fast) var(--ease-out), color var(--dur-effects-fast) var(--ease-out);
}
[data-density="touch"] .vv-fchip { height: 32px; }
/* FC-01 (2.7.0, Gastos): objetivo táctil 44 con puntero grueso sin cambiar la altura visual (área
   táctil ampliada, utilidad arriba, "Expanded touch hit-area" — no 48: `--vv-hit-y` es fijo, no
   depende de la densidad, así que a 28px de base da 44, no 48). FC-02: variante multilínea para
   textos largos (1–2 líneas). `position: relative` ahora incondicional (antes solo dentro de
   `pointer: coarse`): sin desplazamiento, sin efecto visual fuera de touch, necesario para que la
   utilidad compartida ancle el `::after` a este elemento y no a un ancestro posicionado. */
@media (pointer: coarse) { .vv-fchip { --vv-hit-y: -8px; } }
.vv-fchip-multiline { height: auto; min-height: 28px; padding-block: var(--space-1); white-space: normal; text-align: left; line-height: 16px; max-width: 240px; }
[data-density="touch"] .vv-fchip-multiline { min-height: 32px; }
.vv-fchip:hover { background: var(--surface-3); }
.vv-fchip-selected { background: var(--primary-soft); color: var(--primary); border-color: transparent; }
.vv-fchip-selected:hover { background: var(--primary-soft); }
.vv-fchip-icon { width: 14px; height: 14px; flex: none; }
.vv-fchip-checkslot {
  display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; flex: none; margin-right: -2px;
  overflow: hidden;
}
.vv-fchip-check {
  width: 14px !important; height: 14px !important; color: var(--primary);
  opacity: 0; transform: scale(0.5);
  transition: opacity var(--dur-effects-fast) var(--spring-effects-fast), transform var(--dur-spatial-fast) var(--spring-spatial-fast);
}
.vv-fchip-selected .vv-fchip-check { opacity: 1; transform: scale(1); margin-right: 0; }
.vv-fchip-selected .vv-fchip-checkslot { margin-right: 0; }
.vv-fchip-label { overflow: hidden; text-overflow: ellipsis; }
.vv-fchip-count {
  display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: var(--radius-full); background: var(--surface-2); color: var(--ink-muted); font-size: 10px; font-weight: 600; flex: none;
}
.vv-fchip-selected .vv-fchip-count { background: var(--surface); color: var(--primary); }
.vv-fchip-chevron { width: 12px !important; height: 12px !important; flex: none; color: var(--ink-faint); transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast); }
.vv-fchip[aria-expanded="true"] .vv-fchip-chevron { transform: rotate(180deg); }
.vv-fchip-remove {
  all: unset; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; margin-left: 2px; border-radius: var(--radius-full); color: var(--ink-faint); cursor: pointer;
  position: relative;
  transition: background-color var(--dur-effects-fast) var(--ease-out), color var(--dur-effects-fast) var(--ease-out);
}
.vv-fchip-remove:hover { background: var(--surface-3); color: var(--ink); }
.vv-fchip-remove :is(svg, .vv-icon) { width: 12px; height: 12px; }
/* FC-01b (auditoría móvil 2026-09-23): el chip padre ya amplía su propio hit-area con puntero
   grueso (ver FC-01 arriba), pero el botón de eliminar anidado (20×20) no lo heredaba — mismo
   patrón (utilidad arriba, "Expanded touch hit-area"), aplicado al hijo. FC-01 usa -8px porque el
   chip padre ya mide 32px en touch (32+16=48); el botón de eliminar parte de 20px, así que necesita
   -14px de inset para llegar a los mismos 48×48 (20+28=48) — un inset fijo copiado sin recalcular se
   habría quedado corto (36×36); por eso la utilidad no infiere el valor, cada sitio lo declara. */
@media (pointer: coarse) { .vv-fchip-remove { --vv-hit-x: -14px; --vv-hit-y: -14px; } }

.vv-fchip-menu {
  position: absolute; top: calc(100% + var(--space-1)); left: 0; width: 200px; z-index: 20;
  background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); box-shadow: var(--shadow-md);
  overflow: hidden; animation: vv-pop var(--dur-base) var(--ease-spring);
}
.vv-fchip-menu-list { list-style: none; margin: 0; padding: var(--space-1); max-height: 220px; overflow: auto; }
.vv-fchip-menu-item {
  display: flex; align-items: center; gap: var(--space-2); min-height: 32px; padding: 0 var(--space-2);
  border-radius: var(--radius-sm); color: var(--ink); font-size: 13px; font-weight: 400; cursor: pointer;
}
.vv-fchip-menu-item :is(svg, .vv-icon) { width: 14px; height: 14px; color: var(--ink-muted); }
.vv-fchip-menu-item:hover, .vv-fchip-menu-item[aria-selected="true"] { background: var(--surface-3); }
.vv-fchip-menu-check { margin-left: auto; width: 14px !important; height: 14px !important; color: var(--primary); }
.vv-fchip-menu-empty { padding: var(--space-3) var(--space-2); color: var(--ink-muted); font-size: 12px; }

.vv-filterbar { min-width: 0; }
.vv-filterbar-scroll {
  display: flex; align-items: center; gap: var(--space-2); overflow-x: auto; overflow-y: hidden; padding: 2px;
  scrollbar-width: none; -ms-overflow-style: none;
  -webkit-mask-image: linear-gradient(to right, black calc(100% - 20px), transparent 100%);
  mask-image: linear-gradient(to right, black calc(100% - 20px), transparent 100%);
}
.vv-filterbar-scroll::-webkit-scrollbar { display: none; }
.vv-filterbar-clear { flex: none; }

/* ===== Progress ===== */
.vv-progress-wrap { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.vv-progress { flex: 1 1 auto; min-width: 0; }
.vv-progress-track { display: flex; align-items: center; gap: 4px; }
.vv-progress-fill {
  flex: 1 1 auto; min-width: 0; height: 4px; border-radius: var(--radius-full); background: var(--surface-3); overflow: hidden; position: relative;
}
.vv-progress-md .vv-progress-fill { height: 8px; }
.vv-progress-bar {
  position: absolute; inset: 0; width: 100%; transform-origin: left center; border-radius: var(--radius-full);
  background: var(--primary); transition: transform var(--dur-spatial-default) var(--spring-spatial-default);
}
.vv-progress-indeterminate-bar {
  position: absolute; top: 0; bottom: 0; left: 0; width: 35%; border-radius: var(--radius-full); background: var(--primary);
  animation: vv-progress-sweep 1.6s var(--spring-spatial-slow) infinite alternate;
}
/* Recorre de un extremo a otro de la pista sin salir nunca del área visible (0% a ~100% del
   contenedor, ya que la barra mide 35% de ancho): así una captura en cualquier instante del bucle
   siempre muestra el indicador, en vez de arriesgarse a caer en un tramo fuera de pantalla. */
@keyframes vv-progress-sweep {
  0% { transform: translateX(0%); }
  100% { transform: translateX(185%); }
}
@media (prefers-reduced-motion: reduce) {
  .vv-progress-indeterminate-bar { animation: vv-m3e-pulse 1.6s ease-in-out infinite; transform: none; width: 100%; }
}
.vv-progress-stop { flex: none; width: 4px; height: 4px; border-radius: var(--radius-full); background: var(--primary); }
.vv-progress-value { flex: none; font-size: 11px; line-height: 16px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }

/* ===== Stepper ===== */
/* S-01 (auditoría móvil 2026-09-23): `container-type` va en el wrapper (`.vv-stepper-container`,
   ver bundle.js), no en `.vv-stepper` mismo — verificado con un repro mínimo que Chromium no
   aplica un `@container` a propiedades del propio elemento que declara el contenedor
   (`flex-direction` se queda en su valor base aunque el query SÍ dispara para descendientes);
   mismo motivo estructural que `.vv-drawer-scrim`, aunque la causa raíz ahí era otra (ver Drawer). */
.vv-stepper-container { container-type: inline-size; }
.vv-stepper { list-style: none; margin: 0; padding: 0; display: flex; }
.vv-stepper-step {
  --step-size: 24px; --step-fs: 12px;
  position: relative; flex: 1 1 0; display: flex; flex-direction: column; align-items: center; gap: var(--space-2); min-width: 0;
}
[data-density="compact"] .vv-stepper-step { --step-size: 20px; --step-fs: 11px; }
[data-density="touch"] .vv-stepper-step { --step-size: 28px; --step-fs: 13px; }
.vv-stepper-step:not(:first-child)::before {
  content: ""; position: absolute; top: calc(var(--step-size) / 2 - 1px); right: 50%; width: 100%; height: 2px; background: var(--border-subtle); z-index: 0;
}
.vv-stepper-step-done:not(:first-child)::before, .vv-stepper-step-current:not(:first-child)::before { background: var(--primary); }
.vv-stepper-circle {
  position: relative; z-index: 1; display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: var(--step-size, 24px); height: var(--step-size, 24px); border-radius: var(--radius-full); border: 2px solid var(--border-strong); background: var(--surface);
  font-size: var(--step-fs, 12px); font-weight: 600; color: var(--ink-muted); padding: 0; cursor: default;
  transition: background-color var(--dur-effects-fast) var(--ease-out), border-color var(--dur-effects-fast) var(--ease-out), color var(--dur-effects-fast) var(--ease-out),
    width var(--dur-spatial-default) var(--spring-spatial-default), height var(--dur-spatial-default) var(--spring-spatial-default);
}
.vv-stepper-circle :is(svg, .vv-icon) { width: 13px !important; height: 13px !important; }
.vv-stepper-circle-done { background: var(--primary); border-color: var(--primary); color: var(--on-primary); }
.vv-stepper-circle-current { border-color: var(--primary); color: var(--primary); }
button.vv-stepper-circle { cursor: pointer; }
button.vv-stepper-circle:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
/* S-03 (auditoría móvil 2026-09-23): solo el paso "done" con `onStepClick` se monta como
   `<button>` (los demás son `<span>`, no interactivos) — en touch mide 28px, área táctil ampliada
   hasta 48 sin tocar el círculo visual (utilidad arriba, "Expanded touch hit-area"), mismo patrón
   que Button sm/PanelToggle. */
[data-density="touch"] button.vv-stepper-circle {
  --vv-hit-x: calc((var(--step-size, 24px) - var(--row-touch)) / 2); --vv-hit-y: var(--vv-hit-x);
}
.vv-stepper-text { min-width: 0; max-width: 120px; text-align: center; }
.vv-stepper-label {
  font-size: 12px; font-weight: 500; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.vv-stepper-step-pending .vv-stepper-label { color: var(--ink-muted); }
.vv-stepper-desc { font-size: 11px; line-height: 16px; color: var(--ink-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.vv-stepper-vertical { flex-direction: column; align-items: stretch; gap: 0; }
.vv-stepper-vertical .vv-stepper-step {
  flex-direction: row; align-items: flex-start; text-align: left; gap: var(--space-3); min-height: 56px; flex: none;
}
.vv-stepper-vertical .vv-stepper-step:not(:first-child)::before {
  top: var(--step-size, 24px); right: auto; left: calc(var(--step-size, 24px) / 2 - 1px); width: 2px;
  height: calc(100% - var(--step-size, 24px) + var(--space-2, 8px));
  transform: translateY(-100%);
}
.vv-stepper-vertical .vv-stepper-text { max-width: none; text-align: left; padding-top: 2px; }
.vv-stepper-vertical .vv-stepper-label, .vv-stepper-vertical .vv-stepper-desc { white-space: normal; }

/* S-02 (auditoría móvil 2026-09-23): `orientation="vertical"` es un prop explícito que el
   consumidor tiene que recordar pasar — OnboardingFlow no lo hacía y el stepper horizontal de 4
   pasos solapaba descripciones ilegibles bajo 600px de contenedor (`.vv-stepper-text{max-width:
   120px}` truncaba con elipsis, pero el título/desc de un paso invadía visualmente el siguiente
   antes de llegar a ese límite). Mismo tratamiento de `.vv-stepper-vertical` disparado solo por
   ancho de contenedor — así ningún consumidor puede olvidarse de pedirlo, y `.vv-stepper-vertical`
   (el prop explícito) sigue funcionando igual a cualquier ancho. */
@container (max-width: 599.98px) {
  .vv-stepper:not(.vv-stepper-vertical) { flex-direction: column; align-items: stretch; gap: 0; }
  .vv-stepper:not(.vv-stepper-vertical) .vv-stepper-step {
    flex-direction: row; align-items: flex-start; text-align: left; gap: var(--space-3); min-height: 56px; flex: none;
  }
  .vv-stepper:not(.vv-stepper-vertical) .vv-stepper-step:not(:first-child)::before {
    top: var(--step-size, 24px); right: auto; left: calc(var(--step-size, 24px) / 2 - 1px); width: 2px;
    height: calc(100% - var(--step-size, 24px) + var(--space-2, 8px));
    transform: translateY(-100%);
  }
  .vv-stepper:not(.vv-stepper-vertical) .vv-stepper-text { max-width: none; text-align: left; padding-top: 2px; }
  .vv-stepper:not(.vv-stepper-vertical) .vv-stepper-label, .vv-stepper:not(.vv-stepper-vertical) .vv-stepper-desc { white-space: normal; }
}
@supports not (container-type: inline-size) {
  @media (max-width: 599.98px) {
    .vv-stepper:not(.vv-stepper-vertical) { flex-direction: column; align-items: stretch; gap: 0; }
    .vv-stepper:not(.vv-stepper-vertical) .vv-stepper-step {
      flex-direction: row; align-items: flex-start; text-align: left; gap: var(--space-3); min-height: 56px; flex: none;
    }
    .vv-stepper:not(.vv-stepper-vertical) .vv-stepper-step:not(:first-child)::before {
      top: var(--step-size, 24px); right: auto; left: calc(var(--step-size, 24px) / 2 - 1px); width: 2px;
      height: calc(100% - var(--step-size, 24px) + var(--space-2, 8px));
      transform: translateY(-100%);
    }
    .vv-stepper:not(.vv-stepper-vertical) .vv-stepper-text { max-width: none; text-align: left; padding-top: 2px; }
    .vv-stepper:not(.vv-stepper-vertical) .vv-stepper-label, .vv-stepper:not(.vv-stepper-vertical) .vv-stepper-desc { white-space: normal; }
  }
}

/* ===== Drawer ===== */
.vv-drawer-scrim {
  position: fixed; inset: 0; z-index: 50; background: var(--scrim); display: flex;
  transition: background-color var(--dur-effects-default) var(--ease-out);
}
.vv-drawer-scrim-inline { position: relative; inset: auto; height: 100%; background: transparent; }
.vv-drawer-scrim-persistent { position: relative; inset: auto; height: 100%; background: transparent; }
.vv-drawer {
  width: var(--vv-drawer-w, var(--inspector-w)); /* resize */ max-width: 100%; flex: none; display: flex; flex-direction: column; min-height: 0;
  margin-left: auto; background: var(--surface); border-left: 1px solid var(--border-subtle); box-shadow: var(--shadow-lg);
  animation: vv-drawer-in-right var(--dur-spatial-default) var(--spring-spatial-default);
}
/* Nota: `container-type: inline-size` NO se pone en `.vv-drawer` mismo — su propio `width` depende
   de `var(--vv-drawer-w, var(--inspector-w))`, y un elemento no puede ser contenedor de tamaño de
   sí mismo sin crear una dependencia circular (Chromium la resuelve colapsando el ancho a 0, un
   regresión real verificada al probarlo). El footer se apila con un `@media` de viewport (fallback
   de abajo) en vez de un `@container` propio; sigue sin desbordar en cualquier caso porque
   `.vv-drawer-footer` ya envuelve (`flex-wrap`) con botones a tamaño natural, sin encogerse. */
@media (max-width: 479.98px) {
  .vv-drawer-footer { flex-direction: column; align-items: stretch; }
  .vv-drawer-footer > .vv-btn { flex: none; width: 100%; }
}
.vv-drawer-left { margin-left: 0; margin-right: auto; border-left: 0; border-right: 1px solid var(--border-subtle); animation-name: vv-drawer-in-left; }
.vv-drawer-lg { width: 480px; }
.vv-drawer-persistent { animation: none; box-shadow: none; }
@keyframes vv-drawer-in-right { from { transform: translateX(100%); } to { transform: translateX(0); } }
@keyframes vv-drawer-in-left { from { transform: translateX(-100%); } to { transform: translateX(0); } }
.vv-drawer-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); min-height: var(--row-h); padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border-subtle); flex: none; }
.vv-drawer-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-drawer-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-4); }
.vv-drawer-footer { flex: none; display: flex; flex-wrap: wrap; min-width: 0; align-items: center; gap: var(--space-2); padding: var(--space-3) var(--space-4); border-top: 1px solid var(--border-subtle); background: var(--surface); }
[data-density="compact"] .vv-drawer-head, [data-density="compact"] .vv-drawer-footer { padding: var(--space-2) var(--space-3); }
[data-density="touch"] .vv-drawer-head, [data-density="touch"] .vv-drawer-footer { padding: var(--space-4) var(--space-5); }
[data-density="compact"] .vv-drawer-body { padding: var(--space-3); gap: var(--space-3); }
[data-density="touch"] .vv-drawer-body { padding: var(--space-5); gap: var(--space-5); }

@media (prefers-reduced-motion: reduce) {
  .vv-drawer { animation: none; }
}

/* D-01 (auditoría móvil 2026-09-23): esto era un `@container` que dependía de que el CONSUMIDOR
   envolviera el Drawer en un ancestro con `container-type: inline-size` dimensionado por el hueco
   real de pantalla — el propio preview del componente demuestra el fallo: su wrapper `#root` es
   `flex: none` (se encoge a su contenido, el propio Drawer de 400/480px), así que el query nunca
   medía menos de 600px sea cual sea el viewport real. `@media` lee el viewport físico sin depender
   de ese wiring — funciona igual en overlay y en "push/inline", en este componente y en cualquier
   consumidor futuro. En compact, "push/inline" tampoco tiene ya sitio para convivir con el lienzo
   (M3-05: compact = 1 panel) — pasa a comportarse como el overlay por defecto (scrim fijo a toda
   pantalla) en vez de intentar «empujar» dentro de una columna que no existe a este ancho. */
@media (max-width: 599.98px) {
  .vv-drawer-scrim:not(.vv-drawer-scrim-persistent) { align-items: flex-end; justify-content: center; }
  .vv-drawer-scrim-inline { position: fixed; inset: 0; height: auto; background: var(--scrim); }
  .vv-drawer-scrim:not(.vv-drawer-scrim-persistent) .vv-drawer {
    width: 100%; max-width: 640px; margin: 0 auto; height: 90%; max-height: 90%;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0; border-left: 0; border-right: 0;
    border-top: 1px solid var(--border-subtle);
    animation-name: vv-drawer-in-bottom;
  }
}
@keyframes vv-drawer-in-bottom { from { transform: translateY(100%); } to { transform: translateY(0); } }

/* ===== EmptyState ===== */
.vv-emptystate { display: flex; flex-direction: column; align-items: center; gap: var(--space-3); padding: var(--space-8) var(--space-4); text-align: center; }
.vv-emptystate-shape { margin-bottom: var(--space-1); }
.vv-emptystate-shape .vv-shape-inner, .vv-emptystate-shape .vv-shape-inner :is(svg, .vv-icon) { color: var(--ink-faint) !important; }
.vv-emptystate-danger .vv-emptystate-shape .vv-shape-inner, .vv-emptystate-danger .vv-emptystate-shape .vv-shape-inner :is(svg, .vv-icon) { color: var(--danger) !important; }
.vv-emptystate-icon-sm, .vv-emptystate-icon-sm :is(svg, .vv-icon) { width: 20px !important; height: 20px !important; color: var(--ink-faint); }
.vv-emptystate-danger .vv-emptystate-icon-sm { color: var(--danger); }
.vv-emptystate-title { font-size: 15px; line-height: 1.3; font-weight: 600; color: var(--ink); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-emptystate-desc { margin: 0; max-width: 360px; font-size: var(--text-base); line-height: 1.5; color: var(--ink-muted); }
.vv-emptystate-actions { display: flex; flex-wrap: wrap; min-width: 0; align-items: center; gap: var(--space-2); margin-top: var(--space-1); }
.vv-emptystate-sm { padding: var(--space-4); gap: var(--space-2); }
.vv-emptystate-sm .vv-emptystate-title { font-size: var(--text-base); }
.vv-emptystate-sm .vv-emptystate-desc { font-size: 12px; }

/* ===== Tooltip ===== */
.vv-tooltip-wrap { position: relative; display: inline-flex; }
.vv-tooltip {
  position: absolute; z-index: 40; display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm); background: var(--ink); color: var(--canvas);
  font-size: 12px; font-weight: 500; line-height: 16px; white-space: nowrap; box-shadow: var(--shadow-md);
  pointer-events: none;
  animation: vv-tooltip-in var(--dur-effects-fast) var(--spring-effects-fast);
}
@keyframes vv-tooltip-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
.vv-tooltip-top { bottom: calc(100% + var(--space-2)); left: 50%; transform: translateX(-50%); }
.vv-tooltip-bottom { top: calc(100% + var(--space-2)); left: 50%; transform: translateX(-50%); }
.vv-tooltip-left { right: calc(100% + var(--space-2)); top: 50%; transform: translateY(-50%); }
.vv-tooltip-right { left: calc(100% + var(--space-2)); top: 50%; transform: translateY(-50%); }
.vv-tooltip-kbd { font-family: var(--font-mono); font-size: 10px; line-height: 14px; padding: 0 4px; border-radius: 3px; border: 1px solid var(--canvas); color: var(--canvas); opacity: .8; }
.vv-tooltip-arrow { position: absolute; width: 6px; height: 6px; background: var(--ink); transform: rotate(45deg); }
.vv-tooltip-top .vv-tooltip-arrow { bottom: -3px; left: calc(50% - 3px); }
.vv-tooltip-bottom .vv-tooltip-arrow { top: -3px; left: calc(50% - 3px); }
.vv-tooltip-left .vv-tooltip-arrow { right: -3px; top: calc(50% - 3px); }
.vv-tooltip-right .vv-tooltip-arrow { left: -3px; top: calc(50% - 3px); }
@media (prefers-reduced-motion: reduce) {
  .vv-tooltip { animation: none; }
}

/* ---- feedback ---- */
/* Vive UI — frag/feedback.css. StackedBar, Skeleton, Toast, ConfirmDialog. Loads after bundle.css. */

/* ===== StackedBar ===== */
.vv-stackedbar { display: flex; flex-direction: column; gap: var(--space-2); }
.vv-stackedbar-track {
  display: flex; align-items: stretch; gap: 2px; height: 8px;
  border-radius: var(--radius-full); overflow: hidden; background: var(--surface-3);
}
.vv-stackedbar-sm .vv-stackedbar-track { height: 4px; }
.vv-stackedbar-lg .vv-stackedbar-track { height: 12px; }
.vv-stackedbar-seg {
  min-width: 2px; flex-shrink: 0;
  transition: flex-grow var(--dur-spatial-default) var(--spring-spatial-default);
}
.vv-stackedbar-rest { background: var(--surface-3); }
.vv-stackedbar-legend {
  display: flex; align-items: center; gap: var(--space-4);
  overflow-x: auto; padding-bottom: 2px; scrollbar-width: thin;
}
.vv-stackedbar-legend-item {
  display: flex; align-items: center; gap: var(--space-1); flex: none; white-space: nowrap;
  font-size: 11px; line-height: 16px; color: var(--ink-muted);
}
.vv-stackedbar-dot { width: 8px; height: 8px; border-radius: var(--radius-full); flex: none; }
.vv-stackedbar-legend-label { color: var(--ink-muted); }
.vv-stackedbar-legend-value { color: var(--ink); font-weight: 500; font-variant-numeric: tabular-nums; }

/* ===== Skeleton ===== */
.vv-skeleton {
  background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-2) 50%, var(--surface-3) 75%);
  background-size: 200% 100%;
  animation: vv-skel-shimmer 1.6s linear infinite;
}
@keyframes vv-skel-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) {
  .vv-skeleton { animation: vv-skel-pulse 1.6s ease-in-out infinite; background-position: 0 0; }
}
@keyframes vv-skel-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }

.vv-skeleton-text { display: block; width: 100%; height: var(--text-base); border-radius: var(--radius-sm); }
.vv-skeleton-circle { flex: none; width: 32px; height: 32px; border-radius: var(--radius-full); }
.vv-skeleton-rect { width: 100%; height: 80px; border-radius: var(--radius-md); }
.vv-skeleton-row { width: 100%; height: var(--row-h); border-radius: var(--radius-md); }
.vv-skeleton-card { width: 100%; height: 120px; border-radius: var(--radius-lg); }

.vv-skeleton-row-wrap { display: flex; align-items: center; gap: var(--space-3); height: var(--row-h); padding: 0 var(--space-4); }
.vv-skeleton-row-grow { flex: 0 1 auto; min-width: 0; max-width: 40%; }
.vv-skeleton-row-chip { flex: none; width: 56px; height: 20px; border-radius: var(--radius-sm); margin-left: auto; }
.vv-skeleton-row-date { flex: none; width: 52px; }

.vv-skeleton-table { display: flex; flex-direction: column; border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); overflow: hidden; background: var(--surface); }
.vv-skeleton-table-row { display: flex; align-items: center; gap: var(--space-4); height: var(--row-h); padding: 0 var(--space-4); border-bottom: 1px solid var(--border-subtle); }
.vv-skeleton-table-row:last-child { border-bottom: 0; }
.vv-skeleton-table-head { background: var(--surface-2); }
/* SK-01 (2.7.0): una fila mixta (avatar + texto + importe) mantiene sus tamaños fijos; solo el texto crece. */
.vv-skeleton-table-row > .vv-skeleton:not(.vv-skeleton-circle):not(.vv-skeleton-row-chip):not(.vv-skeleton-row-date) { flex: 1 1 0; min-width: 0; }

/* ===== Toast ===== */
.vv-toaster {
  position: fixed; z-index: 60; display: flex; flex-direction: column; gap: var(--space-2);
  width: 360px; max-width: calc(100% - (var(--space-4) * 2)); pointer-events: none;
}
.vv-toaster > .vv-toast { pointer-events: auto; }
.vv-toaster-bottom-right { right: var(--space-4); bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px)); align-items: flex-end; }
.vv-toaster-bottom-center { left: 50%; transform: translateX(-50%); bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px)); align-items: center; }
.vv-toaster-top-right { right: var(--space-4); top: var(--space-4); align-items: flex-end; }
.vv-toaster-inline { position: relative; inset: auto; right: auto; left: auto; top: auto; bottom: auto; transform: none; width: 360px; max-width: 100%; }

/* Contención (2.4.1): `:not(.vv-toaster-inline)` en las dos reglas de abajo — sin él, este `bottom`
   de despeje del dock móvil ganaba en cascada a `.vv-toaster-inline` (declarada antes en el archivo,
   así que un empate de especificidad lo pierde) y desplazaba el toaster inline hacia ARRIBA de su
   contenedor (position: relative + bottom actúa como offset, no como ancla) — 88px de desborde real
   verificado con overflow.js en el propio catálogo, no solo un caso de demo. */
@media (max-width: 599.98px) {
  .vv-toaster-bottom-right:not(.vv-toaster-inline), .vv-toaster-bottom-center:not(.vv-toaster-inline) {
    bottom: calc(var(--dock-h) + var(--space-6) + env(safe-area-inset-bottom, 0px));
  }
}
[data-shell="mobile"] .vv-toaster-bottom-right:not(.vv-toaster-inline),
[data-shell="mobile"] .vv-toaster-bottom-center:not(.vv-toaster-inline) {
  bottom: calc(var(--dock-h) + var(--space-6) + env(safe-area-inset-bottom, 0px));
}

.vv-toast {
  display: flex; align-items: flex-start; gap: var(--space-3); width: 100%;
  padding: var(--space-3); background: var(--surface); color: var(--ink);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  animation: vv-toast-in var(--dur-spatial-default) var(--spring-spatial-default);
}
@keyframes vv-toast-in { from { transform: translateY(8px) scale(.98); opacity: 0; } to { transform: none; opacity: 1; } }
.vv-toast-leaving { animation: vv-toast-out var(--dur-effects-default) var(--spring-effects-default) forwards; pointer-events: none; }
@keyframes vv-toast-out { to { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .vv-toast { animation: none; }
  .vv-toast-leaving { animation: vv-toast-out var(--dur-effects-default) var(--spring-effects-default) forwards; }
}

.vv-toast-icon { flex: none; width: var(--icon-size); height: var(--icon-size); margin-top: 1px; }
.vv-toast-success .vv-toast-icon { color: var(--success); }
.vv-toast-warning .vv-toast-icon { color: var(--warning); }
.vv-toast-danger .vv-toast-icon { color: var(--danger); }
.vv-toast-neutral .vv-toast-icon { color: var(--ink-muted); }
.vv-toast-body { flex: 1 1 auto; min-width: 0; }
.vv-toast-title { font-size: 13px; line-height: 1.35; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-toast-desc { margin-top: 2px; font-size: 11px; line-height: 16px; color: var(--ink-muted); }
.vv-toast-action { flex: none; height: 28px; padding: 0 var(--space-2); font-size: 12px; position: relative; }
.vv-toast-close { flex: none; width: 28px; height: 28px; align-self: flex-start; position: relative; }
/* P3 (review-C, 2026-09-23): 28px, below touch minimum — kept visually small on purpose (a toast is
   transient, not a primary control — Material's own snackbar action is small text too), same hit-area
   utility (arriba, "Expanded touch hit-area") as the rest of the kit's small icon-buttons instead of
   growing the toast itself. */
@media (any-pointer: coarse) {
  .vv-toast-action, .vv-toast-close { --vv-hit-x: -10px; --vv-hit-y: -10px; }
}

/* ===== ConfirmDialog ===== */
.vv-confirm-scrim {
  position: fixed; inset: 0; z-index: 55; background: var(--scrim);
  display: flex; align-items: center; justify-content: center; padding: var(--space-4);
}
.vv-confirm-scrim-inline { position: relative; inset: auto; background: transparent; height: 100%; }
.vv-confirm {
  width: min(420px, 100%); min-width: min(280px, 100%); display: flex; flex-direction: column; gap: var(--space-2); container-type: inline-size;
  padding: var(--space-5); background: var(--surface); color: var(--ink);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-xl); box-shadow: var(--shadow-lg);
  animation: vv-confirm-in var(--dur-spatial-default) var(--spring-spatial-default);
}
/* CD-01 (auditoría móvil 2026-09-23): un `.vv-confirm-scrim-inline` dentro de un flex row sin
   `flex-wrap` (dos demos lado a lado, p.ej.) podía encoger el diálogo a ~155px — el título ya
   elide con «…» (`.vv-confirm-title`) pero por debajo de cierto ancho ni la etiqueta de un botón
   destructivo («Eliminar») cabía entera. min-width es la red de seguridad: un diálogo de
   confirmación nunca debería poder renderizar más estrecho que 280px, sea cual sea el layout del
   consumidor; sigue respetando `100%` en un viewport genuinamente más estrecho que eso. */
/* Contención: bajo 360px de diálogo, «Cancelar / Eliminar cliente» apilan a ancho completo con el
   primario/danger abajo, en vez de sobresalir del diálogo (defecto del brief de contención). */
@container (max-width: 359.98px) {
  .vv-confirm-actions { flex-direction: column; align-items: stretch; }
  .vv-confirm-actions > .vv-btn { flex: none; width: 100%; }
}
@supports not (container-type: inline-size) {
  @media (max-width: 359.98px) {
    .vv-confirm-actions { flex-direction: column; align-items: stretch; }
    .vv-confirm-actions > .vv-btn { flex: none; width: 100%; }
  }
}
@keyframes vv-confirm-in { from { transform: translateY(8px) scale(.98); opacity: 0; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .vv-confirm { animation: none; } }

.vv-confirm-icon-shape { margin-bottom: var(--space-1); }
.vv-confirm-icon-shape-danger .vv-shape-inner, .vv-confirm-icon-shape-danger .vv-shape-inner :is(svg, .vv-icon) { color: var(--danger) !important; }
.vv-confirm-icon-shape-neutral .vv-shape-inner, .vv-confirm-icon-shape-neutral .vv-shape-inner :is(svg, .vv-icon) { color: var(--primary) !important; }

.vv-confirm-title { margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-confirm-desc { margin: 0; color: var(--ink-muted); }
.vv-confirm-textconfirm { display: flex; flex-direction: column; gap: var(--space-1); margin-top: var(--space-1); }
.vv-confirm-textconfirm-hint { margin: 0; font-size: 12px; line-height: 16px; color: var(--ink-muted); }
.vv-confirm-textconfirm-hint strong { color: var(--ink); font-weight: 600; }
.vv-confirm-textconfirm .vv-field { min-width: 0; }

.vv-confirm-actions { display: flex; flex-wrap: wrap; min-width: 0; align-items: center; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-3); }

/* ---- picker ---- */
/* Vive UI — frag/picker.css. Primitivo Picker: popover de selección compartido por PropertyChip,
   FilterChip (dropdown) y Select (searchable/rich). Carga después de tokens.css + bundle.css. */

.vv-picker {
  display: flex; flex-direction: column; max-height: 320px; z-index: 30;
  background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md); overflow: hidden; color: var(--ink); font-size: var(--text-base);
  padding-top: var(--space-2); /* OV-01 (auditoría de espaciado 2026-09-28): igual que .vv-palette, evita que .vv-picker-search-input (Select buscable, FilterChip, PropertyChip) quede a 1px del borde superior. */
  animation: vv-pop var(--dur-base) var(--ease-spring);
}
.vv-picker-inline { position: relative; max-height: none; box-shadow: var(--shadow-sm); }

.vv-picker-search { position: relative; display: flex; align-items: center; flex: none; border-bottom: 1px solid var(--border-subtle); }
.vv-picker-search-icon { position: absolute; left: var(--space-3); width: 14px !important; height: 14px !important; color: var(--ink-faint); pointer-events: none; }
.vv-picker-search-input {
  box-sizing: border-box; width: 100%; height: 32px; padding: 0 var(--space-3) 0 calc(var(--space-3) + var(--space-4));
  font: inherit; font-size: 13px; border: 0; background: transparent; color: var(--ink); outline: none;
}
[data-density="touch"] .vv-picker-search-input { height: var(--row-h); font-size: var(--text-base); }
.vv-picker-search-input::placeholder { color: var(--ink-faint); }

.vv-picker-list { list-style: none; margin: 0; padding: var(--space-1); overflow: auto; flex: 1 1 auto; min-height: 0; }
.vv-picker-group { padding: var(--space-2) var(--space-2) var(--space-1); font-size: 11px; font-weight: 500; color: var(--ink-muted); }

.vv-picker-option {
  display: flex; align-items: center; gap: var(--space-2); min-height: max(32px, var(--row-h));
  padding: 0 var(--space-2); border-radius: var(--radius-sm); color: var(--ink); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.vv-picker-option:hover { background: var(--surface-3); }
.vv-picker-option-active { background: var(--primary-soft); }
.vv-picker-option-disabled { color: var(--ink-faint); cursor: not-allowed; }
.vv-picker-option-disabled:hover, .vv-picker-option-disabled.vv-picker-option-active { background: transparent; }

.vv-picker-option-icon { width: 14px; height: 14px; flex: none; color: var(--ink-muted); }
.vv-picker-avatar {
  width: 20px; height: 20px; border-radius: var(--radius-full); background: var(--primary-soft); color: var(--primary);
  display: flex; align-items: center; justify-content: center; font-size: 9px; font-weight: 600; flex: none;
}
.vv-picker-dot { width: 8px; height: 8px; border-radius: var(--radius-full); flex: none; }

.vv-picker-option-text { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.vv-picker-option-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-picker-option-desc {
  font-size: 11px; line-height: 14px; color: var(--ink-muted);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.vv-picker-check { margin-left: auto; width: 14px !important; height: 14px !important; flex: none; color: var(--primary); }

/* multi: checkbox reutilizando .vv-checkbox-box */
.vv-picker-checkbox { margin-top: 0; }
.vv-picker-checkbox-checked { background: var(--primary); border-color: var(--primary); }
.vv-picker-checkbox-checked .vv-checkbox-check { opacity: 1; }

/* clear / create rows */
.vv-picker-clear .vv-picker-option-icon { color: var(--ink-muted); }
.vv-picker-create .vv-picker-option-icon, .vv-picker-create .vv-picker-option-label { color: var(--primary); }

.vv-picker-empty { padding: var(--space-4) var(--space-2); text-align: center; color: var(--ink-muted); font-size: 12px; }

.vv-picker-foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex: none;
  padding: var(--space-2) var(--space-3); border-top: 1px solid var(--border-subtle); font-size: 12px; color: var(--ink-muted);
}
.vv-picker-foot-done {
  height: 28px; padding: 0 var(--space-3); border: 0; border-radius: var(--radius-md); background: var(--primary-soft);
  color: var(--primary); font: inherit; font-size: 12px; font-weight: 500; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
/* Objetivo táctil (auditoría de contención 2026-09-23): es la acción de confirmación del footer
   (cierra el picker) — crece a `--row-h` real en touch, igual que cualquier botón de acción
   primaria, en vez de un hit-area invisible; hay sobra de padding en `.vv-picker-foot` para los
   +20px sin tocar el borde recortado (`overflow:hidden`) de `.vv-picker`. */
[data-density="touch"] .vv-picker-foot-done { height: var(--row-h); }
.vv-picker-foot-done:hover { background: var(--primary); color: var(--on-primary); }

:where(.vv-picker-option, .vv-picker-foot-done):focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-picker-search-input:focus-visible { outline: none; }

/* Select: siempre el trigger del kit + Picker (owner 2026-09-28) — el <select> nativo del SO queda
   retirado; `.vv-select`/`.vv-select-wrap` se conservan porque otros consumidores del bundle (p. ej.
   el <select> sin estilo de DataTable.pageSize) siguen siendo HTML plano fuera de este componente. */
.vv-select-rich-trigger {
  width: 100%; height: var(--row-h); padding: 0 var(--space-8) 0 var(--space-3); display: flex; align-items: center; gap: var(--space-2);
  font: inherit; font-size: var(--text-base); border-radius: var(--radius-md); border: 1px solid var(--border);
  background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); cursor: pointer; text-align: left;
  transition: border-color var(--dur-fast) var(--ease-out), border-radius var(--dur-spring) var(--ease-spring);
}
.vv-select-rich-trigger:hover { border-color: var(--border-strong); }
.vv-select-rich-trigger[aria-expanded="true"] { border-color: var(--border-strong); border-radius: var(--radius-lg); }
.vv-select-rich-trigger:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-select-rich-trigger[aria-invalid="true"] { border-color: var(--danger); }
.vv-select-rich-trigger[disabled] { background: var(--surface-2); color: var(--ink-faint); cursor: not-allowed; box-shadow: none; }
.vv-select-rich-value { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-select-rich-placeholder { color: var(--ink-faint); }
.vv-select-rich-trigger .vv-picker-avatar { width: 18px; height: 18px; font-size: 8px; }

/* ---- core2 ---- */
/* Vive UI — frag/core2.css. Avatar/AvatarGroup, Menu, Banner, PageHeader, OtpInput, Money. */

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

/* ===== Avatar ===== */
.vv-avatar { position: relative; display: inline-flex; flex: none; border-radius: var(--radius-full); width: var(--avatar-size, 32px); height: var(--avatar-size, 32px); }
/* AV-01 (2.7.0): escala sin React (el bundle la fija con style); misma tabla que AVATAR_SIZES. */
.vv-avatar-xs { --avatar-size: 20px; } .vv-avatar-sm { --avatar-size: 24px; } .vv-avatar-md { --avatar-size: 32px; } .vv-avatar-lg { --avatar-size: 40px; } .vv-avatar-xl { --avatar-size: 56px; }
.vv-avatar-initials { font-size: calc(var(--avatar-size, 32px) * 0.4); }
.vv-avatar-inner {
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  border-radius: var(--radius-full); width: 100%; height: 100%;
}
.vv-avatar-cookie { border-radius: 0; }
.vv-avatar-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vv-avatar-initials { font-weight: 600; line-height: 1; user-select: none; }
.vv-avatar-status {
  position: absolute; right: -1px; bottom: -1px; width: 8px; height: 8px; border-radius: var(--radius-full);
  border: 2px solid var(--surface); box-sizing: content-box;
}
.vv-avatar-status-online { background: var(--success); }
.vv-avatar-status-offline { background: var(--ink-faint); }

.vv-avatar-group { display: inline-flex; align-items: center; }
.vv-avatar-group-item { position: relative; display: inline-flex; margin-left: -8px; border-radius: var(--radius-full); border: 2px solid var(--surface); }
.vv-avatar-group-item:first-child { margin-left: 0; }
.vv-avatar-group-item .vv-avatar { display: flex; }
.vv-avatar-more {
  display: flex; align-items: center; justify-content: center; background: var(--surface-2); color: var(--ink-muted);
  font-size: 11px; font-weight: 600; box-sizing: border-box;
}

/* ===== Menu ===== */
.vv-menu-ctx-zone { display: contents; }
.vv-menu-trigger-wrap { position: relative; display: inline-flex; }
/* Anclaje sin React (2.8.0, feedback esaem PR #120 T04c): en React el popover se posiciona en JS
   (V._popover.computePosition midiendo el trigger) y se porta a document.body, fuera de este wrap;
   una réplica estática (Astro/Svelte/HTML) no porta nada, así que .vv-menu/.vv-accountmenu
   viven dentro del propio .vv-menu-trigger-wrap y necesitan su propio anclaje CSS. Combinador hijo
   directo a propósito: no toca el .vv-menu portado de React (nunca es hijo directo de este wrap). */
.vv-menu-trigger-wrap > .vv-menu { position: absolute; z-index: 30; inset-block-start: calc(100% + var(--space-1)); inset-inline-end: 0; }
.vv-menu {
  display: flex; flex-direction: column; min-width: 200px; max-height: 360px; z-index: 30;
  background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md); color: var(--ink); font-size: var(--text-base);
  padding-top: var(--space-2); /* OV-01 (auditoría de espaciado 2026-09-28): mismo fallo que .vv-palette/.vv-picker; AccountMenu (.vv-accountmenu.vv-menu) hereda el arreglo gratis. */
  animation: vv-pop var(--dur-base) var(--ease-spring);
}
/* overflow:hidden vivía en .vv-menu (tribunal #158, P0): .vv-accountmenu-sub es hijo directo de
   .vv-accountmenu.vv-menu y se posiciona FUERA del panel (inset-inline-start: calc(100% + ...)) —
   heredaba el clip de su propio ancestro y se recortaba a la mitad («Roles» se veía «oles»). Ahora
   el clip vive en .vv-menu-list (min-height:0 + flex para que su propio overflow:auto sí limite el
   alto dentro del max-height:360px del panel, en vez de depender del overflow:hidden del padre) —
   nada dentro de .vv-menu toca el borde redondeado del panel sin el padding de .vv-menu-list, así
   que las esquinas siguen limpias. */
.vv-menu-list { list-style: none; margin: 0; padding: var(--space-1); overflow: auto; flex: 1 1 auto; min-height: 0; }
.vv-menu-group { padding: var(--space-2) var(--space-2) var(--space-1); font-size: 11px; font-weight: 500; color: var(--ink-muted); }
.vv-menu-divider { height: 1px; margin: var(--space-1) var(--space-2); background: var(--border-subtle); list-style: none; }
.vv-menu-item {
  display: flex; align-items: center; gap: var(--space-2); min-height: max(32px, var(--row-h));
  padding: 0 var(--space-2); border-radius: var(--radius-sm); color: var(--ink); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.vv-menu-item:hover { background: var(--surface-3); }
.vv-menu-item-active { background: var(--primary-soft); }
.vv-menu-item-disabled { color: var(--ink-faint); cursor: not-allowed; }
.vv-menu-item-disabled:hover, .vv-menu-item-disabled.vv-menu-item-active { background: transparent; }
.vv-menu-item-icon { width: 14px; height: 14px; flex: none; color: var(--ink-muted); }
.vv-menu-item-check { width: 14px; height: 14px; flex: none; color: var(--primary); }
.vv-menu-item-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-menu-item .vv-kbd { flex: none; }
/* MEN-01 (2.7.0, Gastos): item de dos líneas (título + descripción), el patrón de Picker option-text
   aplicado a Menu; el bloque de texto es .vv-menu-item-text con -label y -desc. */
.vv-menu-item-stack { align-items: flex-start; min-height: auto; padding-block: var(--space-2); }
.vv-menu-item-stack .vv-menu-item-icon, .vv-menu-item-stack .vv-menu-item-check { margin-top: 3px; }
.vv-menu-item-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; gap: 1px; }
.vv-menu-item-desc { font-size: 11px; line-height: 16px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-menu-item-danger { color: var(--danger); }
.vv-menu-item-danger .vv-menu-item-icon { color: var(--danger); }
.vv-menu-item-danger:hover, .vv-menu-item-danger.vv-menu-item-active { background: var(--danger-soft); }
.vv-menu-item:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
/* Cabecera opcional de Menu (prop `header`) — Consolidación 2.3.0: misma regla que .vv-accountmenu-head,
   así AccountMenu y cualquier Menu con cabecera comparten un único bloque visual. */
.vv-menu-header, .vv-accountmenu-head { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3); }
.vv-menu-header-text, .vv-accountmenu-headtext { display: flex; flex-direction: column; min-width: 0; gap: 2px; }
.vv-menu-header-text .vv-text-heading, .vv-menu-header-text .vv-text-caption,
.vv-accountmenu-headtext .vv-text-heading, .vv-accountmenu-headtext .vv-text-caption { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ===== Menu / Picker popover → centered dialog under 600px, without JS (3.0.0) =====
   At runtime V.Menu/V.Picker portal to document.body and swap to `V.Sheet presentation="dialog"`
   under a 600px container (see V._popover) — that decision stays in JS, not touched here. This rule
   gives the same look to *static* `.vv-menu`/`.vv-picker` markup (Astro, Svelte, plain HTML) that
   never portals, so it just sits in the page's own flow: same unnamed-@container pattern as
   `.vv-grouped-chips` above — it fires off the nearest ancestor with `container-type: inline-size`
   (a `.vv-shell-container`, a `.vv-sheet-scrim`, or any wrapper the consumer adds), whatever width
   that ancestor is at.
   Hasta 2.13.x esto era una hoja inferior con asa ("Sheet under 600px" en el CHANGELOG); desde
   3.0.0 ("móvil nativo", owner 2026-09-28) las listas de elección cortas y los menús SIEMPRE son un
   diálogo centrado, nunca una hoja inferior — mismo patrón que `.vv-sheet-dialog`. Referente:
   m3.material.io/components/menus/guidelines. */
@container (max-width: 599.98px) {
  .vv-menu, .vv-picker:not(.vv-picker-inline) {
    position: fixed; inset: 0; margin: auto; z-index: 50;
    width: min(92vw, 400px); max-width: none; min-width: 0; height: max-content; max-height: 80vh;
    border-radius: var(--radius-xl); border: 1px solid var(--border-subtle);
    box-shadow: var(--shadow-lg);
    animation: vv-sheet-in-desktop var(--dur-spatial-default) var(--spring-spatial-default);
  }
}
@supports not (container-type: inline-size) {
  @media (max-width: 599.98px) {
    .vv-menu, .vv-picker:not(.vv-picker-inline) {
      position: fixed; inset: 0; margin: auto; z-index: 50;
      width: min(92vw, 400px); max-width: none; min-width: 0; height: max-content; max-height: 80vh;
      border-radius: var(--radius-xl); border: 1px solid var(--border-subtle);
      box-shadow: var(--shadow-lg);
      animation: vv-sheet-in-desktop var(--dur-spatial-default) var(--spring-spatial-default);
    }
  }
}
@media (prefers-reduced-motion: reduce) {
  .vv-menu, .vv-picker { animation: none; }
}

/* ===== Banner ===== */
.vv-banner { display: flex; align-items: flex-start; gap: var(--space-3); color: var(--ink); }
.vv-banner-icon { width: var(--icon); height: var(--icon); flex: none; margin-top: 1px; }
.vv-banner-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.vv-banner-title { font-weight: 600; font-size: var(--text-base); }
.vv-banner-body { font-size: var(--text-base); color: inherit; opacity: .92; }
.vv-banner-action { flex: none; }
.vv-banner-dismiss { flex: none; }

.vv-banner-inline {
  padding: var(--space-4); border-radius: var(--radius-lg); border: 1px solid var(--border-subtle);
}
.vv-banner-bar {
  padding: 0 var(--space-4); height: var(--row-h); align-items: center; border-radius: 0;
}
.vv-banner-bar .vv-banner-text { flex-direction: row; align-items: baseline; gap: var(--space-2); overflow: hidden; white-space: nowrap; }
.vv-banner-bar .vv-banner-title, .vv-banner-bar .vv-banner-body { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.vv-banner-neutral { background: var(--surface-2); color: var(--ink); }
.vv-banner-neutral .vv-banner-icon { color: var(--ink-muted); }
.vv-banner-info { background: var(--primary-soft); color: var(--primary); }
.vv-banner-success { background: var(--success-soft); color: var(--success); }
.vv-banner-warning { background: var(--warning-soft); color: var(--warning); }
.vv-banner-danger { background: var(--danger-soft); color: var(--danger); }
.vv-banner-info .vv-banner-title, .vv-banner-success .vv-banner-title, .vv-banner-warning .vv-banner-title, .vv-banner-danger .vv-banner-title { color: inherit; }
.vv-banner .vv-btn-quiet.vv-banner-action { color: inherit; border-color: currentColor; opacity: .9; }
.vv-banner .vv-btn-quiet.vv-banner-action:hover { opacity: 1; background: var(--surface); }
.vv-banner .vv-btn-ghost.vv-banner-dismiss { color: inherit; opacity: .8; }
.vv-banner .vv-btn-ghost.vv-banner-dismiss:hover { opacity: 1; }

/* ===== PageHeader ===== */
.vv-pageheader-cq { container-type: inline-size; container-name: pageheader; }
.vv-pageheader-sentinel { height: 1px; }
.vv-pageheader { background: var(--surface); }
.vv-pageheader-sticky { position: sticky; top: 0; z-index: 10; border-bottom: 1px solid transparent; transition: border-color var(--dur-fast) var(--ease-out); }
.vv-pageheader-sticky.is-stuck { border-bottom-color: var(--border-subtle); }
.vv-pageheader-row { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-4) var(--page-gutter); min-height: calc(var(--row-h) + 2 * var(--space-2)); box-sizing: border-box; }
.vv-pageheader-back { flex: none; margin-top: 2px; }
/* avatar (2.13.0, §2): nodo opcional a la izquierda del título; sin él, ningún elemento nuevo en
   el DOM y la fila mide igual que en 2.12.0. */
.vv-pageheader-avatar { flex: none; align-self: center; display: flex; }
.vv-pageheader-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.vv-pageheader-crumbs { display: flex; align-items: center; gap: 2px; font-size: 12px; color: var(--ink-muted); overflow: hidden; white-space: nowrap; }
.vv-pageheader-crumb { color: var(--ink-muted); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
.vv-pageheader-crumb:hover { color: var(--ink); text-decoration: underline; }
.vv-pageheader-crumb-current { color: var(--ink); font-weight: 500; overflow: hidden; text-overflow: ellipsis; }
.vv-pageheader-crumb-sep { width: 12px !important; height: 12px !important; flex: none; color: var(--ink-faint); }
.vv-pageheader-titlewrap { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.vv-pageheader-title {
  margin: 0; font-family: var(--font-display); font-size: 28px; line-height: 34px; font-weight: 600; letter-spacing: -0.015em;
  color: var(--ink); flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* El título tiene prioridad sobre el estado cuando no cabe todo: el estado se deja encoger (con su propio
   elipsis) en vez de reservarse siempre su ancho completo y dejar el título en 0. */
.vv-pageheader-status { flex: 0 1 auto; min-width: 0; max-width: 45%; overflow: hidden; }
.vv-pageheader-status > * { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-pageheader-subtitle { margin: 0; font-size: 11px; line-height: 16px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-pageheader-actions { display: flex; flex-wrap: wrap; min-width: 0; align-items: center; gap: var(--space-2); flex: none; }
.vv-pageheader-tabs { padding: 0 var(--page-gutter); border-top: 1px solid var(--border-subtle); }
.vv-pageheader-tabs .vv-tabs { margin-top: 0; }
/* El botón de cada pestaña lleva su propio padding horizontal (space-4, objetivo táctil) que el
   título de arriba no tiene: sin este ajuste el texto de la pestaña empieza 16px más a la derecha
   que el título/breadcrumbs, aunque las cajas compartan el mismo gutter. Se retrae el área de scroll
   ese mismo padding para que el texto — no el hit-box — alinee con la línea de arriba. */
.vv-pageheader-tabs .vv-tabs-scroll { margin-left: calc(-1 * var(--space-4)); }

@container pageheader (width < 600px) {
  .vv-pageheader-cq { --page-gutter: var(--space-4); }
  .vv-pageheader-row { padding: var(--space-3) var(--page-gutter); }
  .vv-pageheader-title { font-size: 20px; line-height: 28px; }
  .vv-pageheader-crumbs, .vv-pageheader-subtitle, .vv-pageheader-status { display: none; }
  .vv-pageheader-tabs { padding: 0 var(--page-gutter); }
}
/* Degradación sin React del paso a «…» (finding esaem PR #120, 2.8.0): React mide con ResizeObserver
   (isNarrow < 600px de contenedor) y decide qué acciones secundarias quedan visibles; la réplica
   estática no puede medir, así que decide en build-time con dos marcados que conviven siempre en el
   DOM: hasta 2 botones .vv-pageheader-action-secondary (los primeros, visibles ≥600px) + un
   V.Menu «Más acciones» que SIEMPRE lleva el listado completo de acciones secundarias (no solo las
   que sobran) — clicar la copia inline o la del menú hace lo mismo. Bajo 600px los botones inline
   se ocultan y solo queda el primary + «…», igual que en React. Ver PageHeader/README.md §Réplica. */
@container pageheader (width < 600px) {
  .vv-pageheader-action-secondary { display: none; }
}

/* ===== OtpInput ===== */
.vv-otp { display: flex; flex-direction: column; gap: var(--space-2); }
.vv-otp-row { display: flex; align-items: center; gap: var(--space-2); }
.vv-otp-cell {
  box-sizing: border-box; width: max(var(--row-h), 40px); height: max(var(--row-h), 40px);
  text-align: center; font-family: var(--font-mono); font-size: 20px; font-variant-numeric: tabular-nums;
  border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); color: var(--ink);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.vv-otp-cell:hover { border-color: var(--border-strong); }
.vv-otp-cell:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; border-color: var(--border-strong); }
.vv-otp-cell-error { border-color: var(--danger); color: var(--danger); }
.vv-otp-sep { color: var(--ink-faint); font-family: var(--font-mono); }

/* ===== Money ===== */
.vv-money { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--ink); white-space: nowrap; }
.vv-money-sm { font-size: 12px; }
.vv-money-md { font-size: var(--text-base); }
.vv-money-lg { font-size: 15px; font-weight: 600; }
.vv-money-kpi { font-size: min(28px, 11cqi); line-height: 34px; font-weight: 600; letter-spacing: -0.015em; font-family: var(--font-display); }
.vv-money-cents { color: var(--ink-muted); }
.vv-money-danger { color: var(--danger); }
.vv-money-success { color: var(--success); }

/* ---- data2 ---- */
/* Vive UI — frag/data2.css. MediaCard, FileUpload, AuthPage. Loads after tokens.css + bundle.css. */

/* ===== MediaCard ===== */
.vv-mediacard {
  display: flex; flex-direction: column; min-width: 0; max-width: 100%; container-type: inline-size;
  border-radius: var(--radius-lg); overflow: hidden; border: 1px solid var(--border-subtle);
  background: var(--surface); color: var(--ink);
  transition: border-radius var(--dur-spring) var(--ease-spring), box-shadow var(--dur-base) var(--ease-out);
}
/* Contención: bajo 360px de tarjeta, «Compartir / Ver detalle» apilan a ancho completo con el
   primario abajo, en vez de desbordar la tarjeta por la izquierda (defecto del brief de contención). */
@container (max-width: 359.98px) {
  .vv-mediacard-actions { flex-direction: column; align-items: stretch; }
  .vv-mediacard-actions > .vv-btn { flex: none; width: 100%; }
}
@supports not (container-type: inline-size) {
  @media (max-width: 359.98px) {
    .vv-mediacard-actions { flex-direction: column; align-items: stretch; }
    .vv-mediacard-actions > .vv-btn { flex: none; width: 100%; }
  }
}
.vv-mediacard-interactive { cursor: pointer; }
.vv-mediacard-interactive:hover { border-radius: var(--radius-2xl); box-shadow: var(--shadow-md); }
.vv-mediacard-interactive:active { transform: scale(0.99); }
.vv-mediacard-interactive:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-mediacard-cover { position: relative; flex: none; background: var(--surface-3); overflow: hidden; }
.vv-mediacard-img { width: 100%; height: 100%; object-fit: cover; display: block; background: var(--surface-3); }
.vv-mediacard-color { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: var(--surface-2); }
.vv-mediacard-overlay { position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: flex-end; }
.vv-mediacard-scrim {
  position: absolute; inset: 0; background: linear-gradient(to top, var(--scrim), transparent 70%);
}
.vv-mediacard-overlay-content { position: relative; padding: var(--space-3); z-index: 1; }
.vv-mediacard-body { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-4); min-width: 0; }
[data-density="compact"] .vv-mediacard-body { padding: var(--space-3); gap: var(--space-1); }
[data-density="touch"] .vv-mediacard-body { padding: var(--space-5); }
.vv-mediacard-eyebrow { display: flex; align-items: center; gap: var(--space-2); font-size: 11px; line-height: 16px; color: var(--ink-muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-mediacard-title {
  margin: 0; font-size: 15px; line-height: 1.3; font-weight: 600; color: var(--ink);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  /* Exception to "no reserved space for text": the card's height is fixed by `aspect` + this 2-line clamp, so it never reflows once content is known. */
}
.vv-mediacard-meta { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.vv-mediacard-meta-item { display: inline-flex; align-items: center; gap: var(--space-1); font-size: 12px; color: var(--ink-muted); max-width: 100%; }
.vv-mediacard-meta-item :is(svg, .vv-icon) { width: 14px !important; height: 14px !important; flex: none; }
.vv-mediacard-actions { display: flex; flex-wrap: wrap; min-width: 0; align-items: center; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-1); }

.vv-mediacard-horizontal { flex-direction: row; }
.vv-mediacard-horizontal .vv-mediacard-cover { width: 120px; flex: none; }
.vv-mediacard-horizontal .vv-mediacard-body { flex: 1 1 auto; }

/* ===== FileUpload ===== */
.vv-fu { display: flex; flex-direction: column; gap: var(--space-3); }
.vv-fu-zone {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-2);
  padding: var(--space-6) var(--space-4); border: 1px dashed var(--border-strong); border-radius: var(--radius-lg);
  background: var(--surface); text-align: center; cursor: pointer; position: relative; box-sizing: border-box;
  transition: border-color var(--dur-effects-fast) var(--spring-effects-fast), background-color var(--dur-effects-fast) var(--spring-effects-fast);
}
.vv-fu-zone:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-fu-zone-drag { border-color: var(--primary); background: var(--primary-soft); }
.vv-fu-icon { width: 28px !important; height: 28px !important; color: var(--ink-muted); }
.vv-fu-text { margin: 0; font-size: var(--text-base); color: var(--ink); }
.vv-fu-explore { all: unset; color: var(--primary); cursor: pointer; font-weight: 500; }
.vv-fu-explore:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; border-radius: var(--radius-sm); }
.vv-fu-hint { margin: 0; font-size: 11px; line-height: 16px; color: var(--ink-muted); }
.vv-fu-input { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.vv-fu-list { display: flex; flex-direction: column; }
.vv-fu-row { display: flex; align-items: center; gap: var(--space-3); min-height: var(--row-h); border-bottom: 1px solid var(--border-subtle); flex-wrap: wrap; padding-block: var(--space-1); }
.vv-fu-row:last-child { border-bottom: 0; }
.vv-fu-row-icon { flex: none; width: 16px !important; height: 16px !important; color: var(--ink-muted); }
.vv-fu-row-body { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; gap: 2px; }
.vv-fu-row-name { font-size: var(--text-base); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-fu-row-size { font-size: 11px; color: var(--ink-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.vv-fu-row-progress { flex: none; width: 80px; }
/* Audit 2026-09-23 (P2): none of the trailing siblings (Badge, remove Button) had `flex: none`, so
   the default flex-shrink:1 let a long error badge ("Supera el tamaño máximo (10 MB)") and the
   size text fight for space and squash/wrap into each other at 375px instead of the row itself
   wrapping. `.vv-fu-row` now wraps: badge/button never shrink below content, and if the row can't
   fit name+size+badge+actions on one line, the trailing content (badge, remove button) drops to
   its own full-width line instead of colliding. */
.vv-fu-row > .vv-badge, .vv-fu-row > .vv-btn { flex: none; }
.vv-fu-compact { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.vv-fu-compact-file { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.vv-fu-compact-name { font-size: var(--text-base); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; }

/* ===== AuthPage ===== */
/* AP-01 (auditoría móvil 2026-09-23, hallazgo preexistente `reports/overflow-report.md` desde
   2.6.0): con `align-items: center` en un flex sin scroll, una tarjeta más alta que el viewport
   (formulario + OAuth + legal, en un móvil corto o con texto ampliado) se recorta por ARRIBA sin
   forma de hacer scroll hasta ahí — el bug clásico de "flexbox centering + overflow" (el overflow
   escapa por el lado contrario al que el eje de alineación reserva). `overflow-y: auto` en el
   contenedor + `margin: auto 0` en la tarjeta (en vez de `align-items: center`) resuelve el mismo
   centrado pero deja el contenido completo alcanzable con scroll en cualquier dirección. */
.vv-authpage { min-height: 100%; width: 100%; display: flex; justify-content: center; overflow-y: auto; background: var(--canvas); padding: var(--space-6); box-sizing: border-box; }
.vv-authpage-card {
  width: 400px; max-width: 100%; margin: auto 0; display: flex; flex-direction: column; gap: var(--space-4);
  padding: var(--space-6); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  background: var(--surface); box-shadow: var(--shadow-sm); box-sizing: border-box;
}
.vv-authpage-compact { padding: 0; align-items: stretch; }
.vv-authpage-card-compact { width: 100%; min-height: 100%; border: 0; border-radius: 0; box-shadow: none; padding: 0; gap: 0; }
.vv-authpage-brand-wrap { flex: none; display: flex; flex-direction: column; gap: var(--space-4); justify-content: flex-start; padding: 0; min-height: 0; }
.vv-authpage-card-compact .vv-authpage-brand-wrap { flex: 1 1 auto; justify-content: center; padding: var(--space-6) var(--space-4); overflow: auto; }
.vv-authpage-actions-compact { flex: none; border-top: 1px solid var(--border-subtle); padding-bottom: env(safe-area-inset-bottom, 0px); }
.vv-authpage-brand { display: flex; align-items: center; justify-content: center; }
.vv-authpage-form { display: flex; flex-direction: column; gap: var(--space-4); }
.vv-authpage-form > * { flex: none; }
/* .vv-field ya es flex: 0 0 auto por defecto (ver Input arriba); sin regla local. */
.vv-authpage-form .vv-authpage-cta { margin-top: calc(var(--space-5) - var(--space-4)); }
.vv-authpage-label-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); width: 100%; }
.vv-authpage-forgot { position: relative; margin: 0; padding: 0; border: 0; background: none; font: inherit; font-size: 12px; line-height: 16px; color: var(--link); cursor: pointer; }
[data-density="touch"] .vv-authpage-forgot { --vv-hit-y: -16px; }
.vv-authpage-alt { align-self: center; }
.vv-authpage-remember { margin: 0; }
.vv-authpage-remember-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); width: 100%; }
.vv-authpage-remember-row .vv-authpage-remember { min-width: 0; }
.vv-authpage-cta { width: 100%; justify-content: center; }
.vv-authpage-sep { display: flex; align-items: center; gap: var(--space-3); color: var(--ink-faint); font-size: 12px; }
.vv-authpage-sep::before, .vv-authpage-sep::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--border-subtle); }
.vv-authpage-check { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); text-align: center; }
.vv-authpage-check-icon { width: 32px !important; height: 32px !important; color: var(--primary); margin-bottom: var(--space-1); }
.vv-authpage-check-body { margin: 0; font-size: var(--text-base); color: var(--ink-muted); }
.vv-authpage-check .vv-input, .vv-authpage-check .vv-field { width: 100%; }
.vv-authpage-resend { margin-top: var(--space-2); }
.vv-authpage-legal { margin: 0; font-size: 11px; line-height: 16px; color: var(--ink-muted); text-align: center; }
.vv-authpage-legal a { color: var(--link); }

/* ---- shell ---- */
/* Vive UI — frag/shell.css: AppShell, Sidebar, NavRail, TopBar, Breadcrumbs, SizeSwitcher.
   One shell per product — never nested. The container-query subject is always a wrapper
   (.vv-shell-container) separate from the element whose flex-direction changes (.vv-shell),
   because a container cannot query the axis it itself resizes (see guidelines/20-dual-shell.md).
   container-name is the fixed "vvshell" since a page only ever has one AppShell. */

/* ===== NavRail =====
   Cut verbatim from bundle.css (was duplicated there for ListPage/RecordPage): the M3 Expressive
   window size class "medium" (600–839px) vertical 80px rail (`navrail-w`), icon + 11px label
   stacked, replacing the mobile dock once there's room, before there's room for the full sidebar
   (from "expanded", 840px). */
.vv-navrail {
  display: none; flex-direction: column; align-items: stretch; width: var(--navrail-w); flex: none;
  border-right: 1px solid var(--border-subtle); background: var(--surface); padding: var(--space-3) var(--space-1); gap: var(--space-1);
}
.vv-navrail-item {
  all: unset; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  min-height: var(--row-touch); padding: var(--space-1) 2px; border-radius: var(--radius-md); color: var(--ink-muted);
  font-size: 11px; line-height: 14px; font-weight: 500; text-align: center; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.vv-navrail-item :is(svg, .vv-icon) { width: var(--icon); height: var(--icon); }
.vv-navrail-item:hover { background: var(--surface-3); color: var(--ink); }
.vv-navrail-item:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
.vv-navrail-item[aria-current="page"] { background: var(--primary-soft); color: var(--primary); }
.vv-navrail-brand { display: flex; justify-content: center; padding: var(--space-3) 0 var(--space-2); flex: none; }
.vv-navrail-foot { display: flex; justify-content: center; padding: var(--space-2) 0 var(--space-3); flex: none; margin-top: auto; }
.vv-navrail-item-badge { position: absolute; top: -6px; right: -12px; transform: scale(0.85); }
.vv-navrail-item-iconwrap { position: relative; display: inline-flex; }

/* ===== Overlay inspector panel =====
   Cut verbatim from bundle.css. The M3 Expressive window size class "expanded" (840–1199px) shows
   the detail as a panel superimposed over the content — not a bottom sheet (medium and below) and
   not a fixed column (large and up, 1200px+). AppShell applies this class to its inspector only
   while `inspectorOpen` is true and the computed shell class is "expanded". */
.vv-lp-inspector-overlay {
  display: flex; position: absolute; top: 0; right: 0; bottom: 0; z-index: 10;
  box-shadow: var(--shadow-lg); border-left: 1px solid var(--border-subtle);
  animation: vv-lp-panel-in var(--dur-spatial-default) var(--spring-spatial-default);
}
@keyframes vv-lp-panel-in { from { transform: translateX(24px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .vv-lp-inspector-overlay { animation: none; }
}
.vv-lp-insp-close { display: none; }
/* The overlay-only close affordance: the consumer's `inspector` node includes its own button with
   this class (see AppShell/README.md); it only appears in the "expanded" range (840–1199px) where
   the inspector floats over the content — a fixed column (large/xlarge) needs no close button, and
   a Sheet (compact/medium) already has one. */
@container vvshell (min-width: 840px) and (max-width: 1199.98px) {
  .vv-lp-insp-close { display: inline-flex; }
}
/* AppShell reuses one `inspector` node for the Sheet, the overlay panel and the fixed column; the
   Sheet already shows the title in its own head (via `inspectorTitle`), so a head row inside the
   inspector node tagged `vv-lp-insp-head` (see AppShell/README.md) is hidden there to avoid a
   duplicate title — it stays visible in the overlay panel and the fixed column. */
.vv-shell-container .vv-sheet-body .vv-lp-insp-head { display: none; }

/* ===== AppShell container/body ===== */
.vv-shell-container {
  container-type: inline-size; container-name: vvshell; position: relative; contain: layout;
  flex: 1 1 auto; display: flex; min-width: 0; min-height: 0; overflow: hidden;
}
.vv-shell {
  flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; min-height: 0;
  background: var(--canvas);
}
.vv-shell-banner { flex: none; }
.vv-shell-body { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; position: relative; }

/* ===== Sidebar ===== */
.vv-shell-sidebar {
  display: none; flex-direction: column; width: var(--vv-sidebar-w, var(--sidebar-w)); /* resize */ flex: none;
  border-right: 1px solid var(--border-subtle); background: var(--surface);
  transition: width var(--dur-spatial-default) var(--spring-spatial-default);
}
.vv-shell-sidebar-collapsed { width: var(--sidebar-collapsed-w); }
.vv-shell-sidebar-brand { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-4); flex: none; }
.vv-shell-sidebar-collapsed .vv-shell-sidebar-brand span { display: none; }
.vv-shell-nav { display: flex; flex-direction: column; gap: 2px; padding: var(--space-2); flex: 1 1 auto; overflow: auto; }
.vv-shell-nav-section + .vv-shell-nav-section { margin-top: var(--space-2); }
.vv-shell-nav-section-label { padding: var(--space-2) var(--space-3) var(--space-1); font-size: 11px; font-weight: 500; color: var(--ink-muted); }
.vv-shell-sidebar-collapsed .vv-shell-nav-section-label { display: none; }
.vv-shell-nav-item {
  all: unset; box-sizing: border-box; display: flex; align-items: center; gap: var(--space-3);
  height: var(--row-h); flex: none; padding: 0 var(--space-3); border-radius: var(--radius-md); color: var(--ink-muted);
  font-size: var(--text-base); cursor: pointer; white-space: nowrap; overflow: hidden;
}
.vv-shell-nav-item :is(svg, .vv-icon) { width: var(--icon); height: var(--icon); flex: none; }
.vv-shell-nav-item-label { overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; }
.vv-shell-nav-item .vv-badge { flex: none; }
/* SB-01 (auditoría móvil 2026-09-23): `all: unset` en `.vv-shell-nav-item` reinicia `width` a
   `auto` — con contenido de solo icono eso deja un hitbox más estrecho que la columna del rail
   (margen muerto sin tap a los lados). `width:100%` hace que el objetivo cubra toda la columna
   disponible; `--sidebar-collapsed-w` (56px) con el padding-inline por defecto de `.vv-shell-nav`
   (space-2, 8px cada lado) solo deja 40px. `.vv-shell-sidebar` también resta 1px de
   `border-right` (box-sizing: border-box) al ancho de contenido real — con space-1 (4px) se
   quedaba en 47px, 1 corto; space-0-5 (2px) deja 51px, margen de sobra sobre el mínimo M3, sin
   tocar el padding vertical entre ítems. */
.vv-shell-sidebar-collapsed .vv-shell-nav { padding-inline: var(--space-0-5); }
.vv-shell-sidebar-collapsed .vv-shell-nav-item { justify-content: center; padding: 0; width: 100%; }
.vv-shell-sidebar-collapsed .vv-shell-nav-item-label, .vv-shell-sidebar-collapsed .vv-shell-nav-item .vv-badge { display: none; }
.vv-shell-nav-item:hover { background: var(--surface-3); color: var(--ink); }
.vv-shell-nav-item:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
.vv-shell-nav-item[aria-current="page"] { background: var(--primary-soft); color: var(--primary); font-weight: 500; }
.vv-shell-sidebar-foot { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-4); border-top: 1px solid var(--border-subtle); flex: none; min-width: 0; }
/* El texto del pie absorbe el espacio libre para anclar el menú de usuario al borde derecho. El
   `margin-left: auto` de `.vv-shell-sidebar-foot-menu` no basta: cuando el disparador es `V.Menu`
   el botón vive dentro de `.vv-menu-trigger-wrap` (display: inline-flex, ancho de contenido), así
   que el margen automático no ve espacio libre y la fila deja el hueco a la derecha del icono. */
.vv-shell-sidebar-foot-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.vv-shell-sidebar-foot-name { font-size: 12px; font-weight: 500; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vv-shell-sidebar-foot-role { font-size: 11px; color: var(--ink-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vv-shell-sidebar-collapsed .vv-shell-sidebar-foot-text, .vv-shell-sidebar-collapsed .vv-shell-sidebar-foot-menu { display: none; }
.vv-shell-sidebar-foot-menu { margin-left: auto; flex: none; }
/* pie custom (2.13.0, §7): `Sidebar.footer` sustituye al pie por defecto; con la barra colapsada,
   el texto y el chevron del UserChip (patrón AccountMenu no-compacto) se ocultan igual que el pie
   por defecto oculta `-foot-text`/`-foot-menu` arriba — mismo alto de fila (--row-h), sin salto. */
.vv-shell-sidebar-foot-custom { min-width: 0; }
.vv-shell-sidebar-foot-custom .vv-userchip { max-width: 100%; }
.vv-shell-sidebar-collapsed .vv-shell-sidebar-foot-custom .vv-userchip-text,
.vv-shell-sidebar-collapsed .vv-shell-sidebar-foot-custom .vv-userchip-chevron { display: none; }
.vv-shell-sidebar-collapsed .vv-shell-sidebar-foot-custom { padding: var(--space-2); justify-content: center; }
.vv-shell-sidebar-collapsed .vv-shell-sidebar-foot-custom .vv-userchip { width: var(--row-h); padding: 0; justify-content: center; }

/* secciones plegables (2.13.0, §8): botón real (antes, <div> mudo) — mismo tamaño de fuente/color
   que `.vv-shell-nav-section-label`, solo se activa con `collapsibleSections`. */
.vv-shell-nav-section-toggle { all: unset; box-sizing: border-box; display: flex; align-items: center; gap: var(--space-2); width: 100%; cursor: pointer; }
/* SB-02 (2.13.0, auditoría de contención): el <div> mudo de 2.12.0 no era un objetivo táctil;
   ahora que es un <button> real, en touch necesita 48px de alto (M3-03) sin engordar la fila
   visualmente (rompería la densidad de la lista de secciones) — mismo patrón `--vv-hit-y`
   ("Expanded touch hit-area", arriba) que Button sm/Stepper/PanelToggle. */
[data-density="touch"] .vv-shell-nav-section-toggle { position: relative; --vv-hit-y: -13px; }
.vv-shell-nav-section-toggle:hover { color: var(--ink); }
.vv-shell-nav-section-toggle:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; border-radius: var(--radius-sm); }
.vv-shell-nav-section-label-text { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-shell-nav-section-toggle .vv-badge { flex: none; }
.vv-shell-nav-section-chevron { width: 14px !important; height: 14px !important; flex: none; transition: transform var(--dur-fast) var(--ease-out); }
.vv-shell-nav-section-chevron-collapsed { transform: rotate(-90deg); }
.vv-shell-nav-section-body { display: flex; flex-direction: column; gap: 2px; }
.vv-shell-nav-section-body[hidden] { display: none; }
.vv-shell-sidebar-toggle { margin-left: auto; flex: none; }
.vv-shell-sidebar-collapsed .vv-shell-sidebar-brand { padding: var(--space-3) var(--space-2); justify-content: center; }
.vv-shell-sidebar-collapsed .vv-shell-sidebar-toggle { display: none; }
/* Un solo control visible a la vez: el de la barra lateral mientras está abierta, el de la TopBar
   cuando está colapsada (mismo DOM en React y en HTML estático: sidebar y main son hermanos). */
.vv-shell-sidebar:not(.vv-shell-sidebar-collapsed) ~ .vv-shell-main .vv-shell-topbar-sidebar-toggle { display: none; }

/* ===== PanelToggle (abrir/cerrar sidebar y paneles) =====
   Sustituye al `panel-left` + kbd de 2.4: un botón redondo con un glifo propio que ANIMA el
   estado. El glifo es un marco (`-frame`), un panel relleno (`-pane`) que se estrecha a una barra
   cuando el panel está cerrado, y un chevron (`-chev`) que gira 180° con muelle espacial hacia
   donde va a moverse el panel. `aria-pressed="true"` = panel cerrado (así lo lee vive-shell.js).
   `data-side="right|bottom"` refleja el glifo para paneles a la derecha o abajo. Sin React se
   escribe el markup a mano (ver PanelToggle/README.md). Tamaño: row-h − 8 (24/32/40). */
.vv-paneltoggle {
  appearance: none; border: 0; padding: 0; background: transparent; font: inherit; box-sizing: border-box; display: inline-flex; flex: none; align-items: center; justify-content: center;
  width: calc(var(--row-h) - 8px); height: calc(var(--row-h) - 8px); border-radius: var(--radius-full); position: relative;
  color: var(--ink-muted); cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background-color var(--dur-effects-fast) var(--spring-effects-fast), color var(--dur-effects-fast) var(--spring-effects-fast),
    transform var(--dur-spring) var(--ease-spring);
}
/* PT-01 (auditoría móvil 2026-09-23): el glifo (línea 2196) ya crece en touch, pero la caja del
   botón se queda en row-h−8 = 40px — misma utilidad de área táctil que `.vv-btn-sm` (arriba,
   "Expanded touch hit-area"; M3: contenedor 40 / objetivo 48), simétrico en los dos ejes porque el
   botón es cuadrado. */
[data-density="touch"] .vv-paneltoggle {
  --vv-hit-x: calc((var(--row-h) - 8px - var(--row-touch)) / 2); --vv-hit-y: var(--vv-hit-x);
}
.vv-paneltoggle:hover { background: var(--surface-3); color: var(--ink); }
.vv-paneltoggle:active { transform: scale(.9); }
.vv-paneltoggle:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-paneltoggle[disabled] { opacity: .4; cursor: default; }
.vv-paneltoggle-glyph { width: calc(var(--icon) + 2px); height: calc(var(--icon) + 2px); flex: none; overflow: visible; }
[data-density="touch"] .vv-paneltoggle-glyph { width: 22px; height: 22px; }
.vv-paneltoggle[data-side="right"] .vv-paneltoggle-glyph { transform: scaleX(-1); }
.vv-paneltoggle[data-side="bottom"] .vv-paneltoggle-glyph { transform: rotate(-90deg); }
.vv-paneltoggle[data-side="top"] .vv-paneltoggle-glyph { transform: rotate(90deg); }
.vv-paneltoggle-frame { fill: none; stroke: currentColor; stroke-width: 1.75; }
.vv-paneltoggle-pane {
  fill: currentColor; opacity: .38; transform-box: fill-box; transform-origin: left center;
  transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast), opacity var(--dur-effects-fast) var(--spring-effects-fast);
}
.vv-paneltoggle-chev {
  fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  transform-box: fill-box; transform-origin: center;
  transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast);
}
/* Abierto (aria-pressed=false): el panel es ancho y el chevron apunta hacia él («cerrar»). Hover: el chevron
   se asoma 1px hacia donde irá. Cerrado (aria-pressed=true): el panel se encoge a una barra y el chevron
   gira 180° con rebote — el muelle espacial es la firma M3E del control. */
.vv-paneltoggle:hover .vv-paneltoggle-chev { transform: translateX(-1px); }
.vv-paneltoggle[aria-pressed="true"] .vv-paneltoggle-pane { transform: scaleX(.42); opacity: .55; }
.vv-paneltoggle[aria-pressed="true"] .vv-paneltoggle-chev { transform: rotate(180deg); }
.vv-paneltoggle[aria-pressed="true"]:hover .vv-paneltoggle-chev { transform: rotate(180deg) translateX(-1px); }
.vv-paneltoggle[aria-pressed="true"]:hover { color: var(--primary); background: var(--primary-soft); }
.vv-paneltoggle-kbd { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* ===== TopBar ===== */
.vv-shell-topbar {
  display: flex; align-items: center; gap: var(--space-3);
  height: max(var(--titlebar-h), calc(var(--row-h) + 2 * var(--space-2)));
  padding: 0 var(--page-gutter); flex: none; border-bottom: 1px solid var(--border-subtle); background: var(--surface);
}
/* Window Controls Overlay: draggable title bar strip, respecting native inset areas. */
@media (display-mode: window-controls-overlay) {
  .vv-shell-topbar { -webkit-app-region: drag; padding-left: max(var(--page-gutter), env(titlebar-area-x, 0px)); }
  .vv-shell-topbar > * { -webkit-app-region: no-drag; }
}
.vv-shell-topbar-children { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: var(--space-3); }
.vv-shell-topbar-actions { display: flex; align-items: center; gap: var(--space-1); flex: none; }
.vv-shell-topbar-search { flex: none; }
/* compact-title mode: used by the shell in "compact" (or forced with the `compact` prop when the
   TopBar is used loose) — a centered title with an optional back button and one right-hand action. */
.vv-shell-topbar-compact .vv-shell-topbar-sidebar-toggle { display: none; }
.vv-shell-topbar-compact .vv-shell-topbar-children { justify-content: center; text-align: center; }
/* `align="start"` opts a compact TopBar back into a left-aligned title (used by pages whose
   compact header shows a page title + up to two actions instead of a single centered one). */
.vv-shell-topbar-compact.vv-shell-topbar-align-start .vv-shell-topbar-children { justify-content: flex-start; text-align: left; }

/* ===== Breadcrumbs ===== */
.vv-shell-breadcrumbs { display: flex; align-items: center; min-width: 0; overflow: hidden; font-size: 12px; font-weight: 500; }
.vv-shell-breadcrumbs-full { display: flex; align-items: center; gap: var(--space-1); min-width: 0; overflow: hidden; }
.vv-shell-breadcrumb-link { all: unset; box-sizing: border-box; position: relative; color: var(--ink-muted); cursor: pointer; border-radius: var(--radius-sm); white-space: nowrap; }
.vv-shell-breadcrumb-link:hover { color: var(--ink); }
.vv-shell-breadcrumb-link:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
/* TB-01 (auditoría móvil 2026-09-23): el enlace de miga de pan es texto inline de 18px de alto —
   ampliar el área táctil solo en vertical (como `.vv-btn-sm`, utilidad arriba, no en horizontal)
   evita que el hit-area invisible de una miga se solape con la siguiente en una fila «Clientes ›
   Facturas › …». La TopBar ya tiene alto de sobra (`max(titlebar-h, row-h + 2·space-2)`) para los
   48px. Nota: `.vv-shell-breadcrumbs-full` (el envoltorio real de este enlace) fija
   `overflow: hidden` para la elipsis del breadcrumb — recorta esta expansión en el ancestro si
   alguna vez se amplía en vertical más allá de la fila; en horizontal (ver `--vv-hit-x` debajo) no
   aplica: el hueco entre enlaces (gap + separador ~22px) sobra para +3px por lado sin llegar a
   tocar ese límite. */
[data-density="touch"] .vv-shell-breadcrumb-link { --vv-hit-y: calc((18px - var(--row-touch)) / 2); }
/* Objetivo táctil (auditoría de contención 2026-09-23): el fallo real no era la altura (el cálculo
   de arriba ya da 48) sino el ANCHO — una miga corta ("Facturas", "Sistema"…) mide menos de 48px de
   texto propio y no tenía ensanche horizontal alguno (`--vv-hit-x` nunca se fijaba, quedaba en el
   0px por defecto de la utilidad). +3px por lado cubre el peor caso medido (42px reales) sin
   invadir la siguiente miga (queda muy por debajo de los ~22px de separador+gap disponibles). */
[data-density="touch"] .vv-shell-breadcrumb-link { --vv-hit-x: -4px; }
.vv-shell-breadcrumb-current { color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vv-shell-breadcrumb-sep { color: var(--ink-faint); flex: none; display: inline-flex; }
.vv-shell-breadcrumb-sep :is(svg, .vv-icon) { width: 14px; height: 14px; }
.vv-shell-breadcrumbs-collapsed { display: none; }
/* Collapses to "‹ Padre" (the penultimate crumb only) under 600px — inside an AppShell this keys
   off the shell's own container; a standalone Breadcrumbs (data-standalone) falls back to a media
   query against the viewport since it has no shell container to query. */
@container vvshell (max-width: 599.98px) {
  .vv-shell-container { --page-gutter: var(--space-4); }
  .vv-shell-breadcrumbs-full { display: none; }
  .vv-shell-breadcrumbs-collapsed { display: flex; }
}
@media (max-width: 599.98px) {
  .vv-shell-breadcrumbs[data-standalone] .vv-shell-breadcrumbs-full { display: none; }
  .vv-shell-breadcrumbs[data-standalone] .vv-shell-breadcrumbs-collapsed { display: flex; }
}

/* ===== Main column / content / inspector ===== */
.vv-shell-main { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; }
.vv-shell-content { flex: 1 1 auto; min-width: 0; min-height: 0; overflow: auto; }
.vv-shell-inspector { display: none; flex-direction: column; width: var(--vv-inspector-w, var(--inspector-w)); /* resize */ flex: none; border-left: 1px solid var(--border-subtle); background: var(--surface); overflow: auto; }
/* Higher specificity than the bare .vv-shell-inspector{display:none} above: the "expanded" overlay
   panel must show regardless of the large/xlarge @container rule further down. */
.vv-shell-inspector.vv-lp-inspector-overlay { display: flex; }
.vv-shell-dock { position: absolute; left: 0; right: 0; bottom: 0; display: flex; justify-content: center; pointer-events: none; }
.vv-shell-dock > * { pointer-events: auto; }
/* DK-04 (dock móvil, 2.12.0): velo bajo la píldora. En compact el Dock flota sobre el contenido, así
   que al desplazar los campos pasan por detrás y la píldora opaca los corta. El degradado a `--canvas`
   hace que el contenido se lea «pasando por detrás»; es absoluto, no entra en el flujo flex y se pinta
   bajo el .vv-dock (hijo positioned posterior); desaparece con el wrapper a partir de 600px. */
.vv-shell-dock::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: calc(var(--dock-h) + var(--space-8)); pointer-events: none;
  background: linear-gradient(to top, var(--canvas) 55%, transparent);
}
/* ===== Dock «Más» overflow, réplica sin React (2.8.0, feedback esaem PR investor-vive-ui-t04) =====
   React abre V.Sheet con overflowNav (ver AppShell en bundle.js, snap:'half'); la réplica estática no
   porta a document.body, así que ancla un popover pequeño justo encima del propio dock. El disparador
   es el 6º .vv-dock-tab (Íconos «Más») con data-vv-open="dock-more" data-vv-open-class="vv-shell-dock-more-open";
   vive-shell.js (mecanismo genérico data-vv-open, ya usado por el inspector overlay) le quita/pone esa
   clase y hidden. Ver AppShell/README.md §Réplica → «Dock: overflow «Más»». */
.vv-shell-dock-more { display: none; }
.vv-shell-dock-more.vv-shell-dock-more-open {
  display: flex; justify-content: center; position: absolute; left: var(--space-4); right: var(--space-4);
  bottom: calc(100% + var(--space-2)); z-index: 30; pointer-events: none;
  animation: vv-pop var(--dur-base) var(--ease-spring);
}
.vv-shell-dock-more.vv-shell-dock-more-open .vv-menu { width: min(280px, 100%); pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .vv-shell-dock-more { animation: none; } }

/* DK-02 (auditoría de contención 2026-09-23, overflow-report.md "AppShellStatic dock tapa card"):
   AppShellStatic es la réplica sin React (solo HTML + vive-shell.js) — no tiene el JS que calcula
   `showDock` para añadir `.vv-dock-clear` a `.vv-shell-content` como hace AppShell (ver DK-01 más
   arriba en bundle.js). El dock es `position:absolute;bottom:0` en ambas variantes y solo es visible
   por debajo de 600px de contenedor (ver "compact", regla siguiente) — así que la misma clearance se
   puede expresar aquí en CSS puro, condicionada al mismo breakpoint que ya decide si el dock se
   muestra: no hace falta JS ni tocar el markup del preview. Esto no cambia nada para AppShell (React):
   el valor de padding-bottom es el mismo que ya aplica su propia clase `.vv-dock-clear`, así que
   ambas reglas coinciden sin conflicto cuando showDock también es cierto. */
@container vvshell (max-width: 599.98px) {
  .vv-shell-content { padding-bottom: var(--dock-clear); }
}
@supports not (container-type: inline-size) {
  @media (max-width: 599.98px) {
    .vv-shell .vv-shell-content { padding-bottom: var(--dock-clear); }
  }
}

/* ----- medium (600–839px): dock → nav rail, body goes side by side, still a single pane ----- */
@container vvshell (min-width: 600px) {
  .vv-shell-dock { display: none; }
  .vv-navrail { display: flex; }
  .vv-shell-body { flex-direction: row; }
}
/* ----- expanded and up (≥840px): sidebar replaces the nav rail/dock ----- */
@container vvshell (min-width: 840px) {
  .vv-shell-dock { display: none; }
  .vv-navrail { display: none; }
  .vv-shell-sidebar { display: flex; }
}
/* ----- large and up (≥1200px): the inspector is a fixed column ----- */
@container vvshell (min-width: 1200px) {
  .vv-shell-inspector { display: flex; }
}
/* ----- xlarge (≥1600px): wider inspector, content centers at content-max ----- */
@container vvshell (min-width: 1600px) {
  .vv-shell-inspector { width: var(--vv-inspector-w, var(--inspector-w-xl)); /* resize */ }
  .vv-shell-content-centered { display: flex; justify-content: center; }
  .vv-shell-content-centered > * { max-width: var(--content-max); width: 100%; }
}
@supports not (container-type: inline-size) {
  @media (min-width: 600px) {
    .vv-shell .vv-shell-dock { display: none; }
    .vv-shell .vv-navrail { display: flex; }
    .vv-shell .vv-shell-body { flex-direction: row; }
  }
  @media (min-width: 840px) {
    .vv-shell .vv-navrail { display: none; }
    .vv-shell .vv-shell-sidebar { display: flex; }
  }
  @media (min-width: 1200px) {
    .vv-shell .vv-shell-inspector { display: flex; }
  }
}

/* ===== SizeSwitcher / DensitySwitcher (docs/previews only) ===== */
.vv-shell-devbar { display: flex; align-items: center; gap: var(--space-3); flex: none; flex-wrap: wrap; }
.vv-shell-devbar-hint { font-size: 11px; color: var(--ink-muted); flex: 1 1 200px; min-width: 0; }
/* SZ-01 / DS-01 (2.9.0, auditoria adaptativa, P1): el GRUPO de botones en si (6 anchos en
   SizeSwitcher, 3 densidades + label en DensitySwitcher) no tenia ninguna regla de contencion
   propia -- solo el padre (`.vv-shell-devbar`, `flex-wrap:wrap`) la tenia, y eso hacia que
   SizeSwitcher desbordara en seco (sin wrap ni scroll: el grupo vive DENTRO de un `flex` item que
   no se reduce) y que DensitySwitcher (via `.vv-row`, tambien `flex-wrap:wrap`) se partiera en 2
   lineas a ~360px ("Compact/Comfortable" arriba, "Touch" abajo). Sigue el mismo contrato que
   `.vv-btngroup` (ver "Control-group collapse contract" mas abajo): el grupo nunca se envuelve,
   se desplaza horizontalmente si hace falta, y cada boton fija su propio ancho para que el scroll
   se active ANTES de cualquier corte/encogido (la leccion de BG-02, `.vv-bg-btn`). */
.vv-shell-sizer, .vv-densitysw-group {
  display: flex; align-items: center; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; gap: 2px;
  /* max-width:100% (mismo mecanismo que `.vv-btngroup`, no el truco de "auto min-size:0 para
     scroll containers" de flexbox, que solo se aplica cuando el grupo ES el flex item directo:
     aqui el preview de PageHeader monta el switcher dentro de un <div id="density-switcher">
     intermedio sin overflow propio, asi que ese wrapper heredaria un min-width de contenido si
     no se acota aqui) para que el grupo SI se deje encoger por debajo de su ancho natural y el
     scroll interno se active en vez de desbordar al `.vv-row`/wrapper que lo contiene. */
  max-width: 100%;
}
.vv-shell-sizer::-webkit-scrollbar, .vv-densitysw-group::-webkit-scrollbar { display: none; }
.vv-shell-sizer > *, .vv-densitysw-group > * { flex: none; }
.vv-shell-sizer .vv-btn { padding: 0 var(--space-2); font-size: 12px; }
.vv-shell-sizer .vv-btn[aria-pressed="true"] { background: var(--primary-soft); color: var(--primary); border-color: transparent; }
.vv-densitysw-group { gap: var(--space-2); }

/* ---- flows ---- */
/* Vive UI — frag/flows.css: FlowMap + flujos completos (Onboarding, CRUD, Import, Notifications, States). */

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

/* ===== FlowMap ===== */
.vv-fm { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
/* Defensive: a flex/grid ancestor that doesn't shrink its items (e.g. `flex: none`) sizes FlowMap
   to its own max-content width instead of letting `.vv-fm-scroll` take over — `min-width: 0` here
   only helps when the ancestor participates in shrinking; a `flex: none`/no-shrink ancestor still
   needs to opt in itself (see CrudFlow/preview.html note in the audit report — that wrapper isn't
   a `vv-*` class, it's page-local markup, out of this component's CSS). */
.vv-fm-scroll { overflow-x: auto; overflow-y: hidden; border-radius: var(--radius-lg); background: var(--canvas); min-width: 0; }
.vv-fm-canvas { position: relative; }
.vv-fm-svg { position: absolute; left: 0; top: 0; pointer-events: none; }

.vv-fm-node {
  position: absolute; z-index: 1; display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3); box-sizing: border-box;
  background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--dur-effects-fast) var(--spring-effects-fast), box-shadow var(--dur-effects-fast) var(--spring-effects-fast);
}
.vv-fm-node-icon {
  flex: none; width: 26px; height: 26px; border-radius: var(--radius-md); background: var(--surface-2);
  color: var(--ink-muted); display: flex; align-items: center; justify-content: center;
}
.vv-fm-node-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.vv-fm-node-title { font-size: 13px; line-height: 1.25; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-fm-node-sub { font-size: 11px; line-height: 14px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.vv-fm-node-sheet { border-radius: var(--radius-xl) var(--radius-xl) var(--radius-sm) var(--radius-sm); border-top-width: 2px; border-top-color: var(--border-strong); }
.vv-fm-node-dialog { box-shadow: var(--shadow-md); }
.vv-fm-node-state { background: var(--surface-2); border-style: dashed; border-color: var(--border); box-shadow: none; }
.vv-fm-node-state .vv-fm-node-icon { background: var(--surface-3); }
.vv-fm-node-external { border-style: dashed; }
.vv-fm-node-external .vv-fm-node-icon { color: var(--primary); }

.vv-fm-node-selected { border-color: var(--primary); box-shadow: 0 0 0 2px var(--primary-soft, var(--primary)); }

.vv-fm-edge { stroke: var(--ink-muted); stroke-width: 1.5; }
.vv-fm-edge-primary { stroke: var(--primary); stroke-width: 2; }
.vv-fm-edge-error { stroke: var(--danger); stroke-width: 1.5; stroke-dasharray: 4 3; }

/* Audit 2026-09-23 (P2, review-overlap): the label used to share the canvas's own background and
   sit UNDER nodes in DOM/paint order — if an edge's midpoint (bezier-curve dependent, not laid out
   on a grid) landed near a neighboring node, "nothing behind it" (var(--canvas)) either blended
   into the canvas or got covered by that node, reading as text-over-node. Explicit `z-index` above
   `.vv-fm-node` plus an opaque `--surface` card (same treatment as a node, so overlapping reads as
   intentional layering, not clutter) — never invisible, never literally under another text run. */
.vv-fm-edge-label {
  position: absolute; z-index: 2; transform: translate(-50%, -50%); display: flex; align-items: center; gap: 4px;
  background: var(--surface); border: 1px solid var(--border-subtle); box-shadow: var(--shadow-sm);
  padding: 1px 6px; border-radius: var(--radius-sm); max-width: 116px;
  font-size: 11px; line-height: 16px; color: var(--ink-muted); white-space: nowrap; pointer-events: none;
  overflow: hidden; text-overflow: ellipsis;
}
.vv-fm-edge-label-primary { color: var(--primary); font-weight: 500; }
.vv-fm-edge-label-error { color: var(--danger); }
.vv-fm-edge-kbd { font-size: 10px; }

/* ===== Onboarding ===== */
.vv-onb-shell { display: flex; flex-direction: column; gap: var(--space-5); max-width: 560px; margin: 0 auto; width: 100%; }
.vv-onb-header { padding-bottom: var(--space-2); border-bottom: 1px solid var(--border-subtle); }
.vv-onb-body { flex: 1 1 auto; min-height: 260px; }
.vv-onb-actions { flex-wrap: wrap; min-width: 0; position: sticky; bottom: 0; }
.vv-onb-invite-row { display: grid; grid-template-columns: 1fr 160px auto; gap: var(--space-2); align-items: end; margin-bottom: var(--space-2); }
.vv-onb-colorpicker { display: flex; flex-direction: column; gap: var(--space-2); }
.vv-onb-swatches { display: flex; gap: var(--space-2); }
/* Área táctil en touch (auditoría de contención 2026-09-23), mismo criterio y aritmética que
   `.vv-accent-swatch` (AppearanceSettings): 32px visual → 48px real con `--vv-hit-x/-y: -8px`, y el
   gap crece a `--space-4` (16px = 8+8) para que dos swatches contiguos no compitan por el mismo
   punto de la pantalla. */
[data-density="touch"] .vv-onb-swatches { gap: var(--space-4); }
.vv-onb-swatch {
  position: relative; width: 32px; height: 32px; border-radius: var(--radius-full); border: 2px solid transparent; cursor: pointer;
  display: flex; align-items: center; justify-content: center; color: #fff;
  transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast);
}
[data-density="touch"] .vv-onb-swatch { --vv-hit-x: -8px; --vv-hit-y: -8px; }
.vv-onb-swatch:hover { transform: scale(1.08); }
.vv-onb-swatch-selected { border-color: var(--ink); }
.vv-onb-swatch:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-onb-done { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--space-3); padding: var(--space-8) var(--space-4); }
.vv-onb-done-desc { color: var(--ink-muted); max-width: 360px; }

/* ===== CrudFlow ===== */
.vv-crud-shell { display: flex; flex-direction: column; gap: var(--space-4); }
.vv-crud-panel { min-height: 320px; border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); background: var(--surface); overflow: hidden; position: relative; display: flex; flex-direction: column; }
.vv-crud-sheet-body { display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-4); }
.vv-crud-record { display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-5); }
.vv-crud-record-head { display: flex; align-items: center; gap: var(--space-2); }
.vv-crud-record-props { display: flex; flex-direction: column; gap: var(--space-3); max-width: 360px; }

/* ===== ImportFlow ===== */
.vv-imp-shell { display: flex; flex-direction: column; gap: var(--space-5); }
.vv-imp-body { display: flex; flex-direction: column; gap: var(--space-4); }
.vv-imp-template { display: flex; }
.vv-imp-motivo { display: block; margin-top: 2px; }
.vv-imp-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); }

/* ===== NotificationsFlow ===== */
.vv-notif-shell { position: relative; display: inline-flex; }
.vv-notif-bell { position: relative; }
.vv-notif-dot { position: absolute; top: 6px; right: 6px; width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--danger); }
.vv-notif-panel {
  position: absolute; top: calc(100% + var(--space-2)); right: 0; width: min(340px, calc(100vw - var(--space-8)));
  background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg); display: flex; flex-direction: column; z-index: 10;
}
/* Audit 2026-09-23 (P1): anchored under the bell at a fixed 340px, this panel got clipped by the
   viewport edge below ~600px ("Notificaciones" read as "otificaciones") — `NotificationBell`
   (the reference implementation, see ===== NotificationBell / NotificationList =====) solves the
   same problem by switching to a full-width bottom sheet in compact; this hand-rolled flow panel
   didn't inherit that behavior. Match it with a CSS-only fixed bottom sheet below 600px viewport
   (this flow isn't nested in a sized container the way DataTable is, so a viewport media query is
   the right query here, not a container query). */
@media (max-width: 599.98px) {
  .vv-notif-panel {
    position: fixed; inset: auto 0 0 0; width: 100%; max-width: none; max-height: 80vh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0; padding-bottom: env(safe-area-inset-bottom, 0px);
  }
}
.vv-notif-panel-head { display: flex; align-items: center; justify-content: space-between; padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border-subtle); gap: var(--space-2); }
.vv-notif-panel-actions { display: flex; gap: var(--space-1); }
.vv-notif-list { display: flex; flex-direction: column; max-height: 360px; overflow-y: auto; }
.vv-notif-item {
  display: flex; align-items: flex-start; gap: var(--space-2); width: 100%; text-align: left;
  padding: var(--space-3) var(--space-4); background: none; border: none; border-bottom: 1px solid var(--border-subtle);
  color: var(--ink); cursor: pointer; font: inherit;
}
.vv-notif-item:last-child { border-bottom: none; }
.vv-notif-item:hover { background: var(--surface-2); }
.vv-notif-item:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
.vv-notif-item-unread { background: var(--primary-soft, var(--surface-2)); }
.vv-notif-item-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.vv-notif-item-title { font-size: var(--text-base); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-notif-item-dot { flex: none; width: 8px; height: 8px; margin-top: 4px; border-radius: var(--radius-full); background: var(--primary); }
.vv-notif-prefs { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4); }
.vv-notif-detail { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-5); }

/* ===== StatesFlow ===== */
.vv-states-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); }
@media (max-width: 900px) { .vv-states-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .vv-states-grid { grid-template-columns: 1fr; } }
.vv-states-cell { display: flex; flex-direction: column; gap: var(--space-2); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); background: var(--surface); overflow: hidden; }
.vv-states-cell-head { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--border-subtle); background: var(--surface-2); }
.vv-states-cell-body { padding: var(--space-3); min-height: 160px; display: flex; flex-direction: column; justify-content: center; }
.vv-states-toast-demo { display: flex; align-items: center; justify-content: center; padding: var(--space-6); }

@media (prefers-reduced-motion: reduce) {
  .vv-onb-swatch { transition: none; }
}

/* ---- user ---- */
/* Vive UI — frag/user.css. UserChip, AccountMenu, NotificationBell/List, MoneyInput,
   IconPicker/ProjectIcon, Countdown. Solo tokens; reutiliza .vv-menu-*, .vv-numberinput-*,
   .vv-picker-search-* de bundle.css (misma cascada, cargado después). */

/* ===== UserChip ===== */
.vv-userchip {
  all: unset; box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--space-2);
  height: var(--row-h); max-width: 220px; padding: 0 var(--space-2); border-radius: var(--radius-md); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), border-radius var(--dur-spring) var(--ease-spring);
}
.vv-userchip:hover { background: var(--surface-3); border-radius: var(--radius-xl); }
.vv-userchip:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-userchip-compact { width: var(--row-h); padding: 0; justify-content: center; }
.vv-userchip-text { display: flex; flex-direction: column; min-width: 0; gap: 1px; text-align: left; }
.vv-userchip-name { font-size: 13px; font-weight: 600; line-height: 1.2; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-userchip-role { font-size: 11px; line-height: 14px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-userchip-chevron { width: 14px !important; height: 14px !important; flex: none; color: var(--ink-muted); }

/* ===== AccountMenu ===== */
.vv-accountmenu { width: 260px; }
.vv-accountmenu-body { display: flex; flex-direction: column; }
/* .vv-accountmenu-head / -headtext: reglas ahora en el bloque compartido con .vv-menu-header, ver ===== Menu ===== */
.vv-accountmenu-item-aside { margin-left: auto; flex: none; max-width: 96px; font-size: 11px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* AM-sub (2.12.0): segundo panel de un item con `submenu`. Hermano del menú —no hijo de
   `.vv-menu-list`, que tiene `overflow:auto` y lo recortaría—. Alineado ARRIBA con el panel padre,
   no con la fila que lo abre, para que «los dos popover salgan juntos». */
.vv-accountmenu-sub { position: absolute; inset-block-start: 0; inset-inline-start: calc(100% + var(--space-2)); z-index: 1; width: 300px; max-height: 340px; display: none; flex-direction: column; overflow: auto; }
/* AppearanceSettings compacto embebido directamente (sin Sheet que le dé padding): lo aporta aquí. */
.vv-accountmenu-sub .vv-appearance { padding: var(--space-3); }
.vv-accountmenu-sub.vv-open { display: flex; }

/* Móvil (3.0.0, owner 2026-09-28, tribunal #158): ya no hay página propia — la sub-página móvil
   de AccountMenu es un `V.Sheet presentation="page"` (mismo frame/animación que cualquier otro
   Sheet en modo página, ver Sheet en frag/appearance.js). Nada que estilar aquí. */
.vv-menu-item-sub .vv-menu-item-chevron { margin-inline-start: auto; flex: none; width: 14px; height: 14px; color: var(--ink-muted); }
.vv-accountmenu-item-check { margin-inline-start: auto; flex: none; color: var(--primary); }
/* Réplica sin JS: abre al pasar por la fila; `.vv-accountmenu-sub:hover` lo mantiene al cruzar el hueco. */
.vv-accountmenu:has(.vv-menu-item-sub:hover) .vv-accountmenu-sub,
.vv-accountmenu:has(.vv-menu-item-sub:focus-visible) .vv-accountmenu-sub,
.vv-accountmenu-sub:hover, .vv-accountmenu-sub:focus-within { display: flex; }

/* ===== NotificationBell / NotificationList ===== */
.vv-notifbell-wrap { position: relative; display: inline-flex; }
.vv-notifbell-badge { position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px; padding: 0 4px; font-size: 10px; line-height: 16px; pointer-events: none; font-variant-numeric: tabular-nums; }
.vv-notifbell-dot { position: absolute; top: 6px; right: 6px; width: 8px; height: 8px; border-radius: var(--radius-full); background: var(--danger); border: 2px solid var(--surface); pointer-events: none; }

.vv-notiflist { display: flex; flex-direction: column; width: min(360px, 100%); max-height: 480px; background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); overflow: hidden; }
.vv-notiflist-popover { position: fixed; z-index: 30; width: min(360px, calc(100vw - var(--space-8))); animation: vv-pop var(--dur-base) var(--ease-spring); }
/* Anclaje sin React (2.8.0, mismo caso que .vv-menu-trigger-wrap arriba): .vv-notifbell-wrap ya es
   position:relative (ver frag/user.css) y en la réplica estática el popover vive dentro, no portado. */
.vv-notifbell-wrap > .vv-notiflist-popover { position: absolute; inset-block-start: calc(100% + var(--space-1)); inset-inline-end: 0; }
.vv-notiflist-popover .vv-notiflist { box-shadow: var(--shadow-lg); width: 100%; }
.vv-sheet .vv-notiflist { width: 100%; max-height: none; height: 100%; border: 0; border-radius: 0; box-shadow: none; }
.vv-notiflist-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); padding: var(--space-3) var(--space-3) var(--space-2); flex: none; }
.vv-notiflist-tabs { padding: 0 var(--space-3) var(--space-2); flex: none; }
.vv-notiflist-body { flex: 1 1 auto; min-height: 0; overflow: auto; }
.vv-notiflist-skeletons { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--space-2) var(--space-3); }
.vv-notiflist-groupsep { position: sticky; top: 0; z-index: 1; padding: var(--space-2) var(--space-3) var(--space-1); font-size: 11px; font-weight: 500; color: var(--ink-muted); background: var(--surface); }
.vv-notiflist-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-top: 1px solid var(--border-subtle); flex: none; }

.vv-notifrow { position: relative; display: flex; align-items: center; gap: var(--space-2); min-height: 56px; padding: var(--space-2) var(--space-3); }
.vv-notifrow-dot { flex: none; width: 8px; height: 8px; border-radius: var(--radius-full); background: transparent; }
.vv-notifrow-unread .vv-notifrow-dot { background: var(--primary); }
.vv-notifrow-icon { display: flex; align-items: center; justify-content: center; flex: none; width: 28px; height: 28px; border-radius: var(--radius-full); background: var(--surface-2); color: var(--ink-muted); }
.vv-notifrow-icon :is(svg, .vv-icon) { width: 14px !important; height: 14px !important; }
.vv-notifrow-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.vv-notifrow-title { font-size: var(--text-base); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-notifrow-unread .vv-notifrow-title { font-weight: 600; }
.vv-notifrow-desc { font-size: 11px; line-height: 14px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-notifrow-time { flex: none; font-size: 11px; color: var(--ink-muted); white-space: nowrap; }
.vv-notifrow-actions { position: absolute; right: var(--space-3); top: 50%; transform: translateY(-50%); display: none; gap: var(--space-1); background: var(--surface); }
.vv-notifrow:hover { background: var(--surface-3); }
.vv-notifrow:hover .vv-notifrow-actions, .vv-notifrow:focus-within .vv-notifrow-actions { display: inline-flex; }
.vv-notifrow:hover .vv-notifrow-time, .vv-notifrow:focus-within .vv-notifrow-time { display: none; }
.vv-notifrow[role="button"] { cursor: pointer; }
.vv-notifrow[role="button"]:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }

/* ===== MoneyInput ===== */
.vv-moneyinput-wrap { min-width: 200px; }
.vv-moneyinput-error { border-color: var(--danger); }
.vv-moneyinput-symbol { flex: none; padding: 0 var(--space-3); font-size: 12px; font-family: var(--font-mono); color: var(--ink-muted); }
.vv-moneyinput-currency-wrap { position: relative; flex: none; height: 100%; display: flex; }
.vv-moneyinput-currency-btn {
  all: unset; box-sizing: border-box; display: flex; align-items: center; gap: 2px; height: 100%;
  padding: 0 var(--space-2) 0 var(--space-3); font: inherit; font-size: 12px; font-weight: 500; color: var(--ink-muted);
  border-left: 1px solid var(--border-subtle); cursor: pointer; transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.vv-moneyinput-currency-btn:hover { color: var(--ink); background: var(--surface-2); }
.vv-moneyinput-currency-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
.vv-moneyinput-currency-btn :is(svg, .vv-icon) { width: 12px !important; height: 12px !important; }

/* ===== IconPicker / ProjectIcon ===== */
.vv-projecticon { flex: none; }
.vv-iconpicker { display: flex; flex-direction: column; width: min(360px, 100%); max-height: 420px; background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); overflow: hidden; }
.vv-iconpicker-popover { position: fixed; z-index: 30; width: min(360px, calc(100vw - var(--space-8))); animation: vv-pop var(--dur-base) var(--ease-spring); }
.vv-iconpicker-popover .vv-iconpicker { box-shadow: var(--shadow-lg); width: 100%; }
.vv-sheet .vv-iconpicker { width: 100%; max-height: none; border: 0; border-radius: 0; box-shadow: none; }
.vv-iconpicker-tabs { padding: var(--space-3) var(--space-3) var(--space-2); flex: none; }
.vv-iconpicker-colors { display: flex; gap: var(--space-2); padding: var(--space-2) var(--space-3); flex: none; flex-wrap: wrap; }
.vv-iconpicker-swatch {
  all: unset; box-sizing: border-box; width: 24px; height: 24px; flex: none; border-radius: var(--radius-full); cursor: pointer;
  transition: box-shadow var(--dur-fast) var(--ease-out), transform var(--dur-spring) var(--ease-spring);
}
.vv-iconpicker-swatch:hover { transform: scale(1.08); }
.vv-iconpicker-swatch-selected { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--focus-ring); }
.vv-iconpicker-swatch:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-iconpicker-grid { display: grid; grid-template-columns: repeat(8, minmax(0, 1fr)); gap: var(--space-1); padding: var(--space-2) var(--space-3) var(--space-3); overflow: auto; }
.vv-iconpicker-cell {
  all: unset; box-sizing: border-box; display: flex; align-items: center; justify-content: center; height: 40px;
  border-radius: var(--radius-md); color: var(--ink-muted); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
/* Audit 2026-09-23 (P2): 8 columns × 4px gap gave ~38px cells with <8px separation on the phone
   sheet — same "dense grid" tension as MonthGrid/PermissionMatrix. Fewer columns at touch density
   (cells grow via the existing `minmax(0, 1fr)` tracks, nothing to hardcode) plus the M3-14 8px
   minimum gap comfortably clears the 44px touch target without shrinking the sheet's usefulness. */
[data-density="touch"] .vv-iconpicker-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--space-2); }
[data-density="touch"] .vv-iconpicker-cell { height: 44px; }
.vv-iconpicker-cell:hover { background: var(--surface-3); }
.vv-iconpicker-cell-selected { background: var(--primary-soft); color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.vv-iconpicker-cell:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-iconpicker-cell :is(svg, .vv-icon) { width: 18px !important; height: 18px !important; }
.vv-iconpicker-cell-emoji { font-size: 18px; }
.vv-iconpicker-empty { grid-column: 1 / -1; padding: var(--space-4); text-align: center; color: var(--ink-muted); font-size: 12px; }
.vv-iconpicker-trigger {
  all: unset; box-sizing: border-box; display: flex; align-items: center; justify-content: center;
  width: var(--row-h); height: var(--row-h); border-radius: var(--radius-md); background: var(--surface-2); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), border-radius var(--dur-spring) var(--ease-spring);
}
.vv-iconpicker-trigger:hover { background: var(--surface-3); border-radius: var(--radius-lg); }
.vv-iconpicker-trigger:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }

/* ===== Countdown ===== */
.vv-countdown { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--ink); }
.vv-countdown-label { font-size: 11px; line-height: 14px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-countdown-value { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
.vv-countdown-sm .vv-countdown-value { font-size: 12px; }
.vv-countdown-md .vv-countdown-value { font-size: var(--text-base); }
.vv-countdown-lg .vv-countdown-value { font-size: 20px; }
.vv-countdown-warning .vv-countdown-value, .vv-countdown-warning .vv-countdown-ring { color: var(--warning); }
.vv-countdown-danger .vv-countdown-value, .vv-countdown-danger .vv-countdown-ring { color: var(--danger); }
.vv-countdown-ringwrap { position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none; }
.vv-countdown-ring { color: var(--primary); }
.vv-countdown-ringvalue { position: absolute; font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 600; font-size: 11px; }

/* ---- views ---- */
/* Vive UI — frag/views.css. MonthGrid, Gallery, Lightbox, AIAnswer. Tokens only. */

/* ===== MonthGrid ===== */
.vv-mg-wrap { display: flex; flex-direction: column; gap: var(--space-3); }
.vv-mg { display: flex; flex-direction: column; gap: var(--space-2); }
.vv-mg-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
.vv-mg-head-spacer { width: var(--row-h); height: var(--row-h); flex: none; }
.vv-mg-compact-title { font-size: 11px; font-weight: 500; color: var(--ink-muted); }
.vv-mg-weekdays { display: grid; grid-template-columns: repeat(7, var(--vv-mg-cell, 32px)); gap: 4px; justify-content: start; }
.vv-mg-weekdays span { font-size: 10px; font-weight: 500; color: var(--ink-muted); text-align: center; }
.vv-mg-grid { display: grid; grid-template-columns: repeat(7, var(--vv-mg-cell, 32px)); gap: 4px; justify-content: start; }
/* Audit 2026-09-23 (P2): 40px cells at 4px gap is <8px M3-14 separation — 7 fixed columns leave no
   room to also grow the cell to 44px+ (7*44 + 6*8 alone is already 356px, more than the ~343px
   useful width at 375px), so the conscious trade-off is: keep the cell at 40px (documented, same
   call other calendar UIs make — see audit checklist) and spend the freed width on the gap instead
   (7*40 + 6*8 = 328px, fits) — clears the M3-14 separation minimum without shrinking the grid. */
[data-density="touch"] .vv-mg-weekdays,
[data-density="touch"] .vv-mg-grid { gap: var(--space-2); }
/* Área táctil (utilidad "Expanded touch hit-area" más arriba): la celda visual se queda en 40px
   (ver el trade-off documentado justo encima), pero su objetivo táctil real crece a 48×48 con un
   `::before` invisible — 4px por lado caben exactos en el gap de 8px sin solapar con la celda
   vecina (4px + 4px = el gap completo), así que dos celdas contiguas nunca compiten por el mismo
   punto de la pantalla. */
[data-density="touch"] .vv-mg-cell { --vv-hit-x: -4px; --vv-hit-y: -4px; }
.vv-mg-range { display: flex; gap: var(--space-5); flex-wrap: wrap; }

.vv-mg-cell {
  --vv-mg-cell: 32px;
  position: relative; width: var(--vv-mg-cell); height: var(--vv-mg-cell); flex: none;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm); border: 1px solid transparent; background: transparent;
  font: inherit; font-size: 12px; font-variant-numeric: tabular-nums; color: var(--ink); cursor: pointer; padding: 0;
  transition: background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), border-radius var(--dur-spring) var(--ease-spring);
}
.vv-mg-cell:hover { background: var(--surface-3); }
.vv-mg-cell:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-mg-cell-out { color: var(--ink-faint); }
.vv-mg-cell-today { border-color: var(--border-strong); }
.vv-mg-cell-selected { border-color: var(--primary); border-width: 2px; }

.vv-mg-cell-done { background: var(--success); color: var(--on-status); }
.vv-mg-cell-partial { background: var(--warning); color: var(--on-status); }
.vv-mg-cell-missed { background: var(--danger); color: var(--on-status); }
.vv-mg-cell-planned { background: var(--primary-soft); color: var(--primary); border-color: var(--primary); }
.vv-mg-cell-off { background: var(--surface-2); color: var(--ink-muted); }

.vv-mg-cell-heat-1 { background: var(--primary-soft); color: var(--primary); }
.vv-mg-cell-heat-2 { background: color-mix(in srgb, var(--primary-soft) 40%, var(--primary) 60%); color: var(--on-primary); }
.vv-mg-cell-heat-3 { background: color-mix(in srgb, var(--primary-soft) 15%, var(--primary) 85%); color: var(--on-primary); }
.vv-mg-cell-heat-4 { background: var(--primary); color: var(--on-primary); }

.vv-mg-legend { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.vv-mg-legend-item { display: inline-flex; align-items: center; gap: var(--space-1); font-size: 11px; color: var(--ink-muted); }
.vv-mg-legend-dot { width: 10px; height: 10px; border-radius: var(--radius-full); flex: none; }

[data-density="compact"] .vv-mg-cell,
[data-density="compact"] .vv-mg-weekdays,
[data-density="compact"] .vv-mg-grid { --vv-mg-cell: 28px; }
[data-density="touch"] .vv-mg-cell,
[data-density="touch"] .vv-mg-weekdays,
[data-density="touch"] .vv-mg-grid { --vv-mg-cell: 40px; }

/* ===== Gallery ===== */
.vv-gal-container { container-type: inline-size; container-name: vvgal; }
.vv-gal { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-2); }
@container vvgal (min-width: 420px) { .vv-gal { grid-template-columns: repeat(3, 1fr); } }
@container vvgal (min-width: 620px) { .vv-gal { grid-template-columns: repeat(4, 1fr); } }
@container vvgal (min-width: 880px) { .vv-gal { grid-template-columns: repeat(5, 1fr); } }
@container vvgal (min-width: 1080px) { .vv-gal { grid-template-columns: repeat(6, 1fr); } }
@supports not (container-type: inline-size) {
  @media (min-width: 420px) { .vv-gal { grid-template-columns: repeat(3, 1fr); } }
  @media (min-width: 880px) { .vv-gal { grid-template-columns: repeat(5, 1fr); } }
}

.vv-gal-item { position: relative; margin: 0; border-radius: var(--radius-md); overflow: hidden; background: var(--surface-3); }
.vv-gal-thumb-btn { display: block; width: 100%; height: 100%; padding: 0; border: 0; background: var(--surface-3); cursor: pointer; position: relative; }
.vv-gal-thumb-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-gal-thumb { width: 100%; height: 100%; object-fit: cover; display: block; }
.vv-gal-more {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--ink) 60%, transparent); color: var(--canvas); font-size: 15px; font-weight: 600;
}
.vv-gal-caption {
  position: absolute; left: 0; right: 0; bottom: 0; margin: 0; padding: var(--space-1) var(--space-2);
  font-size: 11px; color: var(--canvas); background: linear-gradient(transparent, color-mix(in srgb, var(--ink) 65%, transparent));
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; pointer-events: none;
}
.vv-gal-check {
  position: absolute; top: var(--space-1); left: var(--space-1); width: 24px; height: 24px; border-radius: var(--radius-sm);
  border: 1px solid var(--canvas); background: color-mix(in srgb, var(--ink) 45%, transparent); color: var(--canvas);
  display: flex; align-items: center; justify-content: center; opacity: 0; cursor: pointer; padding: 0;
  transition: opacity var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}
.vv-gal-item:hover .vv-gal-check, .vv-gal-check:focus-visible { opacity: 1; }
.vv-gal-check-on { opacity: 1; background: var(--primary); border-color: var(--primary); }
.vv-gal-item-selected { outline: 2px solid var(--primary); outline-offset: 2px; }
@media (any-pointer: coarse) { .vv-gal-check { opacity: 1; } }
/* Audit 2026-09-23 (P2): 24×24, below the touch minimum in any density — same expanded hit-area
   utility as .vv-fchip (FC-01)/.vv-group-header-add (arriba, "Expanded touch hit-area").
   `.vv-gal-check` is already `position: absolute` (relative to `.vv-gal-item`, already positioned),
   so it's already its own containing block for a `::before` — visual size and the corner offset
   stay untouched, only the tap target grows, under a coarse pointer. `.vv-gal-item` itself sets
   `overflow: hidden` for the thumbnail's rounded corners, which clips this expansion on the two
   edges nearest the corner (top/left, since the check sits at `--space-1` from both) — the other
   two edges have room and grow the full 12px; see the limitation documented in the utility. */
@media (any-pointer: coarse) {
  .vv-gal-check { --vv-hit-x: -12px; --vv-hit-y: -12px; }
}

/* ===== Lightbox ===== */
.vv-lb-scrim {
  position: fixed; inset: 0; background: var(--scrim); display: flex; align-items: center; justify-content: center;
  z-index: 80; animation: vv-lb-fade var(--dur-effects-default) var(--spring-effects-default);
}
@keyframes vv-lb-fade { from { opacity: 0; } to { opacity: 1; } }
.vv-lb {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: min(92vw, 1000px); height: min(90vh, 720px); outline: none;
}
.vv-lb-inline { position: relative; width: 100%; height: 100%; background: var(--ink); border-radius: var(--radius-lg); }
.vv-lb-stage { position: relative; flex: 1 1 auto; min-height: 0; width: 100%; display: flex; align-items: center; justify-content: center; }
.vv-lb-img { max-width: 100%; max-height: 90%; object-fit: contain; border-radius: var(--radius-md); transition: transform var(--dur-spatial-default) var(--spring-spatial-default); }
.vv-lb-img-zoomed { transform: scale(2); }
.vv-lb-close {
  position: absolute; top: var(--space-3); right: var(--space-3); z-index: 2; width: var(--row-h); height: var(--row-h);
  border-radius: var(--radius-full); border: 0; background: color-mix(in srgb, var(--ink) 55%, transparent); color: var(--canvas);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.vv-lb-close:hover { background: color-mix(in srgb, var(--ink) 75%, transparent); }
.vv-lb-close:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-lb-nav {
  /* Audit 2026-09-23 (P2): was a fixed 40px while .vv-lb-close (same file) already scales with
     --row-h — prev/next are the primary navigation of a fullscreen image viewer, not a secondary
     action, so they get the same treatment. */
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; width: var(--row-h); height: var(--row-h);
  border-radius: var(--radius-full); border: 0; background: color-mix(in srgb, var(--ink) 45%, transparent); color: var(--canvas);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.vv-lb-nav:hover { background: color-mix(in srgb, var(--ink) 65%, transparent); }
.vv-lb-nav:disabled { opacity: 0.35; cursor: default; }
.vv-lb-nav:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-lb-nav-prev { left: var(--space-3); }
.vv-lb-nav-next { right: var(--space-3); }
.vv-lb-counter {
  position: absolute; top: var(--space-3); left: 50%; transform: translateX(-50%);
  padding: 2px var(--space-2); border-radius: var(--radius-full); background: color-mix(in srgb, var(--ink) 55%, transparent);
  color: var(--canvas); font-size: 11px; font-variant-numeric: tabular-nums;
}
.vv-lb-foot { flex: none; width: 100%; display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-2) 0; }
.vv-lb-foot-caption { color: var(--canvas); font-size: 12px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-lb-foot-actions { display: flex; gap: var(--space-1); flex: none; }
.vv-lb-foot-btn { color: var(--canvas); }
.vv-lb-foot-btn:hover { background: color-mix(in srgb, var(--canvas) 15%, transparent); }

/* ===== AIAnswer ===== */
.vv-aia { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); background: var(--surface); }
.vv-aia-head { display: flex; align-items: center; gap: var(--space-2); }
.vv-aia-avatar {
  width: 24px; height: 24px; border-radius: var(--radius-full); background: var(--accent-purple); color: var(--on-primary);
  display: flex; align-items: center; justify-content: center; flex: none;
}
.vv-aia-name { font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.vv-aia-time { margin-left: auto; font-size: 11px; color: var(--ink-muted); white-space: nowrap; }
/* VA-01 (2.9.0, auditoria adaptativa): medida de lectura — sin max-width el texto corre a lo
   ancho completo del panel contenedor (~150+ caracteres a 1600px dentro de RecordPage/ListPage sin
   envoltorio angosto); `ch` en vez de px para que escale con la tipografia, mismo patron que
   `.vv-emptystate-desc{max-width:360px}` ya usa para texto libre. No-op dentro de Chat: el panel de
   mensajes ya acota su ancho vía `--vv-chat-aside-w`/resto, por debajo de 75ch casi siempre. */
.vv-aia-body { font-size: var(--text-base); line-height: 1.5; color: var(--ink); max-width: 75ch; }
.vv-aia-body p { margin: 0 0 var(--space-2); }
.vv-aia-body p:last-child { margin-bottom: 0; }
.vv-aia-list { margin: 0 0 var(--space-2); padding-left: 1.25em; }
.vv-aia-link { color: var(--link, var(--primary)); }
.vv-aia-cite {
  display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 4px;
  margin: 0 1px; border: 0; border-radius: var(--radius-full); background: var(--surface-2); color: var(--ink-muted);
  font-size: 10px; font-weight: 600; line-height: 1; vertical-align: super; cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.vv-aia-cite:hover, .vv-aia-cite:focus-visible { background: var(--primary-soft); color: var(--primary); }
.vv-aia-cite:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-aia-cursor { display: inline-block; color: var(--primary); animation: vv-aia-blink var(--dur-effects-slow) var(--spring-effects-slow) infinite alternate; }
@keyframes vv-aia-blink { from { opacity: 1; } to { opacity: 0; } }

.vv-aia-error { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-radius: var(--radius-md); background: var(--danger-soft); color: var(--danger); font-size: 12px; }
.vv-aia-error-icon { flex: none; }

.vv-aia-sources { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--border-subtle); }
.vv-aia-source { display: flex; align-items: center; gap: var(--space-2); min-height: var(--row-h); border-bottom: 1px solid var(--border-subtle); font-size: 12px; transition: background-color var(--dur-fast) var(--ease-out); }
.vv-aia-source:last-child { border-bottom: 0; }
.vv-aia-source-hl { background: var(--primary-soft); }
.vv-aia-source-num {
  flex: none; width: 16px; height: 16px; border-radius: var(--radius-full); background: var(--surface-2); color: var(--ink-muted);
  font-size: 10px; font-weight: 600; display: flex; align-items: center; justify-content: center;
}
.vv-aia-source-icon { flex: none; color: var(--ink-muted); }
.vv-aia-source-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
.vv-aia-source-page { flex: none; color: var(--ink-muted); }
.vv-aia-source-link { flex: none; color: var(--ink-muted); display: flex; align-items: center; }
.vv-aia-source-link:hover { color: var(--primary); }
.vv-aia-source-link:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; border-radius: var(--radius-sm); }

.vv-aia-actions { display: flex; gap: var(--space-1); opacity: 0; transition: opacity var(--dur-fast) var(--ease-out); }
.vv-aia:hover .vv-aia-actions, .vv-aia:focus-within .vv-aia-actions { opacity: 1; }
@media (any-pointer: coarse) { .vv-aia-actions { opacity: 1; } }

/* ---- collab ---- */
/* Vive UI — frag/collab.css. Chat (ThreadList/MessageList/Composer/Chat), PermissionMatrix,
   CashflowTile y ExportCSV. Solo tokens: colores, radios, espaciado y movimiento vienen de
   tokens.css / bundle.css. */

/* =====================================================================
   Chat — layout de dos columnas con @container
   ===================================================================== */
.vv-chat {
  display: flex; width: 100%; height: 100%; min-width: 0; min-height: 0; background: var(--surface);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-xl); overflow: hidden;
  container-type: inline-size; container-name: vvchat;
}
.vv-chat-aside { width: var(--vv-chat-aside-w, 320px); /* resize */ flex: none; min-height: 0; display: flex; flex-direction: column; border-right: 1px solid var(--border-subtle); background: var(--surface); }
.vv-chat-main { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--surface); }
.vv-chat-empty { flex: 1 1 auto; display: flex; align-items: center; justify-content: center; }
.vv-chat-header {
  flex: none; display: flex; align-items: center; gap: var(--space-3); min-height: var(--row-h);
  padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border-subtle);
}
.vv-chat-header-text { display: flex; flex-direction: column; gap: 0; min-width: 0; }
.vv-chat-header-title { font-size: var(--text-base); font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-chat-back {
  display: none; align-items: center; justify-content: center; width: var(--row-h); height: var(--row-h); flex: none;
  border: 0; background: transparent; color: var(--ink); border-radius: var(--radius-full); cursor: pointer;
  transition: background var(--dur-effects-fast) var(--spring-effects-fast);
}
.vv-chat-back:hover { background: var(--surface-2); }
.vv-chat-back:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }

@container vvchat (max-width: 839.98px) {
  .vv-chat[data-view="list"] .vv-chat-main { display: none; }
  .vv-chat[data-view="detail"] .vv-chat-aside { display: none; }
  .vv-chat-aside, .vv-chat-main { width: 100%; }
  .vv-chat-back { display: inline-flex; }
}

/* =====================================================================
   ThreadList
   ===================================================================== */
.vv-threadlist { display: flex; flex-direction: column; min-height: 0; height: 100%; }
.vv-threadlist-search { position: relative; flex: none; padding: var(--space-3); }
.vv-threadlist-search-icon { position: absolute; left: calc(var(--space-3) + var(--space-2)); top: 50%; transform: translateY(-50%); color: var(--ink-faint); display: flex; pointer-events: none; }
.vv-threadlist-search-icon :is(svg, .vv-icon) { width: 15px; height: 15px; }
.vv-threadlist-search-input { padding-left: calc(var(--space-2) + 15px + var(--space-2)); width: 100%; box-sizing: border-box; }
.vv-threadlist-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }

.vv-thread-row {
  display: flex; align-items: flex-start; gap: var(--space-3); width: 100%; box-sizing: border-box;
  padding: var(--space-2) var(--space-3); min-height: calc(var(--row-h) * 2 + 8px);
  border: 0; background: transparent; cursor: pointer; text-align: left; font: inherit; color: inherit;
  transition: background var(--dur-effects-fast) var(--spring-effects-fast);
}
.vv-thread-row:hover { background: var(--surface-2); }
.vv-thread-row:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
.vv-thread-row-active { background: var(--primary-soft); }
.vv-thread-row-body { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; flex: 1 1 auto; justify-content: center; }
.vv-thread-row-top, .vv-thread-row-bottom { display: flex; align-items: center; gap: var(--space-2); min-width: 0; }
.vv-thread-row-title { font-size: var(--text-base); font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; }
.vv-thread-row-time { flex: none; }
.vv-thread-row-last { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; }
.vv-thread-row-unread .vv-thread-row-last { color: var(--ink); font-weight: 500; }
.vv-thread-row-badge { flex: none; }

/* =====================================================================
   MessageList
   ===================================================================== */
.vv-msglist-wrap { position: relative; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.vv-msglist { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-1); }
.vv-msglist-skeleton { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-2) 0; }
.vv-msglist-skeleton-right { align-self: flex-end; }

.vv-msg-day { align-self: center; font-size: 11px; color: var(--ink-muted); padding: var(--space-2) var(--space-3); margin: var(--space-2) 0; }
.vv-msg-system { align-self: center; font-size: 11px; color: var(--ink-muted); padding: var(--space-1) var(--space-3); }

.vv-msg-row { display: flex; align-items: flex-end; gap: var(--space-2); max-width: 76%; margin-top: var(--space-3); }
.vv-msg-row-grouped { margin-top: 2px; }
.vv-msg-row-theirs { align-self: flex-start; }
.vv-msg-row-mine { align-self: flex-end; flex-direction: row-reverse; }
.vv-msg-avatar-slot { width: 28px; flex: none; display: flex; align-items: flex-end; }
.vv-msg-col { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.vv-msg-row-mine .vv-msg-col { align-items: flex-end; }
.vv-msg-author { padding: 0 var(--space-2); color: var(--ink-muted); }

.vv-msg-bubble {
  border-radius: var(--radius-lg); padding: var(--space-2) var(--space-3); font-size: var(--text-base); line-height: 1.4;
  word-break: break-word; display: flex; flex-direction: column; gap: var(--space-1);
}
.vv-msg-bubble-theirs { background: var(--surface-2); color: var(--ink); border-bottom-left-radius: var(--radius-sm); }
.vv-msg-row-grouped.vv-msg-row-theirs .vv-msg-bubble { border-bottom-left-radius: var(--radius-lg); }
.vv-msg-bubble-mine { background: var(--primary-soft); color: var(--ink); border-bottom-right-radius: var(--radius-sm); }
.vv-msg-row-grouped.vv-msg-row-mine .vv-msg-bubble { border-bottom-right-radius: var(--radius-lg); }
.vv-msg-text { white-space: pre-wrap; }
.vv-msg-bubble-typing { padding: var(--space-2) var(--space-3); }

.vv-msg-meta { display: flex; align-items: center; gap: var(--space-1); padding: 0 var(--space-2); }
.vv-msg-status { display: inline-flex; color: var(--ink-faint); }
.vv-msg-status-double { display: inline-flex; }
.vv-msg-status-double :is(svg, .vv-icon):last-child { margin-left: -6px; }
.vv-msg-status-read { color: var(--primary); }

.vv-msg-attachment { max-width: 260px; }
.vv-msg-file { display: flex; align-items: center; gap: var(--space-2); }
.vv-msg-file-icon { color: var(--ink-muted); flex: none; }
.vv-msg-file-body { display: flex; flex-direction: column; min-width: 0; }
.vv-msg-file-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.vv-typing-dots { display: inline-flex; align-items: center; gap: 3px; height: 14px; }
.vv-typing-dot { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--ink-faint); animation: vv-typing-bounce var(--dur-effects-slow) var(--spring-effects-slow) infinite; }
.vv-typing-dot:nth-child(2) { animation-delay: 0.12s; }
.vv-typing-dot:nth-child(3) { animation-delay: 0.24s; }
@keyframes vv-typing-bounce { 0%, 80%, 100% { opacity: 0.35; } 40% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .vv-typing-dot { animation: none; opacity: 0.7; } }

.vv-msglist-newbtn {
  position: absolute; left: 50%; bottom: var(--space-3); transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: var(--space-1); border: 1px solid var(--border-subtle);
  background: var(--surface); color: var(--ink); border-radius: var(--radius-full); padding: var(--space-1) var(--space-3);
  font-size: 12px; font-weight: 500; box-shadow: var(--shadow-md); cursor: pointer;
  transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast), background var(--dur-effects-fast) var(--spring-effects-fast);
}
.vv-msglist-newbtn:hover { background: var(--surface-2); transform: translateX(-50%) translateY(-1px); }
.vv-msglist-newbtn:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }

/* =====================================================================
   Composer
   ===================================================================== */
.vv-composer { flex: none; border-top: 1px solid var(--border-subtle); padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); }
.vv-composer-disabled-note { padding: 0 var(--space-1); }
.vv-composer-row { display: flex; align-items: flex-end; gap: var(--space-2); }
.vv-composer-textarea { flex: 1 1 auto; min-width: 0; }
.vv-composer-textarea .vv-textarea { max-height: calc(1.4em * 6 + var(--space-4)); overflow-y: auto; resize: none; }
.vv-composer-count { align-self: flex-end; }
.vv-composer-attachments { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.vv-composer-chip {
  display: inline-flex; align-items: center; gap: var(--space-1); background: var(--surface-2); color: var(--ink);
  border-radius: var(--radius-full); padding: var(--space-1) var(--space-2); font-size: 12px; max-width: 220px;
}
.vv-composer-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-composer-chip-remove { display: inline-flex; border: 0; background: transparent; color: var(--ink-muted); cursor: pointer; padding: 2px; border-radius: var(--radius-full); }
.vv-composer-chip-remove:hover { color: var(--ink); background: var(--surface-3); }
.vv-composer-chip-remove:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-composer-disabled { opacity: 0.85; }

/* =====================================================================
   PermissionMatrix
   ===================================================================== */
.vv-permatrix-wrap { display: flex; flex-direction: column; gap: var(--space-3); }
.vv-permatrix-scroll { overflow: auto; border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); max-height: 420px; }
/* Audit 2026-09-23 (P3): at 375px only ~1 of 4 role columns is visible with no hint there's more
   to scroll to. Static inset shadow (not the local/scroll gradient trick — this container already
   scrolls vertically too, see the DataTable medium-range note above for why that trick doesn't mix
   cleanly with a second scroll axis) — 2026-09-23 follow-up: made conditional on real overflow via
   `data-overflow-start`/`-end` (bundle.js ResizeObserver/scroll listener), same as DataTable; a
   shadow that's always visible even when there's nothing to scroll to was decoration, not signal. */
.vv-permatrix-scroll[data-overflow-start] { box-shadow: inset 10px 0 8px -8px rgba(0, 0, 0, .12); }
.vv-permatrix-scroll[data-overflow-end] { box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, .12); }
.vv-permatrix-scroll[data-overflow-start][data-overflow-end] { box-shadow: inset -10px 0 8px -8px rgba(0, 0, 0, .12), inset 10px 0 8px -8px rgba(0, 0, 0, .12); }
.vv-permatrix { border-collapse: separate; border-spacing: 0; width: 100%; font-size: var(--text-base); }
.vv-permatrix thead th { position: sticky; top: 0; z-index: 2; background: var(--surface); }
.vv-permatrix-corner { position: sticky; left: 0; top: 0; z-index: 3; background: var(--surface); min-width: 200px; border-bottom: 1px solid var(--border-subtle); border-right: 1px solid var(--border-subtle); }
.vv-permatrix-rolehead { min-width: 132px; padding: var(--space-2); border-bottom: 1px solid var(--border-subtle); font-weight: 500; text-align: left; }
.vv-permatrix-rolehead-inner { display: flex; align-items: center; gap: var(--space-2); }
.vv-permatrix-role-name { font-size: 12px; font-weight: 600; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-permatrix-reshead {
  position: sticky; left: 0; z-index: 1; background: var(--surface); text-align: left; font-weight: 500;
  padding: 0; border-right: 1px solid var(--border-subtle); border-bottom: 1px solid var(--border-subtle);
  min-width: 200px; height: var(--row-h);
}
.vv-permatrix-reshead-btn {
  width: 100%; height: var(--row-h); box-sizing: border-box; text-align: left; border: 0; background: transparent; color: var(--ink);
  font: inherit; font-size: var(--text-base); padding: 0 var(--space-3); cursor: pointer;
  transition: background var(--dur-effects-fast) var(--spring-effects-fast);
}
.vv-permatrix-reshead-btn:hover { background: var(--surface-2); }
.vv-permatrix-reshead-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
.vv-permatrix-grouphead-row th { position: sticky; left: 0; }
.vv-permatrix-grouphead {
  text-align: left; background: var(--surface-2); color: var(--ink-muted); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em; padding: var(--space-1) var(--space-3); height: var(--row-compact);
  border-bottom: 1px solid var(--border-subtle);
}
.vv-permatrix-selectall-row { background: var(--surface-2); }
.vv-permatrix-selectall-row th, .vv-permatrix-selectall-row td { border-bottom: 1px solid var(--border-subtle); }
.vv-permatrix-selectall-label { color: var(--ink-muted); font-size: 12px; padding: 0 var(--space-3); background: var(--surface-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.vv-permatrix-cell-wrap { position: relative; text-align: center; border-bottom: 1px solid var(--border-subtle); height: var(--row-h); padding: 0; }
.vv-permatrix-cell { display: flex; align-items: center; justify-content: center; height: var(--row-h); }
.vv-permatrix-cell:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; border-radius: var(--radius-sm); }
.vv-permatrix-cell .vv-checkbox-row { justify-content: center; }
.vv-permatrix-cell-inherited, .vv-permatrix-cell-locked { color: var(--ink-faint); }
.vv-permatrix-inheritwrap, .vv-permatrix-lockwrap { display: inline-flex; align-items: center; gap: 2px; }
.vv-permatrix-level-btn {
  display: inline-flex; align-items: center; gap: var(--space-1); border: 1px solid transparent; background: transparent;
  color: var(--ink-muted); border-radius: var(--radius-md); padding: var(--space-1) var(--space-2); cursor: pointer; font: inherit;
  transition: background var(--dur-effects-fast) var(--spring-effects-fast), color var(--dur-effects-fast) var(--spring-effects-fast);
}
/* Audit 2026-09-23 (P2): ~30px tall, below --row-h (48px touch). The cell wrapper already reserves
   --row-h (`.vv-permatrix-cell-wrap`/`-cell`), so growing the button to fill it doesn't change the
   table's row height, just closes the gap between the visual button and the tappable cell. */
[data-density="touch"] .vv-permatrix-level-btn { min-height: var(--row-h); padding: 0 var(--space-3); }
.vv-permatrix-level-btn:hover { background: var(--surface-2); color: var(--ink); }
.vv-permatrix-level-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-permatrix-level-text { display: none; font-size: 11px; }
[data-density="touch"] .vv-permatrix-level-text { display: inline; }
.vv-permatrix-pending-dot {
  position: absolute; top: 6px; right: 6px; width: 6px; height: 6px; border-radius: var(--radius-full);
  background: var(--warning); z-index: 1;
}
.vv-permatrix-formactions { position: sticky; bottom: 0; background: var(--surface); }

/* =====================================================================
   CashflowTile (extiende .vv-stattile)
   ===================================================================== */
.vv-cashflow-tile { cursor: default; display: flex; flex-direction: column; gap: var(--space-1); }
.vv-cashflow-tile[role="button"] { cursor: pointer; }
.vv-cashflow-tile[role="button"]:hover { background: var(--surface-2); }
.vv-cashflow-tile[role="button"]:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-cashflow-value { display: block; font-size: 28px; line-height: 1.15; font-weight: 600; letter-spacing: -0.015em; font-variant-numeric: tabular-nums; }
.vv-cashflow-period { color: var(--ink-muted); }
.vv-cashflow-deltas { display: flex; align-items: center; gap: var(--space-4); margin-top: var(--space-1); }
.vv-cashflow-bars { display: block; margin-top: var(--space-1); }

/* =====================================================================
   RoleSwitcher · ActivityLog · Mailbox · SelectionBar (2.12.0)
   Portadas desde los .patch incubating (kit-todo v2). RoleSwitcher reutiliza
   .vv-menu-list/.vv-menu-item* del kit para sus filas; aquí solo van las clases
   propias de cada pieza. Solo tokens.
   ===================================================================== */

/* ===== RoleSwitcher ===== */
/* Las filas de «Tus vistas» y «Sujetos» usan .vv-menu-list/.vv-menu-item* del kit
   (hover --surface-3, activo --primary-soft + .vv-menu-item-check). Solo el ancho de la
   hoja en móvil y las zonas con scroll propio (subjects/audit) son nuevas. */
.vv-roleswitcher-scroll { max-height: calc(var(--row-h) * 6); overflow: auto; }
.vv-roleswitcher-hint { margin: 0 0 var(--space-2); }
.vv-roleswitcher-empty { margin: 0; padding: var(--space-2) var(--space-3); }
.vv-roleswitcher-subject-meta { flex: 0 1 auto; min-width: 0; max-width: 45%; }
/* La franja de suplantación es icono · texto · acción: con una acción larga y poco ancho,
   el botón baja a su propia línea antes que el texto pisarlo. */
.vv-roleswitcher-impersonate { flex-wrap: wrap; }
.vv-roleswitcher-impersonate .vv-banner-title,
.vv-roleswitcher-impersonate .vv-banner-body { min-width: 0; overflow-wrap: anywhere; }
@media (max-width: 640px) { .vv-roleswitcher { width: 100%; } }

/* ===== ActivityLog ===== */
.vv-activitylog { display: grid; margin: 0; padding: 0; list-style: none; }
.vv-activitylog-row { display: grid; grid-template-columns: var(--icon) 1fr auto; gap: var(--space-3); align-items: start; padding: var(--space-3) 0; border-bottom: 1px solid var(--border-subtle); }
.vv-activitylog-row:last-child { border-bottom: 0; }
.vv-activitylog-icon { width: var(--icon); height: var(--icon); margin-top: 1px; flex: none; color: var(--ink-muted); }
.vv-activitylog-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.vv-activitylog-action { color: var(--ink); font-size: var(--text-base); }
.vv-activitylog-action a { color: var(--primary); text-decoration: none; }
.vv-activitylog-meta { color: var(--ink-muted); font-size: 11px; }
.vv-activitylog-time { color: var(--ink-muted); font-size: 11px; white-space: nowrap; }

/* ===== Mailbox ===== */
.vv-mailbox { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.vv-mailbox-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; }
.vv-mailbox-title { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.vv-mailbox-title h3 { margin: 0; }
.vv-mailbox-compose-wrap { display: inline-flex; flex: none; }
.vv-mailbox-list { display: grid; margin: 0; padding: 0; list-style: none; }
.vv-mailbox-msg { display: grid; grid-template-columns: var(--icon) 1fr auto; gap: var(--space-3); align-items: start; padding: var(--space-3) 0; border-bottom: 1px solid var(--border-subtle); }
.vv-mailbox-msg:last-child { border-bottom: 0; }
.vv-mailbox-msg-icon { width: var(--icon); height: var(--icon); margin-top: 1px; flex: none; color: var(--ink-muted); }
.vv-mailbox-msg-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.vv-mailbox-msg-subject { color: var(--ink); font-size: var(--text-base); font-weight: 500; }
.vv-mailbox-msg-meta { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.vv-mailbox-msg-when { color: var(--ink-muted); font-size: 11px; white-space: nowrap; }
.vv-mailbox-msg-preview { color: var(--ink-muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-mailbox-scroll { max-height: calc(var(--row-h) * 8); overflow: auto; }
.vv-mailbox-compose { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }

/* ===== SelectionBar ===== */
.vv-selectionbar { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; padding: var(--space-2) var(--space-3); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md); }
.vv-selectionbar-count { color: var(--ink); font-size: var(--text-base); font-weight: 600; margin-inline-end: auto; }
.vv-selectionbar-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/* ---- resize ---- */
/* Vive UI — frag/resize.css: ResizeHandle. */

.vv-resizehandle {
  position: relative; flex: none; z-index: 3; display: flex; align-items: center; justify-content: center;
  touch-action: none; -webkit-user-select: none; user-select: none; outline: none;
}
.vv-resizehandle-v { width: 8px; margin: 0 -4px; cursor: col-resize; align-self: stretch; }
.vv-resizehandle-h { height: 8px; margin: -4px 0; cursor: row-resize; width: 100%; }
.vv-resizehandle:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -1px; border-radius: var(--radius-sm); }
/* Audit 2026-09-23 (P2): the real grip is 8px on its short axis — `margin: -4px` only recenters it
   over the neighboring edge, it doesn't grow the hitbox. `.vv-resizehandle-collapsed::before` (the
   pastilla indicator) already owns `::before` on this same element, so the shared hit-area utility
   (arriba, "Expanded touch hit-area") is wired to `::after` here instead — same utility, just the
   other pseudo-element, doesn't touch the 8px visual grip nor the pastilla. The long axis is already
   full-length (`align-self: stretch` / `width: 100%`), so only the short axis needs to grow —
   `--vv-hit-x`/`--vv-hit-y` default to `0px` on the other axis, same effect as the previous
   `top: 0; bottom: 0`/`left: 0; right: 0`. */
@media (any-pointer: coarse) {
  .vv-resizehandle-v { --vv-hit-x: calc((8px - var(--row-touch)) / 2); }
  .vv-resizehandle-h { --vv-hit-y: calc((8px - var(--row-touch)) / 2); }
}

/* Reposo: solo una línea de 1px, apenas visible. */
.vv-resizehandle-line {
  position: absolute; background: var(--border-subtle);
}
.vv-resizehandle-v .vv-resizehandle-line { top: 0; bottom: 0; left: 50%; width: 1px; transform: translateX(-50%); }
.vv-resizehandle-h .vv-resizehandle-line { left: 0; right: 0; top: 50%; height: 1px; transform: translateY(-50%); }

/* Hover/arrastre: barra de 2px que crece desde el centro. */
.vv-resizehandle-bar {
  position: absolute; background: var(--primary); border-radius: var(--radius-full);
  transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast), opacity var(--dur-effects-fast) var(--ease-out);
  opacity: 0;
}
.vv-resizehandle-v .vv-resizehandle-bar { top: 0; bottom: 0; left: 50%; width: 2px; transform: translateX(-50%) scaleY(0); transform-origin: center; }
.vv-resizehandle-h .vv-resizehandle-bar { left: 0; right: 0; top: 50%; height: 2px; transform: translateY(-50%) scaleX(0); transform-origin: center; }
.vv-resizehandle:hover .vv-resizehandle-bar,
.vv-resizehandle:focus-visible .vv-resizehandle-bar,
.vv-resizehandle[data-dragging="true"] .vv-resizehandle-bar { opacity: 1; }
.vv-resizehandle-v:hover .vv-resizehandle-bar, .vv-resizehandle-v:focus-visible .vv-resizehandle-bar, .vv-resizehandle-v[data-dragging="true"] .vv-resizehandle-bar { transform: translateX(-50%) scaleY(1); }
.vv-resizehandle-h:hover .vv-resizehandle-bar, .vv-resizehandle-h:focus-visible .vv-resizehandle-bar, .vv-resizehandle-h[data-dragging="true"] .vv-resizehandle-bar { transform: translateY(-50%) scaleX(1); }

/* Pastilla central (M3 drag handle): 4×32 (o 32×4 en horizontal), border-strong. */
.vv-resizehandle-pill {
  position: absolute; top: 50%; left: 50%; background: var(--border-strong); border-radius: var(--radius-full);
  opacity: 0; transition: transform var(--dur-spatial-fast) var(--spring-spatial-fast), opacity var(--dur-effects-fast) var(--ease-out);
}
.vv-resizehandle-v .vv-resizehandle-pill { width: 4px; height: 32px; transform: translate(-50%, -50%) scaleY(0); transform-origin: center; }
.vv-resizehandle-h .vv-resizehandle-pill { width: 32px; height: 4px; transform: translate(-50%, -50%) scaleX(0); transform-origin: center; }
.vv-resizehandle:hover .vv-resizehandle-pill,
.vv-resizehandle:focus-visible .vv-resizehandle-pill,
.vv-resizehandle[data-dragging="true"] .vv-resizehandle-pill { opacity: 1; }
.vv-resizehandle-v:hover .vv-resizehandle-pill, .vv-resizehandle-v:focus-visible .vv-resizehandle-pill, .vv-resizehandle-v[data-dragging="true"] .vv-resizehandle-pill { transform: translate(-50%, -50%) scaleY(1); }
.vv-resizehandle-h:hover .vv-resizehandle-pill, .vv-resizehandle-h:focus-visible .vv-resizehandle-pill, .vv-resizehandle-h[data-dragging="true"] .vv-resizehandle-pill { transform: translate(-50%, -50%) scaleX(1); }

/* Colapsado: el asa se reduce a un borde interactivo con un pastilla flotante para expandir. */
.vv-resizehandle-collapsed { cursor: pointer; }
.vv-resizehandle-collapsed::before {
  content: ''; position: absolute; background: transparent; border-radius: var(--radius-full);
  transition: background-color var(--dur-effects-fast) var(--ease-out);
}
.vv-resizehandle-v.vv-resizehandle-collapsed::before { top: 0; bottom: 0; left: 50%; width: 4px; transform: translateX(-50%); }
.vv-resizehandle-h.vv-resizehandle-collapsed::before { left: 0; right: 0; top: 50%; height: 4px; transform: translateY(-50%); }
.vv-resizehandle-collapsed:hover::before,
.vv-resizehandle-collapsed:focus-within::before { background: var(--primary-soft); }

/* El Tooltip envuelve el botón en un <span> propio (vv-tooltip-wrap, position:relative): se
   estira para cubrir el asa y centra el botón dentro, en vez de posicionar el botón contra un
   ancestro que no es el suyo. */
.vv-resizehandle-collapsed .vv-tooltip-wrap { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.vv-resizehandle-expand {
  background: var(--surface); border: 1px solid var(--border-subtle); box-shadow: var(--shadow-sm); opacity: 0.85;
  transition: opacity var(--dur-effects-fast) var(--ease-out), transform var(--dur-spatial-fast) var(--spring-spatial-fast);
}
.vv-resizehandle-collapsed:hover .vv-resizehandle-expand,
.vv-resizehandle-collapsed:focus-within .vv-resizehandle-expand,
.vv-resizehandle-expand:focus-visible { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .vv-resizehandle-bar, .vv-resizehandle-pill, .vv-resizehandle-expand, .vv-resizehandle-collapsed::before { transition-duration: 1ms; }
}

/* Bajo 600px de contenedor, Chat pasa a una sola columna (data-view) y Drawer a sheet: el asa no
   pinta nada que redimensionar y ocuparía un hueco de 8px de más — se oculta junto al resto del
   layout de escritorio en vez de duplicar la condición en JS. */
@container vvchat (max-width: 599.98px) {
  .vv-chat .vv-resizehandle { display: none; }
}
@container (max-width: 599.98px) {
  .vv-drawer-scrim:not(.vv-drawer-scrim-persistent) .vv-resizehandle { display: none; }
}

/* ---- audio ---- */
/* Vive UI — frag/audio.css: Slider, AudioPlayer, TrackList (2.5.0). Clases vv-slider*, vv-ap-*, vv-audio*, vv-track*. */

/* ===== Slider ===== */
/* Primitivo M3 Expressive sobre <input type="range"> real: la apariencia nativa se anula
   (appearance:none, opacidad 0) y la pista/asa se dibujan con elementos absolutos detrás, leyendo
   --vv-slider-pct (0%–100%) que fija Slider por JS a cada render. El input conserva el foco, el
   teclado (flechas nativas con `step`) y el area de arrastre real — nunca se reimplementa. */
.vv-slider {
  position: relative; display: inline-flex; align-items: center; width: 100%; min-width: 64px;
  height: var(--row-h); flex: none;
}
.vv-slider-sm { min-width: 48px; }
.vv-slider-track, .vv-slider-fill {
  position: absolute; top: 50%; height: 4px; border-radius: var(--radius-full); transform: translateY(-50%);
  transition: background-color var(--dur-effects-fast) var(--spring-effects-fast);
}
/* Pista inactiva: recortada 6px a cada lado del asa (hueco M3E) — arranca 8px después del asa
   (2px mitad del asa de 4px + 6px de hueco) y llega hasta el final. */
.vv-slider-track {
  left: calc(var(--vv-slider-pct) + 8px); right: 0; background: var(--border-subtle);
  width: max(0px, calc(100% - var(--vv-slider-pct) - 8px));
}
/* Relleno activo: de 0 hasta 8px antes del asa. */
.vv-slider-fill {
  left: 0; background: var(--primary);
  width: max(0px, calc(var(--vv-slider-pct) - 8px));
}
/* Punto de tope de 4px al final de la pista inactiva (M3E): queda bajo el relleno cuando el valor
   llega al máximo, así que no hace falta ocultarlo a mano. */
.vv-slider-stop {
  position: absolute; top: 50%; right: 0; width: 4px; height: 4px; border-radius: var(--radius-full);
  background: var(--border-subtle); transform: translate(0, -50%); z-index: 0;
}
.vv-slider-track, .vv-slider-fill { z-index: 1; }
/* Asa: pastilla vertical 4×32 (Compact 4×24, Touch 4×44 — ver reglas de densidad abajo), con hueco
   de 6px a cada lado ya resuelto por el corte de la pista/relleno de arriba. Al arrastrar se estrecha
   a 2px ("pressed" M3E) con el muelle de marca. */
.vv-slider-thumb {
  position: absolute; top: 50%; left: var(--vv-slider-pct); z-index: 2; pointer-events: none;
  width: 4px; height: 32px; border-radius: var(--radius-full); background: var(--primary);
  transform: translate(-50%, -50%);
  transition: width var(--dur-spring) var(--ease-spring), left var(--dur-spatial-fast) var(--spring-spatial-fast), background-color var(--dur-effects-fast) var(--spring-effects-fast);
}
.vv-slider-sm .vv-slider-thumb { height: 20px; }
[data-density="compact"] .vv-slider-thumb { height: 24px; }
[data-density="compact"] .vv-slider-sm .vv-slider-thumb { height: 16px; }
[data-density="touch"] .vv-slider-thumb { height: 44px; }
[data-density="touch"] .vv-slider-sm .vv-slider-thumb { height: 28px; }
.vv-slider-dragging .vv-slider-thumb { width: 2px; }
.vv-slider-input {
  position: relative; z-index: 3; appearance: none; -webkit-appearance: none; width: 100%; height: 100%;
  margin: 0; background: transparent; cursor: pointer;
}
.vv-slider-input:focus-visible { outline: none; }
.vv-slider-input:focus-visible ~ .vv-slider-thumb { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 3px; }
.vv-slider-input::-webkit-slider-thumb { appearance: none; -webkit-appearance: none; width: 1px; height: 1px; opacity: 0; }
.vv-slider-input::-moz-range-thumb { width: 1px; height: 1px; opacity: 0; border: none; background: transparent; }
.vv-slider-input::-moz-range-track { background: transparent; border: none; }
.vv-slider-disabled { opacity: .5; }
.vv-slider-disabled .vv-slider-input { cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) { .vv-slider-thumb { transition-duration: 1ms; } }

/* ===== TrackList ===== */
.vv-tracklist { display: flex; flex-direction: column; min-width: 0; }
.vv-tracklist-group-title { padding: var(--space-2) var(--space-3) var(--space-1); }
.vv-track {
  all: unset; box-sizing: border-box; display: flex; align-items: center; gap: var(--space-3);
  height: var(--row-h); flex: none; width: 100%; min-width: 0; padding: 0 var(--space-3);
  cursor: pointer; color: var(--ink); border-radius: var(--radius-sm);
  transition: background-color var(--dur-effects-fast) var(--spring-effects-fast);
}
.vv-track:hover { background: var(--surface-3); }
.vv-track:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
.vv-track-notready { cursor: not-allowed; color: var(--ink-faint); }
.vv-track-num {
  flex: none; width: 24px; text-align: right; font-size: 12px; font-variant-numeric: tabular-nums;
  color: var(--ink-faint); display: flex; align-items: center; justify-content: flex-end;
}
.vv-track-active .vv-track-num { justify-content: center; }
.vv-track-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.vv-track-title { font-size: var(--text-base); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-track-active .vv-track-title { color: var(--primary); font-weight: 500; }
.vv-track-notready .vv-track-title { color: var(--ink-muted); }
.vv-track-artist { font-size: 12px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-track-duration { flex: none; font-size: 12px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.vv-track .vv-badge { flex: none; }
/* Indicador de ecualizador (fila activa): 3 barras que oscilan en bucle mientras reproduce, quietas
   en pausa y con reduced-motion. */
.vv-track-eq { display: flex; align-items: flex-end; justify-content: center; gap: 2px; width: 14px; height: 14px; }
.vv-track-eq-bar {
  width: 2px; height: 40%; border-radius: 1px; background: var(--primary);
  animation: vv-track-eq-bounce var(--dur-spatial-slow) var(--spring-spatial-slow) infinite;
}
.vv-track-eq-bar:nth-child(1) { animation-delay: -0.3s; }
.vv-track-eq-bar:nth-child(2) { animation-delay: -0.15s; }
.vv-track-eq-bar:nth-child(3) { animation-delay: 0s; }
.vv-track-eq-paused .vv-track-eq-bar { animation-play-state: paused; height: 30%; }
@keyframes vv-track-eq-bounce { 0%, 100% { height: 25%; } 50% { height: 100%; } }
@media (prefers-reduced-motion: reduce) {
  .vv-track-eq-bar { animation: none; height: 55%; }
}

/* ===== AudioPlayer ===== */
.vv-audio { min-width: 0; box-sizing: border-box; color: var(--ink); }
.vv-audio-empty { padding: var(--space-4); color: var(--ink-muted); font-size: var(--text-base); }
.vv-sr-only-live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }

/* Botón principal play/pausa: cuadrado redondeado en pausa, círculo al reproducir (muelle de marca);
   el icono cruza en opacidad. */
.vv-ap-play {
  all: unset; box-sizing: border-box; position: relative; flex: none; display: flex; align-items: center;
  justify-content: center; width: 40px; height: 40px; background: var(--primary); color: var(--on-primary);
  border-radius: var(--radius-lg); cursor: pointer;
  transition: border-radius var(--dur-spatial-default) var(--spring-spatial-default), transform var(--dur-spring) var(--ease-spring), background-color var(--dur-effects-fast) var(--spring-effects-fast);
}
.vv-ap-play:hover { background: var(--primary-hover); }
.vv-ap-play:active { transform: scale(.94); }
.vv-ap-play:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-ap-play-playing { border-radius: var(--radius-full); }
.vv-ap-play[disabled] { background: var(--surface-2); color: var(--ink-faint); cursor: not-allowed; }
.vv-ap-play-icon { width: 18px; height: 18px; transition: opacity var(--dur-effects-fast) var(--spring-effects-fast); }
.vv-ap-play-sm { width: 32px; height: 32px; }
.vv-ap-play-sm .vv-ap-play-icon { width: 14px; height: 14px; }
[data-density="touch"] .vv-ap-play { width: 48px; height: 48px; }
[data-density="touch"] .vv-ap-play-sm { width: 40px; height: 40px; }
/* Objetivo táctil: 40px visual en touch (ya crecido desde 32, ver arriba) se queda corto de 48 —
   el hueco con el botón vecino en `.vv-ap-bar-transport` es `--space-1` (4px), justo lo que hace
   falta por lado (4+4=8, la mitad exacta del gap) para no invadir el objetivo táctil contiguo. */
[data-density="touch"] .vv-ap-play-sm { --vv-hit-x: -4px; --vv-hit-y: -4px; }
[data-density="compact"] .vv-ap-play { width: 32px; height: 32px; }
.vv-ap-play-ring {
  width: 16px; height: 16px; border-radius: var(--radius-full); border: 2px solid var(--on-primary);
  border-top-color: transparent; animation: vv-ap-spin var(--dur-spatial-slow) linear infinite;
}
.vv-ap-play-loading { color: var(--on-primary); }
@keyframes vv-ap-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .vv-ap-play { transition-duration: 1ms; }
  .vv-ap-play-ring { animation-duration: 1.2s; }
}

.vv-ap-toggle { color: var(--ink-muted); }
.vv-ap-toggle-active { color: var(--primary); }
.vv-ap-toggle-active :is(svg, .vv-icon) { color: var(--primary); }

.vv-ap-art {
  flex: none; width: 48px; height: 48px; border-radius: var(--radius-md); overflow: hidden;
  background: var(--surface-2); display: flex; align-items: center; justify-content: center; color: var(--ink-faint);
}
.vv-ap-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
[data-density="touch"] .vv-ap-art { width: 56px; height: 56px; }
.vv-ap-art-lg { width: 100%; max-width: 320px; aspect-ratio: 1 / 1; height: auto; border-radius: var(--radius-lg); margin: 0 auto; }
.vv-ap-art-lg :is(svg, .vv-icon) { width: 40px; height: 40px; }

.vv-ap-title { font-size: var(--text-base); font-weight: 500; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.vv-ap-subtitle { font-size: 12px; color: var(--ink-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
.vv-ap-subtitle-error { color: var(--danger); }

/* Slider de posición + onda decorativa: la onda (SVG) se dibuja encima del Slider real, que sigue
   debajo con la pista y recibe foco/teclado. */
.vv-ap-slider-wrap { position: relative; display: block; flex: 1 1 auto; min-width: 0; }
.vv-ap-wave {
  position: absolute; left: 0; top: 50%; height: 4px; transform: translateY(-50%); pointer-events: none;
  overflow: hidden; z-index: 1;
}
.vv-ap-wave-svg { display: block; width: 200%; height: 4px; overflow: visible; }
.vv-ap-wave-flat, .vv-ap-wave-wavy {
  fill: none; stroke: var(--primary); stroke-width: 4; stroke-linecap: round; vector-effect: non-scaling-stroke;
  transition: opacity var(--dur-spatial-default) var(--spring-spatial-default);
}
.vv-ap-wave-wavy { opacity: 0; }
.vv-ap-wave-playing .vv-ap-wave-flat { opacity: 0; }
.vv-ap-wave-playing .vv-ap-wave-wavy { opacity: 1; animation: vv-ap-wave-scroll var(--dur-spatial-slow) linear infinite; }
@keyframes vv-ap-wave-scroll { from { transform: translateX(0); } to { transform: translateX(-10%); } }
@media (prefers-reduced-motion: reduce) {
  .vv-ap-wave-wavy { animation: none !important; opacity: 0 !important; }
  .vv-ap-wave-flat { opacity: 1 !important; }
}

.vv-ap-times { display: flex; align-items: center; justify-content: space-between; font-size: 12px; color: var(--ink-muted); font-variant-numeric: tabular-nums; gap: var(--space-2); }
.vv-ap-time { flex: none; }
.vv-ap-time-remaining { font-size: 12px; color: var(--ink-muted); font-variant-numeric: tabular-nums; flex: none; }

.vv-ap-rate-btn { min-width: 0; font-variant-numeric: tabular-nums; }

/* -- bar -- */
.vv-audio-bar {
  display: flex; flex-direction: column; gap: var(--space-1); padding: var(--space-2) var(--space-4);
  background: var(--surface); border-top: 1px solid var(--border-subtle);
}
.vv-audio-bar-sticky { position: sticky; bottom: 0; padding-bottom: max(var(--space-2), env(safe-area-inset-bottom)); }
/* Audit 2026-09-23 (P3, review-overlap @768): `align-items: center` centers each flex item's own
   box against the row — fair for single-line items, but `.vv-ap-bar-meta` is a 2-line block
   (title+subtitle) whose OWN center sits ~9px below its title's line, so the title's text visually
   sat higher than the (single-line) rate button's text even though both boxes were correctly
   centered. `baseline` aligns by the first line of text in each item instead of by box height —
   exactly matches title-to-"1×" expectations; icon-only siblings (play/prev/next, the art thumbnail)
   have no text baseline of their own, so they fall back to their bottom margin edge, which reads
   fine for a row of controls next to text (same fallback every browser uses for baseline-aligned
   flex rows mixing text and icon buttons). */
.vv-ap-bar-row { display: flex; align-items: baseline; gap: var(--space-3); min-width: 0; }
.vv-ap-bar-meta { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.vv-ap-bar-transport { display: flex; align-items: center; gap: var(--space-1); flex: none; }
.vv-ap-bar-actions { display: flex; align-items: center; gap: var(--space-1); flex: none; margin-left: auto; }
.vv-ap-bar-progress { display: flex; flex-direction: column; gap: 2px; }
.vv-ap-bar-progress .vv-ap-times { padding: 0 2px; }

.vv-audio-bar-minimized { flex-direction: row; align-items: center; gap: var(--space-3); height: 40px; padding: 0 var(--space-3); }
.vv-ap-min-title { flex: 1 1 auto; min-width: 0; font-size: var(--text-base); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-ap-expand { flex: none; }

@media (max-width: 599.98px) {
  .vv-ap-bar-transport-extra { display: none; }
  .vv-ap-bar-progress .vv-ap-times { display: none; }
  .vv-ap-bar-progress .vv-slider-thumb { display: none; }
  .vv-ap-bar-progress .vv-slider { height: 2px; }
  .vv-ap-bar-progress .vv-slider-track { left: 0; width: 100%; }
  .vv-ap-bar-progress .vv-slider-fill { width: var(--vv-slider-pct); }
  .vv-ap-bar-progress { order: -1; margin: 0 calc(-1 * var(--space-4)); }
  .vv-ap-bar-progress .vv-ap-wave { display: none; }
}

/* -- card -- */
.vv-audio-card {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-4); width: min(360px, 100%);
  padding: var(--space-4); background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
}
.vv-ap-card-meta { display: flex; flex-direction: column; align-items: center; gap: 2px; width: 100%; text-align: center; }
.vv-ap-card-meta .vv-ap-title { text-align: center; }
.vv-ap-card-meta .vv-ap-subtitle { text-align: center; }
.vv-ap-transport { display: flex; align-items: center; justify-content: center; gap: var(--space-3); flex-wrap: wrap; }
.vv-ap-progress-row { width: 100%; }
.vv-audio-card .vv-ap-times { width: 100%; }
.vv-ap-rate-row { align-self: flex-end; }

/* -- inline -- */
.vv-audio-inline {
  display: flex; align-items: center; gap: var(--space-3); height: var(--row-h); min-width: 0;
}
.vv-ap-inline-slider { flex: 1 1 auto; min-width: 0; display: block; }

/* ---- marketing ---- */
/* Capa de marketing (2.7.0, decisión del owner 2026-09-21; evidencia: highlander landings, emaus-web,
   Ana Escobar — reports/requests.md fila 6). Cuatro bloques y una barra, solo CSS, sin React: la
   página editorial se compone en Astro/Next/HTML con estas clases más los primitivos de core (vv-btn,
   vv-card, vv-badge). Mismo contrato que el resto: tokens, nunca literales; la marca entra por
   [data-brand] (fuentes display, primario); el ritmo de sección es space-16 y el ancho content-max.
   Fuera de esta capa: hero animado, vinilo, bandas «editoriales» de una sola marca — eso es del producto. */
.vv-mk { --mk-section-y: var(--space-16); --mk-gutter: var(--space-8); font-family: var(--font-sans); color: var(--ink); background: var(--canvas); }
@media (max-width: 839.98px) { .vv-mk { --mk-section-y: var(--space-12); --mk-gutter: var(--space-6); } }
.vv-mk-container { width: min(var(--content-max), 100%); margin-inline: auto; padding-inline: var(--mk-gutter); }
/* Nav: barra de marketing (logo · enlaces · un primario). Pegajosa, hairline, sin sombra. */
.vv-mk-nav { position: sticky; top: 0; z-index: 10; background: color-mix(in srgb, var(--canvas) 88%, transparent); backdrop-filter: blur(12px); border-bottom: 1px solid var(--border-subtle); }
.vv-mk-nav .vv-mk-container { display: flex; align-items: center; gap: var(--space-4); min-height: calc(var(--row-h) + 2 * var(--space-3)); }
.vv-mk-nav-brand { display: inline-flex; align-items: center; gap: var(--space-2); font-family: var(--font-display); font-weight: 600; font-size: 15px; line-height: 22px; letter-spacing: -0.01em; color: var(--ink); text-decoration: none; }
.vv-mk-nav-links { display: flex; align-items: center; gap: var(--space-1); margin-inline: auto; list-style: none; padding: 0; margin-block: 0; }
.vv-mk-nav-links a { display: inline-flex; align-items: center; height: var(--row-h-sm); padding: 0 var(--space-3); border-radius: var(--radius-md); color: var(--ink-muted); text-decoration: none; font-size: var(--text-base); transition: background var(--dur-effects-default) var(--ease-spring), color var(--dur-effects-default) var(--ease-spring); }
.vv-mk-nav-links a:hover, .vv-mk-nav-links a[aria-current="page"] { background: var(--surface-3); color: var(--ink); }
@media (max-width: 839.98px) { .vv-mk-nav-links { display: none; } }
/* Sección: ritmo vertical y variante alternativa (surface-2) para separar bloques sin líneas. */
.vv-mk-section { padding-block: var(--mk-section-y); }
.vv-mk-section-alt { background: var(--surface-2); }
.vv-mk-eyebrow { display: inline-flex; align-items: center; gap: var(--space-2); margin: 0 0 var(--space-3); font-size: 12px; line-height: 16px; font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase; color: var(--primary); }
.vv-mk-title { margin: 0; font-family: var(--font-display); font-size: 28px; line-height: 34px; font-weight: 600; letter-spacing: -0.015em; text-wrap: balance; color: var(--ink); }
.vv-mk-lede { margin: var(--space-4) 0 0; max-width: 60ch; font-size: 15px; line-height: 22px; color: var(--ink-muted); text-wrap: pretty; }
.vv-mk-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-6); }
/* Hero: título display-lg, lede, acciones y una ranura visual opcional (captura, ilustración) a la derecha desde expanded. */
.vv-mk-hero { display: grid; gap: var(--space-8); align-items: center; padding-block: var(--mk-section-y); }
.vv-mk-hero .vv-mk-title { font-size: 40px; line-height: 44px; }
@media (max-width: 599.98px) { .vv-mk-hero .vv-mk-title { font-size: 28px; line-height: 34px; } }
.vv-mk-hero-visual { min-width: 0; border-radius: var(--radius-lg); border: 1px solid var(--border-subtle); background: var(--surface); overflow: hidden; box-shadow: var(--shadow-sm); }
.vv-mk-hero-visual :is(img, video, svg) { display: block; width: 100%; height: auto; }
@media (min-width: 840px) { .vv-mk-hero-split { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); } }
/* Rejilla de características: tarjetas de core (vv-card) a 3 → 2 → 1 columnas. */
.vv-mk-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); margin-top: var(--space-8); }
.vv-mk-feature { display: flex; flex-direction: column; gap: var(--space-2); }
.vv-mk-feature-icon { display: inline-flex; align-items: center; justify-content: center; width: calc(var(--row-h)); height: calc(var(--row-h)); border-radius: var(--radius-md); background: var(--primary-soft); color: var(--primary); }
.vv-mk-feature-icon :is(svg, .vv-icon) { width: 20px; height: 20px; }
/* CTA: banda final con un solo primario; en móvil apila. */
.vv-mk-cta { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-4); padding: var(--space-8); border-radius: var(--radius-xl); background: var(--primary-soft); border: 1px solid var(--border-subtle); }
.vv-mk-cta .vv-mk-title { font-size: 20px; line-height: 28px; }
.vv-mk-cta .vv-mk-actions { margin-top: 0; }
@media (max-width: 599.98px) { .vv-mk-cta { padding: var(--space-6); } .vv-mk-cta .vv-mk-actions .vv-btn { width: 100%; } }
/* Pie: columnas de enlaces en caption y línea legal. */
.vv-mk-footer { border-top: 1px solid var(--border-subtle); padding-block: var(--space-12) var(--space-8); font-size: 12px; line-height: 16px; color: var(--ink-muted); }
.vv-mk-footer-cols { display: grid; gap: var(--space-6); grid-template-columns: repeat(auto-fit, minmax(min(100%, 160px), 1fr)); }
.vv-mk-footer h4 { margin: 0 0 var(--space-2); font-size: 12px; line-height: 16px; font-weight: 500; color: var(--ink); }
.vv-mk-footer ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.vv-mk-footer a { color: var(--ink-muted); text-decoration: none; } .vv-mk-footer a:hover { color: var(--ink); text-decoration: underline; }
.vv-mk-footer-legal { display: flex; flex-wrap: wrap; gap: var(--space-4); justify-content: space-between; margin-top: var(--space-8); padding-top: var(--space-4); border-top: 1px solid var(--border-subtle); }
@media (prefers-reduced-motion: reduce) { .vv-mk-nav-links a { transition: none; } }

/* ---- rapidez ---- */
/* Vive UI — frag/rapidez.css (2.12.0). Piezas de «rapidez primero»: StatStrip, QuickActions,
   FormDialog (usa .vv-sheet-*), EntityPicker y ModalStack. Portadas del lote ESPEC-2.13.0
   §11–§23; solo tokens. Dependen de clases ya publicadas (.vv-grid, .vv-disclosure, .vv-stattile,
   .vv-sheet, .vv-field, .vv-btn). */

/* ===== StatStrip ===== */
.vv-statstrip { min-width: 0; }
.vv-statstrip-summary { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; min-width: 0; }
.vv-statstrip-summary-figures { display: inline-flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; min-width: 0; }
.vv-statstrip-figure { display: inline-flex; align-items: baseline; gap: var(--space-1); min-width: 0; }
.vv-statstrip-figure-value { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.vv-statstrip-figure-label { color: var(--ink-muted); font-size: 11px; }
.vv-statstrip-more { color: var(--ink-muted); font-size: 11px; }
.vv-statstrip-tile { flex: none; display: block; width: 100%; text-align: start; padding: 0; border: 0; background: transparent; border-radius: inherit; cursor: pointer; color: inherit; font: inherit; }
.vv-statstrip-tile .vv-stattile { width: 100%; }
.vv-statstrip-tile:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-statstrip-tile[aria-pressed="true"] { outline: 2px solid var(--primary); outline-offset: -2px; border-radius: var(--radius-lg); }

/* ===== QuickActions ===== */
.vv-quickactions { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.vv-quickactions-row { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.vv-quickactions-row > * { flex: none; }

/* ===== EntityPicker ===== */
.vv-entitypicker { min-width: 0; }
.vv-entitypicker .vv-select-rich-trigger { width: 100%; }

/* ===== ModalStack ===== */
.vv-modalstack-layer { position: fixed; inset: 0; z-index: var(--vv-stack-z, 50); pointer-events: none; }
.vv-modalstack-layer > * { pointer-events: auto; }

/* ===== ShellBrand.menu (2.12.0) ===== */
.vv-shell-brand-menu-btn { display: flex; align-items: center; gap: var(--space-2); flex: 1 1 auto; min-width: 0; padding: 0; border: 0; background: transparent; cursor: pointer; color: inherit; font: inherit; text-align: start; border-radius: var(--radius-md); }
.vv-shell-brand-menu-btn:hover { background: var(--surface-2); }
.vv-shell-brand-menu-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-shell-brand-menu-name { display: flex; flex-direction: column; min-width: 0; }
.vv-shell-brand-menu-name .vv-text-heading { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-shell-brand-menu-chevron { margin-inline-start: auto; flex: none; width: 15px; height: 15px; color: var(--ink-muted); }
.vv-shell-sidebar-collapsed .vv-shell-brand-menu-name { display: none; }
.vv-shell-sidebar-collapsed .vv-shell-brand-menu-chevron { display: none; }


/* ---- editableblock ---- */
/* Vive UI — EditableBlock (2.13.0, §1). Reposo = texto (o placeholder atenuado); hover/focus revela
   una pista «clic para editar»; clic/Enter/F2 abre un input o textarea con las mismas clases que
   Input/Textarea (misma altura --row-h, misma escala de densidad). El spinner de guardado y el aviso
   de error se anclan dentro del propio campo — nunca desplazan el layout de alrededor. */
.vv-editableblock { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.vv-editableblock-view {
  all: unset; box-sizing: border-box; position: relative; display: flex; align-items: center;
  width: 100%; min-height: var(--row-h); padding: 0 var(--space-3); border-radius: var(--radius-md);
  font: inherit; font-size: var(--text-base); color: var(--ink); cursor: default;
  transition: background-color var(--dur-fast) var(--ease-out), border-radius var(--dur-spring) var(--ease-spring);
}
button.vv-editableblock-view { cursor: pointer; text-align: left; }
.vv-editableblock-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; }
.vv-editableblock-view-multiline { align-items: flex-start; padding-block: var(--space-2); min-height: calc(var(--row-h) * 2); }
.vv-editableblock-view-multiline .vv-editableblock-text { white-space: pre-wrap; }
.vv-editableblock-view-empty .vv-editableblock-text { color: var(--ink-faint); }
button.vv-editableblock-view:hover, button.vv-editableblock-view:focus-visible { background: var(--surface-3); border-radius: var(--radius-lg); }
button.vv-editableblock-view:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: 2px; }
.vv-editableblock-hint {
  flex: none; display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: var(--ink-muted);
  background: var(--surface-3); padding: 2px var(--space-2); border-radius: var(--radius-sm);
  opacity: 0; transition: opacity var(--dur-fast) var(--ease-out); pointer-events: none;
}
.vv-editableblock-hint .vv-icon { width: 12px !important; height: 12px !important; }
button.vv-editableblock-view:hover .vv-editableblock-hint, button.vv-editableblock-view:focus-visible .vv-editableblock-hint { opacity: 1; }
.vv-editableblock-view-multiline .vv-editableblock-hint { position: absolute; top: var(--space-2); right: var(--space-2); }
.vv-editableblock-inputwrap { position: relative; display: flex; }
.vv-editableblock-input { padding-right: calc(var(--space-3) + 20px); }
.vv-editableblock-spinner { position: absolute; right: var(--space-3); top: calc((var(--row-h) - 16px) / 2); width: 16px !important; height: 16px !important; color: var(--ink-muted); }
.vv-editableblock-inputwrap:has(textarea) .vv-editableblock-spinner { top: var(--space-2); }
.vv-editableblock-error { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
@media (pointer: coarse) { .vv-editableblock-input { font-size: max(16px, var(--text-base)); } }
[data-density="touch"] .vv-editableblock-input { font-size: max(16px, var(--text-base)); }

/* ---- recordpage ---- */
/* Vive UI — RecordPage (2.13.0, requests.md #21 / feedback.md 2026-09-22): `RecordPage` sigue sin
   API propia (composición, ver `RecordPage/README.md`), pero sus huecos de layout — columna de
   página y división main/aside en 2fr/1fr desde 860px de contenido — son clases reales del kit, no
   solo README: sin ellas cualquier consumidor que siguiera la documentación al pie de la letra se
   quedaba sin CSS (esaem las reimplementó local como `.inv-split`/`.inv-split-body`). Portadas byte
   a byte desde el `<style>` de `RecordPage/preview.html`, que ahora las consume de aquí. */
.vv-rp-page { display: flex; flex-direction: column; gap: var(--space-6); padding: var(--page-gutter); container-type: inline-size; container-name: rpcontent; }
.vv-rp-split { display: grid; grid-template-columns: 1fr; gap: var(--space-4); align-items: start; }
.vv-rp-left { display: flex; flex-direction: column; gap: var(--space-6); min-width: 0; }
.vv-rp-right { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
@container rpcontent (min-width: 860px) {
  .vv-rp-split { grid-template-columns: 2fr 1fr; }
}
@supports not (container-type: inline-size) {
  @media (min-width: 860px) { .vv-rp-split { grid-template-columns: 2fr 1fr; } }
}

/* ---- recordnav ---- */
/* Vive UI — RecordNav (patrón "ficha de entidad", RecordPage/README.md; incubating por kit-todo
   v2: sin garantía semver hasta el 2.º consumidor). Control «‹ N de M ›» para moverse entre
   registros del resultado actual de una lista (orden que da el adaptador: filtrado/ordenado), sin
   volver a la lista. Referentes: Linear (issue view, J/K), Vive Lightbox (mismo `index`/`total`,
   ver `Lightbox` en este bundle) — aquí en horizontal, botones `V.Button` en vez de flotantes. */
.vv-recordnav { display: inline-flex; align-items: center; gap: var(--space-1); flex: none; }
.vv-recordnav-count {
  font-size: 12px; color: var(--ink-muted); font-variant-numeric: tabular-nums;
  padding-inline: var(--space-1); white-space: nowrap; min-width: 0;
}

/* ---- settingspage ---- */
/* Vive UI — SettingsPage (docs/work/settings-pulido, owner 2026-09-28: nav de secciones apilada a
   ancho completo en vez de columna, título repetido, tarjeta anidada, campos sin max-width). Igual
   que `.vv-rp-split` de RecordPage arriba: portadas byte a byte desde el `<style>` de
   `SettingsPage/preview.html` (nav + cuerpo), que ahora las consume de aquí — así cualquier
   consumidor (incl. este mismo demo de la galería) tiene CSS real sin reimplementar el patrón.
   `.vv-sp-navitem` usa `[aria-current="page"]` para el estado activo, igual que
   `.vv-shell-nav-item`/`.vv-navrail-item` (mismo criterio de nav seleccionado en todo el kit) en vez
   de una clase `-active` propia. Contenido de ejemplo (perfil, matriz de notificaciones, listas de
   sesiones/miembros, facturación) sigue siendo local a cada preview — solo el layout del nav y el
   cuerpo es reutilizable. */
.vv-sp-shell { display: flex; min-height: 100%; container-type: inline-size; container-name: vvset; }

.vv-sp-nav { flex: none; width: 100%; border-right: 1px solid var(--border-subtle); background: var(--surface); padding: var(--space-2); display: flex; flex-direction: column; gap: 2px; }
.vv-sp-navitem {
  all: unset; box-sizing: border-box; display: flex; align-items: center; gap: var(--space-3); min-height: var(--row-h); padding: 0 var(--space-3);
  border-radius: var(--radius-md); color: var(--ink-muted); font-size: var(--text-base); cursor: pointer; width: 100%;
  transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.vv-sp-navitem :is(svg, .vv-icon) { width: var(--icon); height: var(--icon); flex: none; }
.vv-sp-navitem:hover { background: var(--surface-3); color: var(--ink); }
.vv-sp-navitem:focus-visible { outline: 2px solid var(--focus-ring); outline-color: var(--focus-ring-a); outline-offset: -2px; }
.vv-sp-navitem[aria-current="page"] { background: var(--primary-soft); color: var(--primary); font-weight: 500; }
.vv-sp-navitem-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vv-sp-navitem-chevron { flex: none; color: var(--ink-faint); }

.vv-sp-main { flex: none; width: 100%; min-width: 0; display: none; flex-direction: column; }
.vv-sp-main-head { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border-subtle); flex: none; }
.vv-sp-main-back { display: none; }
.vv-sp-main-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding: var(--space-5); }
/* 720px, no 640px (RecordPage/ListPage): columna de contenido de una página de ajustes plana, sin
   rejilla de 2 columnas al lado — sigue GitHub/Linear Settings, más ancha que un split main/aside. */
.vv-sp-section { display: flex; flex-direction: column; gap: var(--space-6); max-width: 720px; }

.vv-sp-detail-open .vv-sp-nav { display: none; }
.vv-sp-detail-open .vv-sp-main { display: flex; }

@container vvset (min-width: 720px) {
  .vv-sp-nav { width: 240px; display: flex; }
  .vv-sp-main { display: flex; width: auto; flex: 1 1 auto; }
  .vv-sp-detail-open .vv-sp-nav { display: flex; }
  .vv-sp-main-back { display: none !important; }
}
@supports not (container-type: inline-size) {
  @media (min-width: 720px) {
    .vv-sp-nav { width: 240px; display: flex; }
    .vv-sp-main { display: flex; width: auto; flex: 1 1 auto; }
    .vv-sp-detail-open .vv-sp-nav { display: flex; }
    .vv-sp-main-back { display: none !important; }
  }
}
@media (max-width: 719.98px) {
  .vv-sp-main-back { display: inline-flex; }
}

/* ---- documentviewer ---- */
/* Vive UI — DocumentViewer (3.0.0, incubating). Frame: compone Sheet (size="xl" + presentation="fullscreen"),
   0 CSS de diálogo propio aquí — solo el contenido: barra de herramientas, escenario con scroll y
   la página (canvas + capa de texto + marcas). El <canvas> y los <span> de la capa de texto de
   pdf.js pintan contenido del propio documento (glifos, imágenes incrustadas) — no son "color
   literal fuera de tokens", igual que un <img> no lo es. `.vv-dv-textlayer` es un subconjunto
   mínimo, bajo nuestro namespace, del CSS de la capa de texto de pdf.js
   (mozilla.github.io/pdf.js → web/pdf_viewer.css): solo lo necesario para que los <span> se
   superpongan seleccionables e invisibles sobre el canvas — nada del editor de anotaciones ni del
   modo presentación, que DocumentViewer no usa. */
.vv-dv-sheet { --sheet-max: 92%; }
.vv-dv-sheet .vv-sheet-body { flex: 1 1 auto; display: flex; padding: 0; overflow: hidden; }
.vv-dv { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; min-height: 0; width: 100%; }
.vv-dv-toolbar {
  flex: none; display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--border-subtle); background: var(--surface);
}
[data-density="compact"] .vv-dv-toolbar { padding: var(--space-1) var(--space-3); }
[data-density="touch"] .vv-dv-toolbar { padding: var(--space-3) var(--space-5); }
.vv-dv-toolbar-group { display: flex; align-items: center; gap: var(--space-1); flex: none; }
.vv-dv-toolbar-group-end { margin-left: auto; }
.vv-dv-pagecount { min-width: 96px; padding: 0 var(--space-1); text-align: center; font-size: 12px; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.vv-dv-stage { flex: 1 1 auto; min-width: 0; min-height: 0; overflow: auto; display: flex; align-items: flex-start; justify-content: center; padding: var(--space-4); background: var(--canvas); }
.vv-dv-status { margin: auto; }
.vv-dv-page { position: relative; box-shadow: var(--shadow-lg); background: var(--surface); }
.vv-dv-canvas { display: block; }
/* Subconjunto de web/pdf_viewer.css de pdf.js (referencia: mozilla.github.io/pdf.js). */
.vv-dv-textlayer { position: absolute; inset: 0; overflow: clip; line-height: 1; text-align: initial; transform-origin: 0 0; opacity: 1; }
.vv-dv-textlayer span, .vv-dv-textlayer br { color: transparent; position: absolute; white-space: pre; cursor: text; transform-origin: 0 0; }
.vv-dv-textlayer > :not(.markedContent), .vv-dv-textlayer .markedContent span:not(.markedContent) {
  --font-height: 0; font-size: calc(var(--total-scale-factor, 1) * var(--font-height));
}
.vv-dv-textlayer .markedContent { display: contents; }
.vv-dv-textlayer ::selection { background: var(--primary-soft); }
/* Marca de cita (petición #4, reports/requests.md): amarillo subrayador, no "aviso" — reutiliza
   el token warning porque es el tono más cercano a un marcador real en la paleta semántica
   existente; mix-blend-mode deja pasar el glifo del canvas debajo en vez de taparlo. */
.vv-dv-highlight { position: absolute; background: var(--warning-soft); border: 1px solid var(--warning); border-radius: var(--radius-sm); mix-blend-mode: multiply; pointer-events: none; }
[data-theme="dark"] .vv-dv-highlight { mix-blend-mode: screen; }
.vv-dv-image { display: block; max-width: 100%; height: auto; }
