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>
This commit is contained in:
sergio
2026-05-15 04:31:02 +00:00
parent 7728013012
commit d94dcfb5af
8 changed files with 377 additions and 108 deletions
+39 -25
View File
@@ -12,45 +12,59 @@
<body> <body>
<canvas id="gioser-canvas" aria-hidden="true"></canvas> <canvas id="gioser-canvas" aria-hidden="true"></canvas>
<main id="tips" aria-label="Cuatro elementos"> <main id="tips" aria-label="Cuatro cardinales">
<a id="tip-aire" class="tip tip-aire" href="#aire" data-md="./md/aire.md" aria-label="Aire — Software e IA"> <!-- 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"> <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"/> <rect x="18" y="18" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.7" rx="1"/>
<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"/> <circle cx="24" cy="24" r="2" fill="currentColor"/>
<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"/> <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> </svg>
<span class="tip-label">AIRE</span> <span class="tip-label">SOFTWARE</span>
<span class="tip-sub">Software · IA</span> <span class="tip-sub">Tecnología</span>
</a> </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"> <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" <path d="M8 14 L24 18 L40 14 V36 L24 32 L8 36 Z"
fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/> fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
<circle cx="24" cy="28" r="3" fill="currentColor" opacity="0.5"/> <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> </svg>
<span class="tip-label">FUEGO</span> <span class="tip-label">QUIÉN SOY</span>
<span class="tip-sub">Inspiración</span> <span class="tip-sub">Bitácora</span>
</a> </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"> <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" <circle cx="24" cy="24" r="18" fill="none" stroke="currentColor" stroke-width="1.4" opacity="0.55"/>
fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/> <path d="M24 8 L39 32 L9 32 Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
<path d="M24 36 v 8 M19 38 l -4 5 M29 38 l 4 5 M24 44 v 2" <path d="M24 40 L9 16 L39 16 Z" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.85" stroke-linejoin="round"/>
fill="none" stroke="currentColor" stroke-width="1.3" opacity="0.7"/> <circle cx="24" cy="24" r="1.8" fill="currentColor"/>
</svg> </svg>
<span class="tip-label">TIERRA</span> <span class="tip-label">MANIFIESTO</span>
<span class="tip-sub">Cuerpo</span> <span class="tip-sub">Invariantes</span>
</a> </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"> <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="M24 6 L42 40 L6 40 Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="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="M13 26 Q24 16 35 26 Q24 34 13 26 Z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>
<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"/> <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> </svg>
<span class="tip-label">AGUA</span> <span class="tip-label">MÍSTICA</span>
<span class="tip-sub">Espiritualidad</span> <span class="tip-sub">Espiritualidad</span>
</a> </a>
</main> </main>
+29 -16
View File
@@ -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 Acá vive lo místico, lo espiritual, las prácticas que sostienen la
prácticas, lecturas y tradiciones que sostienen la atención y dan atención. No es decoración: es la otra mitad del trabajo. Sin esto,
sentido al hacer. No es decoración mística: es la práctica concreta el resto se vuelve ruido.
de mantenerse permeable, vivo, conectado.
## Espiritualidad aplicada ## Prácticas
Aplicada significa que no se queda en libros: pasa por la práctica Lo que sostiene día a día:
diaria — la lectura, la meditación, la ceremonia, la conversación
honda. El agua moja todos los otros ejes.
## 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. ## Por qué mística
- Diario de prácticas (meditación, ceremonias, retiros).
- Conversaciones con maestros y comunidades. 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 ## Próximamente
*Acá se va a ir armando una bitácora de lecturas y prácticas. Por *Acá se va a ir armando una bitácora de lecturas, prácticas y notas.
ahora el placeholder verifica el render bajo el tema **agua**.* Por ahora este placeholder verifica el tema **agua** (cyan).*
+25 -17
View File
@@ -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 Acá viven los proyectos de **software libre**, herramientas, librerías
intangible que transporta pensamiento — los bits que vuelan entre y exploraciones técnicas que voy publicando. La premisa es mantener
máquinas, las inferencias que destilan sentido del ruido, las APIs el código abierto, documentado y útil más allá del autor.
que conversan sin verse.
## Aspiración ## Qué vas a encontrar acá
El Aire **aspira**: empuja hacia arriba. Es el movimiento de subir el - Repos públicos de cosas que escribo (Rust, Python, embedded, web).
nivel de abstracción, de hacer que una cosa difícil parezca obvia, de - Notas técnicas sobre arquitectura, sistemas distribuidos, runtimes.
regalarle al usuario una herramienta que no le pesa. - 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. Porque el conocimiento técnico se multiplica cuando circula. Y porque
- Modelos de IA que asisten al ciclo de creación. mucho de lo que uso a diario me lo regaló alguien que decidió compartir.
- Documentación, ensayos, manifiestos. 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 ## Próximamente
*Esta sección se va a llenar con los proyectos concretos del eje aire.* *Voy a ir enlazando proyectos específicos acá: tools, runtimes,
Por ahora, este placeholder vive en `md/aire.md` y se renderiza vía experimentos. Por ahora, este placeholder vive en `md/aire.md` y se
`pluma-md` con tema *aire*. renderea con el tema **aire** (azul-blanco).*
+24 -16
View File
@@ -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 Acá vive lo personal: quién soy, qué hago, qué leo, qué pienso. Una
convierte una idea en gesto, una frase en ritual, un problema en bitácora honesta, no curada para impresionar. Si vas a leer esto,
prototipo. Sin fuego, los otros tres elementos se enfrían y se quedan asumí que es borrador.
contemplándose.
## Inspiración ## Quién soy
El fuego no se planea, se **atiende**. Llega — y la respuesta es no **Sergio**. Programador, lector, padre, alguien que practica
dejarlo pasar. Acá viven los ensayos, los videos, los manifiestos y mantenerse despierto. Vivo entre código, café, montañas y libros.
los experimentos que nacieron porque algo prendió.
## 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. ## Bitácora
- Bocetos visuales, exploraciones tipográficas.
- Documentos de manifiesto sobre cómo trabajar y para qué. 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 ## Próximamente
*Voy a ir enlazando archivos `.md` específicos acá. Por ahora este *Acá se va a ir armando una bitácora con entradas fechadas. Por ahora
texto sirve para verificar el render bajo el tema **fuego**.* este placeholder verifica el tema **fuego** (ámbar/escarlata).*
+30 -17
View File
@@ -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 Acá vive el manifiesto de GioSer: las **invariantes** que sostienen
huele, lo que se siembra. El eje terrestre de GioSer recuerda que todo lo demás. Lo que no negocio, lo que define la forma del trabajo
todo proyecto —por muy abstracto que parezca— pasa por un cuerpo que antes que cualquier proyecto particular.
respira, come, descansa y se conmueve.
## Cuerpo ## Invariantes
El cuerpo no es una metáfora: es donde aterriza el aire, donde el Cosas que considero **no-negociables** en cómo hago el trabajo:
agua se vuelve vida, donde el fuego deja huella. Cuidarlo es parte
del 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. ## Por qué un manifiesto
- Materialidad: objetos, lugares, oficios.
- Salud y reposo como infraestructura. 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 ## Próximamente
*Esta sección va a recibir notas, fotos y enlaces a oficios y *Esta sección va a recibir el manifiesto completo + revisiones
prácticas concretas. Por ahora el placeholder verifica el tema históricas. Por ahora este placeholder verifica el tema **tierra**
**tierra**.* (ocre cálido).*
+4 -4
View File
@@ -226,10 +226,10 @@ impl AppState {
return; return;
}; };
let (title, tag) = match element { let (title, tag) = match element {
"aire" => ("Aire", "Software · IA · Aspiración"), "aire" => ("Software", "Tecnología · Open Source · IA"),
"fuego" => ("Fuego", "Inspiración"), "fuego" => ("Quién Soy", "Bitácora · Crónica"),
"tierra" => ("Tierra", "Cuerpo"), "tierra" => ("Manifiesto", "Invariantes · Piedra de toque"),
"agua" => ("Agua", "Espiritualidad aplicada"), "agua" => ("Mística", "Espiritualidad aplicada"),
_ => return, _ => return,
}; };
let html = format!( let html = format!(
@@ -31,6 +31,16 @@ const COT_HALF_FOV: f32 = 2.414_213_5;
/// con `FS_CHACANA::ringR_main` del shader. /// con `FS_CHACANA::ringR_main` del shader.
const RING_FACTOR: f32 = 1.45; 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]`. /// Identidad de cada cardinal (id, color de acento, label). Orden `[N, E, S, W]`.
pub mod tips { pub mod tips {
use gioser_palette::{elements, Rgb}; use gioser_palette::{elements, Rgb};
@@ -227,6 +237,9 @@ impl Renderer {
"u_agua_color", "u_agua_color",
"u_zodiac[0]", "u_zodiac[0]",
"u_sun_pulse", "u_sun_pulse",
"u_body_a",
"u_body_b",
"u_body_blend",
], ],
) )
.map_err(JsValue::from)?; .map_err(JsValue::from)?;
@@ -475,6 +488,16 @@ impl Renderer {
if let Some(u) = self.chacana_prog.u("u_sun_pulse") { if let Some(u) = self.chacana_prog.u("u_sun_pulse") {
gl.uniform1f(Some(u), self.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.bind_vertex_array(Some(&self.chacana_vao));
gl.draw_arrays(GL::TRIANGLES, 0, self.chacana_quad_count); gl.draw_arrays(GL::TRIANGLES, 0, self.chacana_quad_count);
gl.bind_vertex_array(None); 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); 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)
}
+177 -13
View File
@@ -192,6 +192,11 @@ uniform vec3 u_tierra_color;
uniform vec3 u_agua_color; uniform vec3 u_agua_color;
uniform vec3 u_zodiac[12]; uniform vec3 u_zodiac[12];
uniform float u_sun_pulse; 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; const float PI = 3.14159265;
@@ -201,12 +206,147 @@ float hash21c(vec2 p) {
float hash11c(float n) { float hash11c(float n) {
return fract(sin(n * 78.233) * 43758.5453); 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) { float sdBox(vec2 p, vec2 b) {
vec2 d = abs(p) - b; vec2 d = abs(p) - b;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); 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). // Chacana de 2 escalones (mística clásica de Tiwanaku).
float sdChacana(vec2 p, float s, float c) { float sdChacana(vec2 p, float s, float c) {
float d = sdBox(p, vec2(c, c)); float d = sdBox(p, vec2(c, c));
@@ -311,22 +451,31 @@ void main() {
float d = sdChacana(p, u_thickness, u_center_half); float d = sdChacana(p, u_thickness, u_center_half);
float r = length(p); float r = length(p);
// === SOL DETRÁS === // === CUERPO CENTRAL — SOL / LUNA / TIERRA ===
// Halo grande, sólo visible dentro de la superficie de la chacana. // 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 inside = 1.0 - smoothstep(-0.004, 0.004, d);
float sunR = u_thickness * 0.42; vec3 central = vec3(0.0);
float sun = exp(-(r * r) / (2.0 * sunR * sunR)); if (inside > 0.001) {
float corR = u_center_half * 0.75; central = render_body(u_body_a, p, r, u_time, u_sun_pulse);
float corona = exp(-(r * r) / (2.0 * corR * corR)); if (u_body_blend > 0.001) {
float halo = sun * (1.15 + 0.20 * u_sun_pulse) + corona * (0.55 + 0.15 * u_sun_pulse); 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, sólo visibles donde la superficie // Rayos radiales sutiles desde el centro (el cuerpo los proyecta a
// de la chacana los recibe. // 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 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) 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) * smoothstep(0.0, u_center_half * 0.8, r)
* (1.0 - smoothstep(u_center_half * 0.85, u_center_half * 1.2, r)) * (1.0 - smoothstep(u_center_half * 0.85, u_center_half * 1.2, r))
* 0.30; * 0.30 * radial_mult;
// === DOBLE OUTLINE === // === DOBLE OUTLINE ===
// Línea interior (sobre la SDF=0). // 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(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); 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 === // === TRAZOS ZODIACALES ===
// 12 líneas radiales muy sutiles entre la chacana y el aro principal, // 12 líneas radiales muy sutiles entre la chacana y el aro principal,
// una por signo, con sus colores significativos (Aries=fuego rojo, // una por signo, con sus colores significativos (Aries=fuego rojo,
@@ -401,11 +559,13 @@ void main() {
// === COMPOSICIÓN === // === COMPOSICIÓN ===
vec3 col = vec3(0.0); vec3 col = vec3(0.0);
// Sol detrás (clip a interior). // Cuerpo central (sol / luna / tierra) clipeado al interior de la chacana.
col += u_sun_color * halo * inside * 1.55; col += central * inside * 1.55;
col += u_line_color * radial * inside * 0.6; col += u_line_color * radial * inside * 0.6;
// Niebla oscura translúcida en el interior para profundidad. // Niebla oscura translúcida en el interior para profundidad.
col += u_dark_color * inside * 0.20; 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. // Líneas y aros.
col += u_line_color * line * 1.70; col += u_line_color * line * 1.70;
col += u_line_color * glow * 0.95; col += u_line_color * glow * 0.95;
@@ -416,9 +576,13 @@ void main() {
col += zodiac * 0.55; // muy sutil — apenas visible. col += zodiac * 0.55; // muy sutil — apenas visible.
float zodiac_lum = zodiac.r + zodiac.g + zodiac.b; 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( 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 + (particles.r + particles.g + particles.b) * 0.5
+ cloud_lum * 0.45
+ zodiac_lum * 0.3, + zodiac_lum * 0.3,
0.0, 1.0); 0.0, 1.0);
fragColor = vec4(col, alpha); fragColor = vec4(col, alpha);