Un portfolio que no es una página: es un instrumento. El DOM cabe en cuatro esquinas (logo, about, metadata, contacto) y todo lo demás es un motor WebGL propio: una tira de película con los proyectos en slices verticales que se arrastra con inercia, se inclina con la velocidad y se expande a pantalla completa al entrar.
Bicolor estricto: negro elevado #141414 y una tinta hueso verdoso #BAC4B8 que baja la temperatura de todo el sitio (10.2:1 AAA, clave cuando el cuerpo de texto real mide 11px). Sin GSAP, sin Three.js: la física del arrastre está escrita a mano en un único bundle, y cada proyecto trae su propio color de acento como inquilino temporal.
Cada case study define su pareja fondo/acento (rosa #FF71CB, verde #53BB89, rojo #F04333, crema #FFF1CE…) y la trae consigo al expandirse. La regla de los inquilinos: nunca dos acentos a la vez en pantalla, nunca un acento como color de UI global. Cuando la tira se repliega, el sitio vuelve a ser bicolor. Pasa el cursor por la banda: la luz es el estado.
El cuerpo de texto real del sitio mide once píxeles con line-height de diez: leyendas de museo, más apretadas que su propio cuerpo. No existe el 16px normal. La jerarquía salta de esto directamente al cartel: un factor de 4.5 a 20 veces sin escalas intermedias.
Todo el sitio va en mayúsculas: no hay una sola palabra en caja baja.
| Rol | Size real | LH | Weight | Tracking |
|---|---|---|---|---|
| título de proyecto (canvas) | ~14vw | 0.85 | 400 | -0.02em |
| logotipo "ARISTIDE" | 50px | 44px (0.88) | 400 | -1px |
| número de índice | 50px | 44px | 400 | -0.5px |
| link de chrome | 12px | 34px | 700 | -0.24px |
| metadata | 11px | 10px (0.91) | 700 | normal |
| cuerpo de lectura | (no existe) | — | — | — |
Cinco tamaños y ningún cuerpo de lectura. Para documentación (como esta página) se deriva un único intermedio: 15px / 1.6 en Archivo 400-500, y nada más.
El chrome completo del sitio: logotipo en titling a 50px (tracking -1px), el navegador de ticks (una marca de 1px por proyecto; la activa crece; es un scrubber, no una lista de links) y los links de 12px con subrayado que se redibuja: sale por un extremo y entra por el otro con cubic-bezier(0.25, 0.46, 0.45, 0.94). Ningún componente tiene fondo, borde o radio.
La slice de película: columna vertical a canto vivo, brightness .35 en reposo. El hover la ilumina al 100%, la ensancha y revela el título en la titling. En el sitio real este componente vive en WebGL con física de arrastre; el click la expande a pantalla completa y el case study hereda el lienzo.
El único momento claro del dominio: el hueso #BAC4B8 pasa de tinta a superficie y el texto baja a negro puro (11.7:1 AAA). No es un tema claro: es el negativo fotográfico exacto del sitio.
Metadata de esquina: bloques de 11px / lh 10px anclados al frame del viewport, como los créditos de un cartel de cine. El panel about cubre la pantalla con canto recto: sin arco, sin fade, un corte de negativo.
La tira sigue al puntero con lag (lerp ~0.1) y al soltar continúa con decaimiento (~0.92 por frame). Cada slice se inclina proporcionalmente a la velocidad instantánea y vuelve a la vertical al frenar: el sello del motor.
Las imágenes viven al ~35% de brillo y despiertan al 100% bajo el cursor, en 400-600ms con easeOutQuad. Donde otros sistemas elevan con sombra, este ilumina: brillo pleno significa "tocable".
Una marca vertical de 1px por proyecto, arriba al centro. Recorrer la fila desplaza la tira proporcionalmente con la misma inercia. No es un menú: es la línea de tiempo de la película.
Entrar a un proyecto no es cambiar de página: la slice crece hasta cubrir el viewport (~900ms) y el case study hereda el lienzo con su acento. El about, en cambio, entra como negativo: un panel hueso con canto recto.
| Token | Valor | Uso |
|---|---|---|
| easeOutQuad (real, CSS) | cubic-bezier(0.25, 0.46, 0.45, 0.94) | paneles, subrayados redibujados, hovers |
| easeOutSine (real, CSS) | cubic-bezier(0.39, 0.575, 0.565, 1) | fades de metadata y chrome |
| lerp del drag (derivado) | ~0.08-0.12 por frame | seguimiento del puntero en el motor WebGL |
| fricción (derivado) | ~0.92 por frame | decaimiento de la inercia al soltar |
| iluminación | 400-600ms | brightness .35 → 1.0 en hover |
| expansión | ~900ms | slice → case study a pantalla completa |
| intro | ~1800ms stagger | la tira se construye slice a slice |
Stack real: ninguno. Sin GSAP, sin Three.js, sin Lenis, sin Locomotive (verificado en runtime: ningún global expuesto). Todo el motor (render WebGL, física, router, precarga) vive en un único bundle propio, /static/js/d.js. Los dos cubic-bezier del CSS son lo único declarativo; el resto es requestAnimationFrame. Con prefers-reduced-motion: tira desplegada y a brillo .6, navegación por teclado sin skew ni inercia, expansión en corte directo.
Sin max-width, sin contenedor de lectura, sin scroll vertical en la home: el plano es fijo (100vw × 100vh), el eje de exploración es horizontal (drag) y la profundidad es el zoom. El vacío central no es espacio muerto: es el escenario de la física.
:root {
/* sistema bicolor */
--color-background: #141414; /* negro elevado, nunca #000 de fondo */
--color-ink: #BAC4B8; /* hueso verdoso: LA tinta (10.2:1 AAA) */
--color-panel: #1E1E1E;
--color-inverse-bg: #BAC4B8; /* el about: negativo exacto */
--color-inverse-text: #000000;
--color-gold: #CC9933; /* único acento editorial propio */
/* acentos POR PROYECTO: uno a la vez, solo con su proyecto activo */
--project-pink: #FF71CB;
--project-rose: #D99299;
--project-plum: #321D44;
--project-umber: #734130;
--project-navy: #1E1F28;
--project-sky: #D2DCF2;
--project-green: #53BB89;
--project-sand: #E0C8A4;
--project-bone: #D4C3A5;
--project-red: #F04333;
--project-blush: #FEA8B1;
--project-cream: #FFF1CE;
--font-display: "Anton"; /* sustituta de TIMMONS NY 2.0 */
--font-body: "Archivo"; /* sustituta de JWStudio Sans Bold */
/* formas: no hay */
--radius: 0;
/* sombras: no hay */
/* easings reales del chrome DOM */
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-out-sine: cubic-bezier(0.39, 0.575, 0.565, 1);
/* jerarquía por luz */
--brightness-idle: 0.35;
--brightness-active: 1.0;
--duration-light: 500ms;
--frame: clamp(40px, 3.5vw, 56px);
--strip-height: 31vh;
}@theme {
--color-canvas: #141414;
--color-ink: #BAC4B8;
--color-panel: #1E1E1E;
--color-gold: #CC9933;
--color-inverse: #BAC4B8;
--color-inverse-ink: #000000;
--color-project-pink: #FF71CB;
--color-project-green: #53BB89;
--color-project-red: #F04333;
--color-project-sky: #D2DCF2;
--color-project-cream: #FFF1CE;
--color-project-plum: #321D44;
--font-display: "Anton", "TIMMONS NY 2.0", "Arial Narrow", Impact, sans-serif;
--font-body: "Archivo", "JWStudio Sans", Helvetica, Arial, sans-serif;
--radius-none: 0px;
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-out-sine: cubic-bezier(0.39, 0.575, 0.565, 1);
--spacing-frame: clamp(40px, 3.5vw, 56px);
--spacing-strip: 31vh;
}
/* Reglas de uso:
- todo uppercase; display lh 0.85-0.88; micro-etiquetas 11-12px
- cero border-radius, cero box-shadow
- jerarquía por luz: brightness .35 en reposo, 1.0 en hover */{
"colors": {
"canvas": "#141414",
"ink": "#BAC4B8",
"panel": "#1E1E1E",
"gold": "#CC9933",
"inverse": { "bg": "#BAC4B8", "text": "#000000" },
"projects": {
"pink": "#FF71CB",
"rose": "#D99299",
"plum": "#321D44",
"umber": "#734130",
"navy": "#1E1F28",
"sky": "#D2DCF2",
"green": "#53BB89",
"sand": "#E0C8A4",
"bone": "#D4C3A5",
"red": "#F04333",
"blush": "#FEA8B1",
"cream": "#FFF1CE"
},
"rule": "bicolor system; project accents are tenants, one at a time"
},
"typography": {
"display": {
"original": "TIMMONS NY 2.0 (Matt Willey)",
"substitute": "Anton",
"logo": { "fontSize": "50px", "lineHeight": "44px", "letterSpacing": "-1px" },
"title": { "fontSize": "clamp(80px, 14vw, 220px)", "lineHeight": "0.85", "letterSpacing": "-0.02em" },
"case": "uppercase"
},
"micro": {
"original": "JWStudio Sans Bold (Jon Way Studio)",
"substitute": "Archivo 600/700",
"meta": { "fontSize": "11px", "lineHeight": "10px", "fontWeight": 700 },
"link": { "fontSize": "12px", "lineHeight": "34px", "letterSpacing": "-0.24px", "fontWeight": 700 },
"case": "uppercase"
},
"rule": "no reading size exists; derived doc body = 15-16px/1.6 Archivo 400"
},
"rounded": { "all": "0px" },
"shadow": { "all": "none" },
"motion": {
"engine": "custom WebGL bundle (/static/js/d.js), no GSAP/Three/Lenis",
"ease-out-quad": "cubic-bezier(0.25, 0.46, 0.45, 0.94)",
"ease-out-sine": "cubic-bezier(0.39, 0.575, 0.565, 1)",
"physics": { "lerp": "0.08-0.12", "friction": "0.92/frame" },
"durations": { "light": "400-600ms", "expand": "~900ms", "intro": "~1800ms" },
"brightness": { "idle": 0.35, "active": 1.0 }
},
"layout": {
"frame": "clamp(40px, 3.5vw, 56px)",
"strip-height": "31vh",
"strip-gap": "~1vw",
"micro-gap": "8px",
"rule": "perimeter frame; DOM in corners; canvas owns the center"
}
}