diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/aquarius.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/aquarius.svg new file mode 100644 index 0000000..2d3dd55 --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/aquarius.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/aries.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/aries.svg new file mode 100644 index 0000000..2843d95 --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/aries.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/cancer.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/cancer.svg new file mode 100644 index 0000000..a1a20a7 --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/cancer.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/capricorn.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/capricorn.svg new file mode 100644 index 0000000..6cabad7 --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/capricorn.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/gemini.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/gemini.svg new file mode 100644 index 0000000..eef33e8 --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/gemini.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/leo.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/leo.svg new file mode 100644 index 0000000..72c2ca9 --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/leo.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/libra.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/libra.svg new file mode 100644 index 0000000..7e020da --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/libra.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/pisces.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/pisces.svg new file mode 100644 index 0000000..2e5fc4e --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/pisces.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/sagittarius.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/sagittarius.svg new file mode 100644 index 0000000..5274e7b --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/sagittarius.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/scorpio.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/scorpio.svg new file mode 100644 index 0000000..e4c9f3e --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/scorpio.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/taurus.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/taurus.svg new file mode 100644 index 0000000..9c491ef --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/taurus.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/virgo.svg b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/virgo.svg new file mode 100644 index 0000000..7bc16d3 --- /dev/null +++ b/src/apps/gameboard/static/apps/gameboard/icons/zodiac-signs/virgo.svg @@ -0,0 +1 @@ + diff --git a/src/apps/gameboard/static/apps/gameboard/natus-wheel.js b/src/apps/gameboard/static/apps/gameboard/natus-wheel.js index 8c923a2..a291b64 100644 --- a/src/apps/gameboard/static/apps/gameboard/natus-wheel.js +++ b/src/apps/gameboard/static/apps/gameboard/natus-wheel.js @@ -27,18 +27,18 @@ const NatusWheel = (() => { // ── Constants ────────────────────────────────────────────────────────────── const SIGNS = [ - { name: 'Aries', symbol: '♈', element: 'Fire' }, - { name: 'Taurus', symbol: '♉', element: 'Stone' }, - { name: 'Gemini', symbol: '♊', element: 'Air' }, - { name: 'Cancer', symbol: '♋', element: 'Water' }, - { name: 'Leo', symbol: '♌', element: 'Fire' }, - { name: 'Virgo', symbol: '♍', element: 'Stone' }, - { name: 'Libra', symbol: '♎', element: 'Air' }, - { name: 'Scorpio', symbol: '♏', element: 'Water' }, - { name: 'Sagittarius', symbol: '♐', element: 'Fire' }, - { name: 'Capricorn', symbol: '♑', element: 'Stone' }, - { name: 'Aquarius', symbol: '♒', element: 'Air' }, - { name: 'Pisces', symbol: '♓', element: 'Water' }, + { name: 'Aries', symbol: '♈', element: 'Fire', fa: 'aries' }, + { name: 'Taurus', symbol: '♉', element: 'Stone', fa: 'taurus' }, + { name: 'Gemini', symbol: '♊', element: 'Air', fa: 'gemini' }, + { name: 'Cancer', symbol: '♋', element: 'Water', fa: 'cancer' }, + { name: 'Leo', symbol: '♌', element: 'Fire', fa: 'leo' }, + { name: 'Virgo', symbol: '♍', element: 'Stone', fa: 'virgo' }, + { name: 'Libra', symbol: '♎', element: 'Air', fa: 'libra' }, + { name: 'Scorpio', symbol: '♏', element: 'Water', fa: 'scorpio' }, + { name: 'Sagittarius', symbol: '♐', element: 'Fire', fa: 'sagittarius' }, + { name: 'Capricorn', symbol: '♑', element: 'Stone', fa: 'capricorn' }, + { name: 'Aquarius', symbol: '♒', element: 'Air', fa: 'aquarius' }, + { name: 'Pisces', symbol: '♓', element: 'Water', fa: 'pisces' }, ]; const PLANET_SYMBOLS = { @@ -46,6 +46,12 @@ const NatusWheel = (() => { Jupiter: '♃', Saturn: '♄', Uranus: '♅', Neptune: '♆', Pluto: '♇', }; + // Alchemical element symbol → CSS modifier class suffix (matches rootvars palette) + const PLANET_ELEMENTS = { + Sun: 'au', Moon: 'ag', Mercury: 'hg', Venus: 'cu', Mars: 'fe', + Jupiter: 'sn', Saturn: 'pb', Uranus: 'u', Neptune: 'np', Pluto: 'pu', + }; + // Aspect stroke colors remain in JS — they are data-driven, not stylistic. const ASPECT_COLORS = { Conjunction: 'var(--priYl, #f0e060)', @@ -66,6 +72,9 @@ const NatusWheel = (() => { let _svg = null; let _cx, _cy, _r; // centre + outer radius + // Cached SVG path strings keyed by sign name, populated by preload(). + const _signPaths = {}; + // Ring radii (fractions of _r, set in _layout) let R = {}; @@ -170,16 +179,29 @@ const NatusWheel = (() => { })) .attr('class', `nw-sign--${sign.element.toLowerCase()}`); - // Symbol at midpoint + // Icon at midpoint const midA = (sa + ea) / 2; - sigGroup.append('text') - .attr('x', _cx + R.labelR * Math.cos(midA)) - .attr('y', _cy + R.labelR * Math.sin(midA)) - .attr('text-anchor', 'middle') - .attr('dominant-baseline', 'middle') - .attr('font-size', `${_r * 0.095}px`) - .attr('class', 'nw-sign-label') - .text(sign.symbol); + const lx = _cx + R.labelR * Math.cos(midA); + const ly = _cy + R.labelR * Math.sin(midA); + const cr = _r * 0.065; // slightly larger than planet circles so icons breathe + // scale the 640×640 icon viewBox down to 85% of the circle diameter + const sf = (cr * 2 * 0.85) / 640; + + // Colored circle behind icon + sigGroup.append('circle') + .attr('cx', lx) + .attr('cy', ly) + .attr('r', cr) + .attr('class', `nw-sign-icon-bg--${sign.element.toLowerCase()}`); + + // Inline SVG path — translate origin to label centre, scale, re-centre icon + if (_signPaths[sign.name]) { + sigGroup.append('path') + .attr('d', _signPaths[sign.name]) + .attr('transform', + `translate(${lx},${ly}) scale(${sf}) translate(-320,-320)`) + .attr('class', `nw-sign-icon nw-sign-icon--${sign.element.toLowerCase()}`); + } }); } @@ -240,13 +262,15 @@ const NatusWheel = (() => { Object.entries(data.planets).forEach(([name, pdata], idx) => { const finalA = _toAngle(pdata.degree, asc); + const el = PLANET_ELEMENTS[name] || ''; // Circle behind symbol + const circleBase = pdata.retrograde ? 'nw-planet-circle--rx' : 'nw-planet-circle'; const circle = planetGroup.append('circle') .attr('cx', _cx + R.planetR * Math.cos(ascAngle)) .attr('cy', _cy + R.planetR * Math.sin(ascAngle)) .attr('r', _r * 0.05) - .attr('class', pdata.retrograde ? 'nw-planet-circle--rx' : 'nw-planet-circle'); + .attr('class', el ? `${circleBase} nw-planet--${el}` : circleBase); // Symbol const label = planetGroup.append('text') @@ -256,7 +280,7 @@ const NatusWheel = (() => { .attr('dominant-baseline', 'middle') .attr('dy', '0.1em') .attr('font-size', `${_r * 0.09}px`) - .attr('class', 'nw-planet-label') + .attr('class', el ? `nw-planet-label nw-planet-label--${el}` : 'nw-planet-label') .text(PLANET_SYMBOLS[name] || name[0]); // Retrograde indicator @@ -358,6 +382,37 @@ const NatusWheel = (() => { // ── Public API ──────────────────────────────────────────────────────────── + /** + * Preload zodiac sign SVGs from `basePath` (default: same dir as this script). + * Returns a Promise that resolves when all 12 are cached in _signPaths. + * Safe to call multiple times; re-fetches every call so swapped files are picked up. + * + * To swap a sign icon: replace the corresponding .svg file in zodiac-signs/ + * and call NatusWheel.preload() before the next draw(). + */ + async function preload(basePath) { + const base = basePath || + (() => { + const scripts = document.querySelectorAll('script[src]'); + for (const s of scripts) { + if (s.src.includes('natus-wheel')) { + return s.src.replace(/natus-wheel\.js.*$/, 'icons/zodiac-signs/'); + } + } + return '/static/apps/gameboard/icons/zodiac-signs/'; + })(); + + await Promise.all(SIGNS.map(async sign => { + const url = base + sign.name.toLowerCase() + '.svg'; + const resp = await window.fetch(url); + if (!resp.ok) { console.warn(`NatusWheel: failed to load ${url}`); return; } + const text = await resp.text(); + const doc = new DOMParser().parseFromString(text, 'image/svg+xml'); + const path = doc.querySelector('path'); + if (path) _signPaths[sign.name] = path.getAttribute('d'); + })); + } + function draw(svgEl, data) { _svg = d3.select(svgEl); _svg.selectAll('*').remove(); @@ -393,5 +448,5 @@ const NatusWheel = (() => { if (_svg) _svg.selectAll('*').remove(); } - return { draw, redraw, clear }; + return { preload, draw, redraw, clear }; })(); diff --git a/src/static_src/scss/_natus.scss b/src/static_src/scss/_natus.scss index 6317037..278b7f0 100644 --- a/src/static_src/scss/_natus.scss +++ b/src/static_src/scss/_natus.scss @@ -359,35 +359,76 @@ html.natus-open .natus-modal-wrap { .nw-outer-ring { fill: none; - stroke: rgba(var(--quaUser), 0.6); - stroke-width: 1px; + stroke: rgba(var(--terUser), 1); + stroke-width: 1.5px; } .nw-inner-disc { fill: rgba(var(--quaUser), 0.6); + stroke: rgba(var(--terUser), 1); + stroke-width: 0.75px; } // Axes (ASC / DSC / MC / IC) .nw-axis-line { stroke: rgba(var(--secUser), 1); stroke-width: 1.5px; } .nw-axis-label { fill: rgba(var(--secUser), 1); } -// Sign ring — vars are RGB tuples ("R, G, B"), must be wrapped in rgba() -.nw-sign--fire { fill: rgba(var(--priRd, 192, 64, 64), 0.75); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; } -.nw-sign--stone { fill: rgba(var(--priFs, 122, 96, 64), 0.75); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; } -.nw-sign--air { fill: rgba(var(--priCy, 64, 144, 176), 0.75); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; } -.nw-sign--water { fill: rgba(var(--priId, 80, 80, 160), 0.75); stroke: rgba(var(--quaUser), 1); stroke-width: 0.5px; } -.nw-sign-label { fill: rgba(var(--secUser), 1); } +// Sign ring — uniform --priMe bg at half opacity +.nw-sign--fire, +.nw-sign--stone, +.nw-sign--air, +.nw-sign--water { + fill: rgba(var(--priMe), 0.25); + stroke: rgba(var(--terUser), 1); + stroke-width: 0.75px; +} -// House ring -.nw-house-cusp { stroke: rgba(var(--quaUser), 0.8); stroke-width: 0.8px; } -.nw-house-num { fill: rgba(var(--quiUser), 1); } -.nw-house-fill--even { fill: rgba(var(--quaUser), 0.45); } -.nw-house-fill--odd { fill: rgba(var(--quiUser), 0.35); } +// Icon bg circles — element fill + matching border +.nw-sign-icon-bg--fire { fill: rgba(var(--terRd), 1); stroke: rgba(var(--priOr), 1); stroke-width: 1px; } +.nw-sign-icon-bg--stone { fill: rgba(var(--priYl), 1); stroke: rgba(var(--priLm), 1); stroke-width: 1px; } +.nw-sign-icon-bg--air { fill: rgba(var(--terGn), 1); stroke: rgba(var(--priTk), 1); stroke-width: 1px; } +.nw-sign-icon-bg--water { fill: rgba(var(--priCy), 1); stroke: rgba(var(--priBl), 1); stroke-width: 1px; } -// Planets -.nw-planet-circle { fill: rgba(var(--quiUser), 1); } -.nw-planet-circle--rx { fill: rgba(var(--quiUser), 1); } -.nw-planet-label { fill: rgba(var(--quaUser), 1); stroke: rgba(var(--quaUser), 0.6); stroke-width: 0.4px; paint-order: stroke fill; } +// Inline SVG path icons — per-element colors +.nw-sign-icon--fire { fill: rgba(var(--priOr), 1); } +.nw-sign-icon--stone { fill: rgba(var(--terLm), 1); } +.nw-sign-icon--air { fill: rgba(var(--priTk), 1); } +.nw-sign-icon--water { fill: rgba(var(--terBl), 1); } + +// House ring — uniform --priFs bg +.nw-house-cusp { stroke: rgba(var(--terUser), 1); stroke-width: 1.2px; } +.nw-house-num { fill: rgba(var(--ninUser), 1); } +.nw-house-fill--even { fill: rgba(var(--priFs), 0.25); stroke: rgba(var(--terUser), 1); stroke-width: 0.75px; } +.nw-house-fill--odd { fill: rgba(var(--priFs), 0.25); stroke: rgba(var(--terUser), 1); stroke-width: 0.75px; } + +// Planets — base geometry +.nw-planet-circle, +.nw-planet-circle--rx { stroke-width: 1px; } +.nw-planet-label { stroke-width: 0.4px; paint-order: stroke fill; } + +// Per-planet circle: fill = ternary, border = senary +.nw-planet--au { fill: rgba(var(--terAu), 1); stroke: rgba(var(--sixAu), 1); } // Sun +.nw-planet--ag { fill: rgba(var(--terAg), 1); stroke: rgba(var(--sixAg), 1); } // Moon +.nw-planet--hg { fill: rgba(var(--terHg), 1); stroke: rgba(var(--sixHg), 1); } // Mercury +.nw-planet--cu { fill: rgba(var(--terCu), 1); stroke: rgba(var(--sixCu), 1); } // Venus +.nw-planet--fe { fill: rgba(var(--terFe), 1); stroke: rgba(var(--sixFe), 1); } // Mars +.nw-planet--sn { fill: rgba(var(--terSn), 1); stroke: rgba(var(--sixSn), 1); } // Jupiter +.nw-planet--pb { fill: rgba(var(--terPb), 1); stroke: rgba(var(--sixPb), 1); } // Saturn +.nw-planet--u { fill: rgba(var(--terU), 1); stroke: rgba(var(--sixU), 1); } // Uranus +.nw-planet--np { fill: rgba(var(--terNp), 1); stroke: rgba(var(--sixNp), 1); } // Neptune +.nw-planet--pu { fill: rgba(var(--terPu), 1); stroke: rgba(var(--sixPu), 1); } // Pluto + +// Per-planet label: fill + stroke halo = senary +.nw-planet-label--au { fill: rgba(var(--sixAu), 1); stroke: rgba(var(--sixAu), 0.6); } +.nw-planet-label--ag { fill: rgba(var(--sixAg), 1); stroke: rgba(var(--sixAg), 0.6); } +.nw-planet-label--hg { fill: rgba(var(--sixHg), 1); stroke: rgba(var(--sixHg), 0.6); } +.nw-planet-label--cu { fill: rgba(var(--sixCu), 1); stroke: rgba(var(--sixCu), 0.6); } +.nw-planet-label--fe { fill: rgba(var(--sixFe), 1); stroke: rgba(var(--sixFe), 0.6); } +.nw-planet-label--sn { fill: rgba(var(--sixSn), 1); stroke: rgba(var(--sixSn), 0.6); } +.nw-planet-label--pb { fill: rgba(var(--sixPb), 1); stroke: rgba(var(--sixPb), 0.6); } +.nw-planet-label--u { fill: rgba(var(--sixU), 1); stroke: rgba(var(--sixU), 0.6); } +.nw-planet-label--np { fill: rgba(var(--sixNp), 1); stroke: rgba(var(--sixNp), 0.6); } +.nw-planet-label--pu { fill: rgba(var(--sixPu), 1); stroke: rgba(var(--sixPu), 0.6); } .nw-rx { fill: rgba(var(--terUser), 1); } // Aspects diff --git a/src/templates/apps/gameboard/_partials/_natus_overlay.html b/src/templates/apps/gameboard/_partials/_natus_overlay.html index 64b0449..a8b2b1f 100644 --- a/src/templates/apps/gameboard/_partials/_natus_overlay.html +++ b/src/templates/apps/gameboard/_partials/_natus_overlay.html @@ -130,6 +130,10 @@ const PLACE_DELAY = 400; // ms — Nominatim polite rate const CHART_DELAY = 300; // ms — chart preview debounce + // Preload zodiac SVG icons eagerly — they'll be cached before any draw() call. + // To swap an icon, replace the .svg file in zodiac-signs/ and hard-refresh. + NatusWheel.preload(); + // ── localStorage persistence ────────────────────────────────────────────── // Key scoped to room so multiple rooms don't clobber each other.