Saltar al contenido
Paco Segovia
Reservar 20 min

Sistema de diseño

El sistema de diseño de esta web

Tokens, componentes y reglas de pacosegovia.es. Todo lo que ves aquí está renderizado con los mismos componentes y la misma hoja de estilos que la portada: si cambia un token, cambia esta página.

Stack: Astro 7, Tailwind 4 y CSS propio. Cómo uso la IA para mantenerlo: /ia. Cómo se edita el contenido: /cms.

Principios

Cuatro reglas antes de diseñar nada

  • Un plano, no una plantilla

    La identidad sale de los planos técnicos: azul de plano, cotas entre número y etiqueta, y un pie que es un cajetín. Cada adorno tiene que poder explicarse con esa idea.

  • Los tokens mandan

    Ocho colores, cinco tamaños de texto y un radio, definidos una vez en global.css. Ningún componente escribe un hex ni un tamaño suelto: si hace falta uno nuevo, primero se crea el token.

  • Claro y oscuro desde el token

    Cada color se declara con light-dark(). El tema del sistema, el claro y el oscuro salen del mismo valor, sin una segunda hoja de estilos ni clases dark: repartidas por los componentes.

  • Movimiento con permiso

    Las animaciones explican algo (qué entra, qué se puede pulsar) y todas se apagan con prefers-reduced-motion. Sin JavaScript, la página se ve completa.

Laboratorio

Cambia los tokens y mira qué pasa

Acento, radio, tamaño de letra y espacio, en vivo. Los cambios solo afectan a la vista previa y te dan el CSS que habría que tocar en la web. Si el acento no llega al contraste mínimo, el laboratorio lo avisa.

Tema de la vista previa
Acento

Elige uno o escribe su código. Se usa el mismo en claro y en oscuro.

Shopify y Shopify PlusVista previa

Tu tienda, con otros tokens.

Mueve los controles: los componentes son los mismos de la web y solo leen variables.

01Servicios

Una sección de ejemplo

Tarjeta de servicio

Texto secundario en muted sobre card.

Pedir presupuesto
Enviar

Color

Ocho tokens, dos temas

Cada token tiene un valor para claro y otro para oscuro. El azul de acento es el de los planos, elegido para alejarse del verde de Shopify. El verde de «Disponible» es la única excepción y vive dentro de su componente.

  • --bg

    claro
    #f7f7f5
    oscuro
    #121314
  • --ink

    claro
    #141518
    oscuro
    #ececea
  • --muted

    claro
    #5c5f66
    oscuro
    #9a9da3
  • --rule

    claro
    #dcdcd8
    oscuro
    #2c2e32
  • --accent

    claro
    #2346d1
    oscuro
    #8aa4ff

    Azul de plano: guiño a la arquitectura técnica, lejos del verde de Shopify.

  • --select

    claro
    #dce3fb
    oscuro
    #1b2452
  • --tint

    claro
    #efefec
    oscuro
    #18191b
  • --card

    claro
    #fcfcfb
    oscuro
    #1c1d20

Contraste, calculado al compilar

Ratios WCAG 2.2 de los pares que usa la web. AA pide 4,5:1 para texto normal y 3:1 para texto grande.

Contraste de los pares de color en tema claro y oscuro
Texto / fondoUsoClaroOscuro
ink / bgTexto principal17,02:1 · AAA15,72:1 · AAA
ink / cardTexto en tarjetas17,78:1 · AAA14,25:1 · AAA
muted / bgTexto secundario5,96:1 · AA6,84:1 · AA
muted / cardSecundario en tarjetas6,23:1 · AA6,20:1 · AA
muted / tintSecundario en bloques tintados5,55:1 · AA6,47:1 · AA
accent / bgNúmeros, etiquetas y enlaces6,83:1 · AA7,83:1 · AAA
accent / tintAcento en bloques tintados6,36:1 · AA7,40:1 · AAA
bg / inkBotón principal17,02:1 · AAA15,72:1 · AAA

css · src/styles/global.css

