Small tweaks
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
File diff suppressed because one or more lines are too long
@@ -12,20 +12,48 @@
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
export function checkStargazing(rows) {
|
||||
// Great stargazing: mostly clear night hours with low cloud
|
||||
// Great stargazing: mostly clear night hours with low cloud.
|
||||
// Split night into contiguous runs (pre-dawn vs post-dusk) and evaluate
|
||||
// each independently so a cloudy evening doesn't suppress a clear morning
|
||||
// (or vice versa), and so the icon is pinned to only the clear hours.
|
||||
const nightRows = rows.filter(r => r.elev < -5);
|
||||
if (nightRows.length < 3) return null;
|
||||
const avgCloud = nightRows.reduce((s, r) => s + r.cc, 0) / nightRows.length;
|
||||
if (avgCloud > 30) return null;
|
||||
|
||||
// Build contiguous runs — a gap > 2 h means we've crossed daytime.
|
||||
const runs = [];
|
||||
let cur = [nightRows[0]];
|
||||
for (let i = 1; i < nightRows.length; i++) {
|
||||
const dtDiff = (new Date(nightRows[i].iso) - new Date(nightRows[i - 1].iso)) / 3600000;
|
||||
if (dtDiff <= 2) { cur.push(nightRows[i]); }
|
||||
else { runs.push(cur); cur = [nightRows[i]]; }
|
||||
}
|
||||
runs.push(cur);
|
||||
|
||||
// Evaluate each run; among those passing ≤30% avg cloud prefer the evening
|
||||
// run (actionable for tonight) then fall back to whichever is clearest.
|
||||
let bestRun = null, bestAvg = Infinity;
|
||||
for (const run of runs) {
|
||||
if (run.length < 2) continue;
|
||||
const avg = run.reduce((s, r) => s + r.cc, 0) / run.length;
|
||||
if (avg > 30) continue;
|
||||
const isEvening = parseInt(run[0].iso.slice(11, 13), 10) >= 12;
|
||||
if (!bestRun || (isEvening && parseInt(bestRun[0].iso.slice(11, 13), 10) < 12) || avg < bestAvg) {
|
||||
bestRun = run; bestAvg = avg;
|
||||
}
|
||||
}
|
||||
if (!bestRun) return null;
|
||||
|
||||
const isPreDawn = parseInt(bestRun[0].iso.slice(11, 13), 10) < 12;
|
||||
return {
|
||||
id: 'stargazing',
|
||||
emoji: '⭐',
|
||||
title: 'Great Stargazing Tonight',
|
||||
message: `Clear skies expected overnight at ${nightRows.length} hours with average ${Math.round(avgCloud)}% cloud — ideal conditions for stargazing.`,
|
||||
title: isPreDawn ? 'Great Stargazing Overnight' : 'Great Stargazing Tonight',
|
||||
message: `Clear skies expected for ${bestRun.length} hours overnight with average ${Math.round(bestAvg)}% cloud — ideal conditions for stargazing.`,
|
||||
color: '#080e1a',
|
||||
textColor: '#c8dcff',
|
||||
type: 'weather',
|
||||
nightOnly: true,
|
||||
isoRange: [bestRun[0].iso, bestRun[bestRun.length - 1].iso],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+15
-2
@@ -18,9 +18,22 @@
|
||||
}
|
||||
},
|
||||
"2026-06-11": {
|
||||
"visits": 28,
|
||||
"visits": 31,
|
||||
"profiles": {
|
||||
"showall": 1
|
||||
"showall": 11,
|
||||
"custom": 9,
|
||||
"alltemps": 1,
|
||||
"vehicle": 3,
|
||||
"home": 4,
|
||||
"basic": 5,
|
||||
"farming": 1
|
||||
}
|
||||
},
|
||||
"2026-06-12": {
|
||||
"visits": 1,
|
||||
"profiles": {
|
||||
"showall": 1,
|
||||
"basic": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>scope cell</title>
|
||||
<link rel="stylesheet" href="./assets/css/header.css" />
|
||||
<style>
|
||||
html,body { margin:0; height:100%; background: transparent; overflow: hidden; }
|
||||
#cell { display:flex; align-items:center; justify-content:center; height:100%; }
|
||||
.scope-ring { width: 200px; height: 200px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="cell"></div>
|
||||
<script type="module">
|
||||
import { h, render } from './assets/vendor/preact.js';
|
||||
const { ScopeReticle } = await import('./assets/js/components.js?v=' + Date.now());
|
||||
|
||||
const q = new URLSearchParams(location.search);
|
||||
const n = (k, d) => { const v = q.get(k); return v == null ? d : Number(v); };
|
||||
|
||||
// hour drives the rising/setting palette (UTC hour < 12 => sunrise tints).
|
||||
const dt = new Date(Date.UTC(2026, 5, 6, n('hour', 12), 0, 0));
|
||||
const cat = { label: 'Comfortable', bg: '#90d090', fg: '#157a15' };
|
||||
const storm = q.get('storm') === '1';
|
||||
|
||||
render(h(ScopeReticle, {
|
||||
value: 12.4, cat, loading: false,
|
||||
elev: n('elev', 30), dt, glob: n('glob', 400),
|
||||
activeEvent: storm ? { id: 'storm' } : null,
|
||||
cc: n('cc', 0), ccLow: n('ccLow', 0), ccMid: n('ccMid', 0), ccHigh: n('ccHigh', 0),
|
||||
precip: n('precip', 0), snow: n('snow', 0), visKm: n('vis', 30),
|
||||
env: q.get('env') || 'open',
|
||||
wind: n('wind', 0), gust: n('gust', 0), wd: q.get('wd') != null ? n('wd', 0) : null,
|
||||
}), document.getElementById('cell'));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,61 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>SunScope — Ground Lab (dev)</title>
|
||||
<style>
|
||||
body { margin: 0; background: #1a140c; font-family: Manrope, system-ui, sans-serif; color: #f5edd6; }
|
||||
h1 { text-align: center; font-weight: 700; font-size: 18px; padding: 14px 0 2px; opacity: 0.8; }
|
||||
p.note { text-align:center; opacity:0.45; font-size:12px; margin:0 0 10px; }
|
||||
.row-label { text-align:center; font-size:12px; font-weight:700; opacity:0.6; margin:14px 0 2px; letter-spacing:1px; text-transform:uppercase; }
|
||||
.grid { display: grid; grid-template-columns: repeat(8, 150px); gap: 6px 8px; justify-content: center; padding: 0 12px; }
|
||||
.cell { display: flex; flex-direction: column; align-items: center; }
|
||||
.label { font-size: 11px; font-weight: 700; opacity: 0.85; }
|
||||
iframe { width: 150px; height: 150px; border: 0; background: transparent; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SunScope — Ground Lab</h1>
|
||||
<p class="note">dev preview · per-environment grounds across the cycle</p>
|
||||
<div id="lab"></div>
|
||||
|
||||
<script>
|
||||
const envs = ['open','forest','alpine','urban','beach','openwater','desert','river'];
|
||||
const envName = { open:'Grass', forest:'Forest', alpine:'Alpine', urban:'City', beach:'Beach', openwater:'Open Water', desert:'Desert', river:'River' };
|
||||
|
||||
// [rowLabel, base query]
|
||||
const rows = [
|
||||
['Midday · clear', 'elev=45&hour=12&glob=800&cc=0&vis=30'],
|
||||
['Sunset · clear', 'elev=2&hour=19&glob=110&cc=10&vis=30'],
|
||||
['Night · clear', 'elev=-15&hour=23&glob=0&cc=10&vis=30'],
|
||||
['Midday · overcast','elev=45&hour=12&glob=300&cc=95&ccLow=90&ccMid=70&vis=18'],
|
||||
['Day · rain', 'elev=40&hour=12&glob=220&cc=98&ccLow=92&ccMid=75&precip=5&vis=6'],
|
||||
['Day · snow', 'elev=30&hour=12&glob=200&cc=95&ccLow=90&snow=1.2&vis=4'],
|
||||
];
|
||||
|
||||
const lab = document.getElementById('lab');
|
||||
for (const [rowLabel, q] of rows) {
|
||||
const rl = document.createElement('div');
|
||||
rl.className = 'row-label';
|
||||
rl.textContent = rowLabel;
|
||||
lab.appendChild(rl);
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'grid';
|
||||
for (const env of envs) {
|
||||
const cell = document.createElement('div');
|
||||
cell.className = 'cell';
|
||||
const lbl = document.createElement('span');
|
||||
lbl.className = 'label';
|
||||
lbl.textContent = envName[env];
|
||||
const ifr = document.createElement('iframe');
|
||||
ifr.src = `./scope-cell?${q}&env=${env}&_=${Date.now()}`;
|
||||
ifr.loading = 'eager';
|
||||
cell.append(lbl, ifr);
|
||||
grid.append(cell);
|
||||
}
|
||||
lab.appendChild(grid);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user