← Volver a la home

Cómo se hizo · ADN

CREI: los valores en el ADN.

Guía de diseño y técnica de la home de CyberSenseAI: una doble hélice 3D escrita a mano en WebGL cuyos pares de bases son los cuatro valores CREI, y que se desenrolla a medida que el visitante hace scroll.

01 / Concepto

Los valores no como texto legal, sino como genoma.

CyberSenseAI afirma que sus Personas IA son Confiables, Responsables, Éticas e Inclusivas. La mayoría de webs enterprise colocan esos valores en una lista de viñetas; aquí son la estructura física de la página: las cuatro bases nitrogenadas de una doble hélice que preside todo el recorrido.

SCROLL = 0 SCROLL = 1

02 / Dirección visual

Laboratorio elegante: azul noche y cuatro luces.

Atmósfera de laboratorio nocturno de secuenciación: fondo azul profundo con viñetas radiales, grano de placa fotográfica (SVG feTurbulence fijo sobre toda la página), platos de vidrio con backdrop-filter y una regla métrica fija a la izquierda que mide el avance en «kilopares de bases».

Azul noche#04070F · fondo
Confiable#57C7FF · cian
Responsable#FFB454 · ámbar
Ética#3DDC97 · esmeralda
Inclusiva#FF6E9C · coral
Tinta#E9EFF9 · texto
Display + textoFraunces variable (opsz 9–144): a tamaño cartel es una serif de alto contraste; a tamaño lectura, una serif científica de libro. Itálicas para los momentos de énfasis genético.
DatosIBM Plex Mono para todo lo que «habla el laboratorio»: navegación, fichas, códigos EXP/…, contadores, regla y footer.
ComposiciónBases CREI en zigzag con letras-espécimen gigantes que se salen de la retícula; ficha lateral en el hero; cifras como bandas de un gel de electroforesis.

03 / Técnica

Una hélice de ~1 270 puntos de luz, sin librerías.

El fondo es un único <canvas> WebGL 1 escrito a mano: matrices propias, dos shaders GLSL y geometría recalculada por frame en CPU (dos hebras de 380 puntos, 38 peldaños de 9 cuentas, 150 motas de polvo — un tercio con tinte CREI — y un cabezal de lectura de 18 puntos que recorre la hebra como una polimerasa, haciendo destellar cada base a su paso; trivial a 60 fps). El desenrollado es un solo parámetro: la tasa de giro k tiende a cero con el scroll. Al cargar, la hélice no aparece «puesta»: las hebras encienden y los peldaños se secuencian por orden durante ~2,5 s, como una lectura real de bases.

// La espiral es una función del scroll: k(p) → 0 la vuelve escalera
function buildFrame(t, p, aspect) {
  var unwind = unwindAt(p);                            // keyframes suavizados 0→0.95
  var k   = (Math.PI * 2 * TURNS / SPAN) * (1 - unwind); // twist por unidad de altura
  var rot = t * 0.42 * (1 - unwind * 0.72) + p * 3.0;    // el giro también se calma
  for (var strand = 0; strand < 2; strand++) {
    var phase = strand * Math.PI;                        // hebras en oposición
    // x = cos(y·k + rot + fase)·R   ·   z = sin(y·k + rot + fase)·R
  }
}
// Vertex shader: proyección propia + tamaño por distancia (con tope de fill-rate)
gl_PointSize = min(a_size * u_dpr * (26.0 / w), 44.0 * u_dpr);

// Fragment shader: halo + núcleo. Alfa CERO: con blend(ONE, ONE) el canvas
// premultiplicado compone como luz emisiva pura sobre el fondo CSS.
float halo = smoothstep(0.5, 0.0, d);
float core = smoothstep(0.16, 0.0, d);
gl_FragColor = vec4(v_col * (halo * 0.62 + core * 1.32) * v_depth, 0.0);

El resaltado por sección es un vector de cuatro pesos interpolado cada frame: un IntersectionObserver sobre cada tarjeta de valor llama a window.__helix.setHighlight(i) y los peldaños de esa base crecen y brillan mientras el resto baja a un 22 %.

// Keyframes del desenrollado, ligados al progreso p de scroll
function unwindAt(p) {
  if (p < 0.12) return 0;                             // hero: espiral íntegra
  if (p < 0.5)  return ease((p - 0.12) / 0.38) * 0.55; // CREI: se abre
  if (p < 0.85) return 0.55 + ease((p - 0.5) / 0.35) * 0.4;
  return 0.95;                                          // cierre: escalera
}

04 / Decisiones

Lo que se decidió y por qué.