:root {
  color-scheme: light dark;
  --accent: light-dark(#2346d1, #8aa4ff);
}

/* Tailwind genera bg-accent, text-accent, border-accent… */
@theme inline {
  --color-accent: var(--accent);
}

Tipografía

Dos familias y una escala fluida

Texto · --font-sans

Inter

Variable, con cv11 y ss01 activados para una «a» y un «1» más limpios.

Titulares · --font-display

Hanken Grotesk

Variable. Titulares y números, con tracking negativo en los tamaños grandes.

Cajetín · solo en el pie

Share Tech Mono

Rotulación de plantilla, en mayúsculas, para los datos del pie.

  1. text-fine

    Etiquetas, metadatos, pie y navegación

    clamp(0.8rem, 0.78rem + 0.1vw, 0.86rem)

    Tu tienda Shopify, rápida y fácil de cambiar.

  2. text-body

    Texto corrido

    clamp(0.94rem, 0.9rem + 0.2vw, 1.06rem)

    lh 1.6

    Tu tienda Shopify, rápida y fácil de cambiar.

  3. text-subhead

    Títulos de tarjeta

    clamp(1.15rem, 1.07rem + 0.4vw, 1.4rem)

    lh 1.25 · peso 600 · tracking -0.015em

    Tu tienda Shopify, rápida y fácil de cambiar.

  4. text-display

    Títulos de sección

    clamp(1.6rem, 1.3rem + 1.4vw, 2.4rem)

    lh 1.1 · peso 600 · tracking -0.015em

    Tu tienda Shopify, rápida y fácil de cambiar.

  5. text-hero

    Titular de portada

    clamp(2.2rem, 1.5rem + 3.2vw, 4.1rem)

    lh 1.02 · peso 600 · tracking -0.025em

    Tu tienda Shopify, rápida y fácil de cambiar.

  6. text-small

    Texto de tarjetas y botones (relativo)

    0.94em

    lh 1.5

    Tu tienda Shopify, rápida y fácil de cambiar.

  7. text-lead

    Entradilla de portada (relativo)

    1.08em

    Tu tienda Shopify, rápida y fácil de cambiar.

Los tamaños son fluidos entre 320 y 1240 px con clamp(): no hay saltos por breakpoint. Las versalitas usan tres tracking: tracking-caps-tight (0.08em), tracking-caps (0.14em), tracking-caps-wide (0.18em).

Espacio y retícula

Un ritmo vertical y dos anchos

--space

clamp(2.75rem, 2rem + 3.5vw, 5rem)

Separación entre secciones (py-space). Crece con la pantalla, sin breakpoints.

--container-measure

46rem

Ancho máximo de lectura para texto corrido (max-w-measure).

--container-wide

72rem

Ancho máximo de la página (max-w-wide), con 20 px de margen en móvil y 32 px desde sm.

Rejillas de 1 columna en móvil, 2–4 en escritorio, con gap-4 entre tarjetas. Los bloques tintados salen hasta el borde de la ventana con la clase .tinted sin romper el contenedor.

Componentes

Los componentes, en vivo

Cada ficha muestra el componente real, sus propiedades y cómo se usa. Pasa el ratón, tabula y cambia el tema arriba a la derecha: todos responden igual que en la portada.

Botones

src/styles/classes.ts

Recetas de clases, no componentes: así sirven igual para un <a> que para un <button>. buttonPlan es solo para la acción principal de la portada (uno por página). buttonHalo, para acciones repetidas dentro de tarjetas. Etiquetas con verbo y lo que pasa después: «Reservar 20 min», no «Saber más».

astro

---
import { buttonPlan, buttonPrimary, buttonSecondary, buttonHalo } from '../styles/classes';
---
<a class={buttonPlan} href={cta.href}>Reservar 20 min</a>
<a class={buttonSecondary} href="#casos">Ver casos</a>

Pastillas

src/components/Home.astro

Shopify y Shopify PlusIbi, Alicante · RemotoDisponible para proyectos nuevos

Datos breves junto al titular. La de disponibilidad tiene tres estados (abierta, limitada, ocupada) que se cambian desde el CMS; solo la abierta lleva movimiento.

astro

<span class={chip}>
  <ShopifyBag class="h-3.5 w-auto" />
  Shopify y Shopify Plus
</span>

Section

src/components/Section.astro

Componentes

Esta página está maquetada con él

El número, la cota azul y la etiqueta imitan la referencia de un plano. Todas las secciones de esta página usan el componente real.

Propiedades de Section
PropiedadTipoUso
idstringAncla y base del id del titular
indexnumberNúmero de la cota; se recalcula si se oculta una sección
labelstringEtiqueta en versalitas
titlestringTitular h2
tintedbooleanFondo tintado a sangre para alternar bloques
slot asideslotSi existe, el contenido pasa a dos columnas (2fr / 3fr)

astro

<Section id="servicios" index={1} label="Servicios" title="…" tinted>
  …
  <form slot="aside">…</form>  <!-- con aside, dos columnas -->
</Section>

Tarjetas

src/styles/classes.ts · card

Refuerzo para agencias

Para agencias que necesitan a alguien senior unas semanas.

Para
Agencias
Entrega
Código revisado y documentado
Pedir presupuesto

Paso del proceso

Variante con filo azul y número grande, para secuencias.

Borde de 1 px y fondo --card, sin sombras ni esquinas redondeadas: las tarjetas son láminas. Los datos van al final con mt-auto para que las tarjetas de una fila alineen su pie.

Propiedades de Tarjetas
PropiedadTipoUso
AudienceIcon name'handshake' | 'store' | 'accessibility'Icono de 34 px de cada servicio

astro

<li class={`${card} flex flex-col`}>
  <AudienceIcon name="handshake" />
  <h3 class="font-display text-subhead">…</h3>
</li>

Campos de formulario

src/components/Home.astro

Etiqueta siempre visible encima del campo (nunca solo placeholder), autocomplete correcto y borde azul al enfocar. El formulario real lleva un campo trampa y un token firmado contra el spam, sin captcha.

astro

<label class="grid gap-2">
  <span class="text-fine">Correo</span>
  <input class={field} type="email" name="email" autocomplete="email" required />
</label>

Controles de cabecera

src/components/LangSwitch.astro · ThemeSwitch.astro

controlItem activoEl selector de tema está en la cabecera: pruébalo con el teclado.

El selector de tema muestra el icono activo antes de que cargue el JavaScript (lo decide el CSS con data-theme) para que no parpadee. Su script va inline con hash en la CSP porque tiene que ejecutarse antes de pintar.

Propiedades de Controles de cabecera
PropiedadTipoUso
lang'es' | 'en'Idioma de las etiquetas y del estado actual

Enlaces y vista previa

src/components/Linked.astro · LinkPreview.astro

Enlace de texto: subrayado en azul a media tinta.

Trabajé en la tienda de Sprinter y en la de Brava.

Linked enlaza solo los nombres de tiendas y estudios que aparecen en un texto plano del CMS. Con ratón, al pasar por encima sale una miniatura de la web (LinkPreview); en táctil no, porque el primer toque ya abre el enlace.

astro

<Linked text="Trabajé en la tienda de Sprinter y de Brava." />

Marca y logos de clientes

src/components/Logo.astro · StoreLogo.astro

FC Barcelona

El isotipo son tres secciones del editor de temas de Shopify con «Añadir sección» en la del medio. StoreLogo calcula el ancho de cada logo por área, no por alto, y los invierte en oscuro salvo los que tienen color propio.

Propiedades de Marca y logos de clientes
PropiedadTipoUso
storeStoreDatos de la tienda (src/data/stores.ts o CMS)
areanumberÁrea aproximada en px²: todos los logos pesan lo mismo a la vista
maxHeightnumberTope de alto para los logos compactos
eagerbooleanCarga inmediata si se ve al abrir la página

astro

<Logo class="size-10" />  <!-- tinta en currentColor, azul en --accent -->
<StoreLogo store={store} area={2400} maxHeight={30} eager />

Iconos

@lucide/astro · AudienceIcon.astro

Lucide con trazo fino (1,5–1,75) y Phosphor Light para los servicios. Siempre en currentColor y con aria-hidden: el texto de al lado ya dice lo que significan.

Pie: el cajetín

src/components/Footer.astro

El pie reproduce el cajetín de un plano: escala 1:1, autor, contacto y la fecha de revisión, que es la del último despliegue. El marco y el boceto del logo se dibujan al llegar. Está al final de esta página.

Movimiento

Animaciones con una razón

Animaciones de la web y su comportamiento con menos movimiento
ClaseQué haceTiempoCon menos movimiento
.revealLos bloques suben 10 px y se funden al cargar500 ms · cubic-bezier(0.2, 0.85, 0.2, 1)Visibles desde el inicio
.wordEl titular entra palabra a palabra, con desenfoque520 ms, 36 ms entre palabrasVisibles desde el inicio
.plan-shadowEl botón principal se hunde en su sombra de plano160 ms; 60 ms al pulsarSin transición
.cta-haloEl borde se ilumina alrededor del cursor300 ms de entradaSin transición
.nav-linkLa línea entra por el lado del cursor y sale por el contrario320 msSin transición
.chip-liveUn brillo verde recorre el borde de «Disponible»5 s, en bucleBorde teñido y quieto
.pulseUna onda sale del punto de disponibilidad2,6 s, en bucleSin animación
.cajetinEl marco y el boceto del pie se dibujan al llegar1,8 s el marco, luego cada trazoDibujados desde el inicio

Una sola curva de salida, cubic-bezier(0.2, 0.85, 0.2, 1), para casi todo. La entradilla de la portada no se anima porque es el LCP: Chrome no lo registra si se pinta primero con opacidad 0.

Accesibilidad

WCAG 2.2 AA como mínimo

Contraste
Todos los pares de texto pasan AA en los dos temas; la tabla de color se calcula al compilar con los valores reales.
Foco visible
Contorno de 2 px en el azul de acento con :focus-visible, separado 3 px del elemento. Nunca se quita sin sustituirlo.
Teclado
Enlace «Saltar al contenido» al principio. El menú de tema sigue el patrón menu con menuitemradio: flechas, Inicio, Fin y Escape.
Áreas táctiles
Mínimo de 24 px (WCAG 2.2, 2.5.8). La skill ui-qa lo mide en cinco anchos de pantalla con toques reales.
Movimiento
Todo respeta prefers-reduced-motion; el contenido nunca depende de que termine una animación.
Semántica
Cada sección tiene su encabezado enlazado con aria-labelledby; los iconos decorativos llevan aria-hidden y los logos, su nombre en texto oculto.
Sin JavaScript
La página se lee entera sin JS. Lo que lo necesita (la barra del CTA en móvil, la vista previa de enlaces) no aparece si falta.

Cómo se mantiene

Una sola fuente para la web y su documentación

  1. global.css

    Los tokens viven en :root y @theme inline. De ahí salen las utilidades de Tailwind.

  2. classes.ts

    Recetas de clases para botones, tarjetas y controles, para no copiar cadenas largas.

  3. src/components

    Componentes Astro sin JavaScript en el cliente salvo donde hace falta.

  4. /ds

    Esta página lee los tokens de global.css al compilar e importa los componentes reales.

Por qué no Storybook

Storybook no tiene un renderizador oficial para componentes Astro, su interfaz necesita scripts que la CSP de esta web bloquea y sería una segunda aplicación que mantener. Una guía viva dentro del propio sitio enseña lo mismo, no puede desfasarse y se publica con cada despliegue. En un tema de Shopify o un proyecto con React, Storybook sí suele ser la herramienta adecuada.

Antes de publicar un cambio visual

Cada cambio pasa por una rama, una comprobación de tipos y la skill ui-qa, que abre la web en Chrome y la pulsa de verdad en cinco tamaños, en claro y en oscuro. La CSP no admite estilos ni scripts externos: cualquier recurso nuevo se revisa antes.