tawasuyu

tawasuyu/brahman

Rust 362TOML 137Markdown 40JSON 174970 Sutichaykuna

feat(gioser): ciclo sol↔luna↔tierra, auras anchas, nuevo set Software/Quién Soy/Manifiesto/Mística

Shader (gioser-shaders):
- 3 cuerpos centrales renderizados realísticamente con interpolación
  gradual entre ellos (cross-fade smoothstep):
  - render_sun: núcleo gauss + corona pulsante + textura de plasma FBM
    (boiling surface).
  - render_moon: disco con limb darkening, cráteres + mares (2 octavas
    de fbm), terminador móvil (fase lunar), halo azulado en el limb
    iluminado.
  - render_earth: disco con continentes fbm (rotación lenta), polos
    blancos, nubes en otra capa, día/noche en hemisferio iluminado,
    halo atmosférico azul (Rayleigh simplificado).
- Uniforms u_body_a, u_body_b (int 0/1/2), u_body_blend (float).
- Cuerpo central se calcula sólo si inside > 0.001 (perf — saltea pixels
  fuera de la superficie de la chacana).
- radial_mult atenúa los rayos cuando luna/tierra están activos — el sol
  es el único que irradia tan intensamente.

- element_cloud(): aura ancha por cardinal (sigma_along=0.42,
  sigma_perp=0.34) con textura fbm animada y modulación por elemento.
  - AIRE: corrientes suaves que ondulan horizontalmente.
  - FUEGO: lengüetazos rápidos con flicker.
  - TIERRA: densidad sólida con variación lenta.
  - AGUA: ondulaciones grandes que viajan hacia afuera.
  Las nubes cubren todo el cuadrante del cardinal, no solo la punta.

- Helper functions vnoise_c + fbm_c agregadas (necesarias para superficies
  realistas de luna/tierra y para nubes elementales).

Renderer (gioser-canvas-web):
- body_state(t) -> (body_a, body_b, blend) state machine:
  - BODY_PHASE_SECS = 45 (≈10 pulsos del sol antes de transicionar).
  - BODY_TRANSITION_SECS = 4 (cross-fade gradual).
  - Total cycle: 147s = sol 45s → trans 4s → luna 45s → trans 4s → tierra 45s → trans 4s.
  - Smoothstep cubic en el blend para curva natural (no linear).
- Sube u_body_a/b como int (uniform1i) y u_body_blend como float.

App + contenido:
- index.html: nuevos labels en los 4 tips
  - NORTE (aire):  SOFTWARE / Tecnología
  - ESTE (fuego):  QUIÉN SOY / Bitácora
  - SUR (tierra):  MANIFIESTO / Invariantes
  - OESTE (agua):  MÍSTICA / Espiritualidad
- Íconos SVG nuevos relacionados al tema:
  - aire: chip de circuito con nodos y conexiones
  - fuego: libro abierto con líneas
  - tierra: hexagrama dentro de círculo (sacred geometry / invariante)
  - agua: ojo en triángulo (mística)