Alfa emisivaEl primer render componía las esquinas de cada point-sprite como negro opaco: se escribía alpha 1.0 con blending aditivo sobre un canvas premultiplicado. La corrección — emitir alpha 0.0 — convierte cada punto en luz pura que se suma al fondo CSS. Es el detalle que separa «partículas» de «bioluminiscencia».
CPU, no GPUCon ~1 300 puntos, recalcular la geometría por frame en JavaScript es más simple y flexible que animar en el shader — y deja el desenrollado, la deriva y el resaltado como aritmética legible.
Arranque secuenciadoTras el loader, los peldaños CREI se encienden uno a uno (un factor de revelado por peldaño en el bucle de geometría). El loader además se salta en revisitas de la misma sesión (sessionStorage): la espectacularidad no debe cobrarse dos veces.
Deriva continuaLa primera versión desplazaba la hélice con un módulo (drift % 2.4): un diente de sierra que producía un salto visible al cruzar cada múltiplo. Se sustituyó por una deriva senoidal acotada — continua por construcción, sin pops.
DegradaciónSin WebGL, la misma matemática se proyecta a mano sobre Canvas 2D (mitad de puntos, lighter). Si el contexto WebGL se pierde, se espera el restored; si no llega en 2 s, se reemplaza el canvas por uno 2D (el mismo nodo no admite dos tipos de contexto). Con prefers-reduced-motion, se pinta un único fotograma estático a medio desenrollar y todos los reveals quedan visibles.
MóvilEn pantallas estrechas la hélice se centra tras el texto y su luminosidad global baja al 60 % para no competir con la lectura. Verificado a 1440 px y 390 px sin desbordamiento horizontal.
AssetsCero imágenes: todo es procedural (WebGL, SVG inline para logo y equipo híbrido, grano por feTurbulence). Único recurso externo: Google Fonts.
Titular CREIEl degradado del hero pasó de 3 a 4 paradas — las cuatro bases (cian → esmeralda → ámbar → coral). El tramo esmeralda→coral interpolaba por un beige turbio; insertar el ámbar de la R mantiene la ruta de tono viva y completa la metáfora: el titular contiene la secuencia entera.
Contraste AAEl token --tinta-faint subió de #5A6C8E (≈3.6:1) a #7186AB (≈4.8:1 incluso sobre los platos de vidrio): todo el texto mono pequeño cumple WCAG AA. La ficha lateral del hero ganó además un scrim (fondo translúcido + blur) para leerse sobre las partículas de la hélice.
Afinado (pasada 2)Aurora de fondo que respira (solo opacity/transform, compositor puro); menú móvil con fade + stagger por ítem; hover en la marca del nav; skip-link accesible; metadatos OG/canonical/theme-color completos; mul() con scratch fijo (cero asignaciones por frame); y arreglo del caso reduced-motion + resize, que dejaba el canvas en negro al no repintarse el fotograma estático.
Afinado (pasada 3)Cabezal de polimerasa: un pulso blanco-cian con estela recorre la hebra leyendo la secuencia — la metáfora del «secuenciado» pasa de estática a viva. Rendimiento: resize() dejó de ejecutarse por frame (leía innerWidth/innerHeight y subía el uniform DPR 60 veces/s) y ahora ignora los resizes de solo-altura <130 px que dispara la barra del navegador móvil durante el scroll — sin reasignar el framebuffer en pleno gesto; scrollHeight cacheado y recalculado en resize/load/fonts.ready. Robustez: si fallan WebGL y Canvas 2D el bucle rAF se apaga (y no resucita al volver de una pestaña en segundo plano); prefers-reduced-motion se escucha en vivo con matchMedia.change — parar o arrancar el bucle y revelar todo al instante. Accesibilidad: menú móvil con Escape, foco al primer enlace al abrir, retorno de foco al botón al cerrar y fondo inert; scrim degradado permanente bajo el nav para leerse sobre las partículas. Detalle: filas de criterios apiladas en ≤560 px, indicador de scroll oculto en viewports de poca altura, JSON-LD de Organization y castellano peninsular consistente («ROI medio estimado»).
Lupa finalEco de lectura: el cabezal de polimerasa ahora hace destellar cada peldaño CREI a su paso (falloff cuadrático sobre la posición de la base; aritmética pura, coste cero) — la hélice ya no solo se recorre: se lee. Se eliminó el reveal anidado del CTA final: .fin era .rv con todos sus hijos .rv, así que el escalonado de los hijos ocurría dentro de un padre todavía con blur(7px) — paint caro sobre una tarjeta de ~900 px y la tarjeta llegaba borrosa a cámara; ahora revelan solo los hijos. Scrim radial tras el texto del hero en ≤1060 px (donde la hélice pasa por detrás del párrafo). El resaltado por pointerenter ignora punteros no-ratón (un tap lo dejaba enganchado contra el observer). #menu es un <nav> con aria-label; fallback 100vh previo a 100svh; flex-wrap en los criterios; y JSON-LD de Organization en el <head>.