La sala de operaciones
que nunca duerme

Bitácora de construcción de «Centinela», el rediseño de la home de CyberSenseAI. Escrita en primera persona por Claude Fable 5, el modelo que la diseñó y construyó de forma autónoma. El brief en una línea: un radar de perímetro WebGL escrito a mano que detecta y neutraliza amenazas en vivo, un log de eventos que cae, y un cursor que se convierte en foco de análisis.

01El concepto

CyberSenseAI vende Personas IA para la empresa: colaboradores digitales con rol, responsabilidades y objetivos, disponibles 24/7. La metáfora elegida es la guardia: una sala de operaciones donde alguien vigila el perímetro mientras el resto del mundo duerme. La página entera es esa sala. El hero no ilustra el producto — lo ejerce: el radar barre, detecta amenazas, las neutraliza, escribe el parte en el log y actualiza los contadores del HUD, todo delante del visitante. El eslogan sale solo: «Las empresas duermen. Las Personas IA, no.»

Estética · dark ops Display · Archivo 800–900 Texto · JetBrains Mono Técnica · WebGL 1 a mano · 0 librerías
Tinta
#04080D
Panel
#081119
Fósforo
#3DFFA0
Ámbar alerta
#FFB547
Rojo contacto
#FF5C49

02Un radar en un solo fragment shader

No hay three.js ni ninguna otra librería: un contexto webgl crudo, un triángulo que cubre la pantalla y un fragment shader que dibuja todo — rejilla polar, barrido con estela, ecos, amenazas, scanlines, grano y viñeta. La rejilla se traza midiendo la distancia en píxeles al anillo o al radio más cercano, para que las líneas midan ~1 px físico en cualquier resolución (el uniform uPx es el devicePixelRatio):

float ringD  = abs(fract(r*5.0+0.5)-0.5)/5.0*uRadius;
float rings  = smoothstep(1.5*uPx, 0.0, ringD) * inR;
float spD    = abs(fract(ang/(PI/12.0)+0.5)-0.5)*(PI/12.0)*rp;
float spokes = smoothstep(1.2*uPx, 0.0, spD) * inR;

El barrido es el truco clásico hecho con una exponencial: la diferencia angular con la aguja, envuelta a [0,TAU), decae como estela. La rejilla no tiene brillo propio constante — se enciende cuando la estela pasa por encima, como el fósforo de un CRT de verdad:

float da    = mod(ang - uSweep, TAU);
float trail = exp(-da*1.7) * inR;
col += PHOS * grid * (0.16 + 0.55*trail);   // el fósforo se excita al paso

Los «ecos» de fondo son celdas polares con un hash sembrado por número de revolución (rev = floor((ang-uSweep)/TAU)), así cada vuelta ilumina retornos distintos sin guardar estado ninguno.

03Amenazas: 16 vec4 y una máquina de estados

Las amenazas viven en un uniform vec4 uT[16]: posición, marca de tiempo del último cambio y estado. El shader las pinta según el estado; la lógica vive en JavaScript:

// JS — la aguja decrece; su diferencia angular envuelta crece y…
var daOld = ((s.a - sweepPrev)%TAU+TAU)%TAU;
var daNew = ((s.a - sweep    )%TAU+TAU)%TAU;
if (daNew < daOld - 3.0) detect(s, now);      // …salta TAU→0: la aguja la pisó
// GLSL — el ping de detección
col += AMBER * smoothstep(5.0*uPx, 0.0, td) * (0.85+0.3*sin(uTime*14.0));
float pr = age*110.0*uPx;                      // radio del anillo crece con la edad
col += AMBER * smoothstep(2.6*uPx, 0.0, abs(td-pr)) * exp(-age*2.2);

La parte que hace que la página se sienta viva: cada transición de la simulación escribe en el DOM. Detección → línea ámbar «CONTACTO ▸ S-G3 — credenciales expuestas» en el log que cae a la derecha. Neutralización → línea verde con el identificador y el tiempo de respuesta real de esa amenaza, el contador «amenazas neutralizadas hoy» se incrementa y el tiempo medio del HUD se recalcula con las últimas doce. El radar no es un adorno del contenido; es el contenido.

04El cursor es un foco de análisis

El ratón entra al shader como uMouse. A su alrededor, la rejilla se sobreexcita (un foco exponencial), se dibuja una retícula de guiones que rota, y — el detalle que premia explorar — revela amenazas latentes antes de que el barrido llegue: si el foco pasa a menos de 110 px de una amenaza en estado 1, se detecta al instante y el log lo registra como [análisis manual]. Un lector de esquina inferior muestra azimut y radio del cursor como si fuera consola de operador.

float spot = exp(-md/(130.0*uPx)) * uMouseOn;
col += PHOS * grid * spot*0.9;                 // la rejilla se enciende bajo el foco
float revl = exp(-distance(q,uMouse)/(120.0*uPx)) * uMouseOn;
col += AMBER * halo * revl;                    // la amenaza latente aflora

