natus wheel: aspect line system, element tooltip overhaul, ring/spoke changes — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful

- DON/DOFF toggle: aspect lines persist across planet switches & outside clicks; cleared only by DON (new planet) or DOFF; planet-keyed --asp-* colors (--sixU/--terU on light palettes)
- planet tooltip: aspect rows w. 2× thick line legend, planet symbol + .tt-asp-in 'in' + sign icon + orb; applying/separating direction symbols
- element tooltip: 80px square badge (float right); DON/DOFF hidden; symbol-based contributor rows (☉ @ 15.3°  +1); Stellium/Parade +N underlined headers; parade sign-grouped w. parenthetical planet symbols, counterclockwise order
- ring swap: planets outer (0.50–0.68r), houses inner (0.32–0.48r) to reduce stellia crowding
- house spokes: angle cusps only (ASC/IC/DSC/MC); non-angle spokes removed
- outside-click guard: bounding-rect check for DON/DOFF/PRV/NXT so pointer-events:none buttons don't trigger close
- add element-square & energy-vector icon dirs (Ardor/Ossum/Tempo/Nexus/Pneuma/Humor SVGs)
- T11a–f Jasmine specs for DON/DOFF line persistence

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-21 03:17:20 -04:00
parent 9c7d58f0b3
commit ea2bfa6ce1
20 changed files with 1498 additions and 140 deletions

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 575.92 576.62">
<defs>
<style>
.cls-1 {
fill: none;
}
.cls-2 {
fill: #007e6e;
}
.cls-3 {
fill: #02b8a1;
}
</style>
</defs>
<path class="cls-3" d="M60.91,511.29l71.65-136.9,97.75-187.79,63.33-121.28,125.49,240.37,107.55,205.57-465.77.03ZM356.95,428.87c-13.35.49-27.13-3.86-27.46-15.87l-.93-32.96c-.02-.56,1.33-1.81,1.58-1.95l28.47-.06,2.82-17.62-32.06-.11-.81-2.11.21-137.2-16.05.25-59.68,85.59-41.14,59.57,1.32,11.65,89.23.02-.35,32.7c-.16,14.62-13.39,18.37-27.93,18.17v9.04s83.05-.03,83.05-.03l-.26-9.06Z"/>
<polygon class="cls-2" points="49.23 509.31 76.73 394.84 282.73 65.32 49.23 509.31"/>
<path class="cls-1" d="M356.95,428.87l.26,9.06-83.05.03v-9.04c14.54.2,27.77-3.55,27.93-18.17l.35-32.7-90.55-.02v-11.65l41.14-59.57,59.68-85.59,16.05-.25-.21,137.2.81,2.11,32.06.11-2.82,17.62-28.47.06c-.24.14-1.59,1.4-1.58,1.95l.93,32.96c.34,12.01,14.12,16.36,27.46,15.87ZM302.55,360.36l-.29-103.19-70.06,103.09,70.35.1Z"/>
<polygon class="cls-3" points="302.55 360.36 232.2 360.25 302.26 257.17 302.55 360.36"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 575.92 576.62">
<defs>
<style>
.cls-1 {
fill: none;
}
.cls-2 {
fill: #ed1f81;
}
.cls-3 {
fill: #9d1d59;
}
</style>
</defs>
<path class="cls-2" d="M451.82,504.82H53.65s.04-433.24.04-433.24h398.13s0,433.24,0,433.24ZM314.3,279.4c-25.3-32.05-69.43-29.71-101.12-5.51,11.97-52.09,51.89-98.16,106.46-99.95l.19-9.77c-40.62-1.43-80.27,14.5-108.46,45.06-29.51,31.99-43.84,74.88-39.56,117.91,3.23,32.51,20.25,66.56,51.61,79.61,19.31,8.04,41.43,7.9,60.57-.55,45.98-20.31,61.02-87.9,30.31-126.8Z"/>
<polygon class="cls-3" points="522.26 164.53 522.23 412.55 463.03 505.05 463.03 71.71 488.6 111.55 522.26 164.53"/>
<path class="cls-1" d="M314.3,279.4c30.71,38.9,15.67,106.49-30.31,126.8-19.14,8.46-41.26,8.59-60.57.55-31.36-13.06-48.38-47.11-51.61-79.61-4.28-43.03,10.06-85.92,39.56-117.91,28.18-30.56,67.84-46.49,108.46-45.06l-.19,9.77c-54.57,1.79-94.49,47.86-106.46,99.95,31.68-24.2,75.81-26.53,101.12,5.51ZM266.02,398.71c20.66-5.9,29.08-30.26,29.63-50.89.4-15.16-1.85-30.25-7.19-44.48-1.73-4.6-4.14-8.48-6.9-12.39-6.92-9.8-17.54-15.98-29.66-16.66-11.29-.63-22.42,2.3-31.32,8.91l-11.2,8.3c-3.77,23.16-4.16,47.03,3.29,69.31,3.35,10.01,7.66,19.24,14.98,27.02,10.55,11.21,24.05,14.98,38.37,10.89Z"/>
<path class="cls-2" d="M266.02,398.71c-14.32,4.09-27.82.33-38.37-10.89-7.32-7.78-11.64-17.01-14.98-27.02-7.45-22.28-7.06-46.14-3.29-69.31l11.2-8.3c8.91-6.61,20.04-9.54,31.32-8.91,12.12.68,22.73,6.86,29.66,16.66,2.77,3.92,5.18,7.8,6.9,12.39,5.34,14.22,7.6,29.32,7.19,44.48-.55,20.63-8.97,44.99-29.63,50.89Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 575.92 576.62">
<defs>
<style>
.cls-1 {
fill: #e18528;
}
.cls-2 {
fill: none;
}
.cls-3 {
fill: #965924;
}
</style>
</defs>
<path class="cls-1" d="M55.89,331.57L271.03.48l195,331.05-410.14.04ZM306.26,259.35c5.73-11.88,4.08-25.51-2.7-36.66-4.08-6.51-8.98-11.72-14.89-16.48l-14.03-11.31c8.86-5.42,16.89-10.62,23.44-18.07,8.79-10,11.18-23.83,4.65-35.7-10.66-19.37-36.98-24.01-57.4-17.16-7.89,2.64-14.89,7.46-19.76,14.16-10.53,14.49-8.6,34.29,3.62,47.26,6.01,6.38,12.21,11.51,18.96,16.7l-12.3,9.02c-4.28,3.14-8.01,6.64-11.22,10.93-8.34,10.28-10.89,23.46-5.75,35.87,4.37,10.55,13.06,18.45,23.89,22.32,22.98,8.22,52.33,2.27,63.5-20.89Z"/>
<polygon class="cls-3" points="271.03 576.14 55.89 341.35 466.03 341.34 271.03 576.14"/>
<path class="cls-3" d="M476.03,331.57S276.4,2.34,277.03.48l243,322.43-44,8.65Z"/>
<polygon class="cls-3" points="277.03 573.53 476.03 341.34 520.03 327.91 277.03 573.53"/>
<path class="cls-2" d="M306.26,259.35c-11.17,23.16-40.52,29.11-63.5,20.89-10.83-3.87-19.52-11.78-23.89-22.32-5.15-12.42-2.6-25.59,5.75-35.87,3.22-4.29,6.94-7.79,11.22-10.93l12.3-9.02c-6.75-5.2-12.95-10.33-18.96-16.7-12.21-12.97-14.15-32.76-3.62-47.26,4.87-6.7,11.87-11.51,19.76-14.16,20.42-6.85,46.75-2.2,57.4,17.16,6.53,11.86,4.14,25.69-4.65,35.7-6.55,7.45-14.58,12.65-23.44,18.07l14.03,11.31c5.91,4.76,10.81,9.97,14.89,16.48,6.77,11.15,8.43,24.78,2.7,36.66ZM280.81,177.03c7.65-9.56,10.35-22,5.96-33.5-5.11-13.4-19.81-17.24-33.32-13.39-13.12,3.74-20.07,17.59-15,30.52,2.18,5.55,5.82,10.47,10.41,14.21l18.27,14.9c5.07-3.85,9.61-7.66,13.67-12.73ZM288.03,263.92c8.56-14.97-.95-30.48-13.68-40.79l-19.94-16.14-9.02,9.16c-9.75,11.69-12.77,27.27-7.58,41.57,4.47,12.3,16.15,19.16,28.98,18.3,8.67-.58,16.78-4.31,21.24-12.1Z"/>
<path class="cls-1" d="M288.03,263.92c-4.45,7.79-12.57,11.52-21.24,12.1-12.84.86-24.51-5.99-28.98-18.3-5.2-14.3-2.17-29.88,7.58-41.57l9.02-9.16,19.94,16.14c12.73,10.3,22.24,25.81,13.68,40.79Z"/>
<path class="cls-1" d="M280.81,177.03c-4.06,5.07-8.6,8.88-13.67,12.73l-18.27-14.9c-4.59-3.74-8.23-8.66-10.41-14.21-5.07-12.92,1.87-26.78,15-30.52,13.51-3.85,28.2,0,33.32,13.39,4.39,11.5,1.68,23.95-5.96,33.5Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 786 786.96">
<defs>
<style>
.cls-1 {
font-size: 108px;
}
.cls-1, .cls-2 {
fill: #e93726;
font-family: SitkaText, Sitka;
font-variation-settings: 'opsz' 11, 'wght' 400;
}
.cls-3 {
fill: #e18528;
}
.cls-4 {
letter-spacing: -.01em;
}
.cls-5 {
fill: #f58530;
}
.cls-6, .cls-7 {
fill: none;
}
.cls-8 {
fill: #965924;
}
.cls-9 {
letter-spacing: 0em;
}
.cls-2 {
font-size: 310.77px;
}
.cls-10 {
fill: #9b2220;
stroke: #fff;
stroke-miterlimit: 10;
}
.cls-7 {
stroke: #965924;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 7.09px;
}
</style>
</defs>
<rect class="cls-10" x="92.3" y="92.78" width="601.4" height="601.4" rx="12" ry="12"/>
<g>
<path class="cls-5" d="M580.99,249.49c-19.32-5.9-30.05-22.42-27.78-41.95,1.26-10.83,5.25-21.18,13.26-29.63-.9,7.39-.16,17.58,6.53,18.66.94-8.84.15-15.28,6.11-23.24l7.92-11.21c6.24-8.83,7.5-19.28,3.37-29.88,15.44,8.26,23.82,24.55,21.89,41.85l-1.67,10.88c.29,2.7,1.62,5.21,4.15,5.78s4.78-1.16,5.94-3.78c2-4.52.44-9.18.65-14.62,10.88,11.73,16.51,26.9,15.31,42.17-1.29,16.3-12.35,30.71-28.53,34.91,6.83-7.12,8.79-15.99,5.72-24.9-1.81-5.25-5.27-9.43-8.83-13.86-5.7-6.72-8.45-14.75-6.72-23.31-7.89,5.36-9.91,14.69-7.9,23.13l3.66,11c1.09,3.28-1.67,6.64-4.54,7.12-3.56.6-5.85-1.73-6.87-5l-.94-7.53c-8.34,10.07-9.51,23.62-.92,32.49.96-.69.98.39.19.91Z"/>
<path class="cls-7" d="M588.15,117.12c-41.42,3.95-71,40.77-67.08,81.3,3.95,40.77,40.15,70.73,80.91,67.01,40.76-3.72,70.94-39.73,67.45-80.54-3.49-40.81-39.62-71.73-81.28-67.76Z"/>
</g>
<g>
<text class="cls-2" transform="translate(222.21 483.35)"><tspan x="0" y="0">Ar</tspan></text>
<text class="cls-1" transform="translate(248.16 633.66)"><tspan x="0" y="0">A</tspan><tspan class="cls-4" x="69.45" y="0">r</tspan><tspan class="cls-9" x="117.31" y="0">dor</tspan></text>
</g>
<g>
<path class="cls-3" d="M130.74,202.06l55.67-85.67,50.46,85.66h-106.13ZM195.52,183.37c1.48-3.07,1.05-6.6-.7-9.49-1.06-1.68-2.32-3.03-3.85-4.27l-3.63-2.93c2.29-1.4,4.37-2.75,6.06-4.68,2.28-2.59,2.89-6.17,1.2-9.24-2.76-5.01-9.57-6.21-14.85-4.44-2.04.68-3.85,1.93-5.11,3.66-2.72,3.75-2.22,8.87.94,12.23,1.55,1.65,3.16,2.98,4.91,4.32l-3.18,2.33c-1.11.81-2.07,1.72-2.9,2.83-2.16,2.66-2.82,6.07-1.49,9.28,1.13,2.73,3.38,4.77,6.18,5.78,5.95,2.13,13.54.59,16.43-5.41Z"/>
<polygon class="cls-8" points="186.41 265.34 130.74 204.59 236.87 204.59 186.41 265.34"/>
<path class="cls-8" d="M239.46,202.06s-51.66-85.19-51.5-85.67l62.88,83.44-11.39,2.24Z"/>
<polygon class="cls-8" points="187.96 264.67 239.46 204.59 250.84 201.11 187.96 264.67"/>
<path class="cls-6" d="M195.52,183.37c-2.89,5.99-10.48,7.53-16.43,5.41-2.8-1-5.05-3.05-6.18-5.78-1.33-3.21-.67-6.62,1.49-9.28.83-1.11,1.8-2.02,2.9-2.83l3.18-2.33c-1.75-1.34-3.35-2.67-4.91-4.32-3.16-3.36-3.66-8.48-.94-12.23,1.26-1.73,3.07-2.98,5.11-3.66,5.28-1.77,12.1-.57,14.85,4.44,1.69,3.07,1.07,6.65-1.2,9.24-1.69,1.93-3.77,3.27-6.06,4.68l3.63,2.93c1.53,1.23,2.8,2.58,3.85,4.27,1.75,2.89,2.18,6.41.7,9.49ZM188.94,162.07c1.98-2.47,2.68-5.69,1.54-8.67-1.32-3.47-5.12-4.46-8.62-3.46s-5.19,4.55-3.88,7.9c.56,1.44,1.51,2.71,2.69,3.68l4.73,3.86c1.31-1,2.49-1.98,3.54-3.29ZM190.81,184.55c2.21-3.88-.25-7.89-3.54-10.55l-5.16-4.18-2.33,2.37c-2.52,3.02-3.31,7.06-1.96,10.76,1.16,3.18,4.18,4.96,7.5,4.74,2.24-.15,4.34-1.12,5.5-3.13Z"/>
<path class="cls-3" d="M190.81,184.55c-1.15,2.02-3.25,2.98-5.5,3.13-3.32.22-6.34-1.55-7.5-4.74-1.34-3.7-.56-7.73,1.96-10.76l2.33-2.37,5.16,4.18c3.29,2.67,5.75,6.68,3.54,10.55Z"/>
<path class="cls-3" d="M188.94,162.07c-1.05,1.31-2.23,2.3-3.54,3.29l-4.73-3.86c-1.19-.97-2.13-2.24-2.69-3.68-1.31-3.34.48-6.93,3.88-7.9s7.3,0,8.62,3.46c1.14,2.98.44,6.2-1.54,8.67Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 786 786.96">
<defs>
<style>
.cls-1 {
stroke: #512d7c;
stroke-width: 8.18px;
}
.cls-1, .cls-2 {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.cls-3 {
fill: #272f69;
stroke: #fff;
stroke-miterlimit: 10;
}
.cls-4 {
fill: #231f20;
font-family: MyriadPro-Regular, 'Myriad Pro';
font-size: 75px;
}
.cls-5 {
font-size: 310.77px;
}
.cls-5, .cls-6 {
fill: #5567af;
font-family: SitkaText, Sitka;
font-variation-settings: 'opsz' 11, 'wght' 400;
}
.cls-6 {
font-size: 108px;
}
.cls-2 {
stroke: #7450a1;
stroke-width: 6px;
}
.cls-7 {
fill: #e93726;
}
.cls-8 {
fill: #7450a1;
}
</style>
</defs>
<rect class="cls-3" x="92.3" y="92.78" width="601.4" height="601.4" rx="12" ry="12"/>
<g>
<text class="cls-5" transform="translate(134.58 483.35)"><tspan x="0" y="0">Hm</tspan></text>
<text class="cls-6" transform="translate(216.39 633.66)"><tspan x="0" y="0">Humor</tspan></text>
</g>
<rect class="cls-7" x="92.3" y="92.81" width="98.49" height="98.49"/>
<g>
<path class="cls-8" d="M618.7,244.79l9.99-.73c-29.83,18.68-68.85,9.52-87.52-21.03,11.14-6.18,24.3-5.58,34.4,1.85l12.6,9.26c9.04,6.64,19.28,10.29,30.53,10.65Z"/>
<path class="cls-8" d="M599.45,214.59c5.27.42,9.33,1.57,13.69-.05,8.88-3.26,6.62-14.12,16.13-12.67,2.78.43,6.28,2.78,6.56,6.47.54,7.08-5.71,12.22-12.82,13.36-8.5,1.36-17-1.6-23.57-7.12Z"/>
<path class="cls-1" d="M649.68,241.67c-30.08,32.38-79.38,32.02-109.26-.43-22.86-24.82-25.69-61.22-7.87-89.75,13.05-20.89,35.85-33.88,60.33-34.67,24.03-.78,47.55,10.41,62.01,30.32,20.91,28.79,19.4,68.02-5.22,94.52Z"/>
<path class="cls-8" d="M565,205.05c5.97.4,11.82,2.39,16.78,5.1,9.16,5,16.42,12.16,25.72,15.76,5.24,2.02,10.84,2.79,16.36,2.07,7.13-.95,14.42-4.77,17.45-11.52,3.43-7.04,1.75-16.07-5.79-19.58-4.16-2.38-9.37-2.85-13.85-1.26-4.36,1.51-8.09,4.77-10.06,9.3.53-13.96,15.64-22.6,28.39-17.39,12.64,5.95,16.59,21.1,10.7,33.3-4.49,9.97-14.93,16.05-25.45,17.41-13.35,1.86-25.58-3.25-36-10.8-7.97-5.53-15.35-11.45-24.92-12.07-11.98-2.34-24.76-.55-36.28,3.57-.76.27-1.53.55-2.29.85,10.99-10.62,24.5-15.61,39.24-14.76h0Z"/>
<path class="cls-2" d="M589.8,201.51c-12.11-.34-20.79-8.81-22.17-20.67-.7-5.98,2.31-11.82,5.05-17.28l8.31-11.95c3.27-4.71,6.06-9.3,8.24-15.11l16.35,24.56c3.12,4.69,4.92,9.51,6.1,15.16,2.79,13.29-7.92,25.67-21.88,25.28Z"/>
<path class="cls-8" d="M603.17,168.39l-6.68-1.6.32.06c-7.64-1.19-14.51,4.49-14.3,12.17.07,7.14,9.57,10.96,14.17,5.33,4.92-4.75,4.17-11.72,1.63-17.62,6.93,5.38,8.05,14.59,3.04,21.38-3.59,4.56-10.16,6.11-15.48,4.05-7.6-2.62-11.16-11.46-8.53-18.83,2.55-8.65,11.47-13.74,20.22-12.43.11.01.22.03.32.06l6.68,1.6c3.82,1.02,2.47,6.66-1.4,5.83h0Z"/>
</g>
<text class="cls-4" transform="translate(103.07 164.07)"><tspan x="0" y="0">20</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 786 786.96">
<defs>
<style>
.cls-1 {
font-size: 310.77px;
}
.cls-1, .cls-2 {
fill: #02a04a;
font-family: SitkaText, Sitka;
font-variation-settings: 'opsz' 11, 'wght' 400;
}
.cls-2 {
font-size: 108px;
}
.cls-3 {
letter-spacing: 0em;
}
.cls-4, .cls-5 {
fill: none;
}
.cls-6 {
fill: #007e6e;
}
.cls-7 {
letter-spacing: -.02em;
}
.cls-8 {
fill: #086d38;
stroke: #fff;
stroke-miterlimit: 10;
}
.cls-5 {
stroke: #007e6e;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 7.64px;
}
.cls-9 {
fill: #02b8a1;
}
</style>
</defs>
<rect class="cls-8" x="92.3" y="92.78" width="601.4" height="601.4" rx="12" ry="12"/>
<g>
<text class="cls-1" transform="translate(188.79 483.35)"><tspan x="0" y="0">Nx</tspan></text>
<text class="cls-2" transform="translate(237.82 633.66)"><tspan class="cls-7" x="0" y="0">N</tspan><tspan class="cls-3" x="80.54" y="0">e</tspan><tspan x="136.37" y="0">xus</tspan></text>
</g>
<g>
<g>
<path class="cls-9" d="M614.31,184.62c-14.86,8.98-36.61,18.58-53.5,19.71-2.33-4.65-2.85-9.35-2.72-14.67.47-18.24,14.86-33.55,32.97-35.01l4.88-.4c.95.91,1.83.2,2.78.29,12.6,1.24,23.29,8.9,28.77,19.95-4.14,4.18-8.42,7.24-13.2,10.13Z"/>
<path class="cls-9" d="M553.79,194.24c4.02,4.41-4.84,9.31-.81,11.92,3.05,1.98,7.17,2.18,10.5,1.72,9.99-1.36,19.25-4.21,28.61-8.1,10.11-4.21,19.83-8.84,28.77-15.14,5.21-3.67,15.34-11.69,12.87-15.68-1.44-2.33-4.5-2.11-6.69-2.58l-3-3.55,13.37-1.53,9.18.11c2.39.03,4.82,1.17,5.96,3.24,3.26,5.9-7.93,15.44-14.45,20.35-22.14,16.68-54.36,30.57-80.51,34.95-2.1.35-4.6-.14-6.73.73-6.07-.48-12.84-.15-14.33-5.1-1.93-6.42,10.04-16,17.26-21.34Z"/>
<path class="cls-9" d="M610.58,224.52c-12.13,6.06-27.36,4.61-37.9-4.1,21.04-5.96,39.86-14.39,58.5-26-1.03,13.37-9.06,24.34-20.6,30.11Z"/>
<path class="cls-9" d="M618.19,132.28l3.55,7.06,7.02,3.16-7.33,3.3-3.28,7.22c-1.19-2.27-1.23-5.41-3.19-7.35l-6.57-3.22,6.67-3.05,3.14-7.13Z"/>
</g>
<path class="cls-5" d="M590.59,116.9c-41.5,2.57-72.27,38.33-69.76,78.93,2.53,41.08,37.95,72.35,79,69.8,40.81-2.53,72.01-37.57,69.79-78.43-2.22-40.85-37.29-72.89-79.03-70.3Z"/>
</g>
<g>
<path class="cls-9" d="M136.04,226.58l15.2-29.04,20.73-39.83,13.43-25.72,26.62,50.98,22.81,43.6h-98.79ZM198.83,209.1c-2.83.1-5.75-.82-5.83-3.37l-.2-6.99c0-.12.28-.38.33-.41h6.04s.6-3.75.6-3.75l-6.8-.02-.17-.45.05-29.1-3.4.05-12.66,18.15-8.73,12.63.28,2.47h18.93s-.07,6.94-.07,6.94c-.03,3.1-2.84,3.9-5.92,3.85v1.92s17.62,0,17.62,0l-.06-1.92Z"/>
<polygon class="cls-6" points="133.56 226.16 139.39 201.88 183.09 132 133.56 226.16"/>
<path class="cls-4" d="M198.83,209.1l.06,1.92h-17.61s0-1.91,0-1.91c3.09.04,5.89-.75,5.92-3.85l.07-6.93h-19.2v-2.47l8.73-12.63,12.66-18.15,3.4-.05-.05,29.1.17.45,6.8.02-.6,3.74h-6.04c-.05.04-.34.31-.33.43l.2,6.99c.07,2.55,2.99,3.47,5.83,3.37ZM187.29,194.57l-.06-21.89-14.86,21.86,14.92.02Z"/>
<polygon class="cls-9" points="187.29 194.57 172.37 194.55 187.23 172.68 187.29 194.57"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 786 786.96">
<defs>
<style>
.cls-1, .cls-2, .cls-3, .cls-4 {
fill: none;
}
.cls-5 {
fill: #ed1f81;
}
.cls-6 {
fill: #6b2365;
stroke: #fff;
stroke-miterlimit: 10;
}
.cls-2 {
stroke-width: 6.55px;
}
.cls-2, .cls-3, .cls-4 {
stroke-linecap: round;
stroke-linejoin: round;
}
.cls-2, .cls-4 {
stroke: #ed1f81;
}
.cls-3 {
stroke: #9d1d59;
stroke-width: 8.18px;
}
.cls-4 {
stroke-width: 4.91px;
}
.cls-7 {
font-size: 310.77px;
}
.cls-7, .cls-8 {
fill: #9e3e97;
font-family: SitkaText, Sitka;
font-variation-settings: 'opsz' 11, 'wght' 400;
}
.cls-8 {
font-size: 108px;
}
.cls-9 {
fill: #9d1d59;
}
</style>
</defs>
<rect class="cls-6" x="92.3" y="92.78" width="601.4" height="601.4" rx="12" ry="12"/>
<g>
<text class="cls-7" transform="translate(143.49 483.35)"><tspan x="0" y="0">Om</tspan></text>
<text class="cls-8" transform="translate(223.8 633.66)"><tspan x="0" y="0">Ossum</tspan></text>
</g>
<g>
<path class="cls-3" d="M643.1,134.32c35.09,30.18,34.97,82.97.8,113.27-20.57,18.25-49.88,22.94-74.59,13.63-27.45-10.34-45.86-35.24-48.13-63.78-2.28-28.78,11.49-55.62,36.37-70.43,26.9-16.02,61.47-13.39,85.54,7.31Z"/>
<g>
<path class="cls-4" d="M621.65,156.13l-8.14,18.74c-1.78,4.11-1.84,8.31-1.41,12.73l1.59,16.07"/>
<line class="cls-2" x1="584.35" y1="143.95" x2="621.65" y2="156.13"/>
<path class="cls-2" d="M584.35,143.95l-11.33,17.17c-2.55,3.84-7.55,5.37-11,8.32l-4.74,4.04c-.08,4.98.56,9.95-1.31,14.53l-5.13,12.55c-.59,1.45.23,3.19-.78,4.27"/>
<path class="cls-4" d="M613.7,203.68l4.68,2.83c3.5,2.09,5.49,4.84,6.83,8.81l4.06,12.05"/>
<path class="cls-5" d="M586.67,143.15l3.43,9.97c.34.8.68,1.69.69,2.58,0,0,.28,11.65.28,11.65.01.59.7.82.78,1.25.06.24-.2.46-.15.76.04.38-.05.74-.5.65-.38-1.04-.7-1.92-1.18-3.23-1.61-4.28-2.83-8.2-4.57-12.06l-3.43-9.97c-.98-3.08,3.51-4.64,4.64-1.6h0Z"/>
<path class="cls-2" d="M621.65,156.13l18.53,41.29c-3,10.38-6.34,20.4-10.92,29.95"/>
<path class="cls-2" d="M550.06,204.83l6.96,11.57c2.21,3.67,3.47,9.08,7.1,11.57l16.21,6.98c2.68,1.16,5.63,3.26,8.69,2.45l37.03-9.76,3.22-.28"/>
<polyline class="cls-4" points="550.06 204.83 560.08 207.84 580.85 217.72 585.52 233.49"/>
<path class="cls-5" d="M614.66,205.93l-3.55,1.52c-4.22,1.75-8.26,4.77-12.82,5.2.2-.19,8.81-8.28,8.64-8.11.54-.39,1.71-1.21,2.25-1.6l3.55-1.52c3.01-1.2,4.88,3.17,1.93,4.51h0Z"/>
</g>
</g>
<g>
<path class="cls-5" d="M236.82,235.06h-86.5s0-94.12,0-94.12h86.49s0,94.12,0,94.12ZM206.94,186.08c-5.5-6.96-15.08-6.46-21.97-1.2,2.6-11.32,11.27-21.32,23.13-21.71l.04-2.12c-8.82-.31-17.44,3.15-23.56,9.79-6.41,6.95-9.52,16.27-8.59,25.62.7,7.06,4.4,14.46,11.21,17.3,4.19,1.75,9,1.72,13.16-.12,9.99-4.41,13.26-19.1,6.58-27.55Z"/>
<polygon class="cls-9" points="252.12 161.13 252.12 215.01 239.26 235.1 239.26 140.97 244.81 149.62 252.12 161.13"/>
<path class="cls-1" d="M206.94,186.08c6.67,8.45,3.4,23.13-6.58,27.55-4.16,1.84-8.96,1.87-13.16.12-6.81-2.84-10.51-10.23-11.21-17.3-.93-9.35,2.18-18.67,8.59-25.62,6.12-6.64,14.74-10.1,23.56-9.79l-.04,2.12c-11.86.39-20.53,10.4-23.13,21.71,6.88-5.26,16.47-5.76,21.97,1.2ZM196.46,212.01c4.49-1.28,6.32-6.57,6.44-11.06.09-3.29-.4-6.57-1.56-9.66-.37-1-.9-1.84-1.5-2.69-1.5-2.13-3.81-3.47-6.44-3.62-2.45-.14-4.87.5-6.8,1.93l-2.43,1.8c-.82,5.03-.9,10.22.71,15.06.73,2.17,1.66,4.18,3.26,5.87,2.29,2.44,5.23,3.25,8.34,2.37Z"/>
<path class="cls-5" d="M196.46,212.01c-3.11.89-6.04.07-8.34-2.37-1.59-1.69-2.53-3.69-3.26-5.87-1.62-4.84-1.53-10.02-.71-15.06l2.43-1.8c1.94-1.43,4.35-2.07,6.8-1.93,2.63.15,4.94,1.49,6.44,3.62.6.85,1.13,1.69,1.5,2.69,1.16,3.09,1.65,6.37,1.56,9.66-.12,4.48-1.95,9.77-6.44,11.06Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 786 786.96">
<defs>
<style>
.cls-1 {
fill: #037988;
stroke: #fff;
stroke-miterlimit: 10;
}
.cls-2 {
stroke: #188dcd;
stroke-width: 7.09px;
}
.cls-2, .cls-3 {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.cls-4 {
font-size: 108px;
}
.cls-4, .cls-5 {
fill: #0bb3c8;
font-family: SitkaText, Sitka;
font-variation-settings: 'opsz' 11, 'wght' 400;
}
.cls-6 {
fill: #231f20;
font-family: MyriadPro-Regular, 'Myriad Pro';
font-size: 75px;
}
.cls-5 {
font-size: 310.77px;
}
.cls-3 {
stroke: #06608d;
stroke-width: 7.64px;
}
.cls-7 {
fill: #e93726;
}
</style>
</defs>
<rect class="cls-1" x="92.3" y="92.78" width="601.4" height="601.4" rx="12" ry="12"/>
<g>
<text class="cls-5" transform="translate(208.54 483.35)"><tspan x="0" y="0">Pn</tspan></text>
<text class="cls-4" transform="translate(191.14 633.66)"><tspan x="0" y="0">Pneuma</tspan></text>
</g>
<rect class="cls-7" x="92.07" y="92.51" width="98.49" height="98.49"/>
<g>
<path class="cls-3" d="M589.51,116.75c41.41-3.06,76.65,28.14,79.72,68.58,3.11,40.94-27.57,76.7-68.47,79.88-40.9,3.18-76.74-27.4-80-68.33-3.24-40.67,27.1-77.06,68.75-80.14Z"/>
<g>
<path class="cls-2" d="M540.98,191.66l94.51-.1c5.42,0,9.38-3.32,11.77-7.42,2.46-4.22,2.34-9.31-.33-14.08s-8.55-7.14-13.74-6.05c-5.56,1.16-9.58,5.2-10.56,11.06l-1.37,3.31"/>
<path class="cls-2" d="M555.16,175.84l41.08-.22c5.22-.04,9.65-3.47,11.75-7.8,1.97-4.05,2.07-9.05-.38-13.57-2.52-4.65-7.73-7.06-12.64-6.38-5.36.74-9.63,4.27-11.31,9.68l-1.59,5.05"/>
<path class="cls-2" d="M554.61,207.47l54.7.22c6.92.04,11.86,6.32,12.41,12.51.65,7.38-3.84,13.8-10.9,14.98-6.86,1.15-14.24-3.17-15.3-10.44l-.96-2.91"/>
</g>
</g>
<text class="cls-6" transform="translate(102.84 163.77)"><tspan x="0" y="0">12</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 786 786.96">
<defs>
<style>
.cls-1 {
fill: #a78a30;
stroke: #fff;
stroke-miterlimit: 10;
}
.cls-2 {
letter-spacing: 0em;
}
.cls-3 {
letter-spacing: 0em;
}
.cls-4 {
stroke: #97ad3b;
stroke-width: 5.45px;
}
.cls-4, .cls-5 {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.cls-6 {
fill: #97ad3b;
}
.cls-5 {
stroke: #617633;
stroke-width: 8.18px;
}
.cls-7 {
font-size: 310.77px;
}
.cls-7, .cls-8 {
fill: #ffd035;
font-family: SitkaText, Sitka;
font-variation-settings: 'opsz' 11, 'wght' 400;
}
.cls-9 {
fill: #231f20;
font-family: MyriadPro-Regular, 'Myriad Pro';
font-size: 75px;
}
.cls-10 {
letter-spacing: -.03em;
}
.cls-11 {
letter-spacing: -.07em;
}
.cls-8 {
font-size: 108px;
}
.cls-12 {
fill: #e93726;
}
</style>
</defs>
<rect class="cls-1" x="92.3" y="92.78" width="601.4" height="601.4" rx="12" ry="12"/>
<g>
<text class="cls-7" transform="translate(209.37 483.35)"><tspan class="cls-10" x="0" y="0">T</tspan><tspan class="cls-3" x="184.81" y="0">p</tspan></text>
<text class="cls-8" transform="translate(224.4 633.66)"><tspan class="cls-11" x="0" y="0">T</tspan><tspan class="cls-2" x="60.38" y="0">empo</tspan></text>
</g>
<rect class="cls-12" x="92.3" y="92.78" width="98.49" height="98.49"/>
<g>
<path class="cls-5" d="M523.34,209.57c10.37,40.36,52.59,65.03,92.15,53.27,25.27-7.51,44.99-27.56,51.59-53.25,7.85-30.54-4.25-62.27-30.01-79.95-25.03-17.18-58.69-17.16-83.68-.03-25.78,17.67-37.89,49.45-30.05,79.97Z"/>
<g>
<path class="cls-6" d="M594.92,204.58c6.01,9.69,17.98,10.43,20.31,23.51l-40.45.06c1.14-6.81,5.67-11.44,11.01-14.91,3.51-2.22,6.23-3.92,9.13-8.66Z"/>
<path class="cls-6" d="M593.13,189.03c-2.81-8.51-14.28-10.71-16.68-21.2,5.71-2.79,11.47-1.63,16.56.73,6.41,2.99,12.97,3.07,19.39-.03.77-.33,1.11.7.72,1.12-3.37,8.56-13.05,11.13-16.16,18.96-.32.8.24,1.64-.56,2.28-.58.46-1.13,1.22-1.98.69-.96-.59-.94-1.53-1.27-2.55Z"/>
<path class="cls-6" d="M593.3,200.02c-.11-.44-.28-1.76-.12-2.18.43-1.1,2.44-1.95,3.39-.42.67,1.07,1.26,1.87-.19,3.26-.82.79-2.64,1.06-3.08-.66Z"/>
<path class="cls-4" d="M569.36,146.38l51.4-.12c1.71,4.37,2.19,8.69,1.61,13.12-3.2,24.61-18.64,25.22-18.48,31.87.12,5.19,8.21,7.28,12.69,14.55,5.67,9.19,7.16,20.13,4.69,30.59l-52.04-.08-1.36-8.12c-.39-2.33.19-4,.4-6.05,2.3-22.2,17.94-24.12,18.26-30.85.28-6.03-13.4-7.93-17.34-25.88-1.37-6.26-2.02-12.33.16-19.02Z"/>
</g>
</g>
<text class="cls-9" transform="translate(122.31 164.04)"><tspan x="0" y="0">2</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297.64 298">
<defs>
<style>
.cls-1 {
stroke: #188dcd;
stroke-width: 7.09px;
}
.cls-1, .cls-2 {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.cls-2 {
stroke: #06608d;
stroke-width: 7.64px;
}
</style>
</defs>
<path class="cls-2" d="M141.55,75.25c41.41-3.06,76.65,28.14,79.72,68.58,3.11,40.94-27.57,76.7-68.47,79.88-40.9,3.18-76.74-27.4-80-68.33-3.24-40.67,27.1-77.06,68.75-80.14Z"/>
<g>
<path class="cls-1" d="M93.02,150.16l94.51-.1c5.42,0,9.38-3.32,11.77-7.42,2.46-4.22,2.34-9.31-.33-14.08s-8.55-7.14-13.74-6.05c-5.56,1.16-9.58,5.2-10.56,11.06l-1.37,3.31"/>
<path class="cls-1" d="M107.2,134.34l41.08-.22c5.22-.04,9.65-3.47,11.75-7.8,1.97-4.05,2.07-9.05-.38-13.57-2.52-4.65-7.73-7.06-12.64-6.38-5.36.74-9.63,4.27-11.31,9.68l-1.59,5.05"/>
<path class="cls-1" d="M106.66,165.98l54.7.22c6.92.04,11.86,6.32,12.41,12.51.65,7.38-3.84,13.8-10.9,14.98-6.86,1.15-14.24-3.17-15.3-10.44l-.96-2.91"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297.64 298">
<defs>
<style>
.cls-1 {
fill: #f58530;
}
.cls-2 {
fill: none;
stroke: #965924;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 7.09px;
}
</style>
</defs>
<path class="cls-1" d="M136.33,208.12c-19.32-5.9-30.05-22.42-27.78-41.95,1.26-10.83,5.25-21.18,13.26-29.63-.9,7.39-.16,17.58,6.53,18.66.94-8.84.15-15.28,6.11-23.24l7.92-11.21c6.24-8.83,7.5-19.28,3.37-29.88,15.44,8.26,23.82,24.55,21.89,41.85l-1.67,10.88c.29,2.7,1.62,5.21,4.15,5.78s4.78-1.16,5.94-3.78c2-4.52.44-9.18.65-14.62,10.88,11.73,16.51,26.9,15.31,42.17-1.29,16.3-12.35,30.71-28.53,34.91,6.83-7.12,8.79-15.99,5.72-24.9-1.81-5.25-5.27-9.43-8.83-13.86-5.7-6.72-8.45-14.75-6.72-23.31-7.89,5.36-9.91,14.69-7.9,23.13l3.66,11c1.09,3.28-1.67,6.64-4.54,7.12-3.56.6-5.85-1.73-6.87-5l-.94-7.53c-8.34,10.07-9.51,23.62-.92,32.49.96-.69.98.39.19.91Z"/>
<path class="cls-2" d="M143.49,75.75c-41.42,3.95-71,40.77-67.08,81.3,3.95,40.77,40.15,70.73,80.91,67.01,40.76-3.72,70.94-39.73,67.45-80.54-3.49-40.81-39.62-71.73-81.28-67.76Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297.64 298">
<defs>
<style>
.cls-1 {
fill: none;
stroke: #007e6e;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 7.64px;
}
.cls-2 {
fill: #02b8a1;
}
</style>
</defs>
<g>
<path class="cls-2" d="M169.65,142.49c-14.86,8.98-36.61,18.58-53.5,19.71-2.33-4.65-2.85-9.35-2.72-14.67.47-18.24,14.86-33.55,32.97-35.01l4.88-.4c.95.91,1.83.2,2.78.29,12.6,1.24,23.29,8.9,28.77,19.95-4.14,4.18-8.42,7.24-13.2,10.13Z"/>
<path class="cls-2" d="M109.13,152.11c4.02,4.41-4.84,9.31-.81,11.92,3.05,1.98,7.17,2.18,10.5,1.72,9.99-1.36,19.25-4.21,28.61-8.1,10.11-4.21,19.83-8.84,28.77-15.14,5.21-3.67,15.34-11.69,12.87-15.68-1.44-2.33-4.5-2.11-6.69-2.58l-3-3.55,13.37-1.53,9.18.11c2.39.03,4.82,1.17,5.96,3.24,3.26,5.9-7.93,15.44-14.45,20.35-22.14,16.68-54.36,30.57-80.51,34.95-2.1.35-4.6-.14-6.73.73-6.07-.48-12.84-.15-14.33-5.1-1.93-6.42,10.04-16,17.26-21.34Z"/>
<path class="cls-2" d="M165.92,182.39c-12.13,6.06-27.36,4.61-37.9-4.1,21.04-5.96,39.86-14.39,58.5-26-1.03,13.37-9.06,24.34-20.6,30.11Z"/>
<path class="cls-2" d="M173.53,90.16l3.55,7.06,7.02,3.16-7.33,3.3-3.28,7.22c-1.19-2.27-1.23-5.41-3.19-7.35l-6.57-3.22,6.67-3.05,3.14-7.13Z"/>
</g>
<path class="cls-1" d="M145.94,74.78c-41.5,2.57-72.27,38.33-69.76,78.93,2.53,41.08,37.95,72.35,79,69.8,40.81-2.53,72.01-37.57,69.79-78.43-2.22-40.85-37.29-72.89-79.03-70.3Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297.64 298">
<defs>
<style>
.cls-1 {
stroke-width: 6.55px;
}
.cls-1, .cls-2, .cls-3 {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.cls-1, .cls-3 {
stroke: #ed1f81;
}
.cls-2 {
stroke: #9d1d59;
stroke-width: 8.18px;
}
.cls-4 {
fill: #ed1f81;
}
.cls-3 {
stroke-width: 4.91px;
}
</style>
</defs>
<path class="cls-2" d="M198.1,92.95c35.09,30.18,34.97,82.97.8,113.27-20.57,18.25-49.88,22.94-74.59,13.63-27.45-10.34-45.86-35.24-48.13-63.78-2.28-28.78,11.49-55.62,36.37-70.43,26.9-16.02,61.47-13.39,85.54,7.31Z"/>
<g>
<path class="cls-3" d="M176.65,114.75l-8.14,18.74c-1.78,4.11-1.84,8.31-1.41,12.73l1.59,16.07"/>
<line class="cls-1" x1="139.35" y1="102.57" x2="176.65" y2="114.75"/>
<path class="cls-1" d="M139.35,102.57l-11.33,17.17c-2.55,3.84-7.55,5.37-11,8.32l-4.74,4.04c-.08,4.98.56,9.95-1.31,14.53l-5.13,12.55c-.59,1.45.23,3.19-.78,4.27"/>
<path class="cls-3" d="M168.7,162.3l4.68,2.83c3.5,2.09,5.49,4.84,6.83,8.81l4.06,12.05"/>
<path class="cls-4" d="M141.67,101.77l3.43,9.97c.34.8.68,1.69.69,2.58,0,0,.28,11.65.28,11.65.01.59.7.82.78,1.25.06.24-.2.46-.15.76.04.38-.05.74-.5.65-.38-1.04-.7-1.92-1.18-3.23-1.61-4.28-2.83-8.2-4.57-12.06l-3.43-9.97c-.98-3.08,3.51-4.64,4.64-1.6h0Z"/>
<path class="cls-1" d="M176.65,114.75l18.53,41.29c-3,10.38-6.34,20.4-10.92,29.95"/>
<path class="cls-1" d="M105.06,163.45l6.96,11.57c2.21,3.67,3.47,9.08,7.1,11.57l16.21,6.98c2.68,1.16,5.63,3.26,8.69,2.45l37.03-9.76,3.22-.28"/>
<polyline class="cls-3" points="105.06 163.45 115.08 166.46 135.85 176.34 140.52 192.11"/>
<path class="cls-4" d="M169.66,164.55l-3.55,1.52c-4.22,1.75-8.26,4.77-12.82,5.2.2-.19,8.81-8.28,8.64-8.11.54-.39,1.71-1.21,2.25-1.6l3.55-1.52c3.01-1.2,4.88,3.17,1.93,4.51h0Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297.64 298">
<defs>
<style>
.cls-1 {
stroke: #97ad3b;
stroke-width: 5.45px;
}
.cls-1, .cls-2 {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.cls-3 {
fill: #97ad3b;
}
.cls-2 {
stroke: #617633;
stroke-width: 8.18px;
}
</style>
</defs>
<path class="cls-2" d="M78.68,167.8c10.37,40.36,52.59,65.03,92.15,53.27,25.27-7.51,44.99-27.56,51.59-53.25,7.85-30.54-4.25-62.27-30.01-79.95-25.03-17.18-58.69-17.16-83.68-.03s-37.89,49.45-30.05,79.97Z"/>
<g>
<path class="cls-3" d="M150.26,162.81c6.01,9.69,17.98,10.43,20.31,23.51l-40.45.06c1.14-6.81,5.67-11.44,11.01-14.91,3.51-2.22,6.23-3.92,9.13-8.66Z"/>
<path class="cls-3" d="M148.47,147.26c-2.81-8.51-14.28-10.71-16.68-21.2,5.71-2.79,11.47-1.63,16.56.73,6.41,2.99,12.97,3.07,19.39-.03.77-.33,1.11.7.72,1.12-3.37,8.56-13.05,11.13-16.16,18.96-.32.8.24,1.64-.56,2.28-.58.46-1.13,1.22-1.98.69-.96-.59-.94-1.53-1.27-2.55Z"/>
<path class="cls-3" d="M148.64,158.25c-.11-.44-.28-1.76-.12-2.18.43-1.1,2.44-1.95,3.39-.42.67,1.07,1.26,1.87-.19,3.26-.82.79-2.64,1.06-3.08-.66Z"/>
<path class="cls-1" d="M124.7,104.6l51.4-.12c1.71,4.37,2.19,8.69,1.61,13.12-3.2,24.61-18.64,25.22-18.48,31.87.12,5.19,8.21,7.28,12.69,14.55,5.67,9.19,7.16,20.13,4.69,30.59l-52.04-.08-1.36-8.12c-.39-2.33.19-4,.4-6.05,2.3-22.2,17.94-24.12,18.26-30.85.28-6.03-13.4-7.93-17.34-25.88-1.37-6.26-2.02-12.33.16-19.02Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297.64 298">
<defs>
<style>
.cls-1 {
stroke: #512d7c;
stroke-width: 8.18px;
}
.cls-1, .cls-2 {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
}
.cls-2 {
stroke: #7450a1;
stroke-width: 6px;
}
.cls-3 {
fill: #7450a1;
}
</style>
</defs>
<path class="cls-3" d="M174.04,203.87l9.99-.73c-29.83,18.68-68.85,9.52-87.52-21.03,11.14-6.18,24.3-5.58,34.4,1.85l12.6,9.26c9.04,6.64,19.28,10.29,30.53,10.65Z"/>
<path class="cls-3" d="M154.79,173.68c5.27.42,9.33,1.57,13.69-.05,8.88-3.26,6.62-14.12,16.13-12.67,2.78.43,6.28,2.78,6.56,6.47.54,7.08-5.71,12.22-12.82,13.36-8.5,1.36-17-1.6-23.57-7.12Z"/>
<path class="cls-1" d="M205.02,200.76c-30.08,32.38-79.38,32.02-109.26-.43-22.86-24.82-25.69-61.22-7.87-89.75,13.05-20.89,35.85-33.88,60.33-34.67,24.03-.78,47.55,10.41,62.01,30.32,20.91,28.79,19.4,68.02-5.22,94.52Z"/>
<path class="cls-3" d="M120.34,164.14c5.97.4,11.82,2.39,16.78,5.1,9.16,5,16.42,12.16,25.72,15.76,5.24,2.02,10.84,2.79,16.36,2.07,7.13-.95,14.42-4.77,17.45-11.52,3.43-7.04,1.75-16.07-5.79-19.58-4.16-2.38-9.37-2.85-13.85-1.26-4.36,1.51-8.09,4.77-10.06,9.3.53-13.96,15.64-22.6,28.39-17.39,12.64,5.95,16.59,21.1,10.7,33.3-4.49,9.97-14.93,16.05-25.45,17.41-13.35,1.86-25.58-3.25-36-10.8-7.97-5.53-15.35-11.45-24.92-12.07-11.98-2.34-24.76-.55-36.28,3.57-.76.27-1.53.55-2.29.85,10.99-10.62,24.5-15.61,39.24-14.76h0Z"/>
<path class="cls-2" d="M145.14,160.6c-12.11-.34-20.79-8.81-22.17-20.67-.7-5.98,2.31-11.82,5.05-17.28l8.31-11.95c3.27-4.71,6.06-9.3,8.24-15.11l16.35,24.56c3.12,4.69,4.92,9.51,6.1,15.16,2.79,13.29-7.92,25.67-21.88,25.28Z"/>
<path class="cls-3" d="M158.51,127.47l-6.68-1.6.32.06c-7.64-1.19-14.51,4.49-14.3,12.17.07,7.14,9.57,10.96,14.17,5.33,4.92-4.75,4.17-11.72,1.63-17.62,6.93,5.38,8.05,14.59,3.04,21.38-3.59,4.56-10.16,6.11-15.48,4.05-7.6-2.62-11.16-11.46-8.53-18.83,2.55-8.65,11.47-13.74,20.22-12.43.11.01.22.03.32.06l6.68,1.6c3.82,1.02,2.47,6.66-1.4,5.83h0Z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -8,10 +8,17 @@
*
* `data` shape — matches the /epic/natus/preview/ proxy response:
* {
* planets: { Sun: { sign, degree, retrograde }, … },
* planets: { Sun: { sign, degree, speed, retrograde }, … },
* houses: { cusps: [f×12], asc: f, mc: f },
* elements: { Fire: n, Water: n, Stone: n, Air: n, Time: n, Space: n },
* aspects: [{ planet1, planet2, type, angle, orb }, …],
* elements: {
* Fire: { count: n, contributors: [{planet, sign}, …] },
* Stone: { count: n, contributors: […] },
* Air: { count: n, contributors: […] },
* Water: { count: n, contributors: […] },
* Time: { count: n, stellia: [{ sign, planets: [{planet, sign}] }] },
* Space: { count: n, parades: [{ signs: […], planets: [{planet, sign}] }] },
* },
* aspects: [{ planet1, planet2, type, angle, orb, applying_planet }, …],
* distinctions: { "1": n, …, "12": n },
* house_system: "O",
* }
@@ -46,7 +53,7 @@ const NatusWheel = (() => {
Jupiter: '♃', Saturn: '♄', Uranus: '♅', Neptune: '♆', Pluto: '♇',
};
// Alchemical element symbol → CSS modifier class suffix (matches rootvars palette)
// Alchemical element code → CSS var suffix (--pri{Cap}) e.g. 'au' → --priAu
const PLANET_ELEMENTS = {
Sun: 'au', Moon: 'ag', Mercury: 'hg', Venus: 'cu', Mars: 'fe',
Jupiter: 'sn', Saturn: 'pb', Uranus: 'u', Neptune: 'np', Pluto: 'pu',
@@ -65,15 +72,31 @@ const NatusWheel = (() => {
// Clockwise ring order for element cycling
const ELEMENT_ORDER = ['Fire', 'Stone', 'Time', 'Space', 'Air', 'Water'];
// Aspect stroke colors remain in JS — they are data-driven, not stylistic.
const ASPECT_COLORS = {
Conjunction: 'var(--priYl, #f0e060)',
Sextile: 'var(--priGn, #60c080)',
Square: 'var(--priRd, #c04040)',
Trine: 'var(--priGn, #60c080)',
Opposition: 'var(--priRd, #c04040)',
const CLASSIC_ELEMENTS = new Set(['Fire', 'Stone', 'Air', 'Water']);
const ASPECT_SYMBOLS = {
Conjunction: '☌',
Semisextile: '⚺',
Sextile: '⚹',
Square: '□',
Trine: '△',
Quincunx: '⚻',
Opposition: '☍',
};
const APPLY_SYM = '⇥'; // →| applying (converging toward exact)
const SEP_SYM = '↦'; // |→ separating (diverging from exact)
// SVG stroke-dasharray and width per aspect type — color comes from applying planet.
const ASPECT_STYLES = {
Conjunction: { dash: 'none', width: 1.2 },
Semisextile: { dash: '1 4', width: 0.6 },
Sextile: { dash: '6 4', width: 0.8 },
Square: { dash: '2 4', width: 1.2 },
Trine: { dash: '12 4', width: 0.8 },
Quincunx: { dash: '12 4 2 4', width: 0.8 },
Opposition: { dash: '10 4 2 4 2 4', width: 1.2 },
};
// Element fill colors live in _natus.scss (.nw-sign--* / .nw-element--*).
const HOUSE_LABELS = [
'', 'Self', 'Worth', 'Education', 'Family', 'Creation', 'Ritual',
@@ -108,16 +131,44 @@ const NatusWheel = (() => {
// AbortController for the outside-click dismiss listener.
let _outsideClickController = null;
// ── Aspect overlay state ──────────────────────────────────────────────────
let _aspectsVisible = false;
let _aspectGroup = null; // D3 selection of the nw-aspects <g>
let _aspectIndex = {}; // planetName → [{partner, type, orb, applying_planet}]
let _aspectPlanet = null; // name of planet whose lines are currently drawn
// ── Static asset base path ────────────────────────────────────────────────
let _staticBase = null;
function _getStaticBase() {
if (_staticBase) return _staticBase;
const scripts = document.querySelectorAll('script[src]');
for (const s of scripts) {
if (s.src.includes('natus-wheel')) {
_staticBase = s.src.replace(/natus-wheel\.js.*$/, '');
return _staticBase;
}
}
_staticBase = '/static/apps/gameboard/';
return _staticBase;
}
// ── Helpers ───────────────────────────────────────────────────────────────
/** Convert ecliptic longitude to SVG angle.
*
* American convention: ASC sits at 9 o'clock (left). SVG 0° is 3 o'clock
* and increases clockwise, so ecliptic (counter-clockwise, ASC-relative)
* maps to SVG via:
* svg_angle = -(ecliptic - asc) - 180° (in radians)
* The 180° offset places ASC exactly at the left (9 o'clock) position.
/**
* Normalise an element value that may be either a legacy flat integer
* (old stored chart_data) or the new enriched object {count, contributors?, …}.
* Always returns an object with at least a `count` key.
*/
function _elNorm(v) {
if (!v) return { count: 0 };
if (typeof v === 'number') return { count: v };
return v;
}
/** Convert ecliptic longitude to SVG angle (ASC at 9 o'clock). */
function _toAngle(degree, asc) {
return (-(degree - asc) - 180) * Math.PI / 180;
}
@@ -133,13 +184,46 @@ const NatusWheel = (() => {
return ((ecliptic % 360) + 360) % 360 % 30;
}
/** Inline SVG for a zodiac sign, sized to 1em, using current text colour. */
/** Inline SVG for a zodiac sign icon (preloaded path). */
function _signIconSvg(signName) {
const d = _signPaths[signName];
if (!d) return '';
return `<svg viewBox="0 0 640 640" width="1em" height="1em" class="tt-sign-icon" aria-hidden="true"><path d="${d}"/></svg>`;
}
/** <img> for an element-square badge (Ardor.svg etc.). */
function _elementSquareImg(elementKey) {
const info = ELEMENT_INFO[elementKey];
if (!info) return '';
const src = _getStaticBase() + 'icons/element-squares/' + info.name + '.svg';
return `<img src="${src}" class="tt-el-square" width="80" height="80" alt="${info.name}" aria-hidden="true">`;
}
/** <img> for an energy-vector icon (fire.svg, stone.svg etc.) — inline in text. */
function _elementVectorImg(elementKey) {
const info = ELEMENT_INFO[elementKey];
if (!info) return '';
const src = _getStaticBase() + 'icons/energy-vectors/' + info.classical + '.svg';
return `<img src="${src}" class="tt-el-vec" width="1em" height="1em" alt="${info.classical}" aria-hidden="true">`;
}
/** CSS color string for an aspect line, keyed to the applying planet. */
function _aspectColor(applying_planet) {
const code = PLANET_ELEMENTS[applying_planet];
if (!code) return '#888';
return `rgba(var(--asp-${code[0].toUpperCase() + code.slice(1)}), 0.85)`;
}
/** Small inline SVG showing the aspect line pattern — used in tooltip legend. */
function _aspectLineSvg(type, applying_planet) {
const style = ASPECT_STYLES[type] || { dash: 'none', width: 0.8 };
const color = _aspectColor(applying_planet);
const dash = style.dash === 'none' ? '' : ` stroke-dasharray="${style.dash}"`;
return `<svg class="tt-asp-line" width="28" height="10" aria-hidden="true">` +
`<line x1="2" y1="5" x2="26" y2="5" stroke="${color}" stroke-width="${style.width * 2}"${dash}/>` +
`</svg>`;
}
// ── Cycle helpers ─────────────────────────────────────────────────────────
@@ -161,25 +245,61 @@ const NatusWheel = (() => {
_activeIdx = null;
}
/** Dismiss tooltip and reset all active state. */
function _clearAspectLines() {
if (_aspectGroup) _aspectGroup.selectAll('*').remove();
if (_svg) _svg.selectAll('.nw-planet-group').classed('nw-planet--asp-active', false);
}
/** Dismiss tooltip and reset all active state. Aspect lines persist. */
function _closeTooltip() {
_clearActive();
if (_tooltipEl) _tooltipEl.style.display = 'none';
}
/**
* Draw aspect lines for `planetName` into the persistent nw-aspects group.
* Only runs when _aspectsVisible is true.
*/
function _showPlanetAspects(planetName) {
if (!_aspectsVisible || !_aspectGroup || !_currentData) return;
_clearAspectLines();
_aspectPlanet = null;
const asc = _currentData.houses.asc;
const degrees = {};
Object.entries(_currentData.planets).forEach(([n, p]) => { degrees[n] = p.degree; });
const myDeg = degrees[planetName];
if (myDeg === undefined) return;
const a1 = _toAngle(myDeg, asc);
(_aspectIndex[planetName] || []).forEach(({ partner, type, applying_planet }) => {
const partnerDeg = degrees[partner];
if (partnerDeg === undefined) return;
const a2 = _toAngle(partnerDeg, asc);
const style = ASPECT_STYLES[type] || { dash: 'none', width: 0.8 };
const color = _aspectColor(applying_planet);
const line = _aspectGroup.append('line')
.attr('x1', _cx + R.planetR * Math.cos(a1))
.attr('y1', _cy + R.planetR * Math.sin(a1))
.attr('x2', _cx + R.planetR * Math.cos(a2))
.attr('y2', _cy + R.planetR * Math.sin(a2))
.attr('stroke', color)
.attr('stroke-width', style.width * 2)
.attr('stroke-opacity', 0.9);
if (style.dash !== 'none') line.attr('stroke-dasharray', style.dash);
});
_aspectPlanet = planetName;
if (_svg) {
_svg.select(`[data-planet="${planetName}"]`).classed('nw-planet--asp-active', true);
}
}
/**
* Position the tooltip in the vertical half of the wheel opposite to the
* clicked planet/element, with the horizontal edge aligned to the item.
*
* Vertical (upper/lower):
* item in lower half (itemY ≥ svgCY) → lower edge 1rem above centreline
* item in upper half (itemY < svgCY) → upper edge 1rem below centreline
*
* Horizontal (left/right of centre):
* item left of centre → tooltip left edge aligns with item left edge
* item right of centre → tooltip right edge aligns with item right edge
*
* "1rem" is approximated as 16 px.
* clicked planet/element.
*/
function _positionTooltipAtItem(ring, idx) {
const svgNode = _svg ? _svg.node() : null;
@@ -194,7 +314,6 @@ const NatusWheel = (() => {
const svgCX = svgRect.left + svgRect.width / 2;
const svgCY = svgRect.top + svgRect.height / 2;
// Item screen rect — fall back to SVG centre if element not found.
let iRect = { left: svgCX, top: svgCY, width: 0, height: 0, right: svgCX, bottom: svgCY };
{
let el = null;
@@ -211,13 +330,9 @@ const NatusWheel = (() => {
const itemX = iRect.left + iRect.width / 2;
const itemY = iRect.top + iRect.height / 2;
// Horizontal: align tooltip edge with item edge on the same side.
// Clamp within the SVG rect so the tooltip stays over the wheel.
const left = Math.max(svgRect.left + REM, Math.min(svgRect.right - ttW - REM,
itemX < svgCX ? iRect.left : iRect.right - ttW
));
// Vertical: place in the opposite half, 1rem from centreline.
const top = Math.max(svgRect.top + REM, Math.min(svgRect.bottom - ttH - REM,
itemY >= svgCY ? svgCY - REM - ttH : svgCY + REM
));
@@ -229,6 +344,12 @@ const NatusWheel = (() => {
/** Lock-activate a planet by cycle index. */
function _activatePlanet(idx) {
_clearActive();
// Aspect lines persist across planet switches — cleared only by DON or DOFF.
// Re-opening the same planet restores _aspectsVisible so DON shows as ×.
const item0 = _planetItems[idx];
if (item0.name !== _aspectPlanet) {
_aspectsVisible = false;
}
_activeRing = 'planets';
_activeIdx = idx;
const item = _planetItems[idx];
@@ -244,12 +365,41 @@ const NatusWheel = (() => {
const rx = pdata.retrograde ? ' ℞' : '';
const icon = _signIconSvg(pdata.sign) || signData.symbol || '';
// Aspect list — always shown in small font; lines on SVG toggled separately.
let aspectHtml = '';
const myAspects = _aspectIndex[item.name] || [];
if (myAspects.length) {
aspectHtml = '<small class="tt-aspects">';
myAspects.forEach(({ partner, type, orb, applying_planet }) => {
const psym = PLANET_SYMBOLS[partner] || partner[0];
const ppdata = _currentData.planets[partner] || {};
const sicon = _signIconSvg(ppdata.sign) || (SIGNS.find(s => s.name === ppdata.sign) || {}).symbol || '';
const asym = ASPECT_SYMBOLS[type] || type;
const dirsym = applying_planet === item.name ? APPLY_SYM : SEP_SYM;
const lineSvg = _aspectLineSvg(type, applying_planet);
aspectHtml +=
`<div class="tt-asp-row">` +
`${lineSvg} ${asym} ${psym} <span class="tt-asp-in">in</span> ${sicon}` +
` <span class="tt-asp-orb">(${dirsym} ${orb}°)</span>` +
`</div>`;
});
aspectHtml += '</small>';
}
if (_ttBody) {
_ttBody.innerHTML =
`<div class="tt-title tt-title--${el}">${item.name} (${sym})</div>` +
`<div class="tt-description">@${inDeg}° ${pdata.sign} (${icon})${rx}</div>`;
`<div class="tt-description">@${inDeg}° ${pdata.sign} (${icon})${rx}</div>` +
aspectHtml;
}
_updateAspectToggleUI();
_showPlanetAspects(item.name);
_positionTooltipAtItem('planets', idx);
if (_tooltipEl) {
_tooltipEl.querySelector('.nw-asp-don')?.style.removeProperty('display');
_tooltipEl.querySelector('.nw-asp-doff')?.style.removeProperty('display');
}
}
/** Lock-activate an element slice by cycle index. */
@@ -261,19 +411,100 @@ const NatusWheel = (() => {
const grp = _svg.select(`[data-element="${item.key}"]`);
grp.classed('nw-element--active', true);
const info = ELEMENT_INFO[item.key] || {};
const elCounts = _currentData.elements;
const total = Object.values(elCounts).reduce((s, v) => s + v, 0);
const count = elCounts[item.key] || 0;
const pct = total > 0 ? Math.round((count / total) * 100) : 0;
const elKey = item.key.toLowerCase();
const info = ELEMENT_INFO[item.key] || {};
const elData = _elNorm((_currentData.elements || {})[item.key]);
const count = elData.count || 0;
const elKey = item.key.toLowerCase();
const squareImg = _elementSquareImg(item.key);
const vecImg = _elementVectorImg(item.key);
let bodyHtml = '';
const pct = Math.round(count / 10 * 100);
if (CLASSIC_ELEMENTS.has(item.key)) {
const contribs = elData.contributors || [];
bodyHtml = `<div class="tt-el-body-line">${vecImg} +${count} (${pct}%)</div>`;
if (contribs.length) {
bodyHtml += '<div class="tt-el-contribs">';
contribs.forEach(c => {
const psym = PLANET_SYMBOLS[c.planet] || c.planet[0];
const pdata = (_currentData.planets || {})[c.planet] || {};
const inDeg = pdata.degree !== undefined ? _inSignDeg(pdata.degree).toFixed(1) : '?';
const sicon = _signIconSvg(c.sign) || (SIGNS.find(s => s.name === c.sign) || {}).symbol || '';
bodyHtml += `<div class="tt-asp-row">${psym} @ ${inDeg}° ${sicon} +1</div>`;
});
bodyHtml += '</div>';
}
} else if (item.key === 'Time') {
const stellia = elData.stellia || [];
bodyHtml = `<div class="tt-el-body-line">${vecImg} +${count} (${pct}%)</div>`;
if (stellia.length) {
bodyHtml += '<div class="tt-el-contribs">';
stellia.forEach(st => {
const bonus = st.planets.length - 1;
bodyHtml += `<div class="tt-el-formation-header">Stellium +${bonus}</div>`;
st.planets.forEach(p => {
const psym = PLANET_SYMBOLS[p.planet] || p.planet[0];
const pdata = (_currentData.planets || {})[p.planet] || {};
const inDeg = pdata.degree !== undefined ? _inSignDeg(pdata.degree).toFixed(1) : '?';
const sicon = _signIconSvg(p.sign) || (SIGNS.find(s => s.name === p.sign) || {}).symbol || '';
bodyHtml += `<div class="tt-asp-row tt-el-planet-row">${psym} @ ${inDeg}° ${sicon}</div>`;
});
});
bodyHtml += '</div>';
} else {
bodyHtml += `<div class="tt-el-formation">—</div>`;
}
} else if (item.key === 'Space') {
const parades = elData.parades || [];
bodyHtml = `<div class="tt-el-body-line">${vecImg} +${count} (${pct}%)</div>`;
if (parades.length) {
bodyHtml += '<div class="tt-el-contribs">';
parades.forEach(pd => {
const bonus = pd.signs.length - 1;
bodyHtml += `<div class="tt-el-formation-header">Parade +${bonus}</div>`;
// Group planets by sign, sorted by ecliptic degree (counterclockwise = ascending)
const bySign = {};
pd.planets.forEach(p => {
if (!bySign[p.sign]) bySign[p.sign] = [];
bySign[p.sign].push(p);
});
Object.keys(bySign).forEach(sign => {
bySign[sign].sort((a, b) => {
const da = ((_currentData.planets || {})[a.planet] || {}).degree || 0;
const db = ((_currentData.planets || {})[b.planet] || {}).degree || 0;
return da - db;
});
});
pd.signs.forEach(sign => {
const planets = bySign[sign] || [];
const sicon = _signIconSvg(sign) || (SIGNS.find(s => s.name === sign) || {}).symbol || sign;
const psyms = planets.map(p => PLANET_SYMBOLS[p.planet] || p.planet[0]).join(' ');
bodyHtml += `<div class="tt-asp-row tt-el-planet-row">${sicon} (${psyms})</div>`;
});
});
bodyHtml += '</div>';
} else {
bodyHtml += `<div class="tt-el-formation">—</div>`;
}
}
if (_ttBody) {
_ttBody.innerHTML =
`<div class="tt-title tt-title--el-${elKey}">[${info.abbr}] ${info.name}</div>` +
`<div class="tt-description">${info.classical} · ${count} (${pct}%)</div>`;
`<div class="tt-el-header">` +
squareImg +
`<span class="tt-title tt-title--el-${elKey}">${info.name}</span>` +
`</div>` +
bodyHtml;
}
_positionTooltipAtItem('elements', idx);
if (_tooltipEl) {
_tooltipEl.querySelector('.nw-asp-don')?.style.setProperty('display', 'none');
_tooltipEl.querySelector('.nw-asp-doff')?.style.setProperty('display', 'none');
}
}
/** Advance the active ring by +1 (NXT) or -1 (PRV). */
@@ -287,37 +518,75 @@ const NatusWheel = (() => {
}
}
function _updateAspectToggleUI() {
if (!_tooltipEl) return;
const don = _tooltipEl.querySelector('.nw-asp-don');
const doff = _tooltipEl.querySelector('.nw-asp-doff');
if (!don || !doff) return;
don.classList.toggle('btn-disabled', _aspectsVisible);
doff.classList.toggle('btn-disabled', !_aspectsVisible);
don.textContent = _aspectsVisible ? '×' : 'DON';
doff.textContent = !_aspectsVisible ? '×' : 'DOFF';
}
function _toggleAspects() {
_aspectsVisible = !_aspectsVisible;
_updateAspectToggleUI();
if (_aspectsVisible) {
if (_activeRing === 'planets' && _activeIdx !== null) {
_showPlanetAspects(_planetItems[_activeIdx].name);
}
} else {
_clearAspectLines();
_aspectPlanet = null;
}
}
/**
* Inject PRV/idx/NXT controls into #id_natus_tooltip and wire their events.
* Inject PRV/NXT + DON|DOFF controls into the tooltip.
* DON|DOFF lives in the top-left corner; PRV/NXT float left/right.
* Called on every draw() so a fresh innerHTML replaces any stale state.
*/
function _injectTooltipControls() {
_tooltipEl = document.getElementById('id_natus_tooltip');
if (!_tooltipEl) return;
_tooltipEl.innerHTML =
`<button type="button" class="btn btn-equip nw-asp-don">DON</button>` +
`<button type="button" class="btn btn-unequip btn-disabled nw-asp-doff">DOFF</button>` +
'<div class="nw-tt-body"></div>' +
'<button type="button" class="btn btn-nav-left nw-tt-prv">PRV</button>' +
'<button type="button" class="btn btn-nav-right nw-tt-nxt">NXT</button>';
_ttBody = _tooltipEl.querySelector('.nw-tt-body');
_tooltipEl.querySelector('.nw-tt-prv').addEventListener('click', (e) => {
e.stopPropagation();
_stepCycle(-1);
e.stopPropagation(); _stepCycle(-1);
});
_tooltipEl.querySelector('.nw-tt-nxt').addEventListener('click', (e) => {
e.stopPropagation();
_stepCycle(1);
e.stopPropagation(); _stepCycle(1);
});
_tooltipEl.querySelector('.nw-asp-don')
.addEventListener('click', (e) => { e.stopPropagation(); _toggleAspects(); });
_tooltipEl.querySelector('.nw-asp-doff')
.addEventListener('click', (e) => { e.stopPropagation(); _toggleAspects(); });
// Sync button to current state in case of redraw mid-session.
_updateAspectToggleUI();
}
/** Attach a document-level click listener that closes the tooltip when the
* user clicks outside the tooltip (including on empty wheel areas).
* Planet/element groups stop propagation so their own clicks are not caught. */
/** Document-level click listener that closes tooltip when clicking outside. */
function _attachOutsideClick() {
if (_outsideClickController) _outsideClickController.abort();
_outsideClickController = new AbortController();
document.addEventListener('click', (e) => {
if (_activeRing === null) return;
if (_tooltipEl && _tooltipEl.contains(e.target)) return;
// DON/DOFF have pointer-events:none when disabled — check bounding rect directly
if (_tooltipEl) {
for (const cls of ['.nw-asp-don', '.nw-asp-doff', '.nw-tt-prv', '.nw-tt-nxt']) {
const btn = _tooltipEl.querySelector(cls);
if (!btn) continue;
const r = btn.getBoundingClientRect();
if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) return;
}
}
_closeTooltip();
}, { signal: _outsideClickController.signal });
}
@@ -327,26 +596,24 @@ const NatusWheel = (() => {
const size = Math.min(rect.width || 400, rect.height || 400);
_cx = size / 2;
_cy = size / 2;
// viewBox pins the coordinate system to size×size; preserveAspectRatio
// centres it inside the SVG element regardless of its aspect ratio.
svgEl.setAttribute('viewBox', `0 0 ${size} ${size}`);
svgEl.setAttribute('preserveAspectRatio', 'xMidYMid meet');
_r = size * 0.46; // leave a small margin
_r = size * 0.46;
R = {
elementInner: _r * 0.20,
elementOuter: _r * 0.28,
planetInner: _r * 0.32,
planetOuter: _r * 0.48,
houseInner: _r * 0.50,
houseOuter: _r * 0.68,
planetInner: _r * 0.50,
planetOuter: _r * 0.68,
houseInner: _r * 0.32,
houseOuter: _r * 0.48,
signInner: _r * 0.70,
signOuter: _r * 0.90,
labelR: _r * 0.80, // sign symbol placement
houseNumR: _r * 0.59, // house number placement
planetR: _r * 0.40, // planet symbol placement
aspectR: _r * 0.29, // aspect lines end here (inner circle)
ascMcR: _r * 0.92, // ASC/MC tick outer
labelR: _r * 0.80,
houseNumR: _r * 0.40,
planetR: _r * 0.59,
aspectR: _r * 0.29,
ascMcR: _r * 0.92,
};
}
@@ -389,12 +656,10 @@ const NatusWheel = (() => {
const sigGroup = g.append('g').attr('class', 'nw-signs');
SIGNS.forEach((sign, i) => {
const startDeg = i * 30; // ecliptic 0360
const startDeg = i * 30;
const endDeg = startDeg + 30;
const startA = _toAngle(startDeg, asc);
const endA = _toAngle(endDeg, asc);
// D3 arc expects startAngle < endAngle in its own convention; we swap
// because our _toAngle goes counter-clockwise
const [sa, ea] = startA > endA ? [endA, startA] : [startA, endA];
sigGroup.append('path')
@@ -407,22 +672,18 @@ const NatusWheel = (() => {
}))
.attr('class', `nw-sign--${sign.element.toLowerCase()}`);
// Icon at midpoint
const midA = (sa + ea) / 2;
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 cr = _r * 0.065;
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])
@@ -438,19 +699,15 @@ const NatusWheel = (() => {
const arc = d3.arc();
const houseGroup = g.append('g').attr('class', 'nw-houses');
// Pre-compute angles; normalise the last house's nextCusp across 360° wrap.
const houses = cusps.map((cusp, i) => {
let nextCusp = cusps[(i + 1) % 12];
if (nextCusp <= cusp) nextCusp += 360; // close the circle for house 12
if (nextCusp <= cusp) nextCusp += 360;
const startA = _toAngle(cusp, asc);
const endA = _toAngle(nextCusp, asc);
// _toAngle is strictly decreasing with degree after normalisation,
// so startA > endA always — D3 arc needs sa < ea.
const sa = endA, ea = startA;
return { i, startA, sa, ea, midA: (sa + ea) / 2 };
});
// 1. Fills first so cusp lines + numbers are never buried beneath them.
houses.forEach(({ i, sa, ea }) => {
houseGroup.append('path')
.attr('transform', `translate(${_cx},${_cy})`)
@@ -463,14 +720,15 @@ const NatusWheel = (() => {
.attr('class', i % 2 === 0 ? 'nw-house-fill--even' : 'nw-house-fill--odd');
});
// 2. Cusp lines + house numbers on top.
houses.forEach(({ i, startA, midA }) => {
houseGroup.append('line')
.attr('x1', _cx + R.houseInner * Math.cos(startA))
.attr('y1', _cy + R.houseInner * Math.sin(startA))
.attr('x2', _cx + R.signInner * Math.cos(startA))
.attr('y2', _cy + R.signInner * Math.sin(startA))
.attr('class', 'nw-house-cusp');
if (i % 3 === 0) {
houseGroup.append('line')
.attr('x1', _cx + R.houseInner * Math.cos(startA))
.attr('y1', _cy + R.houseInner * Math.sin(startA))
.attr('x2', _cx + R.signInner * Math.cos(startA))
.attr('y2', _cy + R.signInner * Math.sin(startA))
.attr('class', 'nw-house-cusp');
}
houseGroup.append('text')
.attr('x', _cx + R.houseNumR * Math.cos(midA))
@@ -486,7 +744,7 @@ const NatusWheel = (() => {
function _drawPlanets(g, data) {
const asc = data.houses.asc;
const planetGroup = g.append('g').attr('class', 'nw-planets');
const ascAngle = _toAngle(asc, asc); // start position for animation
const ascAngle = _toAngle(asc, asc);
const TICK_OUTER = _r * 0.96;
@@ -494,8 +752,6 @@ const NatusWheel = (() => {
const finalA = _toAngle(pdata.degree, asc);
const el = PLANET_ELEMENTS[name] || '';
// Per-planet group — click event lives here so the symbol text and ℞
// indicator don't block mouse events on the circle.
const planetEl = planetGroup.append('g')
.attr('class', 'nw-planet-group')
.attr('data-planet', name)
@@ -512,7 +768,6 @@ const NatusWheel = (() => {
}
});
// Tick line — from planet circle outward past the zodiac ring
const tick = planetEl.append('line')
.attr('class', el ? `nw-planet-tick nw-planet-tick--${el}` : 'nw-planet-tick')
.attr('x1', _cx + R.planetR * Math.cos(ascAngle))
@@ -520,7 +775,6 @@ const NatusWheel = (() => {
.attr('x2', _cx + TICK_OUTER * Math.cos(ascAngle))
.attr('y2', _cy + TICK_OUTER * Math.sin(ascAngle));
// Circle behind symbol
const circleBase = pdata.retrograde ? 'nw-planet-circle--rx' : 'nw-planet-circle';
const circle = planetEl.append('circle')
.attr('cx', _cx + R.planetR * Math.cos(ascAngle))
@@ -528,7 +782,6 @@ const NatusWheel = (() => {
.attr('r', _r * 0.05)
.attr('class', el ? `${circleBase} nw-planet--${el}` : circleBase);
// Symbol — pointer-events:none so click is handled by the group
const label = planetEl.append('text')
.attr('x', _cx + R.planetR * Math.cos(ascAngle))
.attr('y', _cy + R.planetR * Math.sin(ascAngle))
@@ -540,7 +793,6 @@ const NatusWheel = (() => {
.attr('pointer-events', 'none')
.text(PLANET_SYMBOLS[name] || name[0]);
// Retrograde indicator — also pointer-events:none
let rxLabel = null;
if (pdata.retrograde) {
rxLabel = planetEl.append('text')
@@ -554,8 +806,6 @@ const NatusWheel = (() => {
.text('℞');
}
// Animate from ASC → final position (staggered)
// circle uses cx/cy; text uses x/y — must be separate transitions.
const interpAngle = d3.interpolate(ascAngle, finalA);
const transition = () => d3.transition()
.delay(idx * 40)
@@ -584,34 +834,33 @@ const NatusWheel = (() => {
});
}
/**
* Build the aspect index (planet → aspects list) and create the persistent
* nw-aspects group. Lines are drawn per-planet on click, not here.
*/
function _drawAspects(g, data) {
const asc = data.houses.asc;
const aspectGroup = g.append('g').attr('class', 'nw-aspects');
_aspectIndex = {};
Object.entries(data.planets).forEach(([name]) => { _aspectIndex[name] = []; });
// Build degree lookup
const degrees = {};
Object.entries(data.planets).forEach(([name, p]) => { degrees[name] = p.degree; });
data.aspects.forEach(({ planet1, planet2, type }) => {
if (degrees[planet1] === undefined || degrees[planet2] === undefined) return;
const a1 = _toAngle(degrees[planet1], asc);
const a2 = _toAngle(degrees[planet2], asc);
aspectGroup.append('line')
.attr('x1', _cx + R.aspectR * Math.cos(a1))
.attr('y1', _cy + R.aspectR * Math.sin(a1))
.attr('x2', _cx + R.aspectR * Math.cos(a2))
.attr('y2', _cy + R.aspectR * Math.sin(a2))
.attr('stroke', ASPECT_COLORS[type] || '#888')
.attr('stroke-width', type === 'Opposition' || type === 'Square' ? 1.2 : 0.8);
data.aspects.forEach(({ planet1, planet2, type, orb, applying_planet }) => {
if (!(planet1 in _aspectIndex) || !(planet2 in _aspectIndex)) return;
const shared = { type, orb, applying_planet };
_aspectIndex[planet1].push({ partner: planet2, ...shared });
_aspectIndex[planet2].push({ partner: planet1, ...shared });
});
_aspectGroup = g.append('g').attr('class', 'nw-aspects');
}
function _drawElements(g, data) {
const el = data.elements;
const total = ELEMENT_ORDER.reduce((s, k) => s + (el[k] || 0), 0);
const el = data.elements;
const total = ELEMENT_ORDER.reduce((s, k) => s + _elNorm(el[k]).count, 0);
if (total === 0) return;
const pieData = ELEMENT_ORDER.map(k => ({ key: k, value: el[k] || 0 }));
const pieData = ELEMENT_ORDER.map(k => ({
key: k,
value: _elNorm(el[k]).count,
}));
const pie = d3.pie().value(d => d.value).sort(null)(pieData);
const arc = d3.arc().innerRadius(R.elementInner).outerRadius(R.elementOuter);
@@ -643,12 +892,8 @@ 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().
* Preload zodiac sign SVGs from `basePath`.
* Returns a Promise that resolves when all 12 are cached.
*/
async function preload(basePath) {
const base = basePath ||
@@ -686,18 +931,16 @@ const NatusWheel = (() => {
const g = _svg.append('g').attr('class', 'nw-root');
// Outer circle border
g.append('circle')
.attr('cx', _cx).attr('cy', _cy).attr('r', R.signOuter)
.attr('class', 'nw-outer-ring');
// Inner filled disc (aspect area background)
g.append('circle')
.attr('cx', _cx).attr('cy', _cy).attr('r', R.elementOuter)
.attr('class', 'nw-inner-disc');
_drawAspects(g, data);
_drawElements(g, data);
_drawAspects(g, data); // above element ring, below houses/signs/planets
_drawHouses(g, data);
_drawSigns(g, data);
_drawAscMc(g, data);
@@ -706,8 +949,7 @@ const NatusWheel = (() => {
function redraw(data) {
if (!_svg) return;
const svgNode = _svg.node();
draw(svgNode, data);
draw(_svg.node(), data);
}
function clear() {
@@ -717,6 +959,10 @@ const NatusWheel = (() => {
_outsideClickController.abort();
_outsideClickController = null;
}
_aspectsVisible = false;
_aspectPlanet = null;
_aspectGroup = null;
_currentData = null;
}
return { preload, draw, redraw, clear };

View File

@@ -282,6 +282,155 @@ describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
// x ≥ 200 (right side): left = iRect.right - ttW = x + 10 - 0 = x + 10
// ─────────────────────────────────────────────────────────────────────────────
// ── DON / DOFF aspect line persistence ───────────────────────────────────────
//
// Aspect lines belong to the page session, not the tooltip:
// - DON draws lines into .nw-aspects and disables DON btn (shows ×)
// - closing the tooltip does NOT clear lines
// - re-opening the SAME planet preserves _aspectsVisible → DON still disabled
// - opening a DIFFERENT planet resets state: lines cleared, DON active
// - DOFF clears lines; re-opening same planet finds DON active
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — DON/DOFF aspect line persistence", () => {
const ASPECT_CHART = {
planets: {
Sun: { sign: "Capricorn", degree: 280.4, retrograde: false },
Moon: { sign: "Scorpio", degree: 220.1, retrograde: false },
Mars: { sign: "Taurus", degree: 40.7, retrograde: false },
},
houses: {
cusps: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330],
asc: 0, mc: 270,
},
elements: { Fire: 0, Stone: 0, Air: 0, Water: 1, Time: 0, Space: 0 },
aspects: [
{ planet1: "Sun", planet2: "Mars", type: "Trine",
orb: 0.3, angle: 120, applying_planet: "Sun" },
{ planet1: "Sun", planet2: "Moon", type: "Sextile",
orb: 2.9, angle: 60, applying_planet: "Moon" },
],
distinctions: {
"1": 0, "2": 0, "3": 0, "4": 0,
"5": 0, "6": 0, "7": 0, "8": 0,
"9": 0, "10": 0, "11": 0, "12": 0,
},
house_system: "O",
};
let svgEl, tooltipEl;
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
svgEl.style.height = "400px";
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, ASPECT_CHART);
});
afterEach(() => {
NatusWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
function clickPlanet(name) {
svgEl.querySelector(`[data-planet="${name}"]`)
.dispatchEvent(new MouseEvent("click", { bubbles: true }));
}
function clickDon() { tooltipEl.querySelector(".nw-asp-don") .dispatchEvent(new MouseEvent("click", { bubbles: true })); }
function clickDoff() { tooltipEl.querySelector(".nw-asp-doff").dispatchEvent(new MouseEvent("click", { bubbles: true })); }
function aspectLines() { return svgEl.querySelectorAll(".nw-aspects line").length; }
function donDisabled() { return tooltipEl.querySelector(".nw-asp-don").classList.contains("btn-disabled"); }
// T11a — DON draws lines
it("T11a: clicking DON draws aspect lines into .nw-aspects", () => {
clickPlanet("Sun");
expect(aspectLines()).toBe(0);
clickDon();
expect(aspectLines()).toBeGreaterThan(0);
});
// T11b — closing tooltip must not clear aspect lines
it("T11b: closing the tooltip (outside click) does not clear aspect lines", () => {
clickPlanet("Sun");
clickDon();
const lineCount = aspectLines();
expect(lineCount).toBeGreaterThan(0);
document.body.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(tooltipEl.style.display).toBe("none");
expect(aspectLines()).toBe(lineCount);
});
// T11c — re-opening same planet preserves DON-disabled state
it("T11c: re-opening the same planet after DON keeps DON disabled (lines still active)", () => {
clickPlanet("Sun");
clickDon();
expect(donDisabled()).toBe(true);
document.body.dispatchEvent(new MouseEvent("click", { bubbles: true }));
clickPlanet("Sun");
expect(donDisabled()).toBe(true);
expect(aspectLines()).toBeGreaterThan(0);
});
// T11d — switching planet leaves previous DONned lines intact; DON active for new planet
it("T11d: opening a different planet leaves DONned lines intact — DON active for new planet", () => {
clickPlanet("Sun");
clickDon();
const lineCount = aspectLines();
expect(lineCount).toBeGreaterThan(0);
clickPlanet("Moon");
expect(donDisabled()).toBe(false); // Moon's DON is fresh/active
expect(aspectLines()).toBe(lineCount); // Sun's lines still there
});
// T11f — DONning a second planet replaces the first planet's lines + tick
it("T11f: clicking DON on a second planet clears the first planet's lines", () => {
clickPlanet("Sun");
clickDon();
expect(aspectLines()).toBeGreaterThan(0);
clickPlanet("Moon");
clickDon();
expect(donDisabled()).toBe(true); // Moon's DON now disabled
// Moon aspects — Sun's lines replaced (lines may be 0 if Moon has no aspects)
const sunGrp = svgEl.querySelector('[data-planet="Sun"]');
expect(sunGrp.classList.contains('nw-planet--asp-active')).toBe(false);
});
// T11e — DOFF clears lines; re-opening same planet starts fresh
it("T11e: DOFF clears lines; re-opening same planet finds DON active again", () => {
clickPlanet("Sun");
clickDon();
clickDoff();
expect(aspectLines()).toBe(0);
document.body.dispatchEvent(new MouseEvent("click", { bubbles: true }));
clickPlanet("Sun");
expect(donDisabled()).toBe(false);
expect(aspectLines()).toBe(0);
});
});
xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
const HALF_CHART = {

View File

@@ -450,7 +450,8 @@ html.natus-open .natus-modal-wrap {
stroke-linecap: round;
transition: stroke-opacity 0.15s ease;
}
.nw-planet-group.nw-planet--active .nw-planet-tick {
.nw-planet-group.nw-planet--active .nw-planet-tick,
.nw-planet-group.nw-planet--asp-active .nw-planet-tick {
stroke: rgba(var(--terUser), 1);
stroke-opacity: 0.7;
filter: drop-shadow(0 0 3px rgba(var(--terUser), 0.8))
@@ -467,7 +468,21 @@ html.natus-open .natus-modal-wrap {
.nw-planet-tick--np { stroke: rgba(var(--priNp), 1); }
.nw-planet-tick--pu { stroke: rgba(var(--priPu), 1); }
// Aspects
// Aspects — per-planet color tokens (light shades on dark palettes; mid on light)
:root {
--asp-Au: var(--sixAu); --asp-Ag: var(--sixAg);
--asp-Hg: var(--sixHg); --asp-Cu: var(--sixCu);
--asp-Fe: var(--sixFe); --asp-Sn: var(--sixSn);
--asp-Pb: var(--sixPb); --asp-U: var(--sixU);
--asp-Np: var(--sixNp); --asp-Pu: var(--sixPu);
}
body[class*="-light"] {
--asp-Au: var(--terAu); --asp-Ag: var(--terAg);
--asp-Hg: var(--terHg); --asp-Cu: var(--terCu);
--asp-Fe: var(--terFe); --asp-Sn: var(--terSn);
--asp-Pb: var(--terPb); --asp-U: var(--terU);
--asp-Np: var(--terNp); --asp-Pu: var(--terPu);
}
.nw-aspects { opacity: 0.8; }
// Element pie — deasil order: Fire → Stone → Time → Space → Air → Water
@@ -487,12 +502,24 @@ html.natus-open .natus-modal-wrap {
position: fixed;
z-index: 200;
pointer-events: auto;
padding: 0.75rem 1.5rem;
padding: 0.75rem 0.75rem 0.75rem 1.5rem;
min-width: 14rem;
.tt-title { font-size: 1rem; font-weight: 700; }
.tt-title { font-size: 1.25rem; font-weight: 700; margin-bottom: 0.3rem; }
.tt-description { font-size: 0.75rem; }
.tt-sign-icon { fill: currentColor; vertical-align: middle; margin-bottom: 0.1em; }
// DON|DOFF aspect line toggle — stacked at top-left outside the tooltip box,
// matching the PRV/NXT pattern at the bottom corners.
.nw-asp-don,
.nw-asp-doff {
position: absolute;
left: -1rem;
margin: 0;
}
.nw-asp-don { top: -1rem; }
.nw-asp-doff { top: 1.2rem; }
.nw-tt-prv,
.nw-tt-nxt {
position: absolute;
@@ -502,6 +529,82 @@ html.natus-open .natus-modal-wrap {
.nw-tt-prv { left: -1rem; }
.nw-tt-nxt { right: -1rem; }
// Aspect list — always visible below planet description
.tt-aspects {
display: block;
margin-top: 0.6rem;
font-size: 0.94rem;
font-weight: 600;
opacity: 0.85;
line-height: 1.3;
}
.tt-asp-row {
display: flex;
align-items: center;
gap: 0.3rem;
white-space: nowrap;
}
.tt-asp-line { flex-shrink: 0; vertical-align: middle; }
.tt-asp-orb,
.tt-asp-in {
opacity: 0.6;
font-size: 0.65rem;
padding-left: 0.25rem;
}
// Element tooltip — title + square badge
.tt-el-header {
margin-bottom: 0.3rem;
}
.tt-el-square {
float: right;
display: block;
margin-left: 0.5rem;
}
.tt-el-vec {
display: inline;
vertical-align: middle;
margin: 0 0.1em;
}
.tt-el-body-line {
font-size: 0.75rem;
opacity: 0.9;
margin-bottom: 0.25rem;
}
.tt-el-contribs {
display: flex;
flex-direction: column;
gap: 0.2rem;
margin-top: 0.3rem;
}
.tt-el-planet-row {
opacity: 0.75;
margin-left: 0.5rem;
margin-top: -0.1rem;
}
.tt-el-formation-header {
font-size: 0.85rem;
font-weight: 600;
text-decoration: underline;
margin-top: 0.35rem;
}
.tt-el-formation {
font-size: 0.75rem;
opacity: 0.7;
margin-top: 0.2rem;
font-style: italic;
}
// Planet title colors — senary (brightest) tier on dark palettes
.tt-title--au { color: rgba(var(--sixAu), 1); } // Sun
.tt-title--ag { color: rgba(var(--sixAg), 1); } // Moon

View File

@@ -126,49 +126,49 @@
--priOr: 225, 133, 40;
--secOr: 187, 111, 30;
--terOr: 150, 88, 17;
// yellow (
// yellow (A-Time)
--priYl: 255, 207, 52;
--secYl: 211, 172, 44;
--terYl: 168, 138, 33;
// lime
// lime (B-Time)
--priLm: 151, 174, 60;
--secLm: 124, 145, 48;
--terLm: 97, 117, 36;
// green
// green (A-Space)
--priGn: 0, 160, 75;
--secGn: 0, 135, 62;
--terGn: 0, 109, 48;
// teal
// teal (B-Space)
--priTk: 0, 184, 162;
--secTk: 0, 154, 136;
--terTk: 0, 125, 110;
// cyan
// cyan (A-Air)
--priCy: 13, 179, 200;
--secCy: 12, 150, 168;
--terCy: 0, 121, 136;
// blue
// blue (B-Air)
--priBl: 20, 141, 205;
--secBl: 18, 119, 173;
--terBl: 8, 95, 140;
// indigo
// indigo (A-Water)
--priId: 79, 102, 212;
--secId: 66, 88, 184;
--terId: 53, 74, 156;
--quaId: 44, 60, 131;
--quiId: 32, 44, 106;
--sixId: 21, 29, 71;
// violet
// violet (B-Water)
--priVt: 120, 72, 183;
--secVt: 108, 65, 165;
--terVt: 96, 58, 147;
--quaVt: 80, 45, 124;
--quiVt: 64, 30, 100;
--sixVt: 43, 20, 66;
// fuschia
// fuschia (A-Stone)
--priFs: 158, 61, 150;
--secFs: 133, 47, 126;
--terFs: 107, 31, 101;
// magenta
// magenta (B-Stone)
--priMe: 237, 30, 129;
--secMe: 196, 18, 108;
--terMe: 158, 1, 86;

View File

@@ -282,6 +282,155 @@ describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
// x ≥ 200 (right side): left = iRect.right - ttW = x + 10 - 0 = x + 10
// ─────────────────────────────────────────────────────────────────────────────
// ── DON / DOFF aspect line persistence ───────────────────────────────────────
//
// Aspect lines belong to the page session, not the tooltip:
// - DON draws lines into .nw-aspects and disables DON btn (shows ×)
// - closing the tooltip does NOT clear lines
// - re-opening the SAME planet preserves _aspectsVisible → DON still disabled
// - opening a DIFFERENT planet resets state: lines cleared, DON active
// - DOFF clears lines; re-opening same planet finds DON active
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — DON/DOFF aspect line persistence", () => {
const ASPECT_CHART = {
planets: {
Sun: { sign: "Capricorn", degree: 280.4, retrograde: false },
Moon: { sign: "Scorpio", degree: 220.1, retrograde: false },
Mars: { sign: "Taurus", degree: 40.7, retrograde: false },
},
houses: {
cusps: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330],
asc: 0, mc: 270,
},
elements: { Fire: 0, Stone: 0, Air: 0, Water: 1, Time: 0, Space: 0 },
aspects: [
{ planet1: "Sun", planet2: "Mars", type: "Trine",
orb: 0.3, angle: 120, applying_planet: "Sun" },
{ planet1: "Sun", planet2: "Moon", type: "Sextile",
orb: 2.9, angle: 60, applying_planet: "Moon" },
],
distinctions: {
"1": 0, "2": 0, "3": 0, "4": 0,
"5": 0, "6": 0, "7": 0, "8": 0,
"9": 0, "10": 0, "11": 0, "12": 0,
},
house_system: "O",
};
let svgEl, tooltipEl;
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
svgEl.style.height = "400px";
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, ASPECT_CHART);
});
afterEach(() => {
NatusWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
function clickPlanet(name) {
svgEl.querySelector(`[data-planet="${name}"]`)
.dispatchEvent(new MouseEvent("click", { bubbles: true }));
}
function clickDon() { tooltipEl.querySelector(".nw-asp-don") .dispatchEvent(new MouseEvent("click", { bubbles: true })); }
function clickDoff() { tooltipEl.querySelector(".nw-asp-doff").dispatchEvent(new MouseEvent("click", { bubbles: true })); }
function aspectLines() { return svgEl.querySelectorAll(".nw-aspects line").length; }
function donDisabled() { return tooltipEl.querySelector(".nw-asp-don").classList.contains("btn-disabled"); }
// T11a — DON draws lines
it("T11a: clicking DON draws aspect lines into .nw-aspects", () => {
clickPlanet("Sun");
expect(aspectLines()).toBe(0);
clickDon();
expect(aspectLines()).toBeGreaterThan(0);
});
// T11b — closing tooltip must not clear aspect lines
it("T11b: closing the tooltip (outside click) does not clear aspect lines", () => {
clickPlanet("Sun");
clickDon();
const lineCount = aspectLines();
expect(lineCount).toBeGreaterThan(0);
document.body.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(tooltipEl.style.display).toBe("none");
expect(aspectLines()).toBe(lineCount);
});
// T11c — re-opening same planet preserves DON-disabled state
it("T11c: re-opening the same planet after DON keeps DON disabled (lines still active)", () => {
clickPlanet("Sun");
clickDon();
expect(donDisabled()).toBe(true);
document.body.dispatchEvent(new MouseEvent("click", { bubbles: true }));
clickPlanet("Sun");
expect(donDisabled()).toBe(true);
expect(aspectLines()).toBeGreaterThan(0);
});
// T11d — switching planet leaves previous DONned lines intact; DON active for new planet
it("T11d: opening a different planet leaves DONned lines intact — DON active for new planet", () => {
clickPlanet("Sun");
clickDon();
const lineCount = aspectLines();
expect(lineCount).toBeGreaterThan(0);
clickPlanet("Moon");
expect(donDisabled()).toBe(false); // Moon's DON is fresh/active
expect(aspectLines()).toBe(lineCount); // Sun's lines still there
});
// T11f — DONning a second planet replaces the first planet's lines + tick
it("T11f: clicking DON on a second planet clears the first planet's lines", () => {
clickPlanet("Sun");
clickDon();
expect(aspectLines()).toBeGreaterThan(0);
clickPlanet("Moon");
clickDon();
expect(donDisabled()).toBe(true); // Moon's DON now disabled
// Moon aspects — Sun's lines replaced (lines may be 0 if Moon has no aspects)
const sunGrp = svgEl.querySelector('[data-planet="Sun"]');
expect(sunGrp.classList.contains('nw-planet--asp-active')).toBe(false);
});
// T11e — DOFF clears lines; re-opening same planet starts fresh
it("T11e: DOFF clears lines; re-opening same planet finds DON active again", () => {
clickPlanet("Sun");
clickDon();
clickDoff();
expect(aspectLines()).toBe(0);
document.body.dispatchEvent(new MouseEvent("click", { bubbles: true }));
clickPlanet("Sun");
expect(donDisabled()).toBe(false);
expect(aspectLines()).toBe(0);
});
});
xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
const HALF_CHART = {