PICK SKY: natal wheel planet tooltips + FT modernisation

- natus-wheel.js: per-planet <g> group with data-planet/sign/degree/retrograde
  attrs; mouseover/mouseout on group (pointer-events:none on child text/℞ so
  the whole apparatus triggers hover); tooltip uses .tt-title/.tt-description;
  in-sign degree via _inSignDeg() (ecliptic % 30); D3 switched from CDN to
  local d3.min.js
- _natus.scss: .nw-planet--hover glow; #id_natus_tooltip position:fixed z-200
- _natus_overlay.html: tooltip div uses .tt; local d3.min.js script tag
- T3/T4/T5 converted from Selenium execute_script to Jasmine unit tests
  (NatusWheelSpec.js) — NatusWheel was never defined in headless GeckoDriver;
  SpecRunner.html updated to load D3 + natus-wheel.js
- test_pick_sky.py: NatusWheelTooltipTest removed (replaced by Jasmine)
- test_component_cards_tarot / test_trinket_carte_blanche: equip assertions
  updated from legacy .equip-deck-btn/.equip-trinket-btn mini-tooltip pattern
  to current DON|DOFF (.btn-equip in main portal); mini-portal text assertions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-04-16 01:57:02 -04:00
parent db9ac9cb24
commit 2910012b67
12 changed files with 370 additions and 59 deletions

View File

