Add persistent location controls to astronomy page (ZIP, geolocation, manual)
This commit is contained in:
+54
@@ -117,6 +117,44 @@
|
||||
font-size: 0.77rem;
|
||||
}
|
||||
.event-dual span:first-child { color: var(--text); }
|
||||
.loc-wrap {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 0.7rem;
|
||||
margin: 0.2rem 0 1rem;
|
||||
background: #171717;
|
||||
}
|
||||
.loc-head {
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--accent);
|
||||
margin-bottom: 0.45rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.loc-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 0.4rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
.loc-input {
|
||||
background: #111;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
color: var(--text);
|
||||
padding: 0.35rem 0.5rem;
|
||||
font-size: 0.82rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.loc-status {
|
||||
font-size: 0.76rem;
|
||||
color: var(--muted);
|
||||
min-height: 1.1em;
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.loc-row { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -132,6 +170,22 @@
|
||||
<button class="nav-btn" id="nextDay">›</button>
|
||||
</div>
|
||||
|
||||
<div class="loc-wrap">
|
||||
<div class="loc-head">Astronomy Location</div>
|
||||
<div class="loc-row">
|
||||
<input id="locLabel" class="loc-input" type="text" placeholder="Label (Home)" maxlength="64">
|
||||
<input id="locZip" class="loc-input" type="text" placeholder="ZIP" inputmode="numeric" maxlength="10">
|
||||
<button class="nav-btn" id="locUseZip" type="button">Use ZIP</button>
|
||||
</div>
|
||||
<div class="loc-row">
|
||||
<input id="locLat" class="loc-input" type="number" step="0.0001" min="-90" max="90" placeholder="Latitude">
|
||||
<input id="locLon" class="loc-input" type="number" step="0.0001" min="-180" max="180" placeholder="Longitude">
|
||||
<button class="nav-btn" id="locGeo" type="button">Use Current</button>
|
||||
<button class="nav-btn" id="locSave" type="button">Save</button>
|
||||
</div>
|
||||
<div id="locStatus" class="loc-status">Using default location.</div>
|
||||
</div>
|
||||
|
||||
<div class="moon-display">
|
||||
<span class="moon-glyph" id="moonGlyph">🌑</span>
|
||||
<div class="moon-name" id="moonName">—</div>
|
||||
|
||||
@@ -261,15 +261,25 @@ function formatDate(date) {
|
||||
return date.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", timeZone: "UTC" });
|
||||
}
|
||||
|
||||
// Default lat/lon: roughly central US (no geolocation, just reasonable default)
|
||||
// Will use user's timezone offset to pick a reasonable longitude
|
||||
function guessLon() {
|
||||
const offsetHours = -new Date().getTimezoneOffset() / 60;
|
||||
return offsetHours * 15; // rough: UTC offset * 15° per hour
|
||||
function loadLocation() {
|
||||
try {
|
||||
const raw = localStorage.getItem("astro_location");
|
||||
if (!raw) return { label: "Home", lat: 40.0, lon: -75.0 };
|
||||
const d = JSON.parse(raw);
|
||||
const lat = Number(d.lat);
|
||||
const lon = Number(d.lon);
|
||||
if (!Number.isFinite(lat) || !Number.isFinite(lon)) throw new Error("bad loc");
|
||||
return { label: d.label || "Home", lat, lon };
|
||||
} catch {
|
||||
return { label: "Home", lat: 40.0, lon: -75.0 };
|
||||
}
|
||||
}
|
||||
|
||||
const LAT = 40.0; // roughly mid-US latitude
|
||||
const LON = guessLon();
|
||||
function saveLocation(loc) {
|
||||
localStorage.setItem("astro_location", JSON.stringify(loc));
|
||||
}
|
||||
|
||||
let locationState = loadLocation();
|
||||
|
||||
let currentDate = parseDate();
|
||||
|
||||
@@ -281,6 +291,7 @@ function render() {
|
||||
history.replaceState(null, "", newUrl);
|
||||
|
||||
document.getElementById("dateTitle").textContent = formatDate(date);
|
||||
document.getElementById("locStatus").textContent = `${locationState.label}: ${locationState.lat.toFixed(4)}, ${locationState.lon.toFixed(4)}`;
|
||||
|
||||
// Moon
|
||||
const { age, illumination } = moonPhase(date);
|
||||
@@ -304,7 +315,7 @@ function render() {
|
||||
document.getElementById("nextFullGreg").textContent = nextFullDate.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" });
|
||||
|
||||
// Sun
|
||||
const sun = sunriseSunset(date, LAT, LON);
|
||||
const sun = sunriseSunset(date, locationState.lat, locationState.lon);
|
||||
document.getElementById("sunriseSeth").textContent = sun.riseJD ? jdToDecimalTime(sun.riseJD) : "—";
|
||||
document.getElementById("sunriseGreg").textContent = sun.rise || (sun.alwaysUp ? "Midnight sun" : "Below horizon");
|
||||
document.getElementById("sunsetSeth").textContent = sun.setJD ? jdToDecimalTime(sun.setJD) : "—";
|
||||
@@ -360,6 +371,71 @@ function render() {
|
||||
});
|
||||
}
|
||||
|
||||
function initLocationUI() {
|
||||
const labelEl = document.getElementById("locLabel");
|
||||
const zipEl = document.getElementById("locZip");
|
||||
const latEl = document.getElementById("locLat");
|
||||
const lonEl = document.getElementById("locLon");
|
||||
const statusEl = document.getElementById("locStatus");
|
||||
|
||||
labelEl.value = locationState.label || "Home";
|
||||
latEl.value = String(locationState.lat);
|
||||
lonEl.value = String(locationState.lon);
|
||||
|
||||
document.getElementById("locSave").addEventListener("click", () => {
|
||||
const lat = Number(latEl.value);
|
||||
const lon = Number(lonEl.value);
|
||||
if (!Number.isFinite(lat) || !Number.isFinite(lon) || lat < -90 || lat > 90 || lon < -180 || lon > 180) {
|
||||
statusEl.textContent = "Invalid latitude/longitude";
|
||||
return;
|
||||
}
|
||||
locationState = { label: labelEl.value.trim() || "Home", lat, lon };
|
||||
saveLocation(locationState);
|
||||
statusEl.textContent = `Saved: ${locationState.label}`;
|
||||
render();
|
||||
});
|
||||
|
||||
document.getElementById("locUseZip").addEventListener("click", async () => {
|
||||
const zip = (zipEl.value || "").trim();
|
||||
if (!/^\d{5}(-\d{4})?$/.test(zip)) {
|
||||
statusEl.textContent = "Enter valid US ZIP";
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = "Looking up ZIP...";
|
||||
try {
|
||||
const r = await fetch(`https://api.zippopotam.us/us/${encodeURIComponent(zip)}`);
|
||||
if (!r.ok) throw new Error("zip");
|
||||
const data = await r.json();
|
||||
const p = data?.places?.[0];
|
||||
if (!p) throw new Error("zip");
|
||||
latEl.value = String(Number(p.latitude));
|
||||
lonEl.value = String(Number(p.longitude));
|
||||
if (!labelEl.value.trim()) labelEl.value = `${p["place name"]}, ${p["state abbreviation"]}`;
|
||||
statusEl.textContent = "ZIP loaded";
|
||||
} catch {
|
||||
statusEl.textContent = "ZIP lookup failed";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("locGeo").addEventListener("click", () => {
|
||||
if (!navigator.geolocation) {
|
||||
statusEl.textContent = "Geolocation unsupported";
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = "Getting location...";
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
latEl.value = String(pos.coords.latitude.toFixed(6));
|
||||
lonEl.value = String(pos.coords.longitude.toFixed(6));
|
||||
if (!labelEl.value.trim()) labelEl.value = "Current Location";
|
||||
statusEl.textContent = "Location loaded";
|
||||
},
|
||||
() => { statusEl.textContent = "Location denied/unavailable"; },
|
||||
{ enableHighAccuracy: true, timeout: 10000 }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("prevDay").addEventListener("click", () => {
|
||||
currentDate = stepDate(currentDate, -1);
|
||||
render();
|
||||
@@ -369,4 +445,5 @@ document.getElementById("nextDay").addEventListener("click", () => {
|
||||
render();
|
||||
});
|
||||
|
||||
initLocationUI();
|
||||
render();
|
||||
|
||||
Reference in New Issue
Block a user