tawasuyu

tawasuyu/brahman

Rust 332TOML 127JSON 17Markdown 154710 Definiciones

feat(tahuantinsuyu): rueda 3D, hover-highlight, universo, themes papel

Segunda tanda de UX a partir de feedback de uso real:

- Zoom/pan reasignados: wheel = zoom puro (sin modifier). LMB drag
  fuera del anillo de signos = pan; sobre el anillo = jog-dial
  (rectificación). MMB sigue como pan secundario. Tecla `0`
  resetea zoom + pan.

- Planetas legibles: el "dot rellenado" se reduce a 3 px (solo
  marca el grado exacto). Encima va `planet_glyph` con disco-halo
  del bg_panel y border del color del planeta — el glyph unicode
  astronómico (☉☽☿♀♂♃♄♅♆♇) ahora se lee contra cualquier fondo.

- Aspectos hover-highlight: al hovear un planeta, sus líneas se
  mantienen al 100 % y el resto cae a 18 %. Resuelve el "¿quién
  contra quién?" sin desordenar la rueda.

- Ascensionales: cruz completa ASC-DESC + MC-IC (4 radios) con
  α=0.55. Labels ASC/MC/DESC/IC como pills con bg-halo y border
  `angle_highlight`, font 11 — antes eran texto chico que se
  fundía con el dial.

- Universo: el wheel pierde su bg de cuadrado (que cortaba contra
  el panel). El root del canvas pinta un starfield sutil ~130
  puntos deterministas (xorshift32 con seed fija, sin parpadeo
  entre frames). Solo activo en themes dark — sobre fondos claros
  generaría ruido.

- Estilo 3D anillos: `stroke_circle_3d` (highlight +luma + base +
  shadow -luma) reemplaza al stroke plano en sign_outer, sign_inner
  y el outer ring. Más `paint_dial_bevel` con 10 strokes finos en
  bell curve entre sign_inner y sign_outer — simula gradient radial
  que gpui canvas no soporta nativo.

- Theme `Print Color`: papel crema, paleta astro con luminancia
  0.26-0.34 y saturación alta, sin glow ni gradients.

- Theme `Print B&W`: monocromático sobre blanco puro. Aspectos
  diferenciados por dash pattern en lugar de color:
  conjunction/opposition sólidos, square dash medio, trine dash
  largo, sextile dotted, minors dotted finísimo. `paint_segment`
  con `dash: Option<(on,off)>` para implementar dashes (gpui
  canvas no tiene stroke dash nativo).

Todos los tests siguen verdes (6 shell + 5 yahweh-theme +
2 tahuantinsuyu-theme).

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

1078e433f24ac83e2540343e82d2bd0fe49e304a · por sergio · (hace 5 meses) · padre e09207b15 · ver el árbol en este commit

Delta semántico · 23

crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs

crates/modules/tahuantinsuyu/tahuantinsuyu-theme/src/lib.rs

crates/modules/ui_engine/libs/theme/src/lib.rs

3 archivos · +614 −141

diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs
index 68deb84..d3466b9 100644
--- a/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs
+++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs
@@ -37,7 +37,7 @@ use gpui::{
     Bounds, Context, EventEmitter, FocusHandle, Focusable, Hsla, IntoElement,
     KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement,
     PathBuilder, Pixels, Point, Render, ScrollDelta, ScrollWheelEvent, SharedString, Styled,
-    Window, canvas, div, hsla, linear_color_stop, linear_gradient, point, prelude::*, px,
+    Window, canvas, div, hsla, point, prelude::*, px,
 };
 
 use tahuantinsuyu_engine::{Geometry, Layer, LayerKind, OUTER_RING_MODULES, RenderModel};
@@ -324,6 +324,7 @@ impl AstrologyCanvas {
         cx.notify();
     }
 
