# OxiCloud — Roadmap UI/UX: de 4.8 a 10
> Plan de trabajo derivado de la auditoría multi-agente del frontend (`/static`).
> Cada tarea está anclada a archivos reales. Las fases son secuenciales: **Fase 0
> (fundamentos) debe ir primero** porque casi todo lo demás depende de los tokens.
> Esfuerzo: **S** <2h · **M** ~medio día · **L** ~1-2 días · **XL** ~3 días+.
## Trayectoria de puntuación
| Fase | Qué entrega | Nota al terminar |
|------|-------------|:----------------:|
| — | Estado actual | **4.8** |
| **0 · Fundamentos** | Las 6 escalas de tokens que faltan (espaciado, tipo, radios, elevación, z-index, movimiento) + rampas de color OKLCH + higiene | **5.3** |
| **1 · Migración** | Aplicar las escalas en los ~55 archivos CSS, gobernar la paleta, unificar la marca en superficies externas, arreglar jerarquía de headings | **6.5** |
| **2 · Estados y A11y** | `:focus-visible`, disabled/loading, contraste WCAG AA, navegación por teclado, landmarks, reduced-motion, ARIA, touch targets | **8.0** |
| **3 · Pulido** | Empty states + skeletons, placeholders de fotos, lightbox completo, feel del movimiento, login atmosférico, reflow responsive, voz de marca | **9.2** |
| **4 · Clase mundial + guardarraíles** | Command palette, View Transitions, undo, i18n con Intl, offline, onboarding, microcopy + **CI (axe, contraste, visual regression) y design system documentado que sostienen el 10** | **10** |
---
## FASE 0 — Fundamentos (las escalas de tokens que faltan)
> El problema raíz de la auditoría: solo el color está sistematizado. Aquí se crean
> los ejes que faltan en [variables.css](../static/css/base/variables.css). 4-5 áreas
> propusieron estas mismas tareas de forma independiente → son la base correcta.
### Escalas de tokens ✅ (resuelto — definidas en [variables.css](../static/css/base/variables.css), [reset.css](../static/css/base/reset.css), [typography.css](../static/css/base/typography.css))
- [x] **f0-space** (S) — Escala de espaciado `--space-*` en grid de 4px (`--space-0`…`--space-24`, con medios pasos 2/6/10/14px). _Migración de los px crudos → Fase 1._
- [x] **f0-radius** (S) — Escala `--radius-*` (`xs`…`4xl`,`full`) y **token huérfano `--radius` ya definido** (alias de `--radius-2xl`; resuelve los `var(--radius,12px)` de share-public/device-verify).
- [x] **f0-type** (M) — Escala tipográfica en rem `--text-2xs`…`--text-6xl` con `--leading-*`/`--weight-*`/`--tracking-*`, `--font-sans`/`--font-mono`, `--measure-prose` (65ch) y `--icon-*` separada del texto.
- [x] **f0-type-base** (S) — `font-size: var(--text-base)` + `line-height: 1.5` en `body`; `html { font-size: 100% }` (honra zoom); clases `.heading-page/-section/-card/-eyebrow` + `.prose` en typography.css.
- [x] **f0-zindex** (S) — Capas semánticas `--z-*` (base…max) con huecos para insertar.
- [x] **f0-motion** (S) — `--motion-*` (instant…spinner) + `--ease-*` con **5 curvas `cubic-bezier`** (standard/emphasized de desaceleración por defecto) + `--spin-duration`.
- [x] **f0-elevation** (M) — Recetas `--shadow-xs…2xl` compuestas; **`--color-shadow-5xl` muerto eliminado** y **colisión dark arreglada** (base 0.3 < md 0.34 < lg 0.38).
- [~] **f0-breakpoints** (S) — Tokens `--bp-xs…xl` definidos como referencia para JS/docs. ⚠️ **Diferido (razón real, no "dependencia"):** lightningcss SÍ soporta `@custom-media`, pero `web/mod.rs` sirve el CSS **crudo desde `static/`** en debug (`cargo run`), sin preprocesar. `@custom-media` no tiene soporte nativo de navegador → `@media (--bp-sm)` rompería **todas** las media queries en dev. Solo viable si dev también preprocesa CSS (no lo hace). Las custom props tampoco funcionan en condiciones `@media` de forma nativa. Quedan como referencia.
- [x] **f0-density** (S) — Tokens `--density-*` (row/gap/control) + override `html[data-density="compact"]`. _Consumidos por filas/controles en Fase 1._
- [x] **f0-sidebar-fluid** (S) — `--sidebar-width: clamp(220px,18vw,280px)` + tokens min/max/collapsed. _Rail redimensionable → Fase 1._
- [x] **f0-dvh** (S) — `body { height:100vh; height:100dvh }` con fallback. _Migración de `100vh` en views → Fase 1 (f1-vh-views)._
### Fundamentos de color (curado, Opción B) ✅ — validado WCAG por script
- [~] **f0-oklch-ramps** (L) — **Adaptado:** curé los semánticos directamente a un hue sobrio cada uno con valores validados; **no** construí la capa formal de primitivas OKLCH (arquitectura opcional a futuro — el resultado de armonización ya se logró).
- [~] **f0-offwhites** (M) — **Diferido:** bajo valor y riesgo de aplanar el micro-layering de superficies sin poder renderizar. Revisar con la app en navegador.
- [x] **f0-brand-orange** (M) — Naranja confirmado como único acento de marca; `--color-accent-text`/`--color-on-accent` añadidos. _Degradar azul/índigo/púrpura → Fase 1 (f1-demote-*)._
- [x] **f0-textgray** (M) — 14 grises → 4 tiers AA-safe (secondary/muted/subtle/faint) + **todos los nombres legacy como alias** (0 roturas). muted/faint/placeholder **oscurecidos para pasar AA** (antes 2.3–4.0:1). _Adelanta f2-contrast-text._
- [x] **f0-semantic-success** (L) — Unificado a esmeralda sobrio (`#16a34a`); 6 verdes → alias canónicos.
- [x] **f0-semantic-warning** (L) — Unificado a ámbar (`text #b45309` AA, `border #f59e0b`); el oro brillante `#ffc107` se retira. 7 variantes → alias.
- [x] **f0-semantic-danger** (L) — Unificado a `#ef4444/#dc2626`; `danger-text-alt` ahora usa el rojo AA.
- [x] **f0-semantic-info** (L) — Unificado a azul (`text #1d4ed8` AA + tier dark); variantes → alias. _Calendar dots + iconos de nav regenerados a familia armonizada (S55/L62)._
### Tokens de acento, foco y marca
- [x] **f0-accent-text** (S) — `--color-accent-text: light-dark(#d23c18, #ff8a5c)` + `--color-focus-ring` añadidos. _Migración de enlaces → Fase 2 (f2-contrast-link)._
- [x] **f0-on-accent** (S) — `--color-on-accent: #ffffff` añadido. _Repunteo de consumidores de `--color-danger-text` → Fase 1._
- [~] **f0-brand-mark** (M) — **Parcial:** `--color-logo-gradient` canónico definido (unifica los dos gradientes divergentes). _SVG único + componente de lockup + wordmark/settings-layout tokens → Fase 1 (f1-logo-*, f1-settings-container)._
### Higiene y andamiaje (rápidas, habilitan el resto) ✅
- [x] **f0-stylelint-typo** (S) — Glob arreglado (`statc`→`static`). **0 hex/rgba crudos** fuera de variables/themes → CI no se romperá por colores. _(Auditoría 2026-06: una fase posterior había metido 3 hex en `base/a11y.css` (`@media prefers-contrast`) que stylelint habría marcado; movidos a `variables.css` (capa de tokens, whitelisted) → invariante restaurado y reverificado.)_
- [x] **f0-debug-red** (S) — Los tokens "debug" sí se usaban (badge de conteo de drag); **repunteados** a `--color-notification-badge`/`--color-danger-text` y eliminados de variables.css.
- [x] **f0-legacy-badge** (S) — Bloque FIXME (11 tokens `*-dark-*`) **eliminado** — confirmado 0 consumidores.
- [x] **f0-animations-file** (S) — [base/animations.css](../static/css/base/animations.css) creado con el `@keyframes spin` canónico, conectado temprano en main.css.
- [x] **f0-delete-spin-dupes** (S) — 6 `@keyframes spin` duplicados eliminados. _`oxi-spin`/`smdSpin` (1 uso c/u) diferidos a Fase 1 (requieren tocar consumidores)._
- [x] **f0-dead-upload-toast** (S) — Bloque muerto `.upload-toast` (162 líneas) + `
` oculto de index.html **eliminados** (0 referencias JS).
- [x] **f0-sr-only** (S) — `.sr-only` añadida en reset.css.
- [~] **f0-perf-baseline** (M) — **Diferida:** requiere correr la app + build + Lighthouse (no disponible en este entorno). El archivo de presupuestos se crea junto al primer baseline.
---
## FASE 1 — Migración (aplicar las escalas en todo el código)
### Migrar CSS a las escalas ✅ — codemod value-preserving (1645 reemplazos, 41 archivos, 0 cambio visual)
- [x] **f1-mig-base** (S) — forms.css migrado (reset.css ya estaba; main.css no tiene valores).
- [x] **f1-mig-layout** (M) — sidebar/topbar/content migrados.
- [x] **f1-mig-comp-core** (L) — buttons/fileManager/resourceList/fileType/breadcrumb migrados.
- [x] **f1-mig-comp-overlays** (L) — dialogs/modals/shareModal/groupsModal/shareDialog/contextMenu/uploadDropdown migrados.
- [x] **f1-mig-comp-misc** (L) — notifications/tooltip/itemTooltip/search/spinner/batchToolbar/chips/userVignette/userMenu/languageSelector/csp-utilities migrados.
- [x] **f1-mig-views** (XL) — los 14 views migrados (admin/music/profile/auth/photos/trash/recent/favorites/sharedWithMe/mySharesView/inlineViewer/device-verify/share-public/photosLightbox).
- [x] **f1-weight-normalize** (M) — `bold`/`normal` + numéricos → tokens `--weight-*`.
- [x] **f1-leading-normalize** (S) — `line-height` unitless exactos → tokens `--leading-*`.
- [~] **f1-snap-fractional** (M) — **Parcial:** los exactos ya van por token; quedan **184 px off-grid** (15/17/11/13px) sin token exacto → requieren decisión de snapping (cambio visual). _Pendiente._
- [ ] **f1-mig-root-dups** (M) — Reconciliar shims raíz vs views/ (separado de la migración de valores).
- [~] **f1-mig-motion-sweep** (L) — **Diferido:** las duraciones dominantes (0.15s/0.25s) no tienen token exacto → tokenizarlas es un cambio de *feel*, no value-preserving. Decisión aparte.
- [ ] **f1-spin-duration-token** (S) — Diferido (los spinners usan 0.7s/1s; unificar a `--spin-duration` cambia el valor).
- [ ] **f1-icon-size-scale** (M) — Diferido (separar `font-size` de icono vs texto no es automatizable sin ambigüedad).
- [ ] **f1-tracking-normalize** (M) — Diferido (los `letter-spacing` en px/em no tienen token exacto).
### Jerarquía de headings ✅ — cada página con un único h1, sin saltos
- [x] **f1-h-index** (S) — modal-title `h3→h2` (selector `.modal-header h2,h3` actualizado) → orden `h1→h2→h2`.
- [x] **f1-h-login** (S) — `
OxiCloud
` añadido (usa la utilidad `.sr-only` de Fase 0); paneles quedan h2.
- [x] **f1-h-admin** (M) — `
Admin panel
` añadido; secciones h2, subsecciones h3.
- [x] **f1-h-profile** (S) — panel de error `h2→h1` (queda h1 propio en cada panel excluyente).
- [x] **f1-h-rest** (S) — share/device-verify ya tenían h1 (verificado); nextcloud-login/success/error `h2→h1`.
### Gobernanza de paleta ✅ (parcial donde es decorativo)
- [~] **f1-fileicon-pairs** (M) — **Diferido:** re-derivar pares bg/text de iconos (pdf/doc/image/audio/video) de los semánticos — es pulido visual, revisar con la app.
- [—] **f1-fileicon-langs** (L) — **Descartado** por tu elección (mantener colores por lenguaje).
- [x] **f1-calendar-dots** (M) — Hecho en Fase 0 (familia armonizada S55/L62).
- [x] **f1-demote-blue** (M) — `--color-primary` aliasado a `--color-accent` → naranja único (flipa device-verify/share/userMenu).
- [x] **f1-demote-oidc** (S) — `--color-oidc-bg` → `--color-info-blue` (botón SSO distinto de la marca, en el azul info único).
- [x] **f1-demote-purple** (M) — Familia `--color-purple-*` **eliminada** (0 consumidores) de variables.css y del fallback de dark.css.
- [~] **f1-demote-misc-blue** (M) — **Parcial:** share-link → info-text. _admin-blue y avatar-gradient (decorativos) los dejé — bajo valor, requerirían restyle del admin._
- [x] **f1-named-colors** (S) — `white/black` → `#ffffff/#000000`; confirmado **0 named colors** fuera de variables/themes.
### Unificación de marca en superficies externas (primera impresión) ✅
- [x] **f1-share-accent** (S) — Hecho vía el alias `--color-primary → --color-accent` (wordmark + spinner de share ahora naranjas).
- [x] **f1-device-accent** (S) — Acento vía alias **+ bug del Deny arreglado** (`--color-error-text` como fondo → `--color-danger-bg`).
- [x] **f1-auth-foreground** (S) — 3× `--color-danger-text` foreground → `--color-on-accent` en auth.css (+ 2× en device-verify).
- [x] **f1-share-logo** (S) — Tile de marca canónico (`.brand-mark` con el SVG real) añadido a share.html.
- [x] **f1-device-logo** (S) — Tile `.brand-mark` añadido a device-verify.html (SVG inline → no hizo falta icons.js).
- [x] **f1-share-emoji-expired** (S) — 🚫 `🚫` → SVG alert-circle (color danger).
- [x] **f1-share-emoji-file** (M) — 📄 `📄` → SVG de documento (color accent).
- [~] **f1-share-card-icons** (S) — **Diferido:** los cards de folder/file los construye publicShare.js (JS, no HTML).
- [x] **f1-share-download-icon** (S) — SVG de descarga añadido al botón Download.
- [~] **f1-login-nojs** (M) — **Diferido:** requiere entender la lógica de paneles de auth.js (riesgo de mostrar panel equivocado).
- [x] **f1-share-nojs** (S) — `