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>
<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>
+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
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).*
+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
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).*
+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
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).*
+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
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).*
+4 -4
View File
@@ -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!(
@@ -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)
}
+177 -13
View File
@@ -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);
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, sólo visibles donde la superficie
// de la chacana los recibe.
// 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);