Renderer (gioser-canvas-web): - Spring shake (SpringDamper1, 7.5 Hz / ζ=0.13) aplicado como rotación Z en el MVP. impulse_click() inyecta velocidad alternada → vibración fuerte con ~5 ciclos decayendo en ~0.8s. - release_tilt() pone target del tilt en (0,0) → la chacana cae al frente con el rebote natural del spring sub-crítico. - world_scale_for_aspect(): en portrait (aspect<1) escala baja proporcional para que el aro exterior no se corte por los lados. Base 1.05, piso 0.45. - click_radius_css_px() expone radio del aro en CSS-pixels desde el centro del canvas; la app lo usa para hit-test del impulso. - set_client_size() separa CSS-pixels de device-pixels (DPR). - tilt_degrees() ahora retorna (pitch, yaw, roll) — el brand replica los 3. - 4 nuevos uniforms u_aire/fuego/tierra/agua_color para el shader de partículas. Shader (gioser-shaders/FS_CHACANA): - Función element_particles(tip, outward, color, kind) → 4 partículas por cardinal con personalidad: AIRE drift+sway, FUEGO rise+flicker (siempre hacia +Y), TIERRA cae, AGUA ondula descendiendo. Gauss + envelope sinusoidal en la vida. ~16 partículas total, costo modesto. App (gioser-web): - pointerdown en canvas → si distancia al centro < click_radius_css_px → impulse_click(). Touch y mouse vienen unificados por PointerEvent. - mouseleave en canvas → release_tilt(). Sin set_target, el spring se quedaría en la última posición — ahora vuelve al frente con rebote. - position_tips ahora clampea raw_x/raw_y a [margin, viewport - taskbar - margin] en CSS pixels. Los botones NUNCA salen del canvas ni cubren la taskbar incluso en aspect extremos o tilt máximo. - AppState + TaskbarState (RefCell): trackea drawers abiertos + activo. open_tab/switch_tab/close_tab/home aplican mutación + sync(). - sync() rebuild de taskbar-list innerHTML por cada cambio de estado, más swap de body classes + drawer .open classes. - Click delegation en taskbar-list — un listener para todas las cajitas. - Botón home con data-home en la barra (svg de casa) cierra todo y limpia el taskbar. - Escape también cierra el drawer activo. - update_tilt_css ahora setea --tilt-z también — brand title roll visible en el shake. CSS: - .drawer bottom: 52px (reserva taskbar). - .taskbar full ancho fixed bottom, glass + gold border, scrollable horiz para muchas cajitas. - .taskbar-item con --task-color por elemento (aire/fuego/tierra/agua), .active glow del color + inset border bottom. - .taskbar-home con svg de casa dorado, hover glow. - Responsive: taskbar 46px en mobile + ajustes. - .brand transform agrega rotateZ(--tilt-z) para que el título vibre con la chacana en click impulses. Workspace verde + 18 tests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gioser-web
Landing page de GioSer · En el centro, el ser: chacana animada con shaders WebGL2, nebulosa procedural y cuatro botones cardinales (AIRE · FUEGO · TIERRA · AGUA).
Arquitectura
crates/modules/gioser/
├── gioser-geom/ geometría agnóstica de la chacana (20 vértices)
├── gioser-physics/ resorte-amortiguador N-dim crítico-amortiguado
├── gioser-palette/ 4 elementos + cosmos en RGB lineal
├── gioser-shaders/ sources GLSL ES 3.00 (FBM cósmico + SDF chacana)
└── gioser-canvas-web/ renderer WebGL2 que compone todo
crates/apps/gioser-web/ cdylib WASM + index.html + styles.css
Los cuatro primeros son agnósticos del runtime (compilan en cualquier
target). gioser-canvas-web agrega la dependencia de WebGL2 / web-sys.
Cuando exista yahweh-web, los agnósticos siguen tal cual y el renderer
se enchufa al runtime equivalente a yahweh_launcher::launch_app.
Cómo se ve
- Fondo: vacío violeta-noche con tres capas de FBM (5 octavas) en parallax con el mouse + estrellas titilantes + viñeta radial.
- Chacana: SDF de la cruz escalonada con outline gaussiano cyan, glow ámbar exterior, aro circular envolvente y rayos sutiles (calendario andino).
- Sol central: gauss + corona, late con sin(t).
- Tilt físico: spring-damper sub-crítico (ζ=0.72, 2.2 Hz) que apunta hacia el mouse — overshoot suave, settle de ~600 ms.
- Botones: DOM real (accesibles, navegables por teclado, deep-link por hash) proyectados desde 3D al viewport cada frame.
Requisitos
- Rust con el target
wasm32-unknown-unknowninstalado.- Con
rustup:rustup target add wasm32-unknown-unknown. - En Artix/Arch con Rust del sistema: el target suele venir incluido en
/usr/lib/rustlib/wasm32-unknown-unknown(verificá conls /usr/lib/rustlib | grep wasm). Si falta:pacman -S rust-wasm(no existe — el target viene con el paqueterustbase).
- Con
- wasm-bindgen-cli (versión exacta 0.2.121, debe matchear la dep del
Cargo.lock). Verificá congrep -A1 '^name = "wasm-bindgen"$' Cargo.lock | headantes de instalar.
# Una sola vez:
cargo install wasm-bindgen-cli --version 0.2.121 --locked
La versión del CLI debe coincidir con la del crate
wasm-bindgenenCargo.lock. Si no coincide, el output JS no carga el.wasmgenerado. Si actualizás el workspace y wasm-bindgen sube de versión, reinstalá el CLI con la nueva versión.
- Un static server para probar local:
python3 -m http.serveralcanza.
Flujo rápido (un comando)
Hay un wrapper que hace cargo build + wasm-bindgen + copia salida:
# Dev — sin optimización, build rápido (~10 s).
./scripts/build-gioser-web.sh dev
# Release — opt-level=3, lto, strip, ~30 s pero binario pequeño.
./scripts/build-gioser-web.sh release
El output queda en crates/apps/gioser-web/pkg/:
pkg/
├── gioser_web.js ← bindings JS (referenciados por index.html)
├── gioser_web_bg.wasm ← binario WASM
└── gioser_web.d.ts ← typings (no se usan en runtime, son para IDE)
Probarlo local
./scripts/build-gioser-web.sh dev
python3 -m http.server -d crates/apps/gioser-web 8080
# Abrir http://localhost:8080/
Si cambiás Rust: re-ejecutar el script y refrescar el browser.
Si cambiás index.html / styles.css: alcanza con refrescar.
Build release y deploy
./scripts/build-gioser-web.sh release
El binario release optimiza:
opt-level = 3,lto = "thin",codegen-units = 1,panic = "abort",strip = "symbols"(del perfil[profile.release]del workspace).- WASM resultante típico: ~120 KB (canvas + shaders + bindings) sin
comprimir, ~50 KB gzippeado. wasm-bindgen pasa por
wasm-optautomáticamente si lo encontrás en$PATH(instalable viabinaryen).
Para deploy, los artefactos a subir al host estático son sólo cuatro archivos:
crates/apps/gioser-web/
├── index.html ← entry point
├── styles.css ← estilos
└── pkg/
├── gioser_web.js
└── gioser_web_bg.wasm
Funciona en cualquier static host (Nginx, GitHub Pages, S3+CloudFront, Caddy, netlify, fly, Vercel static). Importante:
- El server debe servir
.wasmconContent-Type: application/wasm. Nginx/Caddy lo hacen por default; algunos hosts muy viejos no — fijate. index.htmlreferencia./pkg/gioser_web.jscontype="module", o sea que el browser usa el ES module loader. Eso requiere servir por HTTP/HTTPS (nofile://).- Fonts de Google: el
<link>apunta afonts.googleapis.com. Para uso offline o sin tracking, descargá las fuentes y servilas locales.
Comando deploy "tar + scp"
./scripts/build-gioser-web.sh release
tar czf gioser-web-dist.tar.gz \
-C crates/apps/gioser-web \
index.html styles.css pkg/
# Subir al server:
scp gioser-web-dist.tar.gz user@host:/var/www/gioser/
ssh user@host 'cd /var/www/gioser && tar xzf gioser-web-dist.tar.gz'
GitHub Pages / gitea pages
Configurá la branch de pages a apuntar a un directorio que contenga
sólo los 4 archivos. Un workflow CI que corra el script y commitee el
pkg/ a la branch de deploy hace el trabajo.
Routing
Los <a href="#aire|fuego|tierra|agua"> apuntan a anchors locales.
Cuando definamos rutas reales (otras páginas, sub-apps, etc.), basta con
cambiar el href en index.html o interceptar el click desde JS.
Tests de los crates agnósticos
Los cuatro crates sin gpui/web tienen tests unitarios estándar:
cargo test -p gioser-geom -p gioser-physics -p gioser-palette -p gioser-shaders
gioser-canvas-web no tiene tests (depende de WebGL2 que sólo existe
en browser).
Troubleshooting
Pantalla en blanco + error en consola "no link/binding found"
→ Versión de wasm-bindgen-cli no coincide con la del Cargo.lock.
Reinstalá con cargo install wasm-bindgen-cli --version <X.Y.Z>
donde <X.Y.Z> sale de grep '^version' Cargo.lock cerca de la entrada
wasm-bindgen.
"WebGL2 not supported" en algunos navegadores viejos → No hay fallback. WebGL2 es soporte universal en navegadores modernos (Chrome/Edge/Firefox/Safari desde 2017). Para targets ancestrales habría que escribir un renderer WebGL1, no contemplado por ahora.
Build muy lento por las deps de web-sys → Las features de web-sys están minimizadas en el Cargo.toml; sólo se importan las que el renderer usa. El primer build sí es lento (~1 min), los incrementales son rápidos.