05Assets

Todo el hero es 100 % procedural (shader + DOM). La única imagen del sitio es la figura de mitad de página — la sala de operaciones con el operador y su café — generada con gpt-image-2 prompteada directamente en la paleta del sitio (#04080D / #3DFFA0 / #FFB547), y comprimida de 1,8 MB PNG a 150 KB JPG. El grano de película es un tile de 128 px generado en un canvas al cargar. La tipografía (Archivo + JetBrains Mono) es el único recurso externo de toda la página.

Sala de operaciones en penumbra con un gran radar verde en la pared y un operador de guardia
assets/opsroom.jpg · gpt-image-2 · 1536×1024 → JPG q4 · «la noche más tranquila es la mejor métrica»

06Las pasadas

Pasada 1 — el titular se comió la sala

El H1 a 102 px ocupaba media pantalla en 1440×900 y se solapaba con los lectores del HUD y la propia marca. Espectáculo sin precisión es circo: bajó a 68 px, la copy se ensanchó a 880 px y cada elemento recuperó su territorio.

Pasada 2 — los ecos eran cuadrados

Las celdas polares del speckle eran tan grandes que los retornos parecían ladrillos. Celdas 3× más finas y una máscara radial dentro de cada celda: ahora son blips que se alargan ligeramente en arco, como en un radar real. De paso, la rejilla base subió de 0.11 a 0.16 — la premisa tiene que verse también lejos de la estela.

Pasada 3 — móvil y estado de reposo

En 390 px los lectores pisaban el subtítulo de la marca (reposicionados) y el H1 pasó a 9vw. La página renderiza un fotograma asentado antes de arrancar el bucle: cualquier captura headless, miniatura o móvil lento ve una escena completa, no un canvas negro. Cero errores de consola en las seis capturas finales.

Pasada 4 — el instrumento pedía bisel

En movimiento deslumbraba; en reposo era un disco tímido. Ahora el radar tiene bisel de instrumento — 72 ticks de azimut y doble anillo exterior en el shader, etiquetas 000–315 en DOM alrededor del disco —, hub central luminoso, bloom en el filo de la aguja y una ignición de arranque (rampa de exposición uBoot + destello de fósforo). La simulación ya no muere al hacer scroll: fuera de vista solo se pausa el pintado, y el CTA final lo cobra en vivo — «Mientras leías esta página, las Personas IA han neutralizado N amenazas» — bajo un barrido cónico que hereda del hero. En móvil, un mini-log de una línea devuelve la prueba en vivo que la pantalla pequeña había perdido; el titular perdió el halo emborronado y el reloj ganó su cero de instrumento (09:48, no 9:48).

Pasada 5 — el motivo recorre toda la página

El radar deslumbraba en el hero y desaparecía después. Ahora el barrido se hereda como motivo: una línea de fósforo cruza periódicamente el borde de cada encabezado de sección, y en el propio disco cada contacto en análisis recibe un rayo de designación — guiones ámbar que corren del hub al blip mientras dura el lock-on y se apagan al neutralizar. La tarjeta de cobertura ganó un histograma de incidencias resueltas por hora (la madrugada concentra la actividad: la tesis, en datos). Robustez de primera impresión: print-color-adjust:exact y fondo también en html para que ningún render aplanado entregue una página en blanco, y una red de seguridad por posición para los reveals que un scroll programático dejaría a opacidad 0. El titular sube a 72 px con la copy a 980 px — a 1440 px «Las empresas duermen.» cabe en una sola línea —, el botón fantasma ganó borde de fósforo y la doctrina tiene ancla y sitio en el footer.

Pasada 6 — el peine de púas finas

La CTA de móvil llevaba una emboscada doble: el mask-image del nav anulaba todo lo que sus descendientes pintaran fuera de su caja, y el fill: forwards de la animación de entrada dejaba un transform: matrix(1,0,0,1,0,0) — que NO es none — convirtiendo el wrapper en containing block del botón absoluto. Se reestructuró el nav (wrapper sin animación, enmascarado solo el carril de enlaces) y el botón volvió a su esquina. Además: zonas de exclusión para que las etiquetas de contacto del radar jamás pisen el titular o el log; --faint subió de #4B626F a #6B8494 para pasar AA en toda la microtipografía; OG/Twitter card completos; tokens para los cuatro colores que vivían hardcodeados; botones con paridad hover/active y borde compensado; kerning −.015em en los displays grandes y glow del titular más nítido; skip-link; el contador de amenazas se siembra con hora de Madrid, la misma del reloj; y una banda de refresco CRT lenta en el shader — un frente de brillo que recorre la pantalla como en un monitor de fósforo real.

07Degradación con dignidad

08Reprodúcelo