feat(cosmobiologia-render): compose_wheel rico con palette + dial 3D + spread + coord labels

El render agnóstico ya no es un esqueleto — porta al WASM la mayoría
de los detalles visuales que tenía solo el canvas gpui nativo:

- palette.rs: Palette dark/light replicando AstroPalette del theme
  nativo, pero en Rgba (no Hsla de gpui). Métodos planet/aspect/sign
  para resolver color por id simbólico, + house_ring con hue-shift.
- CompositionOpts extendido: palette, dial_3d, draw_ascensional_cross,
  show_coord_labels, show_minor_aspects. Defaults razonables.
- compose_wheel ahora dibuja: background panel, dial 3D bevel (4
  strokes concéntricos con alpha decreciente), subdivisiones cada 10°
  con sign boundaries reforzados, signos con color elemental, casas
  topocéntricas + geocéntricas en sus rings canónicos, cuerpos con
  spread anti-solapamiento + clusters + disco coloreado por planeta,
  coord labels "DD°MM'" en natal, aspectos con width inversa al
  orbe + filtrado opcional de minors, cruz ascensional dashed +
  pills ASC/MC/DESC/IC.
- cosmobiologia-web: nuevo render_model_to_svg_themed(dark: bool)
  para que el cliente JS elija palette según preferencia del UA.

Tests del módulo math siguen verdes (10/10). Smoke test del server:
/api/sky.svg ahora emite 22 circles, 77 lines, 52 texts con paleta
real (vs ~6 circles, 24 lines, 36 texts del esqueleto previo).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-19 01:41:36 +00:00
parent 4619ba3a2b
commit 86fb6ae20b
4 changed files with 562 additions and 117 deletions
@@ -34,7 +34,7 @@
#[cfg(target_arch = "wasm32")]
mod wasm {
use cosmobiologia_render::{
compose_wheel, draw_commands_to_svg, CompositionOpts, RenderModel,
compose_wheel, draw_commands_to_svg, CompositionOpts, Palette, RenderModel,
};
use wasm_bindgen::prelude::*;
@@ -44,13 +44,39 @@ mod wasm {
/// `size` es el lado del cuadrado contenedor en px (default 600).
/// `rot_offset_deg` permite rotar la vista (jog-dial / preview).
#[wasm_bindgen]
pub fn render_model_to_svg(json: &str, size: f32, rot_offset_deg: f32) -> Result<String, JsValue> {
pub fn render_model_to_svg(
json: &str,
size: f32,
rot_offset_deg: f32,
) -> Result<String, JsValue> {
render_with_opts(json, size, rot_offset_deg, true)
}
/// Variante con palette explícita (dark = `true` por default, light
/// = `false`). El JS pasa el modo según preferencia/tema del UA.
#[wasm_bindgen]
pub fn render_model_to_svg_themed(
json: &str,
size: f32,
rot_offset_deg: f32,
dark: bool,
) -> Result<String, JsValue> {
render_with_opts(json, size, rot_offset_deg, dark)
}
fn render_with_opts(
json: &str,
size: f32,
rot_offset_deg: f32,
dark: bool,
) -> Result<String, JsValue> {
let model: RenderModel = serde_json::from_str(json)
.map_err(|e| JsValue::from_str(&format!("parse RenderModel: {}", e)))?;
let opts = CompositionOpts {
size: if size > 0.0 { size } else { 600.0 },
rot_offset_deg,
include_bodies: true,
palette: if dark { Palette::dark() } else { Palette::light() },
..Default::default()
};
let cmds = compose_wheel(&model, &opts);
Ok(draw_commands_to_svg(&cmds, opts.size))