Diseñar con intención.
Construir con consistencia.
Un sistema de diseño estructurado para Tradeit TCG. Basado en Atomic Design, inspirado en Carbon, pensado para una plataforma de marketplace profesional de coleccionables digitales.
Primeros pasos
Mulligan es el lenguaje visual de Tradeit TCG. Este documento es la fuente de verdad para diseñadores y desarrolladores.
Instalación de fuentes
Agregar las fuentes IBM Plex via Google Fonts en el <head> de tu documento:
<link rel="preconnect" href="https://fonts.googleapis.com"> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@300;400;500&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
Variables CSS base
Copiar estos tokens en tu archivo CSS global. Son la base de todos los componentes del sistema:
:root {
/* Color — Blue (primario) */
--blue-60: #1A6FB5;
--blue-80: #0D4A80;
--blue-10: #EBF2FB;
/* Semánticos light mode */
--color-bg-primary: #FFFFFF;
--color-bg-secondary: #F0EFEC;
--color-text-primary: #131210;
--color-text-secondary: #5C5A56;
--color-border: #D6D4CE;
--color-interactive: #1A6FB5;
--color-success: #2DA67E;
--color-warning: #E69A20;
--color-danger: #D94646;
/* Tipografía */
--font: 'IBM Plex Sans', system-ui, sans-serif;
--mono: 'IBM Plex Mono', monospace;
/* Espaciado base 4px */
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px;
--sp-4: 16px; --sp-6: 24px; --sp-8: 32px;
/* Border radius */
--r-sm: 4px; --r-md: 8px; --r-lg: 12px; --r-xl: 16px;
}
Principios de diseño
Cinco principios guían cada decisión visual en Mulligan. Ante cualquier duda de diseño, volver a estos principios.
Paleta de colores
5 ramps semánticos de 6 stops cada uno. Cada color tiene un uso específico y no debe intercambiarse arbitrariamente.
Blue — Primario interactivo
| Token | Hex | Uso |
|---|---|---|
blue-10 | #EBF2FB | Fondos hover, backgrounds de badges info |
blue-40 | #6AAAD8 | Íconos secundarios, estados intermedios, dark mode interactive |
blue-60 | #1A6FB5 | Color primario de acción. Botones, links, focus rings |
blue-80 | #0D4A80 | Hover de botón primario, texto sobre blue-10 |
Gray — Neutro estructural
Ramps semánticos
Tokens semánticos
Light Mode
Tipografía
Escala modular con ratio 1.25. IBM Plex Sans para UI, IBM Plex Mono exclusivo para precios y datos numéricos.
| Nombre | Size | Weight | Line-height | Uso |
|---|---|---|---|---|
| Display | 48px | 300 | 1.1 | Heroes, landing page |
| Heading 1 | 32px | 300–400 | 1.2 | Título de página principal |
| Heading 2 | 24px | 500 | 1.3 | Sección principal, panel header |
| Heading 3 | 20px | 500 | 1.4 | Nombre de carta en ficha |
| Body | 16px | 400 | 1.6 | Texto de párrafo |
| Body Small | 14px | 400 | 1.6 | Descripciones, helpers |
| Label | 12px | 500 | 1.4 | Etiquetas uppercase (ls: 0.08em) |
| Caption | 11px | 400 | 1.4 | Timestamps, metadatos |
| Price | 14–28px | 400 | 1.2 | Precios en IBM Plex Mono |
Muestras
Espaciado
Sistema base 4px. Todos los valores de espaciado son múltiplos de 4 para garantizar consistencia matemática en toda la plataforma.
4px
8px
12px
16px
20px
24px
32px
40px
48px
64px
| Token | px | rem | Uso típico |
|---|---|---|---|
space-1 | 4px | 0.25rem | Gap interno mínimo |
space-2 | 8px | 0.5rem | Gap entre ícono y texto |
space-3 | 12px | 0.75rem | Padding en badges, chips |
space-4 | 16px | 1rem | Padding en inputs, gap en rows |
space-6 | 24px | 1.5rem | Padding de cards estándar |
space-8 | 32px | 2rem | Separación entre secciones |
space-10 | 40px | 2.5rem | Margin entre bloques principales |
space-12 | 48px | 3rem | Padding de secciones en web |
space-16 | 64px | 4rem | Separadores de página |
Grid y Layout
Sistema de grilla de 12 columnas para web y 4 columnas para app. Máximo de contenido: 1280px.
Breakpoints Web
| Breakpoint | Width | Columnas | Gutter | Margin |
|---|---|---|---|---|
sm | < 640px | 4 | 12px | 16px |
md | 640–1024px | 8 | 16px | 24px |
lg | 1024–1280px | 12 | 16px | 32px |
xl | > 1280px | 12 | 24px | auto (max 1440px) |
Breakpoints App (Mobile)
| Breakpoint | Width | Columnas | Gutter | Margin |
|---|---|---|---|---|
| Mobile | 320–390px | 4 | 12px | 16px |
| Mobile L | 390–428px | 4 | 16px | 16px |
| Tablet | 428–768px | 8 | 16px | 24px |
Elevación
4 niveles de elevación. La profundidad se comunica principalmente con bordes, no con sombras dramáticas.
Solo border
Cards
Dropdowns
Modales
Motion
Animaciones funcionales, no decorativas. Cada tipo tiene una duración y easing específicos calibrados para transmitir la sensación correcta de peso y respuesta. Siempre respetar prefers-reduced-motion.
| Tipo | Duración | Easing | Uso |
|---|---|---|---|
| Micro | 100ms | ease-out | Hover states, toggles, scale en active |
| Short | 150ms | ease-out | Tooltips, badges, chips activos |
| Medium | 200ms | ease-in-out | Dropdowns, notificaciones, color de fondo |
| Long | 300ms | ease-in-out | Modales overlay, drawers, sidebars |
| Chart | 400ms | ease-out | Animación inicial del gráfico de precio |
| Skeleton | 1400ms ∞ | ease-in-out | Loading shimmer en cards y contenido |
| Page Entry | 500ms | ease | Entrada de página, staggered reveals |
Micro — 100ms ease-out
La respuesta más rápida del sistema. El usuario siente feedback inmediato en cada interacción táctil o de cursor. Aplica a botones (color + scale en active), toggles y checkboxes.
transition: background-color 100ms ease-out, transform 100ms ease-out;
Aplicar escala sutil scale(0.97) en el estado :active de los botones. Da sensación física de "presión" sin retardar la interacción.
No superar 100ms en respuestas de hover. Por encima de ese umbral la UI se siente lenta y el usuario pierde la sensación de control directo.
Short — 150ms ease-out
Para elementos que aparecen como respuesta directa a una acción del usuario: tooltips, badges de éxito tras una acción, chips que se activan. Suficientemente rápido para no interrumpir, suficientemente visible para orientar.
transition: opacity 150ms ease-out, transform 150ms ease-out;
Los tooltips deben aparecer con un pequeño desplazamiento vertical (4px → 0) además del fade. Esto da dirección al movimiento e indica de dónde viene el elemento.
No animar la desaparición de tooltips. Deben ocultarse inmediatamente al quitar el cursor para no bloquear visualmente el contenido subyacente.
Medium — 200ms ease-in-out
Para elementos que se despliegan o aparecen desde un contexto ya visible: dropdowns de filtro, notificaciones inline que se insertan en el flujo, transiciones de color de fondo en estados de carga.
animation: dropIn / notifSlide 200ms ease-in-out both;
El ease-in-out en 200ms da la sensación de que el elemento tiene masa pero no peso. Ideal para overlays pequeños que no bloquean el flujo principal de trabajo.
No reutilizar la animación de dropdown para notificaciones toast globales. Los toasts deben deslizarse desde el borde de la pantalla, no aparecer desde un punto fijo.
Long — 300ms ease-in-out
Para transiciones de pantalla completa o que cubren una porción significativa del viewport. El modal combina fade del overlay con scale + translateY del contenido. El drawer desliza desde el borde con transform.
Modal: scale(.96)+translateY(12px) → scale(1) · 300ms ease-in-out
Drawer: translateX(-100%) → translateX(0) · 300ms ease-in-out
El overlay del modal debe animarse de forma independiente al contenedor: el overlay aparece primero (opacity), y el modal aparece 20–30ms después con scale+translateY para dar sensación de capas.
No usar animaciones largas (>300ms) en interacciones repetitivas. Si el usuario abre y cierra el mismo modal varias veces, 400ms+ se vuelve frustrante muy rápido.
Chart — 400ms ease-out
Animación de entrada del gráfico de precio. La línea se dibuja de izquierda a derecha usando stroke-dashoffset, seguida del área sombreada con un fade. El usuario percibe el tiempo como una narrativa: "el precio fue así con el tiempo".
stroke-dasharray:400; stroke-dashoffset: 400 → 0 · 400ms ease-out
Área fill: opacity 0 → 1 · 400ms ease-out · delay 200ms
Al cambiar el periodo (1M → 3M → 9M), volver a disparar la animación. Le recuerda al usuario que los datos cambiaron y que la nueva línea representa un nuevo rango temporal.
No animar el gráfico si ya fue visible y el usuario hace scroll hacia arriba. La animación solo se dispara una vez en la entrada inicial de la ficha de carta.
Skeleton — 1400ms ∞ ease-in-out
El skeleton loader reemplaza el contenido durante la carga. El shimmer viaja de izquierda a derecha simulando un reflejo de luz. Debe preservar exactamente la forma del contenido real para evitar el "layout shift" al cargar.
background: linear-gradient(90deg, bg-secondary 25%, bg-page 50%, bg-secondary 75%);
background-size: 1200px; animation: shimmer 1.4s ease-in-out infinite;
El skeleton debe tener exactamente las mismas dimensiones que el contenido real. La transición de skeleton a contenido debe ser instantánea (sin fade) para minimizar el layout shift percibido.
No mostrar skeleton por más de 3 segundos sin mostrar un estado de error. Si los datos no cargan, reemplazar el skeleton con un empty state que permita al usuario reintentar.
Page Entry — 500ms staggered
Al entrar a una nueva página, los elementos principales aparecen en secuencia con un delay escalonado (stagger) de 80ms entre cada uno. Crea una sensación de "construcción" progresiva sin parecer lenta. Máximo 4 elementos en stagger.
@keyframes fadeUp: opacity 0→1, translateY 14px→0 · 500ms ease
nth-child(1): delay 0ms · nth-child(2): 80ms · nth-child(3): 160ms · nth-child(4): 240ms
Limitar el stagger a máximo 4 elementos con delay máximo de 240ms total. Si hay más elementos, animar solo los primeros 4 y mostrar el resto sin animación para que la página no tarde en ser usable.
No aplicar stagger a listas de items dinámicos (resultados de búsqueda). El stagger en grillas de muchos items se ve errático. Usar solo en elementos estructurales de la página como hero, titulo, badges y precio.
Código de referencia
@media (prefers-reduced-motion: no-preference) {
/* Micro — hover + active */
.btn { transition: background-color 100ms ease-out, transform 100ms ease-out; }
.btn:active { transform: scale(0.97); }
/* Short — tooltip */
.tooltip { opacity:0; transform:translateY(4px);
transition: opacity 150ms ease-out, transform 150ms ease-out; }
.tooltip-trigger:hover .tooltip { opacity:1; transform:translateY(0); }
/* Medium — dropdown */
@keyframes dropIn {
from { opacity:0; transform:translateY(-6px); }
to { opacity:1; transform:translateY(0); }
}
.dropdown { animation: dropIn 200ms ease-in-out both; }
/* Long — modal */
@keyframes modalIn {
from { opacity:0; transform:scale(.96) translateY(12px); }
to { opacity:1; transform:scale(1) translateY(0); }
}
.modal { animation: modalIn 300ms ease-in-out both; }
/* Chart — path draw */
@keyframes chartDraw {
from { stroke-dashoffset: 400; }
to { stroke-dashoffset: 0; }
}
.chart-line { stroke-dasharray:400; animation: chartDraw 400ms ease-out; }
/* Skeleton shimmer */
@keyframes shimmer {
0% { background-position:-600px 0; }
100% { background-position: 600px 0; }
}
.skeleton {
background: linear-gradient(90deg,
var(--bg-secondary) 25%, var(--bg-page) 50%, var(--bg-secondary) 75%);
background-size:1200px;
animation: shimmer 1.4s ease-in-out infinite;
}
/* Staggered page entry */
@keyframes fadeUp {
from { opacity:0; transform:translateY(14px); }
to { opacity:1; transform:translateY(0); }
}
.stagger > *:nth-child(1) { animation: fadeUp 500ms ease both 0ms; }
.stagger > *:nth-child(2) { animation: fadeUp 500ms ease both 80ms; }
.stagger > *:nth-child(3) { animation: fadeUp 500ms ease both 160ms; }
.stagger > *:nth-child(4) { animation: fadeUp 500ms ease both 240ms; }
}
Íconos
Sistema de íconos basado en Phosphor Icons. Trazo consistente de 1.8px, 4 tamaños estándar.
| Tamaño | Uso |
|---|---|
| 16px | Inline en texto, badges, labels |
| 20px | Controles de UI, inputs con ícono |
| 24px | Botones con ícono, navegación |
| 32px | Ilustrativos, empty states |
Íconos del sistema
| Contexto | Ícono (Phosphor) |
|---|---|
| Búsqueda | MagnifyingGlass |
| Filtros | Funnel |
| Comprar | ShoppingCart |
| Vender / Listing | Tag |
| Wishlist | Heart |
| Notificaciones | Bell |
| Perfil | User |
| Verificado | SealCheck |
| Precio ↑ | TrendUp |
| Precio ↓ | TrendDown |
| Envío | Package |
| Grading | Certificate |
Átomos
Las unidades más pequeñas del sistema. Cada átomo es funcional por sí mismo y es la base de moléculas y organismos.
Buttons
Usar Primary solo para la acción más importante de la pantalla. Un máximo de un botón Primary visible por vista.
No usar múltiples botones Primary en paralelo. No usar Danger para acciones reversibles.
Botones de acción destructiva (eliminar listing, cancelar orden) usan variante Danger precedida de confirmación modal.
No deshabilitar botones sin explicar por qué. Usar helper text o tooltip que indique la condición bloqueante.
Badges
Usar el sistema de color semántico: azul para info/condición, teal para éxito/verificado, amber para advertencia, rojo para error. El badge oscuro sólido es exclusivo para grading certificado (PSA, CGC).
No usar más de 2 badges simultáneos en un mismo contexto de card. No inventar colores fuera del sistema para nuevos estados.
Chips / Filtros
Los chips de tipo de producto son mutuamente excluyentes. Siempre hay exactamente uno activo. Usar para filtros rápidos con máximo 5 opciones visibles.
No usar chips para acciones. Los chips son exclusivamente para filtrado y selección de estado. Las acciones van en botones.
Inputs
Validar on blur (cuando el usuario sale del campo). Mostrar el error de forma inline debajo del campo afectado, con color rojo y descripción del problema.
No validar on keypress (bloquea mientras el usuario escribe). No mostrar todos los errores de un formulario al hacer submit sin marcar cada campo individualmente.
Los campos de precio siempre deben incluir la unidad (CLP, USD) en el label o placeholder. El helper text muestra la referencia de mercado como orientación.
No usar placeholder como sustituto del label. El placeholder desaparece al escribir y no debe contener información crítica para completar el campo.
Card de carta
Truncar nombre y set con ellipsis cuando excedan el ancho. La card mantiene siempre la misma altura: el precio y la tendencia deben ser siempre visibles.
No omitir la tendencia de precio. Es un dato crítico para el usuario inversionista. Si no hay datos suficientes, mostrar "–" en lugar de ocultar el espacio.
Las cartas graduadas usan badge oscuro sólido (PSA/CGC + grado). El fondo de la imagen usa amber para diferenciar visualmente el tipo de producto.
No usar imágenes redondas para el arte de la carta. Siempre rectángulo con border-radius 6px para respetar las proporciones reales de las cartas físicas.
Moléculas
Grupos de átomos que funcionan juntos como una unidad con responsabilidad única. Las moléculas combinan átomos simples (labels, inputs, botones, badges) para resolver un problema específico de interfaz, de la misma forma en que los átomos se combinan en química para formar algo con propiedades nuevas.
Notificaciones inline
Alertas contextuales dentro del flujo de contenido. Compuestas por borde lateral semántico (3px), fondo tonal y bloque de texto con título + descripción. Cuatro variantes semánticas fijas: info, éxito, advertencia y error.
Info: border-left: 3px blue-60 · fondo blue-10 · título blue-80
Success: border-left: 3px teal-40 · fondo teal-10 · título teal-60
Warning: border-left: 3px amber-40 · fondo amber-10 · título amber-60
Error: border-left: 3px red-40 · fondo red-10 · título red-60
Las notificaciones inline son estáticas. El usuario las descarta manualmente. Usar en el contexto donde ocurre el evento: formulario con errores, resultado de acción o alerta de sistema persistente.
No apilar más de 2 notificaciones inline en la misma vista. No usar la variante error para comunicar estados informativos: cada variante tiene un significado semántico fijo e inamovible.
Display de precio
Componente de datos financieros. Combina label categórico (uppercase 11px 500), valor principal en IBM Plex Mono y tendencia con flecha + porcentaje + período. Tres tamaños según el contexto de uso.
Precio siempre en IBM Plex Mono para alineación de dígitos. Label en uppercase 11px. Tendencia con flecha + porcentaje + período explícito. La moneda (CLP/USD) siempre visible.
No mezclar CLP y USD en el mismo componente sin indicar cuál es precio local y cuál es referencia. No omitir la moneda pensando que el usuario la recordará del contexto.
Tabs
Navegación entre vistas del mismo contenido agrupado por categoría. Altura de ítem 36px, indicador activo border-bottom 2px en interactive con margin-bottom -1px para solapar el borde del contenedor. Dos variantes: estándar (línea) y contenida (switcher).
Las tabs son navegación entre categorías. El cambio no requiere confirmación: siempre una activa. La variante switcher es solo para exactamente 2 opciones excluyentes (Web/App, Light/Dark, Lista/Grilla).
No usar tabs para pasos de un proceso secuencial: usar stepper. No exceder 5 tabs en la misma barra. No ocultar tabs según el rol sin señalarlo: mostrar la tab deshabilitada en su lugar.
Breadcrumb
Ruta de navegación jerárquica. Compuesto por ítems separados por "/" donde los ítems previos son links en color interactivo y el ítem actual es texto secundario no clickeable. Si hay más de 4 niveles, el centro se colapsa con "…".
El ítem actual en text-secondary sin cursor pointer. Si hay más de 4 niveles, colapsar el medio con "…" clickeable que muestra la ruta completa en tooltip. El separador "/" siempre en text-muted.
No mostrar breadcrumb en home ni en páginas de primer nivel. El nombre de la página actual en el H1 debe ser idéntico al ítem del breadcrumb: no usar títulos distintos entre ambos.
Form Field
Unidad fundamental de cualquier formulario. Composición vertical fija: label (12px 500) → input/select (40px) → helper text (11px). Cuatro estados: default, success (border teal), error (border rojo), disabled (opacidad 0.4, no editable).
Validar on blur (al salir del campo). El helper text en error describe el problema específico, no un mensaje genérico. El campo de precio incluye la unidad (CLP/USD) en el label.
No validar on keypress: interrumpe al usuario mientras escribe. No usar placeholder como sustituto del label: desaparece al escribir. Nunca mostrar todos los errores solo al hacer submit.
Star Rating
Valoración de vendedor. Compuesto por estrellas en amber-40, promedio numérico (1 decimal) y conteo de reseñas. Dos modos: solo lectura (perfiles y listas) e interactivo (flujo post-compra). Con menos de 5 reseñas muestra "Nuevo vendedor".
Con menos de 5 reseñas, mostrar "Nuevo vendedor" en lugar del promedio numérico para no distorsionar la percepción de confianza del usuario comprador con muestras estadísticamente insignificantes.
No mostrar el componente interactivo en el perfil público del vendedor: solo aparece en el flujo post-compra. No permitir que el mismo usuario valore dos veces al mismo vendedor en la misma transacción.
Progress Bar
Indicador de avance para procesos con valor cuantificable. Track de 4px en bg-secondary, fill en color semántico con border-radius 2px. Siempre acompañado de label de contexto y valor numérico visible. No usar para carga indeterminada.
Mostrar siempre el valor numérico (porcentaje o fracción x/total) junto a la barra. El color del fill refleja el estado semántico: azul neutro, teal éxito, amber en progreso, rojo error.
No usar para carga indeterminada (sin tiempo definido). Para eso usar skeleton loader o spinner. La progress bar siempre implica un valor conocido y finito de avance que el sistema puede calcular.
Avatar
Representación visual del usuario. Círculo con iniciales (2 caracteres) sobre fondo tonal. Tres tamaños: Sm 28px para topbar y listas, Md 40px para paneles y cards, Lg 56px para páginas de perfil. Puede incluir un dot indicador de estado en la esquina inferior derecha.
Usar las 2 primeras letras del username. Asignar el color de fondo de forma determinista basada en el username (hash del string → índice de ramp) para que el mismo usuario siempre tenga el mismo color.
No usar avatares cuadrados o rectangulares. No mostrar avatares sin fallback: si la imagen de perfil no carga, siempre mostrar el círculo con iniciales. No usar emoji como avatar en ningún contexto.
Organismos
Secciones completas de la UI compuestas por moléculas y átomos. Cada organismo es independiente, tiene su propio propósito y puede existir en múltiples páginas.
Top Navigation Bar
Presente en todas las páginas web. Altura fija 48px, position fixed, z-index 100. Compuesta por logo, search central y acciones de usuario a la derecha.
La barra de navegación debe ser siempre sticky (position: fixed). El logo siempre enlaza al home. En estado autenticado, el avatar reemplaza los botones de auth.
No agregar más de 2 acciones secundarias en el topbar. No ocultar el topbar en scroll en desktop: solo es aceptable en mobile y solo al hacer scroll hacia abajo.
Search & Filter Bar
Barra secundaria debajo del nav, sticky independiente. Concentra todos los controles de búsqueda: input, selects de edición, chips de tipo de producto y CTA.
Los chips de tipo de producto son mutuamente excluyentes. El botón de búsqueda siempre al extremo derecho. En mobile, colapsar selects y chips en un botón "Filtros" que abre un bottom sheet.
No aplicar la búsqueda automáticamente con debounce en este contexto: el usuario puede estar ingresando un nombre complejo. Requiere acción explícita (Enter o botón).
Grid de resultados
Grilla de 4 columnas en desktop con sidebar de filtros a la izquierda. Máximo 24 cards por página. Incluye header con conteo de resultados y control de ordenamiento.
Mostrar siempre el conteo de resultados en el header de la grilla. Al aplicar un filtro, hacer scroll al inicio del contenido. Usar skeleton loaders durante la carga, no spinners.
No resetear los filtros al cambiar de página de paginación. Los filtros aplicados persisten durante toda la sesión de búsqueda del usuario.
Ficha de carta + Dashboard de precios
Vista de detalle completa. Columna principal con datos oficiales (Pokémon TCG API), gráfico histórico con controles de periodo, y columna lateral con CTA de compra y datos del vendedor.
Los datos de imagen, nombre, número y rareza provienen exclusivamente de la Pokémon TCG API (source of truth). El vendedor no puede editar estos campos en su listing.
No mostrar el gráfico de precio si hay menos de 3 transacciones en el periodo seleccionado. Mostrar mensaje "Datos insuficientes · mostrando referencia TCGPlayer" en su lugar.
Los listings se ordenan por precio ascendente por defecto. El comprador siempre ve primero la mejor oferta disponible para la condición que filtró.
No mezclar condiciones en el mismo listado sin diferenciación visual clara. Un listing NM y uno LP del mismo vendedor deben tener sus badges de condición prominentes.
Panel de Usuario (Vendedor)
Dashboard del vendedor con resumen de métricas, listado de órdenes recientes y acceso rápido a gestión de listings. Mismo patrón para el panel comprador con métricas adaptadas.
Las métricas del panel (total recaudado, completitud) se calculan solo sobre transacciones con estado "Entregado". Las órdenes canceladas no cuentan en el promedio positivo de reputación.
No mostrar el total bruto sin deducir comisiones. El vendedor debe ver siempre el monto neto que realmente recibirá para planificar su negocio correctamente.
El color del dot de estado debe coincidir exactamente con el color del badge de estado. Redundancia visual intencional para usuarios con daltonismo.
No paginar las órdenes recientes en el resumen del panel. Mostrar las últimas 5 con un link "Ver todas" que lleva a la vista completa de seguimiento.
Modal
Overlay centrado con fondo semitransparente. 3 tamaños: sm (480px), md (640px), lg (800px). Siempre con header, body y footer de acciones. En mobile se convierte en bottom sheet.
En modales de confirmación de compra o acciones destructivas, la acción primaria siempre a la derecha. Incluir un resumen del objeto afectado dentro del modal para que el usuario no tenga que recordarlo.
No apilar modales. Un modal que abre otro modal confunde al usuario sobre cómo volver atrás. Si se necesita flujo de pasos, usar un stepper dentro de un solo modal.
Bottom Navigation (App)
Barra de navegación fija al fondo en mobile. 4 tabs: Inicio, Buscar, Mis compras/ventas y Perfil. Siempre visible, no se oculta en scroll.
Mantener exactamente 4 tabs. La tab activa resalta con color interactivo en ícono y label. Touch target de cada tab: mínimo 44×44px incluyendo safe area inferior en iOS.
No ocultar el bottom nav durante el scroll. No usar badges de notificación en más de 2 tabs simultáneamente, genera ruido visual que desenfoca la atención del usuario.
Empty State
Estado vacío para búsquedas sin resultado, wishlist vacía o panel sin actividad. Siempre incluye ícono neutral, título descriptivo, descripción orientativa y CTA cuando corresponde.
El empty state debe explicar por qué no hay contenido y ofrecer una acción concreta para resolverlo. El ícono debe ser neutral y relacionado con el contexto (búsqueda, carrito, perfil).
No mostrar un empty state genérico "Sin resultados" sin contexto. No incluir CTA de acción destructiva en un empty state (nunca "Eliminar todo" como solución a una lista vacía).
Templates
Layouts de página completos que orquestan los organismos en estructuras coherentes. Los templates definen la composición espacial de cada pantalla, sin contenido real: son el esqueleto sobre el que se construyen las páginas.
Template: Búsqueda y Resultados
Layout de 2 columnas: sidebar de filtros (240px) + grilla de resultados (flex 1). Top nav y filter bar son sticky independientes. Paginación centrada al final del contenido.
El sidebar de filtros es sticky dentro del scroll de contenido (no del page). Al aplicar un filtro, el conteo de resultados se actualiza inmediatamente. El template mantiene el sidebar visible incluso si hay pocos resultados.
No colapsar el sidebar automáticamente en pantallas intermedias (1024px). En ese rango, reducir el sidebar a 180px pero mantenerlo visible. Ocultarlo completamente solo bajo 768px.
En mobile (app), el template se transforma: sidebar desaparece, chips de filtro en scroll horizontal, grilla pasa a 2 columnas, paginación pasa a "cargar más" (infinite scroll opcional).
No mostrar un estado vacío en el área de grilla si los filtros aplicados son la causa. Acompañar siempre el empty state con el botón "Limpiar filtros" como CTA principal.
Template: Ficha de Carta (Detalle)
Layout de 2 columnas: columna principal 8/12 con imagen oficial, datos del set, gráfico histórico y listings. Columna lateral 4/12 sticky con CTA de compra e info del vendedor.
La columna lateral (CTA de compra + vendedor) es sticky dentro del scroll de la columna principal. El usuario puede hacer scroll por los listings sin perder de vista el botón de compra.
No duplicar el CTA de compra en la columna principal. Solo existe en la columna lateral para mantener la jerarquía visual clara y evitar confusión sobre cuál acción ejecutar.
En mobile, el template colapsa a una sola columna con el CTA de compra fijo en la barra inferior (position: sticky, bottom: 0 + safe area). Todos los datos se apilan verticalmente en ese orden: imagen → precios → gráfico → listings.
No permitir que el gráfico de precio ocupe más del 30% del viewport sin scroll. En desktop es suficiente con 120px de alto. El gráfico es contexto, no el foco principal de la pantalla.
Template: Panel de Usuario
Layout con sidebar de navegación interna (220px) + área de contenido. La navegación del panel es secundaria y no reemplaza el topbar principal. En mobile usa bottom tab navigation.
El sidebar de navegación del panel es persistente: todas las sub-secciones (Resumen, Compras, Listings, Valoraciones, Configuración) son accesibles en todo momento sin salir del panel.
No mezclar la navegación del panel con la navegación global del topbar. Son contextos independientes. El topbar navega a secciones globales; el sidebar del panel navega dentro del área personal.
El estado activo del sidebar del panel usa border-left de 2px en color interactivo + fondo blue-10, igual que el sidebar de documentación. Coherencia sistémica entre superficies de navegación.
No esconder datos financieros (total recaudado, total gastado) detrás de un click adicional. Las métricas principales están en el resumen al entrar al panel, siempre visibles de inmediato.
Template: Autenticación
Layout minimalista sin topbar principal. Card centrada horizontal y verticalmente sobre un fondo de página. Máximo 480px de ancho. Misma estructura para registro, login y recuperación de contraseña.
La card de autenticación tiene siempre el logo arriba, el formulario al centro y el link alternativo (¿Ya tienes cuenta? / ¿Olvidaste tu contraseña?) al fondo. El orden no varía entre login y registro.
No mostrar el topbar global en páginas de autenticación. Quita toda distracción y enfoca al usuario en la tarea. Solo el logo dentro de la card como punto de identidad de marca.
Validar inline field por field (on blur). Si el formulario es de registro y tiene múltiples pasos (datos → verificación), usar un stepper dentro de la misma card, no pantallas separadas.
No redirigir automáticamente al usuario tras el login sin confirmación visual. Mostrar brevemente "Sesión iniciada" antes de la redirección para confirmar que la acción fue exitosa.
Light Mode
El modo claro es el modo por defecto de Mulligan. Está basado en una escala de grises cálidos derivada del ramp Gray, con superficies blancas y fondos de página ligeramente tintados. La elevación se comunica con sombras sutiles y diferencias de borde, nunca con colores de fondo saturados.
Implementación
Los tokens de Light Mode son los valores por defecto definidos en :root. No requieren clase adicional: cualquier componente que use las variables CSS semánticas renderiza automáticamente en light mode cuando el usuario no ha seleccionado dark mode y el sistema operativo no lo solicita.
/* Tokens por defecto en :root — Light Mode */
:root {
--bg-page: #F8F8F6; /* gray-0: fondo de página */
--bg-primary: #FFFFFF; /* blanco puro: cards, inputs, nav */
--bg-secondary: #F0EFEC; /* gray-10: hover, filter bar, tags */
--text-primary: #131210; /* gray-100: texto principal */
--text-secondary: #5C5A56; /* gray-60: labels, descripciones */
--text-muted: #9B9892; /* gray-40: metadatos, placeholders */
--border: #D6D4CE; /* gray-20: borders por defecto */
--border-em: #B2B0AB; /* gray-30: borders énfasis/hover */
--interactive: #1A6FB5; /* blue-60: botones, links, focus */
--success: #2DA67E; /* teal-40 */
--warning: #E69A20; /* amber-40 */
--danger: #D94646; /* red-40 */
}
/* El modo claro es el default: no requiere clase especial */
/* Solo el dark mode necesita la clase .dark en <html> */
Paleta de fondos — capas de elevación
En light mode la elevación se comunica con sombras progresivamente más pronunciadas y con diferencias de 1px en los bordes. Las capas van desde el fondo de página (más oscuro dentro de la escala clara) hasta los overlays flotantes (con shadow prominente).
Tokens semánticos — Light Mode completo
| Token | Valor Light | Ramp | Uso |
|---|---|---|---|
--bg-page | #F8F8F6 | gray-0 | Fondo de página. Ligeramente tintado para reducir fatiga vs. blanco puro. |
--bg-primary | #FFFFFF | — | Cards, inputs, topbar, modales. La superficie "elevada" principal. |
--bg-secondary | #F0EFEC | gray-10 | Filter bar, hover states, tags, fondos de skeleton. |
--text-primary | #131210 | gray-100 | Cuerpo de texto, títulos, precios. Máximo contraste. |
--text-secondary | #5C5A56 | gray-60 | Labels uppercase, descripciones secundarias, metadatos. |
--text-muted | #9B9892 | gray-40 | Placeholders, timestamps, texto deshabilitado. |
--border | #D6D4CE | gray-20 | Todos los bordes por defecto. 0.5px–1px. |
--border-em | #B2B0AB | gray-30 | Bordes en hover, énfasis, separadores visibles. |
--interactive | #1A6FB5 | blue-60 | Botones primarios, links, focus rings, active tabs. |
--success | #2DA67E | teal-40 | Confirmaciones, precios al alza, vendedor verificado. |
--warning | #E69A20 | amber-40 | Advertencias, precios inusuales, grading highlight. |
--danger | #D94646 | red-40 | Errores, acciones destructivas, precios a la baja. |
Escala tipográfica en Light Mode
En light mode el texto primario #131210 sobre fondo blanco logra un ratio de contraste de 17.5:1, muy por encima del mínimo AA (4.5:1). El texto secundario #5C5A56 sobre blanco alcanza 7.1:1. Todos los pares del sistema cumplen WCAG AA como mínimo.
Vista previa de componentes en Light Mode
Todos los componentes del sistema en su estado de luz: navegación, cards de carta, notificaciones, formulario y botones. Las superficies blancas se elevan sobre el fondo gris mediante bordes y sombras mínimas.
Elevación — escala completa
Cuatro niveles de elevación en light mode. El nivel 0 usa solo borde como separador visual. Los niveles 1–3 agregan box-shadow progresiva para comunicar profundidad y separación del fondo de página.
Solo border
Cards planas, inputs
Shadow suave
Cards en grid
Shadow media
Dropdowns, tooltips
Shadow profunda
Modales, drawers
Usar blue-60 (#1A6FB5) como color interactivo en light mode. Este valor cumple contraste AA (4.5:1) sobre blanco y sobre bg-page. Cumple además contraste 3:1 para elementos de UI (bordes de inputs, íconos).
No usar blanco puro (#FFFFFF) como fondo de página. El sistema usa #F8F8F6 (gray-0) para reducir la fatiga visual y dar contexto de profundidad a las cards blancas que se elevan sobre él.
La elevación en light mode usa box-shadow progresiva. Nivel 0 = solo border, Nivel 1 = shadow 6% opacidad, Nivel 2 = shadow 8%, Nivel 3 = shadow 12%. Las sombras siempre en negro con opacidad, nunca en color.
No usar colores de fondo saturados para diferenciar superficies en light mode (ej. fondo azul claro para el nav). La jerarquía visual se construye con la escala gray, no con tonos de color. Los colores son para semántica, no para estructura.
Los focus rings en light mode usan box-shadow: 0 0 0 2px rgba(26,111,181,.2). El anillo es semitransparente para que se vea bien tanto sobre fondo blanco como sobre bg-secondary gris.
No hardcodear colores hexadecimales en componentes aunque estés en light mode. Si lo haces, el dark mode dejará de funcionar en ese componente. Siempre usar var(--token-semantico) sin excepción.
Dark Mode
Estrategia completa de modo oscuro basada en tokens semánticos CSS. El dark mode no es una inversión de colores: es un sistema de capas donde los fondos más oscuros están abajo y los más claros emergen hacia el usuario.
Implementación
El sistema usa variables CSS que cambian según una clase en el elemento raíz. Esto permite que cada componente se adapte automáticamente sin CSS adicional, siempre que use variables semánticas y nunca valores hexadecimales directos.
/* 1. Detectar preferencia del sistema */
@media (prefers-color-scheme: dark) {
:root { --bg-page: #131210; --bg-primary: #1E1D1A; /* ... */ }
}
/* 2. Clase para toggle manual del usuario */
.dark {
--bg-page: #131210;
--bg-primary: #1E1D1A;
--bg-secondary: #252421;
--text-primary: #F0EFEC;
--text-secondary: #9B9892;
--text-muted: #5C5A56;
--border: #2E2D2A;
--border-em: #3D3C38;
--interactive: #6AAAD8; /* blue-40, más legible sobre oscuro */
}
/* 3. Toggle en JavaScript */
document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
Paleta de fondos — capas de elevación
En dark mode la elevación se comunica con fondos progresivamente más claros (no con sombras). Las capas van desde el fondo de página (más oscuro) hasta los overlays (más claros).
Tokens semánticos — comparación Light / Dark
| Token | Light Mode | Dark Mode | Nota |
|---|---|---|---|
--bg-page | #F8F8F6 | #131210 | Fondo de página base |
--bg-primary | #FFFFFF | #1E1D1A | Cards, modales, inputs |
--bg-secondary | #F0EFEC | #252421 | Hover, filter bar, tags |
--text-primary | #131210 | #F0EFEC | Texto principal — inversión |
--text-secondary | #5C5A56 | #9B9892 | Labels, descripciones |
--border | #D6D4CE | #2E2D2A | Borders por defecto |
--interactive | #1A6FB5 (blue-60) | #6AAAD8 (blue-40) | Más claro para contraste sobre oscuro |
--success | #2DA67E | #2DA67E | Sin cambio (ya legible) |
--warning | #E69A20 | #E69A20 | Sin cambio |
--danger | #D94646 | #D94646 | Sin cambio |
Vista previa de componentes en Dark Mode
Usar blue-40 (#6AAAD8) como color interactivo en dark mode. El blue-60 (#1A6FB5) no cumple contraste AA sobre fondos oscuros. Guardar la preferencia del usuario en localStorage para que persista entre sesiones.
No usar fondo negro puro (#000000) en dark mode. El sistema usa gray-100 (#131210) como base: aporta calidez y reduce la fatiga visual en sesiones largas. No invertir imágenes de cartas: tienen colores propios.
La elevación en dark mode se comunica con fondos progresivamente más claros (bg-page → bg-primary → bg-secondary), no con sombras. Las sombras se reducen al 50% de opacidad respecto a light mode.
No hardcodear ningún color hexadecimal en componentes. Siempre usar var(--nombre-token). Un solo hex hardcodeado puede romper el dark mode de un componente entero silenciosamente.
Responsive
Adaptaciones sistemáticas del mismo componente según el contexto de uso. El 80% del tráfico de Tradeit TCG ocurre en mobile (eventos y torneos): la app es el producto principal, la web es el complemento de escritorio.
Estrategia Mobile-First
Diseñar desde el layout más restrictivo (320px) hacia el más amplio (1440px). En cada breakpoint, se añaden elementos que el espacio adicional permite, no se quitan elementos del diseño de escritorio.
| Breakpoint | Rango | Cols | Gutter | Margin |
|---|---|---|---|---|
xs | 320–390px | 4 | 12px | 16px |
sm | 390–640px | 4 | 16px | 16px |
md | 640–1024px | 8 | 16px | 24px |
lg | 1024–1280px | 12 | 16px | 32px |
xl | >1280px | 12 | 24px | auto (max 1440px) |
Adaptaciones por componente
| Componente | Web (lg+) | App (xs/sm) | Cambio clave |
|---|---|---|---|
| Top Nav | Logo + search central + auth buttons | Logo + título + 2 íconos de acción | Search → pantalla dedicada |
| Navegación | Sidebar vertical 220px permanente | Bottom tab bar 4 items + safe area | De vertical a horizontal fijo en fondo |
| Filtros | Barra horizontal sticky inline | Botón "Filtros" → bottom sheet full | De inline a modal contextual |
| Grid de cartas | 4–6 columnas con sidebar | 2 columnas sin sidebar | Sidebar de filtros desaparece |
| Ficha de carta | 2 col: contenido (8/12) + CTA (4/12) | 1 col vertical + CTA sticky en bottom | CTA migra a barra inferior fija |
| Panel usuario | Sidebar nav 220px + contenido | Stack vertical sin sidebar | Navegación se fusiona en bottom tabs |
| Modal | Overlay centrado (max 640px) | Bottom sheet full width (90vh) | Overlay → slide desde abajo |
| Tablas | Full width con columnas visibles | Scroll horizontal o card view | Priorizar columnas críticas |
Bottom sheet (Mobile)
Sustituto mobile de los modales centrados y los paneles de filtro. Desliza desde el borde inferior. Siempre incluye handle de arrastre, soporte para drag-to-dismiss y cierre al tocar el overlay.
Los bottom sheets tienen siempre un handle visible (36×4px centrado), soportan drag-to-dismiss y cierran al tocar el overlay. Usar max-height: 90vh para que nunca cubran la pantalla completa sin intención.
No usar modales centrados en pantallas menores de 640px. No hacer scroll horizontal en ningún breakpoint excepto en tablas con contenedor controlado. El scroll lateral en toda la página es un error crítico de layout.
Touch targets mínimos de 44×44px en todos los elementos interactivos de la app. El espaciado entre elementos táctiles debe ser de al menos 8px para evitar activaciones accidentales durante el uso en movimiento.
No ocultar contenido importante detrás de más de un tap en mobile. Si el usuario necesita la información para decidir si compra, debe estar visible sin interacción adicional. Priorizar sobre mostrar más items en la grilla.
Accesibilidad
WCAG AA por defecto en todos los componentes. La accesibilidad no es un requisito adicional: es una restricción de diseño que mejora la experiencia para todos los usuarios, incluyendo los que usan la plataforma en condiciones de baja atención o alta velocidad.
Contraste de color
Todos los pares texto/fondo del sistema cumplen el ratio mínimo de contraste WCAG AA. Los colores interactivos cumplen también el ratio mínimo para elementos de UI (3:1).
Focus ring
Todos los elementos interactivos muestran un anillo de foco visible cuando se navega con teclado. Nunca usar outline: none sin un reemplazo equivalente.
box-shadow: 0 0 0 2px rgba(26,111,181, 0.3);
Checklist de accesibilidad
| Regla | Implementación | Nivel |
|---|---|---|
| Contraste de texto | Mínimo 4.5:1 para texto normal, 3:1 para texto grande (+18px o +14px bold) | AA |
| Contraste de UI | Mínimo 3:1 para bordes de inputs y íconos interactivos sin texto | AA |
| Focus visible | box-shadow: 0 0 0 2px rgba(var(--interactive), 0.3). Nunca outline:none sin reemplazo | AA |
| Semántica HTML | Acciones → <button>, navegación → <a>, formularios → <label for=""> asociados. No usar <div> para interacciones | A |
| Alt text | Cartas: alt="Charizard ex — Scarlet & Violet 151 — Special Illustration Rare". Imágenes decorativas: alt="" | A |
| ARIA labels | aria-label en íconos sin texto. role="status" aria-live="polite" en precios que se actualizan dinámicamente | AA |
| Orden de foco | El orden de tabulación debe seguir el flujo visual de lectura. No usar tabindex positivo: genera jerarquías confusas | A |
| Reducción de movimiento | Todas las animaciones envueltas en @media (prefers-reduced-motion: no-preference) | AAA |
| Touch targets | Mínimo 44×44px en app. Espaciado mínimo de 8px entre elementos táctiles | AA (2.5.5) |
| Texto escalable | No usar user-scalable=no. Diseñar para que el zoom hasta 200% no rompa el layout | AA |
| Doble codificación | Nunca comunicar estado solo con color. Siempre acompañar con ícono, texto o patrón adicional para usuarios con daltonismo | A |
| Errores de formulario | Asociar mensaje de error con el campo mediante aria-describedby. No depender solo del color rojo para indicar el error | AA |
Usar doble codificación para estados semánticos: color + ícono o color + texto. Un usuario con daltonismo no puede distinguir entre una notificación de éxito y de error solo por el color del borde.
No usar outline: none en elementos interactivos sin un focus ring equivalente. Los usuarios de teclado y lectores de pantalla necesitan saber en todo momento qué elemento está enfocado.
Los precios que se actualizan en tiempo real deben tener role="status" o aria-live="polite" para que los lectores de pantalla anuncien el cambio sin interrumpir la lectura del contexto actual.
No deshabilitar el zoom con user-scalable=no. Los usuarios con baja visión necesitan poder ampliar hasta 200% sin que el layout se rompa. Diseñar con zoom en mente desde el inicio.
Contenido
Guías de redacción, jerarquía informativa y estándares de etiquetado para toda la plataforma. El contenido es parte del diseño: el tono, la precisión y la consistencia del texto determinan si el usuario confía en la plataforma o no.
Voz y tono
Mulligan usa una voz directa, técnica y sin artificios. La plataforma habla con coleccionistas que conocen el vocabulario del mercado: no hace falta simplificar en exceso. La confianza se gana con precisión, no con informalidad forzada.
| Atributo | Descripción | Ejemplo |
|---|---|---|
| Directo | Frases cortas. Sin rodeos. El usuario actúa, la UI confirma. | "Pago confirmado" no "¡Tu pago fue procesado exitosamente!" |
| Técnico | Usar el vocabulario del coleccionista: NM, LP, PSA, BGS, set, listing, escrow. | "Listing en estado Near Mint" no "Producto en muy buen estado" |
| Honesto | Los errores explican la causa real, no mensajes genéricos. | "Precio mínimo permitido: $500 CLP" no "Valor inválido" |
| Neutro | Sin entusiasmo artificial. Sin "¡Excelente elección!" ni emojis en mensajes de sistema. | "Carta agregada a wishlist" no "¡Añadida a tu colección de favoritos! 🎉" |
Condición de carta — código de color y texto
El estándar de condición es el más crítico del sistema. Un error aquí genera disputas entre compradores y vendedores. La condición siempre se muestra como badge visual + texto corto estandarizado. Nunca se usa solo el color sin la abreviación.
Textos de interfaz — estándares
"Publicar listing" · "Confirmar compra" · "Ver oferta" · "Agregar a wishlist" · "Editar" · "Pausar"
"Click aquí" · "Enviar" · "OK" · "Continuar" · "Siguiente" (sin contexto) · "Procesar"
"Precio mínimo: $500 CLP" · "El nombre no puede estar vacío" · "Foto demasiado pequeña: mínimo 400×560px"
"Valor inválido" · "Error en el campo" · "Algo salió mal, intenta de nuevo" · "Error 422"
Numeración y moneda
| Dato | Formato correcto | Formato incorrecto |
|---|---|---|
| Precio CLP | $48.500 CLP | $48500 / CLP 48.500 / 48.500 pesos |
| Precio USD | $52.40 USD | USD 52.4 / US$52.40 / 52,40 USD |
| Porcentaje | ↑ 12.4% | +12.4% / 12,4% |
| Número de carta | 182/165 | 182 de 165 / #182 |
| Grado PSA | PSA 10 / CGC 9.5 | PSA-10 / 10 PSA / Grado 10 |
| Fecha de orden | Hace 2 horas · Hace 3 días | 2026-04-15 14:32 / 15/04/2026 |
El badge de condición siempre visible en la card de resultado sin hover. Es información crítica de compra: el usuario decide si hace clic o no según la condición visible. Siempre badge + abreviación, nunca solo color.
No inventar condiciones fuera del estándar internacional (M, NM, LP, MP, HP, D). Si el vendedor quiere aclarar algo específico sobre el estado, lo hace en el campo de descripción libre del listing.
Los textos de error deben describir la causa y, cuando sea posible, la solución. "Foto demasiado pequeña: mínimo 400×560px" es más útil que "Archivo no válido". El usuario actúa; la UI explica.
No usar tono entusiasta en mensajes de sistema: "¡Excelente!" "¡Listo!" "¡Genial!". Mulligan es una plataforma de transacciones de valor: la confianza se construye con precisión, no con exclamaciones.
Changelog
Historial de versiones del sistema de diseño.