natus wheel: ASC/MC angles — tooltips, aspect lines, section headers, tooltip polish

- ASC/MC clickable w. DON/DOFF aspect lines (fixed: open w.o. lines; DON/DOFF
  both work; angles ring handled in _toggleAspects; lines origin at R.planetR)
- btn-disabled click-through fix: pointer-events:auto on DON/DOFF; bounding-rect
  workaround removed
- planet tooltip: applying ⇥ left, separating ↦ right; sign shown for angle partners
- sign tooltip: Planets + Cusps section headers; ordinal house + domain; em-dash fallback
- house tooltip: Planets header; Angular/Succedent/Cadent + phase labels; em-dash fallback
- element tooltips: Planets header for Fire/Stone/Air/Water; Stellium/Parade as
  section-header labels; compact single-stellium Tempo; Parade sign : planets format
- tt-ord: no negative margin in .tt-angle-house context

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-22 00:58:19 -04:00
parent 5c05bd6552
commit 0b2320e39b
6 changed files with 752 additions and 109 deletions

View File

@@ -373,33 +373,33 @@ html.natus-open .natus-modal-wrap {
.nw-axis-line { stroke: rgba(var(--secUser), 1); stroke-width: 1.5px; }
.nw-axis-label { fill: rgba(var(--secUser), 1); }
// Sign ring — uniform --priMe bg at half opacity
// Sign ring — uniform --priYl bg at half opacity
.nw-sign--fire,
.nw-sign--stone,
.nw-sign--air,
.nw-sign--water {
fill: rgba(var(--priMe), 0.25);
fill: rgba(var(--priYl), 0.25);
stroke: rgba(var(--terUser), 1);
stroke-width: 0.75px;
}
// Icon bg circles — element fill + matching border
.nw-sign-icon-bg--fire { fill: rgba(var(--terRd), 0.92); stroke: rgba(var(--priOr), 1); stroke-width: 1px; }
.nw-sign-icon-bg--stone { fill: rgba(var(--priYl), 0.92); stroke: rgba(var(--priLm), 1); stroke-width: 1px; }
.nw-sign-icon-bg--air { fill: rgba(var(--terGn), 0.92); stroke: rgba(var(--priTk), 1); stroke-width: 1px; }
.nw-sign-icon-bg--water { fill: rgba(var(--priCy), 0.92); stroke: rgba(var(--priBl), 1); stroke-width: 1px; }
.nw-sign-icon-bg--fire { fill: rgba(var(--quaRd), 0.92); stroke: rgba(var(--priOr), 1); stroke-width: 1px; }
.nw-sign-icon-bg--stone { fill: rgba(var(--quaFs), 0.92); stroke: rgba(var(--priMe), 1); stroke-width: 1px; }
.nw-sign-icon-bg--air { fill: rgba(var(--quiCy), 0.92); stroke: rgba(var(--priBl), 1); stroke-width: 1px; }
.nw-sign-icon-bg--water { fill: rgba(var(--sixId), 0.92); stroke: rgba(var(--priVt), 1); stroke-width: 1px; }
// Inline SVG path icons — per-element colors
.nw-sign-icon--fire { fill: rgba(var(--priOr), 1); text-shadow: 2px 2px 2px rgba(0, 0, 0, 1); }
.nw-sign-icon--stone { fill: rgba(var(--terLm), 1); text-shadow: 2px 2px 2px rgba(0, 0, 0, 1); }
.nw-sign-icon--air { fill: rgba(var(--priTk), 1); text-shadow: 2px 2px 2px rgba(0, 0, 0, 1); }
.nw-sign-icon--water { fill: rgba(var(--terBl), 1); text-shadow: 2px 2px 2px rgba(0, 0, 0, 1); }
.nw-sign-icon--fire { fill: rgba(var(--priOr), 1); }
.nw-sign-icon--stone { fill: rgba(var(--priMe), 1); }
.nw-sign-icon--air { fill: rgba(var(--priBl), 1); }
.nw-sign-icon--water { fill: rgba(var(--priVt), 1); }
// House ring — uniform --priFs bg
// House ring — uniform --priGn 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; }
.nw-house-fill--even { fill: rgba(var(--secGn), 0.25); stroke: rgba(var(--terUser), 1); stroke-width: 0.75px; }
.nw-house-fill--odd { fill: rgba(var(--quiGn), 0.25); stroke: rgba(var(--terUser), 1); stroke-width: 0.75px; }
// Planets — base geometry
.nw-planet-circle,
@@ -431,17 +431,34 @@ html.natus-open .natus-modal-wrap {
.nw-planet-label--pu { fill: rgba(var(--sixPu), 1); stroke: rgba(var(--sixPu), 0.6); }
.nw-rx { fill: rgba(var(--terUser), 1); }
// Hover and active-lock glow — planet groups and element slice groups
// Hover and active-lock glow — planet, element, sign, house groups
.nw-planet-group,
.nw-element-group { cursor: pointer; }
.nw-element-group,
.nw-sign-group,
.nw-house-group { cursor: pointer; }
.nw-planet-group:hover,
.nw-planet-group.nw-planet--active,
.nw-planet-group.nw-planet--asp-active,
.nw-element-group:hover,
.nw-element-group.nw-element--active {
.nw-element-group.nw-element--active,
.nw-sign-group:hover,
.nw-sign-group.nw-sign--active,
.nw-house-group:hover,
.nw-house-group.nw-house--active {
filter: drop-shadow(0 0 5px rgba(var(--ninUser), 0.9));
}
// Zodiac icon circles — muted by default, full opacity on hover/active
.nw-sign-icon-bg { opacity: 0.5; }
.nw-sign-group:hover .nw-sign-icon-bg,
.nw-sign-group.nw-sign--active .nw-sign-icon-bg { opacity: 1; }
// House numbers — muted by default, full opacity on hover/active
.nw-house-num { opacity: 0.75; }
.nw-house-group:hover .nw-house-num,
.nw-house-group.nw-house--active .nw-house-num { opacity: 1; }
// ── Planet tick lines — hidden until parent group is active ──────────────────
.nw-planet-tick {
fill: none;
@@ -521,6 +538,14 @@ body[class*="-light"] {
font-size: 1.2rem;
opacity: 0.85;
}
.tt-angle-sym {
font-variant-caps: all-small-caps;
font-size: 1.1rem;
opacity: 0.85;
}
.tt-angle-house .tt-ord {
margin-left: 0;
}
.tt-planet-loc {
display: flex;
align-items: center;
@@ -560,6 +585,19 @@ body[class*="-light"] {
margin-top: 0.1rem;
font-size: 0.85rem;
}
.tt-sign-section-header {
font-size: 0.65rem;
font-weight: 600;
opacity: 0.55;
letter-spacing: 0.04em;
margin-bottom: 0.15rem;
}
.tt-sign-cusps {
display: flex;
flex-direction: column;
gap: 0.15rem;
font-size: 0.85rem;
}
// House tooltip — "House of X" | number; planets in house
.tt-house-header {
@@ -581,12 +619,24 @@ body[class*="-light"] {
opacity: 1;
flex-shrink: 0;
}
.tt-house-type {
display: block;
font-size: 0.6em;
font-weight: 400;
opacity: 0.7;
margin-top: 0.1em;
}
.tt-house-planets {
display: flex;
flex-direction: column;
gap: 0.15rem;
font-size: 0.85rem;
}
.tt-house-planet-row {
display: flex;
align-items: center;
gap: 0.3rem;
}
// DON|DOFF aspect line toggle — stacked at top-left outside the tooltip box,
// matching the PRV/NXT pattern at the bottom corners.
@@ -595,6 +645,7 @@ body[class*="-light"] {
position: absolute;
left: -1rem;
margin: 0;
pointer-events: auto; // override btn-disabled; click must land here, not pass through to SVG
}
.nw-asp-don { top: -1rem; }
.nw-asp-doff { top: 1.2rem; }
@@ -627,12 +678,30 @@ body[class*="-light"] {
.tt-asp-line { flex-shrink: 0; vertical-align: middle; }
.tt-asp-orb,
.tt-asp-orb {
margin-left: auto;
opacity: 0.6;
font-size: 0.65rem;
padding-left: 0.5rem;
white-space: nowrap;
}
.tt-asp-in {
opacity: 0.6;
font-size: 0.65rem;
padding-left: 0.25rem;
}
.tt-dim {
opacity: 0.6;
font-size: 0.65rem;
}
.tt-ord {
font-size: 0.6rem;
vertical-align: 0.25rem;
line-height: 0;
opacity: 1;
margin-left: -0.25rem;
letter-spacing: 0;
}
// Element tooltip — title + square badge
.tt-el-header {
@@ -672,9 +741,14 @@ body[class*="-light"] {
.tt-el-formation-header {
font-size: 0.85rem;
font-weight: 600;
text-decoration: underline;
font-weight: 700;
margin-top: 0.35rem;
.tt-el-formation-label {
font-size: 0.65rem;
font-weight: 600;
opacity: 0.55;
letter-spacing: 0.04em;
}
}
.tt-el-formation {
@@ -708,6 +782,24 @@ body[class*="-light"] {
.tt-title--el-water { color: rgba(var(--priId), 1); }
}
// Sign tooltip title + sign icon SVG — element border colors (Stone/Air/Fire/Water schema)
#id_natus_tooltip,
#id_natus_tooltip_2 {
.tt-title--sign-fire { color: rgba(var(--priOr), 1); }
.tt-title--sign-stone { color: rgba(var(--priMe), 1); }
.tt-title--sign-air { color: rgba(var(--priBl), 1); }
.tt-title--sign-water { color: rgba(var(--priVt), 1); }
.tt-sign-icon-wrap--fire .tt-sign-icon,
.tt-planet-sign-icon--fire .tt-sign-icon { fill: rgba(var(--priOr), 1); }
.tt-sign-icon-wrap--stone .tt-sign-icon,
.tt-planet-sign-icon--stone .tt-sign-icon { fill: rgba(var(--priMe), 1); }
.tt-sign-icon-wrap--air .tt-sign-icon,
.tt-planet-sign-icon--air .tt-sign-icon { fill: rgba(var(--priBl), 1); }
.tt-sign-icon-wrap--water .tt-sign-icon,
.tt-planet-sign-icon--water .tt-sign-icon { fill: rgba(var(--priVt), 1); }
}
// On light palettes — switch to tertiary tier for legibility
body[class*="-light"] #id_natus_tooltip,
body[class*="-light"] #id_natus_tooltip_2 {

View File

@@ -819,3 +819,137 @@ describe("NatusWheel — house ring click tooltips", () => {
expect(tooltipEl.style.display).toBe("none");
});
});
// ── T15 — ASC / MC angle click tooltips + aspect lines ────────────────────────
//
// ASC and MC labels are clickable groups ([data-angle='ASC'] / [data-angle='MC']).
// Clicking shows a tooltip similar to a planet tooltip:
// Title: "Ascendant" (ASC) or "Midheaven" (MC)
// Degree in sign + sign name, plus the house number the angle defines.
// Aspect list for planets within 10° orb of the angle (client-side computed).
// Aspect lines drawn to those planets.
// Clicking same angle again closes the tooltip.
// Planet tooltips include angle aspects in their own aspect lists.
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — angle (ASC/MC) click tooltips", () => {
// ASC=0°(Aries): Sun@8° → Conjunction orb 8° ✓; Mars@188° → Opposition orb 8° ✓
// MC=90°(Cancer): Moon@97° → Conjunction orb 7° ✓
const ANGLE_CHART = {
planets: {
Sun: { sign: "Aries", degree: 8.0, retrograde: false },
Moon: { sign: "Cancer", degree: 97.0, retrograde: false },
Mars: { sign: "Libra", degree: 188.0, retrograde: false },
},
houses: {
cusps: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330],
asc: 0.0,
mc: 90.0,
},
elements: { Fire: 2, Stone: 0, Air: 1, Water: 1, Time: 0, Space: 0 },
aspects: [],
distinctions: {
"1":1,"2":0,"3":0,"4":1,"5":0,"6":0,
"7":1,"8":0,"9":0,"10":1,"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, ANGLE_CHART);
});
afterEach(() => {
NatusWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
// T15a — ASC label is a clickable group with data-angle='ASC'
it("T15a: clicking [data-angle='ASC'] shows tooltip with 'Ascendant' title and house 1", () => {
const ascGroup = svgEl.querySelector("[data-angle='ASC']");
expect(ascGroup).not.toBeNull("expected [data-angle='ASC'] to exist in the SVG");
ascGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(tooltipEl.style.display).toBe("block");
const text = tooltipEl.textContent;
expect(text).toContain("Ascendant");
// ASC=0° is the cusp of House 1
expect(text).toContain("1");
});
// T15b — MC label is a clickable group with data-angle='MC'
it("T15b: clicking [data-angle='MC'] shows tooltip with 'Midheaven' title and house 10", () => {
const mcGroup = svgEl.querySelector("[data-angle='MC']");
expect(mcGroup).not.toBeNull("expected [data-angle='MC'] to exist in the SVG");
mcGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(tooltipEl.style.display).toBe("block");
const text = tooltipEl.textContent;
expect(text).toContain("Midheaven");
expect(text).toContain("10");
});
// T15c — ASC tooltip shows degree-in-sign and sign name
it("T15c: ASC tooltip shows the in-sign degree and sign of the Ascendant", () => {
const ascGroup = svgEl.querySelector("[data-angle='ASC']");
ascGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
const text = tooltipEl.textContent;
// ASC=0° → 0° Aries
expect(text).toContain("Aries");
expect(text).toContain("0.0");
});
// T15d — clicking same angle a second time hides the tooltip
it("T15d: clicking the same angle again hides the tooltip", () => {
const ascGroup = svgEl.querySelector("[data-angle='ASC']");
ascGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(tooltipEl.style.display).toBe("block");
ascGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(tooltipEl.style.display).toBe("none");
});
// T15e — ASC tooltip lists planets within 10° orb (client-side computed)
it("T15e: ASC tooltip includes aspect rows for planets within 10° orb", () => {
const ascGroup = svgEl.querySelector("[data-angle='ASC']");
ascGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
const bodyHtml = tooltipEl.querySelector(".nw-tt-body").innerHTML;
// Sun at 8° → Conjunction (orb 8°) ✓ — _pSym emits data-planet attr
expect(bodyHtml).toContain('data-planet="Sun"');
// Mars at 188° → Opposition to ASC (0°), angular distance 172° → orb 8° ✓
expect(bodyHtml).toContain('data-planet="Mars"');
});
// T15f — planet tooltip includes ASC in its aspect list when within orb
it("T15f: planet tooltip for Sun lists ASC as an aspect partner", () => {
const sunGroup = svgEl.querySelector("[data-planet='Sun']");
expect(sunGroup).not.toBeNull();
sunGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
const bodyHtml = tooltipEl.querySelector(".nw-tt-body").innerHTML;
expect(bodyHtml).toContain("ASC");
});
});