Communitygithub.com

OpenLabs-so/oa-design

El lenguaje de diseño de [Open Analytics](https://getopen.so) en un solo archivo.

¿Qué es oa-design?

Este skill define el lenguaje de diseño completo de Open Analytics en un único archivo compilado, diseñado tanto para agentes de IA como para los humanos que los dirigen. Incluye todos los tokens visuales que se envían en producción: códigos de color hex, constantes de muelle (spring), radios, curvas de aceleración (easings), y reglas de layout. Es compatible con Claude Code, Cursor y otros agentes que soporten skills, y también se puede usar pegando el archivo en el contexto del agente o como regla en el repositorio. El skill promueve un estilo visual muy específico: superficies blancas con esquinas de curvatura continua sobre un fondo gris, dibujadas con una sola tinta y animadas por un único sistema de muelles. El archivo detalla diez reglas de diseño, tokens para modo claro y oscuro, tipografía, sombras, componentes como tarjetas, botones, modales y notificaciones, y un sistema de animación con muelles nombrados.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/OpenLabs-so/oa-design/tree/HEAD

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

DESIGN-SKILL.md

El lenguaje de diseño de Open Analytics en un solo archivo, escrito para agentes de IA y los humanos que los dirigen. Cada valor aquí se envía en el producto: códigos de color hex, constantes de muelle, radios, curvas de aceleración. Pega este archivo en el contexto de tu agente (o colócalo en tu repositorio como archivo de reglas) y solicita pantallas "en el estilo de OA".

Esta es la edición compilada de un solo archivo, hecha para pegar. El mismo lenguaje también se envía en este repositorio como un skill de agente adecuado (skills/oa-design/: recetas por componente con el código fuente completo tipado incrustado) y como CLI (npx getopen-design add <component>); prefiere esos para Claude Code y otros agentes conscientes de skills.

El aspecto en una frase: superficies blancas con esquinas de curvatura continua, descansando sobre un escenario gris silencioso, dibujadas en una sola tinta, movidas por un solo muelle.


1. Las diez reglas

  1. Una tinta, todo derivado. Todo el sistema neutral es un solo color mezclado con transparencia en porcentajes fijos. Nunca elijas un segundo gris.
  2. Dos capas, y el espacio entre ellas es la página. Las superficies son un marco blanco que sostiene un inserto gris hundido; las secciones son placas y el fondo de página entre ellas es el único divisor. Sin reglas horizontales.
  3. Curvaturas continuas (squircles) para superficies, píldoras para acciones. Las tarjetas tienen esquinas de curvatura continua; todo lo que se puede hacer clic y no es una tarjeta es una píldora.
  4. Una familia de muelles. Siete muelles nombrados cubren todo el producto. No inventes un octavo.
  5. El chrome nunca espera. El layout y los títulos se renderizan instantáneamente; solo los datos cambian desde un esqueleto de píxeles coincidentes, llegando por desenfoque, no por aparición.
  6. El peso se detiene en 500. Sin negrita en ninguna parte. La jerarquía proviene del tamaño, el color y el espaciado.
  7. Un acento, gastado en un solo lugar. Un azul único para acciones primarias y la línea primaria del gráfico; los colores semánticos son tintes de texto, no rellenos.
  8. Los estados obtienen píldoras, los eventos terminan solos. Las condiciones permanentes renderizan tiras no descartables que viven exactamente mientras dura el estado; los resultados únicos son toasts que se retiran solos.
  9. El texto es parte del diseño. Mayúscula inicial; los botones dicen lo que sucede; los errores nombran la causa y la salida, sin culpa.
  10. Suelo de calidad, siempre. Anillos de enfoque, role="status", decoraciones aria-hidden, prefers-reduced-motion, y sin desplazamiento horizontal de página.

2. Tokens

Claro

:root {
  --ink: #292929;

  --background: #f6f6f6;     /* el escenario gris entre placas */
  --card: #ffffff;
  --popover: #ffffff;

  /* neutrales derivados de ink: los porcentajes SON el sistema */
  --border: color-mix(in srgb, var(--ink) 12%, transparent);
  --input:  color-mix(in srgb, var(--ink) 14%, transparent);
  --accent: color-mix(in srgb, var(--ink) 5%, transparent);  /* lavado hover */
  --muted:  color-mix(in srgb, var(--ink) 5%, transparent);
  --muted-foreground: #6d6d6d;
  --foreground: var(--ink);

  --primary: #305dde;             /* el único acento */
  --primary-foreground: #ffffff;
  --ring: #3ba6f1;

  --secondary: #e9e9e9;           /* controles grises planos, sin borde */
  --secondary-foreground: var(--ink);

  /* semánticos, como tintes de TEXTO sobre claro (escala Tailwind, fila 700) */
  --destructive: var(--color-red-500);
  --destructive-foreground: var(--color-red-700);
  --success-foreground: var(--color-emerald-700);
  --warning-foreground: var(--color-amber-700);
  --info-foreground: var(--color-blue-700);

  --chart-1: #296FF0;             /* línea primaria del gráfico */
  --radius: 0.625rem;
}

¿Necesitas un divisor más fuerte? Aumenta el porcentaje de ink (el producto usa solo 4, 5, 10, 12, 14). Los colores semánticos van en palabras y puntos de 8px, nunca en rellenos de panel.

Oscuro

Misma receta, re-entintada, con dos cambios sistemáticos: los porcentajes de línea fina aumentan en 2 puntos, y el texto semántico pasa de la fila 700 a la fila 400.

.dark {
  --ink: #ededed;
  --background: #191919;
  --card: #212121;
  --popover: #242424;
  --border: color-mix(in srgb, var(--ink) 14%, transparent);
  --input:  color-mix(in srgb, var(--ink) 16%, transparent);
  --muted-foreground: #969696;
  --secondary: #2e2e2e;
  --primary: #296FF0;
  --destructive-foreground: var(--color-red-400);
  --success-foreground: var(--color-emerald-400);
}

Radio

Una base, multiplicada; nunca un radio hecho a medida:

--radius: 0.625rem;  /* sm ×0.6, md ×0.8, lg ×1, xl ×1.4,
                        2xl ×1.8, 3xl ×2.2, 4xl ×2.6 */

rounded-full está reservado para píldoras. Las superficies de curvatura continua llevan sus propios radios más grandes (sección 4).

Tipografía

  • Inter Tight, variable, pesos 300 a 500, autoalojado (las CDN de fuentes rompen las compilaciones; mantén el woff2 en el repositorio).
  • Geist Mono para code, kbd, samp, pre, teclas y fragmentos.
  • El techo de peso es la identidad: nada se renderiza más pesado que 500. La jerarquía proviene del tamaño, text-foreground/80 vs text-muted-foreground, y el espaciado.
  • Encabezados: font-medium tracking-tight; los titulares de marketing añaden text-wrap: balance.
  • Datos: tabular-nums en cualquier número en una columna o que se actualice en su lugar.
  • La voz predeterminada de la interfaz es 14px; las líneas secundarias 12px, con leading-5/leading-6.

Sombras

La elevación es binaria:

/* tarjeta en reposo */    box-shadow: 0 1px 2px rgba(0,0,0,0.06);
/* chrome flotante */ box-shadow: 0 1px 2px rgba(0,0,0,0.08),
                                  0 8px 24px rgba(0,0,0,0.08);

Disciplina del fondo

body es blanco (el overscroll muestra el body; el gris asomando detrás de una pantalla blanca se lee como una página diferente). Las pantallas que quieren el escenario gris pintan su propio fondo. La raíz obtiene overflow-x: clip.


3. Layout

Placas. Las secciones son paneles autocontenidos; el fondo de página que se muestra entre ellas es el único límite. Los espacios verticales generosos (gap-12 sm:gap-20 marketing, gap-6 en la app), el padding lateral más ajustado (px-4 sm:px-6). Los hermanos se espacian por el gap del padre, nunca por márgenes por hijo. Sin <hr>, sin border-b entre secciones.

Anchos por ámbito: marketing 76rem (6.5xl), páginas del sitio de la app max-w-6xl, pantallas de listas/selectores max-w-5xl, superficies de lectura ~65ch. Centrado, mx-auto.

Anatomía de la página de la app:

encabezado fijo (h-14) → contenedor pt-14
  píldora flotante opcional (fixed top-16, centrada)
  <main class="px-4 pb-36 pt-8 sm:px-6 bg-white">
    fila de título: h1 text-xl font-medium tracking-tight + UN control a la derecha
    tiras de aviso opcionales
    placas: flex flex-col gap-6

La fila de título contiene exactamente un control. pb-36 mantiene el contenido fuera del borde de la ventana gráfica.

Cuadrículas: grid gap-3, columnas responsivas (grid-cols-2 sm:grid-cols-4 para estadísticas). Las tarjetas comparten una altura de contenido fija y se desplazan internamente; la línea base de la cuadrícula nunca se rompe.

Responsive: diseñar a 390px y 1280px. Las parejas horizontales se apilan debajo de sm: con la acción al final. El contenido ancho se desplaza en su propio contenedor overflow-x-auto; la página nunca se desplaza lateralmente.

Configuración: carril de pestañas estrecho + columna de contenido de paneles de sección; cada sección se abre con un bloque de encabezado (título + una oración en gris, acción única opcional a la derecha). Enlace profundo a pestañas mediante ?tab=<id>.


4. Componentes

La superficie de curvatura continua

Esquinas de curvatura continua a través de un clip-path CSS shape() parametrizado por dos variables, con corner-shape: squircle donde sea compatible:

.squircle {
  border-radius: 26px;
  --card-clip-radius: 14px;
  --card-clip-handle: 2.25px;
  clip-path: shape(
    from var(--card-clip-radius) 0px,
    line to calc(100% - var(--card-clip-radius)) 0px,
    curve to 100% var(--card-clip-radius)
      with calc(100% - var(--card-clip-handle)) 0px / 100% var(--card-clip-handle),
    line to 100% calc(100% - var(--card-clip-radius)),
    curve to calc(100% - var(--card-clip-radius)) 100%
      with 100% calc(100% - var(--card-clip-handle)) / calc(100% - var(--card-clip-handle)) 100%,
    line to var(--card-clip-radius) 100%,
    curve to 0px calc(100% - var(--card-clip-radius))
      with var(--card-clip-handle) 100% / 0px calc(100% - var(--card-clip-handle)),
    line to 0px var(--card-clip-radius),
    curve to var(--card-clip-radius) 0px
      with 0px var(--card-clip-handle) / var(--card-clip-handle) 0px,
    close
  );
  corner-shape: squircle;
}
@media (min-width: 640px) {
  .squircle { border-radius: 50px; --card-clip-radius: 20px; --card-clip-handle: 3px; }
}

La minitarjeta

curvatura continua exterior: bg-card, border, p-1, sombra en reposo
├── tira de encabezado (pb-2 pl-3.5 pr-2 pt-1.5):
│     ícono atenuado de 16px + título text-sm font-medium text-foreground/80
│     chip opcional en la línea central del título
│     "Ver todo ›" (text-xs muted; hover: lavado accent, flecha se desliza 2px)
└── curvatura continua interior: bg #f6f6f6, border, rounded-[22px] sm:rounded-[44px],
      ALTURA FIJA (h-44 = cinco filas de 32px), overflow hidden

Las tarjetas nunca crecen con los datos; las listas más largas se desplazan internamente. El desbordamiento muestra un chip de galón de 24px en la parte inferior derecha, que se mueve y:[0,2.5,0] (bucle easeInOut de 1.4s), desapareciendo (0.18s, escala 0.7) al final del desplazamiento. Filas: 32px, hover de lavado accent a sangre completa, valores tabular-nums.

Botones

Píldoras, font-medium, presionados físicamente: active:translate-y-px active:scale-[0.98].

  • primario: azul acento con un bisel (background/border color-mixed hacia un índigo profundo, inset 0 1px 0 rgba(255,255,255,0.22) arriba, inset oscuro abajo); hover aclara hacia el primary puro.
  • secundario: --secondary plano, borde transparente, hover mezcla 5% ink. El caballo de batalla.
  • fantasma: solo texto atenuado. destructivo: relleno rojo, solo paso de confirmación.
  • Tamaños h-9 / h-8 / h-7; el chrome de la app usa principalmente los pequeños.
  • Cargando mantiene la etiqueta y añade un spinner de anillo de 14px (border-2 border-current border-t-transparent); el ancho nunca salta.
  • focus-visible:ring-[3px] en --ring/50. Nunca se elimina.

Paneles desplegables (menús, selects, conmutadores)

Píldora desencadenante → panel flotante (sombra flotante, borde, blanco, rounded-2xl+), entra con muelle en PANEL (550/38) con pequeño desplazamiento y/escala, salida más rápida. Elementos rounded-lg de 32px con hover de lavado accent; el resaltado activo es un elemento compartido que viaja (layoutId o muelles de ancho/alto), nunca repintados por elemento. Cerrar al hacer clic fuera y con Escape.

Modales

El fondo se desvanece ~0.15s; el panel entra con muelle en POP (400/26) desde opacity 0, scale .96, sale a 380/28. Los diálogos de varios pasos mantienen un panel cuya altura se anima al tamaño medido de cada paso (LAYOUT 550/40 + ResizeObserver) mientras los pasos se deslizan (x: ±12px, popLayout, 0.16s easeOut fades). Pie: tira h-12, Atrás (secundario xs) + primario xs, alineado a la derecha.

Barra de pestañas

La píldora activa es una ventana fija; las etiquetas se deslizan detrás de ella. La etiqueta entrante entra desde la dirección de desplazamiento al 110% de su propio ancho; la saliente sale a 130px fijos en la otra dirección (un porcentaje se queda corto cuando la píldora crece hacia una palabra más larga). El ancho/alto de la píldora sigue la etiqueta medida en muelles LAYOUT. Opacidad completa durante todo el proceso; la máscara hace el trabajo.

Esqueletos

  1. El chrome nunca espera; solo el área de datos es esqueleto.
  2. Coincidencia de píxeles: una barra se sienta dentro de la misma ranura de line-height que el texto al que representa (flex h-6 items-center envolviendo una barra h-3); el intercambio no mueve nada.
  3. Los datos llegan por enfoque: una animación de una sola vez desde opacity ~0.4 + blur(4px) a nítido. Una animación, no una transición (el contenido se monta ya final; una transición no tiene valor anterior). Mantén filter: blur(0) después.

Barra: rounded bg-muted-foreground/15, barrido lento de fondo-position (2s linear infinite, desplazamiento inicial -1s para que las barras estén desfasadas).

Avisos, estados vacíos, la píldora flotante

Tira de aviso encima del contenido que explica:

<div class="flex flex-col gap-2 rounded-2xl border border-border bg-card
            px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
     role="status">
  <p class="text-sm leading-6 text-muted-foreground">
    <span class="font-medium text-foreground">Aún no hay eventos.</span>
    Si el fragmento está activo, navega por tu sitio y captaremos la primera
    página vista en segundos.
  </p>
  <button class="…píldora secundaria…">Verificar la instalación</button>
</div>

Una afirmación en negrita, una oración en gris, una acción secundaria; se apila debajo de sm:; vive exactamente mientras dura el estado; sin descarte.

Píldora flotante para condiciones permanentes en toda la app: fixed top-16, centrada, rounded-full bg-card py-1.5 pl-3 pr-4 text-xs font-medium, sombra flotante, punto de estado de 8px (pulsante cuando es urgente), entra con muelle en BANNER (400/30) desde y:-8, sale 0.15s. Toda la píldora enlaza a donde se resuelve el estado. Las píldoras de estado nunca se descartan a sí mismas; las píldoras de evento se retiran después de ~6s.

Toasts

Menos de 0.35s, easing cubic-bezier(0.5, 1, 0.89, 1):

  • Éxito: pulso scale 1 → 1.025 → 0.99 → 1 (0.32s).
  • Error: sacudida x 0 → -3 → 3 → -3 → 0 px (0.28s).

Alterna copias de fotogramas clave pares e impares para que las repeticiones se reinicien.


5. Movimiento

NombreValorUsado para
PANEL550 / 38desplegables, menús, paneles, alternancias
LAYOUT550 / 40altura/ancho medido, píldoras deslizantes
POP400 / 26entrada de modal
POP_EXIT380 / 28salida de modal
BANNER400 / 30píldoras flotantes, banners de página
FLICK900 / 50micromovimientos de iconos
CHART300 / 25-30tooltips de gráficos, crosshair

Microdesvanecimientos: 0.1s a 0.18s easeOut. Nada en el chrome de la app se anima más de 0.2s; si se siente lento, baja el damping, no alargues la duración.

Movimientos característicos: coreografía de altura medida (un panel constante, pasos deslizándose), el resaltado viajero (un elemento activo compartido, nunca repintados por elemento), asimetría de entrada/salida (las salidas siempre más rápidas), hover solo como cambio de color (más un deslizamiento de flecha de como máximo 2px; las tarjetas nunca se elevan o escalan), y como máximo un bucle ambiental pequeño por pantalla.

Reglas: AnimatePresence para todo lo que se desmonta; respeta prefers-reduced-motion (las revelaciones colapsan a visible, los bucles se detienen); nunca animes un layout no medido; nunca leas el reloj de pared durante el renderizado (suscríbete a un store tickeante en intervalos de minutos).


6. Páginas de aterrizaje

Mismos tokens, s

…

[truncado para traducción]

Skills relacionados