Caso de estudio · Proyecto

Un sitio estático en Astro, en dos idiomas, donde cada cifra se calcula de un registro tipado y cada página se prueba en un navegador real.

Stack
  • Astro
  • TypeScript
  • Tailwind CSS
  • GSAP
  • Playwright
Demo
Sin demo pública

Sección 1 de 4: Contexto

Mi portafolio, reconstruido en septiembre de 2026 con la gramática de un único diseño de referencia. Se publica como HTML estático con un solo script empaquetado para el movimiento; cada texto visible vive en un catálogo en español y otro en inglés, y cada número se deriva del registro al compilar el sitio.

Sección 2 de 4: Arquitectura

  1. Registro

    src/data guarda los roles, los proyectos y el stack; los años, los conteos y el gráfico de trayectoria se calculan de ahí al compilar.

  2. Rutas

    Las URL localizadas viven como pares en un solo archivo, así que cada página existe en ambos idiomas con hreflang recíproco.

  3. Tokens

    Colores y tipografías salen de tokens semánticos con un valor claro y uno oscuro; una banda oscura los resuelve de nuevo cambiando el esquema de color.

  4. Movimiento

    GSAP y Lenis corren en un solo contexto que se revierte por completo con movimiento reducido o con el control de pausa.

Sección 3 de 4: Decisiones

  1. Sin framework en el navegador

    Salida estática; el único script es el paquete de movimiento, y cada página funciona sin JavaScript.

  2. Una política hecha de hashes

    El build calcula el hash de cada script y estilo en línea y lo suma a la Content Security Policy, así que un bloque editado nunca sale con un hash viejo.

  3. Probado tal como se publica

    Playwright recorre el build de producción con axe en ambos temas y reproduce la política en cada página.

  4. Decisiones por escrito

    El hosting, la librería de movimiento y el scroll suave tienen cada uno un ADR con las opciones comparadas y el costo en kilobytes.

Sección 4 de 4: En el código

brunogalvan.dev / cloudflare-headers.mjs · L6–15
// Cloudflare Pages reads `_headers` from the build output. The Content Security
// Policy is derived from the built HTML: every inline script and style block
// becomes a sha256 source, so the pre-paint theme initializer and the font
// pipeline's @font-face block keep working without 'unsafe-inline', and an
// edited inline block can never ship with a stale hash.
const inlineScript = /<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/g;
const inlineStyle = /<style[^>]*>([\s\S]*?)<\/style>/g;

const sha256 = (text) =>
  `'sha256-${createHash('sha256').update(text).digest('base64')}'`;
Extracto de cloudflare-headers.mjs, líneas 6 a 15