60 min
4/12Vinyasa Flow
Sistema de diseño
Una base común, la personalidad de cada estudio.
Eclipse es una plataforma de reservas para estudios de yoga, pilates y entrenamiento. Cada estudio tiene su propia instancia (hoy son unas nueve), todas salen del mismo código. Eclipse UI es el design system que hice para que cada una se vea como su estudio sin bifurcar ese código y sin bajar la calidad.
Diseño del sistema, diseño de interfaz y desarrollo · Una sola persona
Un estudio de barre y un box de HIIT no deberían verse iguales, pero en Eclipse lo único que cambiaba de una instancia a otra era el color de marca. Las vistas escribían sus colores a mano: en las 17 vistas públicas había 442 referencias sueltas a variables ([var(--color-…)]), 207 clases de la paleta de Tailwind (gray-500, white…) y 48 íconos pegados como <svg>. Cambiar la voz o la forma de un estudio significaba editar vista por vista, en cada instancia.
Además, la marca de base, un dorado (#a3946b), se usaba como texto y como fondo de botones: medía entre 2.7 y 3.0:1, y WCAG AA pide 4.5:1. En cinco páginas, axe encontró 58 elementos con contraste insuficiente.
Lo que necesitaba era un sistema que separara dos cosas: lo que todo estudio merece por igual y lo que hace a cada uno distinto.
La base es lo que no cambia de un estudio a otro, y si un componente rompe una de estas garantías, no está terminado: accesible de verdad (contraste AA medido, foco visible, teclado completo, movimiento reducido respetado); solo tokens y escalas, nunca un color o un tamaño suelto; jerarquía por capas y bordes de 1 px, con la sombra reservada para lo que flota; movimiento físico; integrado con Rails (los controles mandan su valor con name:, funcionan con Turbo y con el form builder), y un código que es de quien lo usa, sin cajas negras.
La personalidad es lo que decide cada estudio, en cinco rasgos:
| Rasgo | Qué cambia |
|---|---|
| Voz | tipografías, peso, cursiva, mayúsculas y ancho de los títulos; la voz de los botones |
| Forma | redondez, botones y campos en pastilla o rectos, qué tan marcados son los bordes |
| Color | marca, tinta, papel y cuánto tiñe la marca los grises |
| Aire | qué tan compacta o aireada es la interfaz, cuánta sombra lleva lo que flota |
| Ritmo | qué tan rápido se mueve todo y cuánto brilla lo importante |
Los rasgos se mueven con 27 diales que caben en un bloque de theme.css. El motor deriva lo demás: la paleta, los hover, el texto que va sobre la marca, las capas, las sombras, los radios y las duraciones. Partí de seis personalidades pensadas para tipos de estudio distintos, y cada una es un punto de partida, no un tema cerrado:
| Personalidad | Para | Carácter |
|---|---|---|
| Carbón | interfaces sobrias | monocroma, Inter y JetBrains Mono, bordes de 1 px |
| Eclipse | la base de la app | oro cálido, Fraunces en los títulos, calmada |
| Alba | spa, barre, bienestar | malva, Cormorant en cursiva, todo en pastilla, mucho aire, lenta |
| Brisa | pilates clínico, fisioterapia | verde salvia, Manrope, radios chicos, compacta y rápida |
| Umbra | box, HIIT, cycling | bermellón sobre negro, Archivo condensada en mayúsculas, todo recto |
| Terra | yoga, comunidad | terracota, Fraunces suave y Nunito, la marca tiñe los grises |
Llevar una a un estudio es copiar su bloque a theme.css y mover los diales que haga falta. Todo lo demás se realinea solo.
La misma tarjeta de clase en tres de las seis personalidades de partida. El código de la tarjeta no cambia, solo los diales.
60 min
4/12Vinyasa Flow
Eclipse. Oro cálido sobre papel, Fraunces en los títulos, calmada. Para marcas cálidas y editoriales.
Alba. Malva, Cormorant en cursiva, todo en pastilla, mucho aire, lenta. Para bienestar, belleza y editorial.
Umbra. Bermellón sobre negro, Archivo condensada en mayúsculas, todo recto, rápida. Para deporte, energía y marcas de alto contraste, en oscuro, su modo natural.
Un sistema que promete contraste AA en cualquier combinación de diales no puede revisarse a ojo. Así que la base tiene guardianes que corren con las pruebas:
config/eclipse_ui/contrast.yml) se mide en un navegador real en las seis personalidades y en el theme.css del estudio, en claro y en oscuro: 308 mediciones en 0.6 s. Las mide un navegador porque el motor deriva los colores con color-mix(in oklab) y oklch(from …), y reimplementar eso en Ruby sería una segunda copia del motor que tarde o temprano diría otra cosa que la pantalla. Si un estudio elige una marca con la que algún texto deja de leerse, la prueba dice cuál.rounded sin escala o una variable escrita a mano donde hay una utilidad con nombre.El contraste dejó de ser una decisión estética: la marca como texto usa text-primary-text, un tono derivado que pasa AA en todas las personalidades (mínimo 5.7:1), y el texto sobre un botón de marca se elige solo (6.0:1 sobre el dorado).
Todo lo de abajo lo pinta el mismo motor, con la personalidad Eclipse.
Entran marca, tinta, papel y tinte. Salen superficies, texto, bordes y estados, mezclados en oklab.
Diales
Superficies y bordes
Texto y marca
Estados
Una fuente de texto para toda la interfaz, una mono para datos y etiquetas, y la voz display de cada personalidad para los títulos. Si un tamaño no está en la escala, no existe.
Un dial de densidad, de 0.85 a 1.2, multiplica rellenos, separaciones y alturas de control. Una marca aireada respira; una compacta aprieta.
Un solo dial de redondez multiplica todos los radios, así que toda la app se redondea o se endereza con la marca. Una personalidad puede volver pastilla o rectos los botones y los campos sin tocar las tarjetas.
Los componentes con vida suelen venir de librerías de React. Eclipse es Rails con Stimulus, así que escribí la física desde cero: spring.js son 127 líneas sin dependencias, con un integrador de paso fijo (1/240 s) que se comporta igual en una pantalla de 60 Hz que en una de 120 Hz.
snappy llega al 1 % de su destino en 300 ms (botones, indicadores), gentle en 550 ms (pestañas, diálogos) y responsive en 192 ms, con un rebote de 0.9 % que se reserva para los regresos y las pilas de avisos.--eui-motion) escala el tiempo de todos los resortes y de las transiciones CSS, y con prefers-reduced-motion todo salta al final.Hoy la usan 34 controladores de Stimulus: deslizar para reservar, dígitos que ruedan, pestañas con pastilla, modales que se asientan, avisos que se arrastran.
La prueba de fuego fue vestir el sitio público completo de la demo con los componentes: navegación, pie, avisos, confirmaciones, portada, horario, clase, precios, contacto, acceso, registro, cuenta y Nosotros. Las capturas usan la misma base de datos en las dos versiones; solo cambia el código.
Lo que cambió en el código de las vistas públicas:
| Métrica | Antes | Después |
|---|---|---|
| Líneas de ERB | 1,695 | 1,470 |
Colores escritos a mano ([var(--color-…)]) | 442 | 0 |
Clases de la paleta de Tailwind (gray-*, white…) | 207 | 0 |
| Sombras de Tailwind que no siguen el dial | 24 | 0 |
<svg> pegados | 48 | 0 |
| Componentes de Eclipse UI usados | 4 | 19 |
Lo que cambió en accesibilidad (axe 4.10, WCAG 2.1 AA, en portada, horario, clase, precios y acceso, en escritorio y modo claro):
| Contraste insuficiente | Antes | Después |
|---|---|---|
| En el contenido (el dorado como texto o fondo, grises sobre papel) | 48 | 0 |
| En el pie oscuro (texto tenue sobre negro) | 10 | 0 |
| Total | 58 | 0 |
El pie fue lo último en caer: sus etiquetas y créditos usaban text-faint, a 3.66:1 sobre negro, y el propio sistema dice que text-faint no es para texto que haya que leer. Ahora usan text-muted-foreground. En el teléfono y en modo oscuro, axe todavía marca etiquetas pequeñas del horario y de la clase con ese mismo tono (de 3.4 a 4.3:1); es lo siguiente que hay que alinear.
El catálogo público tiene 42, cada uno con su demo en vivo, props, patrón ARIA y teclado. Estos cuatro, con sus estados, en la personalidad Eclipse.
Cuenta lo que pasa con la acción: gira mientras reserva, dibuja una palomita al terminar y, si algo falla, se sacude y lo dice.
El campo resuelve la etiqueta, la ayuda, el error y el ARIA. Las unidades y los íconos van dentro.
Paquete de 10 clases
Reformer: mínimo 45 min
Unifica cómo se ven los estados de pedidos, pagos, citas y suscripciones. El color nunca va solo: el estado siempre se lee como texto.
Lo que se ve cuando una tabla, una lista o una sección no tiene nada todavía: dice qué pasa, qué hacer y ofrece la acción.
Aún no hay clases esta semana
Programa la primera desde una plantilla y aparecerá en el horario del estudio.
Sin cancelaciones esta semana
Cuando alguien cancele, la verás aquí con su crédito devuelto.
Eclipse UI nació dentro de Eclipse. Para que sirviera fuera de la app, tenía que dejar de ser parte de ella.
eclipse_ui). Eclipse la usa como una copia de solo lectura que se actualiza con un comando (bin/update_eclipse_ui), y unas pruebas impiden editarla dentro de la app. El pack de reservas (clase, horario, paquetes, reservación…) se quedó en Eclipse: es su ventaja frente a otros softwares para estudios.eclipse-ui.css) y, por personalidad, su theme.css, su shadcn.css y su DESIGN.md.shadcn.css se mide. Lleva los valores de shadcn/ui medidos en un navegador real sobre el motor y convertidos a OKLCH. La razón es la misma que con el contraste: medir lo que pinta la pantalla en lugar de reimplementar el motor.DESIGN.md es para la IA. Son instrucciones para que un agente de IA (Claude Code, Cursor) aplique la personalidad en cada pantalla: qué tokens usar, qué reglas seguir y qué haría que la app se viera hecha por IA.
docs/GUI.md) y unas instrucciones para el agente que escribe la UI hacen que el código nuevo salga ya con tokens, con física y accesible.Lo que sigue: llevar el panel del estudio (lo que el dueño usa todos los días) a Eclipse UI y probar en Safari y Firefox reales.
Cómo se midió: las capturas y axe 4.10.3 corrieron en Chromium con Playwright, a 1440 × 900 y 390 × 844, contra el commit 99cdd64 (antes de Eclipse UI) y contra main, con la misma base de datos. El conteo del código corre las reglas de Ui::Guardrails sobre las mismas vistas públicas en los dos commits (17 archivos antes; 19 después, porque dos parciales salieron de ellas). Los tiempos de los resortes simulan spring.js con su propio integrador y un recorrido de 100 px.
Relacionado