natus wheel: planet/sign/house tooltip layout overhaul — TDD

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-21 21:20:05 -04:00
parent 3974fdac82
commit bb1cda9c9c
2 changed files with 154 additions and 11 deletions

View File

@@ -103,6 +103,12 @@ const NatusWheel = (() => {
'Cooperation', 'Regeneration', 'Enterprise', 'Career', 'Reward', 'Reprisal', 'Cooperation', 'Regeneration', 'Enterprise', 'Career', 'Reward', 'Reprisal',
]; ];
// Cardinal / Fixed / Mutable — parallel index to SIGNS
const SIGN_MODALITIES = [
'Cardinal', 'Fixed', 'Mutable', 'Cardinal', 'Fixed', 'Mutable',
'Cardinal', 'Fixed', 'Mutable', 'Cardinal', 'Fixed', 'Mutable',
];
// ── State ───────────────────────────────────────────────────────────────── // ── State ─────────────────────────────────────────────────────────────────
let _svg = null; let _svg = null;
@@ -186,6 +192,21 @@ const NatusWheel = (() => {
return ((ecliptic % 360) + 360) % 360 % 30; return ((ecliptic % 360) + 360) % 360 % 30;
} }
/** Return 1-based house number for an ecliptic degree given 12 cusps. */
function _planetHouse(degree, cusps) {
degree = ((degree % 360) + 360) % 360;
for (let i = 0; i < 12; i++) {
const start = ((cusps[i] % 360) + 360) % 360;
const end = ((cusps[(i + 1) % 12] % 360) + 360) % 360;
if (start < end) {
if (start <= degree && degree < end) return i + 1;
} else {
if (degree >= start || degree < end) return i + 1;
}
}
return 1;
}
/** Inline SVG for a zodiac sign icon (preloaded path). */ /** Inline SVG for a zodiac sign icon (preloaded path). */
function _signIconSvg(signName) { function _signIconSvg(signName) {
const d = _signPaths[signName]; const d = _signPaths[signName];
@@ -400,8 +421,14 @@ const NatusWheel = (() => {
if (_ttBody) { if (_ttBody) {
_ttBody.innerHTML = _ttBody.innerHTML =
`<div class="tt-title tt-title--${el}">${item.name} (${sym})</div>` + `<div class="tt-planet-header">` +
`<div class="tt-description">@${inDeg}° ${pdata.sign} (${icon})${rx}</div>` + `<span class="tt-title tt-title--${el}">${item.name}</span>` +
`<span class="tt-planet-sym tt-title--${el}">${sym}</span>` +
`</div>` +
`<div class="tt-planet-loc">` +
`<span>@${inDeg}° ${pdata.sign}${rx}</span>` +
`<span class="tt-planet-sign-icon">${icon}</span>` +
`</div>` +
aspectHtml; aspectHtml;
} }
@@ -524,14 +551,41 @@ const NatusWheel = (() => {
function _activateSign(idx) { function _activateSign(idx) {
_activeRing = 'signs'; _activeRing = 'signs';
_activeIdx = idx; _activeIdx = idx;
const sign = _signItems[idx]; const sign = _signItems[idx];
const elKey = sign.element.toLowerCase();
const modality = SIGN_MODALITIES[idx];
const vecImg = _elementVectorImg(sign.element);
const iconSvg = _signIconSvg(sign.name) ||
`<span class="tt-sign-sym-fallback">${sign.symbol}</span>`;
let planetsHtml = '';
if (_currentData) {
const cusps = (_currentData.houses || {}).cusps || [];
const inSign = Object.entries(_currentData.planets || {})
.filter(([, p]) => p.sign === sign.name)
.sort((a, b) => a[1].degree - b[1].degree);
inSign.forEach(([pname, pdata]) => {
const psym = PLANET_SYMBOLS[pname] || pname[0];
const inDeg = _inSignDeg(pdata.degree).toFixed(1);
const house = cusps.length ? _planetHouse(pdata.degree, cusps) : null;
const domain = house ? HOUSE_LABELS[house] : '';
planetsHtml +=
`<div class="tt-asp-row">${psym} @${inDeg}°` +
(domain ? `, House of ${domain}` : '') + `</div>`;
});
}
if (_ttBody) { if (_ttBody) {
_ttBody.innerHTML = _ttBody.innerHTML =
`<div class="tt-sign-header">` + `<div class="tt-sign-header">` +
`<span class="tt-sign-symbol">${sign.symbol}</span>` + `<span class="tt-title tt-title--el-${elKey}">${sign.name}</span>` +
`<span class="tt-title">${sign.name}</span>` + `<span class="tt-sign-icon-wrap">${iconSvg}</span>` +
`<span class="tt-sign-element"> · ${sign.element}</span>` + `</div>` +
`</div>`; `<div class="tt-sign-meta">` +
`<span>${modality} ${sign.element}</span>` +
vecImg +
`</div>` +
(planetsHtml ? `<div class="tt-sign-planets">${planetsHtml}</div>` : '');
} }
_positionTooltipAtItem('signs', idx); _positionTooltipAtItem('signs', idx);
if (_tooltipEl) { if (_tooltipEl) {
@@ -544,12 +598,28 @@ const NatusWheel = (() => {
_activeRing = 'houses'; _activeRing = 'houses';
_activeIdx = idx; _activeIdx = idx;
const house = _houseItems[idx]; const house = _houseItems[idx];
const cusps = (_currentData && (_currentData.houses || {}).cusps) || [];
let planetsHtml = '';
if (cusps.length && _currentData) {
const inHouse = Object.entries(_currentData.planets || {})
.filter(([, p]) => _planetHouse(p.degree, cusps) === house.num)
.sort((a, b) => a[1].degree - b[1].degree);
inHouse.forEach(([pname, pdata]) => {
const psym = PLANET_SYMBOLS[pname] || pname[0];
const inDeg = _inSignDeg(pdata.degree).toFixed(1);
const sicon = _signIconSvg(pdata.sign) || (SIGNS.find(s => s.name === pdata.sign) || {}).symbol || '';
planetsHtml += `<div class="tt-asp-row">${psym} @${inDeg}° ${sicon}</div>`;
});
}
if (_ttBody) { if (_ttBody) {
_ttBody.innerHTML = _ttBody.innerHTML =
`<div class="tt-house-header">` + `<div class="tt-house-header">` +
`<span class="tt-title">${house.num}</span>` + `<span class="tt-title">House of ${house.label}</span>` +
`<span class="tt-house-label"> · ${house.label}</span>` + `<span class="tt-house-num">${house.num}</span>` +
`</div>`; `</div>` +
(planetsHtml ? `<div class="tt-house-planets">${planetsHtml}</div>` : '');
} }
_positionTooltipAtItem('houses', idx); _positionTooltipAtItem('houses', idx);
if (_tooltipEl) { if (_tooltipEl) {

View File

@@ -505,10 +505,83 @@ body[class*="-light"] {
padding: 0.75rem 0.75rem 0.75rem 1.5rem; padding: 0.75rem 0.75rem 0.75rem 1.5rem;
min-width: 14rem; min-width: 14rem;
.tt-title { font-size: 1.25rem; font-weight: 700; margin-bottom: 0.3rem; } .tt-title { font-size: 1.25rem; font-weight: 700; margin-bottom: 0; }
.tt-description { font-size: 0.75rem; } .tt-description { font-size: 0.75rem; }
.tt-sign-icon { fill: currentColor; vertical-align: middle; margin-bottom: 0.1em; } .tt-sign-icon { fill: currentColor; vertical-align: middle; margin-bottom: 0.1em; }
// Planet tooltip — flex row: name | symbol; location row: @deg° Sign | sign icon
.tt-planet-header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 0.2rem;
}
.tt-planet-sym {
font-size: 1.2rem;
opacity: 0.85;
}
.tt-planet-loc {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
font-size: 0.8rem;
margin-bottom: 0.3rem;
}
.tt-planet-sign-icon { font-size: 1.2rem; line-height: 1; }
// Sign tooltip — name in element color | SVG icon; modality | vector; planets
.tt-sign-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 0.2rem;
}
.tt-sign-icon-wrap {
font-size: 1.5rem;
line-height: 1;
flex-shrink: 0;
.tt-sign-icon { fill: currentColor; }
}
.tt-sign-meta {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.75rem;
opacity: 0.85;
margin-bottom: 0.3rem;
}
.tt-sign-planets {
display: flex;
flex-direction: column;
gap: 0.15rem;
margin-top: 0.1rem;
font-size: 0.85rem;
}
// House tooltip — "House of X" | number; planets in house
.tt-house-header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 0.5rem;
margin-bottom: 0.3rem;
}
.tt-house-num {
font-size: 1.4rem;
font-weight: 700;
opacity: 0.55;
flex-shrink: 0;
}
.tt-house-planets {
display: flex;
flex-direction: column;
gap: 0.15rem;
font-size: 0.85rem;
}
// DON|DOFF aspect line toggle — stacked at top-left outside the tooltip box, // DON|DOFF aspect line toggle — stacked at top-left outside the tooltip box,
// matching the PRV/NXT pattern at the bottom corners. // matching the PRV/NXT pattern at the bottom corners.
.nw-asp-don, .nw-asp-don,