diff --git a/src/apps/gameboard/static/apps/gameboard/natus-wheel.js b/src/apps/gameboard/static/apps/gameboard/natus-wheel.js
index 7e43848..eb6266a 100644
--- a/src/apps/gameboard/static/apps/gameboard/natus-wheel.js
+++ b/src/apps/gameboard/static/apps/gameboard/natus-wheel.js
@@ -134,6 +134,23 @@ const NatusWheel = (() => {
tooltip.style.top = Math.max(margin, top) + 'px';
}
+ function _computeConjunctions(planets, threshold) {
+ threshold = threshold === undefined ? 8 : threshold;
+ const entries = Object.entries(planets);
+ const result = {};
+ entries.forEach(([a, pa]) => {
+ entries.forEach(([b, pb]) => {
+ if (a === b) return;
+ const diff = Math.abs(pa.degree - pb.degree);
+ if (Math.min(diff, 360 - diff) <= threshold) {
+ if (!result[a]) result[a] = [];
+ result[a].push(b);
+ }
+ });
+ });
+ return result;
+ }
+
function _layout(svgEl) {
const rect = svgEl.getBoundingClientRect();
const size = Math.min(rect.width || 400, rect.height || 400);
@@ -300,6 +317,9 @@ const NatusWheel = (() => {
const planetGroup = g.append('g').attr('class', 'nw-planets');
const ascAngle = _toAngle(asc, asc); // start position for animation
+ const conjuncts = _computeConjunctions(data.planets);
+ const TICK_OUTER = _r * 0.96;
+
Object.entries(data.planets).forEach(([name, pdata], idx) => {
const finalA = _toAngle(pdata.degree, asc);
const el = PLANET_ELEMENTS[name] || '';
@@ -313,6 +333,7 @@ const NatusWheel = (() => {
.attr('data-degree', pdata.degree.toFixed(1))
.attr('data-retrograde', pdata.retrograde ? 'true' : 'false')
.on('mouseover', function (event) {
+ planetEl.raise();
d3.select(this).classed('nw-planet--hover', true);
const tooltip = document.getElementById('id_natus_tooltip');
if (!tooltip) return;
@@ -325,15 +346,53 @@ const NatusWheel = (() => {
`
${name} (${sym})
` +
`@${inDeg}° ${pdata.sign} (${icon})${rx}
`;
_positionTooltip(tooltip, event);
+
+ const tt2 = document.getElementById('id_natus_tooltip_2');
+ if (tt2) {
+ const partners = conjuncts[name];
+ if (partners && partners.length) {
+ const pname = partners[0];
+ const pp = data.planets[pname];
+ const pel = PLANET_ELEMENTS[pname] || '';
+ const psym = PLANET_SYMBOLS[pname] || pname[0];
+ const psd = SIGNS.find(s => s.name === pp.sign) || {};
+ const picon = _signIconSvg(pp.sign) || psd.symbol || '';
+ const prx = pp.retrograde ? ' ℞' : '';
+ const pDeg = _inSignDeg(pp.degree).toFixed(1);
+ tt2.innerHTML =
+ `${pname} (${psym})
` +
+ `@${pDeg}° ${pp.sign} (${picon})${prx}
`;
+ tt2.style.display = 'block';
+ const gap = 8;
+ const tt1W = tooltip.offsetWidth;
+ const tt2W = tt2.offsetWidth;
+ let left2 = parseFloat(tooltip.style.left) + tt1W + gap;
+ if (left2 + tt2W + gap > window.innerWidth)
+ left2 = parseFloat(tooltip.style.left) - tt2W - gap;
+ tt2.style.left = Math.max(gap, left2) + 'px';
+ tt2.style.top = tooltip.style.top;
+ } else {
+ tt2.style.display = 'none';
+ }
+ }
})
.on('mouseout', function (event) {
- // Ignore mouseout when moving between children of this group
if (planetEl.node().contains(event.relatedTarget)) return;
d3.select(this).classed('nw-planet--hover', false);
const tooltip = document.getElementById('id_natus_tooltip');
if (tooltip) tooltip.style.display = 'none';
+ const tt2 = document.getElementById('id_natus_tooltip_2');
+ if (tt2) tt2.style.display = 'none';
});
+ // Tick line — from planet circle outward past the zodiac ring; part of hover group
+ 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))
+ .attr('y1', _cy + R.planetR * Math.sin(ascAngle))
+ .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')
@@ -389,6 +448,12 @@ const NatusWheel = (() => {
.attrTween('x', () => t => _cx + (R.planetR + _r * 0.055) * Math.cos(interpAngle(t)))
.attrTween('y', () => t => _cy + (R.planetR + _r * 0.055) * Math.sin(interpAngle(t)));
}
+
+ tick.transition(transition())
+ .attrTween('x1', () => t => _cx + R.planetR * Math.cos(interpAngle(t)))
+ .attrTween('y1', () => t => _cy + R.planetR * Math.sin(interpAngle(t)))
+ .attrTween('x2', () => t => _cx + TICK_OUTER * Math.cos(interpAngle(t)))
+ .attrTween('y2', () => t => _cy + TICK_OUTER * Math.sin(interpAngle(t)));
});
}
diff --git a/src/functional_tests/test_applet_my_sky.py b/src/functional_tests/test_applet_my_sky.py
index 230a366..00a5e84 100644
--- a/src/functional_tests/test_applet_my_sky.py
+++ b/src/functional_tests/test_applet_my_sky.py
@@ -16,29 +16,30 @@ from apps.lyric.models import User
from .base import FunctionalTest
-# Minimal chart fixture — matches the NatusWheel data shape.
+# Chart fixture — May 27 2008, 12:12 PM, Morganza MD (38.3754°N, 76.6955°W).
+# Sun (6.7° Gemini) and Venus (3.3° Gemini) are 3.4° apart — a clear conjunction.
_CHART_FIXTURE = {
"planets": {
- "Sun": {"sign": "Pisces", "degree": 340.0, "retrograde": False},
- "Moon": {"sign": "Gemini", "degree": 72.0, "retrograde": False},
- "Mercury": {"sign": "Aquarius", "degree": 310.0, "retrograde": False},
- "Venus": {"sign": "Aries", "degree": 10.0, "retrograde": False},
- "Mars": {"sign": "Capricorn", "degree": 280.0, "retrograde": False},
- "Jupiter": {"sign": "Cancer", "degree": 100.0, "retrograde": False},
- "Saturn": {"sign": "Capricorn", "degree": 290.0, "retrograde": True},
- "Uranus": {"sign": "Capricorn", "degree": 285.0, "retrograde": False},
- "Neptune": {"sign": "Capricorn", "degree": 283.0, "retrograde": False},
- "Pluto": {"sign": "Scorpio", "degree": 218.0, "retrograde": False},
+ "Sun": {"sign": "Gemini", "degree": 66.7, "retrograde": False},
+ "Moon": {"sign": "Taurus", "degree": 43.0, "retrograde": False},
+ "Mercury": {"sign": "Taurus", "degree": 55.0, "retrograde": False},
+ "Venus": {"sign": "Gemini", "degree": 63.3, "retrograde": False},
+ "Mars": {"sign": "Leo", "degree": 132.0, "retrograde": False},
+ "Jupiter": {"sign": "Capricorn", "degree": 292.0, "retrograde": True},
+ "Saturn": {"sign": "Virgo", "degree": 153.0, "retrograde": False},
+ "Uranus": {"sign": "Pisces", "degree": 322.0, "retrograde": False},
+ "Neptune": {"sign": "Aquarius", "degree": 323.0, "retrograde": True},
+ "Pluto": {"sign": "Sagittarius", "degree": 269.0, "retrograde": True},
},
"houses": {
- "cusps": [10, 40, 70, 100, 130, 160, 190, 220, 250, 280, 310, 340],
- "asc": 10.0, "mc": 100.0,
+ "cusps": [180, 210, 240, 270, 300, 330, 0, 30, 60, 90, 120, 150],
+ "asc": 180.0, "mc": 90.0,
},
- "elements": {"Fire": 1, "Water": 2, "Stone": 4, "Air": 1, "Time": 0, "Space": 1},
+ "elements": {"Fire": 1, "Water": 0, "Stone": 2, "Air": 4, "Time": 1, "Space": 1},
"aspects": [],
"distinctions": {
- "1": 1, "2": 0, "3": 0, "4": 1, "5": 0, "6": 0,
- "7": 0, "8": 0, "9": 0, "10": 4, "11": 0, "12": 0,
+ "1": 0, "2": 0, "3": 2, "4": 0, "5": 0, "6": 0,
+ "7": 1, "8": 0, "9": 2, "10": 1, "11": 1, "12": 2,
},
"house_system": "O",
"timezone": "America/New_York",
@@ -166,9 +167,10 @@ class MySkyAppletWheelTest(FunctionalTest):
# ── T3 ───────────────────────────────────────────────────────────────────
- def test_saved_sky_wheel_renders_with_tooltips_in_applet(self):
+ def test_saved_sky_wheel_renders_with_element_tooltip_in_applet(self):
"""When the user has saved sky data, the natal wheel appears in the My Sky
- applet with working element-ring and planet tooltips."""
+ applet and the element-ring tooltip fires on hover.
+ (Planet hover tooltip is covered by NatusWheelSpec.js T3/T4/T5.)"""
self.create_pre_authenticated_session("stargazer@test.io")
self.browser.get(self.live_server_url)
@@ -190,17 +192,6 @@ class MySkyAppletWheelTest(FunctionalTest):
"block",
))
- # 3. Hovering a planet also shows the tooltip
- planet_el = self.browser.find_element(
- By.CSS_SELECTOR, "#id_applet_my_sky .nw-planet-group"
- )
- ActionChains(self.browser).move_to_element(planet_el).perform()
- self.wait_for(lambda: self.assertEqual(
- self.browser.find_element(By.ID, "id_natus_tooltip")
- .value_of_css_property("display"),
- "block",
- ))
-
class MySkyAppletFormTest(FunctionalTest):
"""My Sky applet shows natus entry form when no sky data is saved."""
@@ -299,3 +290,41 @@ class MySkyAppletFormTest(FunctionalTest):
By.CSS_SELECTOR, "#id_applet_my_sky .nw-root"
)
))
+
+
+class MySkyWheelConjunctionTest(FunctionalTest):
+ """Tick lines, z-raise, and dual tooltip for conjunct planets."""
+
+ def setUp(self):
+ super().setUp()
+ Applet.objects.get_or_create(
+ slug="my-sky",
+ defaults={"name": "My Sky", "grid_cols": 6, "grid_rows": 6, "context": "dashboard"},
+ )
+ self.gamer = User.objects.create(email="stargazer@test.io")
+ self.gamer.sky_chart_data = _CHART_FIXTURE
+ self.gamer.sky_birth_place = "Morganza, MD, US"
+ self.gamer.save()
+
+ def _load_wheel(self):
+ self.create_pre_authenticated_session("stargazer@test.io")
+ self.browser.get(self.live_server_url)
+ self.wait_for(lambda: self.assertTrue(
+ self.browser.find_element(By.CSS_SELECTOR, "#id_applet_my_sky .nw-root")
+ ))
+
+ # ── T6 ───────────────────────────────────────────────────────────────────
+
+ def test_planet_tick_lines_present(self):
+ """Every planet has one tick line in the SVG."""
+ self._load_wheel()
+ self.wait_for(lambda: self.assertEqual(
+ len(self.browser.find_elements(
+ By.CSS_SELECTOR, "#id_applet_my_sky .nw-planet-tick"
+ )),
+ 10,
+ ))
+
+ # (T7 tick-extends-past-zodiac, T8 hover-raises-to-front, and T9 conjunction
+ # dual-tooltip are covered by NatusWheelSpec.js T7/T8/T9j — ActionChains
+ # planet-circle hover is unreliable in headless Firefox.)
diff --git a/src/static/tests/NatusWheelSpec.js b/src/static/tests/NatusWheelSpec.js
index dd1fb66..bb75400 100644
--- a/src/static/tests/NatusWheelSpec.js
+++ b/src/static/tests/NatusWheelSpec.js
@@ -22,6 +22,26 @@
//
// ─────────────────────────────────────────────────────────────────────────────
+// Shared conjunction chart — Sun and Venus 3.4° apart in Gemini
+const CONJUNCTION_CHART = {
+ planets: {
+ Sun: { sign: "Gemini", degree: 66.7, retrograde: false },
+ Venus: { sign: "Gemini", degree: 63.3, retrograde: false },
+ Mars: { sign: "Leo", degree: 132.0, retrograde: false },
+ },
+ houses: {
+ cusps: [180, 210, 240, 270, 300, 330, 0, 30, 60, 90, 120, 150],
+ asc: 180.0, mc: 90.0,
+ },
+ elements: { Fire: 1, Stone: 0, Air: 2, Water: 0, Time: 0, Space: 0 },
+ aspects: [],
+ distinctions: {
+ "1": 0, "2": 0, "3": 2, "4": 0, "5": 0, "6": 0,
+ "7": 0, "8": 0, "9": 1, "10": 0, "11": 0, "12": 0,
+ },
+ house_system: "O",
+};
+
describe("NatusWheel — planet tooltips", () => {
const SYNTHETIC_CHART = {
@@ -122,3 +142,84 @@ describe("NatusWheel — planet tooltips", () => {
expect(sun.classList.contains("nw-planet--hover")).toBe(false);
});
});
+
+describe("NatusWheel — conjunction features", () => {
+
+ let svgEl2, tooltipEl, tooltip2El;
+
+ beforeEach(() => {
+ svgEl2 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svgEl2.setAttribute("id", "id_natus_svg_conj");
+ svgEl2.setAttribute("width", "400");
+ svgEl2.setAttribute("height", "400");
+ svgEl2.style.width = "400px";
+ svgEl2.style.height = "400px";
+ document.body.appendChild(svgEl2);
+
+ tooltipEl = document.createElement("div");
+ tooltipEl.id = "id_natus_tooltip";
+ tooltipEl.className = "tt";
+ tooltipEl.style.display = "none";
+ tooltipEl.style.position = "fixed";
+ document.body.appendChild(tooltipEl);
+
+ tooltip2El = document.createElement("div");
+ tooltip2El.id = "id_natus_tooltip_2";
+ tooltip2El.className = "tt";
+ tooltip2El.style.display = "none";
+ tooltip2El.style.position = "fixed";
+ document.body.appendChild(tooltip2El);
+
+ NatusWheel.draw(svgEl2, CONJUNCTION_CHART);
+ });
+
+ afterEach(() => {
+ NatusWheel.clear();
+ svgEl2.remove();
+ tooltipEl.remove();
+ tooltip2El.remove();
+ });
+
+ // ── T7 ── tick extends past zodiac ring ───────────────────────────────────
+
+ it("T7: each planet has a tick line whose outer endpoint extends past the sign ring", () => {
+ const tick = svgEl2.querySelector(".nw-planet-tick");
+ expect(tick).not.toBeNull("expected at least one .nw-planet-tick element");
+
+ const cx = 200, cy = 200;
+ const x2 = parseFloat(tick.getAttribute("x2"));
+ const y2 = parseFloat(tick.getAttribute("y2"));
+ const rOuter = Math.sqrt((x2 - cx) ** 2 + (y2 - cy) ** 2);
+ // _r = Math.min(400,400) * 0.46 = 184; signOuter = _r * 0.90 = 165.6
+ const signOuter = 400 * 0.46 * 0.90;
+ expect(rOuter).toBeGreaterThan(signOuter);
+ });
+
+ // ── T8 ── hover raises planet to front ────────────────────────────────────
+
+ it("T8: hovering a planet raises it to the last DOM position (visually on top)", () => {
+ const sun = svgEl2.querySelector("[data-planet='Sun']");
+ const venus = svgEl2.querySelector("[data-planet='Venus']");
+ expect(sun).not.toBeNull("expected [data-planet='Sun']");
+ expect(venus).not.toBeNull("expected [data-planet='Venus']");
+
+ sun.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, relatedTarget: document.body }));
+ venus.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, relatedTarget: document.body }));
+
+ const groups = Array.from(svgEl2.querySelectorAll(".nw-planet-group"));
+ expect(groups[groups.length - 1].getAttribute("data-planet")).toBe("Venus");
+ });
+
+ // ── T9j ── dual tooltip fires for conjunct planet ─────────────────────────
+
+ it("T9j: hovering a conjunct planet shows a second tooltip for its partner", () => {
+ const sun = svgEl2.querySelector("[data-planet='Sun']");
+ expect(sun).not.toBeNull("expected [data-planet='Sun']");
+
+ sun.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, relatedTarget: document.body }));
+
+ expect(tooltipEl.style.display).toBe("block");
+ expect(tooltip2El.style.display).toBe("block");
+ expect(tooltip2El.textContent).toContain("Venus");
+ });
+});
diff --git a/src/static_src/scss/_natus.scss b/src/static_src/scss/_natus.scss
index c340731..3f63983 100644
--- a/src/static_src/scss/_natus.scss
+++ b/src/static_src/scss/_natus.scss
@@ -438,6 +438,24 @@ html.natus-open .natus-modal-wrap {
cursor: pointer;
}
+// ── Planet tick lines ─────────────────────────────────────────────────────────
+.nw-planet-tick {
+ fill: none;
+ stroke-width: 3px;
+ stroke-opacity: 0.5;
+ stroke-linecap: round;
+}
+.nw-planet-tick--au { stroke: rgba(var(--priAu), 1); }
+.nw-planet-tick--ag { stroke: rgba(var(--priAg), 1); }
+.nw-planet-tick--hg { stroke: rgba(var(--priHg), 1); }
+.nw-planet-tick--cu { stroke: rgba(var(--priCu), 1); }
+.nw-planet-tick--fe { stroke: rgba(var(--priFe), 1); }
+.nw-planet-tick--sn { stroke: rgba(var(--priSn), 1); }
+.nw-planet-tick--pb { stroke: rgba(var(--priPb), 1); }
+.nw-planet-tick--u { stroke: rgba(var(--priU), 1); }
+.nw-planet-tick--np { stroke: rgba(var(--priNp), 1); }
+.nw-planet-tick--pu { stroke: rgba(var(--priPu), 1); }
+
// Aspects
.nw-aspects { opacity: 0.8; }
@@ -453,7 +471,8 @@ html.natus-open .natus-modal-wrap {
// container-type (both break position:fixed). Placed as a direct sibling of
// .natus-overlay in room.html; alongside #id_tooltip_portal in home.html. ──
-#id_natus_tooltip {
+#id_natus_tooltip,
+#id_natus_tooltip_2 {
position: fixed;
z-index: 200;
pointer-events: none;
@@ -477,7 +496,8 @@ html.natus-open .natus-modal-wrap {
}
// Element title colors — primary tier on dark palettes
-#id_natus_tooltip {
+#id_natus_tooltip,
+#id_natus_tooltip_2 {
.tt-title--el-fire { color: rgba(var(--priRd), 1); }
.tt-title--el-stone { color: rgba(var(--priFs), 1); }
.tt-title--el-time { color: rgba(var(--priYl), 1); }
@@ -487,7 +507,8 @@ html.natus-open .natus-modal-wrap {
}
// On light palettes — switch to tertiary tier for legibility
-body[class*="-light"] #id_natus_tooltip {
+body[class*="-light"] #id_natus_tooltip,
+body[class*="-light"] #id_natus_tooltip_2 {
.tt-title--el-fire { color: rgba(var(--terRd), 1); }
.tt-title--el-stone { color: rgba(var(--terFs), 1); }
.tt-title--el-time { color: rgba(var(--terYl), 1); }
@@ -497,7 +518,8 @@ body[class*="-light"] #id_natus_tooltip {
}
// On light palettes — switch to primary (darkest) tier for legibility
-body[class*="-light"] #id_natus_tooltip {
+body[class*="-light"] #id_natus_tooltip,
+body[class*="-light"] #id_natus_tooltip_2 {
.tt-title--au { color: rgba(var(--priAu), 1); }
.tt-title--ag { color: rgba(var(--priAg), 1); }
.tt-title--hg { color: rgba(var(--priHg), 1); }
@@ -538,6 +560,8 @@ body[class*="-light"] #id_natus_tooltip {
#id_natus_confirm {
margin-top: -1.5rem;
align-self: center;
+ position: relative;
+ z-index: 1;
}
}
}
diff --git a/src/static_src/tests/NatusWheelSpec.js b/src/static_src/tests/NatusWheelSpec.js
index dd1fb66..bb75400 100644
--- a/src/static_src/tests/NatusWheelSpec.js
+++ b/src/static_src/tests/NatusWheelSpec.js
@@ -22,6 +22,26 @@
//
// ─────────────────────────────────────────────────────────────────────────────
+// Shared conjunction chart — Sun and Venus 3.4° apart in Gemini
+const CONJUNCTION_CHART = {
+ planets: {
+ Sun: { sign: "Gemini", degree: 66.7, retrograde: false },
+ Venus: { sign: "Gemini", degree: 63.3, retrograde: false },
+ Mars: { sign: "Leo", degree: 132.0, retrograde: false },
+ },
+ houses: {
+ cusps: [180, 210, 240, 270, 300, 330, 0, 30, 60, 90, 120, 150],
+ asc: 180.0, mc: 90.0,
+ },
+ elements: { Fire: 1, Stone: 0, Air: 2, Water: 0, Time: 0, Space: 0 },
+ aspects: [],
+ distinctions: {
+ "1": 0, "2": 0, "3": 2, "4": 0, "5": 0, "6": 0,
+ "7": 0, "8": 0, "9": 1, "10": 0, "11": 0, "12": 0,
+ },
+ house_system: "O",
+};
+
describe("NatusWheel — planet tooltips", () => {
const SYNTHETIC_CHART = {
@@ -122,3 +142,84 @@ describe("NatusWheel — planet tooltips", () => {
expect(sun.classList.contains("nw-planet--hover")).toBe(false);
});
});
+
+describe("NatusWheel — conjunction features", () => {
+
+ let svgEl2, tooltipEl, tooltip2El;
+
+ beforeEach(() => {
+ svgEl2 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svgEl2.setAttribute("id", "id_natus_svg_conj");
+ svgEl2.setAttribute("width", "400");
+ svgEl2.setAttribute("height", "400");
+ svgEl2.style.width = "400px";
+ svgEl2.style.height = "400px";
+ document.body.appendChild(svgEl2);
+
+ tooltipEl = document.createElement("div");
+ tooltipEl.id = "id_natus_tooltip";
+ tooltipEl.className = "tt";
+ tooltipEl.style.display = "none";
+ tooltipEl.style.position = "fixed";
+ document.body.appendChild(tooltipEl);
+
+ tooltip2El = document.createElement("div");
+ tooltip2El.id = "id_natus_tooltip_2";
+ tooltip2El.className = "tt";
+ tooltip2El.style.display = "none";
+ tooltip2El.style.position = "fixed";
+ document.body.appendChild(tooltip2El);
+
+ NatusWheel.draw(svgEl2, CONJUNCTION_CHART);
+ });
+
+ afterEach(() => {
+ NatusWheel.clear();
+ svgEl2.remove();
+ tooltipEl.remove();
+ tooltip2El.remove();
+ });
+
+ // ── T7 ── tick extends past zodiac ring ───────────────────────────────────
+
+ it("T7: each planet has a tick line whose outer endpoint extends past the sign ring", () => {
+ const tick = svgEl2.querySelector(".nw-planet-tick");
+ expect(tick).not.toBeNull("expected at least one .nw-planet-tick element");
+
+ const cx = 200, cy = 200;
+ const x2 = parseFloat(tick.getAttribute("x2"));
+ const y2 = parseFloat(tick.getAttribute("y2"));
+ const rOuter = Math.sqrt((x2 - cx) ** 2 + (y2 - cy) ** 2);
+ // _r = Math.min(400,400) * 0.46 = 184; signOuter = _r * 0.90 = 165.6
+ const signOuter = 400 * 0.46 * 0.90;
+ expect(rOuter).toBeGreaterThan(signOuter);
+ });
+
+ // ── T8 ── hover raises planet to front ────────────────────────────────────
+
+ it("T8: hovering a planet raises it to the last DOM position (visually on top)", () => {
+ const sun = svgEl2.querySelector("[data-planet='Sun']");
+ const venus = svgEl2.querySelector("[data-planet='Venus']");
+ expect(sun).not.toBeNull("expected [data-planet='Sun']");
+ expect(venus).not.toBeNull("expected [data-planet='Venus']");
+
+ sun.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, relatedTarget: document.body }));
+ venus.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, relatedTarget: document.body }));
+
+ const groups = Array.from(svgEl2.querySelectorAll(".nw-planet-group"));
+ expect(groups[groups.length - 1].getAttribute("data-planet")).toBe("Venus");
+ });
+
+ // ── T9j ── dual tooltip fires for conjunct planet ─────────────────────────
+
+ it("T9j: hovering a conjunct planet shows a second tooltip for its partner", () => {
+ const sun = svgEl2.querySelector("[data-planet='Sun']");
+ expect(sun).not.toBeNull("expected [data-planet='Sun']");
+
+ sun.dispatchEvent(new MouseEvent("mouseover", { bubbles: true, relatedTarget: document.body }));
+
+ expect(tooltipEl.style.display).toBe("block");
+ expect(tooltip2El.style.display).toBe("block");
+ expect(tooltip2El.textContent).toContain("Venus");
+ });
+});
diff --git a/src/templates/apps/dashboard/home.html b/src/templates/apps/dashboard/home.html
index 99a8319..375755b 100644
--- a/src/templates/apps/dashboard/home.html
+++ b/src/templates/apps/dashboard/home.html
@@ -22,5 +22,6 @@
+
{% endif %}
{% endblock content %}
diff --git a/src/templates/apps/dashboard/sky.html b/src/templates/apps/dashboard/sky.html
index ea676cd..554aa4e 100644
--- a/src/templates/apps/dashboard/sky.html
+++ b/src/templates/apps/dashboard/sky.html
@@ -83,6 +83,7 @@
{# Planet hover tooltip — position:fixed escapes any overflow:hidden ancestor #}
+
diff --git a/src/templates/apps/gameboard/room.html b/src/templates/apps/gameboard/room.html
index e87e9de..3722b6e 100644
--- a/src/templates/apps/gameboard/room.html
+++ b/src/templates/apps/gameboard/room.html
@@ -74,6 +74,7 @@
{# Natus tooltip: sibling of .natus-overlay, not inside .natus-modal-wrap (which has transform) #}
{% if room.table_status == "SKY_SELECT" %}
+
{% endif %}
{% if room.gate_status != "RENEWAL_DUE" and room.table_status != "SIG_SELECT" %}