1.44 - New Filters, profiles & Mobile fixes

This commit is contained in:
fraxle
2026-05-17 16:01:34 +01:00
parent bc4fd3e6f1
commit 1cd2cd0ab4
11 changed files with 640 additions and 298 deletions
+32 -19
View File
@@ -1,22 +1,26 @@
// ════════════════════════════════════════════════════════════════════════
// utils.js — Pure helper functions and lookup tables.
//
// Exports:
// utciCategory(u) UTCI stress band → {label,bg,fg}
// precipPenalty(precipMm,snowCmH,windMs) SunScope soak-factor
// windCompass8(deg) bearing → {label, snapped}
// uvSplit(uv, elevDeg) total UV → {uvA, uvB}
// SKIN_TYPES Fitzpatrick skin type table
// sunburnMinutes(uv, skinType) minutes to MED
// burnLabel(mins) formats burn time as "12m"/"1.5h"
// VEHICLE_TYPES vehicle presets for cabin heat model
// BUILDING_TYPES building presets for indoor heat model
// cloudCategory(total,low,mid,high) → 'clear'|'wispy'|'scattered'|'overcast'
// confidenceBand(i) day-tab gradient + label
// moonPhaseFraction(date) 0..1 synodic phase
// moonGlyph(p) phase fraction → emoji
// skyGradientForElev(e, isRising) solar elevation + direction → {top,bot} sky gradient
// grassFillForElev(e) solar elevation → {top,bot} hex
// No side-effects, no DOM access, no API calls. All functions are safe
// to call server-side or in tests.
//
// Exports (in order of appearance):
// utciCategory(u) UTCI stress band → {label,bg,fg}
// precipPenalty(precipMm,snowCmH,windMs) SunScope soak-factor (°C penalty)
// windCompass8(deg) bearing → {label, snapped}
// uvSplit(uv, elevDeg) total UV index → {uvA, uvB} estimate
// SKIN_TYPES Fitzpatrick IVI lookup table
// sunburnMinutes(uv, skinType) minutes to MED (sunburn threshold)
// burnLabel(mins) formats burn time as "12m" / "1.5h"
// VEHICLE_TYPES vehicle presets for cabin heat model
// BUILDING_TYPES building presets for indoor heat model
// cloudCategory(total,low,mid,high) → 'clear'|'wispy'|'scattered'|'overcast'
// confidenceBand(i) day-tab gradient colour + label
// moonPhaseFraction(date) 0..1 synodic phase fraction
// moonGlyph(p) phase fraction → moon emoji
// skyGradientForElev(e, isRising) solar elevation → {top,bot} hex pair
// skyFillForElev(e, isRising) convenience single-colour sky fill
// grassFillForElev(e) solar elevation → {top,bot} ground hex
// ════════════════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════════
@@ -269,7 +273,8 @@ export function moonPhaseFraction(date) {
return p;
}
export function moonGlyph(p) {
return ['🌑','🌒','🌓','🌔','🌕','🌖','🌗','🌘'][Math.floor(p*8 + 0.5) % 8];
// +0.5 offset rounds to nearest phase (avoids flickering on exact boundaries)
return ['🌑','🌒','🌓','🌔','🌕','🌖','🌗','🌘'][Math.floor(p * 8 + 0.5) % 8];
}
// ═══════════════════════════════════════════════════════════════════
@@ -339,12 +344,20 @@ export function skyGradientForElev(e, isRising) {
return interpolateSkyKeys(isRising ? SKY_KEYS_RISING : SKY_KEYS_SETTING, e);
}
// Convenience flat colour for contexts needing a single fill.
// ─── skyFillForElev ────────────────────────────────────────────────────────
// Convenience single-colour fill for contexts that don't need a gradient
// (e.g. solid background chips). Returns the horizon (bottom) colour.
export function skyFillForElev(e, isRising = false) {
return skyGradientForElev(e, isRising).bot;
}
// Grass palette — mirrors sky but for the ground strip.
// ═══════════════════════════════════════════════════════════════════
// GRASS FILL — ground-strip colour keyed to solar elevation.
// ───────────────────────────────────────────────────────────────────
// Mirrors the sky palette so the SkyScope disk reads naturally: vivid
// green at noon, amber at golden hour, purple-dark at twilight/night.
// Returns {top, bot} for a subtle two-stop ground gradient.
// ═══════════════════════════════════════════════════════════════════
export function grassFillForElev(e) {
if (e > 60) return { top: '#92cc50', bot: '#5e9630' }; // blazing noon
if (e > 30) return { top: '#86c44a', bot: '#558a2e' }; // bright midday