- gioser-web src/lib.rs: ensure_page_dom usa nuevos title+tag por elemento.
- 4 md/*.md reescritos con contenido seed para los nuevos temas, con
  manifiesto explícito en tierra.md.

Workspace verde + 21 tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

d94dcfb5af5cd5af1df9660fd971452b9e41a094 · ruwaq sergio · (5 killa ñawpaq) · tayta 772801301 · kay commitpi sach'ata qhaway

Semántico sayk'uy · 8

crates/apps/gioser-web/src/lib.rs

crates/modules/gioser/gioser-canvas-web/src/lib.rs

crates/modules/gioser/gioser-shaders/src/lib.rs

8 qillqakuna · +378 −109

diff --git a/crates/apps/gioser-web/index.html b/crates/apps/gioser-web/index.html
index 99fe19a..9254583 100644
--- a/crates/apps/gioser-web/index.html
+++ b/crates/apps/gioser-web/index.html
@@ -12,45 +12,59 @@
 <body>
   <canvas id="gioser-canvas" aria-hidden="true"></canvas>
 
-  <main id="tips" aria-label="Cuatro elementos">
-    <a id="tip-aire" class="tip tip-aire" href="#aire" data-md="./md/aire.md" aria-label="Aire — Software e IA">
+  <main id="tips" aria-label="Cuatro cardinales">
+    <!-- NORTE (aire): SOFTWARE · Tecnología — circuito + nodos -->
+    <a id="tip-aire" class="tip tip-aire" href="#aire" data-md="./md/aire.md" aria-label="Software · Tecnología">
       <svg viewBox="0 0 48 48" class="tip-glyph" aria-hidden="true">
-        <path d="M6 18 q 9 -12 18 0 t 18 0" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
-        <path d="M6 28 q 9 -12 18 0 t 18 0" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" opacity="0.75"/>
-        <path d="M6 38 q 9 -12 18 0 t 18 0" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" opacity="0.45"/>
+        <rect x="18" y="18" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.7" rx="1"/>
+        <circle cx="24" cy="24" r="2" fill="currentColor"/>
+        <path d="M22 18 V14 M26 18 V14 M22 30 V34 M26 30 V34 M18 22 H14 M18 26 H14 M30 22 H34 M30 26 H34"
+              stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
+        <circle cx="10" cy="10" r="1.6" fill="currentColor" opacity="0.7"/>
+        <circle cx="38" cy="10" r="1.6" fill="currentColor" opacity="0.7"/>
+        <circle cx="10" cy="38" r="1.6" fill="currentColor" opacity="0.7"/>
+        <circle cx="38" cy="38" r="1.6" fill="currentColor" opacity="0.7"/>
+        <path d="M11 11 L17 17 M37 11 L31 17 M11 37 L17 31 M37 37 L31 31"
+              stroke="currentColor" stroke-width="1.1" opacity="0.45"/>
       </svg>
-      <span class="tip-label">AIRE</span>
-      <span class="tip-sub">Software · IA</span>
+      <span class="tip-label">SOFTWARE</span>
+      <span class="tip-sub">Tecnología</span>
     </a>
 
-    <a id="tip-fuego" class="tip tip-fuego" href="#fuego" data-md="./md/fuego.md" aria-label="Fuego — Inspiración">
+    <!-- ESTE (fuego): QUIÉN SOY · Bitácora — libro abierto -->
+    <a id="tip-fuego" class="tip tip-fuego" href="#fuego" data-md="./md/fuego.md" aria-label="Quién Soy · Bitácora">
       <svg viewBox="0 0 48 48" class="tip-glyph" aria-hidden="true">
-        <path d="M24 4 q -12 12 -6 24 q 3 -6 6 -6 q 1 10 6 12 q 10 -10 0 -22 q -4 6 -6 -8 z"
-              fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
-        <circle cx="24" cy="28" r="3" fill="currentColor" opacity="0.5"/>
+        <path d="M8 14 L24 18 L40 14 V36 L24 32 L8 36 Z"
+              fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
+        <path d="M24 18 V32" stroke="currentColor" stroke-width="1.5"/>
+        <path d="M12 22 H21 M12 26 H21 M12 30 H19" stroke="currentColor" stroke-width="1.1" opacity="0.65" stroke-linecap="round"/>
+        <path d="M27 22 H36 M27 26 H36 M29 30 H36" stroke="currentColor" stroke-width="1.1" opacity="0.65" stroke-linecap="round"/>
       </svg>
-      <span class="tip-label">FUEGO</span>
-      <span class="tip-sub">Inspiración</span>
+      <span class="tip-label">QUIÉN SOY</span>
+      <span class="tip-sub">Bitácora</span>
     </a>
 
-    <a id="tip-tierra" class="tip tip-tierra" href="#tierra" data-md="./md/tierra.md" aria-label="Tierra — Cuerpo">
+    <!-- SUR (tierra): MANIFIESTO · Invariantes — hexagrama + círculo -->
+    <a id="tip-tierra" class="tip tip-tierra" href="#tierra" data-md="./md/tierra.md" aria-label="Manifiesto · Invariantes">
       <svg viewBox="0 0 48 48" class="tip-glyph" aria-hidden="true">
-        <path d="M4 36 l 12 -16 l 8 9 l 6 -12 l 14 19 z"
-              fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
-        <path d="M24 36 v 8 M19 38 l -4 5 M29 38 l 4 5 M24 44 v 2"
-              fill="none" stroke="currentColor" stroke-width="1.3" opacity="0.7"/>
+        <circle cx="24" cy="24" r="18" fill="none" stroke="currentColor" stroke-width="1.4" opacity="0.55"/>
+        <path d="M24 8 L39 32 L9 32 Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
+        <path d="M24 40 L9 16 L39 16 Z" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.85" stroke-linejoin="round"/>
+        <circle cx="24" cy="24" r="1.8" fill="currentColor"/>
       </svg>
-      <span class="tip-label">TIERRA</span>
-      <span class="tip-sub">Cuerpo</span>
+      <span class="tip-label">MANIFIESTO</span>
+      <span class="tip-sub">Invariantes</span>
     </a>
 
-    <a id="tip-agua" class="tip tip-agua" href="#agua" data-md="./md/agua.md" aria-label="Agua — Espiritualidad aplicada">
+    <!-- OESTE (agua): MÍSTICA · Espiritualidad — ojo en triángulo -->
+    <a id="tip-agua" class="tip tip-agua" href="#agua" data-md="./md/agua.md" aria-label="Mística · Espiritualidad">
       <svg viewBox="0 0 48 48" class="tip-glyph" aria-hidden="true">
-        <path d="M6 22 q 6 -10 12 0 t 12 0 t 12 0" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
-        <path d="M6 30 q 6 -10 12 0 t 12 0 t 12 0" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" opacity="0.75"/>
-        <path d="M6 38 q 6 -10 12 0 t 12 0 t 12 0" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" opacity="0.5"/>
+        <path d="M24 6 L42 40 L6 40 Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/>
+        <path d="M13 26 Q24 16 35 26 Q24 34 13 26 Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
+        <circle cx="24" cy="26" r="3.2" fill="currentColor"/>
+        <circle cx="22.5" cy="24.5" r="0.9" fill="rgba(255,255,255,0.55)"/>
       </svg>
-      <span class="tip-label">AGUA</span>
+      <span class="tip-label">MÍSTICA</span>
       <span class="tip-sub">Espiritualidad</span>
     </a>
   </main>
diff --git a/crates/apps/gioser-web/md/agua.md b/crates/apps/gioser-web/md/agua.md
index 43d52bb..37564e7 100644
--- a/crates/apps/gioser-web/md/agua.md
+++ b/crates/apps/gioser-web/md/agua.md
@@ -1,25 +1,38 @@
-# Agua
+# Mística · Espiritualidad
 
-> *Lo que fluye. Lo que une dentro y afuera.*
+> *La práctica como puente. El misterio como interlocutor.*
 
-El **Agua** es el dominio de la **espiritualidad aplicada**: las
-prácticas, lecturas y tradiciones que sostienen la atención y dan
-sentido al hacer. No es decoración mística: es la práctica concreta
-de mantenerse permeable, vivo, conectado.
+Acá vive lo místico, lo espiritual, las prácticas que sostienen la
+atención. No es decoración: es la otra mitad del trabajo. Sin esto,
+el resto se vuelve ruido.
 
-## Espiritualidad aplicada
+## Prácticas
 
-Aplicada significa que no se queda en libros: pasa por la práctica
-diaria — la lectura, la meditación, la ceremonia, la conversación
-honda. El agua moja todos los otros ejes.
+Lo que sostiene día a día:
 
-## Lo que vive acá
+- **Meditación.** Sentarse a observar lo que sucede, sin agarrarlo.
+- **Lectura contemplativa.** Textos que se vuelven a leer hasta que
+  cambian.
+- **Ceremonia.** Marcar inicios y cierres con gestos que pesan.
+- **Naturaleza.** Estar en lugares donde uno no es el centro.
+- **Silencio.** Día completo, una vez por mes mínimo.
 
-- Notas de lectura sobre filosofía, mística, sabiduría andina.
-- Diario de prácticas (meditación, ceremonias, retiros).
-- Conversaciones con maestros y comunidades.
+## Por qué mística
+
+Porque la racionalidad sola no alcanza para vivir. Y porque las
+tradiciones llevan miles de años elaborando vocabulario para lo que
+nos pasa cuando atendemos en serio: contemplación, ego, símbolo,
+muerte, asombro.
+
+**Mística aplicada** = no quedarse en el libro. Pasar por el cuerpo,
+por la relación, por la vida cotidiana.
+
+## Lo que leo
+
+Andino, budista, cristiano-contemplativo, hindú, sufí. Sin
+exclusividad: cada tradición resuelve algunas cosas mejor que otras.
 
 ## Próximamente
 
-*Acá se va a ir armando una bitácora de lecturas y prácticas. Por
-ahora el placeholder verifica el render bajo el tema **agua**.*
+*Acá se va a ir armando una bitácora de lecturas, prácticas y notas.
+Por ahora este placeholder verifica el tema **agua** (cyan).*
diff --git a/crates/apps/gioser-web/md/aire.md b/crates/apps/gioser-web/md/aire.md
index 8147ce8..4e26052 100644
--- a/crates/apps/gioser-web/md/aire.md
+++ b/crates/apps/gioser-web/md/aire.md
@@ -1,26 +1,34 @@
-# Aire
+# Software · Tecnología
 
-> *Lo que respira el sistema. Lo que sube.*
+> *Lo público. Lo que se mantiene abierto.*
 
-El **Aire** es el dominio del **software público y la IA**. Es la capa
-intangible que transporta pensamiento — los bits que vuelan entre
-máquinas, las inferencias que destilan sentido del ruido, las APIs
-que conversan sin verse.
+Acá viven los proyectos de **software libre**, herramientas, librerías
+y exploraciones técnicas que voy publicando. La premisa es mantener
+el código abierto, documentado y útil más allá del autor.
 
-## Aspiración
+## Qué vas a encontrar acá
 
-El Aire **aspira**: empuja hacia arriba. Es el movimiento de subir el
-nivel de abstracción, de hacer que una cosa difícil parezca obvia, de
-regalarle al usuario una herramienta que no le pesa.
+- Repos públicos de cosas que escribo (Rust, Python, embedded, web).
+- Notas técnicas sobre arquitectura, sistemas distribuidos, runtimes.
+- Ensayos sobre **IA aplicada** — sin hype, con ejemplos concretos.
+- Bitácoras de exploración: lo que probé, lo que descarté, lo que sigo
+  usando.
 
-## Lo que vive acá
+## Por qué open source
 
-- Herramientas open source que **GioSer** publica y mantiene.
-- Modelos de IA que asisten al ciclo de creación.
-- Documentación, ensayos, manifiestos.
+Porque el conocimiento técnico se multiplica cuando circula. Y porque
+mucho de lo que uso a diario me lo regaló alguien que decidió compartir.
+La reciprocidad importa.
+
+## Stack actual
+
+- **Rust** para lo que necesita ser rápido, seguro y portable.
+- **Python** para análisis, ML y prototipos rápidos.
+- **Linux** (Artix/Arch) como sistema operativo de trabajo.
+- **gitea** + **nix** para infraestructura personal.
 
 ## Próximamente
 
-*Esta sección se va a llenar con los proyectos concretos del eje aire.*
-Por ahora, este placeholder vive en `md/aire.md` y se renderiza vía
-`pluma-md` con tema *aire*.
+*Voy a ir enlazando proyectos específicos acá: tools, runtimes,
+experimentos. Por ahora, este placeholder vive en `md/aire.md` y se
+renderea con el tema **aire** (azul-blanco).*
diff --git a/crates/apps/gioser-web/md/fuego.md b/crates/apps/gioser-web/md/fuego.md
index f78e4fb..212d2c9 100644
--- a/crates/apps/gioser-web/md/fuego.md
+++ b/crates/apps/gioser-web/md/fuego.md
@@ -1,25 +1,33 @@
-# Fuego
+# Quién Soy · Bitácora
 
-> *Lo que enciende. Lo que transforma.*
+> *La identidad como verbo. La crónica como práctica.*
 
-El **Fuego** es el dominio de la **inspiración**. Es la chispa que
-convierte una idea en gesto, una frase en ritual, un problema en
-prototipo. Sin fuego, los otros tres elementos se enfrían y se quedan
-contemplándose.
+Acá vive lo personal: quién soy, qué hago, qué leo, qué pienso. Una
+bitácora honesta, no curada para impresionar. Si vas a leer esto,
+asumí que es borrador.
 
-## Inspiración
+## Quién soy
 
-El fuego no se planea, se **atiende**. Llega — y la respuesta es no
-dejarlo pasar. Acá viven los ensayos, los videos, los manifiestos y
-los experimentos que nacieron porque algo prendió.
+**Sergio**. Programador, lector, padre, alguien que practica
+mantenerse despierto. Vivo entre código, café, montañas y libros.
 
-## Lo que vive acá
+Las cosas que más me importan no son las que mejor cuento todavía.
+Por eso escribo: para precisar lo que sé y lo que no.
 
-- Charlas, ensayos cortos, posts crudos.
-- Bocetos visuales, exploraciones tipográficas.
-- Documentos de manifiesto sobre cómo trabajar y para qué.
+## Bitácora
+
+Notas más o menos diarias sobre lo que voy pensando, viviendo,
+fallando. Sin algoritmo de engagement, sin métricas. Sólo crónica.
+
+Las entradas se ordenan por fecha. Las más viejas a veces dicen cosas
+que ya no pienso así — las dejo igual.
+
+## Por qué publicarlo
+
+Porque escribir en público obliga a precisar. Y porque a veces lo que
+uno escribe para sí mismo le sirve a otra persona que no conoce.
 
 ## Próximamente
 
-*Voy a ir enlazando archivos `.md` específicos acá. Por ahora este
-texto sirve para verificar el render bajo el tema **fuego**.*
+*Acá se va a ir armando una bitácora con entradas fechadas. Por ahora
+este placeholder verifica el tema **fuego** (ámbar/escarlata).*
diff --git a/crates/apps/gioser-web/md/tierra.md b/crates/apps/gioser-web/md/tierra.md
index 49cb2a4..d1fb42e 100644
--- a/crates/apps/gioser-web/md/tierra.md
+++ b/crates/apps/gioser-web/md/tierra.md
@@ -1,26 +1,39 @@
-# Tierra
+# Manifiesto · Invariantes
 
-> *El cuerpo. La materia. Lo que sostiene.*
+> *Lo que no cambia. La piedra de toque.*
 
-La **Tierra** es el dominio del **cuerpo**. Es lo que se toca, lo que
-huele, lo que se siembra. El eje terrestre de GioSer recuerda que
-todo proyecto —por muy abstracto que parezca— pasa por un cuerpo que
-respira, come, descansa y se conmueve.
+Acá vive el manifiesto de GioSer: las **invariantes** que sostienen
+todo lo demás. Lo que no negocio, lo que define la forma del trabajo
+antes que cualquier proyecto particular.
 
-## Cuerpo
+## Invariantes
 
-El cuerpo no es una metáfora: es donde aterriza el aire, donde el
-agua se vuelve vida, donde el fuego deja huella. Cuidarlo es parte
-del trabajo.
+Cosas que considero **no-negociables** en cómo hago el trabajo:
 
-## Lo que vive acá
+- **Código abierto por defecto.** Si tiene sentido, se publica.
+- **Honestidad por encima de marketing.** No prometo lo que no puedo
+  cumplir, ni vendo lo que no probé.
+- **El cuerpo es infraestructura.** Cuidarlo es parte del trabajo, no
+  opuesto al trabajo. Sin cuerpo no hay nada.
+- **Las ideas se prueban escribiéndolas.** Si no hay documento, todavía
+  no existe la idea.
+- **Compatibilidad hacia abajo > novedad arriba.** Las invariantes
+  duran, las modas no.
+- **Una sola voz.** Lo que digo en privado coincide con lo que publico.
 
-- Prácticas, rutinas, recetas.
-- Materialidad: objetos, lugares, oficios.
-- Salud y reposo como infraestructura.
+## Por qué un manifiesto
+
+Porque sin invariantes, cada decisión es ad hoc. Tener un set chico de
+principios reduce la energía gastada en cada elección — y deja en
+claro cuándo estoy contradiciéndome.
+
+## Revisión
+
+Este manifiesto se revisa una vez al año, no antes. Si una invariante
+deja de aplicarse, se quita con una explicación pública.
 
 ## Próximamente
 
-*Esta sección va a recibir notas, fotos y enlaces a oficios y
-prácticas concretas. Por ahora el placeholder verifica el tema
-**tierra**.*
+*Esta sección va a recibir el manifiesto completo + revisiones
+históricas. Por ahora este placeholder verifica el tema **tierra**
+(ocre cálido).*
diff --git a/crates/apps/gioser-web/src/lib.rs b/crates/apps/gioser-web/src/lib.rs
index 86f9b3e..03af2fb 100644
--- a/crates/apps/gioser-web/src/lib.rs
+++ b/crates/apps/gioser-web/src/lib.rs
@@ -226,10 +226,10 @@ impl AppState {
             return;
         };
         let (title, tag) = match element {
-            "aire" => ("Aire", "Software · IA · Aspiración"),
-            "fuego" => ("Fuego", "Inspiración"),
-            "tierra" => ("Tierra", "Cuerpo"),
-            "agua" => ("Agua", "Espiritualidad aplicada"),
+            "aire" => ("Software", "Tecnología · Open Source · IA"),
+            "fuego" => ("Quién Soy", "Bitácora · Crónica"),
+            "tierra" => ("Manifiesto", "Invariantes · Piedra de toque"),
+            "agua" => ("Mística", "Espiritualidad aplicada"),
             _ => return,
         };
         let html = format!(
diff --git a/crates/modules/gioser/gioser-canvas-web/src/lib.rs b/crates/modules/gioser/gioser-canvas-web/src/lib.rs
index bb34011..1311531 100644
--- a/crates/modules/gioser/gioser-canvas-web/src/lib.rs
+++ b/crates/modules/gioser/gioser-canvas-web/src/lib.rs
@@ -31,6 +31,16 @@ const COT_HALF_FOV: f32 = 2.414_213_5;
 /// con `FS_CHACANA::ringR_main` del shader.
 const RING_FACTOR: f32 = 1.45;
 
+/// Duración en segundos de cada "cuerpo central" (sol / luna / tierra)
+/// antes de empezar a transicionar al siguiente. ~45 s = ~10 pulsos del
+/// sol (`sin(t*1.4)` período ≈ 4.5 s).
+const BODY_PHASE_SECS: f32 = 45.0;
+/// Duración del cross-fade entre cuerpos. Más alto = transiciones más
+/// graduales.
+const BODY_TRANSITION_SECS: f32 = 4.0;
+/// Cantidad de cuerpos en el ciclo: sol(0), luna(1), tierra(2).
+const BODY_COUNT: i32 = 3;
+
 /// Identidad de cada cardinal (id, color de acento, label). Orden `[N, E, S, W]`.
 pub mod tips {
     use gioser_palette::{elements, Rgb};
@@ -227,6 +237,9 @@ impl Renderer {
                 "u_agua_color",
                 "u_zodiac[0]",
                 "u_sun_pulse",
+                "u_body_a",
+                "u_body_b",
+                "u_body_blend",
             ],
         )
         .map_err(JsValue::from)?;
@@ -475,6 +488,16 @@ impl Renderer {
         if let Some(u) = self.chacana_prog.u("u_sun_pulse") {
             gl.uniform1f(Some(u), self.sun_pulse);
         }
+        let (body_a, body_b, blend) = body_state(t);
+        if let Some(u) = self.chacana_prog.u("u_body_a") {
+            gl.uniform1i(Some(u), body_a);
+        }
+        if let Some(u) = self.chacana_prog.u("u_body_b") {
+            gl.uniform1i(Some(u), body_b);
+        }
+        if let Some(u) = self.chacana_prog.u("u_body_blend") {
+            gl.uniform1f(Some(u), blend);
+        }
         gl.bind_vertex_array(Some(&self.chacana_vao));
         gl.draw_arrays(GL::TRIANGLES, 0, self.chacana_quad_count);
         gl.bind_vertex_array(None);
@@ -486,3 +509,29 @@ fn upload_rgb(gl: &GL, loc: Option<&WebGlUniformLocation>, c: Rgb) {
         gl.uniform3f(Some(u), c.0, c.1, c.2);
     }
 }
+
+/// Devuelve `(body_a, body_b, blend)` para el tiempo `t` (segundos).
+/// El ciclo es sol → luna → tierra → sol → ... cada uno estable por
+/// `BODY_PHASE_SECS` y con `BODY_TRANSITION_SECS` de cross-fade al
+/// siguiente.
+fn body_state(t: f32) -> (i32, i32, f32) {
+    let slot = BODY_PHASE_SECS + BODY_TRANSITION_SECS;
+    let cycle = slot * BODY_COUNT as f32;
+    let pt = t.rem_euclid(cycle).max(0.0);
+    let mut acc = 0.0_f32;
+    for i in 0..BODY_COUNT {
+        let stable_end = acc + BODY_PHASE_SECS;
+        if pt < stable_end {
+            return (i, i, 0.0);
+        }
+        let trans_end = stable_end + BODY_TRANSITION_SECS;
+        if pt < trans_end {
+            let blend = ((pt - stable_end) / BODY_TRANSITION_SECS).clamp(0.0, 1.0);
+            // Smoothstep para una curva más natural que linear lerp.
+            let s = blend * blend * (3.0 - 2.0 * blend);
+            return (i, (i + 1) % BODY_COUNT, s);
+        }
+        acc = trans_end;
+    }
+    (0, 0, 0.0)
+}
diff --git a/crates/modules/gioser/gioser-shaders/src/lib.rs b/crates/modules/gioser/gioser-shaders/src/lib.rs
index 773dbc5..fdfb568 100644
--- a/crates/modules/gioser/gioser-shaders/src/lib.rs
+++ b/crates/modules/gioser/gioser-shaders/src/lib.rs
@@ -192,6 +192,11 @@ uniform vec3  u_tierra_color;
 uniform vec3  u_agua_color;
 uniform vec3  u_zodiac[12];
 uniform float u_sun_pulse;
+// Ciclo del cuerpo central: 0=sol, 1=luna, 2=tierra. Se interpola entre
+// `u_body_a` y `u_body_b` con `u_body_blend ∈ [0, 1]`.
+uniform int   u_body_a;
+uniform int   u_body_b;
+uniform float u_body_blend;
 
 const float PI = 3.14159265;
 
@@ -201,12 +206,147 @@ float hash21c(vec2 p) {
 float hash11c(float n) {
     return fract(sin(n * 78.233) * 43758.5453);
 }
+// Value noise + fbm para superficies (lunar craters, continentes terrestres).
+float vnoise_c(vec2 p) {
+    vec2 i = floor(p);
+    vec2 f = fract(p);
+    f = f * f * (3.0 - 2.0 * f);
+    float a = hash21c(i);
+    float b = hash21c(i + vec2(1.0, 0.0));
+    float c = hash21c(i + vec2(0.0, 1.0));
+    float d = hash21c(i + vec2(1.0, 1.0));
+    return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
+}
+float fbm_c(vec2 p) {
+    float v = 0.0;
+    float a = 0.5;
+    for (int i = 0; i < 4; i++) {
+        v += a * vnoise_c(p);
+        p = p * 2.03 + vec2(1.7, 9.2);
+        a *= 0.5;
+    }
+    return v;
+}
 
 float sdBox(vec2 p, vec2 b) {
     vec2 d = abs(p) - b;
     return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
 }
 
+// ===== CUERPO CENTRAL: Sol / Luna / Tierra =====
+//
+// Cada uno renderea dentro del cuadrado central de la chacana con su
+// propia personalidad realista. El loop temporal (cambio entre cuerpos
+// + transiciones graduales) lo decide el host vía u_body_a/b/blend.
+
+vec3 render_sun(vec2 p, float r, float pulse) {
+    // Núcleo brillante + corona difusa con pulso.
+    float coreR = u_thickness * 0.42;
+    float core  = exp(-(r * r) / (2.0 * coreR * coreR));
+    float halR  = u_center_half * 0.70;
+    float halo  = exp(-(r * r) / (2.0 * halR * halR));
+    // Superficie boiling (plasma) muy sutil.
+    float plasma = fbm_c(p * 18.0 + vec2(u_time * 0.15, u_time * 0.10)) * 0.20;
+    vec3 base = u_sun_color * (core * (1.20 + 0.25 * pulse) + halo * (0.55 + 0.15 * pulse));
+    return base + u_sun_color * core * plasma * 0.6;
+}
+
+vec3 render_moon(vec2 p, float r, float time) {
+    float moonR = u_thickness * 1.35;
+    // Disco con borde suave.
+    float disk = 1.0 - smoothstep(moonR * 0.86, moonR * 1.00, r);
+    // Limb darkening: el borde más oscuro que el centro.
+    float limb_factor = sqrt(max(1.0 - (r * r) / (moonR * moonR), 0.0));
+    // Cráteres y mares lunares vía fbm.
+    float craters = fbm_c(p * 22.0) * 0.45 + fbm_c(p * 48.0) * 0.20;
+    // Fase: terminator se mueve de izquierda a derecha lentamente.
+    // 1 ciclo lunar visible cada ~38 s. sin(t*0.165) recorre full→new→full.
+    float phase = sin(time * 0.165) * 0.5 + 0.5; // 0..1
+    float terminator_x = mix(-moonR * 1.1, moonR * 1.1, phase);
+    float nx = p.x;
+    float lit = smoothstep(terminator_x - moonR * 0.12, terminator_x + moonR * 0.05, nx);
+    // Color superficie lunar (gris-azulado).
+    vec3 surface = vec3(0.86, 0.88, 0.94) * (0.70 + craters * 0.55);
+    // Halo cercano al limb iluminado (luz dispersada).
+    float outer_glow = smoothstep(moonR * 1.18, moonR * 0.94, r) - disk;
+    vec3 glow = vec3(0.55, 0.70, 0.95) * max(outer_glow, 0.0) * lit * 0.50;
+    return surface * disk * lit * limb_factor + glow;
+}
+
+vec3 render_earth(vec2 p, float r, float time) {
+    float earthR = u_thickness * 1.35;
+    float disk = 1.0 - smoothstep(earthR * 0.86, earthR * 1.00, r);
+    float limb_factor = sqrt(max(1.0 - (r * r) / (earthR * earthR), 0.0));
+    // Rotación lenta: continentes drift horizontalmente.
+    float rot = time * 0.08;
+    vec2 rp = p + vec2(rot, 0.0);
+    // Continentes con fbm orgánico.
+    float landmass = fbm_c(rp * 7.5);
+    float is_land = smoothstep(0.50, 0.56, landmass);
+    vec3 ocean = vec3(0.08, 0.28, 0.52);
+    vec3 land  = vec3(0.30, 0.52, 0.24);
+    vec3 land_high = vec3(0.55, 0.45, 0.28); // montañas / desiertos
+    land = mix(land, land_high, smoothstep(0.60, 0.78, landmass));
+    vec3 surface = mix(ocean, land, is_land);
+    // Casquetes polares.
+    float ny = abs(p.y / max(earthR, 1e-4));
+    float polar = smoothstep(0.70, 0.94, ny);
+    surface = mix(surface, vec3(0.96, 0.97, 1.0), polar * 0.85);
+    // Nubes flotando en otra capa rotando algo distinto.
+    float clouds = fbm_c(rp * 6.0 + vec2(rot * 0.3, 0.0)) * 0.7;
+    float cloud_mask = smoothstep(0.55, 0.75, clouds);
+    surface = mix(surface, vec3(0.95, 0.96, 0.99), cloud_mask * 0.40);
+    // Día / noche: hemisferio iluminado.
+    float lit = smoothstep(-0.45, 0.55, p.x / max(earthR, 1e-4) + 0.15);
+    surface *= 0.30 + 0.70 * lit;
+    // Atmósfera azul en el limb (Rayleigh scattering simplificado).
+    float atm_inner = smoothstep(earthR * 1.10, earthR * 0.95, r);
+    float atm = max(atm_inner - disk, 0.0);
+    return surface * disk * limb_factor + vec3(0.30, 0.55, 0.95) * atm * 0.55;
+}
+
+vec3 render_body(int kind, vec2 p, float r, float time, float pulse) {
+    if (kind == 0) return render_sun(p, r, pulse);
+    if (kind == 1) return render_moon(p, r, time);
+    return render_earth(p, r, time);
+}
+
+// ===== AURA ELEMENTAL (NUBE ANCHA POR CARDINAL) =====
+// Se suma a las partículas puntuales: una cobertura ancha del cuadrante
+// del cardinal correspondiente, con personalidad por elemento.
+vec3 element_cloud(vec2 p, vec2 tip, vec2 outward, vec3 color, float time, int kind) {
+    vec2 perp = vec2(-outward.y, outward.x);
+    // Centro de la nube: bien adentro del cuadrante (más allá del tip).
+    vec2 cloud_center = tip + outward * 0.22;
+    vec2 to_p = p - cloud_center;
+    float along = dot(to_p, outward);
+    float perp_d = dot(to_p, perp);
+    // Anisotropía: bien ancha perpendicular, larga a lo largo del eje.
+    float sigma_along = 0.42;
+    float sigma_perp  = 0.34;
+    float base = exp(-(along * along) / (2.0 * sigma_along * sigma_along)
+                     -(perp_d * perp_d) / (2.0 * sigma_perp * sigma_perp));
+    // Textura noise animada por elemento.
+    vec2 noise_uv = (p - tip) * (3.5 + float(kind) * 0.4)
+                  + vec2(time * (0.20 + float(kind) * 0.05), time * 0.10);
+    float n = fbm_c(noise_uv);
+    float modulation;
+    if (kind == 0) {
+        // AIRE: corrientes suaves que se mueven horizontalmente.
+        modulation = 0.55 + 0.45 * (n * 0.7 + sin(time * 0.6 + perp_d * 4.0) * 0.3);
+    } else if (kind == 1) {
+        // FUEGO: lengüetazos que parpadean rápido.
+        modulation = (0.40 + 0.60 * n) * (0.7 + 0.3 * sin(time * 3.2 + along * 8.0));
+    } else if (kind == 2) {
+        // TIERRA: densidad sólida con variación lenta.
+        modulation = 0.65 + 0.35 * fbm_c(p * 4.0 + vec2(time * 0.05, 0.0));
+    } else {
+        // AGUA: ondulaciones grandes que viajan hacia afuera.
+        modulation = 0.50 + 0.50 * sin(time * 0.9 - along * 5.0 + n * 4.0);
+    }
+    return color * base * max(modulation, 0.0) * 0.28;
+}
+
 // Chacana de 2 escalones (mística clásica de Tiwanaku).
 float sdChacana(vec2 p, float s, float c) {
     float d = sdBox(p, vec2(c, c));
@@ -311,22 +451,31 @@ void main() {
     float d = sdChacana(p, u_thickness, u_center_half);
     float r = length(p);
 
-    // === SOL DETRÁS ===
-    // Halo grande, sólo visible dentro de la superficie de la chacana.
+    // === CUERPO CENTRAL — SOL / LUNA / TIERRA ===
+    // Sólo se computa dentro de la superficie de la chacana (perf + estética).
     float inside = 1.0 - smoothstep(-0.004, 0.004, d);
-    float sunR = u_thickness * 0.42;
-    float sun = exp(-(r * r) / (2.0 * sunR * sunR));
-    float corR = u_center_half * 0.75;
-    float corona = exp(-(r * r) / (2.0 * corR * corR));
-    float halo = sun * (1.15 + 0.20 * u_sun_pulse) + corona * (0.55 + 0.15 * u_sun_pulse);
-
-    // Rayos radiales sutiles desde el centro, sólo visibles donde la superficie
-    // de la chacana los recibe.
+    vec3 central = vec3(0.0);
+    if (inside > 0.001) {
+        central = render_body(u_body_a, p, r, u_time, u_sun_pulse);
+        if (u_body_blend > 0.001) {
+            vec3 next_body = render_body(u_body_b, p, r, u_time, u_sun_pulse);
+            central = mix(central, next_body, u_body_blend);
+        }
+    }
+
+    // Rayos radiales sutiles desde el centro (el cuerpo los proyecta a
+    // través de la superficie). El multiplicador disminuye cuando la luna
+    // o la tierra están activas — el sol es el que más irradia.
     float ang = atan(p.y, p.x);
+    float radial_mult = (u_body_a == 0) ? 1.0 : 0.35;
+    if (u_body_blend > 0.001) {
+        float next_mult = (u_body_b == 0) ? 1.0 : 0.35;
+        radial_mult = mix(radial_mult, next_mult, u_body_blend);
+    }
     float radial = pow(abs(cos(ang * 4.0 + sin(u_time * 0.3) * 0.2)), 8.0)
                  * smoothstep(0.0, u_center_half * 0.8, r)
                  * (1.0 - smoothstep(u_center_half * 0.85, u_center_half * 1.2, r))
-                 * 0.30;
+                 * 0.30 * radial_mult;
 
     // === DOBLE OUTLINE ===
     // Línea interior (sobre la SDF=0).
@@ -362,6 +511,15 @@ void main() {
     particles += element_particles(p, vec2(0.0, -L), vec2(0.0, -1.0), u_tierra_color, 2, 3.11);
     particles += element_particles(p, vec2(-L, 0.0), vec2(-1.0, 0.0), u_agua_color,   3, 5.97);
 
+    // === AURA ELEMENTAL ANCHA ===
+    // Una nube wide por cardinal — ocupa el cuadrante entero, no sólo la
+    // punta. Las partículas puntuales aportan detalle agudo encima.
+    vec3 clouds = vec3(0.0);
+    clouds += element_cloud(p, vec2(0.0,  L), vec2(0.0,  1.0), u_aire_color,   u_time, 0);
+    clouds += element_cloud(p, vec2( L, 0.0), vec2( 1.0, 0.0), u_fuego_color,  u_time, 1);
+    clouds += element_cloud(p, vec2(0.0, -L), vec2(0.0, -1.0), u_tierra_color, u_time, 2);
+    clouds += element_cloud(p, vec2(-L, 0.0), vec2(-1.0, 0.0), u_agua_color,   u_time, 3);
+
     // === TRAZOS ZODIACALES ===
     // 12 líneas radiales muy sutiles entre la chacana y el aro principal,
     // una por signo, con sus colores significativos (Aries=fuego rojo,
@@ -401,11 +559,13 @@ void main() {
 
     // === COMPOSICIÓN ===
     vec3 col = vec3(0.0);
-    // Sol detrás (clip a interior).
-    col += u_sun_color * halo * inside * 1.55;
+    // Cuerpo central (sol / luna / tierra) clipeado al interior de la chacana.
+    col += central * inside * 1.55;
     col += u_line_color * radial * inside * 0.6;
     // Niebla oscura translúcida en el interior para profundidad.
     col += u_dark_color * inside * 0.20;
+    // Auras anchas de los elementos (debajo de los aros, sin clip al interior).
+    col += clouds;
     // Líneas y aros.
     col += u_line_color * line * 1.70;
     col += u_line_color * glow * 0.95;
@@ -416,9 +576,13 @@ void main() {
     col += zodiac * 0.55; // muy sutil — apenas visible.
 
     float zodiac_lum = zodiac.r + zodiac.g + zodiac.b;
+    float cloud_lum = clouds.r + clouds.g + clouds.b;
+    float central_lum = central.r + central.g + central.b;
     float alpha = clamp(
-        halo * inside + line + glow + ring_main + ring_inner + dots + inside * 0.12
+        central_lum * inside * 0.7 + line + glow + ring_main + ring_inner
+            + dots + inside * 0.12
             + (particles.r + particles.g + particles.b) * 0.5
+            + cloud_lum * 0.45
             + zodiac_lum * 0.3,
         0.0, 1.0);
     fragColor = vec4(col, alpha);