diff --git a/src/apps/gameboard/static/apps/gameboard/natus-wheel.js b/src/apps/gameboard/static/apps/gameboard/natus-wheel.js
index 5015806..9ec641e 100644
--- a/src/apps/gameboard/static/apps/gameboard/natus-wheel.js
+++ b/src/apps/gameboard/static/apps/gameboard/natus-wheel.js
@@ -159,11 +159,12 @@ const NatusWheel = (() => {
let _activeRing = null; // 'planets' | 'elements' | 'signs' | 'houses' | 'angles' | null
let _activeIdx = null; // index within the active ring's sorted list
- let _planetItems = []; // [{name, degree}] sorted by ecliptic degree ascending
+ let _planetItems = []; // [{name, degree}] sorted by ecliptic degree descending
let _elementItems = []; // [{key}] in ELEMENT_ORDER
let _signItems = []; // [{name, symbol, element}] in SIGNS order
let _houseItems = []; // [{num, label}] houses 1–12
- let _angleItems = []; // [{name, label, house}] — ASC and MC
+ let _angleItems = []; // [{name, deg}] — ASC and MC
+ let _chartItems = []; // [{type, name, deg}] planets+angles merged, degree desc
// Tooltip DOM refs — set by _injectTooltipControls() on each draw().
let _tooltipEl = null;
@@ -321,6 +322,10 @@ const NatusWheel = (() => {
{ name: 'ASC', deg: data.houses.asc },
{ name: 'MC', deg: data.houses.mc },
];
+ _chartItems = [
+ ..._planetItems.map(p => ({ type: 'planet', name: p.name, deg: p.degree })),
+ ..._angleItems.map(a => ({ type: 'angle', name: a.name, deg: a.deg })),
+ ].sort((a, b) => b.deg - a.deg);
}
/** Clear all active-lock classes and reset cycle state. */
@@ -640,9 +645,7 @@ const NatusWheel = (() => {
if (contribs.length) {
bodyHtml += '
';
contribs.forEach(c => {
- const pdata = (_currentData.planets || {})[c.planet] || {};
- const inDeg = pdata.degree !== undefined ? _inSignDeg(pdata.degree).toFixed(1) : '?';
- bodyHtml += `
${_pSym(c.planet)} @ ${inDeg}° ${_signIcon(c.sign)} +1
`;
+ bodyHtml += `
${_pSym(c.planet)} in ${_signIcon(c.sign)} +1
`;
});
bodyHtml += '
';
} else {
@@ -666,7 +669,7 @@ const NatusWheel = (() => {
});
} else {
const psyms = st.planets.map(p => _pSym(p.planet)).join(' ');
- bodyHtml += `${_signIcon(st.sign)} : ${psyms}
`;
+ bodyHtml += `in ${_signIcon(st.sign)} : ${psyms}
`;
}
});
bodyHtml += '';
@@ -697,7 +700,7 @@ const NatusWheel = (() => {
pd.signs.forEach(sign => {
const planets = bySign[sign] || [];
const psyms = planets.map(p => _pSym(p.planet)).join(' ');
- bodyHtml += `${_signIcon(sign)} : ${psyms}
`;
+ bodyHtml += `in ${_signIcon(sign)} : ${psyms}
`;
});
});
bodyHtml += '';
@@ -771,7 +774,7 @@ const NatusWheel = (() => {
`${iconSvg}` +
`` +
`` +
- `${modality} ${sign.element}` +
+ `${modality} ${sign.element}` +
vecImg +
`
` +
`` +
@@ -831,9 +834,18 @@ const NatusWheel = (() => {
/** Advance the active ring by +1 (NXT) or -1 (PRV). */
function _stepCycle(dir) {
- if (_activeRing === 'planets') {
- _activeIdx = (_activeIdx + dir + _planetItems.length) % _planetItems.length;
- _activatePlanet(_activeIdx);
+ if (_activeRing === 'planets' || _activeRing === 'angles') {
+ const currentName = _activeRing === 'planets'
+ ? _planetItems[_activeIdx].name
+ : _activeIdx; // angles use name string as _activeIdx
+ const pos = _chartItems.findIndex(c => c.name === currentName);
+ if (pos === -1) return;
+ const next = _chartItems[(pos + dir + _chartItems.length) % _chartItems.length];
+ if (next.type === 'planet') {
+ _activatePlanet(_planetItems.findIndex(p => p.name === next.name));
+ } else {
+ _activateAngle(next.name);
+ }
} else if (_activeRing === 'elements') {
_activeIdx = (_activeIdx + dir + _elementItems.length) % _elementItems.length;
_activateElement(_activeIdx);
diff --git a/src/static/tests/NatusWheelSpec.js b/src/static/tests/NatusWheelSpec.js
index 259862c..868702a 100644
--- a/src/static/tests/NatusWheelSpec.js
+++ b/src/static/tests/NatusWheelSpec.js
@@ -232,8 +232,10 @@ describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
});
// ── T9n ── PRV cycles counterclockwise (to higher ecliptic degree) ────────
+ // CONJUNCTION_CHART merged sorted desc: ASC(180)→Mars(132)→MC(90)→Sun(66.7)→Venus(63.3)
+ // PRV from Sun (pos 3) → MC (pos 2, 90°) — angles and planets share the cycle.
- it("T9n: clicking PRV from Sun shows Mars (previous planet counterclockwise = higher degree)", () => {
+ it("T9n: clicking PRV from Sun shows MC (next higher ecliptic degree in merged cycle)", () => {
const sun = svgEl2.querySelector("[data-planet='Sun']");
expect(sun).not.toBeNull("expected [data-planet='Sun']");
@@ -242,24 +244,25 @@ describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
const prvBtn = tooltipEl.querySelector(".nw-tt-prv");
prvBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
- expect(tooltipEl.textContent).toContain("Mars");
- const mars = svgEl2.querySelector("[data-planet='Mars']");
- expect(mars.classList.contains("nw-planet--active")).toBe(true);
+ expect(tooltipEl.textContent).toContain("Midheaven");
+ const mc = svgEl2.querySelector("[data-angle='MC']");
+ expect(mc.classList.contains("nw-angle--active")).toBe(true);
+ expect(sun.classList.contains("nw-planet--active")).toBe(false);
});
- // ── T9w ── NXT wraps clockwise from the last (lowest-degree) planet ───────
+ // ── T9w ── NXT wraps clockwise from the lowest-degree item ───────────────
+ // Venus(63.3°) is lowest; NXT wraps to ASC(180°) — the highest-degree item.
- it("T9w: cycling NXT from Venus (lowest degree) wraps clockwise to Mars (highest degree)", () => {
- // Venus is idx 2 (lowest degree = furthest clockwise); NXT wraps to idx 0 = Mars
+ it("T9w: cycling NXT from Venus (lowest degree) wraps clockwise to ASC (highest degree)", () => {
const venus = svgEl2.querySelector("[data-planet='Venus']");
venus.dispatchEvent(new MouseEvent("click", { bubbles: true }));
const nxtBtn = tooltipEl.querySelector(".nw-tt-nxt");
nxtBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
- expect(tooltipEl.textContent).toContain("Mars");
- const mars = svgEl2.querySelector("[data-planet='Mars']");
- expect(mars.classList.contains("nw-planet--active")).toBe(true);
+ expect(tooltipEl.textContent).toContain("Ascendant");
+ const asc = svgEl2.querySelector("[data-angle='ASC']");
+ expect(asc.classList.contains("nw-angle--active")).toBe(true);
});
});
@@ -952,4 +955,47 @@ describe("NatusWheel — angle (ASC/MC) click tooltips", () => {
const bodyHtml = tooltipEl.querySelector(".nw-tt-body").innerHTML;
expect(bodyHtml).toContain("ASC");
});
+
+ // T15g — NXT from a planet steps into an angle when angle is next by degree
+ // ANGLE_CHART sorted descending: Mars(188)→Moon(97)→MC(90)→Sun(8)→ASC(0)
+ // Moon is idx 1; NXT steps to MC (idx 2).
+ it("T15g: clicking NXT from Moon (97°) activates MC (90°, next clockwise)", () => {
+ const moonGroup = svgEl.querySelector("[data-planet='Moon']");
+ expect(moonGroup).not.toBeNull("expected [data-planet='Moon']");
+ moonGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ const nxtBtn = tooltipEl.querySelector(".nw-tt-nxt");
+ nxtBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ expect(tooltipEl.textContent).toContain("Midheaven");
+ const mcGroup = svgEl.querySelector("[data-angle='MC']");
+ expect(mcGroup.classList.contains("nw-angle--active")).toBe(true);
+ expect(moonGroup.classList.contains("nw-planet--active")).toBe(false);
+ });
+
+ // T15h — PRV from an angle steps back into a planet
+ it("T15h: clicking PRV from MC (90°) activates Moon (97°, previous counterclockwise)", () => {
+ const mcGroup = svgEl.querySelector("[data-angle='MC']");
+ mcGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ const prvBtn = tooltipEl.querySelector(".nw-tt-prv");
+ prvBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ const moonGroup = svgEl.querySelector("[data-planet='Moon']");
+ expect(moonGroup.classList.contains("nw-planet--active")).toBe(true);
+ expect(mcGroup.classList.contains("nw-angle--active")).toBe(false);
+ });
+
+ // T15i — NXT from ASC (lowest degree, 0°) wraps to Mars (highest degree, 188°)
+ it("T15i: NXT from ASC (0°, lowest) wraps clockwise to Mars (188°, highest)", () => {
+ const ascGroup = svgEl.querySelector("[data-angle='ASC']");
+ ascGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ const nxtBtn = tooltipEl.querySelector(".nw-tt-nxt");
+ nxtBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ expect(tooltipEl.textContent).toContain("Mars");
+ const marsGroup = svgEl.querySelector("[data-planet='Mars']");
+ expect(marsGroup.classList.contains("nw-planet--active")).toBe(true);
+ });
});
diff --git a/src/static_src/scss/_natus.scss b/src/static_src/scss/_natus.scss
index 65c4900..b527443 100644
--- a/src/static_src/scss/_natus.scss
+++ b/src/static_src/scss/_natus.scss
@@ -535,7 +535,7 @@ body[class*="-light"] {
margin-bottom: 0.2rem;
}
.tt-planet-sym {
- font-size: 1.2rem;
+ font-size: 1.8rem;
opacity: 0.85;
}
.tt-angle-sym {
@@ -608,7 +608,7 @@ body[class*="-light"] {
margin-bottom: 0.3rem;
}
.tt-house-of {
- font-size: 0.6em;
+ font-size: 0.7rem;
font-weight: 700;
margin-right: 0.15em;
opacity: 0.9;
@@ -619,12 +619,14 @@ body[class*="-light"] {
opacity: 1;
flex-shrink: 0;
}
- .tt-house-type {
+ .tt-house-type,
+ .tt-sign-type {
display: block;
- font-size: 0.6em;
+ font-size: 0.7rem;
font-weight: 400;
opacity: 0.7;
margin-top: 0.1em;
+ font-style: italic;
}
.tt-house-planets {
display: flex;
diff --git a/src/static_src/tests/NatusWheelSpec.js b/src/static_src/tests/NatusWheelSpec.js
index 259862c..868702a 100644
--- a/src/static_src/tests/NatusWheelSpec.js
+++ b/src/static_src/tests/NatusWheelSpec.js
@@ -232,8 +232,10 @@ describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
});
// ── T9n ── PRV cycles counterclockwise (to higher ecliptic degree) ────────
+ // CONJUNCTION_CHART merged sorted desc: ASC(180)→Mars(132)→MC(90)→Sun(66.7)→Venus(63.3)
+ // PRV from Sun (pos 3) → MC (pos 2, 90°) — angles and planets share the cycle.
- it("T9n: clicking PRV from Sun shows Mars (previous planet counterclockwise = higher degree)", () => {
+ it("T9n: clicking PRV from Sun shows MC (next higher ecliptic degree in merged cycle)", () => {
const sun = svgEl2.querySelector("[data-planet='Sun']");
expect(sun).not.toBeNull("expected [data-planet='Sun']");
@@ -242,24 +244,25 @@ describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
const prvBtn = tooltipEl.querySelector(".nw-tt-prv");
prvBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
- expect(tooltipEl.textContent).toContain("Mars");
- const mars = svgEl2.querySelector("[data-planet='Mars']");
- expect(mars.classList.contains("nw-planet--active")).toBe(true);
+ expect(tooltipEl.textContent).toContain("Midheaven");
+ const mc = svgEl2.querySelector("[data-angle='MC']");
+ expect(mc.classList.contains("nw-angle--active")).toBe(true);
+ expect(sun.classList.contains("nw-planet--active")).toBe(false);
});
- // ── T9w ── NXT wraps clockwise from the last (lowest-degree) planet ───────
+ // ── T9w ── NXT wraps clockwise from the lowest-degree item ───────────────
+ // Venus(63.3°) is lowest; NXT wraps to ASC(180°) — the highest-degree item.
- it("T9w: cycling NXT from Venus (lowest degree) wraps clockwise to Mars (highest degree)", () => {
- // Venus is idx 2 (lowest degree = furthest clockwise); NXT wraps to idx 0 = Mars
+ it("T9w: cycling NXT from Venus (lowest degree) wraps clockwise to ASC (highest degree)", () => {
const venus = svgEl2.querySelector("[data-planet='Venus']");
venus.dispatchEvent(new MouseEvent("click", { bubbles: true }));
const nxtBtn = tooltipEl.querySelector(".nw-tt-nxt");
nxtBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
- expect(tooltipEl.textContent).toContain("Mars");
- const mars = svgEl2.querySelector("[data-planet='Mars']");
- expect(mars.classList.contains("nw-planet--active")).toBe(true);
+ expect(tooltipEl.textContent).toContain("Ascendant");
+ const asc = svgEl2.querySelector("[data-angle='ASC']");
+ expect(asc.classList.contains("nw-angle--active")).toBe(true);
});
});
@@ -952,4 +955,47 @@ describe("NatusWheel — angle (ASC/MC) click tooltips", () => {
const bodyHtml = tooltipEl.querySelector(".nw-tt-body").innerHTML;
expect(bodyHtml).toContain("ASC");
});
+
+ // T15g — NXT from a planet steps into an angle when angle is next by degree
+ // ANGLE_CHART sorted descending: Mars(188)→Moon(97)→MC(90)→Sun(8)→ASC(0)
+ // Moon is idx 1; NXT steps to MC (idx 2).
+ it("T15g: clicking NXT from Moon (97°) activates MC (90°, next clockwise)", () => {
+ const moonGroup = svgEl.querySelector("[data-planet='Moon']");
+ expect(moonGroup).not.toBeNull("expected [data-planet='Moon']");
+ moonGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ const nxtBtn = tooltipEl.querySelector(".nw-tt-nxt");
+ nxtBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ expect(tooltipEl.textContent).toContain("Midheaven");
+ const mcGroup = svgEl.querySelector("[data-angle='MC']");
+ expect(mcGroup.classList.contains("nw-angle--active")).toBe(true);
+ expect(moonGroup.classList.contains("nw-planet--active")).toBe(false);
+ });
+
+ // T15h — PRV from an angle steps back into a planet
+ it("T15h: clicking PRV from MC (90°) activates Moon (97°, previous counterclockwise)", () => {
+ const mcGroup = svgEl.querySelector("[data-angle='MC']");
+ mcGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ const prvBtn = tooltipEl.querySelector(".nw-tt-prv");
+ prvBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ const moonGroup = svgEl.querySelector("[data-planet='Moon']");
+ expect(moonGroup.classList.contains("nw-planet--active")).toBe(true);
+ expect(mcGroup.classList.contains("nw-angle--active")).toBe(false);
+ });
+
+ // T15i — NXT from ASC (lowest degree, 0°) wraps to Mars (highest degree, 188°)
+ it("T15i: NXT from ASC (0°, lowest) wraps clockwise to Mars (188°, highest)", () => {
+ const ascGroup = svgEl.querySelector("[data-angle='ASC']");
+ ascGroup.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ const nxtBtn = tooltipEl.querySelector(".nw-tt-nxt");
+ nxtBtn.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+
+ expect(tooltipEl.textContent).toContain("Mars");
+ const marsGroup = svgEl.querySelector("[data-planet='Mars']");
+ expect(marsGroup.classList.contains("nw-planet--active")).toBe(true);
+ });
});