From c78ecb61bf4e86fb1cd8170693a95ba357b7eeb5 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Sun, 26 Apr 2026 18:42:47 -0400 Subject: [PATCH] =?UTF-8?q?natus=20wheel:=20unified=20PRV/NXT=20cycle=20me?= =?UTF-8?q?rging=20planets=20&=20angles;=20drop=20degree=20from=20classic?= =?UTF-8?q?=20element=20contribs;=20tt-planet-sym=20larger;=20tt-sign-type?= =?UTF-8?q?=20italic=20=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _chartItems merges _planetItems + _angleItems sorted by degree desc; _stepCycle dispatches to _activatePlanet or _activateAngle via unified list - T15g/h/i: angle↔planet boundary navigation & wrap; T9n/T9w updated for merged cycle - classic element contrib rows: removed @ deg° (pdata/inDeg lookup dropped) - .tt-planet-sym 1.2→1.8rem; .tt-house-of/.tt-house-type 0.6em→0.7rem; .tt-sign-type added alongside .tt-house-type selector, font-style: italic Code architected by Disco DeDisco Git commit message Co-Authored-By: Claude Sonnet 4.6 --- .../static/apps/gameboard/natus-wheel.js | 34 ++++++---- src/static/tests/NatusWheelSpec.js | 66 ++++++++++++++++--- src/static_src/scss/_natus.scss | 10 +-- src/static_src/tests/NatusWheelSpec.js | 66 ++++++++++++++++--- 4 files changed, 141 insertions(+), 35 deletions(-) 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 + `
` + `
Planets
` + @@ -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); + }); });