rename natus → sky across the codebase — natal chart abstraction is now sky throughout, since chart inputs aren't birthday-gated
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed

Mechanical rename: 5 files (sky-wheel.js, _sky.scss, _sky_overlay.html, SkyWheelSpec.js x2), 24 in-place edits across templates/views/urls/SCSS/JS/tests/CLAUDE.md. URL names epic:natus_save → epic:sky_save (epic namespaced, no clash w. dashboard:sky_save), JS module NatusWheel → SkyWheel, DOM ids id_natus_* → id_sky_*, BEM classes natus-* → sky-*, dashboard sky_natus_data/sky_natus_preview collapsed to sky_data/sky_preview_data. No DB migration needed (User.sky_chart_data + GameEvent.SKY_SAVED already used sky-prefix). 778 ITs + Jasmine green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-04 20:36:15 -04:00
parent 19b7828ea9
commit cc2a3f3526
29 changed files with 338 additions and 338 deletions

View File

@@ -110,7 +110,7 @@ Drop → RESERVED → confirm/reject. `_gate_context()` builds slot state; `_exp
`epic:room` view at `/gameboard/room/<uuid>/`. `gatekeeper` redirects there when `table_status` is set. Error redirects in `select_role`/`select_sig` use `epic:room` if `table_status` is set, else `epic:gatekeeper`.
## SCSS Import Order
`core.scss`: `rootvars → applets → base → button-pad → dashboard → gameboard → palette-picker → room → card-deck → natus → tray → billboard → tooltips → game-kit → wallet-tokens`
`core.scss`: `rootvars → applets → base → button-pad → dashboard → gameboard → palette-picker → room → card-deck → sky → tray → billboard → tooltips → game-kit → wallet-tokens`
## Critical Gotchas

View File

@@ -619,13 +619,13 @@ class SkySaveViewTest(TestCase):
self.assertAlmostEqual(self.user.sky_chart_data["houses"]["asc"], 123.4)
class SkyNatusDataViewTest(TestCase):
class SkyDataViewTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="disco@test.io")
self.client.force_login(self.user)
def test_returns_stored_chart_with_asc_preserved(self):
"""sky_natus_data returns sky_chart_data — asc must match what was saved."""
"""sky_data returns sky_chart_data — asc must match what was saved."""
stored = {
"planets": {},
"houses": {"cusps": [float(i * 30) for i in range(12)], "asc": 236.1, "mc": 159.1},

View File

@@ -17,6 +17,6 @@ urlpatterns = [
path('sky/', views.sky_view, name='sky'),
path('sky/preview', views.sky_preview, name='sky_preview'),
path('sky/save', views.sky_save, name='sky_save'),
path('sky/data', views.sky_natus_data, name='sky_natus_data'),
path('sky/data', views.sky_data, name='sky_data'),
path('set-pronouns', views.set_pronouns, name='set_pronouns'),
]

View File

@@ -287,7 +287,7 @@ def save_payment_method(request):
# ── My Sky (personal natal chart) ────────────────────────────────────────────
def _sky_natus_preview(request):
def _sky_preview_data(request):
"""Shared preview logic — proxies to PySwiss, no DB writes."""
date_str = request.GET.get('date')
time_str = request.GET.get('time', '12:00')
@@ -380,7 +380,7 @@ def sky_view(request):
@login_required(login_url="/")
def sky_preview(request):
return _sky_natus_preview(request)
return _sky_preview_data(request)
@login_required(login_url="/")
@@ -440,7 +440,7 @@ def sky_save(request):
@login_required(login_url="/")
def sky_natus_data(request):
def sky_data(request):
user = request.user
if not user.sky_chart_data:
return HttpResponse(status=404)

View File

@@ -653,7 +653,7 @@ class Character(models.Model):
on_delete=models.SET_NULL, related_name='character_significators',
)
# ── natus input (what the gamer entered) ─────────────────────────────
# ── sky input (what the gamer entered) ─────────────────────────────
birth_dt = models.DateTimeField(null=True, blank=True) # UTC
birth_lat = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
birth_lon = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
@@ -662,7 +662,7 @@ class Character(models.Model):
max_length=1, choices=HOUSE_SYSTEM_CHOICES, default=PORPHYRY,
)
# ── computed natus snapshot (full PySwiss response) ───────────────────
# ── computed sky snapshot (full PySwiss response) ───────────────────
chart_data = models.JSONField(null=True, blank=True)
# ── celtic cross spread (added at PICK SEA) ───────────────────────────

View File

@@ -1933,16 +1933,16 @@ class SelectSigViewTest(TestCase):
self.assertEqual(response.status_code, 400)
# ── natus_preview (epic) ──────────────────────────────────────────────────────
# ── sky_preview (epic) ──────────────────────────────────────────────────────
class NatusPreviewViewTest(TestCase):
class SkyPreviewViewTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="pc@natus.io")
self.user = User.objects.create(email="pc@sky.io")
self.client.force_login(self.user)
self.room, _ = _make_sig_room(self.user)
self.room.table_status = Room.SKY_SELECT
self.room.save()
self.url = reverse("epic:natus_preview", kwargs={"room_id": self.room.id})
self.url = reverse("epic:sky_preview", kwargs={"room_id": self.room.id})
def test_missing_params_returns_400(self):
response = self.client.get(self.url, {"date": "1990-06-15"})
@@ -2025,16 +2025,16 @@ class TarotDealViewTest(TestCase):
)
# ── natus_save (epic) ─────────────────────────────────────────────────────────
# ── sky_save (epic) ─────────────────────────────────────────────────────────
class NatusSaveViewTest(TestCase):
class SkySaveViewTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="pc@natussave.io")
self.user = User.objects.create(email="pc@skysave.io")
self.client.force_login(self.user)
self.room, _ = _make_sig_room(self.user)
self.room.table_status = Room.SKY_SELECT
self.room.save()
self.url = reverse("epic:natus_save", kwargs={"room_id": self.room.id})
self.url = reverse("epic:sky_save", kwargs={"room_id": self.room.id})
def _post(self, payload):
import json as _json
@@ -2134,7 +2134,7 @@ class NatusSaveViewTest(TestCase):
def test_confirm_with_dict_shaped_elements_extracts_count(self):
"""Some chart payloads enrich each element to {count, contributors};
natus_save should read .count rather than treating the dict as a value."""
sky_save should read .count rather than treating the dict as a value."""
from apps.drama.models import GameEvent
chart = {
"elements": {
@@ -2156,7 +2156,7 @@ class NatusSaveViewTest(TestCase):
self.assertEqual(event.data.get("top_capacitors"), ["Ardor"])
def test_confirm_copies_seat_significator_to_character(self):
"""natus_save with action=confirm copies seat.significator onto Character."""
"""sky_save with action=confirm copies seat.significator onto Character."""
earthman, _ = DeckVariant.objects.get_or_create(
slug="earthman", defaults={"name": "Earthman Deck", "card_count": 108}
)

View File

@@ -25,8 +25,8 @@ urlpatterns = [
path('room/<uuid:room_id>/abandon', views.abandon_room, name='abandon_room'),
path('room/<uuid:room_id>/tarot/', views.tarot_deck, name='tarot_deck'),
path('room/<uuid:room_id>/tarot/deal', views.tarot_deal, name='tarot_deal'),
path('room/<uuid:room_id>/natus/preview', views.natus_preview, name='natus_preview'),
path('room/<uuid:room_id>/natus/save', views.natus_save, name='natus_save'),
path('room/<uuid:room_id>/sky/preview', views.sky_preview, name='sky_preview'),
path('room/<uuid:room_id>/sky/save', views.sky_save, name='sky_save'),
path('room/<uuid:room_id>/sea/partial', views.sea_partial, name='sea_partial'),
path('room/<uuid:room_id>/sea/deck', views.sea_deck, name='sea_deck'),
]

View File

@@ -25,7 +25,7 @@ def stack_reversal_probability(user=None, room=None):
# Element key → in-game capacitor name (mirrors ELEMENT_INFO in natus-wheel.js).
# Element key → in-game capacitor name (mirrors ELEMENT_INFO in sky-wheel.js).
# Used by the SKY_SAVED provenance event to render prose like
# "yields them a unique Ardor capacity."
ELEMENT_CAPACITOR_NAMES = {

View File

@@ -975,10 +975,10 @@ def tarot_deal(request, room_id):
})
# ── Natus (natal chart) ───────────────────────────────────────────────────────
# ── Sky (natal chart) ───────────────────────────────────────────────────────
@login_required
def natus_preview(request, room_id):
def sky_preview(request, room_id):
"""Proxy GET to PySwiss /api/chart/ and augment with distinction counts.
Query params:
@@ -1064,7 +1064,7 @@ def natus_preview(request, room_id):
@login_required
def natus_save(request, room_id):
def sky_save(request, room_id):
"""Create or update the draft Character for the requesting gamer's seat.
POST body (JSON):

View File

@@ -1,12 +1,12 @@
/**
* natus-wheel.js Self-contained D3 natal-chart module.
* sky-wheel.js Self-contained D3 natal-chart module.
*
* Public API:
* NatusWheel.draw(svgEl, data) first render
* NatusWheel.redraw(data) live update (same SVG)
* NatusWheel.clear() empty the SVG
* SkyWheel.draw(svgEl, data) first render
* SkyWheel.redraw(data) live update (same SVG)
* SkyWheel.clear() empty the SVG
*
* `data` shape matches the /epic/natus/preview/ proxy response:
* `data` shape matches the /epic/sky/preview/ proxy response:
* {
* planets: { Sun: { sign, degree, speed, retrograde }, },
* houses: { cusps: [f×12], asc: f, mc: f },
@@ -28,7 +28,7 @@
* already defined in the page; falls back to neutral colours if absent.
*/
const NatusWheel = (() => {
const SkyWheel = (() => {
'use strict';
// ── Constants ──────────────────────────────────────────────────────────────
@@ -223,8 +223,8 @@ const NatusWheel = (() => {
if (_staticBase) return _staticBase;
const scripts = document.querySelectorAll('script[src]');
for (const s of scripts) {
if (s.src.includes('natus-wheel')) {
_staticBase = s.src.replace(/natus-wheel\.js.*$/, '');
if (s.src.includes('sky-wheel')) {
_staticBase = s.src.replace(/sky-wheel\.js.*$/, '');
return _staticBase;
}
}
@@ -957,7 +957,7 @@ const NatusWheel = (() => {
* Called on every draw() so a fresh innerHTML replaces any stale state.
*/
function _injectTooltipControls() {
_tooltipEl = document.getElementById('id_natus_tooltip');
_tooltipEl = document.getElementById('id_sky_tooltip');
if (!_tooltipEl) return;
_tooltipEl.innerHTML =
`<button type="button" class="btn btn-equip nw-asp-don">DON</button>` +
@@ -1384,8 +1384,8 @@ const NatusWheel = (() => {
(() => {
const scripts = document.querySelectorAll('script[src]');
for (const s of scripts) {
if (s.src.includes('natus-wheel')) {
return s.src.replace(/natus-wheel\.js.*$/, 'icons/zodiac-signs/');
if (s.src.includes('sky-wheel')) {
return s.src.replace(/sky-wheel\.js.*$/, 'icons/zodiac-signs/');
}
}
return '/static/apps/gameboard/icons/zodiac-signs/';
@@ -1394,7 +1394,7 @@ const NatusWheel = (() => {
await Promise.all(SIGNS.map(async sign => {
const url = base + sign.name.toLowerCase() + '.svg';
const resp = await window.fetch(url);
if (!resp.ok) { console.warn(`NatusWheel: failed to load ${url}`); return; }
if (!resp.ok) { console.warn(`SkyWheel: failed to load ${url}`); return; }
const text = await resp.text();
const doc = new DOMParser().parseFromString(text, 'image/svg+xml');
const path = doc.querySelector('path');

View File

@@ -119,7 +119,7 @@ class StargazerNoteFromDashboardTest(FunctionalTest):
self.browser.get(self.live_server_url)
confirm_btn = self.wait_for(
lambda: self.browser.find_element(By.ID, "id_natus_confirm")
lambda: self.browser.find_element(By.ID, "id_sky_confirm")
)
self.assertIsNotNone(confirm_btn.get_attribute("disabled"))
self.assertFalse(self.browser.find_elements(By.CSS_SELECTOR, ".note-banner"))
@@ -136,7 +136,7 @@ class StargazerNoteFromDashboardTest(FunctionalTest):
self.browser.execute_script(_mock_preview_js(_CHART_FIXTURE))
_fill_valid_sky_form(self.browser)
confirm_btn = self.browser.find_element(By.ID, "id_natus_confirm")
confirm_btn = self.browser.find_element(By.ID, "id_sky_confirm")
self.wait_for(lambda: self.assertIsNone(confirm_btn.get_attribute("disabled")))
confirm_btn.click()
@@ -338,7 +338,7 @@ class StargazerNoteFromSkyPageTest(FunctionalTest):
self.browser.get(self.sky_url)
confirm_btn = self.wait_for(
lambda: self.browser.find_element(By.ID, "id_natus_confirm")
lambda: self.browser.find_element(By.ID, "id_sky_confirm")
)
self.assertIsNotNone(confirm_btn.get_attribute("disabled"))
self.assertFalse(self.browser.find_elements(By.CSS_SELECTOR, ".note-banner"))
@@ -351,11 +351,11 @@ class StargazerNoteFromSkyPageTest(FunctionalTest):
self.create_pre_authenticated_session("stargazer@test.io")
self.browser.get(self.sky_url)
self.wait_for(lambda: self.browser.find_element(By.ID, "id_natus_confirm"))
self.wait_for(lambda: self.browser.find_element(By.ID, "id_sky_confirm"))
self.browser.execute_script(_mock_preview_js(_CHART_FIXTURE))
_fill_valid_sky_form(self.browser)
confirm_btn = self.browser.find_element(By.ID, "id_natus_confirm")
confirm_btn = self.browser.find_element(By.ID, "id_sky_confirm")
self.wait_for(lambda: self.assertIsNone(confirm_btn.get_attribute("disabled")))
confirm_btn.click()
@@ -386,11 +386,11 @@ class StargazerNoteFromSkyPageTest(FunctionalTest):
self.create_pre_authenticated_session("stargazer@test.io")
self.browser.get(self.sky_url)
self.wait_for(lambda: self.browser.find_element(By.ID, "id_natus_confirm"))
self.wait_for(lambda: self.browser.find_element(By.ID, "id_sky_confirm"))
self.browser.execute_script(_mock_preview_js(_CHART_FIXTURE))
_fill_valid_sky_form(self.browser)
confirm_btn = self.browser.find_element(By.ID, "id_natus_confirm")
confirm_btn = self.browser.find_element(By.ID, "id_sky_confirm")
self.wait_for(lambda: self.assertIsNone(confirm_btn.get_attribute("disabled")))
confirm_btn.click()

View File

@@ -1,7 +1,7 @@
"""Functional tests for the My Sky dashboard feature.
My Sky is a dashboard applet linking to /dashboard/sky/ — a full-page
natus (natal chart) interface where the user can save their personal sky
sky (natal chart) interface where the user can save their personal sky
to their account (stored on the User model, independent of any game room).
"""
@@ -61,7 +61,7 @@ class MySkyAppletTest(FunctionalTest):
def test_my_sky_applet_links_to_sky_page_with_form(self):
"""Applet is visible on dashboard; link leads to /dashboard/sky/ with
all natus form fields present."""
all sky form fields present."""
self.create_pre_authenticated_session("stargazer@test.io")
self.browser.get(self.live_server_url)
@@ -80,14 +80,14 @@ class MySkyAppletTest(FunctionalTest):
lambda: self.assertRegex(self.browser.current_url, r"/dashboard/sky/$")
)
# 4. All natus form fields are present
# 4. All sky form fields are present
self.browser.find_element(By.ID, "id_nf_date")
self.browser.find_element(By.ID, "id_nf_time")
self.browser.find_element(By.ID, "id_nf_place")
self.browser.find_element(By.ID, "id_nf_lat")
self.browser.find_element(By.ID, "id_nf_lon")
self.browser.find_element(By.ID, "id_nf_tz")
self.browser.find_element(By.ID, "id_natus_confirm")
self.browser.find_element(By.ID, "id_sky_confirm")
class MySkyLocalStorageTest(FunctionalTest):
@@ -171,7 +171,7 @@ class MySkyAppletWheelTest(FunctionalTest):
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 and clicking an element-ring slice shows the tooltip.
(Planet click tooltip is covered by NatusWheelSpec.js T3/T4/T5.)"""
(Planet click tooltip is covered by SkyWheelSpec.js T3/T4/T5.)"""
self.create_pre_authenticated_session("stargazer@test.io")
self.browser.get(self.live_server_url)
@@ -192,14 +192,14 @@ class MySkyAppletWheelTest(FunctionalTest):
slice_el,
)
self.wait_for(lambda: self.assertEqual(
self.browser.find_element(By.ID, "id_natus_tooltip")
self.browser.find_element(By.ID, "id_sky_tooltip")
.value_of_css_property("display"),
"block",
))
class MySkyAppletFormTest(FunctionalTest):
"""My Sky applet shows natus entry form when no sky data is saved."""
"""My Sky applet shows sky entry form when no sky data is saved."""
def setUp(self):
super().setUp()
@@ -212,7 +212,7 @@ class MySkyAppletFormTest(FunctionalTest):
# ── T4 ───────────────────────────────────────────────────────────────────
def test_applet_shows_entry_form_when_no_sky_saved(self):
"""When no sky data is saved the My Sky applet shows all natus form
"""When no sky data is saved the My Sky applet shows all sky form
fields and a disabled SAVE SKY button; no wheel is drawn yet."""
self.create_pre_authenticated_session("stargazer@test.io")
self.browser.get(self.live_server_url)
@@ -227,7 +227,7 @@ class MySkyAppletFormTest(FunctionalTest):
applet.find_element(By.ID, "id_nf_lat")
applet.find_element(By.ID, "id_nf_lon")
applet.find_element(By.ID, "id_nf_tz")
applet.find_element(By.ID, "id_natus_confirm")
applet.find_element(By.ID, "id_sky_confirm")
self.assertFalse(applet.find_elements(By.CSS_SELECTOR, ".nw-root"))
@@ -276,7 +276,7 @@ class MySkyAppletFormTest(FunctionalTest):
""")
# Wait for confirm button to be enabled (preview resolved)
confirm_btn = self.browser.find_element(By.ID, "id_natus_confirm")
confirm_btn = self.browser.find_element(By.ID, "id_sky_confirm")
self.wait_for(lambda: self.assertIsNone(
confirm_btn.get_attribute("disabled")
))
@@ -331,4 +331,4 @@ class MySkyWheelConjunctionTest(FunctionalTest):
))
# (T7 tick-extends-past-zodiac, T8 click-raises-to-front, and T9c/T9n/T9w
# cycle navigation are covered by NatusWheelSpec.js.)
# cycle navigation are covered by SkyWheelSpec.js.)

View File

@@ -38,7 +38,7 @@ def _make_sky_confirmed_room(live_server_url, user, earthman):
@tag("channels")
class PickSeaAsyncTransitionTest(ChannelsFunctionalTest):
"""After sky confirm, the natus overlay closes and the room reloads to the
"""After sky confirm, the sky overlay closes and the room reloads to the
table hex w. the PICK SEA btn visible — the gamer must opt into the sea
overlay rather than be auto-launched into it."""
@@ -66,19 +66,19 @@ class PickSeaAsyncTransitionTest(ChannelsFunctionalTest):
self.room_url = self.live_server_url + reverse(
"epic:room", kwargs={"room_id": self.room.id}
)
self.natus_save_url = self.live_server_url + reverse(
"epic:natus_save", kwargs={"room_id": self.room.id}
self.sky_save_url = self.live_server_url + reverse(
"epic:sky_save", kwargs={"room_id": self.room.id}
)
def _confirm_sky(self):
"""POST to natus_save with action=confirm from browser JS (bypasses chart form)."""
"""POST to sky_save with action=confirm from browser JS (bypasses chart form)."""
# Wait for the room WS connection to be ready before triggering confirm
self.wait_for(lambda: self.browser.execute_script(
"return !!(window._roomSocket && window._roomSocket.readyState === 1);"
))
self.browser.execute_script(f"""
const csrf = (document.cookie.match(/csrftoken=([^;]+)/) || ['',''])[1];
fetch('{self.natus_save_url}', {{
fetch('{self.sky_save_url}', {{
method: 'POST',
credentials: 'same-origin',
headers: {{'Content-Type': 'application/json', 'X-CSRFToken': csrf}},
@@ -113,8 +113,8 @@ class PickSeaAsyncTransitionTest(ChannelsFunctionalTest):
)
self.assertFalse(has_sea_open)
def test_natus_overlay_closed_after_sky_confirm(self):
"""Natus overlay is gone (page reloaded) after sky confirm."""
def test_sky_overlay_closed_after_sky_confirm(self):
"""Sky overlay is gone (page reloaded) after sky confirm."""
self.create_pre_authenticated_session("founder@test.io")
self.browser.get(self.room_url)
self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sky_btn"))
@@ -122,8 +122,8 @@ class PickSeaAsyncTransitionTest(ChannelsFunctionalTest):
self._confirm_sky()
self.wait_for(lambda: self.browser.find_element(By.ID, "id_pick_sea_btn"))
natus = self.browser.find_elements(By.ID, "id_natus_overlay")
self.assertTrue(not natus or not natus[0].is_displayed())
sky = self.browser.find_elements(By.ID, "id_sky_overlay")
self.assertTrue(not sky or not sky[0].is_displayed())
def test_clicking_pick_sea_btn_opens_sea_overlay(self):
"""The gamer's explicit click on PICK SEA is what opens the sea overlay."""

View File

@@ -46,7 +46,7 @@ class PickSkyLocalStorageTest(FunctionalTest):
)
self.browser.execute_script("arguments[0].click()", btn)
self.wait_for(
lambda: self.browser.find_element(By.ID, "id_natus_overlay")
lambda: self.browser.find_element(By.ID, "id_sky_overlay")
)
def _fill_form(self):
@@ -89,7 +89,7 @@ class PickSkyLocalStorageTest(FunctionalTest):
self._fill_form()
# Close via NVM
self.browser.find_element(By.ID, "id_natus_cancel").click()
self.browser.find_element(By.ID, "id_sky_cancel").click()
# Reopen
self._open_overlay()

View File

@@ -27,7 +27,7 @@ from apps.lyric.models import User
# - On page reload: tray always starts closed (JS in-memory only).
#
# Contents (populated in later sprints): Role card, Significator, Celtic Cross
# draw, natus wheel, committed dice/cards for this table.
# draw, sky wheel, committed dice/cards for this table.
#
# ─────────────────────────────────────────────────────────────────────────────

View File

@@ -1,15 +1,15 @@
// ── NatusWheelSpec.js ─────────────────────────────────────────────────────────
// ── SkyWheelSpec.js ─────────────────────────────────────────────────────────
//
// Unit specs for natus-wheel.js — planet/element click-to-lock tooltips.
// Unit specs for sky-wheel.js — planet/element click-to-lock tooltips.
//
// DOM contract assumed:
// <svg id="id_natus_svg"> — target for NatusWheel.draw()
// <div id="id_natus_tooltip"> — tooltip portal (position:fixed on page)
// <svg id="id_sky_svg"> — target for SkyWheel.draw()
// <div id="id_sky_tooltip"> — tooltip portal (position:fixed on page)
//
// Click-lock contract:
// click on [data-planet] group → adds .nw-planet--active class
// raises group to DOM front
// shows #id_natus_tooltip with
// shows #id_sky_tooltip with
// planet name, in-sign degree, sign name,
// ℞ if retrograde, and "n / total" index
// click same planet again → removes .nw-planet--active; hides tooltip
@@ -40,7 +40,7 @@ const CONJUNCTION_CHART = {
house_system: "O",
};
describe("NatusWheel — planet click tooltips", () => {
describe("SkyWheel — planet click tooltips", () => {
const SYNTHETIC_CHART = {
planets: {
@@ -67,7 +67,7 @@ describe("NatusWheel — planet click tooltips", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -75,16 +75,16 @@ describe("NatusWheel — planet click tooltips", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, SYNTHETIC_CHART);
SkyWheel.draw(svgEl, SYNTHETIC_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
@@ -145,13 +145,13 @@ describe("NatusWheel — planet click tooltips", () => {
});
});
describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
describe("SkyWheel — tick lines, raise, and cycle navigation", () => {
let svgEl2, tooltipEl;
beforeEach(() => {
svgEl2 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl2.setAttribute("id", "id_natus_svg_conj");
svgEl2.setAttribute("id", "id_sky_svg_conj");
svgEl2.setAttribute("width", "400");
svgEl2.setAttribute("height", "400");
svgEl2.style.width = "400px";
@@ -159,17 +159,17 @@ describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
document.body.appendChild(svgEl2);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
tooltipEl.style.position = "fixed";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl2, CONJUNCTION_CHART);
SkyWheel.draw(svgEl2, CONJUNCTION_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl2.remove();
tooltipEl.remove();
});
@@ -295,7 +295,7 @@ describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
// - DOFF clears lines; re-opening same planet finds DON active
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — DON/DOFF aspect line persistence", () => {
describe("SkyWheel — DON/DOFF aspect line persistence", () => {
const ASPECT_CHART = {
planets: {
@@ -326,7 +326,7 @@ describe("NatusWheel — DON/DOFF aspect line persistence", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -334,16 +334,16 @@ describe("NatusWheel — DON/DOFF aspect line persistence", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, ASPECT_CHART);
SkyWheel.draw(svgEl, ASPECT_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
@@ -454,7 +454,7 @@ describe("NatusWheel — DON/DOFF aspect line persistence", () => {
});
});
xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
xdescribe("SkyWheel — half-wheel tooltip positioning", () => {
const HALF_CHART = {
planets: {
@@ -481,7 +481,7 @@ xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
beforeEach(() => {
svgEl3 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl3.setAttribute("id", "id_natus_svg_half");
svgEl3.setAttribute("id", "id_sky_svg_half");
svgEl3.setAttribute("width", "400");
svgEl3.setAttribute("height", "400");
svgEl3.style.width = "400px";
@@ -489,7 +489,7 @@ xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
document.body.appendChild(svgEl3);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
tooltipEl.style.position = "fixed";
@@ -501,11 +501,11 @@ xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
{ left: 0, top: 0, width: 400, height: 400, right: 400, bottom: 400 }
);
NatusWheel.draw(svgEl3, HALF_CHART);
SkyWheel.draw(svgEl3, HALF_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl3.remove();
tooltipEl.remove();
});
@@ -561,7 +561,7 @@ xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
// clicking a classic-element slice lists contributor planet names in the tooltip.
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — element tooltip contributor display", () => {
describe("SkyWheel — element tooltip contributor display", () => {
const ENRICHED_CHART = {
planets: {
@@ -608,7 +608,7 @@ describe("NatusWheel — element tooltip contributor display", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -616,16 +616,16 @@ describe("NatusWheel — element tooltip contributor display", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, ENRICHED_CHART);
SkyWheel.draw(svgEl, ENRICHED_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
@@ -682,7 +682,7 @@ describe("NatusWheel — element tooltip contributor display", () => {
// Clicking the same sign again closes the tooltip.
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — sign ring click tooltips", () => {
describe("SkyWheel — sign ring click tooltips", () => {
const SIGN_CHART = {
planets: {
@@ -703,7 +703,7 @@ describe("NatusWheel — sign ring click tooltips", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -711,16 +711,16 @@ describe("NatusWheel — sign ring click tooltips", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, SIGN_CHART);
SkyWheel.draw(svgEl, SIGN_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
@@ -756,7 +756,7 @@ describe("NatusWheel — sign ring click tooltips", () => {
// Clicking the same house again closes the tooltip.
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — house ring click tooltips", () => {
describe("SkyWheel — house ring click tooltips", () => {
const HOUSE_CHART = {
planets: {
@@ -777,7 +777,7 @@ describe("NatusWheel — house ring click tooltips", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -785,16 +785,16 @@ describe("NatusWheel — house ring click tooltips", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, HOUSE_CHART);
SkyWheel.draw(svgEl, HOUSE_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
@@ -835,7 +835,7 @@ describe("NatusWheel — house ring click tooltips", () => {
// Planet tooltips include angle aspects in their own aspect lists.
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — angle (ASC/MC) click tooltips", () => {
describe("SkyWheel — 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° ✓
@@ -863,7 +863,7 @@ describe("NatusWheel — angle (ASC/MC) click tooltips", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -871,16 +871,16 @@ describe("NatusWheel — angle (ASC/MC) click tooltips", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, ANGLE_CHART);
SkyWheel.draw(svgEl, ANGLE_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});

View File

@@ -25,7 +25,7 @@
<script src="SigSelectSpec.js"></script>
<script src="SeaDealSpec.js"></script>
<script src="FanStageSpec.js"></script>
<script src="NatusWheelSpec.js"></script>
<script src="SkyWheelSpec.js"></script>
<script src="NoteSpec.js"></script>
<script src="NotePageSpec.js"></script>
<!-- src files -->
@@ -40,7 +40,7 @@
<script src="/static/apps/epic/sea.js"></script>
<script src="/static/apps/gameboard/game-kit.js"></script>
<script src="/static/apps/gameboard/d3.min.js"></script>
<script src="/static/apps/gameboard/natus-wheel.js"></script>
<script src="/static/apps/gameboard/sky-wheel.js"></script>
<!-- Jasmine env config (optional) -->
<script src="lib/jasmine-6.0.1/boot1.js"></script>

View File

@@ -926,7 +926,7 @@ html:has(.sig-backdrop) {
}
// ── PICK SEA overlay ─────────────────────────────────────────────────────────
// Mirrors .natus-* structure but with columns reversed:
// Mirrors .sky-* structure but with columns reversed:
// left = transparent (Celtic Cross card positions)
// right = rgba(--priUser) opaque (spread select)
@@ -1414,7 +1414,7 @@ $_glow-gravity: 0 0 0.8rem 0.15rem rgba(var(--quaUser), 0.6);
}
// NVM button — same positioning as .natus-modal-wrap > .btn-cancel
// NVM button — same positioning as .sky-modal-wrap > .btn-cancel
.sea-modal-wrap > .btn-cancel {
position: absolute;
top: -1rem;

View File

@@ -1,13 +1,13 @@
// Natus (Pick Sky) overlay
// Sky (Pick Sky) overlay
// Gaussian backdrop + centred modal, matching the gate/sig overlay pattern.
// Open state: html.natus-open (added by JS on PICK SKY click).
// Open state: html.sky-open (added by JS on PICK SKY click).
//
// Layout: header / two-column body (form | wheel) / footer
// Collapses to stacked single-column below 600 px.
// Scroll-lock
html.natus-open {
html.sky-open {
overflow: hidden;
#id_aperture_fill { opacity: 1; }
@@ -15,7 +15,7 @@ html.natus-open {
// Backdrop
.natus-backdrop {
.sky-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.75);
@@ -23,18 +23,18 @@ html.natus-open {
z-index: 100;
pointer-events: none;
// Hidden until html.natus-open
// Hidden until html.sky-open
opacity: 0;
transition: opacity 0.15s ease;
}
html.natus-open .natus-backdrop {
html.sky-open .sky-backdrop {
opacity: 1;
}
// Overlay shell (positions + scrolls the modal)
.natus-overlay {
.sky-overlay {
position: fixed;
inset: 0;
display: flex;
@@ -45,7 +45,7 @@ html.natus-open .natus-backdrop {
overscroll-behavior: contain;
pointer-events: none;
// Hidden until html.natus-open
// Hidden until html.sky-open
visibility: hidden;
@media (orientation: landscape) {
@@ -55,7 +55,7 @@ html.natus-open .natus-backdrop {
}
}
html.natus-open .natus-overlay {
html.sky-open .sky-overlay {
visibility: visible;
pointer-events: none; // modal itself is pointer-events: auto
}
@@ -64,7 +64,7 @@ html.natus-open .natus-overlay {
// Thin wrapper: position:relative so the NVM circle can sit on the corner
// without being clipped by the modal's overflow:hidden.
.natus-modal-wrap {
.sky-modal-wrap {
position: relative;
pointer-events: none; // overlay handles pointer-events; children re-enable
width: 92vw;
@@ -76,16 +76,16 @@ html.natus-open .natus-overlay {
transition: opacity 0.2s ease, transform 0.2s ease;
}
html.natus-open .natus-modal-wrap {
html.sky-open .sky-modal-wrap {
opacity: 1;
transform: translateY(0);
}
.natus-modal {
.sky-modal {
pointer-events: auto;
display: flex;
flex-direction: column;
width: 100%; // fills .natus-modal-wrap
width: 100%; // fills .sky-modal-wrap
max-height: 96vh;
border: 0.1rem solid rgba(var(--terUser), 0.25);
border-radius: 0.5rem;
@@ -94,7 +94,7 @@ html.natus-open .natus-modal-wrap {
// Header
.natus-modal-header {
.sky-modal-header {
flex-shrink: 0;
padding: 0.6rem 1rem;
background: rgba(var(--priUser), 1);
@@ -121,7 +121,7 @@ html.natus-open .natus-modal-wrap {
// Body: two columns
.natus-modal-body {
.sky-modal-body {
flex: 1;
min-height: 0;
display: flex;
@@ -130,7 +130,7 @@ html.natus-open .natus-modal-wrap {
}
// Form column fixed width; form-main scrolls, confirm btn pinned at bottom
.natus-form-col {
.sky-form-col {
flex: 0 0 240px;
overflow: hidden;
padding: 0.9rem 1rem;
@@ -142,7 +142,7 @@ html.natus-open .natus-modal-wrap {
}
// Scrollable inner container (form fields + status)
.natus-form-main {
.sky-form-main {
flex: 1;
min-height: 0;
overflow-y: auto;
@@ -152,12 +152,12 @@ html.natus-open .natus-modal-wrap {
}
// Confirm btn inside form-col full width, pinned at column bottom
.natus-form-col > #id_natus_confirm {
.sky-form-col > #id_sky_confirm {
flex-shrink: 0;
}
// Wheel column fills remaining space
.natus-wheel-col {
.sky-wheel-col {
flex: 1;
min-width: 0;
display: flex;
@@ -168,7 +168,7 @@ html.natus-open .natus-modal-wrap {
position: relative;
}
.natus-svg {
.sky-svg {
display: block;
width: 100%;
height: 100%;
@@ -179,7 +179,7 @@ html.natus-open .natus-modal-wrap {
// Form fields
.natus-field {
.sky-field {
display: flex;
flex-direction: column;
gap: 0.25rem;
@@ -204,9 +204,9 @@ html.natus-open .natus-modal-wrap {
}
// Place search field wrapper: text input + geo button inline
.natus-place-field { position: relative; }
.sky-place-field { position: relative; }
.natus-place-wrap {
.sky-place-wrap {
display: flex;
gap: 0.4rem;
align-items: center;
@@ -216,7 +216,7 @@ html.natus-open .natus-modal-wrap {
}
// Nominatim suggestion dropdown
.natus-suggestions {
.sky-suggestions {
position: absolute;
left: 0;
right: 0;
@@ -230,7 +230,7 @@ html.natus-open .natus-modal-wrap {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
.natus-suggestion-item {
.sky-suggestion-item {
display: block;
width: 100%;
padding: 0.4rem 0.6rem;
@@ -253,7 +253,7 @@ html.natus-open .natus-modal-wrap {
}
// Coords row: lat | lon (read-only, populated by place selection)
.natus-coords {
.sky-coords {
flex-direction: row;
align-items: flex-end;
gap: 0.4rem;
@@ -282,7 +282,7 @@ html.natus-open .natus-modal-wrap {
// Status line
.natus-status {
.sky-status {
font-size: 0.65rem;
opacity: 0.6;
min-height: 1rem;
@@ -295,11 +295,11 @@ html.natus-open .natus-modal-wrap {
}
// NVM corner btn
// Absolutely pinned to top-right corner of .natus-modal-wrap.
// Absolutely pinned to top-right corner of .sky-modal-wrap.
// transform: translate(50%,-50%) centres the circle on the corner point.
// Lives outside .natus-modal so overflow:hidden doesn't clip it.
// Lives outside .sky-modal so overflow:hidden doesn't clip it.
#id_natus_cancel {
#id_sky_cancel {
position: absolute;
top: 0;
right: 0;
@@ -312,22 +312,22 @@ html.natus-open .natus-modal-wrap {
// Narrow / portrait
@media (max-width: 600px) {
.natus-modal-wrap {
.sky-modal-wrap {
width: 92vw;
}
.natus-modal {
.sky-modal {
max-height: 96vh;
}
.natus-modal-body {
.sky-modal-body {
flex-direction: column;
overflow-y: auto;
}
// Form col stacks above wheel; internally becomes a flex-row so
// form-main gets most of the width and confirm btn sits to its right.
.natus-form-col {
.sky-form-col {
flex: 0 0 auto;
flex-direction: row;
align-items: flex-end;
@@ -337,19 +337,19 @@ html.natus-open .natus-modal-wrap {
gap: 0.5rem;
}
.natus-form-main {
.sky-form-main {
flex: 1;
min-width: 0;
overflow-y: auto;
max-height: 40vh;
}
.natus-form-col > #id_natus_confirm {
.sky-form-col > #id_sky_confirm {
flex-shrink: 0;
align-self: flex-end;
}
.natus-wheel-col {
.sky-wheel-col {
flex: 0 0 280px;
}
}
@@ -512,10 +512,10 @@ body[class*="-light"] {
// Planet hover tooltip must live outside any ancestor with transform or
// container-type (both break position:fixed). Placed as a direct sibling of
// .natus-overlay in room.html; alongside #id_tooltip_portal in home.html.
// .sky-overlay in room.html; alongside #id_tooltip_portal in home.html.
#id_natus_tooltip,
#id_natus_tooltip_2 {
#id_sky_tooltip,
#id_sky_tooltip_2 {
position: fixed;
z-index: 200;
pointer-events: auto;
@@ -787,8 +787,8 @@ body[class*="-light"] {
}
// Element title colors primary tier on dark palettes
#id_natus_tooltip,
#id_natus_tooltip_2 {
#id_sky_tooltip,
#id_sky_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); }
@@ -798,8 +798,8 @@ body[class*="-light"] {
}
// Sign tooltip title + sign icon SVG element border colors (Stone/Air/Fire/Water schema)
#id_natus_tooltip,
#id_natus_tooltip_2 {
#id_sky_tooltip,
#id_sky_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); }
@@ -816,8 +816,8 @@ body[class*="-light"] {
}
// On light palettes switch to tertiary tier for legibility
body[class*="-light"] #id_natus_tooltip,
body[class*="-light"] #id_natus_tooltip_2 {
body[class*="-light"] #id_sky_tooltip,
body[class*="-light"] #id_sky_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); }
@@ -827,8 +827,8 @@ body[class*="-light"] #id_natus_tooltip_2 {
}
// On light palettes switch to primary (darkest) tier for legibility
body[class*="-light"] #id_natus_tooltip,
body[class*="-light"] #id_natus_tooltip_2 {
body[class*="-light"] #id_sky_tooltip,
body[class*="-light"] #id_sky_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); }
@@ -849,7 +849,7 @@ body[class*="-light"] #id_natus_tooltip_2 {
h2 { flex-shrink: 0; }
.natus-svg {
.sky-svg {
flex: 1;
min-height: 0;
max-width: none;
@@ -866,7 +866,7 @@ body[class*="-light"] #id_natus_tooltip_2 {
flex-direction: column;
gap: 0.5rem;
#id_natus_confirm {
#id_sky_confirm {
margin-top: -1.5rem;
align-self: center;
position: relative;
@@ -908,13 +908,13 @@ body.page-sky {
}
// Stack wheel above form; allow body to grow past viewport (page scrolls, not body)
.sky-page .natus-modal-body {
.sky-page .sky-modal-body {
flex-direction: column;
flex-shrink: 0;
}
// Wheel takes its natural square size from its width never shrinks for the form
.sky-page .natus-wheel-col {
.sky-page .sky-wheel-col {
order: -1;
flex: 0 0 auto;
width: 100%;
@@ -925,7 +925,7 @@ body.page-sky {
}
// Form col runs horizontally below the wheel (same compact pattern as narrow-portrait modal)
.sky-page .natus-form-col {
.sky-page .sky-form-col {
flex: 0 0 auto;
flex-direction: row;
align-items: flex-end;
@@ -933,7 +933,7 @@ body.page-sky {
border-top: 0.1rem solid rgba(var(--terUser), 0.12);
}
.sky-page .natus-form-main {
.sky-page .sky-form-main {
flex: 1;
min-width: 0;
overflow-y: visible;
@@ -942,8 +942,8 @@ body.page-sky {
// Sidebar z-index sink (landscape sidebars must go below backdrop)
@media (orientation: landscape) {
html.natus-open body .container .navbar,
html.natus-open body #id_footer {
html.sky-open body .container .navbar,
html.sky-open body #id_footer {
z-index: 90;
}
}

View File

@@ -1,6 +1,6 @@
// ── Tooltip base styles ───────────────────────────────────────────────────────
// Shared by wallet tokens, game-kit kit bag, and natus wheel tooltips.
// Portal tooltips (#id_tooltip_portal, #id_natus_tooltip) are position:fixed
// Shared by wallet tokens, game-kit kit bag, and sky wheel tooltips.
// Portal tooltips (#id_tooltip_portal, #id_sky_tooltip) are position:fixed
// and override z-index; inline .tt cards use position:absolute within their
// parent token container.

View File

@@ -258,7 +258,7 @@ $tray-bevel: 0.3rem; // inner bevel ring; grid must sit inside this
position: relative;
// Whatever a cell holds (role-card img, sig stage card, future Celtic Cross
// / natus wheel / dice) gets a soft drop shadow to lift it off the felt.
// / sky wheel / dice) gets a soft drop shadow to lift it off the felt.
// Applied to the child rather than the cell itself so the dotted grid
// borders stay shadow-free.
> * {

View File

@@ -7,7 +7,7 @@
@import 'palette-picker';
@import 'room';
@import 'card-deck';
@import 'natus';
@import 'sky';
@import 'tray';
@import 'billboard';
@import 'note';

View File

@@ -1,15 +1,15 @@
// ── NatusWheelSpec.js ─────────────────────────────────────────────────────────
// ── SkyWheelSpec.js ─────────────────────────────────────────────────────────
//
// Unit specs for natus-wheel.js — planet/element click-to-lock tooltips.
// Unit specs for sky-wheel.js — planet/element click-to-lock tooltips.
//
// DOM contract assumed:
// <svg id="id_natus_svg"> — target for NatusWheel.draw()
// <div id="id_natus_tooltip"> — tooltip portal (position:fixed on page)
// <svg id="id_sky_svg"> — target for SkyWheel.draw()
// <div id="id_sky_tooltip"> — tooltip portal (position:fixed on page)
//
// Click-lock contract:
// click on [data-planet] group → adds .nw-planet--active class
// raises group to DOM front
// shows #id_natus_tooltip with
// shows #id_sky_tooltip with
// planet name, in-sign degree, sign name,
// ℞ if retrograde, and "n / total" index
// click same planet again → removes .nw-planet--active; hides tooltip
@@ -40,7 +40,7 @@ const CONJUNCTION_CHART = {
house_system: "O",
};
describe("NatusWheel — planet click tooltips", () => {
describe("SkyWheel — planet click tooltips", () => {
const SYNTHETIC_CHART = {
planets: {
@@ -67,7 +67,7 @@ describe("NatusWheel — planet click tooltips", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -75,16 +75,16 @@ describe("NatusWheel — planet click tooltips", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, SYNTHETIC_CHART);
SkyWheel.draw(svgEl, SYNTHETIC_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
@@ -145,13 +145,13 @@ describe("NatusWheel — planet click tooltips", () => {
});
});
describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
describe("SkyWheel — tick lines, raise, and cycle navigation", () => {
let svgEl2, tooltipEl;
beforeEach(() => {
svgEl2 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl2.setAttribute("id", "id_natus_svg_conj");
svgEl2.setAttribute("id", "id_sky_svg_conj");
svgEl2.setAttribute("width", "400");
svgEl2.setAttribute("height", "400");
svgEl2.style.width = "400px";
@@ -159,17 +159,17 @@ describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
document.body.appendChild(svgEl2);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
tooltipEl.style.position = "fixed";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl2, CONJUNCTION_CHART);
SkyWheel.draw(svgEl2, CONJUNCTION_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl2.remove();
tooltipEl.remove();
});
@@ -295,7 +295,7 @@ describe("NatusWheel — tick lines, raise, and cycle navigation", () => {
// - DOFF clears lines; re-opening same planet finds DON active
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — DON/DOFF aspect line persistence", () => {
describe("SkyWheel — DON/DOFF aspect line persistence", () => {
const ASPECT_CHART = {
planets: {
@@ -326,7 +326,7 @@ describe("NatusWheel — DON/DOFF aspect line persistence", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -334,16 +334,16 @@ describe("NatusWheel — DON/DOFF aspect line persistence", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, ASPECT_CHART);
SkyWheel.draw(svgEl, ASPECT_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
@@ -454,7 +454,7 @@ describe("NatusWheel — DON/DOFF aspect line persistence", () => {
});
});
xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
xdescribe("SkyWheel — half-wheel tooltip positioning", () => {
const HALF_CHART = {
planets: {
@@ -481,7 +481,7 @@ xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
beforeEach(() => {
svgEl3 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl3.setAttribute("id", "id_natus_svg_half");
svgEl3.setAttribute("id", "id_sky_svg_half");
svgEl3.setAttribute("width", "400");
svgEl3.setAttribute("height", "400");
svgEl3.style.width = "400px";
@@ -489,7 +489,7 @@ xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
document.body.appendChild(svgEl3);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
tooltipEl.style.position = "fixed";
@@ -501,11 +501,11 @@ xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
{ left: 0, top: 0, width: 400, height: 400, right: 400, bottom: 400 }
);
NatusWheel.draw(svgEl3, HALF_CHART);
SkyWheel.draw(svgEl3, HALF_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl3.remove();
tooltipEl.remove();
});
@@ -561,7 +561,7 @@ xdescribe("NatusWheel — half-wheel tooltip positioning", () => {
// clicking a classic-element slice lists contributor planet names in the tooltip.
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — element tooltip contributor display", () => {
describe("SkyWheel — element tooltip contributor display", () => {
const ENRICHED_CHART = {
planets: {
@@ -608,7 +608,7 @@ describe("NatusWheel — element tooltip contributor display", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -616,16 +616,16 @@ describe("NatusWheel — element tooltip contributor display", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, ENRICHED_CHART);
SkyWheel.draw(svgEl, ENRICHED_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
@@ -682,7 +682,7 @@ describe("NatusWheel — element tooltip contributor display", () => {
// Clicking the same sign again closes the tooltip.
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — sign ring click tooltips", () => {
describe("SkyWheel — sign ring click tooltips", () => {
const SIGN_CHART = {
planets: {
@@ -703,7 +703,7 @@ describe("NatusWheel — sign ring click tooltips", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -711,16 +711,16 @@ describe("NatusWheel — sign ring click tooltips", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, SIGN_CHART);
SkyWheel.draw(svgEl, SIGN_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
@@ -756,7 +756,7 @@ describe("NatusWheel — sign ring click tooltips", () => {
// Clicking the same house again closes the tooltip.
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — house ring click tooltips", () => {
describe("SkyWheel — house ring click tooltips", () => {
const HOUSE_CHART = {
planets: {
@@ -777,7 +777,7 @@ describe("NatusWheel — house ring click tooltips", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -785,16 +785,16 @@ describe("NatusWheel — house ring click tooltips", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, HOUSE_CHART);
SkyWheel.draw(svgEl, HOUSE_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});
@@ -835,7 +835,7 @@ describe("NatusWheel — house ring click tooltips", () => {
// Planet tooltips include angle aspects in their own aspect lists.
// ─────────────────────────────────────────────────────────────────────────────
describe("NatusWheel — angle (ASC/MC) click tooltips", () => {
describe("SkyWheel — 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° ✓
@@ -863,7 +863,7 @@ describe("NatusWheel — angle (ASC/MC) click tooltips", () => {
beforeEach(() => {
svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgEl.setAttribute("id", "id_natus_svg");
svgEl.setAttribute("id", "id_sky_svg");
svgEl.setAttribute("width", "400");
svgEl.setAttribute("height", "400");
svgEl.style.width = "400px";
@@ -871,16 +871,16 @@ describe("NatusWheel — angle (ASC/MC) click tooltips", () => {
document.body.appendChild(svgEl);
tooltipEl = document.createElement("div");
tooltipEl.id = "id_natus_tooltip";
tooltipEl.id = "id_sky_tooltip";
tooltipEl.className = "tt";
tooltipEl.style.display = "none";
document.body.appendChild(tooltipEl);
NatusWheel.draw(svgEl, ANGLE_CHART);
SkyWheel.draw(svgEl, ANGLE_CHART);
});
afterEach(() => {
NatusWheel.clear();
SkyWheel.clear();
svgEl.remove();
tooltipEl.remove();
});

View File

@@ -25,7 +25,7 @@
<script src="SigSelectSpec.js"></script>
<script src="SeaDealSpec.js"></script>
<script src="FanStageSpec.js"></script>
<script src="NatusWheelSpec.js"></script>
<script src="SkyWheelSpec.js"></script>
<script src="NoteSpec.js"></script>
<script src="NotePageSpec.js"></script>
<!-- src files -->
@@ -40,7 +40,7 @@
<script src="/static/apps/epic/sea.js"></script>
<script src="/static/apps/gameboard/game-kit.js"></script>
<script src="/static/apps/gameboard/d3.min.js"></script>
<script src="/static/apps/gameboard/natus-wheel.js"></script>
<script src="/static/apps/gameboard/sky-wheel.js"></script>
<!-- Jasmine env config (optional) -->
<script src="lib/jasmine-6.0.1/boot1.js"></script>

View File

@@ -10,22 +10,22 @@
{% if not request.user.sky_chart_data %}
<div id="id_applet_sky_form_wrap">
<form id="id_natus_form" autocomplete="off">
<form id="id_sky_form" autocomplete="off">
<div class="natus-field">
<div class="sky-field">
<label for="id_nf_date">Birth date</label>
<input id="id_nf_date" name="date" type="date" required>
</div>
<div class="natus-field">
<div class="sky-field">
<label for="id_nf_time">Birth time</label>
<input id="id_nf_time" name="time" type="time" value="12:00">
<small>Local time at birth place. Use 12:00 if unknown.</small>
</div>
<div class="natus-field natus-place-field">
<div class="sky-field sky-place-field">
<label for="id_nf_place">Birth place</label>
<div class="natus-place-wrap">
<div class="sky-place-wrap">
<input id="id_nf_place" name="place" type="text"
placeholder="Start typing a city…"
autocomplete="off">
@@ -35,10 +35,10 @@
<i class="fa-solid fa-location-crosshairs"></i>
</button>
</div>
<div id="id_nf_suggestions" class="natus-suggestions" hidden></div>
<div id="id_nf_suggestions" class="sky-suggestions" hidden></div>
</div>
<div class="natus-field natus-coords">
<div class="sky-field sky-coords">
<div>
<label>Latitude</label>
<input id="id_nf_lat" name="lat" type="text"
@@ -51,7 +51,7 @@
</div>
</div>
<div class="natus-field">
<div class="sky-field">
<label for="id_nf_tz">Timezone</label>
<input id="id_nf_tz" name="tz" type="text"
placeholder="auto-detected from location">
@@ -60,15 +60,15 @@
</form>
<div id="id_natus_status" class="natus-status"></div>
<div id="id_sky_status" class="sky-status"></div>
<button type="button" id="id_natus_confirm" class="btn btn-primary" disabled>
<button type="button" id="id_sky_confirm" class="btn btn-primary" disabled>
Save Sky
</button>
</div>
{% endif %}
<svg id="id_my_sky_svg" class="natus-svg"
<svg id="id_my_sky_svg" class="sky-svg"
{% if not request.user.sky_chart_data %}style="display:none;"{% endif %}></svg>
{% if request.user.sky_chart_data %}
{{ request.user.sky_chart_data|json_script:"id_my_sky_data" }}
@@ -76,7 +76,7 @@
</section>
<script src="{% static 'apps/gameboard/d3.min.js' %}"></script>
<script src="{% static 'apps/gameboard/natus-wheel.js' %}"></script>
<script src="{% static 'apps/gameboard/sky-wheel.js' %}"></script>
<script>
(function () {
'use strict';
@@ -87,24 +87,24 @@
{% if request.user.sky_chart_data %}
// Sky already saved — fetch fresh enriched data from server then draw.
fetch('{% url "sky_natus_data" %}')
fetch('{% url "sky_data" %}')
.then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
.then(function (data) {
NatusWheel.preload().then(function () { NatusWheel.draw(svgEl, data); });
SkyWheel.preload().then(function () { SkyWheel.draw(svgEl, data); });
})
.catch(function () {
// Fallback: draw from inline stale data if endpoint fails.
var stale = JSON.parse(document.getElementById('id_my_sky_data').textContent);
NatusWheel.preload().then(function () { NatusWheel.draw(svgEl, stale); });
SkyWheel.preload().then(function () { SkyWheel.draw(svgEl, stale); });
});
{% else %}
// No sky saved yet — wire up the entry form.
const formWrap = document.getElementById('id_applet_sky_form_wrap');
const form = document.getElementById('id_natus_form');
const statusEl = document.getElementById('id_natus_status');
const confirmBtn = document.getElementById('id_natus_confirm');
const form = document.getElementById('id_sky_form');
const statusEl = document.getElementById('id_sky_status');
const confirmBtn = document.getElementById('id_sky_confirm');
const geoBtn = document.getElementById('id_nf_geolocate');
const placeInput = document.getElementById('id_nf_place');
const latInput = document.getElementById('id_nf_lat');
@@ -117,7 +117,7 @@
const SAVE_URL = section.dataset.saveUrl;
const NOMINATIM = 'https://nominatim.openstreetmap.org/search';
const USER_AGENT = 'EarthmanRPG/1.0 (https://earthmanrpg.me)';
const LS_KEY = 'natus-form:dashboard:sky';
const LS_KEY = 'sky-form:dashboard:sky';
let _lastChartData = null;
let _placeDebounce = null;
@@ -125,7 +125,7 @@
const PLACE_DELAY = 400;
const CHART_DELAY = 300;
NatusWheel.preload();
SkyWheel.preload();
// ── localStorage persistence ──────────────────────────────────────────────
@@ -158,7 +158,7 @@
function setStatus(msg, type) {
statusEl.textContent = msg;
statusEl.className = 'natus-status' + (type ? ` natus-status--${type}` : '');
statusEl.className = 'sky-status' + (type ? ` sky-status--${type}` : '');
}
// ── Nominatim place search ────────────────────────────────────────────────
@@ -197,7 +197,7 @@
results.forEach(place => {
const item = document.createElement('button');
item.type = 'button';
item.className = 'natus-suggestion-item';
item.className = 'sky-suggestion-item';
item.textContent = place.display_name;
item.addEventListener('click', () => selectPlace(place));
suggestions.appendChild(item);
@@ -331,7 +331,7 @@
.then(data => {
formWrap.style.display = 'none';
svgEl.style.display = '';
NatusWheel.preload().then(() => NatusWheel.draw(svgEl, _lastChartData));
SkyWheel.preload().then(() => SkyWheel.draw(svgEl, _lastChartData));
Note.handleSaveResponse(data);
})
.catch(err => {

View File

@@ -21,7 +21,7 @@
{% include "apps/applets/_partials/_gear.html" with menu_id="id_dash_applet_menu" %}
</div>
<div id="id_tooltip_portal" class="token-tooltip" style="display:none;"></div>
<div id="id_natus_tooltip" class="tt" style="display:none;"></div>
<div id="id_natus_tooltip_2" class="tt" style="display:none;"></div>
<div id="id_sky_tooltip" class="tt" style="display:none;"></div>
<div id="id_sky_tooltip_2" class="tt" style="display:none;"></div>
{% endif %}
{% endblock content %}

View File

@@ -6,33 +6,33 @@
{% block content %}
<div class="sky-page"
id="id_natus_overlay"
id="id_sky_overlay"
data-preview-url="{% url 'sky_preview' %}"
data-save-url="{% url 'sky_save' %}">
<div class="natus-modal-body sky-body">
<div class="sky-modal-body sky-body">
{# ── Form column ─────────────────────────────────────────────────── #}
<div class="natus-form-col">
<div class="natus-form-main">
<form id="id_natus_form" autocomplete="off">
<div class="sky-form-col">
<div class="sky-form-main">
<form id="id_sky_form" autocomplete="off">
<div class="natus-field">
<div class="sky-field">
<label for="id_nf_date">Birth date</label>
<input id="id_nf_date" name="date" type="date" required
{% if saved_birth_date %}value="{{ saved_birth_date }}"{% endif %}>
</div>
<div class="natus-field">
<div class="sky-field">
<label for="id_nf_time">Birth time</label>
<input id="id_nf_time" name="time" type="time"
value="{{ saved_birth_time|default:'12:00' }}">
<small>Local time at birth place. Use 12:00 if unknown.</small>
</div>
<div class="natus-field natus-place-field">
<div class="sky-field sky-place-field">
<label for="id_nf_place">Birth place</label>
<div class="natus-place-wrap">
<div class="sky-place-wrap">
<input id="id_nf_place" name="place" type="text"
placeholder="Start typing a city…"
autocomplete="off"
@@ -43,10 +43,10 @@
<i class="fa-solid fa-location-crosshairs"></i>
</button>
</div>
<div id="id_nf_suggestions" class="natus-suggestions" hidden></div>
<div id="id_nf_suggestions" class="sky-suggestions" hidden></div>
</div>
<div class="natus-field natus-coords">
<div class="sky-field sky-coords">
<div>
<label>Latitude</label>
<input id="id_nf_lat" name="lat" type="text"
@@ -61,7 +61,7 @@
</div>
</div>
<div class="natus-field">
<div class="sky-field">
<label for="id_nf_tz">Timezone</label>
<input id="id_nf_tz" name="tz" type="text"
placeholder="auto-detected from location"
@@ -71,38 +71,38 @@
</form>
<div id="id_natus_status" class="natus-status"></div>
</div>{# /.natus-form-main #}
<div id="id_sky_status" class="sky-status"></div>
</div>{# /.sky-form-main #}
<button type="button" id="id_natus_confirm" class="btn btn-primary" disabled>
<button type="button" id="id_sky_confirm" class="btn btn-primary" disabled>
Save Sky
</button>
</div>
{# ── Wheel column ────────────────────────────────────────────────── #}
<div class="natus-wheel-col">
<svg id="id_natus_svg" class="natus-svg"></svg>
<div class="sky-wheel-col">
<svg id="id_sky_svg" class="sky-svg"></svg>
</div>
</div>{# /.natus-modal-body #}
</div>{# /.sky-modal-body #}
</div>{# /.sky-page #}
{# Planet hover tooltip — position:fixed escapes any overflow:hidden ancestor #}
<div id="id_natus_tooltip" class="tt" style="display:none;"></div>
<div id="id_natus_tooltip_2" class="tt" style="display:none;"></div>
<div id="id_sky_tooltip" class="tt" style="display:none;"></div>
<div id="id_sky_tooltip_2" class="tt" style="display:none;"></div>
<script src="{% static 'apps/gameboard/d3.min.js' %}"></script>
<script src="{% static 'apps/gameboard/natus-wheel.js' %}"></script>
<script src="{% static 'apps/gameboard/sky-wheel.js' %}"></script>
<script src="{% static 'apps/dashboard/note.js' %}"></script>
<script>
(function () {
'use strict';
const overlay = document.getElementById('id_natus_overlay');
const form = document.getElementById('id_natus_form');
const svgEl = document.getElementById('id_natus_svg');
const statusEl = document.getElementById('id_natus_status');
const confirmBtn = document.getElementById('id_natus_confirm');
const overlay = document.getElementById('id_sky_overlay');
const form = document.getElementById('id_sky_form');
const svgEl = document.getElementById('id_sky_svg');
const statusEl = document.getElementById('id_sky_status');
const confirmBtn = document.getElementById('id_sky_confirm');
const geoBtn = document.getElementById('id_nf_geolocate');
const placeInput = document.getElementById('id_nf_place');
const latInput = document.getElementById('id_nf_lat');
@@ -122,13 +122,13 @@
const PLACE_DELAY = 400;
const CHART_DELAY = 300;
const _preloadReady = NatusWheel.preload();
const _preloadReady = SkyWheel.preload();
// ── Status helper ───────────────────────────────────────────────────────
function setStatus(msg, type) {
statusEl.textContent = msg;
statusEl.className = 'natus-status' + (type ? ` natus-status--${type}` : '');
statusEl.className = 'sky-status' + (type ? ` sky-status--${type}` : '');
}
// ── Nominatim place search ──────────────────────────────────────────────
@@ -167,7 +167,7 @@
results.forEach(place => {
const item = document.createElement('button');
item.type = 'button';
item.className = 'natus-suggestion-item';
item.className = 'sky-suggestion-item';
item.textContent = place.display_name;
item.addEventListener('click', () => selectPlace(place));
suggestions.appendChild(item);
@@ -262,9 +262,9 @@
setStatus('');
confirmBtn.disabled = false;
if (svgEl.querySelector('*')) {
NatusWheel.redraw(data);
SkyWheel.redraw(data);
} else {
NatusWheel.draw(svgEl, data);
SkyWheel.draw(svgEl, data);
}
})
.catch(err => {
@@ -321,7 +321,7 @@
if (_savedSky) {
_lastChartData = _savedSky;
confirmBtn.disabled = false;
NatusWheel.draw(svgEl, _savedSky);
SkyWheel.draw(svgEl, _savedSky);
} else if (_formReady()) {
schedulePreview();
}

View File

@@ -1,48 +1,48 @@
{% load static %}
{# PICK SKY overlay — natal chart entry + D3 wheel preview #}
{# Included in room.html when table_status == "SKY_SELECT" #}
{# Opens when user clicks #id_pick_sky_btn; html.natus-open controls #}
{# Opens when user clicks #id_pick_sky_btn; html.sky-open controls #}
{# visibility via CSS — backdrop-filter blur + centred modal. #}
<div class="natus-backdrop"></div>
<div class="natus-overlay"
id="id_natus_overlay"
data-preview-url="{% url 'epic:natus_preview' room.id %}"
data-save-url="{% url 'epic:natus_save' room.id %}"
<div class="sky-backdrop"></div>
<div class="sky-overlay"
id="id_sky_overlay"
data-preview-url="{% url 'epic:sky_preview' room.id %}"
data-save-url="{% url 'epic:sky_save' room.id %}"
data-sea-partial-url="{% url 'epic:sea_partial' room.id %}"
data-user-seat-role="{{ user_seat_role }}">
<div class="natus-modal-wrap">
<div class="natus-modal">
<div class="sky-modal-wrap">
<div class="sky-modal">
<header class="natus-modal-header">
<header class="sky-modal-header">
<h2>PICK <span>SKY</span></h2>
<p>Enter your birth details to generate your natal chart.</p>
</header>
<div class="natus-modal-body">
<div class="sky-modal-body">
{# ── Form column ──────────────────────────────────────── #}
<div class="natus-form-col">
<div class="sky-form-col">
{# form-main scrolls independently; confirm btn stays pinned below it #}
<div class="natus-form-main">
<form id="id_natus_form" autocomplete="off">
<div class="sky-form-main">
<form id="id_sky_form" autocomplete="off">
<div class="natus-field">
<div class="sky-field">
<label for="id_nf_date">Birth date</label>
<input id="id_nf_date" name="date" type="date" required>
</div>
<div class="natus-field">
<div class="sky-field">
<label for="id_nf_time">Birth time</label>
<input id="id_nf_time" name="time" type="time" value="12:00">
<small>Local time at birth place. Use 12:00 if unknown.</small>
</div>
<div class="natus-field natus-place-field">
<div class="sky-field sky-place-field">
<label for="id_nf_place">Birth place</label>
<div class="natus-place-wrap">
<div class="sky-place-wrap">
<input id="id_nf_place" name="place" type="text"
placeholder="Start typing a city…"
autocomplete="off">
@@ -52,10 +52,10 @@
<i class="fa-solid fa-location-crosshairs"></i>
</button>
</div>
<div id="id_nf_suggestions" class="natus-suggestions" hidden></div>
<div id="id_nf_suggestions" class="sky-suggestions" hidden></div>
</div>
<div class="natus-field natus-coords">
<div class="sky-field sky-coords">
<div>
<label>Latitude</label>
<input id="id_nf_lat" name="lat" type="text"
@@ -68,7 +68,7 @@
</div>
</div>
<div class="natus-field">
<div class="sky-field">
<label for="id_nf_tz">Timezone</label>
<input id="id_nf_tz" name="tz" type="text"
placeholder="auto-detected from location">
@@ -77,42 +77,42 @@
</form>
<div id="id_natus_status" class="natus-status"></div>
</div>{# /.natus-form-main #}
<div id="id_sky_status" class="sky-status"></div>
</div>{# /.sky-form-main #}
<button type="button" id="id_natus_confirm" class="btn btn-primary" disabled>
<button type="button" id="id_sky_confirm" class="btn btn-primary" disabled>
Save Sky
</button>
</div>
{# ── Wheel column ─────────────────────────────────────── #}
<div class="natus-wheel-col">
<svg id="id_natus_svg" class="natus-svg"></svg>
<div class="sky-wheel-col">
<svg id="id_sky_svg" class="sky-svg"></svg>
</div>
</div>{# /.natus-modal-body #}
</div>{# /.sky-modal-body #}
</div>{# /.natus-modal #}
</div>{# /.sky-modal #}
{# NVM: circle btn centered on the top-right corner of the modal #}
<button type="button" id="id_natus_cancel" class="btn btn-cancel btn-sm">NVM</button>
<button type="button" id="id_sky_cancel" class="btn btn-cancel btn-sm">NVM</button>
</div>{# /.natus-modal-wrap #}
</div>{# /.natus-overlay #}
</div>{# /.sky-modal-wrap #}
</div>{# /.sky-overlay #}
<script src="{% static 'apps/gameboard/d3.min.js' %}"></script>
<script src="{% static 'apps/gameboard/natus-wheel.js' %}"></script>
<script src="{% static 'apps/gameboard/sky-wheel.js' %}"></script>
<script>
(function () {
'use strict';
const overlay = document.getElementById('id_natus_overlay');
const form = document.getElementById('id_natus_form');
const svgEl = document.getElementById('id_natus_svg');
const statusEl = document.getElementById('id_natus_status');
const confirmBtn = document.getElementById('id_natus_confirm');
const cancelBtn = document.getElementById('id_natus_cancel');
const overlay = document.getElementById('id_sky_overlay');
const form = document.getElementById('id_sky_form');
const svgEl = document.getElementById('id_sky_svg');
const statusEl = document.getElementById('id_sky_status');
const confirmBtn = document.getElementById('id_sky_confirm');
const cancelBtn = document.getElementById('id_sky_cancel');
const geoBtn = document.getElementById('id_nf_geolocate');
const placeInput = document.getElementById('id_nf_place');
const latInput = document.getElementById('id_nf_lat');
@@ -134,12 +134,12 @@
// Preload zodiac SVG icons eagerly — they'll be cached before any draw() call.
// To swap an icon, replace the .svg file in zodiac-signs/ and hard-refresh.
NatusWheel.preload();
SkyWheel.preload();
// ── localStorage persistence ──────────────────────────────────────────────
// Key scoped to room so multiple rooms don't clobber each other.
const LS_KEY = 'natus-form:' + SAVE_URL;
const LS_KEY = 'sky-form:' + SAVE_URL;
function _saveForm() {
const data = {
@@ -167,8 +167,8 @@
// ── Open / Close ──────────────────────────────────────────────────────────
function openNatus() {
document.documentElement.classList.add('natus-open');
function openSky() {
document.documentElement.classList.add('sky-open');
// If the wheel is empty but the form has enough data (restored from
// localStorage), kick off a fresh preview so the animation plays.
if (!svgEl.querySelector('*') && _formReady()) {
@@ -176,21 +176,21 @@
}
}
function closeNatus() {
document.documentElement.classList.remove('natus-open');
function closeSky() {
document.documentElement.classList.remove('sky-open');
hideSuggestions();
}
const pickSkyBtn = document.getElementById('id_pick_sky_btn');
if (pickSkyBtn) pickSkyBtn.addEventListener('click', openNatus);
cancelBtn.addEventListener('click', closeNatus);
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeNatus(); });
if (pickSkyBtn) pickSkyBtn.addEventListener('click', openSky);
cancelBtn.addEventListener('click', closeSky);
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSky(); });
// ── Status helper ─────────────────────────────────────────────────────────
function setStatus(msg, type) {
statusEl.textContent = msg;
statusEl.className = 'natus-status' + (type ? ` natus-status--${type}` : '');
statusEl.className = 'sky-status' + (type ? ` sky-status--${type}` : '');
}
// ── Nominatim place search ────────────────────────────────────────────────
@@ -229,7 +229,7 @@
results.forEach(place => {
const item = document.createElement('button');
item.type = 'button';
item.className = 'natus-suggestion-item';
item.className = 'sky-suggestion-item';
item.textContent = place.display_name;
item.addEventListener('click', () => selectPlace(place));
suggestions.appendChild(item);
@@ -333,9 +333,9 @@
setStatus('');
confirmBtn.disabled = false;
if (svgEl.querySelector('*')) {
NatusWheel.redraw(data);
SkyWheel.redraw(data);
} else {
NatusWheel.draw(svgEl, data);
SkyWheel.draw(svgEl, data);
}
})
.catch(err => {
@@ -383,7 +383,7 @@
});
});
// ── Sky confirmed → close natus & reload to land on hex w. PICK SEA ──────
// ── Sky confirmed → close sky & reload to land on hex w. PICK SEA ──────
//
// The gamer should witness the table hex (now showing PICK SEA in place of
// PICK SKY) before opting into the sea overlay. We reload the room page —
@@ -391,7 +391,7 @@
// hex's btn flips automatically, and the user clicks PICK SEA to continue.
function _onSkyConfirmed() {
closeNatus();
closeSky();
window.location.reload();
}
@@ -404,7 +404,7 @@
// ── Restore persisted form data ────────────────────────────────────────────
// Called after all functions are defined. Wheel draw is deferred to
// openNatus() so the animation plays when the modal opens, not silently
// openSky() so the animation plays when the modal opens, not silently
// in the background on page load.
// WS: server broadcasts sky_confirmed when any gamer confirms their sky.

View File

@@ -72,14 +72,14 @@
{% include "apps/gameboard/_partials/_sig_select_overlay.html" %}
{% endif %}
{# Natus (Pick Sky) overlay — natal chart entry #}
{# Sky (Pick Sky) overlay — natal chart entry #}
{% if room.table_status == "SKY_SELECT" and not sky_confirmed %}
{% include "apps/gameboard/_partials/_natus_overlay.html" %}
{% include "apps/gameboard/_partials/_sky_overlay.html" %}
{% endif %}
{# Natus tooltip: sibling of .natus-overlay, not inside .natus-modal-wrap (which has transform) #}
{# Sky tooltip: sibling of .sky-overlay, not inside .sky-modal-wrap (which has transform) #}
{% if room.table_status == "SKY_SELECT" and not sky_confirmed %}
<div id="id_natus_tooltip" class="tt" style="display:none;"></div>
<div id="id_natus_tooltip_2" class="tt" style="display:none;"></div>
<div id="id_sky_tooltip" class="tt" style="display:none;"></div>
<div id="id_sky_tooltip_2" class="tt" style="display:none;"></div>
{% endif %}
{# Sea (Pick Sea) overlay — Celtic Cross spread entry #}