fce630c8d0
Visual de la chacana retrabajado contra chakana.png de referencia:
- Sol detrás (gauss + corona, masked al interior de la chacana — sólo
asoma por la superficie de la cruz, no se cuela afuera).
- Doble outline dorado (línea principal + paralela offset 0.020), color
CHACANA_LINE pasa de cyan helado a dorado-ámbar del logo.
- Interior con niebla violeta-noche (u_dark_color) y rayos radiales
sutiles desde el centro, modulados por sin(t * 0.3).
- Aro doble exterior: ring fino interior + ring grueso con 4 grupos de
3 puntos cardinales (calculados angularmente, no rayos largos).
- WORLD_SCALE 1.45→1.05, MAX_TILT 35°→28° (más sólido, menos caricaturesco).
Título "GioSer" centrado dentro de la superficie de la chacana, sin
subtítulo. Se inclina junto con la chacana vía CSS perspective +
rotateX/rotateY desde u-tilt-x/y inyectadas cada frame por WASM.
Botones (4 tips):
- Reposicionados a `arm_extent * 1.32` (entre punta y aro grueso).
- Bigger: min-width 168px, glyph 54px, label Cinzel 0.95rem.
- Doble anillo en hover (::before con border + glow).
- Cuando un drawer se abre, fade-out de tips + canvas + brand.
Drawers MD (uno por elemento):
- `<aside class="drawer drawer-{element}">` con transform-origin desde
CSS vars (--origin-x/y) seteadas por WASM al click — crece desde la
posición exacta del botón hasta fullscreen en 700ms con cubic-bezier.
- Ambience por elemento: AIRE (radial drift), FUEGO (flicker keyframe),
AGUA (tide vertical), TIERRA (warm earth gradient).
- Cerrado con botón X, Escape o data-close-drawer.
- Carga MD desde ./md/{element}.md via spawn_local + Reader::open_url.
Pluma (visor MD agnóstico, dos crates nuevos):
- `crates/modules/pluma/pluma-md` — wrapper sobre pulldown-cmark 0.12.
API: to_html(), to_themed_html(md, theme) con sanitización del theme,
events() para AST stream. GFM completo. No deps web. 5 tests.
- `crates/modules/pluma/pluma-reader-web` — toma HtmlElement, expone
open_url async (fetch via wasm-bindgen-futures), render_md sync,
show_loading/show_error. NO inyecta CSS — el host estiliza
`.pluma-doc[data-pluma-theme="..."]` con sus colores.
CSS pluma-doc completo: h1/h2/h3, code/pre con border-left accent,
blockquote, tables, lists, hr gradient. Loader spinner + error state.
Placeholders en md/{aire,fuego,tierra,agua}.md con texto seed.
Workspace verde + 18 tests (6 geom + 4 palette + 3 physics + 5 pluma-md).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
133 lines
4.1 KiB
Rust
133 lines
4.1 KiB
Rust
//! Paleta visual de GioSer: cuatro elementos + cosmos.
|
|
//!
|
|
//! Los colores se exponen en RGB lineal (rango `0..=1`). Para CSS,
|
|
//! convertir con `to_srgb_hex()`. Para shaders WebGL, pasar como `vec3`.
|
|
//! La separación lineal/sRGB es deliberada: el motor blending suma luz
|
|
//! en lineal y el ojo lee sRGB.
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct Rgb(pub f32, pub f32, pub f32);
|
|
|
|
impl Rgb {
|
|
pub const fn new(r: f32, g: f32, b: f32) -> Self {
|
|
Self(r, g, b)
|
|
}
|
|
|
|
pub const fn array(self) -> [f32; 3] {
|
|
[self.0, self.1, self.2]
|
|
}
|
|
|
|
/// Hex string sRGB `#rrggbb` en bytes ASCII (7 chars).
|
|
/// Hex en bytes evita allocs al pasar a CSS desde WASM.
|
|
pub fn to_srgb_hex(self) -> [u8; 7] {
|
|
fn encode(c: f32) -> u8 {
|
|
let g = if c <= 0.003_130_8 {
|
|
12.92 * c
|
|
} else {
|
|
1.055 * c.clamp(0.0, 1.0).powf(1.0 / 2.4) - 0.055
|
|
};
|
|
(g.clamp(0.0, 1.0) * 255.0 + 0.5) as u8
|
|
}
|
|
fn nib(x: u8) -> u8 {
|
|
if x < 10 {
|
|
b'0' + x
|
|
} else {
|
|
b'a' + (x - 10)
|
|
}
|
|
}
|
|
let r = encode(self.0);
|
|
let g = encode(self.1);
|
|
let b = encode(self.2);
|
|
[
|
|
b'#',
|
|
nib(r >> 4),
|
|
nib(r & 0x0f),
|
|
nib(g >> 4),
|
|
nib(g & 0x0f),
|
|
nib(b >> 4),
|
|
nib(b & 0x0f),
|
|
]
|
|
}
|
|
|
|
pub fn lerp(self, other: Rgb, t: f32) -> Rgb {
|
|
Rgb(
|
|
self.0 + (other.0 - self.0) * t,
|
|
self.1 + (other.1 - self.1) * t,
|
|
self.2 + (other.2 - self.2) * t,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Los cuatro elementos canónicos.
|
|
pub mod elements {
|
|
use super::Rgb;
|
|
/// Aire — azul-blanco luminoso. Software, IA, aspiración.
|
|
pub const AIRE: Rgb = Rgb(0.78, 0.86, 1.00);
|
|
/// Agua — cyan profundo. Espiritualidad aplicada.
|
|
pub const AGUA: Rgb = Rgb(0.28, 0.74, 0.95);
|
|
/// Fuego — ámbar/escarlata. Inspiración.
|
|
pub const FUEGO: Rgb = Rgb(0.98, 0.45, 0.18);
|
|
/// Tierra — ocre cálido. Cuerpo.
|
|
pub const TIERRA: Rgb = Rgb(0.82, 0.55, 0.28);
|
|
|
|
pub const ALL: [(&str, Rgb); 4] = [
|
|
("aire", AIRE),
|
|
("agua", AGUA),
|
|
("fuego", FUEGO),
|
|
("tierra", TIERRA),
|
|
];
|
|
}
|
|
|
|
/// Fondo cósmico + elementos arquitectónicos.
|
|
pub mod cosmos {
|
|
use super::Rgb;
|
|
/// Vacío profundo, casi negro con tinte violeta.
|
|
pub const VOID: Rgb = Rgb(0.030, 0.025, 0.060);
|
|
/// Nebulosa interior — violeta tenue.
|
|
pub const NEBULA_A: Rgb = Rgb(0.220, 0.130, 0.380);
|
|
/// Nebulosa exterior — azul profundo.
|
|
pub const NEBULA_B: Rgb = Rgb(0.080, 0.180, 0.320);
|
|
/// Núcleo solar central — amarillo cálido, base del halo dorado.
|
|
pub const SUN_CORE: Rgb = Rgb(1.000, 0.870, 0.540);
|
|
/// Línea principal de la chacana — dorado/ámbar luminoso (color del logo).
|
|
pub const CHACANA_LINE: Rgb = Rgb(0.96, 0.74, 0.40);
|
|
/// Aro/rim cálido más profundo — ámbar tostado.
|
|
pub const CHACANA_RIM: Rgb = Rgb(0.88, 0.58, 0.28);
|
|
/// Niebla oscura del interior de la chacana — violeta-negro translúcido.
|
|
pub const CHACANA_DARK: Rgb = Rgb(0.04, 0.03, 0.10);
|
|
/// Polvo de estrellas.
|
|
pub const STARDUST: Rgb = Rgb(0.85, 0.88, 1.00);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn hex_white() {
|
|
assert_eq!(&Rgb(1.0, 1.0, 1.0).to_srgb_hex(), b"#ffffff");
|
|
}
|
|
|
|
#[test]
|
|
fn hex_black() {
|
|
assert_eq!(&Rgb(0.0, 0.0, 0.0).to_srgb_hex(), b"#000000");
|
|
}
|
|
|
|
#[test]
|
|
fn lerp_midpoint() {
|
|
let m = Rgb(0.0, 0.0, 0.0).lerp(Rgb(1.0, 0.5, 0.0), 0.5);
|
|
assert_eq!(m, Rgb(0.5, 0.25, 0.0));
|
|
}
|
|
|
|
#[test]
|
|
fn linear_to_srgb_midgray_lifts_brightness() {
|
|
// 0.5 lineal codifica a sRGB ≈ 0.735 → byte ≈ 187 (0xbb..0xbc segun redondeo de powf).
|
|
// Bandgap [0xb8, 0xbf] permite drift de implementaciones de powf entre plataformas.
|
|
let h = Rgb(0.5, 0.5, 0.5).to_srgb_hex();
|
|
let lo = b"#b8b8b8";
|
|
let hi = b"#bfbfbf";
|
|
assert!(h.as_slice() >= lo.as_slice() && h.as_slice() <= hi.as_slice(),
|
|
"got {:?}", core::str::from_utf8(&h).unwrap_or(""));
|
|
}
|
|
}
|