PICK SKY: natal wheel polish — house/sign fill fixes, button layout, localStorage FT
- Fix D3 arc coordinate offset (add π/2 to all arc angles — D3 subtracts it internally, causing fills to render 90° CW from label midpoints) - Fix house-12 wrap-around: normalise nextCusp += 360 when it crosses 0°, eliminating the 330° ghost arc that buried house fill/number layers - Draw all house fills before cusp lines + numbers (z-order fix) - SCSS: sign/element fills corrected to rgba(var(--priXx, R, G, B), α) — CSS vars are raw RGB tuples so bare var() in fill was invalid - brighten Stone/Air/Water fallback colours; raise house fill opacities - Button layout: SAVE SKY moves into form column (full-width, pinned bottom); NVM becomes a btn-sm circle anchored on the modal's top-right corner via .natus-modal-wrap (position:relative, outside overflow:hidden modal); entrance animation moved to wrapper so NVM rides the fade+slide - Form fields wrapped in .natus-form-main (scrollable); portrait layout switches form-col to flex-row so form spans most width, SAVE SKY on right - Modal max-height 92→96vh, max-width 840→920px, SVG cap 400→480px - FT: PickSkyLocalStorageTest (2 tests) — form fields restored after NVM and after page refresh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
data-preview-url="{% url 'epic:natus_preview' room.id %}"
|
||||
data-save-url="{% url 'epic:natus_save' room.id %}">
|
||||
|
||||
<div class="natus-modal-wrap">
|
||||
<div class="natus-modal">
|
||||
|
||||
<header class="natus-modal-header">
|
||||
@@ -21,57 +22,66 @@
|
||||
|
||||
{# ── Form column ──────────────────────────────────────── #}
|
||||
<div class="natus-form-col">
|
||||
<form id="id_natus_form" autocomplete="off">
|
||||
|
||||
<div class="natus-field">
|
||||
<label for="id_nf_date">Birth date</label>
|
||||
<input id="id_nf_date" name="date" type="date" required>
|
||||
</div>
|
||||
{# form-main scrolls independently; confirm btn stays pinned below it #}
|
||||
<div class="natus-form-main">
|
||||
<form id="id_natus_form" autocomplete="off">
|
||||
|
||||
<div class="natus-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">
|
||||
<label for="id_nf_place">Birth place</label>
|
||||
<div class="natus-place-wrap">
|
||||
<input id="id_nf_place" name="place" type="text"
|
||||
placeholder="Start typing a city…"
|
||||
autocomplete="off">
|
||||
<button type="button" id="id_nf_geolocate"
|
||||
class="btn btn-secondary btn-sm"
|
||||
title="Use device location">
|
||||
<i class="fa-solid fa-location-crosshairs"></i>
|
||||
</button>
|
||||
<div class="natus-field">
|
||||
<label for="id_nf_date">Birth date</label>
|
||||
<input id="id_nf_date" name="date" type="date" required>
|
||||
</div>
|
||||
<div id="id_nf_suggestions" class="natus-suggestions" hidden></div>
|
||||
</div>
|
||||
|
||||
<div class="natus-field natus-coords">
|
||||
<div>
|
||||
<label>Latitude</label>
|
||||
<input id="id_nf_lat" name="lat" type="text"
|
||||
placeholder="—" readonly tabindex="-1">
|
||||
<div class="natus-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>
|
||||
<label>Longitude</label>
|
||||
<input id="id_nf_lon" name="lon" type="text"
|
||||
placeholder="—" readonly tabindex="-1">
|
||||
|
||||
<div class="natus-field natus-place-field">
|
||||
<label for="id_nf_place">Birth place</label>
|
||||
<div class="natus-place-wrap">
|
||||
<input id="id_nf_place" name="place" type="text"
|
||||
placeholder="Start typing a city…"
|
||||
autocomplete="off">
|
||||
<button type="button" id="id_nf_geolocate"
|
||||
class="btn btn-secondary btn-sm"
|
||||
title="Use device location">
|
||||
<i class="fa-solid fa-location-crosshairs"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="id_nf_suggestions" class="natus-suggestions" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="natus-field">
|
||||
<label for="id_nf_tz">Timezone</label>
|
||||
<input id="id_nf_tz" name="tz" type="text"
|
||||
placeholder="auto-detected from location">
|
||||
<small id="id_nf_tz_hint"></small>
|
||||
</div>
|
||||
<div class="natus-field natus-coords">
|
||||
<div>
|
||||
<label>Latitude</label>
|
||||
<input id="id_nf_lat" name="lat" type="text"
|
||||
placeholder="—" readonly tabindex="-1">
|
||||
</div>
|
||||
<div>
|
||||
<label>Longitude</label>
|
||||
<input id="id_nf_lon" name="lon" type="text"
|
||||
placeholder="—" readonly tabindex="-1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<div class="natus-field">
|
||||
<label for="id_nf_tz">Timezone</label>
|
||||
<input id="id_nf_tz" name="tz" type="text"
|
||||
placeholder="auto-detected from location">
|
||||
<small id="id_nf_tz_hint"></small>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<div id="id_natus_status" class="natus-status"></div>
|
||||
</div>{# /.natus-form-main #}
|
||||
|
||||
<button type="button" id="id_natus_confirm" class="btn btn-primary" disabled>
|
||||
Save Sky
|
||||
</button>
|
||||
|
||||
<div id="id_natus_status" class="natus-status"></div>
|
||||
</div>
|
||||
|
||||
{# ── Wheel column ─────────────────────────────────────── #}
|
||||
@@ -81,14 +91,12 @@
|
||||
|
||||
</div>{# /.natus-modal-body #}
|
||||
|
||||
<footer class="natus-modal-footer">
|
||||
<button type="button" id="id_natus_cancel" class="btn btn-cancel">NVM</button>
|
||||
<button type="button" id="id_natus_confirm" class="btn btn-primary" disabled>
|
||||
Save Sky
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
</div>{# /.natus-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>
|
||||
|
||||
</div>{# /.natus-modal-wrap #}
|
||||
</div>{# /.natus-overlay #}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
|
||||
@@ -122,10 +130,44 @@
|
||||
const PLACE_DELAY = 400; // ms — Nominatim polite rate
|
||||
const CHART_DELAY = 300; // ms — chart preview debounce
|
||||
|
||||
// ── localStorage persistence ──────────────────────────────────────────────
|
||||
// Key scoped to room so multiple rooms don't clobber each other.
|
||||
|
||||
const LS_KEY = 'natus-form:' + SAVE_URL;
|
||||
|
||||
function _saveForm() {
|
||||
const data = {
|
||||
date: document.getElementById('id_nf_date').value,
|
||||
time: document.getElementById('id_nf_time').value,
|
||||
place: placeInput.value,
|
||||
lat: latInput.value,
|
||||
lon: lonInput.value,
|
||||
tz: tzInput.value,
|
||||
};
|
||||
try { localStorage.setItem(LS_KEY, JSON.stringify(data)); } catch (_) {}
|
||||
}
|
||||
|
||||
function _restoreForm() {
|
||||
let data;
|
||||
try { data = JSON.parse(localStorage.getItem(LS_KEY) || 'null'); } catch (_) {}
|
||||
if (!data) return;
|
||||
if (data.date) document.getElementById('id_nf_date').value = data.date;
|
||||
if (data.time) document.getElementById('id_nf_time').value = data.time;
|
||||
if (data.place) placeInput.value = data.place;
|
||||
if (data.lat) latInput.value = data.lat;
|
||||
if (data.lon) lonInput.value = data.lon;
|
||||
if (data.tz) { tzInput.value = data.tz; tzHint.textContent = 'Auto-detected from coordinates.'; }
|
||||
}
|
||||
|
||||
// ── Open / Close ──────────────────────────────────────────────────────────
|
||||
|
||||
function openNatus() {
|
||||
document.documentElement.classList.add('natus-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()) {
|
||||
schedulePreview();
|
||||
}
|
||||
}
|
||||
|
||||
function closeNatus() {
|
||||
@@ -199,6 +241,7 @@
|
||||
latInput.value = parseFloat(place.lat).toFixed(4);
|
||||
lonInput.value = parseFloat(place.lon).toFixed(4);
|
||||
hideSuggestions();
|
||||
_saveForm();
|
||||
schedulePreview();
|
||||
}
|
||||
|
||||
@@ -219,7 +262,8 @@
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => { placeInput.value = _cityName(data.address) || data.display_name || ''; })
|
||||
.catch(() => {});
|
||||
.catch(() => {})
|
||||
.finally(() => _saveForm());
|
||||
setStatus('');
|
||||
schedulePreview();
|
||||
},
|
||||
@@ -242,6 +286,7 @@
|
||||
// Trigger on date / time / tz changes (coords come via selectPlace / geolocation)
|
||||
form.addEventListener('input', (e) => {
|
||||
if (e.target === placeInput) return; // place triggers via selectPlace
|
||||
_saveForm();
|
||||
clearTimeout(_chartDebounce);
|
||||
_chartDebounce = setTimeout(schedulePreview, CHART_DELAY);
|
||||
});
|
||||
@@ -336,5 +381,12 @@
|
||||
const m = document.cookie.match(/csrftoken=([^;]+)/);
|
||||
return m ? m[1] : '';
|
||||
}
|
||||
|
||||
// ── 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
|
||||
// in the background on page load.
|
||||
|
||||
_restoreForm();
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user