@@ -384,16 +384,16 @@ html.natus-open .natus-modal-wrap {
}
// Icon bg circles — element fill + matching border
.nw-sign-icon-bg--fire { fill: rgba(var(--terRd), 1); stroke: rgba(var(--priOr), 1); stroke-width: 1px; }
.nw-sign-icon-bg--stone { fill: rgba(var(--priYl), 1); stroke: rgba(var(--priLm), 1); stroke-width: 1px; }
.nw-sign-icon-bg--air { fill: rgba(var(--terGn), 1); stroke: rgba(var(--priTk), 1); stroke-width: 1px; }
.nw-sign-icon-bg--water { fill: rgba(var(--priCy), 1); stroke: rgba(var(--priBl), 1); stroke-width: 1px; }
.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; }
// Inline SVG path icons — per-element colors
.nw-sign-icon--fire { fill: rgba(var(--priOr), 1); }
.nw-sign-icon--stone { fill: rgba(var(--terLm), 1); }
.nw-sign-icon--air { fill: rgba(var(--priTk), 1); }
.nw-sign-icon--water { fill: rgba(var(--terBl), 1); }
.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); }
// House ring — uniform --priFs bg
.nw-house-cusp { stroke: rgba(var(--terUser), 1); stroke-width: 1.2px; }
@@ -431,6 +431,12 @@ 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); }
// Planet hover glow (--ninUser)
.nw-planet--hover {
filter: drop-shadow(0 0 5px rgba(var(--ninUser), 0.9));
cursor: pointer;
}
// Aspects
.nw-aspects { opacity: 0.8; }
@@ -444,6 +450,14 @@ html.natus-open .natus-modal-wrap {
.nw-element-label--time { fill: rgba(var(--priYl, 192, 160, 48), 1); }
.nw-element-label--space { fill: rgba(var(--priGn, 64, 96, 64), 1); }
// ── Planet hover tooltip — uses .tt base styles; overrides position + z ───────
#id_natus_tooltip {
position: fixed;
z-index: 200;
pointer-events: none;
}
// ── Sidebar z-index sink (landscape sidebars must go below backdrop) ───────────
@media (orientation: landscape) {

View File

@@ -0,0 +1,124 @@
// ── NatusWheelSpec.js ─────────────────────────────────────────────────────────
//
// Unit specs for natus-wheel.js — planet hover tooltips.
//
// DOM contract assumed:
// <svg id="id_natus_svg"> — target for NatusWheel.draw()
// <div id="id_natus_tooltip"> — tooltip portal (position:fixed on page)
//
// Public API under test:
// NatusWheel.draw(svgEl, data) — renders wheel; attaches hover listeners
// NatusWheel.clear() — empties the SVG (used in afterEach)
//
// Hover contract:
// mouseover on [data-planet] group → adds .nw-planet--hover class
// shows #id_natus_tooltip with
// planet name, in-sign degree, sign name
// and ℞ if retrograde
// mouseout on [data-planet] group → removes .nw-planet--hover
// hides #id_natus_tooltip
//
// In-sign degree: ecliptic_longitude % 30 (e.g. 338.4° → 8.4° Pisces)
//
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — planet tooltips", () => {
const SYNTHETIC_CHART = {
planets: {
Sun: { sign: "Pisces", degree: 338.4, retrograde: false },
Moon: { sign: "Capricorn", degree: 295.1, retrograde: false },
Mercury: { sign: "Aquarius", degree: 312.8, retrograde: true },
},
houses: {
cusps: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330],
asc: 0,
mc: 270,
},
elements: { Fire: 1, Stone: 2, Air: 1, Water: 3, Time: 1, Space: 2 },
aspects: [],
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: "P",
};
let svgEl, tooltipEl;
beforeEach(() => {
// SVG element — D3 draws into this
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);
// Tooltip portal — same markup as _natus_overlay.html
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, SYNTHETIC_CHART);
});
afterEach(() => {
NatusWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
// ── T3 ── hover planet shows name / sign / in-sign degree + glow ─────────
it("T3: hovering a planet group adds the glow class and shows the tooltip with name, sign, and in-sign degree", () => {
const sun = svgEl.querySelector("[data-planet='Sun']");
expect(sun).not.toBeNull("expected [data-planet='Sun'] to exist in the SVG");
sun.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
expect(sun.classList.contains("nw-planet--hover")).toBe(true);
expect(tooltipEl.style.display).toBe("block");
const text = tooltipEl.textContent;
expect(text).toContain("Sun");
expect(text).toContain("Pisces");
// in-sign degree: 338.4° ecliptic 330° (Pisces start) = 8.4°
expect(text).toContain("8.4");
});
// ── T4 ── retrograde planet shows ℞ ──────────────────────────────────────
it("T4: hovering a retrograde planet shows ℞ in the tooltip", () => {
const mercury = svgEl.querySelector("[data-planet='Mercury']");
expect(mercury).not.toBeNull("expected [data-planet='Mercury'] to exist in the SVG");
mercury.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
expect(tooltipEl.style.display).toBe("block");
expect(tooltipEl.textContent).toContain("℞");
});
// ── T5 ── mouseout hides tooltip and removes glow ─────────────────────────
it("T5: mouseout hides the tooltip and removes the glow class", () => {
const sun = svgEl.querySelector("[data-planet='Sun']");
expect(sun).not.toBeNull("expected [data-planet='Sun'] to exist in the SVG");
sun.dispatchEvent(new MouseEvent("mouseover", { bubbles: true }));
expect(tooltipEl.style.display).toBe("block");
// relatedTarget is document.body — outside the planet group
sun.dispatchEvent(new MouseEvent("mouseout", {
bubbles: true,
relatedTarget: document.body,
}));
expect(tooltipEl.style.display).toBe("none");
expect(sun.classList.contains("nw-planet--hover")).toBe(false);
});
});

View File

@@ -22,11 +22,14 @@
<script src="RoleSelectSpec.js"></script>
<script src="TraySpec.js"></script>
<script src="SigSelectSpec.js"></script>
<script src="NatusWheelSpec.js"></script>
<!-- src files -->
<script src="/static/apps/dashboard/dashboard.js"></script>
<script src="/static/apps/epic/role-select.js"></script>
<script src="/static/apps/epic/tray.js"></script>
<script src="/static/apps/epic/sig-select.js"></script>
<script src="/static/apps/gameboard/d3.min.js"></script>
<script src="/static/apps/gameboard/natus-wheel.js"></script>
<!-- Jasmine env config (optional) -->
<script src="lib/jasmine-6.0.1/boot1.js"></script>