+    #[allow(dead_code)]
     fn pan_by(&mut self, dx: f32, dy: f32, cx: &mut Context<'_, Self>) {
         if dx == 0.0 && dy == 0.0 {
             return;
@@ -335,11 +336,14 @@ impl AstrologyCanvas {
 
     // ----- Internos: handlers de jog-dial -----
 
-    fn on_jog_down(
+    /// Despacha el LMB down entre jog-dial (sobre el anillo de signos)
+    /// y pan (cualquier otra parte del canvas). El jog-dial es el
+    /// control de rectificación de hora; el pan es navegación libre.
+    fn on_primary_down(
         &mut self,
         position: Point<Pixels>,
         bounds: Bounds<Pixels>,
-        _cx: &mut Context<'_, Self>,
+        cx: &mut Context<'_, Self>,
     ) {
         let (cx_px, cy_px) = bounds_center(bounds);
         let mx: f32 = position.x.into();
@@ -347,20 +351,18 @@ impl AstrologyCanvas {
         let dx = mx - cx_px;
         let dy = my - cy_px;
         let dist = (dx * dx + dy * dy).sqrt();
-        // r_outer se deriva del width actual del canvas (que ya
-        // incorpora view_scale), no del WHEEL_SIZE constante. Sin esto,
-        // el jog-dial dejaría de funcionar al hacer zoom.
         let r_outer = effective_r_outer(bounds);
         let radii = Radii::from_outer(r_outer);
-        // Aro de captura un poco más generoso que el anillo del dial.
-        if dist < radii.sign_inner * 0.95 || dist > radii.sign_outer * 1.10 {
-            return;
+        let on_dial = dist >= radii.sign_inner * 0.95 && dist <= radii.sign_outer * 1.10;
+        if on_dial {
+            let angle = dy.atan2(dx).to_degrees();
+            self.state.drag_jog = Some(JogDragState {
+                last_screen_angle_deg: angle,
+                accumulated_delta_deg: 0.0,
+            });
+        } else {
+            self.on_pan_down(position, cx);
         }
-        let angle = dy.atan2(dx).to_degrees();
-        self.state.drag_jog = Some(JogDragState {
-            last_screen_angle_deg: angle,
-            accumulated_delta_deg: 0.0,
-        });
     }
 
     fn on_jog_move(
@@ -600,22 +602,14 @@ impl AstrologyCanvas {
         _w: &mut Window,
         cx: &mut Context<'_, Self>,
     ) {
-        let (dx_px, dy_px) = match event.delta {
+        let (_dx_px, dy_px) = match event.delta {
             ScrollDelta::Pixels(p) => (f32::from(p.x), f32::from(p.y)),
             ScrollDelta::Lines(p) => (p.x * 16.0, p.y * 16.0),
         };
-        // Ctrl + wheel = zoom. wheel solo = pan (contenido sigue al
-        // dedo). El criterio de "modifier" usa el control flag estándar
-        // de gpui (en macOS sería cmd; aceptamos ambos como zoom).
-        let zoom_mod = event.modifiers.control || event.modifiers.platform;
-        if zoom_mod {
-            // Sensibilidad: 100px de scroll ≈ ±20% zoom. exp es suave y
-            // simétrico contra dy negativo (zoom out).
-            let factor = (dy_px * 0.002).exp();
-            self.zoom_by(factor, cx);
-        } else {
-            self.pan_by(dx_px, dy_px, cx);
-        }
+        // Wheel = zoom puro, sin modifier. Pan se hace con drag (LMB
+        // fuera del anillo, o MMB). 100px de scroll ≈ ±20% zoom.
+        let factor = (dy_px * 0.002).exp();
+        self.zoom_by(factor, cx);
     }
 
     fn on_jog_up(&mut self, cx: &mut Context<'_, Self>) {
@@ -673,6 +667,58 @@ impl AstrologyCanvas {
 const WHEEL_SIZE: f32 = 580.0;
 const WHEEL_MARGIN: f32 = 28.0;
 
+/// Pinta un starfield sutil sobre el background del panel del canvas.
+/// Posiciones generadas con xorshift32 + seed const → idénticas entre
+/// frames (no parpadea). Las estrellas viven solo cuando el theme es
+/// dark — sobre fondos claros (impresora / solarized) un punteado de
+/// puntos quedaría como ruido visual y NO suma al sentido "papel".
+fn paint_starfield(bounds: Bounds<Pixels>, window: &mut Window, theme: &Theme) {
+    if !theme.is_dark {
+        return;
+    }
+    let ox: f32 = bounds.origin.x.into();
+    let oy: f32 = bounds.origin.y.into();
+    let bw: f32 = bounds.size.width.into();
+    let bh: f32 = bounds.size.height.into();
+    if bw <= 0.0 || bh <= 0.0 {
+        return;
+    }
+
+    // Densidad: ~1 estrella por 4800 px² → unas 130 estrellas en
+    // 800×800, escala con el panel.
+    let count = ((bw * bh) / 4800.0).clamp(40.0, 320.0) as u32;
+
+    let mut state: u32 = 0x1f3a_5b7d;
+    let mut next = || -> u32 {
+        // xorshift32 — barato y determinístico.
+        state ^= state << 13;
+        state ^= state >> 17;
+        state ^= state << 5;
+        state
+    };
+
+    let star_color = hsla(220.0 / 360.0, 0.20, 0.92, 1.0);
+    for _ in 0..count {
+        let rx = (next() as f32) / (u32::MAX as f32);
+        let ry = (next() as f32) / (u32::MAX as f32);
+        let ra = (next() as f32) / (u32::MAX as f32);
+        let rs = (next() as f32) / (u32::MAX as f32);
+        let x = ox + rx * bw;
+        let y = oy + ry * bh;
+        // Distribución de tamaños: la mayoría 0.6-1.0px ("polvo"), un
+        // 15% un poco más grandes (1.4-2.2px) que actúan como
+        // "estrellas brillantes".
+        let r = if rs > 0.85 {
+            1.4 + rs * 0.8
+        } else {
+            0.6 + rs * 0.4
+        };
+        // Alpha entre 0.10 y 0.55 — sutil, nunca compite con la rueda.
+        let a = 0.10 + ra * 0.45;
+        fill_circle(window, x, y, r, with_alpha(star_color, a));
+    }
+}
+
 fn bounds_center(bounds: Bounds<Pixels>) -> (f32, f32) {
     let ox: f32 = bounds.origin.x.into();
     let oy: f32 = bounds.origin.y.into();
@@ -721,6 +767,19 @@ impl Render for AstrologyCanvas {
             CanvasMode::Thumbnails { items, .. } => render_thumbnails(&theme, items),
         };
 
+        // Starfield: capa absoluta detrás del body, ocupa todo el
+        // canvas. Pinta ~140 puntos pequeños semi-transparentes en
+        // posiciones deterministas (PRNG con seed const) — sin
+        // parpadeo entre frames. Sutil; aporta el "universo" sin
+        // competir con la rueda.
+        let theme_for_stars = theme.clone();
+        let starfield = canvas(
+            |_b, _w, _cx| (),
+            move |bounds, _, window, _| paint_starfield(bounds, window, &theme_for_stars),
+        )
+        .absolute()
+        .size_full();
+
         div()
             .id("astrology-canvas-root")
             .track_focus(&focus)
@@ -735,12 +794,18 @@ impl Render for AstrologyCanvas {
             .on_scroll_wheel(cx.listener(Self::on_scroll))
             .size_full()
             .bg(theme.bg_panel.clone())
-            .flex()
-            .flex_col()
-            .items_center()
-            .justify_center()
+            .relative()
             .overflow_hidden()
-            .child(body)
+            .child(starfield)
+            .child(
+                div()
+                    .size_full()
+                    .flex()
+                    .flex_col()
+                    .items_center()
+                    .justify_center()
+                    .child(body),
+            )
     }
 }
 
@@ -843,6 +908,12 @@ fn render_wheel(
     let mc_for_paint = render.midheaven_deg;
     let visibility_for_paint = visible.clone();
     let entity_for_canvas = entity.clone();
+    // Hover focus para el highlight de aspectos — solo cuando el hover
+    // es un Body (sobre un planeta), no sobre cusps ni aspectos.
+    let hover_focus_paint: Option<String> = match hover {
+        Some(HoverInfo::Body { symbol, .. }) => Some(symbol.clone()),
+        _ => None,
+    };
     let canvas_element = canvas(
         move |_b: Bounds<Pixels>, _w, _cx| (),
         move |bounds: Bounds<Pixels>, _, window, _| {
@@ -858,12 +929,13 @@ fn render_wheel(
                 rot_offset,
                 radii,
                 &visibility_for_paint,
+                hover_focus_paint.as_deref(),
             );
 
             // Handlers de mouse — se registran cada frame contra el
-            // window; GPUI los reemplaza al re-renderear. Jog-dial (LMB
-            // sobre el anillo de signos) y pan (MMB en cualquier parte
-            // del canvas) coexisten porque consumen botones distintos.
+            // window; GPUI los reemplaza al re-renderear. LMB despacha
+            // entre jog-dial (sobre el anillo) y pan (afuera). MMB es
+            // pan secundario para usuarios con scroll-mouse.
             let entity_d = entity_for_canvas.clone();
             window.on_mouse_event(move |ev: &MouseDownEvent, _, _w, cx| {
                 if !bounds.contains(&ev.position) {
@@ -871,8 +943,9 @@ fn render_wheel(
                 }
                 match ev.button {
                     MouseButton::Left => {
-                        entity_d
-                            .update(cx, |this, cx| this.on_jog_down(ev.position, bounds, cx));
+                        entity_d.update(cx, |this, cx| {
+                            this.on_primary_down(ev.position, bounds, cx)
+                        });
                     }
                     MouseButton::Middle => {
                         entity_d.update(cx, |this, cx| this.on_pan_down(ev.position, cx));
@@ -915,33 +988,17 @@ fn render_wheel(
     .w(px(wheel_size))
     .h(px(wheel_size));
 
-    // Gradient sutil diagonal en el fondo del wheel — toque "místico
-    // velado". En dark la alpha es muy baja (el fondo del panel ya es
-    // oscuro, no hace falta tinte fuerte). En light el panel es claro,
-    // así que necesitamos alphas mayores para que el gradient se vea
-    // como un fondo "papel teñido" y no se borre contra blanco.
-    let (a0, a1) = if theme.is_dark {
-        (0.06, 0.03)
-    } else {
-        (0.18, 0.10)
-    };
-    let wheel_bg = linear_gradient(
-        155.0,
-        linear_color_stop(with_alpha(palette.dial_ring, a0), 0.0),
-        linear_color_stop(with_alpha(palette.angle_highlight, a1), 1.0),
-    );
-
+    // El wheel ya no tiene bg propio — antes era un cuadrado con
+    // gradient que cortaba contra el fondo del panel; ahora el panel
+    // (con su starfield encima en `render`) fluye continuo a través
+    // del área del wheel, dando el efecto de "rueda flotando en el
+    // universo" en lugar de "rueda sobre placa cuadrada".
     let mut wheel = div()
         .relative()
         .w(px(wheel_size))
         .h(px(wheel_size))
-        // El parent del canvas centra con flex; aplicamos el pan como
-        // margin shift desde ese centrado natural. Positivo = a la
-        // derecha / abajo; negativo desplaza al lado opuesto.
         .ml(px(view_pan_x))
         .mt(px(view_pan_y))
-        .bg(wheel_bg)
-        .rounded(px(12.0))
         .child(canvas_element);
 
     // Sign glyphs.
@@ -987,17 +1044,19 @@ fn render_wheel(
         }
     }
 
-    // Planet glyphs: natal (en `bodies`) + overlays en sus rings
-    // (progression, solar_arc) con alpha + tamaño más chico para
-    // diferenciarse visualmente del natal.
+    // Planet glyphs: natal en `bodies` + overlays (progression,
+    // solar_arc) en sus rings, ambos con disco-halo para legibilidad
+    // contra cualquier fondo. El natal lleva un tamaño un poco mayor
+    // que los overlays para que se lea como "el cuerpo principal".
+    let halo_bg = glyph_halo(theme);
     if visible.get(&LayerKind::Bodies).copied().unwrap_or(true) {
         for layer in &render.layers {
             if matches!(layer.kind, LayerKind::Bodies) {
                 let is_natal = layer.module_id == "natal";
                 let ring = radii.body_ring(&layer.module_id);
-                let alpha = if is_natal { 1.0 } else { 0.85 };
+                let alpha = if is_natal { 1.0 } else { 0.88 };
                 let font_size = if is_natal { 18.0 } else { 14.0 };
-                let box_size = if is_natal { 24.0 } else { 20.0 };
+                let disk_size = if is_natal { 26.0 } else { 22.0 };
                 for g in &layer.glyphs {
                     let (x, y) = polar_to_screen(g.deg, asc, rot_offset, ring);
                     let color = with_alpha(planet_color(palette, &g.symbol), alpha);
@@ -1008,21 +1067,24 @@ fn render_wheel(
                     if let Some(marker) = &g.dignity_marker {
                         glyph_text.push_str(marker);
                     }
-                    wheel = wheel.child(centered_glyph(
+                    wheel = wheel.child(planet_glyph(
                         cx_center + x,
                         cy_center + y,
-                        box_size,
+                        disk_size,
                         font_size,
                         glyph_text.into(),
                         color,
+                        halo_bg,
+                        with_alpha(color, 0.85),
                     ));
                 }
             }
         }
     }
 
-    // Planet glyphs en el outer ring — transit o synastry, los dos
-    // comparten ese slot (mutuamente excluyentes a nivel de Shell).
+    // Planet glyphs en el outer ring — transit o synastry (slot
+    // compartido, mutuamente excluyentes a nivel de Shell). Disco un
+    // poco más chico que el natal — el outer es "secundario".
     if visible.get(&LayerKind::Outer).copied().unwrap_or(true) {
         for layer in &render.layers {
             if matches!(layer.kind, LayerKind::Outer)
@@ -1030,19 +1092,21 @@ fn render_wheel(
             {
                 for g in &layer.glyphs {
                     let (x, y) = polar_to_screen(g.deg, asc, rot_offset, radii.transits);
-                    let color = with_alpha(planet_color(palette, &g.symbol), 0.9);
+                    let color = with_alpha(planet_color(palette, &g.symbol), 0.92);
                     let glyph_text = if g.retrograde {
                         format!("{}ᴿ", planet_unicode(&g.symbol))
                     } else {
                         planet_unicode(&g.symbol).into()
                     };
-                    wheel = wheel.child(centered_glyph(
+                    wheel = wheel.child(planet_glyph(
                         cx_center + x,
                         cy_center + y,
                         20.0,
-                        14.0,
+                        13.0,
                         glyph_text.into(),
                         color,
+                        halo_bg,
+                        with_alpha(color, 0.75),
                     ));
                 }
             }
@@ -1147,27 +1211,39 @@ fn render_wheel(
         );
     }
 
-    // Labels ASC/MC/DESC/IC en el perímetro. Texto pequeño en el
-    // margen exterior (radius * 1.05) para que no se monte con los
-    // glifos de los signos. Color angle_highlight para que el ojo los
-    // reconozca como los cuatro ángulos cardinales.
+    // Labels ASC/MC/DESC/IC como pills en el perímetro — bg del halo
+    // + border y texto en `angle_highlight`. Más legibles que el
+    // centered_glyph plano del fase anterior, en especial sobre
+    // fondos claros donde el ámbar/oro de angle_highlight se diluye.
     let angle_labels = [
         (asc, "ASC"),
         (render.midheaven_deg, "MC"),
         (render.descendant_deg, "DESC"),
         (render.imum_coeli_deg, "IC"),
     ];
-    let label_r = r_outer * 1.06;
+    let label_r = r_outer * 1.08;
     for (deg, label) in angle_labels {
         let (x, y) = polar_to_screen(deg, asc, rot_offset, label_r);
-        wheel = wheel.child(centered_glyph(
-            cx_center + x,
-            cy_center + y,
-            32.0,
-            10.0,
-            label.into(),
-            palette.angle_highlight,
-        ));
+        let pill_w = if label.len() > 2 { 38.0 } else { 30.0 };
+        let pill_h = 18.0;
+        wheel = wheel.child(
+            div()
+                .absolute()
+                .left(px(cx_center + x - pill_w / 2.0))
+                .top(px(cy_center + y - pill_h / 2.0))
+                .w(px(pill_w))
+                .h(px(pill_h))
+                .flex()
+                .items_center()
+                .justify_center()
+                .rounded(px(9.0))
+                .bg(halo_bg)
+                .border_1()
+                .border_color(with_alpha(palette.angle_highlight, 0.85))
+                .text_size(px(11.0))
+                .text_color(palette.angle_highlight)
+                .child(SharedString::from(label)),
+        );
     }
 
     // --- Header + footer + indicador de tiempo ---
@@ -1487,6 +1563,10 @@ impl Radii {
 }
 
 #[allow(clippy::too_many_arguments)]
+// `hover_focus`: symbol del planeta hovereado en este frame (si lo
+// hay). Las líneas de aspecto que NO tocan a ese planeta se opacan
+// para que el usuario lea claramente "qué afecta a qué". Si `None`,
+// todas las líneas se pintan a alpha plena.
 fn paint_wheel(
     bounds: Bounds<Pixels>,
     window: &mut Window,
@@ -1498,18 +1578,23 @@ fn paint_wheel(
     rot_offset_deg: f32,
     radii: Radii,
     visibility: &HashMap<LayerKind, bool>,
+    hover_focus: Option<&str>,
 ) {
     let (cx, cy) = bounds_center(bounds);
-    let _ = theme;
     let show = |k: LayerKind| visibility.get(&k).copied().unwrap_or(true);
 
     // 1. Sectores zodiacales (parte del SignDial layer).
     if show(LayerKind::SignDial) {
         paint_sign_sectors(window, cx, cy, &radii, palette, ascendant_deg, rot_offset_deg);
 
-        // Anillos del dial.
-        stroke_circle(window, cx, cy, radii.sign_outer, 1.5, palette.dial_ring);
-        stroke_circle(window, cx, cy, radii.sign_inner, 1.0, palette.dial_ring);
+        // Anillos del dial con efecto 3D: highlight interior + base +
+        // shadow exterior. El highlight es 1 px hacia el centro con
+        // luminancia +0.18; la shadow 1 px hacia afuera con -0.18.
+        // El bevel central — varios strokes finos con alpha en bell
+        // curve entre sign_inner y sign_outer — da volumen al dial.
+        stroke_circle_3d(window, cx, cy, radii.sign_outer, 1.5, palette.dial_ring, theme);
+        stroke_circle_3d(window, cx, cy, radii.sign_inner, 1.0, palette.dial_ring, theme);
+        paint_dial_bevel(window, cx, cy, &radii, palette, theme);
 
         // Cusps zodiacales cada 30°.
         for i in 0..12 {
@@ -1569,45 +1654,70 @@ fn paint_wheel(
             }
         }
 
-        // Asc + MC extendidos hasta el centro con opacidad sutil.
-        paint_radial_line(
-            window,
-            cx,
-            cy,
+        // Cruz completa Asc-Desc + MC-IC, alpha bastante visible para
+        // que orienten la lectura sin competir con cuerpos/aspectos.
+        // 4 radios desde el centro: ASC, DESC (=asc+180), MC, IC
+        // (=mc+180). `paint_radial_line` con r_inner=0 pinta un radio
+        // del centro al borde — la cruz es la unión de los 4.
+        let axis_color = with_alpha(palette.angle_highlight, 0.55);
+        for axis_deg in [
             ascendant_deg,
-            ascendant_deg,
-            rot_offset_deg,
-            0.0,
-            radii.houses_outer,
-            with_alpha(palette.angle_highlight, 0.35),
-            1.0,
-        );
-        paint_radial_line(
-            window,
-            cx,
-            cy,
+            ascendant_deg + 180.0,
             midheaven_deg,
-            ascendant_deg,
-            rot_offset_deg,
-            0.0,
-            radii.houses_outer,
-            with_alpha(palette.angle_highlight, 0.35),
-            1.0,
-        );
+            midheaven_deg + 180.0,
+        ] {
+            paint_radial_line(
+                window,
+                cx,
+                cy,
+                axis_deg,
+                ascendant_deg,
+                rot_offset_deg,
+                0.0,
+                radii.houses_outer,
+                axis_color,
+                1.4,
+            );
+        }
     }
 
     // 3. Aspectos. Cada module_id usa su par de radios — natal-natal
     // ambos en `aspects`, cross con transit en `bodies → transits`,
     // cross con progression en `bodies → progression`.
     if show(LayerKind::Aspects) {
+        let mono = palette.is_monochrome();
         for layer in layers {
             if matches!(layer.kind, LayerKind::Aspects) {
                 if let Geometry::Lines(segs) = &layer.geometry {
                     let (r_from, r_to) = radii.aspect_endpoints(&layer.module_id);
                     let is_cross = r_from != r_to;
                     for seg in segs {
-                        let color = aspect_color(palette, &seg.kind);
-                        let color = with_alpha(color, color.a * seg.opacity);
+                        let base = aspect_color(palette, &seg.kind);
+                        let base = with_alpha(base, base.a * seg.opacity);
+                        // Hover focus: si hay un planeta hovereado y
+                        // este segmento NO lo toca, lo atenuamos al
+                        // 18%; si lo toca o no hay hover, va pleno.
+                        let touches_hover = hover_focus
+                            .map(|sym| seg.from_body == sym || seg.to_body == sym)
+                            .unwrap_or(true);
+                        let factor = if touches_hover { 1.0 } else { 0.18 };
+                        let color = with_alpha(base, base.a * factor);
+                        let dash = if mono {
+                            dash_pattern_for_kind(&seg.kind)
+                        } else {
+                            None
+                        };
+                        // En BW las "fuertes" (conjunction/opposition/
+                        // square) van un poco más gruesas para sumar
+                        // diferenciación al dash.
+                        let width = if mono {
+                            match seg.kind.as_str() {
+                                "conjunction" | "opposition" | "square" => 1.3,
+                                _ => 1.0,
+                            }
+                        } else {
+                            1.0
+                        };
                         if is_cross {
                             paint_cross_aspect_line(
                                 window,
@@ -1620,6 +1730,7 @@ fn paint_wheel(
                                 r_from,
                                 r_to,
                                 color,
+                                dash,
                             );
                         } else {
                             paint_aspect_line(
@@ -1632,6 +1743,8 @@ fn paint_wheel(
                                 rot_offset_deg,
                                 r_from,
                                 color,
+                                dash,
+                                width,
                             );
                         }
                     }
@@ -1640,11 +1753,13 @@ fn paint_wheel(
         }
     }
 
-    // 4. Dots de cuerpos: natal en `bodies`, overlays en sus rings
-    // específicos (progression, solar_arc). Las luminarias natales
-    // (Sol/Luna) llevan glow halo — invita la mística sin saturar.
+    // 4. Marcadores de posición exacta. Antes el dot era "el planeta";
+    // ahora el glyph (con halo, en DOM) lo es. El círculo acá queda
+    // como marker de precisión angular — chico, alpha alta, sobre el
+    // anillo correspondiente. Glow se mantiene para Sol/Luna como
+    // toque místico, pero también reducido.
     if show(LayerKind::Bodies) {
-        let dot_r = (radii.sign_outer * 0.018).max(2.0);
+        let dot_r = (radii.sign_outer * 0.009).max(1.5);
         for layer in layers {
             if matches!(layer.kind, LayerKind::Bodies) {
                 let ring = radii.body_ring(&layer.module_id);
@@ -1654,7 +1769,7 @@ fn paint_wheel(
                     let color = with_alpha(planet_color(palette, &g.symbol), alpha);
                     let (x, y) = polar_to_screen(g.deg, ascendant_deg, rot_offset_deg, ring);
                     if is_natal && (g.symbol == "sun" || g.symbol == "moon") {
-                        paint_glow(window, cx + x, cy + y, dot_r, color);
+                        paint_glow(window, cx + x, cy + y, dot_r * 1.8, color);
                     }
                     fill_circle(window, cx + x, cy + y, dot_r, color);
                 }
@@ -1699,24 +1814,27 @@ fn paint_wheel(
             && OUTER_RING_MODULES.contains(&l.module_id.as_str())
     });
     if outer_active && show(LayerKind::Outer) {
-        stroke_circle(
+        let band = radii.sign_outer * 0.035;
+        stroke_circle_3d(
             window,
             cx,
             cy,
-            radii.transits + radii.sign_outer * 0.035,
-            0.6,
-            with_alpha(palette.dial_ring, 0.4),
+            radii.transits + band,
+            0.7,
+            with_alpha(palette.dial_ring, 0.55),
+            theme,
         );
-        stroke_circle(
+        stroke_circle_3d(
             window,
             cx,
             cy,
-            radii.transits - radii.sign_outer * 0.035,
-            0.6,
-            with_alpha(palette.dial_ring, 0.4),
+            radii.transits - band,
+            0.7,
+            with_alpha(palette.dial_ring, 0.55),
+            theme,
         );
 
-        let dot_r = (radii.sign_outer * 0.017).max(2.0);
+        let dot_r = (radii.sign_outer * 0.008).max(1.5);
         for layer in layers {
             if matches!(layer.kind, LayerKind::Outer)
                 && (OUTER_RING_MODULES.contains(&layer.module_id.as_str()))
@@ -1853,15 +1971,12 @@ fn paint_aspect_line(
     rot_offset_deg: f32,
     r: f32,
     color: Hsla,
+    dash: Option<(f32, f32)>,
+    width: f32,
 ) {
     let (xa, ya) = polar_to_screen(a_deg, ascendant_deg, rot_offset_deg, r);
     let (xb, yb) = polar_to_screen(b_deg, ascendant_deg, rot_offset_deg, r);
-    let mut builder = PathBuilder::stroke(px(1.0));
-    builder.move_to(point(px(cx + xa), px(cy + ya)));
-    builder.line_to(point(px(cx + xb), px(cy + yb)));
-    if let Ok(path) = builder.build() {
-        window.paint_path(path, color);
-    }
+    paint_segment(window, cx + xa, cy + ya, cx + xb, cy + yb, color, dash, width);
 }
 
 /// Línea de aspecto natal ↔ tránsito: extremos en radios distintos.
@@ -1880,14 +1995,82 @@ fn paint_cross_aspect_line(
     r_from: f32,
     r_to: f32,
     color: Hsla,
+    dash: Option<(f32, f32)>,
 ) {
     let (xa, ya) = polar_to_screen(natal_deg, ascendant_deg, rot_offset_deg, r_from);
     let (xb, yb) = polar_to_screen(transit_deg, ascendant_deg, rot_offset_deg, r_to);
-    let mut builder = PathBuilder::stroke(px(0.7));
-    builder.move_to(point(px(cx + xa), px(cy + ya)));
-    builder.line_to(point(px(cx + xb), px(cy + yb)));
-    if let Ok(path) = builder.build() {
-        window.paint_path(path, color);
+    paint_segment(window, cx + xa, cy + ya, cx + xb, cy + yb, color, dash, 0.7);
+}
+
+/// Pinta un segmento entre dos puntos. Si `dash` es `Some((on, off))`,
+/// itera el vector pintando trechos de `on` px con gaps de `off` px.
+/// Si `None`, una sola línea continua. Usado por todos los aspect
+/// painters — el dash pattern es la forma de distinguir kinds en
+/// el theme BW (donde el color no sirve).
+fn paint_segment(
+    window: &mut Window,
+    x0: f32,
+    y0: f32,
+    x1: f32,
+    y1: f32,
+    color: Hsla,
+    dash: Option<(f32, f32)>,
+    width: f32,
+) {
+    let Some((on, off)) = dash else {
+        let mut b = PathBuilder::stroke(px(width));
+        b.move_to(point(px(x0), px(y0)));
+        b.line_to(point(px(x1), px(y1)));
+        if let Ok(p) = b.build() {
+            window.paint_path(p, color);
+        }
+        return;
+    };
+    let dx = x1 - x0;
+    let dy = y1 - y0;
+    let len = (dx * dx + dy * dy).sqrt();
+    if len < 0.1 {
+        return;
+    }
+    let ux = dx / len;
+    let uy = dy / len;
+    let step = on + off;
+    if step < 0.1 {
+        return;
+    }
+    let mut t = 0.0;
+    while t < len {
+        let t_end = (t + on).min(len);
+        let sx = x0 + ux * t;
+        let sy = y0 + uy * t;
+        let ex = x0 + ux * t_end;
+        let ey = y0 + uy * t_end;
+        let mut b = PathBuilder::stroke(px(width));
+        b.move_to(point(px(sx), px(sy)));
+        b.line_to(point(px(ex), px(ey)));
+        if let Ok(p) = b.build() {
+            window.paint_path(p, color);
+        }
+        t += step;
+    }
+}
+
+/// Dash pattern por aspecto, para modo monocromático. En modo color
+/// el caller pasa `None` y las líneas van sólidas. Patterns elegidos
+/// para que cada kind sea distinguible a ojo:
+/// - conjunction/opposition: sólido (más peso visual, son los
+///   aspectos "fuertes")
+/// - square: dash medio (4 on / 3 off)
+/// - trine: dash largo (8 on / 2 off) — casi sólido pero distinguible
+/// - sextile: dotted (1.5 on / 3 off)
+/// - minor: dotted finísimo (1 on / 4 off)
+fn dash_pattern_for_kind(kind: &str) -> Option<(f32, f32)> {
+    match kind {
+        "conjunction" | "opposition" => None,
+        "square" => Some((4.0, 3.0)),
+        "trine" => Some((8.0, 2.0)),
+        "sextile" => Some((1.5, 3.0)),
+        _ => Some((1.0, 4.0)),
     }
 }
 
@@ -1948,10 +2131,115 @@ fn centered_glyph(
         .child(text)
 }
 
+/// Glyph de planeta con disco-halo detrás del char. El disco viene en
+/// `disk_bg` (semi-opaco para que se vea a través el fondo del wheel)
+/// y `disk_border` (típicamente el color del planeta). El char por
+/// dentro va en `text_color` — recomendado el color del planeta sobre
+/// disco neutro, o color contrastante sobre disco coloreado.
+fn planet_glyph(
+    x: f32,
+    y: f32,
+    disk_size: f32,
+    font_size: f32,
+    text: SharedString,
+    text_color: Hsla,
+    disk_bg: Hsla,
+    disk_border: Hsla,
+) -> gpui::Div {
+    div()
+        .absolute()
+        .left(px(x - disk_size / 2.0))
+        .top(px(y - disk_size / 2.0))
+        .w(px(disk_size))
+        .h(px(disk_size))
+        .rounded_full()
+        .bg(disk_bg)
+        .border_1()
+        .border_color(disk_border)
+        .flex()
+        .items_center()
+        .justify_center()
+        .text_size(px(font_size))
+        .text_color(text_color)
+        .child(text)
+}
+
+/// Color HSL semi-opaco para los halos de los glyphs — derivado del
+/// theme. En dark va casi negro; en light casi blanco. Alpha alta para
+/// que el char quede legible contra cualquier cosa que haya detrás
+/// (anillo, líneas de aspecto, starfield).
+fn glyph_halo(theme: &Theme) -> Hsla {
+    if theme.is_dark {
+        hsla(0.0, 0.0, 0.07, 0.92)
+    } else {
+        hsla(0.0, 0.0, 0.97, 0.92)
+    }
+}
+
 fn with_alpha(c: Hsla, a: f32) -> Hsla {
     hsla(c.h, c.s, c.l, a.clamp(0.0, 1.0))
 }
 
+/// Devuelve `c` con la luminancia modificada por `delta` (clamp 0..1).
+/// Útil para derivar highlight (+luma) y shadow (-luma) de un color
+/// base manteniendo hue y saturación — efecto bevel/3D barato.
+fn adjust_luma(c: Hsla, delta: f32) -> Hsla {
+    hsla(c.h, c.s, (c.l + delta).clamp(0.0, 1.0), c.a)
+}
+
+/// Stroke con efecto embossed: 3 trazos concéntricos. El highlight va
+/// 0.7 px hacia el centro con luminancia subida; el principal en `r`;
+/// el shadow 0.7 px hacia afuera con luminancia bajada. La dirección
+/// del bevel depende del theme: en dark el highlight es exterior (luz
+/// "desde arriba"), en light interior (sombra "desde arriba" hacia
+/// el centro).
+fn stroke_circle_3d(
+    window: &mut Window,
+    cx: f32,
+    cy: f32,
+    r: f32,
+    width: f32,
+    color: Hsla,
+    theme: &Theme,
+) {
+    let (hl_offset, sh_offset) = if theme.is_dark {
+        (-0.7, 0.7)
+    } else {
+        (0.7, -0.7)
+    };
+    let hl = with_alpha(adjust_luma(color, 0.20), color.a * 0.55);
+    let sh = with_alpha(adjust_luma(color, -0.18), color.a * 0.55);
+    stroke_circle(window, cx, cy, r + hl_offset, (width * 0.7).max(0.4), hl);
+    stroke_circle(window, cx, cy, r, width, color);
+    stroke_circle(window, cx, cy, r + sh_offset, (width * 0.7).max(0.4), sh);
+}
+
+/// Bevel central del anillo de signos: ~10 strokes finos entre
+/// sign_inner y sign_outer, con alpha en bell curve (máximo en el
+/// medio, decae hacia los bordes). Genera la sensación de volumen
+/// sin pintar gradient radial (no soportado en gpui canvas).
+fn paint_dial_bevel(
+    window: &mut Window,
+    cx: f32,
+    cy: f32,
+    radii: &Radii,
+    palette: &AstroPalette,
+    theme: &Theme,
+) {
+    let steps = 10;
+    let base = if theme.is_dark { 0.07 } else { 0.10 };
+    let color = palette.dial_ring;
+    for i in 0..steps {
+        let t = (i as f32 + 0.5) / steps as f32;
+        let r = radii.sign_inner + (radii.sign_outer - radii.sign_inner) * t;
+        // Bell curve simétrica: |t-0.5|*2 da 0..1 desde el centro, lo
+        // invertimos para que el centro tenga peso máximo.
+        let bell = 1.0 - ((t - 0.5).abs() * 2.0);
+        let a = base * bell;
+        stroke_circle(window, cx, cy, r, 1.0, with_alpha(color, a));
+    }
+}
+
 fn sign_unicode(name: &str) -> &'static str {
     match name {
         "aries" => "♈",
diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-theme/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-theme/src/lib.rs
index 0086108..b7ceb94 100644
--- a/crates/modules/tahuantinsuyu/tahuantinsuyu-theme/src/lib.rs
+++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-theme/src/lib.rs
@@ -215,14 +215,132 @@ impl AstroPalette {
         }
     }
 
+    /// Variante "papel coloreado" — para preview de impresión. Hue de
+    /// cada slot mantenido; luminancia 0.26-0.34 y saturación alta
+    /// para que sobreviva el ink-bleed sin perder identidad. Sin glow.
+    pub fn print_color() -> Self {
+        Self {
+            is_dark: false,
+
+            fire: hsla(11.0 / 360.0, 0.78, 0.34, 1.0),
+            earth: hsla(95.0 / 360.0, 0.55, 0.26, 1.0),
+            air: hsla(48.0 / 360.0, 0.78, 0.34, 1.0),
+            water: hsla(210.0 / 360.0, 0.72, 0.32, 1.0),
+
+            cardinal: hsla(340.0 / 360.0, 0.65, 0.34, 1.0),
+            fixed: hsla(258.0 / 360.0, 0.55, 0.32, 1.0),
+            mutable: hsla(170.0 / 360.0, 0.55, 0.28, 1.0),
+
+            sun: hsla(35.0 / 360.0, 0.95, 0.34, 1.0),
+            moon: hsla(220.0 / 360.0, 0.35, 0.34, 1.0),
+            mercury: hsla(140.0 / 360.0, 0.55, 0.28, 1.0),
+            venus: hsla(330.0 / 360.0, 0.65, 0.36, 1.0),
+            mars: hsla(8.0 / 360.0, 0.85, 0.34, 1.0),
+            jupiter: hsla(38.0 / 360.0, 0.85, 0.34, 1.0),
+            saturn: hsla(28.0 / 360.0, 0.30, 0.26, 1.0),
+            uranus: hsla(195.0 / 360.0, 0.75, 0.34, 1.0),
+            neptune: hsla(225.0 / 360.0, 0.65, 0.34, 1.0),
+            pluto: hsla(280.0 / 360.0, 0.55, 0.28, 1.0),
+            chiron: hsla(75.0 / 360.0, 0.42, 0.30, 1.0),
+            north_node: hsla(35.0 / 360.0, 0.55, 0.36, 1.0),
+            south_node: hsla(35.0 / 360.0, 0.30, 0.28, 1.0),
+            lilith: hsla(310.0 / 360.0, 0.60, 0.26, 1.0),
+
+            conjunction: hsla(45.0 / 360.0, 0.75, 0.32, 1.0),
+            sextile: hsla(195.0 / 360.0, 0.70, 0.32, 1.0),
+            square: hsla(8.0 / 360.0, 0.85, 0.34, 1.0),
+            trine: hsla(140.0 / 360.0, 0.65, 0.28, 1.0),
+            opposition: hsla(280.0 / 360.0, 0.65, 0.36, 1.0),
+            minor_aspect: hsla(220.0 / 360.0, 0.40, 0.40, 0.85),
+
+            dial_ring: hsla(40.0 / 360.0, 0.30, 0.22, 1.0),
+            house_cusp: hsla(40.0 / 360.0, 0.20, 0.28, 0.90),
+            angle_highlight: hsla(15.0 / 360.0, 0.85, 0.36, 1.0),
+        }
+    }
+
+    /// Variante "papel B&N" — preview de impresión monocromática.
+    /// TODO los slots de planeta y aspecto se reducen a niveles de
+    /// gris. El canvas se encarga de diferenciar aspectos por dash
+    /// pattern y planetas por glyph (el unicode astronómico es
+    /// distintivo aunque pierda color).
+    pub fn print_bw() -> Self {
+        // Tres niveles funcionales: muy oscuro (texto, glyphs
+        // principales), medio (cusps, líneas), claro (fondos, minors).
+        let ink_strong = hsla(0.0, 0.0, 0.10, 1.0);
+        let ink_mid = hsla(0.0, 0.0, 0.30, 1.0);
+        let ink_soft = hsla(0.0, 0.0, 0.50, 0.90);
+        let ink_faint = hsla(0.0, 0.0, 0.55, 0.75);
+
+        Self {
+            is_dark: false,
+
+            fire: ink_strong,
+            earth: ink_strong,
+            air: ink_strong,
+            water: ink_strong,
+
+            cardinal: ink_mid,
+            fixed: ink_mid,
+            mutable: ink_mid,
+
+            // Planetas: todos en ink_strong para que los glyphs se
+            // lean fuerte. El usuario distingue por el unicode
+            // astronómico, no por hue.
+            sun: ink_strong,
+            moon: ink_strong,
+            mercury: ink_strong,
+            venus: ink_strong,
+            mars: ink_strong,
+            jupiter: ink_strong,
+            saturn: ink_strong,
+            uranus: ink_strong,
+            neptune: ink_strong,
+            pluto: ink_strong,
+            chiron: ink_mid,
+            north_node: ink_mid,
+            south_node: ink_mid,
+            lilith: ink_mid,
+
+            // Aspectos: el color es uniforme; la diferenciación es por
+            // dash pattern en el painter (square=dashed, trine=solid,
+            // sextile=dotted, etc.). Acá solo damos el "intensity"
+            // base que el painter modula.
+            conjunction: ink_strong,
+            sextile: ink_mid,
+            square: ink_strong,
+            trine: ink_mid,
+            opposition: ink_strong,
+            minor_aspect: ink_faint,
+
+            dial_ring: ink_mid,
+            house_cusp: ink_soft,
+            angle_highlight: ink_strong,
+        }
+    }
+
     pub fn for_theme(theme: &yahweh_theme::Theme) -> Self {
-        if theme.is_dark {
-            Self::dark()
-        } else {
-            Self::light()
+        // Dispatcher por nombre para los themes "papel"; el resto cae
+        // al binary dark/light según `is_dark`. Mantenemos el match
+        // case-insensitive por defensa contra cambios de naming.
+        match theme.name {
+            "Print Color" => Self::print_color(),
+            "Print B&W" => Self::print_bw(),
+            _ if theme.is_dark => Self::dark(),
+            _ => Self::light(),
         }
     }
 
+    /// Devuelve `true` si la paleta es monocromática — los painters
+    /// la usan para activar dash patterns en lugar de diferenciar
+    /// aspectos por color.
+    pub fn is_monochrome(&self) -> bool {
+        // Heurística simple: si conjunction y square (que en color
+        // siempre tienen hues distintos) tienen el mismo hue,
+        // estamos en BW.
+        (self.conjunction.h - self.square.h).abs() < 1e-3
+    }
+
     pub fn element(&self, e: Element) -> Hsla {
         match e {
             Element::Fire => self.fire,
diff --git a/crates/modules/ui_engine/libs/theme/src/lib.rs b/crates/modules/ui_engine/libs/theme/src/lib.rs
index 61a7f5a..f89f810 100644
--- a/crates/modules/ui_engine/libs/theme/src/lib.rs
+++ b/crates/modules/ui_engine/libs/theme/src/lib.rs
@@ -147,6 +147,8 @@ impl Theme {
             Self::flat_dark(),
             Self::solarized_light(),
             Self::high_contrast(),
+            Self::print_color(),
+            Self::print_bw(),
         ]
     }
 
@@ -369,6 +371,71 @@ impl Theme {
         }
     }
 
+    /// **Print Color** — preview de impresión a color sobre papel.
+    /// Fondo crema cálido (#f7f4ea-ish), texto y ornamentos en
+    /// luminancias bajas para que sobrevivan ink-bleed. Sin gradientes
+    /// (los gradients no imprimen bien) y sin glow.
+    pub fn print_color() -> Self {
+        let bg_app: Background = hsla(42.0 / 360.0, 0.30, 0.94, 1.0).into();
+        let bg_panel: Background = hsla(40.0 / 360.0, 0.25, 0.97, 1.0).into();
+        let bg_panel_alt: Background = hsla(40.0 / 360.0, 0.20, 0.92, 1.0).into();
+        Self {
+            name: "Print Color",
+            is_dark: false,
+            bg_app,
+            bg_panel,
+            bg_panel_alt,
+            bg_row_hover: hsla(40.0 / 360.0, 0.30, 0.86, 0.70),
+            bg_row_active: hsla(35.0 / 360.0, 0.45, 0.78, 0.85),
+            fg_text: hsla(30.0 / 360.0, 0.15, 0.18, 1.0),
+            fg_muted: hsla(30.0 / 360.0, 0.12, 0.40, 1.0),
+            fg_disabled: hsla(30.0 / 360.0, 0.08, 0.62, 1.0),
+            accent: hsla(15.0 / 360.0, 0.70, 0.40, 1.0),
+            accent_strong: hsla(355.0 / 360.0, 0.78, 0.36, 1.0),
+            border: hsla(40.0 / 360.0, 0.22, 0.82, 1.0),
+            border_strong: hsla(30.0 / 360.0, 0.30, 0.55, 1.0),
+            marker_palette: vec![
+                hsla(15.0 / 360.0, 0.70, 0.35, 0.30),
+                hsla(210.0 / 360.0, 0.65, 0.35, 0.30),
+                hsla(140.0 / 360.0, 0.55, 0.30, 0.30),
+                hsla(285.0 / 360.0, 0.55, 0.38, 0.30),
+                hsla(40.0 / 360.0, 0.85, 0.40, 0.30),
+            ],
+        }
+    }
+
+    /// **Print B&W** — preview de impresión monocromática. Fondo
+    /// blanco puro, todo en escala de grises. Cualquier slot que
+    /// dependa de "color" en widgets astrológicos se diferencia por
+    /// forma o por dash pattern, no por tinte.
+    pub fn print_bw() -> Self {
+        let bg_app: Background = hsla(0.0, 0.0, 1.00, 1.0).into();
+        let bg_panel: Background = hsla(0.0, 0.0, 0.99, 1.0).into();
+        let bg_panel_alt: Background = hsla(0.0, 0.0, 0.95, 1.0).into();
+        Self {
+            name: "Print B&W",
+            is_dark: false,
+            bg_app,
+            bg_panel,
+            bg_panel_alt,
+            bg_row_hover: hsla(0.0, 0.0, 0.88, 0.85),
+            bg_row_active: hsla(0.0, 0.0, 0.78, 0.95),
+            fg_text: hsla(0.0, 0.0, 0.10, 1.0),
+            fg_muted: hsla(0.0, 0.0, 0.40, 1.0),
+            fg_disabled: hsla(0.0, 0.0, 0.65, 1.0),
+            accent: hsla(0.0, 0.0, 0.20, 1.0),
+            accent_strong: hsla(0.0, 0.0, 0.05, 1.0),
+            border: hsla(0.0, 0.0, 0.80, 1.0),
+            border_strong: hsla(0.0, 0.0, 0.40, 1.0),
+            marker_palette: vec![
+                hsla(0.0, 0.0, 0.30, 0.35),
+                hsla(0.0, 0.0, 0.50, 0.35),
+                hsla(0.0, 0.0, 0.20, 0.35),
+                hsla(0.0, 0.0, 0.60, 0.35),
+            ],
+        }
+    }
+
     /// **High Contrast** — accesibilidad. Negro puro con texto blanco y
     /// ornamentos amarillo/verde fuertes. Suficientemente diferente para
     /// notar inmediatamente al usar el switcher.