⎇ 1078e433f24ac83e2540343e82d2bd0fe49e304a
crates/modules/tahuantinsuyu/tahuantinsuyu-theme/src/lib.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
feat(tahuantinsuyu): rueda 3D, hover-highlight, universo, themes papel
1//! `tahuantinsuyu-theme` — paleta simbólica + presets místicos.
2//!
3//! Una capa fina sobre [`yahweh_theme::Theme`]: el theme base aporta los
4//! slots de panel/foreground/accent; nosotros agregamos paletas
5//! semánticas para los elementos (fuego/tierra/aire/agua), los modos
6//! (cardinal/fijo/mutable), los planetas y los aspectos.
7//!
8//! El canvas pide colores por símbolo (`palette.element(Element::Fire)`),
9//! nunca hex directos. Así una sola tabla controla tanto el dark como el
10//! light, y cambiar la paleta no requiere tocar el render.
11
12#![forbid(unsafe_code)]
13#![warn(rust_2018_idioms)]
14
15use gpui::{Hsla, hsla};
16
17// =====================================================================
18// Símbolos
19// =====================================================================
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum Element {
23 Fire,
24 Earth,
25 Air,
26 Water,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum Modality {
31 Cardinal,
32 Fixed,
33 Mutable,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum Planet {
38 Sun,
39 Moon,
40 Mercury,
41 Venus,
42 Mars,
43 Jupiter,
44 Saturn,
45 Uranus,
46 Neptune,
47 Pluto,
48 Chiron,
49 NorthNode,
50 SouthNode,
51 Lilith,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub enum AspectKind {
56 Conjunction,
57 Sextile,
58 Square,
59 Trine,
60 Opposition,
61 Quincunx,
62 Semisextile,
63 Semisquare,
64 Sesquisquare,
65 Quintile,
66 Biquintile,
67}
68
69// =====================================================================
70// Paleta
71// =====================================================================
72
73/// Paleta completa de símbolos astrológicos resuelta a colores HSLA. Las
74/// dos variantes (`dark` / `light`) comparten estructura — el canvas
75/// elige según `yahweh_theme::Theme::is_dark`.
76#[derive(Debug, Clone)]
77pub struct AstroPalette {
78 pub is_dark: bool,
79
80 pub fire: Hsla,
81 pub earth: Hsla,
82 pub air: Hsla,
83 pub water: Hsla,
84
85 pub cardinal: Hsla,
86 pub fixed: Hsla,
87 pub mutable: Hsla,
88
89 pub sun: Hsla,
90 pub moon: Hsla,
91 pub mercury: Hsla,
92 pub venus: Hsla,
93 pub mars: Hsla,
94 pub jupiter: Hsla,
95 pub saturn: Hsla,
96 pub uranus: Hsla,
97 pub neptune: Hsla,
98 pub pluto: Hsla,
99 pub chiron: Hsla,
100 pub north_node: Hsla,
101 pub south_node: Hsla,
102 pub lilith: Hsla,
103
104 pub conjunction: Hsla,
105 pub sextile: Hsla,
106 pub square: Hsla,
107 pub trine: Hsla,
108 pub opposition: Hsla,
109 pub minor_aspect: Hsla,
110
111 /// Color del dial zodiacal (anillo exterior).
112 pub dial_ring: Hsla,
113 /// Cusps de casas.
114 pub house_cusp: Hsla,
115 /// Resaltado del ascendente / MC.
116 pub angle_highlight: Hsla,
117}
118
119impl AstroPalette {
120 /// Variante oscura — calibrada para sentirse cálida y mística sin
121 /// caer en saturación de carnaval. Las cusps quedan apenas más
122 /// claras que el fondo, los planetas tienen luminancia media-alta
123 /// para destacar sin glow falso.
124 pub fn dark() -> Self {
125 Self {
126 is_dark: true,
127
128 // Elementos — saturación alta + luminancia media. Familiares
129 // al símbolo pero suaves para coexistir.
130 fire: hsla(11.0 / 360.0, 0.78, 0.58, 1.0),
131 earth: hsla(95.0 / 360.0, 0.40, 0.48, 1.0),
132 air: hsla(48.0 / 360.0, 0.72, 0.66, 1.0),
133 water: hsla(210.0 / 360.0, 0.68, 0.58, 1.0),
134
135 cardinal: hsla(340.0 / 360.0, 0.55, 0.62, 1.0),
136 fixed: hsla(258.0 / 360.0, 0.48, 0.58, 1.0),
137 mutable: hsla(170.0 / 360.0, 0.42, 0.55, 1.0),
138
139 sun: hsla(45.0 / 360.0, 0.92, 0.62, 1.0),
140 moon: hsla(220.0 / 360.0, 0.25, 0.85, 1.0),
141 mercury: hsla(140.0 / 360.0, 0.40, 0.62, 1.0),
142 venus: hsla(330.0 / 360.0, 0.55, 0.70, 1.0),
143 mars: hsla(8.0 / 360.0, 0.78, 0.55, 1.0),
144 jupiter: hsla(38.0 / 360.0, 0.72, 0.62, 1.0),
145 saturn: hsla(28.0 / 360.0, 0.20, 0.50, 1.0),
146 uranus: hsla(195.0 / 360.0, 0.65, 0.62, 1.0),
147 neptune: hsla(225.0 / 360.0, 0.55, 0.66, 1.0),
148 pluto: hsla(280.0 / 360.0, 0.40, 0.45, 1.0),
149 chiron: hsla(75.0 / 360.0, 0.30, 0.55, 1.0),
150 north_node: hsla(35.0 / 360.0, 0.35, 0.70, 1.0),
151 south_node: hsla(35.0 / 360.0, 0.20, 0.45, 1.0),
152 lilith: hsla(310.0 / 360.0, 0.45, 0.40, 1.0),
153
154 conjunction: hsla(50.0 / 360.0, 0.65, 0.70, 0.85),
155 sextile: hsla(195.0 / 360.0, 0.60, 0.62, 0.75),
156 square: hsla(8.0 / 360.0, 0.75, 0.58, 0.85),
157 trine: hsla(140.0 / 360.0, 0.55, 0.55, 0.80),
158 opposition: hsla(280.0 / 360.0, 0.55, 0.62, 0.85),
159 minor_aspect: hsla(220.0 / 360.0, 0.20, 0.55, 0.55),
160
161 dial_ring: hsla(40.0 / 360.0, 0.18, 0.78, 0.85),
162 house_cusp: hsla(40.0 / 360.0, 0.12, 0.55, 0.60),
163 angle_highlight: hsla(50.0 / 360.0, 0.95, 0.65, 1.0),
164 }
165 }
166
167 /// Variante clara — desaturada y con luminancias bajas para que los
168 /// símbolos no compitan con el fondo blanco. Pensada para imprimir.
169 pub fn light() -> Self {
170 Self {
171 is_dark: false,
172
173 fire: hsla(11.0 / 360.0, 0.65, 0.42, 1.0),
174 earth: hsla(95.0 / 360.0, 0.45, 0.30, 1.0),
175 air: hsla(48.0 / 360.0, 0.55, 0.42, 1.0),
176 water: hsla(210.0 / 360.0, 0.60, 0.38, 1.0),
177
178 cardinal: hsla(340.0 / 360.0, 0.55, 0.42, 1.0),
179 fixed: hsla(258.0 / 360.0, 0.45, 0.40, 1.0),
180 mutable: hsla(170.0 / 360.0, 0.42, 0.35, 1.0),
181
182 sun: hsla(38.0 / 360.0, 0.85, 0.45, 1.0),
183 moon: hsla(220.0 / 360.0, 0.22, 0.45, 1.0),
184 mercury: hsla(140.0 / 360.0, 0.45, 0.36, 1.0),
185 venus: hsla(330.0 / 360.0, 0.55, 0.45, 1.0),
186 mars: hsla(8.0 / 360.0, 0.75, 0.40, 1.0),
187 jupiter: hsla(38.0 / 360.0, 0.72, 0.42, 1.0),
188 saturn: hsla(28.0 / 360.0, 0.25, 0.30, 1.0),
189 uranus: hsla(195.0 / 360.0, 0.65, 0.40, 1.0),
190 neptune: hsla(225.0 / 360.0, 0.55, 0.42, 1.0),
191 pluto: hsla(280.0 / 360.0, 0.45, 0.30, 1.0),
192 chiron: hsla(75.0 / 360.0, 0.32, 0.35, 1.0),
193 north_node: hsla(35.0 / 360.0, 0.45, 0.45, 1.0),
194 south_node: hsla(35.0 / 360.0, 0.20, 0.30, 1.0),
195 lilith: hsla(310.0 / 360.0, 0.50, 0.30, 1.0),
196
197 // Aspectos en light: alpha alta y luminancia media-baja para
198 // que las líneas tengan presencia contra fondo claro. En dark
199 // las alphas pueden ser más bajas porque el contraste contra
200 // el fondo oscuro ya las hace destacar.
201 conjunction: hsla(45.0 / 360.0, 0.70, 0.38, 0.95),
202 sextile: hsla(195.0 / 360.0, 0.65, 0.36, 0.90),
203 square: hsla(8.0 / 360.0, 0.80, 0.38, 0.95),
204 trine: hsla(140.0 / 360.0, 0.60, 0.32, 0.92),
205 opposition: hsla(280.0 / 360.0, 0.60, 0.40, 0.95),
206 minor_aspect: hsla(220.0 / 360.0, 0.30, 0.38, 0.75),
207
208 // dial_ring: luminancia baja (oscuro sobre blanco) para que
209 // el anillo de signos tenga peso. house_cusp: subimos alpha
210 // y bajamos luminancia para que las cúspides no se laven en
211 // un beige translúcido.
212 dial_ring: hsla(40.0 / 360.0, 0.20, 0.28, 0.95),
213 house_cusp: hsla(40.0 / 360.0, 0.15, 0.32, 0.80),
214 angle_highlight: hsla(38.0 / 360.0, 0.90, 0.38, 1.0),
215 }
216 }
217
218 /// Variante "papel coloreado" — para preview de impresión. Hue de
219 /// cada slot mantenido; luminancia 0.26-0.34 y saturación alta
220 /// para que sobreviva el ink-bleed sin perder identidad. Sin glow.
221 pub fn print_color() -> Self {
222 Self {
223 is_dark: false,
224
225 fire: hsla(11.0 / 360.0, 0.78, 0.34, 1.0),
226 earth: hsla(95.0 / 360.0, 0.55, 0.26, 1.0),
227 air: hsla(48.0 / 360.0, 0.78, 0.34, 1.0),
228 water: hsla(210.0 / 360.0, 0.72, 0.32, 1.0),
229
230 cardinal: hsla(340.0 / 360.0, 0.65, 0.34, 1.0),
231 fixed: hsla(258.0 / 360.0, 0.55, 0.32, 1.0),
232 mutable: hsla(170.0 / 360.0, 0.55, 0.28, 1.0),
233
234 sun: hsla(35.0 / 360.0, 0.95, 0.34, 1.0),
235 moon: hsla(220.0 / 360.0, 0.35, 0.34, 1.0),
236 mercury: hsla(140.0 / 360.0, 0.55, 0.28, 1.0),
237 venus: hsla(330.0 / 360.0, 0.65, 0.36, 1.0),
238 mars: hsla(8.0 / 360.0, 0.85, 0.34, 1.0),
239 jupiter: hsla(38.0 / 360.0, 0.85, 0.34, 1.0),
240 saturn: hsla(28.0 / 360.0, 0.30, 0.26, 1.0),
241 uranus: hsla(195.0 / 360.0, 0.75, 0.34, 1.0),
242 neptune: hsla(225.0 / 360.0, 0.65, 0.34, 1.0),
243 pluto: hsla(280.0 / 360.0, 0.55, 0.28, 1.0),
244 chiron: hsla(75.0 / 360.0, 0.42, 0.30, 1.0),
245 north_node: hsla(35.0 / 360.0, 0.55, 0.36, 1.0),
246 south_node: hsla(35.0 / 360.0, 0.30, 0.28, 1.0),
247 lilith: hsla(310.0 / 360.0, 0.60, 0.26, 1.0),
248
249 conjunction: hsla(45.0 / 360.0, 0.75, 0.32, 1.0),
250 sextile: hsla(195.0 / 360.0, 0.70, 0.32, 1.0),
251 square: hsla(8.0 / 360.0, 0.85, 0.34, 1.0),
252 trine: hsla(140.0 / 360.0, 0.65, 0.28, 1.0),
253 opposition: hsla(280.0 / 360.0, 0.65, 0.36, 1.0),
254 minor_aspect: hsla(220.0 / 360.0, 0.40, 0.40, 0.85),
255
256 dial_ring: hsla(40.0 / 360.0, 0.30, 0.22, 1.0),
257 house_cusp: hsla(40.0 / 360.0, 0.20, 0.28, 0.90),
258 angle_highlight: hsla(15.0 / 360.0, 0.85, 0.36, 1.0),
259 }
260 }
261
262 /// Variante "papel B&N" — preview de impresión monocromática.
263 /// TODO los slots de planeta y aspecto se reducen a niveles de
264 /// gris. El canvas se encarga de diferenciar aspectos por dash
265 /// pattern y planetas por glyph (el unicode astronómico es
266 /// distintivo aunque pierda color).
267 pub fn print_bw() -> Self {
268 // Tres niveles funcionales: muy oscuro (texto, glyphs
269 // principales), medio (cusps, líneas), claro (fondos, minors).
270 let ink_strong = hsla(0.0, 0.0, 0.10, 1.0);
271 let ink_mid = hsla(0.0, 0.0, 0.30, 1.0);
272 let ink_soft = hsla(0.0, 0.0, 0.50, 0.90);
273 let ink_faint = hsla(0.0, 0.0, 0.55, 0.75);
274
275 Self {
276 is_dark: false,
277
278 fire: ink_strong,
279 earth: ink_strong,
280 air: ink_strong,
281 water: ink_strong,
282
283 cardinal: ink_mid,
284 fixed: ink_mid,
285 mutable: ink_mid,
286
287 // Planetas: todos en ink_strong para que los glyphs se
288 // lean fuerte. El usuario distingue por el unicode
289 // astronómico, no por hue.
290 sun: ink_strong,
291 moon: ink_strong,
292 mercury: ink_strong,
293 venus: ink_strong,
294 mars: ink_strong,
295 jupiter: ink_strong,
296 saturn: ink_strong,
297 uranus: ink_strong,
298 neptune: ink_strong,
299 pluto: ink_strong,
300 chiron: ink_mid,
301 north_node: ink_mid,
302 south_node: ink_mid,
303 lilith: ink_mid,
304
305 // Aspectos: el color es uniforme; la diferenciación es por
306 // dash pattern en el painter (square=dashed, trine=solid,
307 // sextile=dotted, etc.). Acá solo damos el "intensity"
308 // base que el painter modula.
309 conjunction: ink_strong,
310 sextile: ink_mid,
311 square: ink_strong,
312 trine: ink_mid,
313 opposition: ink_strong,
314 minor_aspect: ink_faint,
315
316 dial_ring: ink_mid,
317 house_cusp: ink_soft,
318 angle_highlight: ink_strong,
319 }
320 }
321
322 pub fn for_theme(theme: &yahweh_theme::Theme) -> Self {
323 // Dispatcher por nombre para los themes "papel"; el resto cae
324 // al binary dark/light según `is_dark`. Mantenemos el match
325 // case-insensitive por defensa contra cambios de naming.
326 match theme.name {
327 "Print Color" => Self::print_color(),
328 "Print B&W" => Self::print_bw(),
329 _ if theme.is_dark => Self::dark(),
330 _ => Self::light(),
331 }
332 }
333
334 /// Devuelve `true` si la paleta es monocromática — los painters
335 /// la usan para activar dash patterns en lugar de diferenciar
336 /// aspectos por color.
337 pub fn is_monochrome(&self) -> bool {
338 // Heurística simple: si conjunction y square (que en color
339 // siempre tienen hues distintos) tienen el mismo hue,
340 // estamos en BW.
341 (self.conjunction.h - self.square.h).abs() < 1e-3
342 }
343
344 pub fn element(&self, e: Element) -> Hsla {
345 match e {
346 Element::Fire => self.fire,
347 Element::Earth => self.earth,
348 Element::Air => self.air,
349 Element::Water => self.water,
350 }
351 }
352
353 pub fn modality(&self, m: Modality) -> Hsla {
354 match m {
355 Modality::Cardinal => self.cardinal,
356 Modality::Fixed => self.fixed,
357 Modality::Mutable => self.mutable,
358 }
359 }
360
361 pub fn planet(&self, p: Planet) -> Hsla {
362 match p {
363 Planet::Sun => self.sun,
364 Planet::Moon => self.moon,
365 Planet::Mercury => self.mercury,
366 Planet::Venus => self.venus,
367 Planet::Mars => self.mars,
368 Planet::Jupiter => self.jupiter,
369 Planet::Saturn => self.saturn,
370 Planet::Uranus => self.uranus,
371 Planet::Neptune => self.neptune,
372 Planet::Pluto => self.pluto,
373 Planet::Chiron => self.chiron,
374 Planet::NorthNode => self.north_node,
375 Planet::SouthNode => self.south_node,
376 Planet::Lilith => self.lilith,
377 }
378 }
379
380 pub fn aspect(&self, a: AspectKind) -> Hsla {
381 match a {
382 AspectKind::Conjunction => self.conjunction,
383 AspectKind::Sextile => self.sextile,
384 AspectKind::Square => self.square,
385 AspectKind::Trine => self.trine,
386 AspectKind::Opposition => self.opposition,
387 _ => self.minor_aspect,
388 }
389 }
390}
391
392/// Resuelve un símbolo zodiacal (string) a su elemento.
393/// Ej. `"aries" → Fire`, `"taurus" → Earth`, …
394pub fn element_for_sign(sign: &str) -> Option<Element> {
395 Some(match sign.to_ascii_lowercase().as_str() {
396 "aries" | "leo" | "sagittarius" => Element::Fire,
397 "taurus" | "virgo" | "capricorn" => Element::Earth,
398 "gemini" | "libra" | "aquarius" => Element::Air,
399 "cancer" | "scorpio" | "pisces" => Element::Water,
400 _ => return None,
401 })
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn element_lookup() {
410 assert_eq!(element_for_sign("aries"), Some(Element::Fire));
411 assert_eq!(element_for_sign("CAPRICORN"), Some(Element::Earth));
412 assert_eq!(element_for_sign("zod"), None);
413 }
414
415 #[test]
416 fn palette_indexes() {
417 let p = AstroPalette::dark();
418 assert_eq!(p.planet(Planet::Sun), p.sun);
419 assert_eq!(p.element(Element::Water), p.water);
420 }
421}